Playmaker Forum

PlayMaker Updates & Downloads => Share New Actions => Topic started by: Lane on December 01, 2014, 05:15:55 PM

Title: String Typewriter
Post by: Lane on December 01, 2014, 05:15:55 PM
Ecosystem, but the .cs is attached below as well.

Git Link (https://github.com/jeanfabre/PlayMakerCustomActions_U3/blob/master/Assets/PlayMaker%20Custom%20Actions/String/StringTypewriter.cs)
Download the .cs file (http://hutonggames.com/playmakerforum/index.php?action=dlattach;topic=8870.0;attach=6088)


r5:
... <p=1.8> Will produce a pause of 1.8 seconds mid-sentence.
... <s=0.01> Will produce a speed change to a very fast rate, mid-sentence.


r4:

r3:

r2:

r1:


Let me know if you have any problems or feature requests.

Animated GIF below![/list]
Title: Re: String Typewriter
Post by: mdotstrange on December 01, 2014, 07:45:26 PM
Nice!  :) Thank you Lane!
Title: Re: String Typewriter
Post by: jeanfabre on December 02, 2014, 12:00:20 AM
Hi,

 it's compatible with 3.x :)

 Bye,

Jean
Title: Re: String Typewriter
Post by: marcos on December 02, 2014, 12:53:00 AM
This is cool! Thank you!
Title: Re: String Typewriter
Post by: Lane on December 02, 2014, 02:41:41 PM
Updated with Punctuation features.
Title: Re: String Typewriter
Post by: Lane on December 03, 2014, 09:34:46 AM
r3: bugfix, error when looping back into an original state. out of index.
Title: Re: String Typewriter
Post by: Sly on December 03, 2014, 12:20:07 PM
Wow, it's an impressive action!
Thanks guyz!
Title: Re: String Typewriter
Post by: terri on December 03, 2014, 12:54:28 PM
Awesome, thanks!
Title: Re: String Typewriter
Post by: fish on December 21, 2014, 09:38:06 PM
It looks like the last letter isn't written if you use the finished event.
Title: Re: String Typewriter
Post by: Lane on December 22, 2014, 04:50:01 PM
It looks like the last letter isn't written if you use the finished event.

Found it... Changed things up to process and finish under more controlled circumstances. Updated on this Thread and Ecosystem.

Should be fixed now, let me know.
Title: Re: String Typewriter
Post by: fish on December 22, 2014, 07:29:22 PM
Yup, problem solved! I haven't posted much but I was watching this thread. Thanks for the PM though!
Title: Re: String Typewriter
Post by: LogLady on January 05, 2015, 02:12:44 PM
Following this post: http://hutonggames.com/playmakerforum/index.php?topic=9118.0

I *suggest* that we could load multiple bitmap font textures (textures with everything placed in a pre-determined order, like this: https://crimild.files.wordpress.com/2013/08/lucidagrande.jpg), get the corresponding character from the texture and bake it to another texture. This way we could have characters with different colors and fonts on each texture map. Recognize tags to change the bitmap font texture before displaying it on screen (to avoid displaying the tags before it gets formatted while blitting). Choose the speed of the blit and how many characters are blitted on each time.
Title: Re: String Typewriter
Post by: Lane on January 05, 2015, 03:02:07 PM
String Typewriter will output a string, one character at a time, at a defined time interval. There is no need for this action to extrapolate an alphabet atlas from an image or convert anything at all. You should use String Typewriter to construct the string over time and simply feed that into a GUI Text or UI Text where you have already setup the font.

Blitting in this context is an archaic method used to display text on old consoles like Atari because there were no better methods available like the one we're using here.
Title: Re: String Typewriter
Post by: LogLady on January 05, 2015, 04:19:33 PM
I made one string typewritter and had the same problem with tags even the html tags for rich text format. The tag is drawn while typewritting and them gets formatted after the closing tag just like the problem occuring with the string typewritter action. I've googled but couldn't find anything about typewritting and rich text format or how to cache it. I tried this too:

Used this tag "<@>" to change the font color to red and them replace the tag with an empty space. This works almost fine because everything that was written before gets red too and I just want to change the color of the words inside the tag, not before. So until now the arcaic text blitting appears to offer better control over the text formatting than the better method available today.
Title: Re: String Typewriter
Post by: Lane on January 05, 2015, 05:57:49 PM
I made one string typewritter and had the same problem with tags even the html tags for rich text format. The tag is drawn while typewritting and them gets formatted after the closing tag just like the problem occuring with the string typewritter action. I've googled but couldn't find anything about typewritting and rich text format or how to cache it.

This is a known issue, since the current version doesn't support Rich Text..

Quote
So until now the arcaic text blitting appears to offer better control over the text formatting than the better method available today.

Well then its a good thing I sat down and updated the action to support Rich Text this morning so we don't have to regress 20 years. ;)

There are couple of limitations with the latest code, but I think they can be iterated out over time and the support as it stands should be enough for basic formatting uses. I'll post more in a few hours. Currently adding the pausing and speeding up formatting codes and cleanup so you can control the Action variables from inside the string.
Title: Re: String Typewriter
Post by: LogLady on January 05, 2015, 06: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.
Title: Re: String Typewriter
Post by: Lane on January 06, 2015, 08:24:44 AM
r5 Update and Patch Notes on first post.
Title: Re: String Typewriter
Post by: LogLady on January 06, 2015, 11: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!
Title: Re: String Typewriter
Post by: Lane on January 06, 2015, 11: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.
Title: Re: String Typewriter
Post by: Lane on January 06, 2015, 12:33:02 PM
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...
Title: Re: String Typewriter
Post by: LogLady on January 06, 2015, 12:43:34 PM
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?
Title: Re: String Typewriter
Post by: Lane on January 06, 2015, 01:04:05 PM
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.

Title: Re: String Typewriter
Post by: LogLady on January 06, 2015, 01:19:29 PM
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.
Title: Re: String Typewriter
Post by: FritsLyn on March 30, 2015, 05: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!!
Title: Re: String Typewriter
Post by: calculmentor on April 09, 2015, 08:11:32 AM
perfect!
works with TexmeshPro (beta) and Unity5
thx you!
n.
Title: Re: String Typewriter
Post by: FritsLyn on April 16, 2015, 06: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 :)
Title: Re: String Typewriter
Post by: AdamJ on April 29, 2015, 04: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 :)
Title: Re: String Typewriter
Post by: Lane on April 29, 2015, 04: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.
Title: Re: String Typewriter
Post by: AdamJ on April 29, 2015, 04: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?
Title: Re: String Typewriter
Post by: Lane on April 29, 2015, 04:38:14 PM
They don't ship with the vanilla package, you need to download Ecosystem (https://hutonggames.fogbugz.com/default.asp?W1181) to access the custom actions repository.
Title: Re: String Typewriter
Post by: AdamJ on April 29, 2015, 05:13:38 PM
Oh right okay, thanks allot!  :)
Title: Re: String Typewriter
Post by: AdamJ on April 29, 2015, 06:08:18 PM
Its working nicely apart from 1 issue. When i type <p=x> It doesn't pause, instead it changes the speed, the same as the <s=x> which is still functioning as intended.
Title: Re: String Typewriter
Post by: Mutajon on November 20, 2015, 11:24:15 AM
First, a huge thanks for this immensely useful action! 8)

Second, two questions:

1. Can this somehow work with uGui (canvas text)? If so, how?

2. Can someone perhaps help point me in how can I costume change this to work exactly as it does now, only to type the string from the last char to the first one? i.e. typing the string in reverse order?
I tried changing the starting index to 'message.Length' (instead of 0), and then change all the 'index++' to 'index--'. But I get an error in the OnUpdate() that array index is out of range...

Thanks a ton to anyone who can help!  :)

Title: Re: String Typewriter
Post by: Lane on November 20, 2015, 12:24:16 PM
Hmm..  I think it would be best if you gave it a reversed string instead of rewriting the whole typewriter to invert its process.
Title: Re: String Typewriter
Post by: Mutajon on November 21, 2015, 06:40:34 AM
Hmmm... But I want the string to be normal at the end of the process. So something like this (each row here represents adding the next char to the string, just from end to beginning):
!
P!
OP!
TOP!
STOP!

But if I reverse the string I'll get !POTS instead of STOP!
Title: Re: String Typewriter
Post by: phannDOTde on November 21, 2015, 07:13:27 AM
Probably you need to reverse it twice - once before putting it into the typewriter and then again the output String created by the typewriter?
Title: Re: String Typewriter
Post by: Just on June 07, 2016, 06:19:53 AM
Thank you for this, it works fine for me! Expect it seems I can't to change position of text, it always print out for upper left corner. Am I missing something really obvious to change position?
Title: Re: String Typewriter
Post by: Lane on June 08, 2016, 01:06:24 PM
It outputs a string, you need to configure whatever text component you're sending it to to look like you want.
Title: Re: String Typewriter
Post by: Just on June 08, 2016, 04:54:39 PM
Thank you, I got it now working! Silly enough, I used GUIlayout instead of GUI which is why I couldn't change position.
Title: Re: String Typewriter
Post by: Alatriste on August 16, 2017, 01:00:26 PM
Hi,

I'm using the String Typewriter effect to write some texts in a iGUI text box. However what happens many times is that the words close to the limit of the border of the box get cut and jump to the next line. The effect is not nice and I was wondering whether there is away to improve it.

For instance, if I write a sentence. "Hello, This is a test" but the text box is smaller than the whole text, what happens is that I'll see this: "Hello, This is a te" in the line, and right after "Hello, This is a (in the first line) test (in the second line).

Does it make sense? "Test" will start writing in the first line, but because there is not enough space, it will jump to the second line.

Edit: I have been investigating and aparently there is a script that makes something similar in the templates of TextMeshPro. Is there anyway to implement it to the existing Typewriter effect?
Title: Re: String Typewriter
Post by: FractalCore on October 10, 2017, 11:10:44 PM
I came across this problem recently too.

A lot of RPG/adventure games have the dialog display in this manner, rather than just all appearing at once.

I guess it makes it feel more like a non-voiced character is actually saying it.

If anyone has a way to fix it for this action would be great.
Title: Re: String Typewriter
Post by: Gustav on October 13, 2017, 03:19:20 PM
Everyone looking for a solution have a look at the corresponding threads:

String Typewriter - Cutting words (http://hutonggames.com/playmakerforum/index.php?topic=15621.0)

Typewriter Textmesh Pro without cutting words (http://hutonggames.com/playmakerforum/index.php?topic=15820.0)