Scripted sequence AI

I’m a programming beginner and i’ve been reading many topics on different implementations of game AI. Honestly, it’s been very overwhelming and i don’t even know where to start.

So i came up with an idea, what about a state machine that plays out a manually created sequence of actions? Sort of like making the bots follow a script that is triggered by specific variables, with a break condition to make it seem more dynamic.

Basically like this: A cube sees the player, but the player doesn’t see the cube. It picks a sequence of actions i created manually, something like a “Sneak to” script. It then throws a few actions (like move to, look at,attack) in a specific order into a list where the cube has to follow through. The script has a few conditions that if met, it removes an action from the list. On top of that, there could also be a break condition that stops the current script, like if the player turns around sees the cube. It then picks another sequence i designed for another scenario.

Thoughts?

I’m not sure what you’re trying to say. You’ve just described a state machine.

If you’re starting out then it’s probably better to stick to an exiting AI system rather than trying to create your own. Probably the most common three I’ve seen are:

State machines: normally the easiest to setup and get started with. I’ve found that they’re not really great for sequences and can turn into spaghetti if your not careful. A hierarchical state machine can help a bit. For simple AI a state machine is fine.

Behavior trees: deals more with sequences / selectors and it’s easier to avoid getting stuck in a state like you can in a state machine. If you have complex logic with sequences then I’d recommend trying behavior trees.

Planners: HTN, GOAP. Creates the best plan it can find at a given time (sequence of actions). You mentioned creating a sequence of actions so I’m not sure if you might be referring to a planner. This tends to be a bit more advanced so I wouldn’t recommend it for a beginner.

Sounds like you have a good grasp of how State Machines are typically used to implement enemy behavior. The only thing I’ll add is- Don’t spend too much time trying to invent the ultimate abstract FSM system before you start working on your game. Just start with simplest solution possible and build it up as you go.

Also, what you’re describing is exactly how playMaker works. There are other tools on the asset store that are similar as well. These tools can potentially save you time, but also require an investment of time to learn these systems, so it’s sometimes easier just code your own.

1 Like

It’s a state machine that follows a pre-made plan…

Wonder what would a (finite) state machine look like that does not follow a premade plan?

Then you would just have states with no “planning”. The idea is to make the bot follow a small plan that consists of a sequence of smaller arbitrary actions.

Like some kind of machine… with a finite amount of states…

…my god…

Remembered about this post yesterday and i ended up cobbling something that works.
It’s pretty much a state machine with extra steps. I guess it’s benefit is that you can just re-use the state machine’s states for different purposes. I found it fun to write this despite my shaky programming skills.

The plan manager:

using UnityEngine;

public class PseudoPlanner : MonoBehaviour
{

    public BotMotor botMotor; // the bot's state machine that controls the actions
    public AIFlags flags; // the flags that will be checked in the plan's monitor functions
    public PseudoPlannerBasePlan curPlan;
    PseudoPlannerBasePlan defaultPlan = new PlanTest();

    void Start()
    {
        botMotor = GetComponent<BotMotor>(); // Assumes there's a StateMachine component attached

        //Setting up the entry plan 
        curPlan = defaultPlan;
        curPlan.planner = this;
        curPlan.Enter();
    }

    //Monitoring the advance/retreat/break out conditions
    void Update()
    {
        curPlan.MonitorAdvance();
        curPlan.MonitorRetreat();
        curPlan.MonitorBreak();
    }


    //External function to switch the plans
    public void SwitchPlan(PseudoPlannerBasePlan plan)
    {
        curPlan.Exit();
        curPlan = plan;
        curPlan.planner = this;
        curPlan.Enter();

    }
}

The “plan” template:

public abstract class PseudoPlannerBasePlan
{
    public PseudoPlanner planner;
    public List<BotBaseState> Plan;
    public int index;

    public abstract void MonitorAdvance();
    public abstract void MonitorRetreat();
    public abstract void MonitorBreak();
    public abstract void Exit();
   
    public virtual void Enter()
    {
        index = 0;
        planner.botMotor.SwitchState(Plan[0]);
    }

    public virtual void GoNextStep()
    {
        index += 1;
        planner.botMotor.SwitchState(Plan[index]);
    }

    public virtual void GoPrevStep()
    {
        index -= 1;
        planner.botMotor.SwitchState(Plan[index]);
    }

}

The flag checker:

using UnityEngine;

public class AIFlags : MonoBehaviour
{
    [SerializeField] HitboxManager hitboxManager;
    [SerializeField] BotTargetManager botTargetManager;

    public bool LowHealth => hitboxManager.Health < 100f;
    public bool CloseToTarget => Vector3.Distance(botTargetManager.Target.transform.position, transform.position) <= 5;
    public bool FarFromTarget => Vector3.Distance(botTargetManager.Target.transform.position, transform.position) > 8;
    
}

“Plan” example:

using System.Collections.Generic;


public class PlanTest : PseudoPlannerBasePlan
{
    public override void Enter()
    {

        Plan = new List<BotBaseState>()
        {
               new WalkTowardsTarget(),
               new StrafeAround()
        };

        base.Enter();
    }

    public override void Exit()
    {

    }

    public override void MonitorAdvance()
    {
        switch (index)
        {
            case 0:
                if (planner.flags.CloseToTarget)
                    GoNextStep();
                break;
        }

    }

    public override void MonitorRetreat()
    {
        switch (index)
        {
            case 1:
                if (planner.flags.FarFromTarget)
                    GoPrevStep();
                break;
        }
    }

    public override void MonitorBreak()
    {
        if (planner.flags.LowHealth)
            planner.SwitchPlan(new FleeSimple());
    }


}