<- Back

The Ice Hiker

Made with Unity
Space Invaders Clone Gameplay Screenshot

This is a project that I worked on with 3 other people. This game is about a mountainclimber who is climbing a very tall mountain and has to survive a fight with the mystical creature that protects the mountain.

Controls

Features

I made use of state machines for the player and boss.
Some features made by me for this project include:

Interesting problems

Boss charge attack

The charge attack consists of two parts. The boss first charges up before flying towards to player. To inform the player that the boss is preparing an attack I made the boss scale up and down the y-axis and added some particles which go to the boss. To make the boss scale up and down I created a ShakeBehaviour component.

public class ShakeBehaviour : MonoBehaviour
{
    [SerializeField] private float lowerBoundScale;
    [SerializeField] private float upperBoundScale;
    [SerializeField] private float speed;
    private bool goingToUpperBound;
    private Vector3 originalScale;
    void Start()
    {
        if (speed <= 0)
        {
            throw new System.Exception("speed should be a non-zero positive number");
        }

        if (upperBoundScale <= lowerBoundScale)
        {
            throw new System.Exception("upperBoundScale should be greater than lowerBoundScale");
        }

        originalScale = transform.localScale;
        lowerBoundScale *= originalScale.y;
        upperBoundScale *= originalScale.y;
        transform.localScale = new Vector3(transform.localScale.x, lowerBoundScale, transform.localScale.z);
        goingToUpperBound = true;
    }

    void Update()
    {
        Vector3 scaleVelocity = Vector3.up * speed * Time.deltaTime;
        transform.localScale += goingToUpperBound ? scaleVelocity : -scaleVelocity;
        
        if (goingToUpperBound && transform.localScale.y >= upperBoundScale)
        {
            goingToUpperBound = false;
        }
        else if (!goingToUpperBound && transform.localScale.y <= lowerBoundScale)
        {
            goingToUpperBound = true;
        }
    }

    public void ResetScale()
    {
        transform.localScale = originalScale;
    }
}
        

After that I made the boss fly in the direction of the player. I used an exponential function to calculate the height the boss had to be to smoothly fly downwards.

The result can be viewed below.

Enemy state machine

We wanted the boss's behaviour to be easily changable in the Unity editor. So I created a state machine where you add each state sepperatly to a GameObject in the inspector. You can also choose which state exits to which other state in the editor as well.

State machine UML

In the editor it will look something like this:

State machine UML

Dynamic terrain spikes

Because this game is about climbing we wanted to have a way to add obstacles to any climbable object. My solution for this was a TerrainIcicleGenerator component. This component generates icicles (spikes) around the model of a GameObject.

public class TerrainIcicleGenerator : MonoBehaviour
{
    Mesh mesh;
    List triangleCenters;
    List terrainIcicles;
    [SerializeField] private GameObject terrainIcicle;
    
    void Start()
    {
        mesh = GetComponent().mesh;
        triangleCenters = new List();
        terrainIcicles = new List();
        for (int i = 0; i < mesh.triangles.Length; i += 3)
        {
            triangleCenters.Add(
                (mesh.vertices[mesh.triangles[i]] + mesh.vertices[mesh.triangles[i + 1]] + mesh.vertices[mesh.triangles[i + 2]]) / 3
            );
            terrainIcicles.Add(null);
        }
    }

    void Update()
    {
        for (int i = 0; i < triangleCenters.Count; i++)
        {
            RaycastHit hit;
            Vector3 startPosition = triangleCenters[i];
            startPosition.x *= transform.localScale.x;
            startPosition.y *= transform.localScale.y;
            startPosition.z *= transform.localScale.z;
            startPosition += transform.position;
            Vector3 triangleNormal = (startPosition - transform.position).normalized;

            // Check to see if there is no object in front of the triangle of the mesh
            if (!Physics.Raycast(startPosition, triangleNormal, out hit, 5))
            {
                float randomNumber = Random.Range(0f, 500f);

                // If there isn't an icicle / spike already at this specific triangleCenter there is a 1 / 500 chance for it to spawn
                if (terrainIcicles[i] == null
                    && Mathf.Abs(triangleNormal.y) < 0.7f
                    && randomNumber < 1)
                {
                    GameObject actualTerrainIcicle = Instantiate(terrainIcicle, transform.parent);
                    actualTerrainIcicle.transform.position = startPosition;
                    actualTerrainIcicle.transform.forward = triangleNormal;
                    terrainIcicles[i] = actualTerrainIcicle.GetComponent();
                }
            }
        }

        for (int i = 0; i < terrainIcicles.Count; i++)
        {
            // Once an icicle is ready to be destroyed remove it
            if (terrainIcicles[i] != null && terrainIcicles[i].GetReadyToBeDestroyed())
            {
                Destroy(terrainIcicles[i].gameObject);
                terrainIcicles[i] = null;
            }
        }
    }

}
        

The result can be seen below.

Rolling snowballs using object pooling

We wanted to have snowballs that grow in size and that roll down the mountain. And in order to improve the performance I made use of object pooling.

Snowball object pooling UML