Making Robocto: Scripting an opening sequence

Making Robocto: Scripting an opening sequence

What do you want from an opening sequence?  In my opinion, it should be a fairly short 1 or 2 minute animation that hooks you on what a game has to offer.  Note: not all games need an opening sequence.  If your game isn’t very story-driven, and the controls are simple enough to understand from a loading screen, then you might just annoy gamers by adding an opening sequence.  Robocto is a story driven game, and requires explanation of the controls, so I want to:

  • Introduce players to the environment

  • Get them started moving around

 

Here is a video of the full opening sequence.

Here is the full code for my DemoManager and TextManager classes.

I created a class called DemoManager and made it a Singleton.  A singleton is a class that you’ll only use one instance of.  It’s a convenient design pattern that I use for all of my Manager classes (AnimalManager, ResourceManager, DemoManager, etc.).  It lets me access this one DemoManager from anywhere in code.

    // Singleton
    public class DemoManager : MonoBehaviour
    {
        public static DemoManager instance = null;  //Static instance of DemoManager which allows it to be accessed by any other script.
      ...
    }

        public void Awake() {
            //Check if instance already exists
            if (instance == null) {
                //if not, set instance to this
                instance = this;
                // initialize class variables here
            }
            //If instance already exists and it's not this:
            else if (instance != this) {
                //Then destroy this. This enforces our singleton pattern, meaning there can only ever be one instance of a MenuManager.
                Destroy(gameObject);
            }
        }

    Like pretty much all classes in a Unity game, DemoManager is a MonoBehaviour.  Monobehaviors allow you to define Awake, Start, and Update functions.  There are other functions, but these are the most commonly used.

    Awake is called first during initialization.  This is where we initialize variables.  Start is called after awake.  It’s used to set the stage after all other modules have been initialized, before the update loop starts.  My DemoManager won’t need Start or Update functions because it won’t be changing throughout the game, but rather it’ll be called at specific times, like when players first launch Robocto, and when they choose to rewatch the controls demo from a menu button.

    So I’ve split the opening animation into 2 parts: PlayGameIntro() and PlayControlsExplanation().  When players launch the game they’ll see the game intro followed by the controls explanation.  If they launch it from the menu, they’ll just see the controls explanation.

        public IEnumerator StartDemo(DemoState demoState) {
            isDemoActive = true;
            isDemoWaitingOnUser = false;
            den.EnableCollider(false);
            skipDemoButtonGO.SetActive(true);

            if(robocto.NVhasCrawler) {
                // make crawler invisible while we're at den
                //Crawler crawler = robocto.crawlerGO.GetComponent<Crawler>();
                RCCAICarController crawlerController = robocto.crawlerGO.GetComponent<RCCAICarController>();
                crawlerWasStopped = crawlerController.Stopped;
                crawlerController.Stopped = true;
                robocto.crawlerGO.transform.localScale = new Vector3(0f, 0f, 0f);
            }
            StoreRoboctoState();
            if(DemoState.NEW_GAME == demoState) {
                MoveRoboctoInsideDen();
                //First show Robocto being printed
                yield return StartCoroutine(PlayGameIntro());
            }
            else {
                MoveRoboctoToControlsDemoStart();
            }
            yield return StartCoroutine(PlayControlsExplanation());

            FinishDemo();
        }

        private void StoreRoboctoState() {
            originalRoboctoPosition.Set (robocto.transform.position.x, robocto.transform.position.y, robocto.transform.position.z);
            originalRoboctoRotation.Set (robocto.transform.rotation.x, robocto.transform.rotation.y, robocto.transform.rotation.z, robocto.transform.rotation.w);
            originalControlState = robocto.controlState;
            for (int i = 0; i < MechSpiderController.NUM_BONES; i++) {
                originalBonesRotation [i].Set (robocto.bones [i].localRotation.x, 
                                               robocto.bones [i].localRotation.y, 
                                               robocto.bones [i].localRotation.z, 
                                               robocto.bones [i].localRotation.w);
            }
        }

        private void RestoreRoboctoState() {
            robocto.transform.position = originalRoboctoPosition;
            robocto.transform.rotation = originalRoboctoRotation;
            robocto.controlState = originalControlState;
            for (int i = 0; i < MechSpiderController.NUM_BONES; i++) {
                robocto.bones [i].localRotation.Set (originalBonesRotation [i].x, 
                                                     originalBonesRotation [i].y, 
                                                     originalBonesRotation [i].z, 
                                                     originalBonesRotation [i].w);
            }
        }

    I start out by storing Robocto’s state so I can snap him back here after the demo is done.  I record his body position, body rotation, and leg bone rotations.  I then call MoveRoboctoInsideDen to set him where he needs to be for the start of the demo.  This function does the opposite of RestoreRoboctoState which we’ll call later to bring Robocto back to where he was before the demo.

    I want Robocto to start inside the den so he can be printed out.  I use the den’s position and rotation to set Robocto’s position and rotation and then straighten out Robocto’s legs by setting their local rotation.  This sets their rotation relative to Robocto’s root bone which is the egg-shaped body.  I set the localRotation not the rotation here,  because I want the legs to point down from the body which is different from down in world space, since Robocto is tilted to match the den’s tilt.

        private const float PRINT_ROBOCTO_TIME_S = 10f;
        private const float RETRACT_ROBOCTO_TIME_S = 2f;
        private const float PRINTED_DISTANCE_M = 2.8f;
        public IEnumerator PlayGameIntro() {
            // Print robocto slowly pushing out of den
            float elapsedTime = 0f;
            float printRoboctoPercentDone = 0f;
            Vector3 printedPosition = new Vector3(0f, 0f, 0f);
            printedPosition = den.transform.position + (den.transform.forward * PRINTED_DISTANCE_M);

            yield return new WaitForSeconds(2f);

            while (elapsedTime < PRINT_ROBOCTO_TIME_S) {
                printRoboctoPercentDone = elapsedTime / PRINT_ROBOCTO_TIME_S;
                robocto.transform.position = Vector3.Lerp(den.transform.position, printedPosition, printRoboctoPercentDone);
                
                elapsedTime += Time.deltaTime;
                yield return new WaitForEndOfFrame ();
            }

            // Retract Robocto back into den at docked distance, intro text “Hello, Robocto. I am your den.  I just printed you into existence.”
            elapsedTime = 0f;
            float retractRoboctoPercentDone = 0f;
            while (elapsedTime < RETRACT_ROBOCTO_TIME_S) {
                retractRoboctoPercentDone = elapsedTime / RETRACT_ROBOCTO_TIME_S;
                
                robocto.transform.position = Vector3.Lerp(printedPosition, 
                                                           den.transform.position + (den.transform.forward * MechSpiderController.DOCKED_DISTANCE_FROM_DEN_M), 
                                                           retractIntoDenCurve.Evaluate(retractRoboctoPercentDone));
                
                elapsedTime += Time.deltaTime;
                yield return new WaitForEndOfFrame ();
            }

            yield return StartCoroutine(textManager.MakeDenSay("DenHello"));
            yield return StartCoroutine(textManager.MakeDenSay("DenIAm"));
            yield return StartCoroutine(textManager.MakeDenSay("DenICanPrint"));

            StartCoroutine(textManager.MakeDenSay("DenYouCan"));

            // Undock and swim towards center of cove  "You can swim, walk, and hide."
            robocto.controlState = ControlState.WALK_TO_SWIM;
            robocto.StartJump(den.transform.position + (den.transform.forward * MechSpiderController.EJECTED_DISTANCE_FROM_DEN_M), 
                              den.transform.position + (den.transform.forward * MechSpiderController.EJECTED_DISTANCE_FROM_DEN_M) + (-den.transform.up * MechSpiderController.EJECTED_FORWARD_DISTANCE_FROM_DEN_M),
                              false);
            // turn left
            yield return new WaitForSeconds(1f);
            xTouchNorm = -0.25f;
            yTouchNorm = -0.5f;
            robocto.StartThrust ();

            yield return new WaitForSeconds(3f);
            xTouchNorm = 0f;
            yTouchNorm = 0f;
            robocto.StopThrust ();

            // Land and walk a second, then hide.  "Hiding is good for avoiding predators and getting access to menus."
            yield return new WaitForSeconds(0.5f);
            robocto.handleSingleTap (EvadeDirection.DOWN);
            yield return new WaitForSeconds(1.5f);  // give time to land to get all legs down before walking
            inputVector.Set (0f, 0f, 1f);
            yield return new WaitForSeconds(2.5f);
            inputVector.Set (0f, 0f, 0f);

            robocto.handleSingleTap (EvadeDirection.DOWN);
            yield return StartCoroutine(textManager.WaitForDenToBeDoneTalking());
            yield return StartCoroutine(textManager.MakeDenSay("DenHidingIs"));

            // Stand up and walk  "Walking is good for inspecting the ground for useful material, and catching crabs."

            robocto.handleSingleTap (EvadeDirection.UP);
            inputVector.Set (0f, 0f, 1f);
            yield return new WaitForSeconds(1f);
            yield return StartCoroutine(textManager.MakeDenSay("DenWalkingIs"));
            inputVector.Set (0f, 0f, 0f);
            //yield return new WaitForSeconds(5f);

            robocto.handleSingleTap (EvadeDirection.UP);
            xTouchNorm = -0.25f;
            yTouchNorm = -0.5f;
            robocto.StartThrust ();
            yield return StartCoroutine(textManager.MakeDenSay("DenSwimmingIs"));
            xTouchNorm = 0f;
            yTouchNorm = 0f;
            robocto.StopThrust ();

            // Hide
            StartCoroutine(textManager.MakeDenSay("DenIllTeach"));
            robocto.handleSingleTap (EvadeDirection.DOWN);
            yield return StartCoroutine(textManager.WaitForDenToBeDoneTalking());
        }

    PlayGameIntro() is called from my GameManager when the game is first launched.  Here I have some constants that allow me to tweak the timing of each part of the animation.  First Robocto is printed, I simply Lerp his position from inside the den to outside the den.  Lerp is just a linear interpolation function that moves a value over time from a start point to an end point.  Lerp can be used on floating point numbers, say to change an object’s opacity from transparent to opaque.  Or it can be used on Vector3s for changing an object’s position, or Quaternions for rotating an object.  Here the den is printing Robocto, and printing happens in a very linear way, so I use Lerp.  To bring Robocto back into the den after printing, I use an Animation curve, it’s like Lerp, but rather than moving linearly from start to end, I choose a curve that eases in and out of a movement which looks more natural.

using UnityEngine;
using Translation;
using System;
using UnityEngine.UI;
using System.Collections;


namespace Robocto {

    // Singleton
    public class TextManager : MonoBehaviour
    {
        public static TextManager instance = null;  //Static instance which allows it to be accessed by any other script.
        public GameObject denTextRect;
        public Text denTextComponent;
        private bool isTalking = false;

//      #region Language Translator
        /* Translator */
        private Translator trans;
        protected Translator Trans {
            get {
                return this.trans;
            }
            set {
                trans = value;
            }
        }   
//      #endregion

//      #region Some GUI attributes
        private int selection = 0;
        private string[] selections;
        string error = "";
//      #endregion

        //******************************************************************************************************************************************
        //                                                Initialization
        //******************************************************************************************************************************************


        public void Awake() {
            //Check if instance already exists
            if (instance == null) {
                //if not, set instance to this
                instance = this;
                isTalking = false;
            }
            //If instance already exists and it's not this:
            else if (instance != this) {
                //Then destroy this. This enforces our singleton pattern, meaning there can only ever be one instance of a MenuManager.
                Destroy(gameObject);
            }
        }


        void Start() {
            try
            {
                // Create a new translator
                Trans = new Translator(ELangFormat.Csv, "Example CSV/RoboctoText", ";");
                
                // Get all available languages
                selections = Trans.GetAvailableLanguages();
                
                // Select the first one
                Trans.LoadDictionary (selections[selection]);
            }
            catch(Exception ex)
            {
                error = ex.Message;
                Trans = null;
            }
        }

        //******************************************************************************************************************************************
        //                                                Den Speaking
        //******************************************************************************************************************************************

        private const float DISPLAY_TIME_PER_LETTER_S = 0.07f;
        private const float DISPLAY_TIME_PADDING_S = 1f;
        private const float TEXT_FADE_TIME_S = 0.2f;
        public IEnumerator MakeDenSay(string denText) {
            isTalking = true;
            denTextComponent.text = Trans[denText];
            float displayTime = (denTextComponent.text.Length * DISPLAY_TIME_PER_LETTER_S) + DISPLAY_TIME_PADDING_S;
            denTextComponent.color = new Color(denTextComponent.color.r, denTextComponent.color.g, denTextComponent.color.b, 0f);
            denTextRect.SetActive(true);
            yield return StartCoroutine(DenFadeTo(1f));
            yield return new WaitForSeconds(displayTime);
            yield return StartCoroutine(DenFadeTo(0f));
            denTextRect.SetActive(false);
            isTalking = false;
        }

        public void MakeDenSayEnable(string denText) {
            isTalking = true;
            denTextComponent.text = Trans[denText];
            denTextComponent.color = new Color(denTextComponent.color.r, denTextComponent.color.g, denTextComponent.color.b, 0f);
            denTextRect.SetActive(true);
            StartCoroutine(DenFadeTo(1f));
        }

        public IEnumerator MakeDenSayDisable() {
            yield return StartCoroutine(DenFadeTo(0f));
            denTextRect.SetActive(false);
            isTalking = false;
        }

        private IEnumerator DenFadeTo(float aValue)
        {
            float alpha = denTextComponent.color.a;
            Color newColor;
            for (float t = 0.0f; t < 1.0f; t += Time.deltaTime / TEXT_FADE_TIME_S)
            {
                newColor = new Color(denTextComponent.color.r, denTextComponent.color.g, denTextComponent.color.b, Mathf.Lerp(alpha,aValue,t));
                denTextComponent.color = newColor;
                yield return null;
            }
            newColor = new Color(denTextComponent.color.r, denTextComponent.color.g, denTextComponent.color.b, aValue);
            denTextComponent.color = newColor;
        }

        public IEnumerator WaitForDenToBeDoneTalking() {
            while(isTalking) {
                yield return new WaitForEndOfFrame();
            }
        }

        //******************************************************************************************************************************************
        //                                                Popups
        //******************************************************************************************************************************************

        public void PopupMessage() {
            
        }

        public string Translate(string untranslatedString) {
            return Trans[untranslatedString];
        }
    }
}

    Once inside the den, the den starts talking to Robocto.  I created a TextManager singleton class to handle text strings.  This allows me to keep all of the references to GUI objects, that display my text, in one place, and will allow for easy localization of my strings later on when I release Robocto in different languages.

    I made a function in TextManager called MakeDenSay, which takes a string, translates it into some language, and uses the length of the resulting string to determine how long to display the string on the screen.  It also fades the strings in and out.

    After the den has spoken, I call some functions on my Robocto character that are usually only called when the user touches the screen, like StartJump, StartThrust, and handleSingleTap.  The first thing I did in StartDemo was to set isDemoActive to true.  This is a boolean flag I created in my inputHandler class that I set up to ignore input from the user while the demo is active.

    The xTouchNorm, yTouchNorm, and inputVector variables hold the values of where the user is touching the screen.  I set them manually here, faking user input to make Robocto swim and walk in a desired direction.  I call “yield return new WaitForSeconds” to tell unity to keep doing what it’s doing and return to this code when the time has elapsed, so the other monobehaviours are still running, Robocto is still swimming or walking with whatever input variables I’ve set.

        public IEnumerator PlayControlsExplanation() {
            demoCancelled = false;
            isDemoActive = true;
            isDemoWaitingOnUser = false;

            demoOverlay.SetActive(true);
            phoneAndHands.SetActive(true);
            SetDemoThumbs(DemoThumbState.NO_TOUCH, DemoThumbState.NO_TOUCH);
            yield return StartCoroutine(textManager.MakeDenSay("DenOneOrTwo"));
            
            StartCoroutine(textManager.MakeDenSay("DenControlsTapSummary"));

            //Jump into swim
            SetDemoThumbs(DemoThumbState.NO_TOUCH, DemoThumbState.UP);
            yield return new WaitForSeconds(0.2f);
            SetDemoThumbs(DemoThumbState.NO_TOUCH, DemoThumbState.NO_TOUCH);
            robocto.handleSingleTap (EvadeDirection.UP);
            yield return new WaitForSeconds(1.5f);

            // Land into walk
            SetDemoThumbs(DemoThumbState.NO_TOUCH, DemoThumbState.DOWN);
            yield return new WaitForSeconds(0.2f);
            SetDemoThumbs(DemoThumbState.NO_TOUCH, DemoThumbState.NO_TOUCH);
            robocto.handleSingleTap (EvadeDirection.DOWN);
            yield return new WaitForSeconds(3.5f);

            yield return StartCoroutine(textManager.WaitForDenToBeDoneTalking());

            StartCoroutine(textManager.MakeDenSay("DenControlsPressSummary"));

            //Walk forward
            SetDemoThumbs(DemoThumbState.MIDDLE, DemoThumbState.MIDDLE);
            inputVector.Set (0f, 0f, 1f);
            yield return new WaitForSeconds(3f);
            SetDemoThumbs(DemoThumbState.NO_TOUCH, DemoThumbState.NO_TOUCH);
            inputVector.Set (0f, 0f, 0f);
            yield return new WaitForSeconds(1f);

            // Walk turning left
            SetDemoThumbs(DemoThumbState.MIDDLE, DemoThumbState.NO_TOUCH);
            inputVector.Set (-1f, 0f, 1f);
            yield return new WaitForSeconds(1.5f);
            SetDemoThumbs(DemoThumbState.NO_TOUCH, DemoThumbState.NO_TOUCH);
            inputVector.Set (0f, 0f, 0f);
            xTouchNorm = 0f;
            yTouchNorm = 0f;
            yield return new WaitForSeconds(0.5f);

            // Hide
            SetDemoThumbs(DemoThumbState.NO_TOUCH, DemoThumbState.DOWN);
            yield return new WaitForSeconds(0.2f);
            SetDemoThumbs(DemoThumbState.NO_TOUCH, DemoThumbState.NO_TOUCH);
            robocto.handleSingleTap (EvadeDirection.DOWN);
            yield return new WaitForSeconds(3.5f);

            //******************  Now You Try *****************
            isDemoActive = false;
            isDemoWaitingOnUser = true;

            if(!demoCancelled) yield return StartCoroutine(WaitForPlayerAction(DemoWaitStep.LOOK));
            if(!demoCancelled) yield return StartCoroutine(WaitForPlayerAction(DemoWaitStep.UP_TO_WALK));
            if(!demoCancelled) yield return StartCoroutine(WaitForPlayerAction(DemoWaitStep.UP_TO_SWIM));
            if(!demoCancelled) yield return StartCoroutine(WaitForPlayerAction(DemoWaitStep.SWIM_FORWARD));
            if(!demoCancelled) yield return StartCoroutine(WaitForPlayerAction(DemoWaitStep.SWIM_UP));
            if(!demoCancelled) yield return StartCoroutine(WaitForPlayerAction(DemoWaitStep.SWIM_DOWN));
            if(!demoCancelled) yield return StartCoroutine(WaitForPlayerAction(DemoWaitStep.SWIM_TURN));
            if(!demoCancelled) yield return StartCoroutine(WaitForPlayerAction(DemoWaitStep.DOWN_TO_LAND));
            if(!demoCancelled) yield return StartCoroutine(WaitForPlayerAction(DemoWaitStep.WALK_FORWARD));
            if(!demoCancelled) yield return StartCoroutine(WaitForPlayerAction(DemoWaitStep.WALK_TURN));
            if(!demoCancelled) yield return StartCoroutine(WaitForPlayerAction(DemoWaitStep.DOWN_TO_HIDE));
            if(!demoCancelled) ES2.Save(true,  "playerSettings.txt?tag=finishedDemo");
            EndControlsExplanation();
        }

        private void EndControlsExplanation() {
            SetDemoThumbs(DemoThumbState.OFF, DemoThumbState.OFF);
            demoOverlay.SetActive(false);
            phoneAndHands.SetActive(false);
            StartCoroutine(textManager.MakeDenSayDisable());
        }

        private IEnumerator WaitForPlayerAction(DemoWaitStep waitStep) {
            // Reset all flags and timers
            ResetControlsExplanationFlagsAndTimers();
            bool done = false;

            // Display instructions and set demo thumbs for this step
            bool isPressAndHold = true;
            yield return StartCoroutine(textManager.WaitForDenToBeDoneTalking());
            switch(waitStep) {
            case DemoWaitStep.LOOK:
                textManager.MakeDenSayEnable("DenLook");
                break;
            case DemoWaitStep.UP_TO_WALK:
                textManager.MakeDenSayEnable("DenUpToWalk");
                isPressAndHold = false;
                break;
            case DemoWaitStep.UP_TO_SWIM:
                textManager.MakeDenSayEnable("DenUpToSwim");
                isPressAndHold = false;
                break;
            case DemoWaitStep.SWIM_FORWARD:
                textManager.MakeDenSayEnable("DenSwimForward");
                break;
            case DemoWaitStep.SWIM_UP:
                textManager.MakeDenSayEnable("DenSwimUp");
                break;
            case DemoWaitStep.SWIM_DOWN:
                textManager.MakeDenSayEnable("DenSwimDown");
                break;
            case DemoWaitStep.SWIM_TURN:
                textManager.MakeDenSayEnable("DenOneThumbTurn");
                break;
            case DemoWaitStep.DOWN_TO_LAND:
                textManager.MakeDenSayEnable("DenDownToLand");
                isPressAndHold = false;
                break;
            case DemoWaitStep.WALK_FORWARD:
                textManager.MakeDenSayEnable("DenWalkForward");
                break;
            case DemoWaitStep.WALK_TURN:
                textManager.MakeDenSayEnable("DenOneThumbTurn");
                break;
            case DemoWaitStep.DOWN_TO_HIDE:
                textManager.MakeDenSayEnable("DenDownToHide");
                isPressAndHold = false;
                break;
            }
            
            Coroutine thumbDisplayLoop = StartCoroutine(StartThumbDisplayLoop(isPressAndHold, waitStep));

            while(!done) {
                // Check if we've completed this step
                switch(waitStep) {
                case DemoWaitStep.LOOK:
                    done = (demoPlayerLookTimer > REQUIRED_PRESS_AND_HOLD_TIME_S);
                    break;
                case DemoWaitStep.UP_TO_WALK:
                    done = demoUpToWalkFlag;
                    break;
                case DemoWaitStep.UP_TO_SWIM:
                    done = demoUpToSwimFlag;
                    break;
                case DemoWaitStep.SWIM_FORWARD:
                    done = (demoSwimForwardTimer > REQUIRED_PRESS_AND_HOLD_TIME_S);
                    break;
                case DemoWaitStep.SWIM_UP:
                    done = (demoSwimUpTimer > REQUIRED_PRESS_AND_HOLD_TIME_S);
                    break;
                case DemoWaitStep.SWIM_DOWN:
                    done = (demoSwimDownTimer > REQUIRED_PRESS_AND_HOLD_TIME_S);
                    break;
                case DemoWaitStep.SWIM_TURN:
                    done = (demoSwimTurnTimer > REQUIRED_PRESS_AND_HOLD_TIME_S);
                    break;
                case DemoWaitStep.DOWN_TO_LAND:
                    done = demoDownToLandFlag;
                    break;
                case DemoWaitStep.WALK_FORWARD:
                    done = (demoWalkForwardTimer > REQUIRED_PRESS_AND_HOLD_TIME_S);
                    break;
                case DemoWaitStep.WALK_TURN:
                    done = (demoWalkTurnTimer > REQUIRED_PRESS_AND_HOLD_TIME_S);
                    break;
                case DemoWaitStep.DOWN_TO_HIDE:
                    done = demoDownToHideFlag;
                    break;
                }
                yield return new WaitForEndOfFrame();
            }
            StopCoroutine (thumbDisplayLoop);
            SetDemoThumbs(DemoThumbState.NO_TOUCH, DemoThumbState.NO_TOUCH);
            // Fade out instructions for this step
            yield return StartCoroutine(textManager.MakeDenSayDisable());
        }

    Once Robocto has demonstrated swimming, walking, and hiding, I start the controls explanation.  Here I display some overlaid images of a hand and phone to show how Robocto responds to certain screen presses.  I call SetDemoThumbs which will set the left and right thumb positions by disabling the old thumb position image, and enabling the new thumb position image for desired left and right thumb positions.

    Once I’ve run through the various ways to control Robocto, I start an interactive sequence, where the user has to give the requested input to progress.  For more involved control schemes like Robocto, where you have hide, walk, and swim modes, each that handle input slightly differently, it helps to know a player can do the basics before ending your demo.

    First I’m going to create an isDemoWaitingOnUser flag to tell the input handler that I’m waiting for a specific action from the user, and set it to true.  Then I’ll add a set of flags and timers that record the required steps the user has completed to move the demo along.  Here I tell the user “Now you try.” and disable my isDemoActive flag to reenable user input.

    I’ll add a UserInput function that’s called by my inputHandler when isDemoWaitingOnUser is true and one of required steps has been been completed.  In my switch statement I’ll check the step that was just met and if it’s the one I’m waiting for, I’ll move on the the next stage.  Once all required steps are completed, the demo is over and I call my RestoreRoboctoState function to bring Robocto back to where he was.

The Truth Behind Robocto (and a few lies)

The Truth Behind Robocto (and a few lies)

Be Nice To Siri

Be Nice To Siri