Diary of a hobby gamer: Part one - Ambition

I've always loved playing games but I've never really understood how they were made at a code level. I've decided I want to learn from the ground up and decided to write a blog about my journey. Why not use Unity3D you say? Well I took a stab at it and decided to pump the brakes on that to understand the core essence of games first. I will definitely give Unity another try at some point in the future. This is part one of a series of blogs on my learning process on building an entire game with it's own engine.

Ability assessment

When taking on 'building a game engine', you either have to be crazy or super curious. I'm probably both. It also requires a ton of ambition. How dare I decide to write a game engine when I have no idea how to do so? Since there's not really much in terms of resources online to build your own engine, I decided to try on my own based on my own set of skills. So I took stock of what I'm pretty good at:

  • Software patterns
  • Network interaction
  • C# backend
  • Windows Services
  • SQL
  • Web frontend
  • Git
  • Photoshop
  • Photography
  • Cloud services

Based on that list, you'd think I'd naturally building a browser based game based on a web stack. And it does look attractive to do so, however I really like C# -- a lot. Therefore I wanted to see what I could outside of the traditional web stack. Another reason for shunning the web stack is that I've tried that before and crashed and burned.

I'm also good at Photoshop, but then I thought about the amount of time it takes to make a simple sprite animation; I began to think that perhaps I'll leave the game building to a large shop complete with artists.

What if?

I asked myself, "what if I wasn't too worried about flashy graphics?". That will free me from having to spend a ton of time on artwork just to get my engine running. 

Also, "what if I had a dedicated client instead of assuming I had to use a web browser?".

I was starting to imagine a simple console application that displayed retro-like text in the form of the MUD games I used to play in the late 80's.

At this point I had no idea how to interact with a user. Perhaps a command line that took in commands?

One thing that bothered me was I didn't like the idea of an constantly vertically scrolling block of text.

Then I asked myself, "what if my client acted like a TV screen where it redrew each line over the top of the old lines?".

Can a console app on windows even do that??

It can.

So for my game, I'm simply going to try to create a game inside of a console application.

Structures and concepts

Once I got the idea that I can just redraw lines of text over old ones in a controlled manner, I figured I now unlocked the secret to making my game engine. For my first proof of concept I decided that a 'map' could simply be a two dimensional text file with rows and columns.

However I didn't want maps that could only fit on my console screen which is limited to font size and monitor size.

To facilitate large maps, I devised a way to load the 2D maps into a buffer and only display a small portion that I call the window (viewport).

Then I was faced with the issue of having a single 'view' for my game but I knew full well that I needed to do other things like show an options screen, a splash screen and perhaps an inventory screen.

The next structure for me to create was a scene. A scene is essentially just like a 'view' in the web world but without templating.

So if you think of a game as simply a series of scenes (start screen, explore a map screen, inventory screen), I just needed to switch between the scenes. Therefore I have a scene manager. It simply decides how to load a different scene.

One of my essential scenes it the ExploreMapScene. This handles many things inside:

  • Rendering a map in a window
  • Moving the player and handle any other valid input
  • Detecting collision

The good news is that detecting collisions in a 2D world is pretty simple. The real design decision I had to make is what to do when I detected a collision. I started out with a simple if\then statement but that quickly becomes spaghetti code. Therefore I decided that I'd dynamically instantiate a handler class. Essentially, when you collide with a certain character; create and invoke a handler.

The other big issue I had was accepting input without blocking execution. That is actually easily done with code like the following:

public void HandleInput(IMap map)
{
    if (Console.KeyAvailable)
    {
        var input = Console.ReadKey(true);
        var player = ClientContext.Instance.Player;

        if (input.KeyChar == _moveLeftKey && player.CurrentColumn > 0 &&
            _collisionDetector.CanMoveToPosition(map, player.CurrentRow, player.CurrentColumn - 1))
        {
            player.CurrentColumn--;
        }
}

More game things

Games run in mostly infinite loops it seems. To redraw a scene, you have to do so many times per second to adjust for movement of things. Also the main game itself has to run in a mostly infinite loop. My main console app file is very spartan, but you'll see some of the concepts I've mentioned:

using System;
using log4net.Config;
using RetroMud.Core.Context;
using RetroMud.Core.Events.Helpers;
using RetroMud.Messaging.Publishing;
using RetroMud.Core.Healthchecks.Messages;
using RetroMud.Core.Players.Messages;
using RetroMud.Core.Scenes;
using RetroMud.Core.Status;

namespace RetroMud
{
    class Program
    {
        private static readonly Random Rand = new Random();
        private static ISendTcpMessages _messenger;

        static void Main(string[] args)
        {
            XmlConfigurator.Configure();

            Console.ReadKey(true);

            EventHelper.RegisterAllClientEventHandlers();

            Console.WindowWidth = 150;
            Console.WindowHeight = 40;

            var clientId = Rand.Next(123123123);
            var clientVersion = "0.1.0";

            _messenger = TcpMessengerFactory.GetMessenger();

            Console.WriteLine("Sending healthcheck info...");

            var healthCheckResponse = _messenger.Send<CurrentClientVersionResponse>(new CurrentClientVersionRequest
            {
                ClientId = clientId,
                CurrentVersion = clientVersion
            });

            Console.WriteLine($"Requires upgrade: {healthCheckResponse.RequiresUpgrade}");
            Console.Clear();

            ClientContext.Instance.Player = _messenger.Send<GetPlayerResponse>(new GetPlayerRequest
            {
                PlayerId = 1
            }).Player;
        
            ClientContext.Instance.GameSceneManager = new GameSceneManager();
            ClientContext.Instance.GameSceneManager.ChangeToNextScene(new StartSplashScene());

            ClientContext.Instance.StatusMessageManager = new StatusMessageManager();

            while (ClientContext.Instance.GameSceneManager.CurrentGameScene != null)
            {
                ClientContext.Instance.GameSceneManager.CurrentGameScene.Render();
            }

            Console.WriteLine("Game over!");

            Console.ReadKey();
        }
    }
}

Another concept that is reflected above is the character is fetched from a server. I'm still trying to understand the role of a remote server, but for now it's a place for healthchecks (need to upgrade or not) and to grab a character off the server.

Scenes, scenes, scenes

Scenes are the window to the user. So as an illustration of what a scene looks like, please see the opening splash code below:

using System;
using RetroMud.Core.Context;
using RetroMud.Core.Scenes.Helpers;

namespace RetroMud.Core.Scenes
{
    public class StartSplashScene : IGameScene
    {
        public void Render()
        {
            while (IsSceneActive)
            {
                Console.SetCursorPosition(0, 0);
                Console.CursorVisible = false;
                Console.WriteLine();

                LogoHelper.RenderLogo();

                Console.WriteLine();
                Console.WriteLine($"Version 0.0.1");

                Console.WriteLine();
                Console.WriteLine($"Copyright Kevin Giszewski 2017");
                Console.WriteLine();

                Console.WriteLine("Press any key to continue...");

                if (Console.KeyAvailable)
                {
                    ClientContext.Instance.GameSceneManager.ChangeToNextScene(new ExploreMapScene(1));
                }
            }
        }

        public bool IsSceneActive { get; set; }
    }
}

As you can see in the code above, we're simply just rendering a mostly infinite loop and listen to the input stream for any key to be pressed. Once a key has been pressed, a new scene is loaded.

The logo is displayed with a helper method that uses glorious ASCII art:

using System;

namespace RetroMud.Core.Scenes.Helpers
{
    public static class LogoHelper
    {
        public static void RenderLogo()
        {
            Console.WriteLine("  ██████╗ ███████╗████████╗██████╗  ██████╗ ███╗   ███╗██╗   ██╗██████╗ ");
            Console.WriteLine("  ██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔═══██╗████╗ ████║██║   ██║██╔══██╗");
            Console.WriteLine("  ██████╔╝█████╗     ██║   ██████╔╝██║   ██║██╔████╔██║██║   ██║██║  ██║");
            Console.WriteLine("  ██╔══██╗██╔══╝     ██║   ██╔══██╗██║   ██║██║╚██╔╝██║██║   ██║██║  ██║");
            Console.WriteLine("  ██║  ██║███████╗   ██║   ██║  ██║╚██████╔╝██║ ╚═╝ ██║╚██████╔╝██████╔╝");
            Console.WriteLine("  ╚═╝  ╚═╝╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝ ╚═╝     ╚═╝ ╚═════╝ ╚═════╝ ");
        }
    }
}

If this interests you at all, you can follow me on Twitter as I stumble through things :)

Summary

To sum up, I'm making a game engine based on very limited knowledge on how a game engine actually works. By stringing together scenes and dispatching handlers based on input, the possibilities are endless.

This is a hobby game and is turning out to be be pretty fun to do. It gives me a chance to practice software patterns in ways that my regular 9-5 doesn't always allow me to. In the coming posts, I'll go through some of the gotchas and issues I've encountered so far. The best part is that while the game is being written, I'm not sure what the future may hold!