Thomson Gunardi Teguh

Games and Graphics Programmer

Retro Rabbit

Specifications

  • Platform: Android
  • Engine: Unity3D
  • Language: C#
  • Tools: Git, Visual Studio

Summary

An endless runner that takes place in a spherical world that is shrinking overtime.  The goal is to survive as long as possible by plugging leaks, dodging obstacles, and eating carrots to activate MUNCH MODE.

The game was originally made during Ludum Dare 38 with the theme: A Small World.

Responsibilities

  • Create and implement a system that allows game objects to spawn and move on the spherical playing field with correct orientation
  • Integrate art assets into the game, adding effects to certain assets where necessary
  • Generate obstacles, fissures, and carrots as well as handling each object’s collision and it’s appropriate effect on the rabbit
  • Create shrinking mechanic and apply it to every single object on the sphere, making sure that the shrinking of each object is handled appropriately (e.g each object should still stand on the surface of the sphere even though the sphere has shrunk in size)

 

Movement

As Retro Rabbit takes place on a spherical world, movement around the sphere was done using rotations and not translations.  Here is an overhead view of the game, showing a view of one of the infinitely many sides of the sphere:

This was accomplished by simply using a Rotator game object that is the parent of any moving game object on the world.  The Rotator object’s orientation determines the position of an object on the sphere.  Hence, any sort of player movement is done by rotating the orientation of the rotator.

public class Rotator : MonoBehaviour {

    public Vector3 axis;
    public float angularSpeed;

    private float radius;

    void Update () 
    {
        if (!Mathf.Approximately(angularSpeed, 0))
        {
            radius = transform.GetChild(0).transform.localPosition.y;
            float anglePerTime = (angularSpeed / radius);
            axis = axis.normalized;
            transform.localRotation *= Quaternion.AngleAxis(anglePerTime * Time.deltaTime, axis);
        }
    }
}

The above script rotates the rotator’s orientation around a particular axis.  Hence, in order for the player to turn left and right in a way that the player is able to make a 360 degree turn, the axis of rotation needs to change based on input.

private void MovementControl()
{
    Vector3 axis = this.gameObject.GetComponentInParent<Rotator>().axis;
    float angleChange = turnSpeedRadians * Time.deltaTime;

    int touchResult = CheckTouchForMovement();
    bool movePressed = Input.GetButton("Horizontal") || touchResult != 0;
    bool moveRight = Input.GetAxisRaw("Horizontal") > 0 || touchResult > 0;

    if(!(currentState == PlayerState.DEAD))
    {
        if(movePressed)
        {
            if(moveRight)
            {
                angleChange *= -1;
            }

            //apply 2D rotation to x and z component of rotation axis of rotator
            float newX = Mathf.Cos(angleChange) * axis.x - Mathf.Sin(angleChange) * axis.z;
            float newZ = Mathf.Sin(angleChange) * axis.x + Mathf.Cos(angleChange) * axis.z;
            this.gameObject.GetComponentInParent<Rotator>().axis = new Vector3(newX, 0, newZ);

            //apply the rotation to the localRotation of player to rotate the player model accordingly
            transform.localRotation *= Quaternion.AngleAxis(-angleChange * Mathf.Rad2Deg, Vector3.up);
        }
    }
}

A 2D rotation on the x and z value of the axis was applied.  This is done so that the player’s forward direction changes in a circular motion.  Additionally, the player’s orientation also needs to change accordingly so that the player model turns with the change in direction.

Shrinking

The world shrinks at a rate that is dependent on the number of fissures that are active around the world.  As the world shrinks, objects on the world need to shrink its radius/distance from it’s rotator object in order for the object to stay on the surface of the shrinking world.

void Update () 
{
    if(fissureManager.spawnCount > 0 && world.transform.localScale.x > minShrinkScale)
    {
        Shrink();
    }
}

private void Shrink()
{
    float totalShrinkRate = shrinkRate * fissureManager.spawnCount;

    //shrink the world's radius
    world.transform.localScale -= new Vector3(totalShrinkRate, totalShrinkRate, totalShrinkRate);

    float percentageScale = world.transform.localScale.x / worldStartRadius;

    //shrink the radius of the world objects
    Transform rotator;
    Transform worldObj;
    for (int i = 0; i < worldObjects.transform.childCount; i++)
    {
        rotator = worldObjects.transform.GetChild(i);
        worldObj = rotator.transform.GetChild(0);
        worldObj.localPosition -= new Vector3(0, totalShrinkRate / 2, 0);

        //shrink the size of fissure to match world
        if(worldObj.tag == "Fissure")
        {
            worldObj.transform.localScale = fissureStartScale * percentageScale;
        }
    }
}

Only fissures shrink in size.  This is to increase the game’s difficulty over time.  Here is a look at how the world shrinks at a rate faster than the intended in-game rate:

Next Post

© 2020 Thomson Gunardi Teguh

LinkedIn | GitHub | thomsongunardi@hotmail.com