playMaker

Author Topic: String Typewriter  (Read 8787 times)

LogLady

  • Full Member
  • ***
  • Posts: 125
    • View Profile
Re: String Typewriter
« Reply #15 on: January 05, 2015, 04:10:17 PM »
Nice to know that rich text is working!  :)

I don't care if it is new or old as long as it works.
I'm getting old but still working ^^

Thanks for helping us all.

Lane

  • Administrator
  • Hero Member
  • *****
  • Posts: 2484
  • Yup.
    • View Profile
    • Cleverous
Re: String Typewriter
« Reply #16 on: January 06, 2015, 06:24:44 AM »
r5 Update and Patch Notes on first post.
  • Rich Text Support. (limited)
  • Special mid-sentence pause and speed modifiers.
  • Bugfixes.

LogLady

  • Full Member
  • ***
  • Posts: 125
    • View Profile
Re: String Typewriter
« Reply #17 on: January 06, 2015, 09:53:56 AM »
Here the rich text tags are working fine on editor or at runtime but the </p> is displayed on both (the <p=x> is not displayed) but works bringing back the text speed to normal. <s=0.01> works but </s> not. I don't know if it is supposed to work like this or if I have to set back to normal using <s=0.2> (if it is the normal speed that I'm using). Do you plan / is it possible to add support to change the font in the middle of sentence? Do you plan / is it possible to add support to when pressing "enter" ignore the type writing and display the whole formatted string? For this I tried changing the Pause value to 0 while typewriting but it just makes it go faster not instantly.

Thanks!
« Last Edit: January 06, 2015, 10:00:14 AM by LogLady »

Lane

  • Administrator
  • Hero Member
  • *****
  • Posts: 2484
  • Yup.
    • View Profile
    • Cleverous
Re: String Typewriter
« Reply #18 on: January 06, 2015, 09:56:06 AM »
There is no closer for <s=[float]> or <p=[float]>. Speed is changed until further notice, and pause is simply executed where you put it.

There isn't a tag for changing the font, so it isn't supported.

Lane

  • Administrator
  • Hero Member
  • *****
  • Posts: 2484
  • Yup.
    • View Profile
    • Cleverous
Re: String Typewriter
« Reply #19 on: January 06, 2015, 10:33:02 AM »
Do you plan / is it possible to add support to when pressing "enter" ignore the type writing and display the whole formatted string? For this I tried changing the Pause value to 0 while typewriting but it just makes it go faster not instantly.

It's necessary for the string to be built in sequence so it can trim out the special speed and pause tags. I could reorganize the code, but it would take time and testing. Technically <s=0.0> should make it output near-instant. I'll look into why that isn't working. Probably also necessary to toggle sound off it the pause gap is zero to avoid stacking tons of sounds horribly...

LogLady

  • Full Member
  • ***
  • Posts: 125
    • View Profile
Re: String Typewriter
« Reply #20 on: January 06, 2015, 10:43:34 AM »
I understand.

Maybe if you expose the speed variable like the pause variable so it could be related to a float and when pressing "enter" or any other determined key it would set the speed variable to 0.0. What do you think?

Lane

  • Administrator
  • Hero Member
  • *****
  • Posts: 2484
  • Yup.
    • View Profile
    • Cleverous
Re: String Typewriter
« Reply #21 on: January 06, 2015, 11:04:05 AM »
I agree it would be nice to have something for input. A key dropdown choice would not work for anything other than PC users and also wouldn't support any kind of control remapping. This would need to be a named Input Axis or even a Bool trigger. In the meantime you can use a couple of actions and states to handle this for you.

I'll need to reorganize the code anyway it seems, because if you force finish the state then it doesn't clean up the tags in the string. I'm using it a lot for this 1GAM so I'll update it as I work through.


LogLady

  • Full Member
  • ***
  • Posts: 125
    • View Profile
Re: String Typewriter
« Reply #22 on: January 06, 2015, 11:19:29 AM »
No, no. The key press thing doesn't need to be supported by the typewritter by any means. That's why I proposed to expose the speed variable so it could be changed using another fsm that listen for the typing process and change the value of the speed var when the user press a determined key so any input could be used.

FritsLyn

  • Full Member
  • ***
  • Posts: 191
    • View Profile
Re: String Typewriter
« Reply #23 on: March 30, 2015, 02:21:11 PM »
This is absolutely awesome, thank you!

I have one request: <top>

It should erase the array and start writing from 'the top', continuing reading the string provided.

So for example, if this very comment was to be written out, and we only had like 1-2 lines of text to write it in, then every sentence<top>

could be cropped off / like starting over in the top, and the writer would start over<top>

every time it came to that 'tag'.<top>

Thank you!!

calculmentor

  • Playmaker Newbie
  • *
  • Posts: 10
    • View Profile
Re: String Typewriter
« Reply #24 on: April 09, 2015, 05:11:32 AM »
perfect!
works with TexmeshPro (beta) and Unity5
thx you!
n.
calcul mentor

  *designer*

FritsLyn

  • Full Member
  • ***
  • Posts: 191
    • View Profile
Re: String Typewriter
« Reply #25 on: April 16, 2015, 03:12:55 AM »
OK, my hero Esben S optimized this code, so now you can write <clear> and the previous textly written is deleted / you start on 'a fresh page'.

Should probably be used with a pause in most circumstances.

Thanks Esben ;)

****
Code: [Select]
// (c) Copyright HutongGames, LLC 2010-2015. All rights reserved.
/*--- __ECO__ __ACTION__ ---*/

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

namespace HutongGames.PlayMaker.Actions
{
[ActionCategory(ActionCategory.String)]
[Tooltip("Automatically types into a string.")]
public class StringTypewriter : FsmStateAction
{
[RequiredField]
[UIHint(UIHint.TextArea)]
[Tooltip("The string with the entire message to type out.")]
public FsmString baseString;

[RequiredField]
[UIHint(UIHint.Variable)]
[Tooltip("The target result string (can be the same as the base string).")]
public FsmString resultString;

[Tooltip("The time between letters appearing.")]
public FsmFloat pause;

[Tooltip("When punctuation is encountered then pause is multiplied by this.\n(period, exclamation, question, comma, semicolon, colon and ellipsis).\nIt also handles repeating characters and pauses only one time at their end.")]
public FsmFloat punctuationMultiplier;

[Tooltip("True is realtime: continues typing while game is paused. False will subject time variable to the game's timeScale.")]
public FsmBool realtime;

[Tooltip("Support <color><b><i><size> use. \n LIMITATION: Cannot stack formats together yet! eg <b><i>Text</i></b> won't work. \n Pause: <p=0.9> for a 0.9 second pause (mid sentance pause). \n Speed: <s=1.5> changes Pause to 1.5 (time between characters).")]
public bool richText;

[Tooltip("Send this event when finished typing.")]
public FsmEvent finishEvent;

[UIHint(UIHint.Description)]
public string d1 = "     Optional Sounds Section:";

[Tooltip("Check this to play sounds while typing.")]
public bool useSounds;

[Tooltip("Check this to not play a sound when it is a spacebar character.")]
public bool noSoundOnSpaces;

[ObjectType(typeof(AudioClip))]
[Tooltip("The sound to play for each letter typed.")]
public FsmObject typingSound;

[Tooltip("The GameObject with an AudioSource component.")]
public FsmOwnerDefault audioSourceObj;

// Time data
float p = 0.0f;
float startTime;
float timer = 0.0f;

// Character data
char[] punctuation = {'.', '!', '?', ',', ';', ':'};
string message = "";
int index = 0;
char lastChar;
char nextChar;

// Rich Text formatting
private string block;
private string suffix;
private bool fBold = false;
private bool fItal = false;
private bool fSize = false;
private bool fColor = false;
private float forcedPause;

// Audio
private AudioSource audioSource;
private AudioClip sound;

public override void Reset()
{
// --- Basic ---
baseString = null;
resultString = null;
pause = 0.05f;
punctuationMultiplier = 10.0f;
realtime = false;
richText = true;
finishEvent = null;

// --- Sounds ---
useSounds = false;
noSoundOnSpaces = true;
typingSound = null;
audioSourceObj = null;
}

public override void OnEnter()
{
// sort out the sound stuff
if (useSounds){
var go = Fsm.GetOwnerDefaultTarget(audioSourceObj);
if (go != null){
audioSource = go.GetComponent<AudioSource>();
if (audioSource == null){
Debug.LogError ("String Typewriter Action reports: The <color=#ffa500ff>AudioSource component</color> was not found! Does the target object have an Audio Source component?");
useSounds = false;
}

sound = typingSound.Value as AudioClip;
if (sound == null){
Debug.LogError ("String Typewriter Action reports: The <color=#ffa500ff>AudioClip</color> was not found!");
useSounds = false;
}
}

else {
Debug.LogError ("String Typewriter Action reports: The <color=#ffa500ff>target Game Object</color> for the audio source was not found!");
useSounds = false;
}
}

index = 0;
message = baseString.Value; // clone the base string.
resultString.Value = ""; // clear the target string.
startTime = Time.realtimeSinceStartup; // get the actual time since the game started.
}

// Here in OnUpdate we handle...
// 1) Pausing between letters
// 2) Checking for punctuation marks
// 3) Identifying Rich Text Formatting
public override void OnUpdate()
{
// Check if the string is complete
if (message == resultString.Value)
{
DoFinish();
}

// If the string is not complete, continue work
else
{
p = pause.Value; // clone the pause variable in OnUpdate in case it is changed by the user at runtime.

nextChar = message[index];

// fetch/compare the characters to see if they exist in the punctuation array or not
int _iLast = Array.IndexOf (punctuation, lastChar); // get last index
int _iNext = Array.IndexOf (punctuation, nextChar); // get next index

// compare the result
bool _lastIsMark = _iLast != -1; // if index is not -1, there is a punctuation mark.
bool _nextIsMark = _iNext != -1; // if index is not -1, there is a punctuation mark.

if (_lastIsMark)
{
// if the next char is a punctuation mark then we should not pause.
if (!_nextIsMark)
{
pause.Value = (p * punctuationMultiplier.Value);
}
}

// if we run into a format opener, we should process the block!
if (richText && message[index] == '<')
{
DoRichText();
}

if (realtime.Value)
{
// check the current time minus the previous Typing event time.
// if that's more than the pause gap, then its time for another character.
if (Time.realtimeSinceStartup - startTime >= ((forcedPause != 0) ? forcedPause : pause.Value))
{
DoTyping();
}
}

if (!realtime.Value)
{
// add delta time until its equal or greater than the pause gap.
timer += Time.deltaTime;
if (timer >= ((forcedPause != 0) ? forcedPause : pause.Value))
{
DoTyping();
}
}

// done with pausing, so revert the pause in case it was changed for punctuation.
// this also catches the speed change from the <s=[float]> format
// for the <p=[float]> format this
pause.Value = p;
}
}

// Here in DoTyping we handle...
// 1) Playing sound
// 2) Adding text to the message
// 3) Iterating the character index value
// 4) Resetting the timer and getting time
public void DoTyping()
{
// play the sound if enabled
if (useSounds)
{
if (noSoundOnSpaces && message[index] != ' ')
{
audioSource.PlayOneShot (sound);
}

else
{
audioSource.PlayOneShot (sound);
}
}

// build the display string
if (richText)
{
// TODO //
// This needs to have support for organzizing the openers with the closers.
// If the openers are <b><i><color>... then the closers must be organized as </color><i><b> instead of the fixed arrangement below.
suffix = (fBold ? "</b>" : "") + (fItal ? "</i>" : "") + (fSize ? "</size>" : "") + (fColor ? "</color>" : "");

// add one character to the string, and the suffix.
resultString.Value = message.Substring(0, index) + (message[index] + suffix);
}

if (!richText)
{
resultString.Value += message[index]; // add one character to the string
}

lastChar = message[index]; // store the index that we just typed
index++; // iterate the index

timer = 0.0f; // reset timer
startTime = Time.realtimeSinceStartup; // update realtime
}

public void DoRichText()
{
//reset the forced pause here because it should only be used for one character.
forcedPause = 0.0f;

block = "";
int blockStartPoint = index;

// Construct the <block>
while (index < message.Length)
{
block += message[index];
index++;

if (message[index] == '>')
{
block += message[index];
index = index+1;
break;
}
}

block = block.ToLower();

if (block.Contains("/")) // block is an closer, disable the flag for the suffix builder since it isn't necessary anymore.
{
if (block.Contains ("</c")){
fColor = false; return;}
if (block.Contains ("</s")){
fSize = false; return;}
if (block.Contains ("</i")){
fItal = false; return;}
if (block.Contains ("</b")){
fBold = false; return;}
}

if (!block.Contains("/")) // block is an opener (or special), tell the suffix to make a closer for it OR catch the speed/pause change.
{
if (block.Contains ("<clear")){
message = message.Substring(index);
index = 0;
return;
}

if (block.Contains ("<color")){
fColor = true; return;}
if (block.Contains ("<size")){
fSize = true; return;}
if (block.Contains ("<i")){
fItal = true; return;}
if (block.Contains ("<b")){
fBold = true; return;}
if (block.Contains ("<s="))
{
int i = 3;
string speed = "";
while (i < block.Length)
{
speed += block[i];
i++;

if (block[i] == '>')
{
p = float.Parse(speed);
pause.Value = p;

string front = message.Substring(0, blockStartPoint);
string back = message.Substring(blockStartPoint+block.Length, (message.Length - front.Length - block.Length));

index = blockStartPoint;
message = front+back;

break;
}
}
}

if (block.Contains ("<p="))
{
int i = 3;
string pause = "";
while (i < block.Length)
{
pause += block[i];
i++;

if (block[i] == '>')
{
float pa = float.Parse(pause);
forcedPause += pa;

string front = message.Substring(0, blockStartPoint);
string back = message.Substring(blockStartPoint+block.Length, (message.Length - front.Length - block.Length));

index = blockStartPoint;
message = front+back;

break;
}
}
}
}
}

public void DoFinish()
{
Finish();
if (finishEvent != null)
{
Fsm.Event(finishEvent);
}
}

public override void OnExit()
{
// if the state exits before finishing the string
// then it needs to be auto-completed.
resultString.Value = message;
}
}
}

mod edit: added code tags for post readability :)
« Last Edit: April 16, 2015, 03:54:37 AM by Lane »

AdamJ

  • Playmaker Newbie
  • *
  • Posts: 11
    • View Profile
Re: String Typewriter
« Reply #26 on: April 29, 2015, 01:13:26 PM »
Hey guys,

Does this work at all with the 4.6+ UI 'canvas' System in unity? I can't seem to get it to work.

I've been using that for my game so far. This would be a nice touch that for my game but I don't really want to learn the old system just to use it.

Thanks :)

Lane

  • Administrator
  • Hero Member
  • *****
  • Posts: 2484
  • Yup.
    • View Profile
    • Cleverous
Re: String Typewriter
« Reply #27 on: April 29, 2015, 01:19:47 PM »
It works, you just have to use Set Property or one of the uGUI actions to apply the output string to the UI Text element.

AdamJ

  • Playmaker Newbie
  • *
  • Posts: 11
    • View Profile
Re: String Typewriter
« Reply #28 on: April 29, 2015, 01:33:58 PM »
Ah okay i'll have a play with that then, thanks for the speedy reply!

Just out of curiosity what or where are the uGUI actions? I don't see anything by that name on my actions list. Pretty sure I have the latest version of Playmaker too?

Lane

  • Administrator
  • Hero Member
  • *****
  • Posts: 2484
  • Yup.
    • View Profile
    • Cleverous
Re: String Typewriter
« Reply #29 on: April 29, 2015, 01:38:14 PM »
They don't ship with the vanilla package, you need to download Ecosystem to access the custom actions repository.