This is part 18 of a tutorial series about hexagon maps. Now that we've figured out how to do pathfinding, let's put some units on the map.

Creating Units

So far, we've only dealt with cells and their immobile features. Units are different, because they are mobile. A unit can represent anything at any scale, from one person or vehicle to an entire army. In this tutorial, we'll limit ourselves to a single generic unit type. Once we have that covered we'll move on to supporting a mix of unit types.

Unit Prefab To work with units, create a new HexUnit component type. Start with an empty MonoBehaviour for now, we'll add functionality to it later. using UnityEngine; public class HexUnit : MonoBehaviour { } Create an empty game object with this component, which should become a prefab. This is the root object for a unit. Unit prefab. Add a 3D model to represent the unit as a child object. I simply used a scaled cube, which I gave a blue material. The root object defines the ground level for the unit, so offset the child accordingly.

Cube child. Giving the unit a collider will make it easier to select them later. The collider of the default cube works fine. Just make sure that the collider fits inside a single cell.

Instantiating Units As we have no gameplay yet, spawning units is done in edit mode. So it's the responsibility of HexMapEditor to create them. It needs the prefab to do this, so add a HexUnit unitPrefab field for it and hook it up. public HexUnit unitPrefab; Connecting the prefab. When creating units, we're going to place them on the cell that's underneath the cursor. HandleInput has code to find this cell when editing the terrain. We now need it for units as well, so let's move the relevant code to its own method. HexCell GetCellUnderCursor () { Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(inputRay, out hit)) { return hexGrid.GetCell(hit.point); } return null; } Now we can use this method in HandleInput , simplifying it. void HandleInput () { // Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition); // RaycastHit hit; // if (Physics.Raycast(inputRay, out hit)) { // HexCell currentCell = hexGrid.GetCell(hit.point); HexCell currentCell = GetCellUnderCursor(); if (currentCell) { … } else { previousCell = null; } } Next, add a new CreateUnit method that uses GetCellUnderCursor as well. If there's a cell, instantiate a new unit. void CreateUnit () { HexCell cell = GetCellUnderCursor(); if (cell) { Instantiate(unitPrefab); } } To keep the hierarchy clean, let's use the grid as the parent for all unit game objects. void CreateUnit () { HexCell cell = GetCellUnderCursor(); if (cell) { HexUnit unit = Instantiate(unitPrefab); unit.transform.SetParent(hexGrid.transform, false); } } The simplest way to add support for creating units to HexMapEditor is via a key press. Adjust the Update method so it invokes CreateUnit when the U key is pressed. Like HandleInput , this should only happen if the cursor is not on top of a GUI element. First check whether we should edit the map, and if not check whether we should add a unit. If so, invoke CreateUnit . void Update () { // if ( // Input.GetMouseButton(0) && // !EventSystem.current.IsPointerOverGameObject() // ) { // HandleInput(); // } // else { // previousCell = null; // } if (!EventSystem.current.IsPointerOverGameObject()) { if (Input.GetMouseButton(0)) { HandleInput(); return; } if (Input.GetKeyDown(KeyCode.U)) { CreateUnit(); return; } } previousCell = null; } Instantiated unit.

Positioning Units It is now possible to create units, but they all end up at the origin of the map. The next step it to put them in the right place. This requires that units are aware of their location. So add a Location property to HexUnit to identify the cell that they are occupying. When setting it, adjust the unit's position so it matches the cell's. public HexCell Location { get { return location; } set { location = value; transform.localPosition = value.Position; } } HexCell location; Now HexMapEditor.CreateUnit has to assign the cell under the cursor to the unit's location. Then the units will end up where they're expected. void CreateUnit () { HexCell cell = GetCellUnderCursor(); if (cell) { HexUnit unit = Instantiate(unitPrefab); unit.transform.SetParent(hexGrid.transform, false); unit.Location = cell; } } Positioned units.

Unit Orientation Currently, all unit have the same orientation, which looks quite rigid. To liven things up, add an Orientation property to HexUnit . This is a float which represents the unit's rotation around the Y axis, in degrees. When setting it, adjust the unit's actual game object rotation accordingly. public float Orientation { get { return orientation; } set { orientation = value; transform.localRotation = Quaternion.Euler(0f, value, 0f); } } float orientation; In HexMapEditor.CreateUnit , assign a random rotation, between 0 and 360 degrees. void CreateUnit () { HexCell cell = GetCellUnderCursor(); if (cell) { HexUnit unit = Instantiate(unitPrefab); unit.transform.SetParent(hexGrid.transform, false); unit.Location = cell; unit.Orientation = Random.Range(0f, 360f); } } Varied unit orientations.

One Unit Per Cell The units look good, except when multiple are created in the same location. Then we get overlapping cubes, which looks bad. Overlapping units. Some games allow multiple units in the same location, while other do not allow this. As a single unit per cell is easier to work with, we'll go for this option. That means that we should only create a new unit if the current cell is not occupied. To make it possible to know this, add a default Unit property to HexCell . public HexUnit Unit { get; set; } Use this property in HexUnit.Location to make the cell aware that there is a unit standing on it. public HexCell Location { get { return location; } set { location = value; value.Unit = this; transform.localPosition = value.Position; } } Now HexMapEditor.CreateUnit can check whether the current cell is available. void CreateUnit () { HexCell cell = GetCellUnderCursor(); if (cell && !cell.Unit ) { HexUnit unit = Instantiate(unitPrefab); unit.Location = cell; unit.Orientation = Random.Range(0f, 360f); } }

Editing Occupied Cells While units are correctly positioned initially, it can go wrong when their locations are edited later. If a cell's elevation is changed, the unit occupying it will end up either hovering above it, or sinking into it. Floating and sunken units. The solution is to validate the unit's location after a change has been made. Add a method for this to HexUnit . Currently, we only care about the unit's position, so just set it again. public void ValidateLocation () { transform.localPosition = location.Position; } We should validate the unit's location whenever we refresh a cell, which is when either the Refresh or RefreshSelfOnly methods of HexCell are invoked. Of course this is only required when there's actually a unit in the cell. void Refresh () { if (chunk) { chunk.Refresh(); … if (Unit) { Unit.ValidateLocation(); } } } void RefreshSelfOnly () { chunk.Refresh(); if (Unit) { Unit.ValidateLocation(); } }