Unity Retro Demo, how I made this intro


unity retro demo featured

Unity Retro Demo, takes you straight back to the golden years of 80s of the 8/16 bit computer era. Since I started learning my self Unity a few months ago. I have tried Unity a few times before, but never liked it until now. Yes, I’m probably weird. So as a fun little programming challenge for my self, I decided to create something from the Amiga age.

I’m by no means an expert in C# or Unity. I have used C# for some time now. Previously only writing sensor and instruments simulators and stuff like that. I have no idea about Unity best practices. So for all I know this might be rubbish. Anyhow, this is how I created this cute little Unity retro demo program.

The entire project can be found on this GitHub repository.

All assets used in the project are in the GitHub repository. If you only want the assets you can download it here.

Unity Retro Demo Video

A short one minute video showing the Unity retro demo. I really enjoyed it as a programming exercise for my self. Learning more about the Unity framework and the Unity editor.

Scripts used in Unity Retro Demo

SceneManager.cs – Is used to control the whole scene.
SineWave.cs – Move the upper text in a sine wave.
SineWaveUpDown.cs – Move the lower text in a sine wave.
TypeWriter.cs – It is more like a Time-To-Live script. Deletes the typewriter characters after xx seconds.
UpDown.cs – Moving the bars around the Sine Wave picture at the top.

UpDown script

All intros need a logo or picture. This one is no exception. At the top there is three bars moving up and down. It really moves in a sine wave motion, at a fixed position. So it looks a bit like the three bars is moving in an elliptic motion around the Sine Wave Picture. By the way, the Sine Wave picture is a png file with transparent background. And the sorting order is set to 4.

UpDown.cs

public class UpDown : MonoBehaviour
{
    [SerializeField]
    float frequency = 2.0f;
    [SerializeField]
    float magnitude = 1.0f;
    [SerializeField]
    float yPos = 1.0f;
    [SerializeField]
    float elapsedTime = 0f;

    private Vector3 pos;
    private Renderer rend;
    private Vector3 lastPos;

    private void Start()
    {
        pos.y = yPos;
        rend = GetComponent<Renderer>();    // Needed to change sorting order
    }

    void Update()
    {
        pos.y = yPos;
        elapsedTime += Time.deltaTime;
        lastPos = transform.position;
        transform.position = pos + (transform.up * Mathf.Sin(elapsedTime * frequency) * magnitude);

        // Check if the bars are moving up or down
        if (transform.position.y > lastPos.y)
        {
            rend.sortingOrder = 5;  // Move on the front of the picture
        }                           // The Sine Wave picture has sorting order 4
        else
        {
            rend.sortingOrder = 3;  // Mone on the back of the picture
        }
    }
}

In the start function we just get the y-position. Since we do the same in Update, that line is not needed in Start function. Then we get the renderer component. We need the renderer to change the sorting order of those bars. However the plugin does not show that line correct. It is correct in the GitHub repository, and should look like this: rend = GetComponent<Renderer>();

The Update function does the movement and calculation of the movement. It starts with getting the y position. Then we add deltatime to our bars animation to get a smooth movement. lastPos is needed to determine if the bars are moving up or down. Transform.position is how we move the bars. Check out that link for the documentation. What we do here is to move them in a sine wave, but with a fixed position so it only moves up and down.

At the bottom of the Update function, we determine if the bars are moving up or down. We do that by checking the current position with the position in the previous frame. So if the bars are moving up, we set the sorting layer to 5 so we move the bars in front of the Sine Wave picture. And behind the picture when they are moving down.

SineWave.cs

This is the script that moves each character in a sine wave from right to left. The characters are created as a prefab and we use a sprite sheet to change letters.

Unity Retro Demo 2

Let’s look at the code for the sine wave text. I have made one script for the upper text scroll, and another script for the bottom text scroll. Even tho they are almost identical, except for one tiny little thing. Here is the variables I use for the SineWave script.

[SerializeField]
float moveSpeed = 3.0f;
[SerializeField]
float frequency = 2.0f;
[SerializeField]
float magnitude = 0.6f;

private Vector3 pos;
private Vector2 left;
private Renderer rend;
private Vector2 spriteHalfSize;

float elapsedTime = 0;

Just some serialized variables for speed, frequency and magnitude. Those were made serialized when creating the script since it is so easy to edit from the Unity editor. Then just some vector variables for the position, left border and sprite size (or half the sprite size) for the characters.

void Start()
    {
        pos = transform.position;
        left = Camera.main.ViewportToWorldPoint(Vector3.zero);  // Find left screen border
        rend = GetComponent<Renderer>();        // Used to find character sprite size
        spriteHalfSize = rend.bounds.extents;   // Spritesize / 2
    }

The Start function is again pretty straight forward. We get the position of the sprite, the left screen border, the renderer component and half the size of the sprite (character).

void Update()
    {
        if (pos.x < (left.x - spriteHalfSize.x))
        {
            Destroy(gameObject);
        }
        pos -= transform.right * Time.deltaTime * moveSpeed;    
        elapsedTime += Time.deltaTime;
        transform.position = pos + (transform.up * Mathf.Sin(elapsedTime * frequency) * magnitude); 
    }

The first thing we do in the Update method is to check if the sprite has left the screen. If it has then we destroy it. Then we calculate the next position by multiplying transform.right, deltatime and moveSpeed and subtract that from current position. We subtract because we want to move the text to the left. With the transform.position command we actually move the sprite in a sine wave.

SineWaveUpSide script

This script is almost identical to the SineWave script. This one is for the characters on the bottom scroll text.

transform.position = pos + (transform.up * Mathf.Sin(elapsedTime * frequency) * -1 * magnitude);

The line above is the only difference from the SineWave script. Almost at the end, we multiply the sin function with -1 to inverse the sinus wave. So instead of using two almost identical scripts, maybe you can combine them? Use bool variable to decide if it should be inverted or not.

TypeWriter script

This is the script that removes the characters at the bottom part of the screen. Those characters are written with some kind of typewriter effect.

Unity Retro Demo 3
public class Typewriter : MonoBehaviour
{
    float timeToLive = 5.0f;

    private void Update()
    {
        timeToLive -= Time.deltaTime;
        if (timeToLive <= 0)
            Destroy(gameObject);
    }
}

This is the entire script. It is just a kind of time-to-live script. Destroy the character after a specified amount of time. In this case 5 seconds.

SceneManager script

The Scene manager script is the main script in the unity retro demo. It puts it all togheter.

Unity Retro Demo 4

First is the declaration of variables. Nothing special going on there, so we gonna skip that part. Check out Unity’s tutorial about data types if you are not confident with data types. Just remember if you are going to use these scripts, these two variables need to use lower caps letters only. And you can only use letters from a to z. Anything else will be treated as a space.

private static string message = "    hello world      check out the description below for the github link if you want the code      enjoy   ";

string[,] data = new string[,]
{
   {"music", "by", "joshuaempyree"},
   {"graphics", "by", "a magic unicorn"},
   {"code", "by", "it just appeared here"}
};

Start and Update functions only runs the typewriter and sine wave functions.

private void Start()
{
    StartCoroutine(TypeWriter());
}

void Update()
{
    SineText();
}

For both the SineWave and TypeWriter effect, we need to convert each character to a number so we can pick out correct character from the sprite sheet. We will use the ascii codes to do that. To convert characters into ascii codes we use this function.

int SelectChar(char selChar)    // convert from char to "ascii code" as integer
{
    return selChar - 97;    // a has ascii code 97 and our spritesheet starts at 0
}

The ASCII code for the letter is 97. By converting from char to integer we get the ASCII code. Our sprite sheets start at 0 and not 97. Just subtract 97 from the ASCII code and we get a number which matches our sprite sheets.

The typewriter

Look at the TypeWriter function. Unlike the other functions, this one is declared as an IEnumerator instead as void or a data type.

IEnumerator TypeWriter()
{
    float startingPoint;
    GameObject spriteChar;
    int stupidChar;
    float startLine = -2.6f;

    dataLines = data.GetLength(0);
    dataWords = data.GetLength(1);

    yield return new WaitForSeconds(1f);

    while (true)
    {
        for (int y = 0; y < dataLines; y++)
        {
            for (int i = 0; i < dataWords; i++)
            {
                printChars = data[y, i].ToCharArray();  // Convert string to char array
                startingPoint = ((data[y, i].Length * charSize) / 2) * -1;  // Find the starting point
                foreach (char oneChar in printChars)
                {
                    stupidChar = SelectChar(oneChar);
                    if (stupidChar >= 0 && stupidChar <= 26)
                    {
                        spriteChar = Instantiate(letter3, new Vector3(startingPoint, startLine, 0f), Quaternion.identity);
                        spriteChar.GetComponent<SpriteRenderer>().sprite = characters[stupidChar];  // Select correct char to display
                        yield return new WaitForSeconds(.2f);
                    }
                    startingPoint += charSize;
                }
                startLine -= lineSpacing;
            }
            startLine = -2.6f;  // Reset the start line after each block of text
            yield return new WaitForSeconds(5.5f);
        }
    }
}

First the usual. Variable declarations. Since we want this effect to loop unlimited we put the entire thing inside a while true loop.

We use a nested for loop to read thru all the words in the 2d string array data. The inner for loop read the actual words, and the outer for loop cycle thru each “line”. Take a look at the code inside the nested for loop.

printChars = data[y, i].ToCharArray();
startingPoint = ((data[y, i].Length * charSize) / 2) * -1;  // Find the starting point
foreach (char oneChar in printChars)
{
    stupidChar = SelectChar(oneChar);
    if (stupidChar >= 0 && stupidChar <= 26)
    {
        spriteChar = Instantiate(letter3, new Vector3(startingPoint, startLine, 0f), Quaternion.identity);
        spriteChar.GetComponent<SpriteRenderer>().sprite = characters[stupidChar];  
    }
        startingPoint += charSize;
}

First, we convert the string to a char array into printChars. Then we calculate the starting for each line. We want each line to be centered on the screen. To find the starting point we take the number of characters and multiply with a specified char size. Next, we divide it by 2 to get half the size and multiply with -1. We multiply with -1 to make it start on the left side of the screen. The origo in Unity coordinate system is exactly in the middle of the screen. So the coordinates on the left side are negative.

We use a foreach function to cycle thru all letters in each word and print them. First we see the SelectChar function we use to convert from chars to integers. Before we print out each letter we make sure only letters from a to z (lower case) are printed. Anything else will be printed as a space. Between each letter we adjust the starting point for the next letter.

The Sine Wave

void SineText()
{
    timer += Time.deltaTime;
    if (timer >= waitTime && charCounter < message.Length)
    {
        whatChar = SelectChar(messageChars[charCounter]);
        if (whatChar >= 0 && whatChar <= 26)    // Only accept a to z in lower capital
        {
            myChar = Instantiate(letter, new Vector3(9f, firstYpos, 0f), Quaternion.identity); // X=9, Y=-0.25
            myChar.GetComponent<SpriteRenderer>().sprite = characters[whatChar];    // Select image for the sprite
            MyUpsideChar = Instantiate(letter2, new Vector3(9f, secondYpos, 0f), Quaternion.identity);
            MyUpsideChar.GetComponent<SpriteRenderer>().sprite = characters[whatChar];
        }
        charCounter++;
        timer = 0f;
    }
    else if (charCounter >= message.Length)   // If end of string, restart
    {
        charCounter = 0;
    }
}

First, we make sure that the timer is more or equal to waitTime and there is still more characters in the message to be printed. Then we see the same char to ASCII conversion function again, SelectChar. And again only a to z accepted. If it is between a and z we spawn a letter and we select the right sprite and use the renderer to change it. Since we have two scrolling texts we do that twice. At the end we reset the char counter and do it all again.

MyChar and MyUpsideChar. Again this plugin does not show <> in codes. So don’t copy from this code. Copy from the GitHub code instead.

If you lost the will to live by reading this and starting to hate Unity, I also have an article on how to remove it from your mac. Or maybe you tried to install Unity and ended up with error 1.

Have any Question or Comment?

Leave a Reply