playMaker

Author Topic: New String Typewriter  (Read 1778 times)

Pivetta

  • Playmaker Newbie
  • *
  • Posts: 16
    • View Profile
New String Typewriter
« on: January 07, 2016, 09:47:21 AM »
Hi, I just made some little improvements on the typewriter script (I'm still looking forward to use /n to get into a new line, If I manage to do it I will send an update)

I'm not practical with Git so I will just send you the code (I'm not even sure everything is correct)

What does it do more than the standard String Typewriter:

-set the Index where to start
-store the Index
-send an event when something is being typed (like the sound it can exclude spaces)

Code: [Select]
// (c) Copyright HutongGames, LLC 2010-2015. All rights reserved.
/*--- __ECO__ __PLAYMAKER__ __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 NewStringTypewriter : 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 text index to start from")]
public FsmInt startIndex;

[UIHint(UIHint.Variable)]
public FsmInt storeIndex;

[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("Send event on sound")]
public bool isSoundEvent;

[Tooltip("Where to send the event.")]
public FsmEventTarget eventTarget;

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

[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;
startIndex = 0;
storeIndex = null;

// --- Sounds ---
isSoundEvent = false;
soundEvent = null;
eventTarget = null;
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 = startIndex.Value;
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);
}
}

if (isSoundEvent)
{
if (noSoundOnSpaces && message[index] != ' ')
{
Fsm.Event(eventTarget, soundEvent);
}

else
{
Fsm.Event(eventTarget, soundEvent);
}
}

// 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
storeIndex.Value = 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 ("<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;
}
}
}

It's a few small changes, but I felt the function was to less general, so I packed inside what I needed.

Cheers

Pivetta

Pivetta

  • Playmaker Newbie
  • *
  • Posts: 16
    • View Profile
Re: New String Typewriter
« Reply #1 on: January 07, 2016, 10:02:11 AM »
Ok the /n newlines are already being read, I was using Split text to Array list to get a txt file into a FsmString variable, and now I modified it to look for a 'ยง' charachter instead of a newline, It's cheap but it works :)

Pivetta

  • Playmaker Newbie
  • *
  • Posts: 16
    • View Profile
Re: New String Typewriter
« Reply #2 on: January 07, 2016, 11:37:30 AM »
Update:

Now you can send also the finish event to another Fsm:

Code: [Select]
// (c) Copyright HutongGames, LLC 2010-2015. All rights reserved.
/*--- __ECO__ __PLAYMAKER__ __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 NewStringTypewriter : 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 text index to start from")]
public FsmInt startIndex;

[UIHint(UIHint.Variable)]
public FsmInt storeIndex;

[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;

[Tooltip("Where to send the event.")]
public FsmEventTarget eventTarget;

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

[Tooltip("Send event on sound")]
public bool isSoundEvent;

[Tooltip("Where to send the event.")]
public FsmEventTarget soundTarget;

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

[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;
startIndex = 0;
storeIndex = null;

// --- Sounds ---
isSoundEvent = false;
soundEvent = null;
eventTarget = null;
soundTarget = null;
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 = startIndex.Value;
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);
}
}

if (isSoundEvent)
{
if (noSoundOnSpaces && message[index] != ' ')
{
Fsm.Event(soundTarget, soundEvent);
}

else
{
Fsm.Event(soundTarget, soundEvent);
}
}

// 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
storeIndex.Value = 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 ("<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(eventTarget, finishEvent);
}
}

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

The action is working even if it's always displaying:
'Event not used by this state or any global transition!'

(screenshot in the attachments)



jeanfabre

  • Administrator
  • Hero Member
  • *****
  • Posts: 14698
  • Official Playmaker Support
    • View Profile
Re: New String Typewriter
« Reply #3 on: January 12, 2016, 11:36:30 PM »
Hi,

 Thanks.

 For convenience, I strongly suggest you use the Ecosystem to publish your actions, PlayMaker members will be able to find it and get it within their project a lot quicker that way.

Here's a video on the quickest way to publish on the Ecosystem.


Bye,

 Jean