I have a bunch of different kind of NPCs in my game and of course they logically similar, they have health, they have vision, they can navigate using agent and stuff.
But each NPC type has it's own custom behavior with states, actions, decisions and hooks. And those scripts require various specific data like coroutines running, target altitude or current leaping direction.
And I have to store it or have it on NPC mono behavior, so it is accessible inside state's scripts (they are scriptable objects called from NPC mono behavior)
Right now what I do is specifying array for each data type and count of it that I assign on NPC prefab. And it feels wrong...
public class Npc : MonoBehaviour
{
public static Dictionary<int, Npc> npcs = new Dictionary<int, Npc>();
public int npcId;
public NpcType type;
public Transform shootOrigin;
public Transform head;
public float maxHealth = 50f;
public float visionRange = 15;
public float visionAngle = 60;
public float headAngle = 120;
public float movementSpeed = 4.5f;
public int indexedActionsCount = 0;
[HideInInspector] public float[] lastActTimeIndexed;
[HideInInspector] public bool[] wasActionCompletedIndexed;
public int indexedVector3DataCount = 0;
[HideInInspector] public Vector3[] vector3DataIndexed;
public int indexedFloatDataCount = 0;
[HideInInspector] public float[] floatDataIndexed;
public int indexedBoolDataCount = 0;
[HideInInspector] public bool[] boolDataIndexed;
public int indexedCoroutineDataCount = 0;
[HideInInspector] public IEnumerator[] coroutineDataIndexed;
public NpcState currentState;
public NpcState remainState;
public float Health { get; private set; }
[HideInInspector] public NavMeshAgent agent;
public static int decisionUpdatesPerSecond = 2; // Check for decisions in 2FPS
public static int actionUpdatesPerSecond = 5; // Act in 5FPS
public static int reportUpdatesPerSecond = 15; // Report in 15FPS
private static int nextNpcId = 10000;
public void Awake()
{
agent = GetComponent<NavMeshAgent>();
}
public void Start()
{
npcId = nextNpcId;
nextNpcId++;
npcs.Add(npcId, this);
Health = maxHealth;
agent.speed = movementSpeed;
lastActTimeIndexed = new float[indexedActionsCount];
wasActionCompletedIndexed = new bool[indexedActionsCount];
floatDataIndexed = new float[indexedFloatDataCount];
boolDataIndexed = new bool[indexedBoolDataCount];
vector3DataIndexed = new Vector3[indexedVector3DataCount];
coroutineDataIndexed = new IEnumerator[indexedCoroutineDataCount];
ServerSend.SpawnNpc(npcId, type, transform.position);
InvokeRepeating("GetTarget", 1.0f, 1.0f);
InvokeRepeating("UpdateDecisions", 0.0f, 1.0f / decisionUpdatesPerSecond);
InvokeRepeating("UpdateActions", 0.0f, 1.0f / actionUpdatesPerSecond);
InvokeRepeating("SendUpdates", 0.0f, 1.0f / reportUpdatesPerSecond);
OnEnterState();
}
public void TakeDamage(float _damage)
{
}
public bool GoTo(Vector3 location)
{
}
public void TransitionToState(NpcState nextState)
{
OnExitState();
currentState = nextState;
OnEnterState();
}
public void StartCoroutineOnNpc(IEnumerator routine)
{
StartCoroutine(routine);
}
public void StopCoroutineOnNpc(IEnumerator routine)
{
StopCoroutine(routine);
}
private void OnEnterState()
{
var hooks = currentState.onEnterHooks;
for (int i = 0; i < hooks.Length; i++)
{
hooks[i].Apply(this);
}
stateTimeOnEnter = Time.time;
wasActionCompleted = false;
}
private void OnExitState()
{
var hooks = currentState.onExitHooks;
for (int i = 0; i < hooks.Length; i++)
{
hooks[i].Apply(this);
}
}
private void UpdateDecisions()
{
currentState.UpdateDecisions(this);
}
private void UpdateActions()
{
currentState.UpdateState(this);
}
private void SendUpdates()
{
ServerSend.NpcState(this);
}
}
In JavaScript world I would just have 1 array or object and put any data this particular NPC needs to it. But in C# I need a strongly typed place to put data for each data type my scripts could require.
Example of data usage in script:
I don't think having so many arrays and counters on MonoBehavior is a good idea, especially that there may be a lot of NPCs on scene. Any advice on building better storage while maintaining script flexibility?
Clarification: All the behavior logic is controlled by flexible ScriptableObject states. The problem is these objects cannot store any runtime data, but they have access to my Npc MonoBehavior (component) instance.
Initial code for this approach came from Unity tutorial