Rustbelt

Software Used: Unity C#, Substance Painter, Blender 2.8
Solo Project

Rustbelt is a game that combines computer hacking and horror. The player, a computer hacker from the future, must infiltrate a series of defenses by hacking into a terminal; all while evading security robots who are out to get the player!

I started this game as an academic project and finished it independently. Rustbelt took two months to finish and I was very satisfied with being able to work out the challenges that the game presented.

Dialogue System

In RustBelt, the player is talked to by a remote communicator. Text is brought up to a heads up display and the player can hear the voice recording of the message. On the video to the left is a demo of the dialogue system in action. When the player reaches a certain condition or enters a trigger area, the dialogue system activates. This is used extensively in Rustbelt.

The code shown below is the dialogue manager. It controls when and how messages are displayed in the player's HUD. Demo is shown in the video above.

 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class DialogueManager : MonoBehaviour
{
    //Simple singelton pattern that allows other classes to reference a single instance of this one. (There will only be one instance of this class).
    #region Singelton
    public static DialogueManager instance;
    private void Awake()
    {
        if (instance != null)
        {
            Debug.LogError("More than one instance detected!!");
            return;
        }
        instance = this;
    }
    #endregion

    
    //Three lists the hold data for message strings, message sounds, and the time required to display each message. 
    //A queue would have been just as effective for this version but in the future extra flexibility may be needed.   
    public List<string> messages;
    public List<AudioClip> messageSounds;
    public List<float> messageTimes;

    //Objects that hold necessary items in the HUD
    public Text nameText;
    public Text messageText;
    public GameObject dialogueBox;
    

    void Start()
    {
        StartCoroutine("dialogue");
    }

    private void Update()
    {
     if (Input.GetKeyDown(KeyCode.R))
        {
            SkipMessage();
        }
    }

    //This method controls when messages are displayed and when to remove messages from the list.
    //When an event trigger with dialogueue is activated, it sends messages to the three lists in this class. 
    //This method then activates and displays those messages properly. 
    IEnumerator dialogue()
    {
        ToggleText(false);

        while (true)
        {
            while (messages.Count != 0)
            {
                ToggleText(true);

                StartCoroutine(Filldialogue(messages[0]));

                GetComponent<AudioSource>().clip = messageSounds[0];

                GetComponent<AudioSource>().Play();

                //Each time a message is finished being displayed, it is removed from the list. 
                messageSounds.RemoveAt(0);
                messages.RemoveAt(0);


                yield return new WaitForSeconds(messageTimes[0]);
                messageTimes.RemoveAt(0);
            }

            ToggleText(false);
            yield return new WaitForSeconds(.5f);
        }

    }


    //This method toggles the visability of the HUD. 
    //Usually, when a message needs to be displayed, the HUD is visible. 
    void ToggleText(bool trueFalse)
    {
        dialogueBox.gameObject.SetActive(trueFalse);
    }

    //This method slowly fills in the HUD message with the latest message in the messages list. 
    //It inserts a character from the message once every two frames
    //until the message is completely displayed. 
    IEnumerator Filldialogue(string text)
    {
        messageText.text = "";
        foreach (char letter in text.ToCharArray())
        {
            messageText.text += letter;

            yield return null;
            yield return null;
        }
    }

    //If a player hits the 'r' hotkey, this method will be called. It clears the messageText buffer
    //and continues onto the next message in the list. 
    public void SkipMessage()
    {
        if (messageSounds.Count > 0)
        {
            //All coroutines are temporarily stopped because we do not want leftovers from the previous message 
            //to be filled into the new message. 
            StopAllCoroutines();

            messageText.text = "";

            //Clearing the current message in the lists. 
            messageSounds.RemoveAt(0);
            messages.RemoveAt(0);
            messageTimes.RemoveAt(0);

            GetComponent<AudioSource>().Stop();

            StartCoroutine("dialogue");
        }
    }
}

Event Trigger System

All events in Rustbelt use the event trigger system, which creates a standardized way of handling the execution of events all in one class. Examples of events could be instantiating a security bot or activating dialogue. When an event trigger is activated, it will call specified methods from the Master Controller class.​ On the video to the left is a demo of event triggers being used in different scenarios. The first scenario (0:03) is an event trigger being used to activate dialogue. The second scenario (0:10) is an event trigger being used to instantiate a security bot at the far end of the hallway. In both instances, the event trigger is activated with a trigger collider. However any event trigger can be manually activated at any time.

The code shown below is the base class for the event trigger.

 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EventTrigger : MonoBehaviour
{

    //If checkpoint does not equal zero, this variable will set the checkpoint.
    public int checkPoint = 0;

    //Check point threshold for event trigger.
    public int checkPointNeeded = 0;
    

    //Method name in the Master Controller that the event trigger will call.
    //If "nothing", then no method call will happen. 
    public string methodName = "nothing";

    public bool destroyAfterActivating = true;

    //If any delay after the trigger is activated is desired, these variables exist. 
    public float methodDelay = 0;
    public float speakingDelay = 0;


    public AudioSource anyAudio; 

    //Put in an embedded class to keep organization.
    [System.Serializable]
    public class DialogueStuff{
    [TextArea(2, 2)]
    public string[] messages;
    public AudioClip[] messageSounds;
    public float[] times;
}
    public DialogueStuff dialogue; 


    //This method logic is run when the player enters the bounding box of the trigger.
    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            if (PlayerPrefs.GetInt("CheckPoint") >= checkPointNeeded)
            {
                if (checkPoint != 0 && PlayerPrefs.GetInt("CheckPoint") < checkPoint)
            {
                //If checkpiont variable is not zero, the checkpoint will be set. 
                PlayerPrefs.SetInt("CheckPoint", checkPoint);
                print("Checkpoint is now " + PlayerPrefs.GetInt("CheckPoint"));
            }

                //Activate event trigger.
                StartCoroutine("DoActivation");
            }
        }
    }


    //This method is called if dialogue is contained in the event trigger. It sends dialogue to the dialogue manager.  
    void Senddialogue()
    {
        for (int i = 0; i < dialogue.messages.Length; i++)
        {
            //Adding message(s) to the lists of the dialogue manager.
            DialogueManager.instance.messages.Add(dialogue.messages[i]);
            DialogueManager.instance.messageSounds.Add(dialogue.messageSounds[i]);
            DialogueManager.instance.messageTimes.Add(dialogue.times[i]);
        }
    }


    //This method is called if the event trigger needs to be manually activated by another script. 
    public void Activate()
    {
        if (anyAudio != null)
        {
            anyAudio.Play();
        }
        StartCoroutine("DoActivation");
    }

    //This is the main activation method. It calls the specified method in the Master Controller and sends dialogue if there is any. 
    //It also destroys the object if desired. 
    public IEnumerator DoActivation()
    {
        if (methodName != "nothing")
        {
            //"CallMethod" in MasterController is the same as calling a method in MasterController, 
            //but this specific method calls the desired method with a variable delay. 
            MasterController.instance.CallMethod(methodName, methodDelay);
        }

        yield return new WaitForSeconds(speakingDelay);

        if (dialogue.messages.Length != 0)
        {
            Senddialogue();
        }

        if (destroyAfterActivating)
        {
            Destroy(gameObject);
        }
    }
}