Game Design 2 - Movement Patterns
Implementing movement patterns for enemies.
Written the by Thomas Cairns.
Introduction
This post is part three in a five part series. For more details please read previous posts.
Previous posts:
- Game Design 2 - State Manager // 16th Feb
- Game Design 2 - Bounce Mechanic // 8th Feb
Task
One of the problems for me to solve, in order move our project one step closer to our goal, has been to create a system for enemy movement patterns. The basic premise was that enemies will only move along a very simple pattern. For example, moving along a line between two points, moving along the edge of a triangle and so on.
Unity NavMesh
Based on recommendation from one of my team mates, who has previously done 3D work in Unity, I looked into Unity's NavMesh system. The system allows for creation of planes that can be navigated by so called agents. An agent will try to find the shortest possible path between its current position and its targeted destination within a plane. What makes the system interesting is that it allows for both static and dynamic/moving obstacles. This allows you to have multiple moving obstacles that the agent will try to avoid while travelling towards its destination.
The NavMesh sounds just like the kind of system I was looking for, a system that takes care of all pathing logic and is even more advance than anything I could create within the short amount of time we have. However, while I was learning about the system and trying to implement it into our game I stumbled upon two factors that prevented us from using the NavMesh at the current stage of the project.
Axis
The planes of the NavMesh are locked into using the X and Z axis, which fits very well with the default 3D viewport and setup in Unity. The problem is that the 2D viewport is instead centered around using the X and Y axis. In order to use the NavMesh and still work in 2D you would have to rotate the camera and every sprite component, which creates a different kind of pipeline. The pipeline becomes a tiny bit more complex but depending on what kind of features you want it might very well be worth it. However I would say that it's a decision that should be made in the early stages of the project. For us it felt a bit late to change the pipeline half-way through the project even though the game is still small in size.
3D Meshes
In order to us the NavMesh you have to work with meshes, which the name in itself implies. I was hoping that quad meshes would be enough to work with. A quad mesh in Unity is simply four points that together create a 2D rectangle. The quad mesh work perfectly fine in order to create a navigational plane however the objects actually need volume. Which means that all our game objects would require a 3D mesh. This would also append more complexity to the pipeline. Even though the previous problem was enough to discourage us this really put the nail in the coffin.
Disclaimer
However I should however put down a disclaimer that I don't have much experience with Unity and none of the statements above are definite facts. Rather they are my understanding of how Unity works based on the short amount of time I have had to do research. Any corrections are appreciated.
Transform Path
In the end I decided to go with a very simplistic approach. I created a script that fetches the position of each child object within a specified Transform object. Based on each child a relative path is created based on the position of the object that the script is attached to. In order to create a movement pattern I then simply have to create multiple position within another element and then store the collection as a prefab.
Code
CS_Enemy_Movement.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CS_Enemy_Movement : MonoBehaviour { public float speed = 1.0f; public Transform path; private int current = 0; private List<Vector3> targets = new List<Vector3>(); void Start() { if (path != null) { for (int i = 0; i < path.childCount; i++) { targets.Add(transform.position + path.GetChild(i).position); } } } void Update() { if (targets.Count == 0) { return; } if (transform.position == targets[current]) { current = CS_Utils.Mod(current + 1, targets.Count); } transform.position = Vector3.MoveTowards(transform.position, targets[current], Time.deltaTime * speed); } }