Design Docs - Thinking about Characters - Part 4

Life Reflection Thing

When I had initially started writing these documents relating to game development, I had no intention in actually chronicling my experiences in creating a JRPG. Originally, when I had written my first article in the series, I really was just in need of some brain dump where I could share my thoughts in inventory management. Who I wanted to share it with has been sort of my partner, and I'd say pretty good pal now in this whole JRPG-hobby thing, Scott.

We have been interacting with each other more often in the past months. Scott and I actually go back a little further than that. Almost 8 years. That's strange to think since we had barely spoken to each other until recently.

We used to work together at Yardi Systems. You could say that he was my tech lead for a short period. I had come on board to the team about a few months before his departure to start Mutated Software.

Anyway, we didn't get back in touch until this past summer when Scott messaged me on Twitter to check up on a dead RPG project I had worked on. He asked if I'd be willing to work on a JRPG hobby project with him. Of course, I responded in the lines of "fuck yes", but without the F-bomb.

The experience has been wonderful. It has been nice to just talk about JRPGs to someone who gets it. That's rare in my world, as most people who I talk to about programming tend to be about the latest web framework, or some new JavaScript thing, or another little SaaS thing.

For myself, I became a software developer specifically to create games. Scott has reinvigorated my passion and reasoning as to why I even got into computers and software to begin with.

It doesn't really matter whether or not this hobby project becomes of anything to me. Selfish to say, for myself, I have already reaped tons of benefits from just working with Scott on the project. Most of it has been learning more about myself, and finding philosophical answers as to why I do things the way I do:

  • Why do I even find programming fun?

    • Everyone I know of sees it as work. I don't. It's okay to be passionate about something others may find it hard to believe to be enjoyable.
    • I think writing code also let's me play out the inner-God complex. Making something out of nothing, and with complete control can be quite fun in that regard.
  • For a person like myself, why shouldn't I accept that I like to play the same games over again? That's unproductive in the world of everyone being highly productive, right?

    • I consider video games as art. We can look at the same painting, or sculpture over again. That's okay right? We can also listen to the same song many times and still enjoy it as much as the first time. Why not video games? It has writing, visual art and music!
  • Why should I invest in learning how to make games, when I won't make any money from it?

    • I have now come to terms in that making a game is just a personal battle for myself to finally do what I had set out to do when I had first cracked open my first programming book as a kid in elementary school hoarding 16-bit SNES ROMs on a dial-up internet connection. It goes beyond making more money, or having it be a career, or anything of that sort. I just want to fulfill my dream.
    • Besides, I enjoy my day job anyway. It's easier to say what I just said when all that is well, I guess.
  • Why not learn more web stuff so I can be better at work-related things?

    • I do crave web projects from time to time. Web stuff is like cake, but sometimes, I just want pie.
  • Why do I keep considering myself as a non-gamer just because I happen to like playing games for enjoyment of the art-form and story rather than the brutal ego-busting (or boosting) challenges involved?

    • I think by definition, a "gamer" can appreciate video games as art, and as means to just cruise and unwind. I've come to accept that I'll always be shitty at games like Counter-Strike, or StarCraft. But I can always enjoy a game of Diablo, Final Fantasy X or Sim City 2000.
    • I also like to play lame edutainment stuff too... By the way fun fact, I think Oregon Trail is overrated... I was always a Zoombinis kid.

Zoombinis

 

Anyway, it's been a fun ride so far, so thanks, dude. πŸ‘

Back to Work

We have worked through a lot of code in the past couple of chapters, making our characters come to life. Although unit tests are nice to confirm the thought processes behind how character statistics and status effects are handled with our Characters, it is even better to see it in action.

I would like to slow down again when it comes to adding more features. The plan was to start implementing skills to our characters, but I feel like right now is a good time to create a new game demo using the code that has been written over the the course of these posts.

Getting the Code

All code can be found in my GitHub. If you're interested in the code discussed specifically in this chapter, the code repository can be found here: https://github.com/urbanspr1nter/jrpg-sample-game.

Since we are building on top of this game over time, you'll want to take a look at the Demo0 snapshot of the branch!

Demo Overview

We're going to create a very simple JRPG game using Unity. The overall goal of the game is very simple:

  • Two characters are in the party. Each character can be switched back and forth to be the active character.
  • The party goes around the map gathering items represented by treasure chests.
  • The party may choose to consume the items gathered. The active party member is the one who by default, is consuming the item.
  • If the character becomes poisoned through consumption of an item, the player must figure out how to remove this status effect with the current items in possession.

This game represents the teaching of the basic mechanics in:

  • Receiving an item through loot.
  • Learning that status effects can be harmful to the success of the adventure.
  • Removing status effects by using the appropriate items.

The ultimate goal is to really demonstrate status effects, however the benefit is a bigger demo is that one can appreciate all the features that has been built so far, working as a cohesive whole to realize that we are one step closer to building a JRPG. πŸš€

Here's what the finished product looks like:

Full Demo

It will be fun!

Tooling and Assets

For this demo I used the newest beta (as of December, 2019) of Unity, 2019.3.0f1. I'm still a Unity newbie, but I don't think a slightly older version is going to hold one back from making this project work.

The characters Alexa, and Roger, were actually drawn to be based on my wife and myself. Err... well, to be based on us looking at our best. πŸ˜† There is no way I look that good in real life. The concept art was originally drawn by my sister, Janelia Ngo.

Alexa

Roger

The character sprite assets were from an older project I had worked over a year ago. I had commissioned them from JR Cheney when I was actually attempting to create a game with a real storyline. His work is awesome.

Alexa sprite

Roger sprite

I am reusing the Zelda-Like Tilesets and Sprites material from OpenGameArt.org. These are the same tilesets I had used in the previous demo. The only difference here is that I have upscaled the tilesets to be in 24x24 units instead of 16x16. I had done this using Adobe Photoshop and the nearest neighbor interpolation algorithm.

I think I'll one discuss filtering in a future post, and why I think nearest neighbor interpolation is the best "easy upscaling" filter for pixel art. Who'd like to read something like that? πŸ˜„

Jrpg.System

I can't find a more appropriate time than now to make this announcement. I would like to introduce everyone to a new project, jrpg-system! This project combines both the inventory, character and party management code I have written so far in the past 11 articles or so.

My hope is that this project will continue to grow, and eventually become a reliable framework to serve as a data/backend layer for a JRPG using any game engine.

Aside from just wanting to test our character code, I am using this demo as an opportunity to dogfood the Jrpg.System features and API that exist so far.

Therefore, if you inspect the project, you will find several DLLs being referenced in the prefixed form of Jrpg.*. Yes, this is exactly what it is -- built version of the above project.

If you're interested in seeing more, here is a link to the Jrpg.System code repository: https://github.com/urbanspr1nter/jrpg-system.

Implementing the Game

We'll first want to create a new Unity project. Using Unity, I started out with a 2D profile. Once the bare project was created, I decided to build the game in 2 phases: designing the scene, and scripting all objects.

The general approach for each phase can be summarized below.

Scene Design

  1. Create tilemaps, and tile palettes needed to build out a simple scene.
  2. Create the scene using the tile palettes.
  3. Slice the character sprite assets.
  4. Create a basic player object and use the Cinemachine camera asset to track the player in the scene.
  5. Animate all movements of the character using the Unity Animator.
  6. Add the 3 treasure chests onto the scene.
  7. Add all Box 2D colliders to every place in the scene in which we do not want the characters walking through.

Scripting Objects

  1. Create initialization code, and a main game state singleton to house all objects in which will be shared across the game.
  2. Add scripts to allow the player to walk across the scene.
  3. Add scripts to manage the game's party and render out the UI for debugging purposes.
  4. Add scripts to trigger the action of acquiring items and using (consuming) them.
  5. Add scripts to handle the status effects which may result from using specific items.

Scene Design

Creating the tilemaps and tile palettes needed to build out the scene was easier this time around. I think it is because I am now more familiar with the Unity user interface, and the concepts involved with it. I basically took the upscaled tilesets, and configured the units to be 24x24, and have the sprites be represented as multiple slices.

It was just slice 'n dice from there!

Sprite editing

Drawing scenes are usually the hard part for me in that I want to always try to create something aesthetically pleasing, and of course, something that makes sense. I personally feel that I am not naturally creative in this area. I am always tempted to put a lot of houses into the scene if it is a town scene, or a lot of random objects which scream out HEY YOU ARE IN A TOWN!.

However, after playing more 16-bit JRPGs recently, I learned that it is really good to use a lot of objects relating to nature, such as trees and water to "fill" a scene. It also creates environments which can appear larger, but keeps your development time and effort reasonable as these elements cause areas to remain inaccessible. The result is that there is less code to write to deal with certain areas in a game.

Slicing the character assets was similar to creating the tile palettes. The only extra work involved here is that there is a character object associated with these sprite assets. So creating that, I had to animate all the basic movements for both Alexa and Roger. I decided to take advantage of the Unity Animator tool, and created several states for the characters. The states all represent a direction of movement.

Walk animation

Each movement was sampled at 6 frames.

Animation states

Notice how I tied all the animation states together? This helps with the anmation when the character is switched back and forth between Alexa, and Roger. Most the logic is controlled through a script where a few variables are set to transition to a new state.

One thing that has impressed me with Unity has been the fact that tracking a character on screen with the orthographic camera is super simple. I have been so used to implementing my own cameras, that I was absolutely blown away when I had first hooked up the Cinemachine Unity asset to track the player on the scene.

All I had to do was download the asset, and configure a couple of components to lock in on the player game object!

Cinemachine camera dolly

Adding the treasure chests on screen involed creating 3 game objects with the treasure chest sprite. I am still impressed that the treasure box sprites are so well done. It really does feel like it is something out of a 16-bit Zelda game.

I would say the most tedious part of building the scene was adding a bunch of Box Collider 2D components to every place where we didn't want the player traversing to. I haven't figured out a good way to create components with colliders attached to them yet, but of course, I am sure there is some way to do it in Unity, and I just haven't had the knowledge yet! πŸ˜„

Box Colliders

Phew, once all that was done, I had finally built my beautiful scene. One thing I find reassuring is that after building out a few scenes a few times, and just getting back into playing a few JRPG games, my imagination has gotten better.

Complete scene

Here's what I would come up with back in the day for "demos":

Old demo

It gives me hope for myself that I can actually improve on something related to game development which is outside of my expertise, and easily appreciated in a visual sense.

Scriping Objects

The Global Game Store: MainGame

In order to allow the game objects in our Unity game to make calls across each other, I found that creating a simple class called MainGame on top of the GameStore object found in Jrpg.System would be an okay approach to hold additional game state.

If you're not familiar with the singleton pattern, this is a pretty good introduction to what it is, but I'll explain more in the context of this game.

The GameStore itself is a singleton object exposed by Jrpg.System and we want our MainGame class to access the functions of this store, and also extend it with other functionality pertaining to the state of the current game which the GameStore does not explicitly implement.

A singleton class will usually have:

  • A singular, and global (static) instance of its type which starts out as uninitialized.
  • A private constructor.
  • An static method to retrieve the singular instance, or initialize one if uninitialized.

In Jrpg.System, here is what GameStore looks like with all its other helpers removed (an abridged versionπŸ˜†). I had left one instance variable, DataStore in this class to demonstrate how the constructor can be used to initialize any class instance variables.

Jrpg.System.GameStore (Abridged)
using System;
using System.Collections.Generic;

namespace Jrpg.System
{
    public class GameStore
    {
        private static GameStore Instance;

        public Dictionary<string, object> DataStore { get; }

        private GameStore()
        {
            DataStore = new Dictionary<string, object>();
        }

        public void Put<T>(string key, T value)
        {
            DataStore.Add(key, value);
        }

        public T Get<T>(string key)
        {
            if(!DataStore.ContainsKey(key))
            {
                throw new NullReferenceException("Key does not exist in Data Store.");
            }

            return (T)DataStore[key];
        }

        public static GameStore GetInstance()
        {
            if(Instance == null)
            {
                Instance = new GameStore();
            }

            return Instance;
        }
    }
}

We create a global instance called Instance, and never expose the default constructor out to clients. This default constructor is marked as private as only the GameStore class itself can create a new instance and assign it to the Instance variable.

Clients wishing to initialize the Instance may only do so once when they are wanting to retrieve it. If the Instance is uninitialized, then the GetInstance method will then initialize the object by calling the private constructor before returning back to the client, the instance.

GetInstance is the only way to retrieve this singleton, and it is made accessible through making this method a static method so that it does not rely on any sort of GameStore object to be used. Instead, one accesses this method through the class.

Now, moving back to our demo, MainGame is a wrapper around this GameStore singleton. As mentioned before, it contains additional logic relevant to our game, and should be the primary way in which we manipulate the global data within the game.

The intention was to use MainGame as yet another singleton wrapper around the GameStore object. We are allowed to retrieve the GameStore directly by invoking the Store method.

As for making sure that there isn't more than one single instance of MainGame, I didn't choose to explicitly build in this manner this time around, but left it open for that. Therefore, you will find that there is no private constructor in MainGame yet.

However, the approach is the same. Most methods in the MainGame class are static, and therefore there is no need to worry about instantiating the object for using it, as the class holds global variables relating to the game along with a GameStore itself.

MainGame.cs (Abridged)
using System.Collections;
using System.Collections.Generic;
using Jrpg.System;
using Jrpg.InventorySystem.PgItems;
using Jrpg.PartySystem;
using UnityEngine;

public class MainGame
{
    public static Vector2 PreviousPosition = new Vector2(0, 0);
    public static bool Initialized { get; private set; }

    public static Party Party()
    {
        return Store().MainParty;
    }

		/*
			Other static utility methods
		*/
  
    public static GameStore Store()
    {
        if (!Initialized)
        {
						/*  
							ALL INITIALIZATION CODE HERE
						*/
            Initialized = true;
        }

        return GameStore.GetInstance();
    }
}

Making Characters Walk

With this state management implemented, we can proceed to make use of the animation states, and conditionals to make our player game objects walk!

The game play mechanic for controlling the characters is minimal. We just want our characters to be able to move with the standard W, A, S, and D keys, and also alternatively UP, LEFT, DOWN, and RIGHT.

Since this demo demonstrates the concept of a JRPG party, the player object implements not only a single character, but two! In order to allow switching between the first and second character in the party, the keyboard keys Z and X will toggle back and forth between the first and second characters respectively.

Looking back at our animation states, there are a total of 10 states which need to be considered. There are 2 transitions per state: (1) entry into the state, (2) exit out of the state -- making for a total of 18 transitions to be considered.

Animation states

As the party gets bigger, the number of states increase, and the number of transitions will intimidate just about anyone. This is something I am aware of, and is in my to-do list in learning how to get better at Unity. 😎.

Anyway, our our script file which will be attached to the player object is called Walking.cs. It will hold a ton of logic relating to translating the game object across the scene. There are a few other responsibilities belonging to this script in which I would like to call out.

Since we want to implement an effect of Poison in our game, we must take into consideration the status effects on the character while walking, and also the steps which are being taken in order to continually apply damage while a character is in the poisoned state.

The solution is hacky for now, but it is good enough demonstrate the real purpose of this demo in showing an implementation of status effects.

With that being said, our Walking script will also be responsible for:

  1. Status effect life cycle methods are handled in this script.
  2. Steps are "measured" and once a delta is reached, the PerformEffect status effect life cycle method is executed.

The logic for Walking is quite large, so I will just be referencing bits and pieces of it in this part of the discussion. The full code can easily be referenced in the GitHub repository.

Our Walking class exposes several Unity Sprite objects to represent the character in the idle state in different directions.

    public Sprite alexaIdleDown;
    public Sprite alexaIdleUp;
    public Sprite alexaIdleLeft;
    public Sprite alexaIdleRight;

    public Sprite rogerIdleDown;
    public Sprite rogerIdleUp;
    public Sprite rogerIdleLeft;
    public Sprite rogerIdleRight;

These sprites are actually assigned manually in the Unity Editor.

Idle sprite assignment

In order to make the process in checking against a current state, or setting a state easier, we can define some constants to represent the animation state names. These match exactly the animation state names that were created in the Unity Animator tool.

    private class StateNames
    {
        public static string AlexaIdle = "AlexaIdle";
        public static string AlexaUp = "AlexaUp";
        public static string AlexaDown = "AlexaDown";
        public static string AlexaLeft = "AlexaLeft";
        public static string AlexaRight = "AlexaRight";

        public static string RogerIdle = "RogerIdle";
        public static string RogerUp = "RogerUp";
        public static string RogerDown = "RogerDown";
        public static string RogerLeft = "RogerLeft";
        public static string RogerRight = "RogerRight";
    }

Since we are using the Unity Animator tool to control the state transitions, there are a few conditionals which are used by both the script and the Animator tool to move from one state to another.

    private class DirectionFlagKeys
    {
        public static string FacingUp = "FacingUp";
        public static string FacingDown = "FacingDown";
        public static string FacingLeft = "FacingLeft";
        public static string FacingRight = "FacingRight";
    }

We can then set the flag by retrieving the animator instance, and then calling one of its Set methods to set the condition variable to the intended value. Here is an example.

// Current state is AlexaIdle

var animator = GetComponent<Animator>();

// Set the player object to face up
animator.SetBool(DirectionFlagKeys.FacingUp, true);

// Transition will move from the AlexaIdle state to AlexaUp state

Defining a bunch of helper methods to just make life easier in setting these variables:

    private void FaceLeft()
    {
        Animator.SetBool(DirectionFlagKeys.FacingLeft, true);
        Animator.SetBool(DirectionFlagKeys.FacingUp, false);
        Animator.SetBool(DirectionFlagKeys.FacingRight, false);
        Animator.SetBool(DirectionFlagKeys.FacingDown, false);
    }

    private void FaceRight()
    {
        Animator.SetBool(DirectionFlagKeys.FacingLeft, false);
        Animator.SetBool(DirectionFlagKeys.FacingUp, false);
        Animator.SetBool(DirectionFlagKeys.FacingRight, true);
        Animator.SetBool(DirectionFlagKeys.FacingDown, false);
    }

    private void FaceUp()
    {
        Animator.SetBool(DirectionFlagKeys.FacingLeft, false);
        Animator.SetBool(DirectionFlagKeys.FacingUp, true);
        Animator.SetBool(DirectionFlagKeys.FacingRight, false);
        Animator.SetBool(DirectionFlagKeys.FacingDown, false);
    }

    private void FaceDown()
    {
        Animator.SetBool(DirectionFlagKeys.FacingLeft, false);
        Animator.SetBool(DirectionFlagKeys.FacingUp, false);
        Animator.SetBool(DirectionFlagKeys.FacingRight, false);
        Animator.SetBool(DirectionFlagKeys.FacingDown, true);
    }

    private void Idle()
    {
        Animator.SetBool(DirectionFlagKeys.FacingLeft, false);
        Animator.SetBool(DirectionFlagKeys.FacingUp, false);
        Animator.SetBool(DirectionFlagKeys.FacingRight, false);
        Animator.SetBool(DirectionFlagKeys.FacingDown, false);
    }

Here is what a transition is configured to be like in the Unity Editor.

Sample transition

I'll explain what ActiveCharacter is when we dive into the discussion in switching in between party members, but I just wanted show the interoperability between the Unity script code and the Animator tool, and how one can leverage the two to make character movement simple.

You may have noticed that in our previous demo, the character sprite would fall back to facing downwards when no movement event is triggered by the keyboard. This was because we had fallen back to the default sprite assigned to the game object when no keyboard event was detected.

In order to make the experience a bit more "polished", we had introduced the idle sprite references, but we need to make use of them by setting a PreviousState whenever the player has transitioned to an idle state. This PreviousState should then allow the game to render the appropriate sprite representing the idle direction it is currently in (IdleLeft, IdleRight, etc).

    private void SetPreviousState()
    {
        var currentState = Animator.GetCurrentAnimatorStateInfo(0);
        var activeCharacterName = CurrentParty.GetActiveCharacter();

        if (activeCharacterName.Equals(AlexaTag))
        {
            if (currentState.IsName(StateNames.AlexaUp))
            {
                PreviousState = StateNames.AlexaUp;
            }
            else if (currentState.IsName(StateNames.AlexaLeft))
            {
                PreviousState = StateNames.AlexaLeft;
            }
            else if (currentState.IsName(StateNames.AlexaRight))
            {
                PreviousState = StateNames.AlexaRight;
            }
            else if (currentState.IsName(StateNames.AlexaDown))
            {
                PreviousState = StateNames.AlexaDown;
            }
        }
        else if (activeCharacterName.Equals(RogerTag))
        {
            // Handle the other character here.
        }

        // Perform the AfterEffects() status effect life cycle method here.
    }

Well, okay now we have the PreviousState set, we now need to make sure that the sprite graphic corresponding to the idle state actually gets rendered.

In Unity, the Animator manages a lot of the sprite representation stuff, but we can actually get around some of it by overriding the LateUpdate method found in the MonoBehaviour base class.

The Unity documentation has a good discussion in Order of Execution for Event Functions. The Animation Update Loop is the phase where a lot of the Animation controller logic is handled. Notice how it happens after the Update method. This means that setting the sprites programmtically in Update, or FixedUpdate will not work as the animation loop will override all of that.

Instead, LateUpdate is a method in which we can leverage to programmatically set the sprites as the Animation Update Loop would have finished executing by then.

    // Fun fact, override the sprite set by animator here.
    private void LateUpdate()
    {
        var currentState = Animator.GetCurrentAnimatorStateInfo(0);

        if (currentState.IsName(StateNames.AlexaIdle))
        {
            if (PreviousState.Equals(StateNames.AlexaLeft))
            {
                SpriteRenderer.sprite = alexaIdleLeft;
            }
            else if (PreviousState.Equals(StateNames.AlexaRight))
            {
                SpriteRenderer.sprite = alexaIdleRight;
            }
            else if (PreviousState.Equals(StateNames.AlexaUp))
            {
                SpriteRenderer.sprite = alexaIdleUp;
            }
            else if (PreviousState.Equals(StateNames.AlexaDown))
            {
                SpriteRenderer.sprite = alexaIdleDown;
            } else
            {
                SpriteRenderer.sprite = alexaIdleDown;
            }
        }
        else if (currentState.IsName(StateNames.RogerIdle))
        {
            // Handle the other character here
        }
    }

Handy utility methods to check whether a character has 0 HP yet, or has fallen are also needed. I'm not going to go over them in detail as I think the code here is pretty self-explanatory.

    private void CheckFallenEvent()
    {
			if(// Check if character has fallen AND if current HP is 0.) 
      {
        MainGame
          .Store()
          .StatusEffectManager.RemoveEffect(
          	MainGame.Party().GetMember(AlexaTag), 
          	StatusEffectType.Poison
        	);
        MainGame.Log($"{AlexaTag} has fallen!");
        AlexaFallen = true;
      }
      if(// Handle other character here)
      {
      	/* ... */
      }
    }
        
    private void AdjustHp()
    {
        if (// Check if character has less than 0 for their CURRENT HP)
        {
          MainGame
          	.Store()
            .MainParty
            .GetMember(AlexaTag)
            .Statistics[Jrpg.CharacterSystem.StatisticType.HpCurrent].CurrentValue = 0;
        }
        if ( // Handle other character here)
        {
        	/* ... */
        }
    }

Our last two utility methods relate to the translation of the player object within the scene. As with the previous demo, UpdateVelocity exists to set the new velocity for the RigidBody component in the player game object.

    private void UpdateVelocity()
    {
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");

        float newHorizontal = h * MaxSpeed;
        float newVertical = v * MaxSpeed;

        RigidBody.velocity = new Vector2(newHorizontal, newVertical);
    }

Addtionally, we also now want to measure the distance between steps, and we can use the basic distance formula to do that:

    private float GetDistance()
    {
        return Mathf.Sqrt(
            Mathf.Pow(RigidBody.position.x - MainGame.PreviousPosition.x, 2) +
            Mathf.Pow(RigidBody.position.y - MainGame.PreviousPosition.y, 2)
        );
    }

Alright, now that we have most of this class explained, it's actually now time to implement the FixedUpdate method to handle the input events to move the character!

Our FixedUpdate method will be responsible for performing the following in order:

  1. Execute the BeforeEffect status effect life cycle methods
  2. Read whether or not a key has been pressed, and apply the necessary values to transition the animation state.
  3. Get the current distance between the current position and last position set.
  4. If the total distance is over some number, call the PerformEffect status effect life cycle methods.

    // Update is called once per frame
    void FixedUpdate()
    {
        MainGame.Store().StatusEffectManager.BeforeEffects();

        CheckFallenEvent();

        var keyLeftPressed = Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.A);
        var keyRightPressed = Input.GetKey(KeyCode.RightArrow) || Input.GetKey(KeyCode.D);
        var keyUpPressed = Input.GetKey(KeyCode.UpArrow) || Input.GetKey(KeyCode.W);
        var keyDownPressed = Input.GetKey(KeyCode.DownArrow) || Input.GetKey(KeyCode.S);

        if (!(keyLeftPressed || keyRightPressed || keyUpPressed || keyDownPressed))
        {
            SetPreviousState();

            // No key is being held down.
            Idle();

            // Make the sprite still.
            RigidBody.velocity = new Vector2(0, 0);
            return;
        }

        if (keyLeftPressed)
        {
            FaceLeft();
        }
        else if (keyRightPressed)
        {
            FaceRight();
        }
        else if (keyUpPressed)
        {
            FaceUp();
        }
        else if (keyDownPressed)
        {
            FaceDown();
        }

        var distance = GetDistance();
        if (distance >= 2.25)
        {
            MainGame.PreviousPosition = new Vector2(RigidBody.position.x, RigidBody.position.y);
            MainGame.Store().StatusEffectManager.PerformEffects();
            AdjustHp();
        }

        UpdateVelocity();
    }

Since this is a demo, I had just decided to stick the AfterEffects call within LateUpdate. Since most of this stuff is still being thought out, I'll leave the flexibility in where one decides to call the status effect life cycle methods throughout the game. Maybe one can come up with some cool effects this way?

Treasure!

Bruno Mars Treasure

We have created three game objects within our scene representing loot, or treasure. These treasure game objects will contain unrandomized items: Rotten Fish, Antidote, and Potion.

Our drop source database will look like this in JSON format:

[
    {
        "Name": "Potion",
        "Level": 1,
        "ItemClass": [
            {
                "Name": "Potion",
                "Weight": 15
            }
        ]
    },
    {
        "Name": "Poison",
        "Level": 1,
        "ItemClass": [
            {
                "Name": "Rotten Fish",
                "Weight": 15
            }
        ]
    },
    {
        "Name": "Antidote",
        "Level": 1,
        "ItemClass": [
            {
                "Name": "Antidote",
                "Weight":  15
            }
        ]
    }
]

And our items database will look like:

[
    {
        "Name": "NoDrop",
        "ItemClass": [],
        "Properties": [],
        "Value": 0,
        "BodyPart": "Default"
    },
    {
        "Name": "Potion",
        "ItemClass": [],
        "Properties": [
            {
                "Name": "HP Current",
                "Value": 10
            }
        ],
        "Value": 200,
        "BodyPart": "Default"
    },
    {
        "Name": "Rotten Fish",
        "ItemClass": [],
        "Properties": [],
        "Value": 10000,
        "BodyPart": "Default",
        "IsKeyItem": true,
        "PublishHandler": "PoisonHandler, Assembly-CSharp"
    },
    {
        "Name": "Antidote",
        "ItemClass": [],
        "Properties": [],
        "Value": 200,
        "BodyPart": "Default",
        "IsKeyItem": true,
        "PublishHandler": "AntidoteHandler, Assembly-CSharp"
    }
]

The PublishHandler in each of the item entries references the fully qualified asssembly name of the handler class to build the message that will be sent to a subscriber when the item is consumed. In this Unity project, the game scripts are compiled into a DLL called Assembly-CSharp. Since our classes are not under any namespaces, we can just reference the class names as-is.

Picking up and consuming Rotten Fish will afflict a negative status effect by poisoning the character. Antidote will alleviate the Poison status effect, and finally, Potion restores a fixed number of HP to the character. All these items are applied in the context of the active character, which is the current character being shown in the scene.

Thankfully, the Treasure game script attached to all these game objects isn't as big as Walking, but it does bring in an interesting discussion as it is a good example in demonstrating the usage of key items in the game.

Treasure.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Jrpg.InventorySystem;
using Jrpg.InventorySystem.Items;
using Jrpg.InventorySystem.PgItems;

public class Treasure : MonoBehaviour, IItemSubscriber
{
    private bool collided;
    private Item item;

    private void Acquire()
    {
        var baseItem = new BaseItem(item);
        baseItem.Register(this);

        MainGame.Party().ReceiveItem(baseItem, ItemReceiveAction.Treasure);
        MainGame.Log($"Received: {baseItem.Name}.");

        gameObject.GetComponent<BoxCollider2D>().enabled = false;
        gameObject.GetComponent<SpriteRenderer>().enabled = false;
    }

    void OnCollisionEnter2D(Collision2D collision)
    {
        collided = true;
    }

    void OnCollisionExit2D(Collision2D collision)
    {
        collided = false;
    }

    void Update()
    {
        if (collided && Input.GetKey(KeyCode.Space))
        {
            GetComponent<AudioSource>().Play();

            item = MainGame
                .ItemGenerator()
                .GenerateItem(
                    MainGame.DropSourcesDb()["Treasure"]
                        .Find(t => t.Name.Equals(gameObject.tag)
                    )
                );

            Acquire();
        }
    }

    public void Publish(Dictionary<string, object> message)
    {
        MainGame.Log($"{message["MemberName"]} {message["Message"]}");
    }
}

Notice how the Treasure class not only implements the MonoBehaviour class, but it also implements an IItemSubscriber class which had been built during our adventures in Thinking about Inventory.

As with the previous demo, the Spacebar will invoke the event to Acquire the item, but the handling of this event is a little different this time around.

For this particular demo, a BaseItem object is created from the item definition, but this time, since the Treasure class itself is an IItemSubscriber, it will register itself against the BaseItem that was generated so that during consumption, the Treasure::Publish method is called.

The Treasure::Publish method doesn't do much right now aside from just spitting out a log message indicating that the item had been used.

Now, let's take a look at the PublishHandler themselves. We have 2 specific ones: PoisonHandler, and AntidoteHandler. I know, they're not really named well, and should probably be named PoisonPublisher and AntidotePublisher, or something like that. πŸ˜„

PoisonHandler.cs
using System;
using System.Collections.Generic;
using Jrpg.System;
using Jrpg.ItemComponents;
using Jrpg.CharacterSystem;
using Jrpg.CharacterSystem.StatusEffects;
using Jrpg.InventorySystem.Items;

public class PoisonHandler : IPublishHandler
{
    public Dictionary<string, object> GetMessage(Dictionary<string, object> parameters)
    {
        var afflictedCharacter = (Character)(parameters["AfflictedCharacter"]);
        var item = (ItemInfo)(parameters["Item"]);

        MainGame
          .Store()
          .StatusEffectManager.ApplyEffect(afflictedCharacter, StatusEffectType.Poison);

        return new Dictionary<string, object> {
                { "MemberName", afflictedCharacter.Name },
                { "Message", $"was poisoned by eating {item.Name}" }
            };
    }
}

These publishers are actually very simple! For our PoisonHandler, we just consume the parameters and use that information to apply the status effect. Then, we send back a payload to the subscriber which will then receive any other information as an acknowledgement that the handler has completed its action.

AntidoteHandler.cs
using System;
using System.Collections.Generic;
using Jrpg.CharacterSystem;
using Jrpg.CharacterSystem.StatusEffects;
using Jrpg.InventorySystem.Items;
using Jrpg.ItemComponents;

public class AntidoteHandler : IPublishHandler
{
    public Dictionary<string, object> GetMessage(Dictionary<string, object> parameters)
    {
        var afflictedCharacter = (Character)(parameters["AfflictedCharacter"]);
        var item = (ItemInfo)(parameters["Item"]);

        MainGame
          .Store()
          .StatusEffectManager.RemoveEffect(afflictedCharacter, StatusEffectType.Poison);

        return new Dictionary<string, object> {
                { "MemberName", afflictedCharacter.Name },
                { "Message", $"was healed by using {item.Name}" }
            };
    }
}

The AntidoteHandler is similar to PoisonHandler in that it just removes the poison effect from the AfflictedCharacter.

Next, let's dive into the really big class. This one is the one I am a bit ashamed of since it is just so darn big with a lot of code doing different things tied together. 😒

GameParty

The naming sort of sucks here, but it does describe the original intention of the class, haha. It is supposed to handle the switching of the characters within the party, and taking the animation states of one character to another.

This class can be improved immensely as it has turned to also being an event handler for using items within the inventory, displaying logging information and rendering the main game "HUD". Whew... lot's of stuff, but it's all managed to be crammed into 273 lines of code. So, it really isn't all as scary as I am making it out to be.

For what it is worth though, I'm going to step through each section in the GameParty class for the sake of education.

Awake!

The Awake method can be thought of as the constructor of the game object for our purposes. In this specific usage, we override this method to set up our party members, active party member and the textures needed to render out the HUD.

    private void Awake()
    {
        AlexaTag = MainGame.Store().Get<string>("AlexaTag");
        RogerTag = MainGame.Store().Get<string>("RogerTag");

        Alexa = new Character(AlexaTag);
        Roger = new Character(RogerTag);

        PlayerAnimator = GameObject
          .FindGameObjectWithTag("Player")
          .GetComponent<Animator>();

        MainGame.Party().AddMember(AlexaTag, Alexa);
        MainGame.Party().AddMember(RogerTag, Roger);
        MainGame.Party().SetActiveCharacter(AlexaTag);
        MainGame.Store().SetGameState(Jrpg.GameState.GameStateValue.Map);

        var ActiveCharacterMap = MainGame
          .Store()
          .Config.Get<Dictionary<string, int>>("ActiveCharacter");
      
        ActiveAlexaValue = ActiveCharacterMap["Alexa"];
        ActiveRogerValue = ActiveCharacterMap["Roger"];
        UsingItem = false;

        // UI Related
        PartyHudBackground = new Texture2D(1, 1);
        PartyHudBackground.SetPixel(0, 0, new Color(0.7f, 0.7f, 0.0f, 0.45f));
        PartyHudBackground.Apply();

        LogBoxBackground = new Texture2D(1, 1);
        LogBoxBackground.SetPixel(0, 0, new Color(0.7f, 0.7f, 0.0f, 0.45f));
        LogBoxBackground.Apply();

        NormalStatusBackground = new Texture2D(1, 1);
        NormalStatusBackground.SetPixel(0, 0, new Color(0.0f, 0.8f, 0.3f, 0.5f));
        NormalStatusBackground.Apply();

        PoisonStatusBackground = new Texture2D(1, 1);
        PoisonStatusBackground.SetPixel(0, 0, new Color(0.8f, 0.0f, 0.3f, 0.5f));
        PoisonStatusBackground.Apply();
    }

Just to warn you, I have heard many things about OnGUI being deprecated, and that we shouldn't use it. I have personally found it to be invaluable as a part of my development processes. It is extremely easy to use and intuitive.

Therefore, I don't know whether or not, I am applying best practices when it comes to building this little HUD using OnGUI, but this paradigm has worked out for me so well as of now.

When Unity then invokes the Start method, we immediately set the animation controller's ActiveCharacter variable to the corresponding integer indicating the specific Character object.

Using the MainGame global state class here, we have a super easy time just accessing the party directly to get the active character across all scripts in our game.

    void Start()
    {
        if (MainGame.Store().MainParty.GetActiveCharacter().Name.Equals(AlexaTag))
        {
            PlayerAnimator.SetInteger("ActiveCharacter", ActiveAlexaValue);
        }
        else
        {
            PlayerAnimator.SetInteger("ActiveCharacter", ActiveRogerValue);
        }
    }

Interactivity

As mentioned before, the Z, and X keys are the controls which allow the player to switch back and forth between characters in the party. The overridden Update method handles this case by just invoking SetActiveCharacter in our Jrpg.PartySystem.Party class. It also sets the animation controller variable to render out the sprite needed to represent the active character.

The Update method also takes care of handling the calls from the player to use an item. The method HandleUseItemEvent will bind the first 3 number keys 1, 2, and 3 to the items that were picked up from the treasure chests.

The HUD display will indicate which key is associated with a specific item.

I am very aware that the HandleUseItemEvent is a bit awkwardly implemented in that I use a lot of known facts about the overall scene. (Ex: Explicitly checking whether a Potion is being used.) My justification for this is that the demo is small enough in that should I choose to make it more intelligent, I can do so in the future.

The main thing I want to call out here is that for items which are not Potion, I assume that they are specifically key items, and thus I pass in a Dictionary object to serve as parameters for the publish handler of that item to use.

    void Update()
    {
        if (Input.GetKey(KeyCode.Z))
        {
            MainGame.Store().MainParty.SetActiveCharacter(AlexaTag);
            PlayerAnimator.SetInteger("ActiveCharacter", ActiveAlexaValue);
        }
        if (Input.GetKey(KeyCode.X))
        {
            MainGame.Store().MainParty.SetActiveCharacter(RogerTag);
            PlayerAnimator.SetInteger("ActiveCharacter", ActiveRogerValue);
        }

        HandleUseItemEvent();
    }

    private void HandleUseItemEvent()
    {
        if (UsingItem)
            return;

        if (!(Input.GetKeyDown(KeyCode.Alpha1) || 
              Input.GetKeyDown(KeyCode.Alpha2) || 
              Input.GetKeyDown(KeyCode.Alpha3)))
            return;

        var index = 0;
        if (Input.GetKeyDown(KeyCode.Alpha1) && !UsingItem)
        {
            index = 0;
        }
        if (Input.GetKeyDown(KeyCode.Alpha2) && !UsingItem)
        {
            index = 1;
        }
        if (Input.GetKeyDown(KeyCode.Alpha3) && !UsingItem)
        {
            index = 2;
        }

        UsingItem = true;

        var itemList = MainGame.Party().QueryAllItems().FindAll(i => i.Quantity > 0);

        if (index >= itemList.Count)
        {
            index = itemList.Count - 1;
        }

        if (index == -1)
        {
            return;
        }

        var itemInfo = itemList[index];
        MainGame.Log($"Used: {itemInfo.Name}.");

        var activeCharacter = MainGame.Party().GetActiveCharacter();
        if (!itemInfo.Name.Equals("Potion"))
        {
            var parameters = new Dictionary<string, object> { 
              { "AfflictedCharacter", activeCharacter }, 
              { "Item", itemInfo } 
            };
            MainGame.Party().UseItem(itemInfo.Name, activeCharacter, parameters);
        }
        else
        {
            MainGame.Party().UseItem(itemInfo.Name, activeCharacter);

            // Used a potion but it healed more than needed... Lazy for now... Just adjust the HP if over the max
            if(activeCharacter.Statistics[StatisticType.HpCurrent].CurrentValue 
               > StatisticTypeCollection.MaxValues[StatisticType.HpCurrent])
            {
                activeCharacter.Statistics[StatisticType.HpCurrent].CurrentValue 
                  = StatisticTypeCollection.MaxValues[StatisticType.HpCurrent];
            }
        }

        UsingItem = false;
    }

HUD

Finally the rest of the methods in this GameParty class relate to a lot of the HUD stuff. For those who don't know what I mean by HUD, I don't mean the Department of Housing and Urban Development, but rather, this type of HUD:

Extreme HUD

Okay, okay, not to that extreme, but basically inspired by transparent displays in military aircraft, these are widgets which display on screen to give information about the overall state of the game back to the player.

HUD

I decided to implement that with this demo to make it easier to show what the player is trying to do.

Most of the logic happens in a set of utility methods where OnGUI is the entry point. I decided to keep the background transparent-yellow as it was the most aesthetically pleasing color to display the black text foreground.

I'm not going to go through all the methods here, but let's take RenderCharacterStatusEffects for example:

    private void RenderCharacterStatusEffects(Character character, Rect rect, GUIStyle textStyle)
    {
        // new Rect(958, 70, 96, 16)
        var statusEffectTypes = BuildStatusEffectTypesString(character);

        if (statusEffectTypes.Length > 0)
        {
            GUI.skin.box.normal.background = PoisonStatusBackground;
            GUI.Box(rect, GUIContent.none);
            GUI.Label(new Rect(rect.x + 2, rect.y + 2, 500, 100), $" Effects {statusEffectTypes}", textStyle);
        }
        else if (statusEffectTypes.Length == 0)
        {
            GUI.skin.box.normal.background = NormalStatusBackground;
            GUI.Box(rect, GUIContent.none);
            GUI.Label(new Rect(rect.x + 2, rect.y + 2, 500, 100), $" Effects NONE", textStyle);
        }
    }

I basically assign the background to be the PoisonStatusBackground, or NormalStatusBackground texture that was created in Awake. These are just red-ish and green-ish rectangles.

I specify that text should also be rendered over these rectangles through the use of GUI.Label.

Using this approach, I was also able to render out log messages onto the bottom of the screen, and character party information.

One thing that I am curious about is the performance impact of this type of approach to rendering out GUI-related things. When I have some time down the line, I will try profiling the code to see the impact.

Conclusion

Alrighty, so we actually have a more interactive game! I actually had a few other ideas to put into this demo, which I didn't really find time for.

  • Create a second scene, and also various gates in one scene which can be unlockable through a "key". Unlocking a gate in one scene takes the player to the second scene!
  • Create an NPC, or two, and have some event trigger when the player has invoked some interactive event through input.
  • Flash the screen whenever the character takes a step when they are poisoned.

Maybe you can extend this demo and implement those ideas. ☝️