1

I am creating a Finite State Machine for use in Unity for an enemy in a game.

All the states are made from an EmptyState.cs blueprint that defines the state exit, update, and start functions, and all states are derived from it.

In my FSM core, I have a dictionary reference to all states, and the current state;

private Dictionary<Type, EmptyState> amalgamStates;

public EmptyState currentState;

The dictionary, looks like this;

Dictionary<Type, EmptyState> states = new Dictionary<Type, EmptyState>
{
    {typeof(RoamingState), new RoamingState() },
    {typeof(IdleState), new IdleState() },
    {typeof(StalkingState), new StalkingState() },
    {typeof(HuntingState), new HuntingState() },
    {typeof(HauntingState), new HauntingState() },
    {typeof(LeavingState), new LeavingState() }
};

However, when I assign a state to currentState, it always sets as null.

 private void Update()
 {
     if (currentState == null)
     {
         currentState = amalgamStates.Values.First(); **<--- here**
         Debug.Log(currentState); 
         currentState.stateStart(amalgamBrain);
     }
     //Run state's update and store output (output will be next state)
     var temp = currentState.stateUpdate(amalgamBrain);

     //If the stored state is different -> switch states to new state
     if (temp != currentState.GetType())
     {
         switchStates(temp);
     }
 }

Debug.Log(currentState); always returns null. I can't even set the variable manually in the unity inspector.

I know that this should work, as I am working from a project of a similar framework that works fine, so I'm just making a dumb mistake somewhere. Does anyone have any ideas? Here is the entirety of EmptyState.cs to anyone curious.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EmptyState : MonoBehaviour
{
    public AmalgamCentralAI amalgamBrain;

    public virtual void stateStart(AmalgamCentralAI amalgamBrain) { }
    public virtual Type stateUpdate(AmalgamCentralAI amalgamBrain) { return null; }
    public virtual void stateExit(AmalgamCentralAI amalgamBrain) { }
}

EDIT: Just some additional context;

the states dictionary is part of another script that is populating the one titled amalgamStates.

I will post the relevant scripts here in its entirety.

AmalgamCentralAI.cs

using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class AmalgamCentralAI : MonoBehaviour
{
    private float stunHealth;

    public bool playerInSight;

    public Transform interuptedEvent;

    public GameObject playerTracker;

    public Transform[] leavingAreas;

    private int tensionMeter;

    public AmalgamFSM FSMLogic;

    private void Start()
    {
        interuptedEvent = null;
        FSMLogic = GetComponent<AmalgamFSM>();
        Dictionary<Type, EmptyState> states = new Dictionary<Type, EmptyState>
        {
            {typeof(RoamingState), new RoamingState() },
            {typeof(IdleState), new IdleState() },
            {typeof(StalkingState), new StalkingState() },
            {typeof(HuntingState), new HuntingState() },
            {typeof(HauntingState), new HauntingState() },
            {typeof(LeavingState), new LeavingState() }
        };
        Debug.Log(states);
        FSMLogic.GrabAllStates(states);
    }

    private void Update()
    {
        trackPlayerVisability();
    }

    private void trackPlayerVisability()
    {
        RaycastHit hit;
        if (Physics.Raycast(transform.position, playerTracker.transform.position, out hit, Mathf.Infinity))
        {
            if(hit.transform.tag == "Player")
            {
                playerInSight = true;
            }
            else
            {
                playerInSight = false;
            }
        }
    }
    
}

AmalgamFSM.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class AmalgamFSM : MonoBehaviour
{
    public AmalgamCentralAI amalgamBrain;
    public Dictionary<Type, EmptyState> amalgamStates;

    public EmptyState currentState;

    private void Start()
    {
        //Fetch all Amalgam Commands
        amalgamBrain = GetComponent<AmalgamCentralAI>();
        //Start in Roaming State
        
        //Debug.Log("state start");
    }

    private void Update()
    {
        if (currentState == null)
        {
            currentState = amalgamStates.Values.First();
            Debug.Log(currentState);
            currentState.stateStart(amalgamBrain);
        }
        //Run state's update and store output (output will be next state)
        var temp = currentState.stateUpdate(amalgamBrain);

        //If the stored state is different -> switch states to new state
        if (temp != currentState.GetType())
        {
            switchStates(temp);
        }
    }

    public void GrabAllStates(Dictionary<Type, EmptyState> states)
    {
        //Fetch dictionary of all states
        amalgamStates = states;
    }

    public void switchStates(Type state)
    {
        //Run current state's exit, create reference to new state, run new state's start
        currentState.stateExit(amalgamBrain);
        currentState = amalgamStates[state];
        currentState.stateStart(amalgamBrain);
    }
}

RoamingState.cs

using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class RoamingState : EmptyState
{
    private float SearchRange;
    private Transform centerPoint;
    private bool pointFound;

    private NavMeshAgent agent;

    public override void stateStart(AmalgamCentralAI amalgamBrain)
    {
        //Define search range
        SearchRange = 100f;
        //Amalgam serves as the center of the search area
        //Debug.Log("fetching center point");
        centerPoint = amalgamBrain.gameObject.transform;
        agent = amalgamBrain.gameObject.GetComponent<NavMeshAgent>();
        pointFound = false;
    }

    public override Type stateUpdate(AmalgamCentralAI amalgamBrain)
    {
        if(!pointFound)
        {
            //Find suitable Roam Position
            Vector3 point;
            if(RandomPointOnNav(centerPoint.position, SearchRange, out point))
            {
                //Set roam position in navmeshagent
                agent.SetDestination(point);
                pointFound = true;
            }
        }
        // if the agent is close enough to the random point
        else if(agent.remainingDistance <= agent.stoppingDistance && pointFound)
        {
            //idle
            return typeof(IdleState);
        }
        //if agent is still looking for a point or is travelling to a point, remain in roaming state
        return typeof(RoamingState);
    }

    public override void stateExit(AmalgamCentralAI amalgamBrain)
    {

    }

    public bool RandomPointOnNav(Vector3 centerPoint, float range, out Vector3 resultPoint)
    {
        //Find a random point in a sphere of searchrange radius around the amalgam
        Vector3 RandomSearchPoint = centerPoint + UnityEngine.Random.insideUnitSphere * range;
        NavMeshHit hit;
        // Can the amalgam travel to this point on the navmesh?
        if (NavMesh.SamplePosition(RandomSearchPoint, out hit, 1.0f, NavMesh.AllAreas))
        {
            //return that the amalgam has found a suitable position
            resultPoint = hit.position;
            return true;
        }
        // if not remain still and continue searching on next update
        resultPoint = Vector3.zero;
        return false;

    }
}

Just an additional point I found weird.

    currentState = amalgamStates.Values.First(); **<--- here**
    Debug.Log(currentState); 
    currentState.stateStart(amalgamBrain);

in the currentState.stateStart(amalgamBrain);, even though debug.log is returning null, it does run the stateStart of the First amalgamState, which is the RoamingState.

5
  • 2
    Don't see where amalgamStates is assigned or filled .. please post a minimal reproducible example Commented Apr 28, 2024 at 19:27
  • you create states, not amalgamStates.. Commented Apr 28, 2024 at 19:27
  • @derHugo I've edited it to supply all the relevant scripts Commented Apr 28, 2024 at 19:39
  • Seems like my dictionary is being initialised properly in AmalgamCentralAI.cs? Running checks on it says that it contains no values after being initialized. Commented Apr 28, 2024 at 20:33
  • Instead of doing a Debug.Log call, have you thought about attaching a debugger and checking if "Values" actually contains something valuable? Commented Apr 28, 2024 at 21:32

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.