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.
I made use of state machines for the player and boss.
Some features made by me for this project include:
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.
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.
In the editor it will look something like this:
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; ListtriangleCenters; 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.
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.