EDIT: I have made this less cringey
This is not very difficult to do. We can already select the player units by clicking on them, we just need to extend this to the enemies.
This is what I did (the following is off the code that is linked to this lecture)
NOTE: All the code is in UnitActionSystem.cs
In the update method, we try to handle unit selection for player units. We need almost the same thing for enemy selection, but there is code in this function specifically for player selection. The first thing I did was to change TryHandleUnitSelection
to instead be a TryGetClickedUnit
function that will return a clicked unit, if any was clicked, and pass the Unit into a TryHandlePlayerUnitClicked
function to handle the player clicked stuff we are currently doing
Change TryHandleUnitSelection to TryGetClickedUnit
private void Update()
{
if (isBusy) { return; }
if (!TurnSystem.Instance.IsPlayerTurn()) { return; }
if (EventSystem.current.IsPointerOverGameObject()) { return; }
if (TryGetClickedUnit(out Unit clickedUnit)) // try to get the unit that was clicked
{
if (TryHandlePlayerUnitClicked(clickedUnit)) { return; } // try to handle the unit if it's a player
}
HandleSelectedAction();
}
private bool TryGetClickedUnit(out Unit clickedUnit)
{
clickedUnit = default;
if (InputManager.Instance.IsMouseButtonDownThisFrame())
{
Ray ray = Camera.main.ScreenPointToRay(InputManager.Instance.GetMousePosition());
if (Physics.Raycast(ray, out RaycastHit raycastHit, float.MaxValue, unitLayerMask))
{
if (raycastHit.transform.TryGetComponent<Unit>(out clickedUnit))
{
// We have found a unit that was clicked on. return true;
return true;
}
}
}
return false;
}
// This is the new function that checks if the clicked
// unit is the player and if it is, selects it
private bool TryHandlePlayerUnitClicked(Unit clickedUnit)
{
if (clickedUnit == selectedUnit) { return false; } // Unit is already selected
if (clickedUnit.IsEnemy()) { return false; } // Clicked on an Enemy
SetSelectedUnit(clickedUnit);
return true;
}
Ok, so next we want to be able to do something else when the enemy is clicked, so we will create a new function for that.
TryHandleEnemyUnitClicked - Part 1
private void Update()
{
if (isBusy) { return; }
if (!TurnSystem.Instance.IsPlayerTurn()) { return; }
if (EventSystem.current.IsPointerOverGameObject()) { return; }
if (TryGetClickedUnit(out Unit clickedUnit)) // try to get the unit that was clicked
{
if (TryHandlePlayerUnitClicked(clickedUnit)) { return; } // try to handle the unit if it's a player
if (TryHandleEnemyUnitClicked(clickedUnit)) { return; } // try to handle the unit if it's an enemy
}
HandleSelectedAction();
}
// So far this does not do much. We need to make another
// small change before we can complete this function
private bool TryHandleEnemyUnitClicked(Unit clickedUnit)
{
if (!clickedUnit.IsEnemy()) { return false; } // Clicked on a Player unit
return true;
}
OK, we can now do something when the player is clicked and something else when the enemy is clicked. The player selection is already done.
What we want to do is handle the selected action. We already have a TryHandleSelectedAction()
, so we will just reuse that. It needs a little refactoring, though. It gets the grid position where we clicked, but if we clicked an enemy, we want that enemy’s grid position to be used.
Let’s supply the grid position instead of having the function determine it. What we can do is to overload the method
HandleSelectedAction overload
// Original method - It determines the grid position where we clicked
private void HandleSelectedAction()
{
// Here we determine the clicked grid position, and pass the position on to the overloaded method
GridPosition mouseGridPosition = LevelGrid.Instance.GetGridPosition(MouseWorld.GetPosition());
HandleSelectedAction(mouseGridPosition);
}
// Overloaded method - It does not calculate the
// grid position. we must supply it
private void HandleSelectedAction(GridPosition gridPosition)
{
if (InputManager.Instance.IsMouseButtonDownThisFrame())
{
if (!selectedAction.IsValidActionGridPosition(gridPosition)) { return; }
if (!selectedUnit.TrySpendActionPointsToTakeAction(selectedAction)) { return; }
SetBusy();
selectedAction.TakeAction(gridPosition, ClearBusy);
OnActionStarted?.Invoke(this, EventArgs.Empty);
}
}
This code is much the same as before, but split into two bits: The first method works like before by calculating the grid position of where we clicked, but then it passes it on to the second method which processes it as before. This allows us to now also handle the selected action, but with a grid position that we supply. So let’s do that in the enemy handling function
TryHandleEnemyUnitClicked - Part 2
private bool TryHandleEnemyUnitClicked(Unit clickedUnit)
{
if (!clickedUnit.IsEnemy()) { return false; } // Clicked on a Player unit
// Get the grid position of the enemy we clicked on
var enemyGridPosition = clickedUnit.GetGridPosition();
// and pass it to the new HandleSelectedAction method
HandleSelectedAction(enemyGridPosition);
return true;
}
That’s it. This code will now perform the selected action on the enemy if you click on the enemy unit, while still doing all the other things it did before.
Sorry for the long post
Disclaimer: I came up with this solution in about 5 or 10 minutes and there are certainly other, probably better, ways of achieving the same thing.