Playmaker Forum
PlayMaker Updates & Downloads => Share New Actions => Topic started by: Lane on December 01, 2014, 05:15:55 PM
-
Now you can type fancy text out like the old school games used to without all the trouble it used to be!
Jean helped me start this one out.. There was a base script on the Unity Wiki called AutoType, but it didn't really fit in the Playmaker action space with its Iterator so we circumvented it.
Recommened to download via
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:
- Feature: Rich Text Support!.. (http://docs.unity3d.com/Manual/StyledText.html) Standard <color><b><i><size> plus <p=[float]> and <s=[float]>.
- ....Limitations:
- Cannot use more than one type of modifier at a time. This means you can't have <b><i>text</i></b>, you can only have <b> or <i> to modify a block of text, but you could use any number of them individually anywhere in the string. Just not together (yet).
- Feature: Special mid-sentence pause and mid-sentence speed change.
- ....Usage/examples:
- " Hi Steve.<p=1.8> So.. Um, how are you? "
... <p=1.8> Will produce a pause of 1.8 seconds mid-sentence.
- " Uhhhh yeaaaah the BBQ at Dekkers is <s=0.01>soamazingomgwtfbbq i need moar nao lol lets go. "
... <s=0.01> Will produce a speed change to a very fast rate, mid-sentence.
- Known bug: You might notice formatting can get displayed in the text while in the Editor, but it should clear and format correctly at Runtime. This is because the Editor does not recogize the special <s=[float]> and <p=[float]> (speed/pause) blocks and they are physically removed from the string while its being built at Runtime.
- Known Bug: Using <s=[float]> or <p=[float]> and transitioning states before the action is finished will not cleanup the tags.
- Bugfix: Lots of small adjustments were made all over the code. It shouldn't affect your existing action definitions but backup before updating anyway just in case
- Please report any issues related to these features along with the string you're typing.
r4:
- Bugfix: Last letter was truncated if firing the finished event from the action. String is now compared to the target rather than reading the index value and the Update loop is gated by the finished conditions to avoid out of index errors.
r3:
- Bugfix: Failed on an index error when looping through states.
r2:
- Punctuation works now. Use the multiplier to define how much longer it waits in addition to the standard pause.
- Formatting punctuation is a bit outside normal grammer, rather than punctuating explicitly on each mark or ellipsis it will instead see if the next character is a punctuation mark and if so, not pause for punctuation. This lets you string together question marks combined with exclamations and stuff without a punctuation pause at each one - only the last one. See the new GIF below.
r1:
- You need a base string for reference and it will type it out from the base string into any target string variable you want (even the original base variable if you prefer, its uncorrupted if you choose to do this.)
- You can throttle the speed at which it sprays letters into the string with a float variable.
- You can choose Realtime or not, so it can work with your pause menu's that operate during timeScale=0, if you prefer.
- If your state is interrupted, the result string is immediately filled on exiting the state so your strings are not corrupted.
- You have the option add sounds on every key (choose an Audio Source and an AudioClip), and an additional option to skip playing the sound on Spaces.
Let me know if you have any problems or feature requests.
Animated GIF below![/list]
-
Nice! :) Thank you Lane!
-
Hi,
it's compatible with 3.x :)
Bye,
Jean
-
This is cool! Thank you!
-
Updated with Punctuation features.
-
r3: bugfix, error when looping back into an original state. out of index.
-
Wow, it's an impressive action!
Thanks guyz!
-
Awesome, thanks!
-
It looks like the last letter isn't written if you use the finished event.
-
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.
-
Yup, problem solved! I haven't posted much but I was watching this thread. Thanks for the PM though!
-
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.
-
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.
-
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.
-
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..
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.
-
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.
-
r5 Update and Patch Notes on first post.
- Rich Text Support. (limited)
- Special mid-sentence pause and speed modifiers.
- Bugfixes.
-
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!
-
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.
-
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...
-
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?
-
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.
-
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.
-
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!!
-
perfect!
works with TexmeshPro (beta) and Unity5
thx you!
n.
-
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 ;)
****
// (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 :)
-
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 :)
-
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.
-
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?
-
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.
-
Oh right okay, thanks allot! :)
-
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.
-
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! :)
-
Hmm.. I think it would be best if you gave it a reversed string instead of rewriting the whole typewriter to invert its process.
-
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!
-
Probably you need to reverse it twice - once before putting it into the typewriter and then again the output String created by the typewriter?
-
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?
-
It outputs a string, you need to configure whatever text component you're sending it to to look like you want.
-
Thank you, I got it now working! Silly enough, I used GUIlayout instead of GUI which is why I couldn't change position.
-
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?
-
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.
-
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)