The Tests
  • Project Type:
    Self
  • Engine Used:
    Unity
  • Languages Used:
    C#
  • Target Platform:
    PC

A Platformer/Metroidvania made in Unity as part of a 3-day Game Jam. Inspired by games such as Super Metroid and Hollow Knight.

The game consists of a tutorial level and two other levels. The player has to reach the end of each level to reach the next one. The player will encounter locked gates and enemies. The player can dash to attack the enemies and open gates. Some gates require keys to open.

The main features were implemented as follows:

  • A 2D Rigidbody character controller with a camera following it. Extensive work was put in to tweak the player to provide smooth movement.
  • A smooth camera that interpolates to the player’s position but is bound to the confines of the current room. All of this was done via code without using third-party plugins.
// CameraFollow.cs
void FixedUpdate()
{
    if (toFollow)
    {
        Vector3 targetPOS = target.transform.position + offset;
        if (!isChanging)
        {
            Vector3 smoothPosition = Vector3.Lerp(transform.position, targetPOS, smoothFactor);
            smoothPosition.x = Mathf.Clamp(smoothPosition.x, bounds[0].x, bounds[1].x);
            smoothPosition.y = Mathf.Clamp(smoothPosition.y, bounds[0].y, bounds[1].y);
            transform.position = smoothPosition;
        }
        ...
    }
}
...
private Vector2[] CalculateBounds()
{
    Vector2[] newBounds = bounds;
    Vector2 cameraDistance = (new Vector2(Camera.main.aspect, 1)) * Camera.main.orthographicSize;
    Vector2 bound1 = (Vector2)(roomBounds.min) + cameraDistance;
    Vector2 bound2 = (Vector2)(roomBounds.max) - cameraDistance;
    newBounds[0] = new Vector2(Mathf.Min(bound1.x, bound2.x), Mathf.Min(bound1.y, bound2.y));
    newBounds[1] = new Vector2(Mathf.Max(bound1.x, bound2.x), Mathf.Max(bound1.y, bound2.y));
    return newBounds;
}

  • A modular room system where enemies are allotted a room in the world environment and are reset or turned off based on the room the player is in. This was primarily done to save processing power.

  • A simple dash functions as an alternate form of movement and also a mode of interaction with the world. The dash is also used to open doors, making it a versatile action available to the player.
// Gate.cs
void OnTriggerStay2D(Collider2D other)
{
    if (other.gameObject.tag == "Player")
    {
        // Check if player is dashing
        if (other.gameObject.GetComponent<Controller>.GetStatus())
        {
            if (sprite.activeSelf)
            {
                if (gateType == KeyGateType.Normal)
                {
                    sprite.SetActive(false);
                }
                else
                {
                    Debug.Log("not normal gate");
                    if (CheckKey())
                    {
                        GameObject.Find("GameManager").GetComponent<KeyManager>.UseKey(gateType);
                        sprite.SetActive(false);
                    }
                }
            }
        }
    }
}

  • Enemies patrol rooms in an oscillating fashion. If the player contacts the enemies, they will take damage. The player can damage the enemies by using the dash attack.

I have further broken down the development process in this blog that I wrote after said Game Jam at Fall Game Jam or The Tests: Devlog