How I simulate my AI to "Press Buttons"

Wanted to get some thoughts on how I’ve tackled this problem.
I have a spaceship with multiple modules, each module has a function and is activated by pressing a particular key.
I want AI to also be able to fly the ship, but I don’t want to hook directly into the modules, I want the AI to “Press Keys” which in turn activate the modules.

What I’ve done is instead of using “Input.GetKeyDown” directly, my Module class has a “IUnityService” property which is an interface with “GetKeyDown”, “GetKeyUp”, “GetKey” etc.
The standard implementation (for the user) just acts like a wrapper around the Unity “Input” class:

public class UnityService : IUnityService
{
    public bool GetKeyDown(KeyCode keyCode)
    {
        return Input.GetKeyDown(keyCode);
    }

    public bool GetKeyUp(KeyCode keyCode)
    {
        return Input.GetKeyUp(keyCode);
    }

    public bool GetKey(KeyCode keyCode)
    {
        return Input.GetKey(keyCode);
    }
}

The AI Implementation also has “SetKeyDown”, “SetKey”, “SetKeyUp” etc. Each of these functions have an associated hashtable with the key as the KeyCode and a boolean for the value.

public class AIInputService : IUnityService
{

    Hashtable _keysHeld = new Hashtable();
    Hashtable _keysPressed = new Hashtable();
    Hashtable _keysReleased = new Hashtable();

    public void SetKey(KeyCode keyCode, bool isHeld)
    {
        if (_keysHeld.ContainsKey(keyCode))
        {
            _keysHeld[keyCode] = isHeld;
        }
        else
        {
            _keysHeld.Add(keyCode, isHeld);
        }
    }

    public void SetKeyDown(KeyCode keyCode, bool isPressed)
    {
        if (_keysPressed.ContainsKey(keyCode))
        {
            _keysPressed[keyCode] = isPressed;
        }
        else
        {
            _keysPressed.Add(keyCode, isPressed);
        }
    }

    public void SetKeyUp(KeyCode keyCode, bool isReleased)
    {
        if (_keysReleased.ContainsKey(keyCode))
        {
            _keysReleased[keyCode] = isReleased;
        }
        else
        {
            _keysReleased.Add(keyCode, isReleased);
        }
    }

    public bool GetKey(KeyCode keyCode)
    {
        if (_keysHeld.ContainsKey(keyCode))
        {
            return (bool)_keysHeld[keyCode];
        }

        return false;
    }

    public bool GetKeyDown(KeyCode keyCode)
    {
        if (_keysPressed.ContainsKey(keyCode))
        {
            var value = (bool)_keysPressed[keyCode];
            return value;
        }

        return false;
    }

    public bool GetKeyUp(KeyCode keyCode)
    {
        if (_keysReleased.ContainsKey(keyCode))
        {
            var value = (bool)_keysReleased[keyCode];
            return value;
        }

        return false;
    }
}

The AI will override the UnityService property on the modules with the AI Implementation, and then it can use the “Set” functions to change keys to be pressed and not pressed etc.

This is all great because I don’t even have to worry that AI will be able to do things that the player is not able to do, and because it’s all running the same code, there’s no repeated code and I don’t need to expose a bunch of functions on my modules.

I learned the “UnityService” injection for testing purposes, but it turns out it has another great use case!

I also realized this could be (situationally) used for multiplayer, if you wanted to pass the inputs over the network rather than just syncing the transforms.

Hopefully that helps anyone trying to keep their player and AI controllers more in sync!

You can’t basically. Not with old input system at least. Look into new Input system, there’s a section on this forum dedicated to that. Usually for this people using signals or Command pattern. You have separate PlayerInput class. When player presses a key, this class reads it, generates a structure describing player command like this

struct MoveTo {
  public Vector3 position;
}

and pushes it into commands queue. Player avatar script reads that queue and handles incoming signals as appopriate. The AI generates same signals when it wants to simulate human actions and that’s it.

1 Like

Yep, I had something like @palex-nx .
Simply having forward, left, right, fire etc. commands. I just hook player inputs, or AI to these commands.
No worrying about keys bindings at all.

1 Like

@palex-nx That’s a good way of doing it. I suppose mine is a little more specific as I can’t just tell the ship to “Move Here”. The AI needs to know where to go sure, but it also needs to know how to activate the thrusters (and which thrusters to activate) in order to reach the target. The keybinds will all be different for different configurations of the ship, so that’s why I’ve had to do it this way.

I like the way I’ve done it because it allows me to code the player inputs really easily, and also gives other inputs a hook into the exact same system without having to expose methods like “Activate Thruster”, “Deactivate Thruster”, “Fire Turret”. Instead, I just write AIInputService.SetKeyDown(thruster.KeyBind, True) and my standard code will see that as that key being pressed.

Imagine this scenario:
You’ve got a game where you have multiple spells that you can bind to different keys.
When the players press the key, it loops through the spells and calls spell.cast when it finds one with the matching keyCode.
You write some AI which instead of using the input, just calls spell.cast on random spells.
You decide to add a UI to display when spells are on cooldown, so in your spell.cast code you say you also want to update the UI.
Wait a second, now the AI is also updating my UI!
Ok, I’ll move the UI code out of spell.cast and only do it when the player presses the button.
Sweet, all is good.
A few months later I decide to add a spectate function, but when I’m spectating the AI and they cast spells, I can’t see the UI updating - what the heck is happening!?

You can probably think of other solutions here but my main point is that I’m trying to avoid multiple code paths that make code unpredictable, unstable and difficult to test.
If I never made spell.cast public, I never would have had this problem. It could only be accessed by “pressing the key” and from there, the exact same code path would be executed for the player and the AI.
Obviously in this scenario, you’d need to have the “active character” updating the UI instead of all of the characters being able to update it.

I suppose I could have abstracted it further away from keybinds but I think this is a good way to have it make sense in a video game setting, because all games have keybinds.

Anyway, I just wanted to share my solution for anyone else in a similar situation. Hopefully, it helps someone out!

So if you have 50 keys assigned, you make function for each key? That make a bit a messy to maintain.

Why simply not use array/list of spells/thrusters config if you need. This way very easy hook AI without worrying how many configurations there is.

For the player side, is just simple key binding map, to each index of array/list.

You should have decoupled key binding from any controllers. This way you allow to hook any type of interface. What if you decide to hook in a joystick, or other game pad controller?
Or player has different keyboard layout?

@Antypodish The KeyCode is a property on the module class, you can change this to whatever you want. The module fires an event when the KeyCode is pressed. I haven’t hardcoded any keyCodes anywhere in my code.

Might be easier to show you the ShipModule abstract class. Real modules like ThrusterModule and BlasterModule inherit from ShipModule and have their specific code contained in the overridden KeyDown and KeyUp methods:

public abstract class ShipModule : MonoBehaviour
{
    public KeyCode keyCode { get; set; }

    public bool recieveInput { get; set; }

    /// <summary>
    /// Override this to mock out the inputs for testing and AI, defaults to UnityService which uses standard input and time functions
    /// </summary>
    public IUnityService UnityService { get; set; } = new UnityService();

    private void Update()
    {
        if (recieveInput)
        {
            if (UnityService.GetKeyDown(keyCode))
            {
                KeyDown();
            }
            else if (UnityService.GetKeyUp(keyCode))
            {
                KeyUp();
            }
        }
    }

    protected abstract void KeyDown();
    protected abstract void KeyUp();
}
public class ThrusterModule : ShipModule
{
    [SerializeField] float power;
    [SerializeField] ParticleSystem particles;

    Rigidbody2D rig;

    private void Start()
    {
        rig = GetComponent<Rigidbody2D>();
    }

    Coroutine thrusting;

    protected override void KeyDown()
    {
        StartThrust();
    }
    protected override void KeyUp()
    {
        StopThrust();
    }

    private void StartThrust()
    {
        //check if we're already thrusting
        if (thrusting == null)
        {
            thrusting = StartCoroutine(Thrusting());
            particles.Play();
        }
    }

    private void StopThrust()
    {
        if (thrusting != null)
        {
            StopCoroutine(thrusting);
            particles.Stop();
        }
        thrusting = null;
    }

    private IEnumerator Thrusting()
    {
        while (true)
        {
            rig?.AddForceAtPosition(transform.up * power, transform.position, ForceMode2D.Force);
           
            yield return new WaitForFixedUpdate();
        }
    }

}

Where to go is just a simple example. This architecture fits any kind of input because any kind of input processing is about someone issuing the commands and others executing issued commands. You can have ActivateTruster and DeactivateTruster commands, no problem

This is essentialy the same. The command is just data representation of method call.

I do not do it this way. No. Updating UI from game logic code? That’s caveman approach. My game will issue Cooldown event. The UI code will check if this spell belongs to player and it’s button is currently visible and will change it’s state accordingly if it is.

1 Like

True, you could do it that way and it might be a bit more readable. My way is just more abstracted and fits best with what I have.

True, I’d agree with that.

I never specified how It would update the UI, but that wasn’t the point I was trying to get across at all. I also use event driven UI updates.

Thanks for your input, It’s given me some good food for thought :slight_smile: