Devlog #2: Adding some gameplay

With the addition of NPC traffic, a game over state and some progress indicators, my infinite runner game now technically has some gameplay

Major additions from last week are proper background scenery, traffic so the player has something to avoid hitting, a game over state when they do hit something and some placeholder UI elements to show score and distance travelled. I also ended up spending a fair bit of time on background systems and structuring to try and keep the codebase maintainable.

Here's a 10 second video showing the basic gameplay. The left side is what the player would see on their phone, the right side is the scene view in Unity. You can also watch it on YouTube if the video isn't showing here.

Environment

I've added some proper scenery graphics, so the road no longer looks like it's just floating in the sky. I was planning on doing this elaborate system that would spawn buildings in different positions from a random pool but it seems like that's not necessary. When you're playing, you can't really notice it's just the same three buildings repeating over and over.

Another big addition is the NPC traffic. Because the player's car is actually stationary along the game world's Z axis, I had to set it up so I can specify the speeds of each civilian vehicle in meters per second which then gets converted to speed relative to the player. The civilian cars are actually moving backwards in the game world when the player's "virtual speed" is faster than theirs.

Because the player's usually supposed to be moving faster anyway, new civilian cars are only spawned in front of the player. This means that if the player car stops, the road becomes empty because no new cars are being spawned. There are also way too many buses because all the cars (plus the power-up) are spawned with equal probability.

Animations

I added animations to the power-up and when the player's vehicle changes lanes or crashes into something. The car also vibrates slightly as it moves down the road. It's been a while since I've last used Unity's animation tools so I'm having to re-learn them again. It's a bit fiddly trying to get the states, transitions and interruptions working correctly but it's also rewarding to see how much life small animations can breathe to a scene with relatively little work.

Testing New Ways of Structuring Projects

I've never been a huge fan of the standard Unity way of wiring MonoBehaviour references together in the editor. For one, this doesn't work across scenes and the way I structure my projects I often have high-level code in a main scene which then loads individual levels which in some cases can themselves consist of multiple Unity scenes. There are ways to mimic cross-scene references but that won't remove the second problem: turning your project's internal dependency graph into spaghetti of the worst order.

I'm not well versed in the abstract software development theory behind all of this, so I won't go into it. From a practical point of view, it's all pretty simple to explain though: does it feel like whenever you change one thing in your project five other things break? If so, it's probably because you haven't been paying attention to how your project's internal connect to one another.

Dependency Injection Frameworks

One way to accomplish a cleaner dependency graph is with a dependency injection framework. If you want to go this route, you'll probably want use Zenject. The example code here is for mtti.Inject which is a dependency injection framework I wrote for myself. It allows me to do things like this:

using UnityEngine;

using mtti.Inject;

namespace mtti.Whatever
{
    public class MyComponent: MonoBehaviour
    {
        [Inject]
        private IPrefabService _prefabService;

        private void Update()
        {
            // do something with _prefabService
        }
    }
}

As long as the GameObject with MyComponent is either in the scene when it's loaded or is spawned by something that calls the dependency injection container, _prefabService will magically contain the right thing.

It looks pretty clean, but it also obfuscates things. Just looking at the code you won't know where _prefabService comes from. You can search for it all day but you will never find anything assigning to that variable because mtti.Inject sets the value using reflection. I've also encountered bugs caused by dependencies sometimes getting injected in a weird order, GameObjects technically being active before dependencies are injected, injection happening multiple times and so on.

Also, because nothing ever directly assigns to _prefabService here for example you need to be careful so that code stripping doesn't strip away the concrete implementation of IPrefabService.

Manual Dependency Injection

The other way to do dependency injection is to do it manually. If class Child depends on class Parent you pass an instance of Parent to Child's constructor:

class Child
{
    private Parent _parent;

    public Child(Parent parent)
    {
        _parent = parent;
    }
}

I use this way all the time in TypeScript and it works fine. When applied to Unity, there are two problems: firstly MonoBehaviours aren't created like regular C# objects and secondly some aren't created in code at all if they've been added to the scene in the editor.

For creating objects completely from scratch, I'm using a static Create method in the MonoBehaviour script:

using UnityEngine;

namespace mtti.Whatever
{
    public class MyComponent: MonoBehaviour
    {
        public static MyComponent Create(IPrefabService prefabService)
        {
            var obj = new GameObject();
            var myComp = obj.AddComponent<MyComponent>();
            myComp._prefabService = prefabService;
            return myComp;
        }

        private IPrefabService _prefabService;

        private void Update()
        {
            // do something with _prefabService
        }
    }
}

This works best for manager type objects that usually just have one script are invisible an sit at Vector3.zero minding their business. For anything more complicated than that, I make them into a prefab and as a convention I have an Initialize() method that assigns the dependencies, like the fictional IPrefabService above.

I'm not sure yet about this approach. It's clearer where the dependencies come from but it's a bit of a pain to add new top-level dependencies which then need to be injected through every layer and added to every Create or Initialize call along the way which tend to accumulate a fair amount of parameters even in a fairly simple project like this infinite driver game.

Sidenote: GoldSrc Nostalgia

In a sudden bout of nostalgia, I installed the Half-Life 1 SDK this week to see if GoldSrc mapping is still something one can do. It is!

Screenshot of the Valve Hammer editor In-game screenshots of the four different Half-Life scientists

The Half-Life 1 map editor (now called Hammer, I first used it when it was still Worldcraft) was one of my first touches with game development, after Duke Nukem 3D's Build editor. I never made anything playable, but I do remember having fun making some basic scripted sequences, setting up traps for the NPN scientists and staging arena fights between the grunts and the aliens.

The editor seems to work fine. The original build executables from 1998 didn't, so I downloaded ZHLT which worked fine. I still remembered the UI and the different entity names well enough to create a basic room without having to look up 20-year-old tutorials.

I am tempted to try and make a short level of some kind. I have no experience at all with level design and I'd like to learn it. Even the smallest unfinished game prototypes I've made along the years have relied on procedural or random level generation in some way. I guess it's because I'm a programmer that generating levels with code is more in my comfort zone than trying to craft them by hand. We'll see.

Hello. I'm Matti Hiltunen, a Finnish software developer and wannabe game designer. This is my blog about software development, gaming and technology.