Day 14: Better Visuals, and TextMeshPro

Today I demonstrated my favorite method for making cool voxel enemies that appear to animated. The process is relatively simple, and when we add explosion effects it gives everything a great juicy quality. We also looked at Unity’s preferred method for text rendering, TextMeshPro, and implemented a sample overlay message. (TMP uses different libraries and types than Unity’s Legacy Text, so your code from the previous project will require some adjustment).

Part 1: Better Enemies (optional!)

Our previous alien attackers were rather primitive – Unity primitives, to be exact. (Thank you, I’ll be here all week!) Now that we have a fancy setting, our enemies should be a little more… dramatic. For this section, we are going to look to the source material and draw some inspiration from the classic pixel-y goodness of the original, and build our enemies out of cubes.

Why cubes? First off, they are super easy to create and work with, and require surprisingly little overhead in terms of rendering and physics calculation. Second, I want to show you how you can create a game using just the tools provided – no need for special 3D modeling/animation software. Third, we are going to use Physics on those blocks to create a satisfying explosion effect on the alien ship that will add some fun to our game.

First, we create a single cube, using GameObject > 3D Object > Cube or Create… > 3D Object > Cube. I’m going to want my bottom center cube to represent the “position” of the object so I will set its position to the origin (0, 0, 0). If yours is not at that location, you can select the settings dropdown from the Transform component and select Reset Transform to do so.

Next we make copies of the cube and offset them by 1 unit increments so that you have a row of 5 cubes with their edges touching. Now copy those rows and move them up by 1 unit increments, until you have a 5×5 cube grid.

In case you were wondering what 25 cubes look like

Then, we create an empty game object (GameObject > Create Empty or Create… > Create Empty) also located at (0, 0, 0) and make all of the cubes children of this empty object. Notice that when you add the cubes as children, they now indent under the parent object in the Hierarchy, and the parent object now has a small button that lets you roll-up the list.

I expect to create a number of aliens using this same block grid, and so I will create a prefab object from it that I can use as a template for future iterations. I name the parent object “EnemyBase” and drag it into my Asset window. I can now use this prefab to override the cubes that are on and off in order to make shapes. Once I have edited this to make an alien shape, I will save this as a different prefab. (This is my path to create these objects, but this is by no means the only path. Do what works for you!)

Once I have unpacked the object, I edit it by de-activating some of the blocks. You can delete these, or simply turn the cube objects off using the check box next to the GameObject name in the Inspector. This way, the block is preserved, in case you want to go back later and edit quickly. Remove some blocks and make an alien shape like so:

Time to give the parent object a name. I called mine EnemyFrameA, since I will have multiple versions of this, each one representing a frame of animation.

Now I turn off EnemyFrameA, and place a new instance of the EnemyBasetemplate in the scene at the same position. I unpack, edit it to look as though the legs have moved, and name the parent object EnemyFrameB.

Now I am going to create a duplicate of EnemyA_Frame1, but I will call this one EnemyA_FrameExplode. This object will be out “stunt double”, swapping places with the other frames when it is time for the ship to blow up. To get that great explosive force, add a Rigidbody component to each of the cubes in EnemyA_FrameExplode. (You can simply select all of the cubes at once and go to Add Component).

It is also worth noting that in my objects for frames A & B, I have turned their colliders off. This is because I want to have to worry about managing collisions on the individual blocks – I want these to simply be a part of a cohesive whole.

I’m going to make each one of our alien parent objects a prefab by dragging it down into the Assets folder. Next, I am going to create a new empty game object and parent it to these prefabs, so that it serves as a container to the individual frames. Name this object EnemyTypeA1 and make this object into a prefab as well. I add my Enemy class to this, as well as a Box Collider component which I will resize to fit around my enemy object. (Don’t forget to change your layer designation to “Enemy”!)

Now, let’s add a little bit of scripting to bring our little attacker to life. We want to open our Enemy script and add variables to hold the three child objects of this particular enemy.

public GameObject FrameA;
public GameObject FrameB;
public GameObject FrameExplode;

We define three objects (which we must populate with the Inspector), and then on Start( ) we set their conditions so that only the modelOne model is active.

NOTE: I should probably use Awake so that this is set instantly, just in case something else is going to access our models when the Start( ) event runs, but for today, Start will do.

private void Start()
{
    FrameA.SetActive(true);
    FrameB.SetActive(false);
    FrameExplode.SetActive(false);

    // start the swapping
    StartCoroutine(NextStage());
}

When Start( ) does arrive, we use that to launch a Coroutine that I have created called NextStage( ) that will call my SwapFrames( ) command once per second. It accomplishes this by nesting the call and the 1.0 second yield inside of a “while” loop that will run continuously for the duration of the object because the condition is set to “true”.

public IEnumerator NextStage()
{
    while (true)
    {
        SwapFrames();
        yield return new WaitForSeconds(1.0f);
    }
}

The SwapFrames( ) command simply set the active state of both frames to the opposite of its current value. (I get that current value with the GameObject property activeSelf)

public void SwapFrames()
{
    FrameA.SetActive(!FrameA.activeSelf);
    FrameB.SetActive(!FrameB.activeSelf);
}

NOTE
In previous versions of this assignment, I would tend to use the GameObject’s “active” property, getting and setting that directly. But over time, Unity started to become mad at this, because this particular interaction is being phased out. So instead the proper method is used, utilizing the GameObject.SetActive( ) command, and reading the value using the “activeSelf” property.

Once this is done, I am free to go back into my main scene and replace the MotherShip prefab enemies with our new advanced enemy prefabs.

Part 2: Bigger Explosions!

For this next part, we want to test out our explosion effect – where we swap out our model frames with our “stunt double” and then apply an explosive force to the individual boxes. We do this by creating an Explode( ) command in our Enemy, like so:

private void Explode()
{
    // make an explosion
    Instantiate(enemyExplosionPrefab, transform.position, Quaternion.identity);

    // activate explosion frame
    FrameA.SetActive(false);
    FrameB.SetActive(false);
    FrameExplode.SetActive(true);

    Rigidbody[] cubes; // variable to hold rigidbodies

    // get every rigidbody inside the explosion object.
    cubes = FrameExplode.GetComponentsInChildren<Rigidbody>();

    // apply an explosive force
    foreach (Rigidbody rb in cubes)
    {
        rb.AddExplosionForce(250f, (FrameExplode.transform.position + Vector3.forward), 10f);
    }

    FrameExplode.transform.parent = null;
}

Once we have turned off our models and turned on our “stunt double” object, our goal is to apply the Rigidbody.AddExplosionForce( ) to each cube in the object. This method takes three arguments – a force to exert, an origination point for the explosion, and a radius within which the explosion will have an effect. We create public float objects for “explosionForce” and “explosionRadius” in the class declaration, and then set those values.

In our explode command, you see that we declared a variable as “Rigidbody[ ] cubes”. These brackets mean that we expect to receive more than one result – an array of Rigidbody components. We then generate this list using the GetComponentsInChildren<>( ) method which returns the array of all child objects for “modelExplosion” that have a Rigidbody component in their backpack. Finally we use a “foreach” loop that lets us cycle through each result in our array. In each pass of the loop (that is, for each Rigidbody object in cubes[ ]) we set that object to the variable “rb”, and then apply our explosive force to that Rigidbody. By cycling through all of these we ensure that every object is affected by the force.

Kind of, but not quite…

Now, this creates a problem, because we previously set the Enemy object to self-destruct as soon as it collided with the bullet. If we destroy the object, the children will be destroyed as well, along with our “stunt double”. Everything will just blink out of existence. But what if our stunt double was not a child of the enemy object anymore? To accomplish this, we are going to emancipate our stunt double from it’s parent object, making it an orphan (or as I like to call it, a “child of the world”). We do this by setting the parent of that object to “null”.

FrameExplode.transform.parent = null;

Now our enemy object destroys itself, but not before throwing the stunt double out into the world and applying the explosion just before it disappears. Because our Explode( ) command runs prior to the destruction of the Enemy object, our stunt double makes it out alive (but not for long).

BOOM-shaka-laka!

Tomorrow we will look at improved UI fonts using TextMeshPro, and at the “build” process, where we compile our game that only runs in an editor into it’s own standalone executable.


Part 3: Finding a Font

Fonts are wonderful little things. They can add so much personality to your game, helping to set a mood or support a theme. Just one look at a font can often tell you so much about an experience. Are we in for a fun, lighthearted adventure? Perhaps your game wants us to know that it means serious business. Or maybe you want your game to feel as though it is set in a particular time period. Fonts are a great way to quickly establish that tone… or ruin it, if the font feels out of sync with the style, especially when it is the “default” font. Imagine you spend weeks and weeks building a fantasy style game with wizards and dragons and characters with armor and swords, and beautiful orchestral background music, but then all of your dialog is in Arial. Yuck.

So how do you find fonts? Well, one way is to look on your computer. Macs & PCs have a “Fonts” folder that contains font files, most of which can be used by Unity. You can use either a TTF (True-Type Font) or OTF (Open-Type Font), and import it into your game either by dragging it into the Asset window or using Import New Asset… (Both of these methods will create a local copy of the file, rather than moving it).

But if you want to find more fonts, I recommend visiting Google Fonts, where they have a massive library of fonts that are free for use. You can download the font-family and import them into your game. (Just be sure to check the license on the font if you are planning on selling your creations, to make sure that usage is covered).

Part 4: TextMeshPro

One thing that you may have noticed when working with the text objects is that they do not scale well. When scaled up to a larger size, the characters in the text often appear to be blurry, with fuzzy edges. This is because Unity uses a “bitmap” texture – an image of the font – to build your text. It chops up the texture into small rectangles and displays those on your screen. Depending on the original size of your font, this jagged effect – an artifact of upscaling the anti-aliased font image – may be quite pronounced.

Thankfully, Unity now offers us an alternative solution that can render crisp text at any resolution. TextMeshPro was once a third-party asset, but was so popular that Unity acquired it and now includes it with the engine. It takes a few more steps to set up, and is implemented in a slightly different manner, but the end results are spectacular.

Left: Unity’s legacy text | Right: TextMeshPro
Both set to a font size of 18

TextMeshPro differs from the legacy Unity text system in that it does not display bitmap textures – instead it uses a technique called a Signed Distance Field (SDF). In simple terms, it uses a low resolution image that looks like a blurry version of the font texture and applies a bunch of crazy graphics math on those images to generate a crisp looking render at any distance. If you would like to read more about the process, check out this SIGGRAPH paper from the folks at Valve who invented the technique.

A closeup of the “font atlas”

What this means for us is that we cannot simply load and assign a font as we can with a Text object. Instead, we need to generate a Font Atlas by importing a font and letting TextMeshPro process it into one of these fancy blurry texture maps.

To begin, launch the font creator by going to Window > TextMeshPro > Font Asset Creator. If this is the first time you have launched TextMeshPro in this project, you will get a menu asking you to Import TMP Essentials. Click this and install. You do not need to load the Examples & Extras.

Importing Fonts

In the Font Asset Creator, assign a Source Font File – this is a font file that you have already loaded into your Assets folder. As a reminder, Unity supports both True-Type Font (.ttf) and Open-Type Font (.otf) formats. Click the Generate Font Atlas button and the creator will generate the font image. Select Save As… to save the font atlas to your asset folder.

Font Asset Creator
A completed font atlas – just 512×512 pixels to store everything for our font!

TextMeshPro will also create a folder in your Asset directory that includes some instructions and documentation if you want to dig deeper into the settings.

Changing TextMeshPro Text Values

In the past, we used GetComponent<Text>.text to set the value of a text string. TextMeshPro objects have their own component types – one for UI, and one for In-World objects. To access these, you must also call the TMPro library.

using UnityEngine.UI;
using TMPro;
 
...
 
GetComponent<Text>().text = "This is how you set a Unity UI Text value";
 
GetComponent<TextMeshPro>().text = "This is how you set a TMPro In-World text value";
 
GetComponent<TextMeshProUGUI>().text = "This is how youet a TMPro UI value";

Note: There is a big difference between TextMeshProUGUI and TextMeshPro components. One is for UI, the other for in-world. They are not interchangeable.

To update our game UI elements, I simply created new TextMeshPro Text objects for the UI, then changed the assignment type of the public variables in our GameManager from “Text” to “TextMeshProUGUI”. The rest continues to work as expected, by updating the content of the .text property.

I am a big fan of Special Elite, as it feels very “X-Files” to me.

Enemy.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    public GameObject enemyBombPrefab;

    // animated frames
    public GameObject FrameA;
    public GameObject FrameB;
    public GameObject FrameExplode;

    private void Awake()
    {
        // set up the frames
        FrameA.SetActive(true);
        FrameB.SetActive(false);
        FrameExplode.SetActive(false);
    }

    private void Update()
    {
        /*
        if (Input.GetKeyDown(KeyCode.B))
        {
            DropABomb();
        }
        */

        if (Input.GetKeyDown(KeyCode.F))
        {
            SwapFrames();
        }

        if (Input.GetKeyDown(KeyCode.E))
        {
            Explode();
        }

    }

    private void SwapFrames()
    {
        // invert the frame states
        FrameA.SetActive(!FrameA.activeSelf);
        FrameB.SetActive(!FrameB.activeSelf);
    }

    private void Explode()
    {
        // turn off parent collider
        GetComponent<Collider>().enabled = false;

        // activate the explosion frame
        FrameA.SetActive(false);
        FrameB.SetActive(false);
        FrameExplode.SetActive(true);

        // find all of the rigidbodies in FrameExplode
        Rigidbody[] cubes; // define the array
        cubes = FrameExplode.GetComponentsInChildren<Rigidbody>();

        // apply an explosive force to each
        foreach (Rigidbody rb in cubes)
        {
            rb.AddExplosionForce(650f, (FrameExplode.transform.position + (2 * Vector3.back)), 10f);
        }

        // set the explosion frame to the world.
        FrameExplode.transform.parent = null;

        Destroy(FrameExplode, 5.0f);
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.transform.tag == "PlayerBullet")
        {
            // make the explosion noise
            SoundManager.S.MakeEnemyExplosionSound();

            // call the explosion
            Explode();

            // destroy myself
            Destroy(this.gameObject);

            // destroy the thing that hit me
            Destroy(collision.gameObject);
        }
    }

    public void DropABomb()
    {
        // make the bomb
        Instantiate(enemyBombPrefab, transform.position + Vector3.down, Quaternion.identity);

    }

}

GameManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    public GameObject enemyBombPrefab;

    // animated frames
    public GameObject FrameA;
    public GameObject FrameB;
    public GameObject FrameExplode;

    private void Awake()
    {
        // set up the frames
        FrameA.SetActive(true);
        FrameB.SetActive(false);
        FrameExplode.SetActive(false);
    }

    private void Update()
    {
        /*
        if (Input.GetKeyDown(KeyCode.B))
        {
            DropABomb();
        }
        */

        if (Input.GetKeyDown(KeyCode.F))
        {
            SwapFrames();
        }

        if (Input.GetKeyDown(KeyCode.E))
        {
            Explode();
        }

    }

    private void SwapFrames()
    {
        // invert the frame states
        FrameA.SetActive(!FrameA.activeSelf);
        FrameB.SetActive(!FrameB.activeSelf);
    }

    private void Explode()
    {
        // turn off parent collider
        GetComponent<Collider>().enabled = false;

        // activate the explosion frame
        FrameA.SetActive(false);
        FrameB.SetActive(false);
        FrameExplode.SetActive(true);

        // find all of the rigidbodies in FrameExplode
        Rigidbody[] cubes; // define the array
        cubes = FrameExplode.GetComponentsInChildren<Rigidbody>();

        // apply an explosive force to each
        foreach (Rigidbody rb in cubes)
        {
            rb.AddExplosionForce(650f, (FrameExplode.transform.position + (2 * Vector3.back)), 10f);
        }

        // set the explosion frame to the world.
        FrameExplode.transform.parent = null;

        Destroy(FrameExplode, 5.0f);
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.transform.tag == "PlayerBullet")
        {
            // make the explosion noise
            SoundManager.S.MakeEnemyExplosionSound();

            // call the explosion
            Explode();

            // destroy myself
            Destroy(this.gameObject);

            // destroy the thing that hit me
            Destroy(collision.gameObject);
        }
    }

    public void DropABomb()
    {
        // make the bomb
        Instantiate(enemyBombPrefab, transform.position + Vector3.down, Quaternion.identity);

    }

}