Unit that dies while selected still has move action

There’s a bug in the Turn Based Strategy course game, where if a unit you have selected dies (units stay selected through the enemy turn) you can still try to take an action with it on the following turn. If you do, it essentially softlocks the game - it tries to access an animator component or unit script on a unit that no longer exists (because the unit has been destroyed and replaced with a ragdoll), and the ‘BUSY’ UI panel gets stuck blocking all of the action buttons, preventing any more actions.

I’ve downloaded the course files, and it does indeed happen there too (so the bug is not a typo by me for once!)

Just passing this on - I’ll get back to banging my head against a wall trying to implement a way to deselect units

1 Like

I took a quick look at this and I think the most straightforward solution is to test to see if the selectedUnit is null. I revised the Update loop in UnitActionSystem.cs

 private void Update()
    {
        if (isBusy)
        {
            return;
        }

        if (!TurnSystem.Instance.IsPlayerTurn())
        {
            return;
        }

        if (selectedUnit == null) 
        {
            //We should never get here if it's the enemy turn or there are no units remaing
            SetSelectedUnit(UnitManager.Instance.GetFriendlyUnitList().First());
            return; //Give the system a frame to catch up.
        }
        
        if (EventSystem.current.IsPointerOverGameObject())
        {
            return;
        }

        if (TryHandleUnitSelection())
        {
            return;
        }

        HandleSelectedAction();
    }

Thanks. I tried something very similar, but I didn’t do it very well - placed the null check in all of the action scripts and ended up breaking the AI. I’ll do this instead

Okay so this appears to solve the softlock problem. I should add for anyone else implementing this - you need to add using System.linq at the top of the script, or the .First() function won’t work

However it does create another issue, where if all player units are destroyed, the First() function still tries to set another unit and can’t, and throws an InvalidOperationException every frame.

I’ve made an EndScreenUI to show victory or defeat if the enemy or friendly lists are depleted, respectively. I guess I’ll try to make a similar function to freeze the gameplay if one of those lists empties

Oops, I did forget the UnitActionSystem keeps running after a Win condition.
Simple fix, change the Linq call to FirstOrDefault();
This does have a slight cascading effect, though, as we need our buttons to also not respond…

    private void SetSelectedUnit(Unit unit)
    {
        selectedUnit = unit;
        if(selectedUnit!=null) SetSelectedAction(selectedUnit.GetAction<MoveAction>());
        OnSelectedUnitChanged?.Invoke(this, EventArgs.Empty)
    }

In UnitActionSystemUI

    private void UpdateActionPoints()
    {
        Unit selectedUnit = UnitActionSystem.Instance.GetSelectedUnit();
        
        actionPointsText.text = selectedUnit!=null?"Action Points: " + selectedUnit.GetActionPoints():"Game Over";
    }

And in ActionButtonUI

    public void UpdateSelectedVisual()
    {
        if (UnitActionSystem.Instance.GetSelectedUnit() == null)
        {
            button.interactable = false;
            selectedGameObject.SetActive(false);
            return;
        }
        BaseAction selectedBaseAction = UnitActionSystem.Instance.GetSelectedAction();
        button.interactable = !selectedBaseAction.HasActedThisTurn() && selectedBaseAction.GetActionPointsCost() <=
            selectedBaseAction.GetUnit().GetActionPoints();
        selectedGameObject.SetActive(selectedBaseAction == baseAction);
    }

Thanks - implemented all of that (I don’t seem to have a HasActedThisTurn() function so I removed that part) and all seems well.

I’m getting a new NullReferenceException error when the next player turn starts with all units dead, but from the error code I’m pretty sure it’s just because my endgame screen hides all the other UI elements when it loads, therefore there’s no container for UnitActionSystemUI to put the action buttons in.

I’ve made the end screen set ScaleTime to 0 shortly after it appears, so it was only a problem if I turned that functionality off, but as a failsafe I prevented the NextTurn function on TurnSystem from triggering if the friendly unit list is empty, which seemed to fix it.

I’ve just started playing with action point costs, and I encountered an issue which I think might be related to this. Apologies for taking this rabbit hole further if so!

I set unit actions to 3, and made the shoot action cost 2. If I attempt to use a shootAction twice, all of the action buttons fade their colour, not just the shootAction, despite everything but the shootAction costing 1 and the unit having 1 action point remaining. I have to switch between units to enable the buttons again, so the unit can spend its remaining action point on something else.

I don’t recall ever seeing an action button fade before this, so I wondered if this change has affected it? I tried commenting out…

button.interactable = !selectedBaseAction.HasActedThisTurn() && selectedBaseAction.GetActionPointsCost() <=
           selectedBaseAction.GetUnit().GetActionPoints();

… and didn’t seem to have the problem.

I thought the solution might be to put that code inside a foreach and cycle through the ActionButtonUIList, but I can’t seem to get it to work.

The HasActedThisTurn(), I think was an addition I made… restricts character to one move and one action, and disallows multiple actions.

If you just use

button.interactable = selectedBaseAction.GetActionPointsCost()<=selectedBaseAction.GetUnit().GetActionPoints();

This should restore dual actions.

Still seem to have the same issue - if I click shoot with 1 action point left, it fades out all the buttons and makes them unclickable until I switch to another unit and switch back

I think I see the problem… I’m checking against selectedBaseAction insteads of baseAction.
selectedBaseAction is the action currently selected, which is used for the next line. The button should be disabled if it’s the same action, enabled if it’s different. For the line in question, we should be checking if the baseAction tied to this button is possible.

        button.interactable = baseAction.GetActionPointsCost() <=
           baseAction.GetUnit().GetActionPoints();

Yep, that looks good now, thanks

Privacy & Terms