Game Components

As a self taught programmer I often worry that I am at a disadvantage when compared to those who have had formal education in programming, but I do know that I have just as much, if not more desire to make games. One thing I have learned is that there are practically infinite ways to write code that will in the end have the same output. Code can be written to follow certain design patterns, which will dictate how future code is written. Ultimately, the resulting game will play the same, but during the development of a game it is very desirable to use a design pattern that reduces the current workload, and potentially the future workload when adding new features or making a new game. Today I will discuss the usage of the component-entity design pattern for my new game Sulfur.

What is the component-entity design pattern?

In a nutshell, all of the interactive or dynamic elements of a game (e.g. the player, the enemies, the bullets) are called entities. Normally, each entity would have its own class, which would contain the code that runs every frame for this entity. In the traditional inheritance design pattern, these classes might inherit from simpler classes. So a Player might inherit from Human, which might inherit from Entity. In the component pattern, entities are built up out of smaller components, which are logical separations of the behaviour each entity exhibits. Lets say the player in my game can move around, shoot bullets and be shot. So it should have the components Mover, Shooter, and Health. When a bullet collides with the player, instead of calling a function in the Player script, it calls TakeDamage() in the Health script. This is a gross simplification, but illustrates the point I think.

There are a lot of different names for this design pattern, and a lot of people have written articles about it. There are also vastly different (and very strong) opinions on how to implement this pattern, but I’m not going to get into it too much. My justification for the use of this pattern is that it will save me time in not only this project, but also future projects. I will do my best to keep my components generic and reusable.

What components will I use in Sulfur?

For the player/enemies:
Entity
- has a faction
- has a list of opponent entities
- can Die()

Health
- has a float HealthValue
- can TakeDamage()
- when HealthValue is below or equal to 0, it calls Entity.Die()

Inventory
- has a list of Items
- has a currently equipped Item
- can PickUp(), Drop(), Equip()

ItemUser
- can UseItem()

Mover
- has a float Speed
- can Move()

PlayerController
- can GetInput(), which turns input into commands for other components

NPCController
- can Think(), which outputs commands for other components

For weapons:
Item
- has a string DisplayName
- can Use()

Fireable
- has a float TimeBetweenShots
- can Use()

Ammo
- tracks ammo count of a weapon
- can StartReload()

For bullets:
Projectile
- has a float Speed
- has a float Damage
- can Launch(), Collide()

How will I apply this in Unity3D?

Fortunately for me, the engine I am using for Sulfur is Unity3D. Unity has a system that is very conducive to the component-entity design pattern, and in fact a lot of its out of the box functionality was written in a way that makes it quite compatible with this pattern. For example the CharacterController component.

Ideally, each component that I write would be as flexible and reusable as the CharacterController component. I will strive to keep my components independent of each other, and therefore interchangeable. When I do get around to making another game I will hopefully be able to pick up all of my components from Sulfur and use them again. However, it is inevitable that some dependencies exist between components, for example: when the Health component has its function TakeDamage() called, and it’s health value falls below or equal to 0, it calls Entity.Die(). Therefore, the Health component now requires an Entity component. To avoid that, I could move the Die() function over to the Health component. There will always be some dependencies though, I am sure I will run into some. The task at hand then, is to minimize dependencies.

Before I conclude this post, I will leave you with some example code from Sulfur that demonstrates how the weapon components work together. I have a prefab called AssaultRifle that contains these components:

Ammo component

using UnityEngine;
using System.Collections.Generic;

public class Ammo : CmpMonoBehaviour
{
    [SerializeField]
    private int ammoLoaded;
    public int AmmoLoaded { get { return ammoLoaded; } }
   
    [SerializeField]
    private int ammoReserve;
    public int AmmoReserve { get { return ammoReserve; } }
   
    [SerializeField]
    private int ammoClipSize;
    public int AmmoClipSize { get { return ammoClipSize; } }
   
    [SerializeField]
    private int ammoMaxCarry;
    public int AmmoMaxCarry { get { return ammoMaxCarry; } }
   
    [SerializeField]
    private float reloadDuration;
    public float ReloadDuration { get { return reloadDuration; } }
   
    private bool reloading = false;
    public bool Reloading { get { return reloading; } }
   
    private Timer reloadTimer;
   
    protected override void Awake()
    {
        base.Awake();
       
        reloadTimer = new Timer(reloadDuration);
        reloadTimer.Stop();
    }
   
    private void Update()
    {
        if (reloading)
        {
            if (!reloadTimer.Started)
            {
                reloadTimer.Reset();
                reloadTimer.Start();
            }
           
            if (reloadTimer.Complete())
            {
                reloadTimer.Stop();
                FinishReload();
            }
        }
    }
   
    public void StartReload()
    {
        reloading = true;
    }
   
    private void FinishReload()
    {
        reloading = false;
       
        // if we have more or exactly the size of a clip in reserve, give a clip
        if (ammoReserve >= ammoClipSize)
        {
            ammoLoaded = ammoClipSize;
        }
        // else give whatever we've got
        else
        {
            ammoLoaded = ammoReserve;
        }
    }
}

Fireable component

using UnityEngine;
using System.Collections.Generic;

[RequireComponent (typeof(Ammo))]
public class Fireable : CmpMonoBehaviour
{
    [SerializeField]
    private GameObject emittedPrefab;
   
    [SerializeField]
    private float timeBetweenShots;
   
    private Timer fireTimer;
   
    protected override void Awake()
    {
        base.Awake();
       
        fireTimer = new Timer(timeBetweenShots);
        fireTimer.Start();
    }
   
    public bool Use(Vector3 direction, Entity source = null)
    {
        // reloading? dont fire.
        if (mAmmo.Reloading)
        {
            return false;
        }
       
        // no ammo? reload
        if (mAmmo.AmmoLoaded <= 0)
        {
            mAmmo.StartReload();
            return false;
        }
       
        if (fireTimer.Loop())
        {
            GameObject bullet = (GameObject)Instantiate(emittedPrefab);
            bullet.transform.position = mTransform.position;
            bullet.transform.rotation = Quaternion.LookRotation(direction);
            bullet.transform.Translate(Vector3.forward * 1f);
           
            bullet.GetComponent<Projectile>().Launch(source);
           
            return true;
        }
       
        return false;
    }
}

Item component

using UnityEngine;
using System.Collections.Generic;

public class Item : CmpMonoBehaviour
{
    [SerializeField]
    private string displayName;
    public string DisplayName { get { return displayName; } }
   
    [SerializeField]
    protected bool isPrimaryItem = true;
    public bool IsPrimaryItem { get { return isPrimaryItem; } }
   
    public void Use(Vector3 direction, Entity source = null)
    {
        if (mFireable != null)
        {
            mFireable.Use(direction, source);
        }
    }
}

They inherit from CmpMonoBehaviour, which inherits from Unity’s MonoBehaviour. CmpMonoBehaviour includes cached references to the other components, so they can be accessed easily.

Thank you for reading, I hope you found this post interesting and/or useful. I would love your feedback! You can leave a comment here, email me at , or send a tweet to @cmvanb.

Posted in Blog

Leave a Reply

Your email address will not be published. Required fields are marked *

*

YrO685

Please type the text above:

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>