Design Docs - Thinking About Characters - Part 0 (Interlude)

What Have I Been Up To?

The past two weeks have been really busy for me at my day job. I've been working on a lot of exciting projects, and I have been really focused on getting stuff done.

Pokemon Gameboy games

I've also been playing more Pokemon Red and have made a lot of progress in Final Fantasy 6. I actually walk to Mission Creek Park in San Francisco during my lunch hours everyday and use some of that time to train up some Pokemon. I think people who pass by me probably think it is weird seeing a guy with a Gameboy Advance and a red cartrdige sticking out playing so intensely. They're probably even just surprised for seeing a Gameboy Advance still out in the wild!

For playing only 10-15 minutes at a time, I've gotten my party of 6 to the following levels:

  • Charmeleon (16)
  • Pikachu (15)
  • Nidoran (14)
  • Butterfree (14)
  • Pidgey (14)
  • Geodude (15)

I'm currently at Mt. Moon right now, and have been taking some time to just grind a few random battles before heading off to Cerulean City. My party isn't ideal right now, as I'm hoping to switch out my Butterfree soon for a water-type Pokemon. I also really want to evolve my Nidoran to a Nidorino! Nidoking is one of my favorite Pokemon, so I am just pumped to see that happen.

I've also been using this strategy guide I used to have as a kid. I recently picked it up again back at the Portland Retrogaming Expo. The book is so nostalgic to me.

Players Guide

Patreon Announcement!

Other than gaming, I have also decided that I am going to start compiling all my development notes I have written over the past couple of years (2017 until now), and... brace yourself... turn it into a book!

Yes! This very article you're reading now will one day be a page in my future book!

The Patreon page is here: https://www.patreon.com/rogermakesgames, and it would be nice if you could support me if possible.

If you're strapped for cash, a pat on the back is good too. πŸ˜„

Thinking about Invent... Wait, what?

Yeah, I thought we were done with inventory stuff too!

As it turns out, not quite. I had mentioned previously in Part 6 that HashCode.Combine wasn't available to be ported over to the Unity project. I had also established that the workaround was to implement a temporary solution of overriding GetHashCode with string concatenation.

The solution was something along the lines of:

public override int GetHashCode() 
    {
        var toHash = Name + Weight.ToString();
        return tohash.GetHashCode();
    }
    

Yeah, not very good at all.

Not only was that solution not something I was happy with, I also was not happy that we had to actually duplicated the jrpg-inventory-system code manually by copying and pasting, and rewriting bits and pieces of it just to get the library working with Unity.

I wanted to stay DRY with the jrpg-inventory-system, so copying and pasting defeated the whole point of that! πŸ˜“

So, before I even started thinking about characters and their management, I had decided to fix some of these existing problems. The goal was to finally get jrpg-inventory-system to be a reusable library under Unity (or, anything else).

Unity and .NET Standard 2.0

Originally, the jrpg-inventory-system project was created under dotnet core 3.0. At the time when I had started developing the inventory system, I had no Unity experience. I had no idea that Unity scripts could only be compiled with only either the .NET 4, or .NET Standard 2.0 frameworks.

Due to my ignorance, the jrpg-inventory-system used some very important features that were only available in newer versions of .NET Standard. A perfect example of this was my existing problem of unavailable framework methods -- the methods found in System.HashCode.

For the jrpg-inventory-system's procedurally generated items code, I had to override the GetHashCode method so that Equals would work properly in some of the POCO classes.

I had used System.HashCode.Combine() to do the heavy lifting in generating the appropriate hashcode for an object instance. When I had discovered this method, I was super excited!

However, once I had started porting over code, I ultimately could not use this method since Unity did not natively support any .NET API beyond .NET Standard 2.0.

The quick and dirty fix I had mentioned before was to simply just substitute the implementation with something else.

Well, that worked along with just porting over the existing code into the Unity project. It wasn't until writing Part 7, when I realized that the new code I had written would also have to ported into my existing Unity project.

D'oh!

D'oh!

Okay, well I had thought -- maybe it won't be so bad. It wasn't that much code.

I thought to myself, "Maybe I'll just block off an hour or two during a weekend and just do more of that copy and paste magic." Eewww...

No.

Coupled with the fact that I really felt like I needed to contribute more ont the Unity side of things for my current game project with Scott, I decided it was time to actually slow down, and really focus on learning how jrpg-inventory-system can be useful in a real game.

Well, what better way than to just make one? 😎

Yep, so introducing jrpg-inventory-system-sample, your traditional sample game which demonstrates how to use an existing library!

Yay!

JRPG Inventory System Sample Game - Generating Items

Screenshot of the game

Ripping off the elevator pitch directly from the GitHub repository, the game aims to demonstrate the jrpg-inventory-system library. The purpose of the demo is to:

  1. Test the modularity of the library, and effectiveness of its usability.
  2. Provide a real-world application of the ItemGenerator class found within the jrpg-inventory-system.

The game is a 2D tile-based JRPG that puts a player in a world that spawns a treasure chest. The treasure chest is an object which will constantly give either items or nothing at all to the player. It all just depends on "random-ness".

The treasure chest will close upon the player exiting the proximity of it. This will internally spawn a new item for the player to grab upon the next collision and trigger.

The sample binaries for both Mac OS X and Windows can be downloaded here at the releases page. Give it a whirl!

How the Game was Made

The game was developed with Unity 2018.4.11f1. I also perused OpenGameArt.org for the graphics and sound. If you're really digging the sprites found in the sample game, check out the Zelda-like tileset asset found here:

To supplement all this and to get a better understanding in character/sprite animation, one tutorial I found immensely helpful was a particular article found in Game Code School: http://gamecodeschool.com/unity/simple-2d-sprite-sheet-animations-in-unity/

The article was helpful in that it showed character movement in a single direction, but also explained how animation states worked in Unity. It really opened my mind.

Anyway, step 1 was essentially just to perform the following:

  • Build out a tile map with the tileset found from OpenGameArt
  • Drop in a character into the tile map and have it be able to move in the up, down, left, and right directions without having to worry about the sprite animation "being polished".

I got that done fairly quickly and resulted in this:

Movement first try

Not bad.

Once that was achieved, step 2 was to make things more interesting by adding more elements to the environment and dropping a treasure chest object into the map.

  • Added non-collidable elements into the tilemap (house, trees, etc). By non-collidable I mean that they have no form of collision detection.
  • Polished up the character animation and movement.

The result:

Movement polished

By this phase, I was starting to think about collision detection, and the input events that could trigger the inventory system to generate an item.

Step 3 was then to figure out collisions worked in Unity.

I had huge issues getting this to work initially. One of my main hurdles initially when trying to understand collision in Unity in the 2D context was that objects must have the following components:

  • Rigid Body 2D
  • Box Collider 2D

In general, I had realized that I did not know what Unity game object "components" really were.

It took some time to get used to the terminology, but it had occurred to me somewhere down the line that components can be simply thought of as instance variables fulfilling some sort of logic internally within the game engine for the game object itself.

Okay, maybe to Unity experts, this may be a dangerous way to think of it, or it can be brilliant as heck. But right now, it works for me, so I'll stick to that train of thought -- until at least I run into some other issue down the line. πŸ˜„

Once I understood the component concepts, I knew that all I had to do was create the Rigid Body 2D component to be a static solid object, and a Box Collider 2D with the defined areas, and trust the engine to handle the collision detection!

Be gentle about this experience and don't judge me! I have always been writing my own collision detection for things, and really, I wasn't used to some of the cool automatic stuff that Unity provides. πŸ˜„

It was super satisfying to see the collision detection working after some time.

Movement collision

After I had gotten the basic game play mechanics down, I thought it was then appropriate to begin modularizing jrpg-inventory-system, and plug it into the sample game.

Modularizing jrpg-inventory-system

I immediately knew that the first step in getting a compiled library of jrpg-inventory-system to be recognized properly by Unity was to first get the project built against the .NET Standard 2.0 framework, rather than dotnet core 3.0.

I first changed the InventorySystem.csproj settings to build against C# 7.0, not reference mscorlib.dll, and allowing unsafe code.

Version 7 of C#

The approach in the thinking behind this was that downgrading the framework would then not cause any language incompatibilities.

My assumption was that this would reduce the number of modifications and re-writes I would have needed to get the project to compile again.

The process was rather smooth. I think I only had 1 error come up during this and that was due to declaring an interface method public.

The next step to getting the jrpg-inventory-system library compatible for Unity was to figure out the whole System.HashCode situation. Since this library was introduced in .NET Standard 2.1 rather than 2.0, I had to figure out what to substitute this with.

I had two paths:

  1. Come up with the "old school" way of implementing it myself through the use of some basic arithmetic, and hashing.
  2. Figure out how to get the existing System.HashCode found in .NET Standard 2.1, into the project.

Fortunately, (2) was an option!

Microsoft had created a package to polyfill the System.HashCode class into .NET Standard 2.0 with the Microsoft.Bcl.HashCode package.

This was exactly what I had needed! Upon downloading that and including it into the jrpg-inventory-system project, it was time to do a framework switch.

The switch was not that obvious. Since the project was dotnet core 3.0, the project switcher only had dotnet core related SDKs I could choose from.

What was really needed to be done was actually manually specifying the project to use .NET Standard 2.0 by hand.

I opened up the InventorySystem.csproj file and edited the project file to use netstandard2.0 found in the TargetFramework element:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <OutputType>Library</OutputType>
  </PropertyGroup>
 
  ...
 
</Project>

After commiting these changes, the project was now being read as .NET Standard 2.0:

Project framework change

Whew! The project built just fine. I published the project using Visual Studio 2019. The final build consisted of the following DLLs:

  • InventorySystem.dll
  • Microsoft.Bcl.HashCode.dll
  • Newtonsoft.Json.dll

Awesome... all these were exactly what I had referenced in my project. The next step was to now get those DLLs and migrate them into Unity.

That part was easy. Yes, really!

Adding the Library

Importing the jrpg-inventory-system package to the Unity project was as simple as dragging and dropping the DLLs into the Unity Editor's Project tab while in the Assets folder. Doing so allowed Unity to automatically import the libraries into the current working project.

To test this out, I had created a TreasureCollision.cs script in Unity that would attach to the treasure chest game object.

The purpose of the script was to read the collision of the player sprite and treasure chest in the map. If the objects had collided, and the player invokes the SPACE key on their keyboard, then the treasure chest would be triggered to open and generate a random item off from the InventorySystem library.

Two other interactive elements would also be triggered.

  1. A label indicating the type of item acquired is displayed on the bottom left corner of the screen
  2. Sound is played to indicate to the user that an event had occurred
using System.Collections.Generic;
using InventorySystem.PgItems;
using UnityEngine;

public class TreasureCollision : MonoBehaviour
{

    static string DefaultText = "Check the treasure chest!";
    static string ReceivedItemConditionName = "receivedItem";

    private Animator anim;
    private ItemGenerator itemGenerator;
    private string ItemsDbContent;
    private string PrefixDbContent;
    private string SuffixDbContent;
    private string DropSourceDbContent;
    private List<Item> items;
    private List<Affix> prefixes;
    private List<Affix> suffixes;
    private DropSource treasureChest;
    private bool receivedItem;
    private bool isCollided;
    private AudioSource[] audSrc;
    private string text;
    
    void Start()
    {
        ItemsDbContent = Resources.Load<TextAsset>("Items").text;
        PrefixDbContent = Resources.Load<TextAsset>("Prefixes").text;
        SuffixDbContent = Resources.Load<TextAsset>("Suffixes").text;
        DropSourceDbContent = Resources.Load<TextAsset>("TreasureChests").text;

        audSrc = GetComponents<AudioSource>();
        items = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Item>>(ItemsDbContent);
        prefixes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Affix>>(PrefixDbContent);
        suffixes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Affix>>(SuffixDbContent);
        treasureChest = Newtonsoft.Json.JsonConvert.DeserializeObject<List<DropSource>>(DropSourceDbContent)[0];
        itemGenerator = new ItemGenerator(items, prefixes, suffixes);
        anim = GetComponent<Animator>();

        anim.SetBool(ReceivedItemConditionName, receivedItem);

        text = DefaultText;
    }

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

    void OnCollisionExit2D(Collision2D other)
    {
        isCollided = false;
        receivedItem = false;
        anim.SetBool(ReceivedItemConditionName, receivedItem);

        text = DefaultText;
    }

    void OnGUI()
    {
        GUI.Label(new Rect(10, 680, 400, 20), text, new GUIStyle()
        {
            fontSize = 24,
            fontStyle = FontStyle.Bold
        });
    }

    // Update is called once per frame
    void Update()
    {
        var openKeyPressed = Input.GetKey(KeyCode.Space);

        if(openKeyPressed && isCollided && !receivedItem)
        {
            var item = itemGenerator.GenerateItem(treasureChest);
            if (item.Name.Equals("NoDrop"))
            {
                var clip = audSrc[1];
                clip.Play();
                text = "Aww, you didn't get anything!";
            }
            else
            {
                var clip = audSrc[0];
                clip.Play();
                text = $"You have received a: {item.Name}";
            }

            receivedItem = true;
        }

        anim.SetBool(ReceivedItemConditionName, receivedItem);
    }
}

Yes, the code is probably not using "best Unity practices", and I'm not aware of them yet... but... it all works!

And here it is!

Demo

Where To Get It

You can download the released version of the library here. The current verison is 1.0.1, and can be imported to pretty much any .NET Standard 2.0 project. So if you're not really feeling Unity, but your current engine uses .NET Standard 2.0, it'll work with that too!

Concluding Thoughts

This experience has made me decide that I will for sure develop the jrpg-character-system in a modular way using .NET Standard 2.0 from the start.

In the next chapter, let's start designing what we need for our character system, and get a project set up for this.

Stay tuned!