Shooting at enemy by Click on 'em instead the grid?

I realize just now, that to shot at enemy you have to click on the grid, but it isn’t intuitive at all, I had a friend play with my prototype, and I realized, that he was constantly wrong by clicking on the character instead of on the grid cell.

So from here I’ll start digging on how to fix the issue :thinking:

1 Like

You have the Unit Select logic, for that it’s the exact same thing.
If you click on a friendly unit, select that Unit, if you click on an enemy unit, attempt to do the Selected Action on that Unit.

6 Likes

This is how I implemented clicking on enemy hitboxes to do a shoot action and also keeping in the click on tile to do the action. I first do a check if it is the players turn and he has shoot action active. If so, any clicks on enemy hitboxes will fire the shoot action then it returns. If you do not return it is possible to do 2 shoot actions on a hitbox and the tile behind.

private void HandleSelectedAction()
    {
        if (Input.GetMouseButtonDown(0) && selectedAction is ShootAction && !selectedUnit.IsEnemy())
        {  //if clicking on Unit Hitbox while shoot action is active and as the player, shoot the enemy unit. Bypasses mouseworld grid click.
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if(Physics.Raycast(ray, out RaycastHit raycastHit, float.MaxValue, unitLayerMask))
            {
                if (raycastHit.transform.TryGetComponent<Unit>(out Unit unit))
                {
                    GridPosition unitGridPosition = unit.GetGridPosition();
                    if (!selectedAction.IsValidActionGridPosition(unitGridPosition))
                    {
                        return;
                    }
                    if (!selectedUnit.TrySpendActionPointsToTakeAction(selectedAction))
                    {
                        return;
                    }
                    if (unit.IsEnemy())
                    {
                        SetBusy();
                        selectedAction.TakeAction(unitGridPosition, ClearBusy); 

                        OnActionStarted?.Invoke(this, EventArgs.Empty); 
                        return;
                    }
                }
            }
        }

        if (Input.GetMouseButtonDown(0))
        { 
            // click on tile to do action
            GridPosition mouseGridPosition = LevelGrid.Instance.GetGridPosition(MouseWorld.GetPosition());


            if (!selectedAction.IsValidActionGridPosition(mouseGridPosition))
            {
                return;
            }
            if (!selectedUnit.TrySpendActionPointsToTakeAction(selectedAction))
            {
                return;
            }
            SetBusy();
            selectedAction.TakeAction(mouseGridPosition, ClearBusy); 

            OnActionStarted?.Invoke(this, EventArgs.Empty);      
        }
    }
3 Likes

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.

6 Likes

Privacy & Terms