Game Development Community

dev|Pro Game Development Curriculum

GUITypeWriter for TorqueX

by Josh Butterworth · 07/06/2007 (5:34 pm) · 1 comments

Here's a simple custom GUI control you can use to make a cool type writer like effect on your screens, it takes text in as a list of lines, then writes out each one in turn. Sorry there aren't more comments, I'll come back and tidy it up in a bit.

//-----------------------------------------------------------------------------
// Component Written by Josh Butterworth www.grassrootsgames.co.uk
// Notes: 
// Make sure your textStyle.SizeToText property equals false or the multiline
// feature will not work
//-----------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;

using PointF = Microsoft.Xna.Framework.Vector2;
using RectF = GarageGames.Torque.MathUtil.RectangleF;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using GarageGames.Torque.Core;
using GarageGames.Torque.GFX;
using GarageGames.Torque.MathUtil;
using GarageGames.Torque.Sim;
using GarageGames.Torque.GUI;

namespace SpaceShooter.GUI.CustomControls
{
    /// <summary>
    /// Called when the GUITypeWriter has finished typing
    /// </summary>
    public delegate void OnTypeWriterFinished();

    /// <summary>
    /// Renders a string to the GUICanvas.
    /// </summary>
    public class GUITypeWriter : GUIControl, IAnimatedObject
    {
        //======================================================
        #region Public properties, operators, constants, and enums


        /// <summary>
        /// The Character displayed after the current text to show
        /// the text is being written Default none
        /// </summary>
        public char Cursor
        {
            get { return _cursor; }
            set { _cursor = value; }
        }

        /// <summary>
        /// The current character in the sequence, a number between 0
        /// and the length of the string
        /// </summary>
        public int CurrentChar
        {
            get { return _currentChar; }
            set { _currentChar = value; }
        }

        /// <summary>
        /// The full text to be written
        /// </summary>
        public List<string> FullText
        {
            get { return _fullText; }
            set { _fullText = value; }
        }

        /// <summary>
        /// The height of each line, for best results set to slightly larger than your font size
        /// Default 20
        /// </summary>
        public float LineHeight
        {
            get { return _lineHeight; }
            set { _lineHeight = value; }
        }

        /// <summary>
        /// The Numbers of characters to add per step. This is the only way to make the typewriter faster
        /// than it's default settings. Default 1
        /// </summary>
        public int CharactersPerStep
        {
            get { return _charactersPerStep; }
            set { _charactersPerStep = value; }
        }

        /// <summary>
        /// Defines length of time the cursor should blink on/off for
        /// Default 0.25f
        /// </summary>
        public float BlinkDelay
        {
            get { return _blinkDelay; }
            set { _blinkDelay = value; }
        }
	

        /// <summary>
        /// Defines Whether or not the Cursor should blink on and off
        /// Default False
        /// </summary>
        public bool BlinkCursor
        {
            get { return _blinkCursor; }
            set { _blinkCursor = value; }
        }
	

        /// <summary>
        /// Defines the time between the GUI screen being activated and TypeWriter starting
        /// Default 0
        /// </summary>
        public float StartDelay
        {
            get { return _startDelay; }
            set { _startDelay = value; }
        }
	

        /// <summary>
        /// Defines an extra delay between each step, this will add on to the 0.03 defined by the 
        /// ProcessList callback. Default 0
        /// </summary>
        public float StepDelay
        {
            get { return _stepDelay; }
            set { _stepDelay = value; }
        }

        /// <summary>
        /// Pauses the component, this is useful if you want to trigger a type writer to start at
        /// a certain event.
        /// </summary>
        public bool Paused
        {
            get { return _paused; }
            set { _paused = value; }
        }
        
        /// <summary>
        /// Sets a method to run when the type writer finishes typing
        /// </summary>
        public OnTypeWriterFinished OnTypeWriterFinishedDelegate
        {
            get { return _finishedDelegate; }
            set { _finishedDelegate = value; }
        }
        
        /// <summary>
        /// Opacity of the text to be written, default 255 (max)
        /// </summary>
        public byte Opacity
        {
            get { return _opacity; }
            set { _opacity = value; }
        }

        /// <summary>
        /// The current text on the screen
        /// </summary>
        public string Text
        {
            get { return _text; }
            set
            {
                if (_style == null)
                    return;

                Assert.Fatal(!_style.Font.IsNull, "GUIText::Text: Font resource is not valid!");
                if (_style.Font.IsNull)
                    return;

                _text = value;

                if (_style.SizeToText)
                    SetSize(new SizeF(_style.Font.Instance.MeasureString(_text), _style.Font.Instance.Height + 4.0f));
            }
        }

        #endregion

        //======================================================
        #region Public methods

        public override void OnRender(PointF offset, RectF updateRect)
        {
            DrawUtil.ClearBitmapModulation();

            RectF ctrlRect = new RectF(offset, _bounds.Size);

            // fill the update rect with the fill color
            if (_style.IsOpaque)
                DrawUtil.RectFill(ctrlRect, _style.FillColor);

            // if there's a border, draw the border
            if (_style.HasBorder)
                DrawUtil.Rect(ctrlRect, _style.BorderColor);

            float yPos;
            
            //Create a new color using the Opacity parameter, this allows parent controls to manage the opacity of this one
            Color thisColor = new Color(_style.TextColor.R, _style.TextColor.G, _style.TextColor.B, _opacity);

            for (int i = 0; i < _lineNo; i++)
            {
                yPos = (_lineHeight * (i + 1)) - (_bounds.Size.Y/2);

                //Console.WriteLine(i + " " + Position.Y + " - " + (_bounds.Size.Y) + " + " + (_lineHeight * (i))+ " = " + yPos);
                string thisLine = _fullText[i];

                DrawUtil.JustifiedText(_style.Font, new PointF(0.0f, yPos), _bounds.Size, _style.Alignment, thisColor, thisLine);
            }

            yPos = (_lineHeight * (_lineNo + 1)) - (_bounds.Size.Y / 2);
            DrawUtil.JustifiedText(_style.Font, new PointF(0.0f, yPos), _bounds.Size, _style.Alignment, thisColor, _text);

            // render the child controls
            _RenderChildControls(offset, updateRect);
        }

        #region IAnimatedObject Members

        public void UpdateAnimation(float dt)
        {
            if (!_paused)
            {
                UpdateCursor(dt);

                UpdateText(dt);
            }
        }

        private void UpdateText(float dt)
        {
            if (_startDelay > 0)
            {
                _startDelay -= dt;
            }
            else
            {
                _startDelay += _stepDelay;
                string thisLine = _fullText[_lineNo];

                if (_currentChar < thisLine.Length)
                {
                    _currentChar += _charactersPerStep;

                    //Check the new addition doesnt send the character marker over the length of the string
                    if (_currentChar > thisLine.Length) _currentChar = thisLine.Length;

                    //Create the new line of text
                    Text = thisLine.Substring(0, _currentChar);

                    //If the Cursor is supposed to be visible add it to the new string
                    if (_cursorVisible) Text += _cursor;
                }
                else
                {
                    if (_lineNo < _fullText.Count - 1)
                    {
                        _text = "";
                        _lineNo++;
                        _currentChar = 0;

                    }
                    else
                    {
                        //We're done.
                        if (_finishedDelegate != null)
                            _finishedDelegate();
                    }
                }

            }
        }

        private void UpdateCursor(float dt)
        {
            //If the cursor is supposed to blink then manage its visible state according to time past.
            if (_blinkCursor)
            {
                if (_cursorTimer > 0)
                {
                    _cursorTimer -= dt;
                }
                else
                {
                    _cursorTimer += _blinkDelay;
                    _cursorVisible = _cursorVisible ? _cursorVisible = false : _cursorVisible = true;
                }
            }
        }
        
        /// <summary>
        /// Skip the typewriting method and just write all the lines, assign this method to an input.
        /// </summary>
        public void Skip()
        {
            _text = "";
            _lineNo = _fullText.Count -1;
            _currentChar = 0;

            if (_finishedDelegate != null)
                _finishedDelegate();
        }

        #endregion

        //======================================================
        #region Private, protected, internal methods

        protected override bool _OnNewStyle(GUIStyle style)
        {
            _style = (style as GUITextStyle);

            Assert.Fatal(_style != null, "GUIText::_OnNewStyle: control was assigned an invalid style!");
            if (_style == null || !base._OnNewStyle(style))
                return false;

            if (_style.SizeToText && _text != String.Empty)
                SetSize(new SizeF(_style.Font.Instance.MeasureString(_text), _style.Font.Instance.Height + 4.0f));

            return true;
        }

        protected override bool _OnWake()
        {
            if (!base._OnWake())
                return false;

            _currentChar = 0;
            
            ProcessList.Instance.AddAnimationCallback(this);

            return true;
        }

        protected override void _OnSleep()
        {
            ProcessList.Instance.RemoveObject(this);
            base._OnSleep();
        }

        #endregion

        //======================================================
        #region Private, protected, internal fields

        string _text = "";
        private List<string> _fullText;
        
        private int _currentChar = 15;
        private char _cursor = '|';
        private float _lineHeight = 20;
        private int _charactersPerStep = 1;
        private float _stepDelay = 0f;
        private float _startDelay = 0;
        private bool _blinkCursor = true;
        private float _blinkDelay = 0.25f;
        private bool _paused = false;
        private byte _opacity = 255;	

        int _lineNo = 0;
        bool _cursorVisible = true;
        float _cursorTimer = 0;
        GUITextStyle _style = null;

        private OnTypeWriterFinished _finishedDelegate;
	

        #endregion

        #endregion
    }
}


So to initialise one you'd write something like this:

List<string> note = new List<string>();
            note.Add("NOTE: Manual weapon switching is enabeled for the turret system.");  
            note.Add("This will allow the pilot to initiate a magazine change in realtime ");
            note.Add("while engaged in combat, without the need for activating the ");
            note.Add("ordinance assignment system.");
            
            _arrowNoteText = new GUITypeWriter();
            _arrowNoteText.Style = _textStyle;
            _arrowNoteText.FullText = note;
            _arrowNoteText.Position = new Vector2(650, 525);
            _arrowNoteText.Size = new SizeF(400, 250);
            _arrowNoteText.LineHeight = 16.0f;
            _arrowNoteText.StartDelay = 5.0f;
            _arrowNoteText.StepDelay = 0.01f;
            _arrowNoteText.CharactersPerStep = 1;
            _arrowNoteText.Visible = true;
            _arrowNoteText.Folder = this;


Quick Tip:
To insert a 'wait' period in your text you can use a character that doesnt appear in your character map (the prime symbol works well for this).

Future Work:
Add an optional sound effect to the typing effect for extra impact, I had this working but removed it to make sure it was generic enough for everyones uses.

About the author

Recent Blogs


#1
07/09/2007 (2:56 pm)
Nice Josh! Can't wait to check this out.