1. Building an RTS game in Unity - Basic Unit Navigation and selection tool (2024)

Introduction

Devlog Part 1:
In my previous article, I introduced the Hot Heads project and discussed the fundamental need for a unit navigation system in RTS games. I opted for Unity's NavMesh system, a well-established solution. In this article, I'll share my experience with Unity's NavMesh system.

Unity NavMesh

Unity's NavMesh, based on the A* algorithm, provides a comprehensive pathfinding system. All you need in your scene is a NavMesh Surface and some NavMesh Agents.

1. Building an RTS game in Unity - Basic Unit Navigation and selection tool (1)NavMesh example from Unity Documentation

NavMeshAgent components assist in creating characters that navigate while avoiding collisions with each other. Agents leverage the NavMesh to navigate and intelligently avoid both static and dynamic obstacles. Pathfinding and spatial reasoning are handled using the scripting API of the NavMesh Agent.

To accommodate different unit types, ranging from small infantry units to large tanks, I decided to incorporate NavMesh Components into my project.

Components for Runtime NavMesh Building

Building a Navigation System

I attached the NavMesh Agent to my unit and implemented the movement logic in the following code within my UnitBase class.

private void MoveToClickPoint(Vector3 mouseClickPosition){ Ray ray = mainCamera.ScreenPointToRay(mouseClickPosition); RaycastHit hit; if (Physics.Raycast(ray, out hit, 1000f, mask)) { agent.SetDestination(hit.point); }}

Here, mask represents the LayerMask for the Ground layer in the game. The line if (Physics.Raycast(ray, out hit, 1000f, mask)) ensures that the hit point is obtained only from objects on the Ground layer.

This function is called when the mouse0 button is pressed and is checked in the Update function.

if (Selected){ // Movement if (Input.GetKeyDown(KeyCode.Mouse1)) { // Compute onTerrain position; MoveToClickPoint(Input.mousePosition); }}

Selection tool

One of the most important RTS mechanics is the ability to select one or multiple units. Currently, the game lacks this mechanic. Let's add the selection tool.

Firstly, I added a Canvas gameObject to my scene and an Image object to it.

Initial settings for the selection box

Now, with a selection box in place, it's time to add some logic. I created the ISelectable interface and the Selection class to control the selection box and the selectable items.

public interface ISelectable{ Vector3 WorldPosition { get; } void Register(); void ToggleSelection(bool state);}

The ISelectable interface is implemented by every class that can be selected by an on-screen box. It includes methods for obtaining the world position, registering a unit upon spawn, and toggling the selection state.

Now, let's discuss the Selection class. It has references to the selection tool's RectTransform component and a delta value that defines the delta position of the mouse cursor while the mouse0 button is being held to initiate a selection.

[SerializeField, Range(0f, 40f)] private float delta;[SerializeField] private RectTransform selection;

The core logic involves updating two main parameters, position and sizeDelta, when a mouse drag is detected.

private void UpdateSelectionBox(Vector2 curMousePos){ if (!selection.gameObject.activeInHierarchy) selection.gameObject.SetActive(true); float width = curMousePos.x - startPos.x; float height = curMousePos.y - startPos.y; selection.sizeDelta = new Vector2(Mathf.Abs(width), Mathf.Abs(height)); selection.position = startPos + new Vector2(width / 2, height / 2);}

In the ReleaseSelectionBox method, every selectable object within the selection box is tracked and selected.

void ReleaseSelectionBox(){ selection.gameObject.SetActive(false); Vector2 min = selection.anchoredPosition - (selection.sizeDelta / 2); Vector2 max = selection.anchoredPosition + (selection.sizeDelta / 2); foreach (ISelectable _selectable in selectables) { Vector2 screenPosition = mainCamera.WorldToScreenPoint(_selectable.WorldPosition); if ( (screenPosition.x > min.x && screenPosition.y > min.y) && (screenPosition.x < max.x && screenPosition.y < max.y) ) { _selectable.ToggleSelection(true); } else _selectable.ToggleSelection(false); }}

This code disables the selection box, retrieves box borders, and selects every ISelectable item within the screen region.

The complete code is provided below:

public class Selection : MonoBehaviour{ [SerializeField, Range(0f, 40f)] private float delta; [SerializeField] private RectTransform selection; private Camera mainCamera; private bool isSelection = false; private bool selectionEnabled = true; private Vector2 startPos, endPos; private static List<ISelectable> selectables = new List<ISelectable>(); public static void RegisterNewSelectable(ISelectable selectable) { if (!selectables.Contains(selectable)) selectables.Add(selectable); } public static void RemoveSelectable(ISelectable selectable) { if (selectables.Contains(selectable)) selectables.Remove(selectable); } // Start is called before the first frame update void Start() { mainCamera = Camera.main; } // Update is called once per frame void Update() { if (selectionEnabled) { Vector2 mousePos = Input.mousePosition; if (Input.GetKeyDown(KeyCode.Mouse0)) { startPos = mousePos; } if (Input.GetKey(KeyCode.Mouse0)) { if ( Vector2.Distance(mousePos, startPos) > delta ) { isSelection = true; UpdateSelectionBox(mousePos); } } if (Input.GetKeyUp(KeyCode.Mouse0) && isSelection) { ReleaseSelectionBox(); isSelection = false; } } if (Input.GetKeyDown(KeyCode.Escape)) selectionEnabled = !selectionEnabled; } private void UpdateSelectionBox(Vector2 curMousePos) { if (!selection.gameObject.activeInHierarchy) selection.gameObject.SetActive(true); float width = curMousePos.x - startPos.x; float height = curMousePos.y - startPos.y; selection.sizeDelta = new Vector2(Mathf.Abs(width), Mathf.Abs(height)); selection.position = startPos + new Vector2(width / 2, height / 2); } void ReleaseSelectionBox() { selection.gameObject.SetActive(false); Vector2 min = selection.anchoredPosition - (selection.sizeDelta / 2); Vector2 max = selection.anchoredPosition + (selection.sizeDelta / 2); foreach (ISelectable _selectable in selectables) { Vector2 screenPosition = mainCamera.WorldToScreenPoint(_selectable.WorldPosition); if ( (screenPosition.x > min.x && screenPosition.y > min.y) && (screenPosition.x < max.x && screenPosition.y < max.y) ) { _selectable.ToggleSelection(true); } else _selectable.ToggleSelection(false); } }}

I followed this tutorial while implementing this, making a few modifications to the code.

This marks the second part of my devlog; stay tuned for more updates on my project. Feel free to share your thoughts and suggestions in the comments.

1. Building an RTS game in Unity - Basic Unit Navigation and selection tool (2024)

References

Top Articles
Latest Posts
Article information

Author: Otha Schamberger

Last Updated:

Views: 5990

Rating: 4.4 / 5 (75 voted)

Reviews: 82% of readers found this page helpful

Author information

Name: Otha Schamberger

Birthday: 1999-08-15

Address: Suite 490 606 Hammes Ferry, Carterhaven, IL 62290

Phone: +8557035444877

Job: Forward IT Agent

Hobby: Fishing, Flying, Jewelry making, Digital arts, Sand art, Parkour, tabletop games

Introduction: My name is Otha Schamberger, I am a vast, good, healthy, cheerful, energetic, gorgeous, magnificent person who loves writing and wants to share my knowledge and understanding with you.