Where's the proper place to store the state of an NPC?

Hey guys!

Creatin’ a multiplayer game with both Player Controlled Characters (doh) and Characters controlled by the AI (aka monsters, creatures, enemies, whatever.).

I’m using the PlayerState to store Current HP, Current Mana, etc. of the Player’s character. Also, under the server supervision, this PlayerState has some functions to calculate damage given, received, and et cetera. These functions, naturally, can also be reused by AI enemies because the same rules will always apply (like damage calculation, etc.).

But according to this official article:

A PlayerState is the state of a participant in the game, […] Non-player AI that exists as part of the game would not have a PlayerState. […]

As you may guess, I’m struggling to decide the proper place to store the state of my NPCs. Should I create a separated Actor, spawn it, attach it to my NPC and use it as the state, repeating exactly every variable and function my PlayerState has?

The question may sound dumb, but even after reading several contents and sources, I couldn’t come up with a solid, scalable solution.

Any guidance is welcome! Thanks!

1 Like

There are many ways you could go about doing this, and I don’t think there’s a single proper way of doing it.

If you want it to function identically between players and AI without duplicating the system between the two, you could just use the same PlayerState class as the players for the AI, but you’d have to manually spawn it for the AI since PlayerStates are only spawned for players by default. I wouldn’t really recommend this though, because PlayerState is an Actor, so you’re effectively spawning two Actors for each AI you spawn (the AI itself and its State).

You could also use a Struct to store the necessary information, but then you sacrifice the ability of having the functions built into it since Structs are purely data objects. Structs don’t have the overhead that comes with effectively doubling your AI Actors as the PlayerState method would, though. Also Struct variables can be replicated, so you wouldn’t need to create a separate Actor attached to your AI to handle it.

A nice compromise between these two methods may be to create a Component. Components can be replicated and can have both variables and functions. In addition to this, they are automatically spawned for each instance of the class they’re attached to, so you’re guaranteed to get an instance with each AI you spawn without having to worry about manually spawning another Actor and attaching it.

Yet another way, which I personally use, is to create a base class shared between all your characters that stores HP, Mana, etc. and the functions. Then, I extend that class with the AI version and the Player version. For example, I have RPGCharacter which handles basic stuff like stats, attributes, equipment, skills, etc. Then I have RPGPlayerCharacter which adds player specific functions such as input, and RPGAICharacter, which adds AI specific functions. If necessary, you can override functions in the subclasses to completely change their functionality.

I may be a bit biased, but I highly recommend the last method because, as a programmer, it makes the most sense to me. You have functionality you want to share between two classes, so you derive them from the same base class and override or extend things to change it rather than writing the same thing twice.

I hope this helps!

1 Like

Hello, Chromarict, and first of all THANK YOU VERY MUCH for taking your time to write such a nice answer as you did.

So, first of all, yes, PlayerState doesn’t make sense at all. I was miscomprehending it. Making it manage an inventory which is limited to players is fine, but making it deal directly with characters which can be a playable character or an NPC smells. My architecture, at first, was exactly what you mentioned: NPCs spawning the PlayerState whilst the PlayerController had it by default. Was awful!

Before reading your answer, I’ve started modeling a new architecture: creating an actor called CharacterState and attaching it to my characters. Thankfully, my architecture is, again, exactly what you’ve described: a CharacterBase, a CharacterPlayable and a CharacterNPC, where the two latter inherit from CharacterBase, sharing common approaches, rules, logic and this CharacterState, which I managed to implement easily.

One thing that was bothering me with this new approach is that both Current HP and Max HP can be increased on-the-fly, and the issue of having this CharacterState as a third-party actor attached to my characters is redundancy.

I mean, I had to have Current HP and Max HP on both my characters and their states. Having these two variables isn’t exactly the problem, but this design simply doesn’t scale. As soon as the CharacterState was spawned, I had to set its initial values to mimic what my characters had set as initial values. If I added a new mutable variable to my characters such as AttackSpeed, I had again to set its initial value as the state’s AttackSpeed initial value.

After reading your post, spotlighting the last proposal you made–the one you recommended–, I realized that I don’t need to create this CharacterState. I was overcomplicating what’s supposed to be easy: let’s leave all this information to where it belongs, the own character.

1 Like

And this is something very interesting about programming and a very broad framework like Unreal: deciding design is the most complicated thing. Sometimes when you are less experienced like me in creating something–in our case, games,–you end up idealizing and mounting hardcore architectures to do basic stuff, which oftentimes, just like our current context, is easily and simply resolved with the most obvious, minimal implementation.

To summarize, my brand new architecture (wow!) will look like this:

Three actors. CharacterBase, CharacterPlayable and CharacterNPC. Both CharacterPlayable and CharacterNPC are children of CharacterBase.

Inside CharacterBase, I’ll have common attributes that all characters have, such as Max HP, Current HP, as well as functions managed by the server to calculate how these attributes will be decreased or increased, considering transcendental modifiers, et cetera. This way, both Playable Characters and NPCs will share the same rules, avoiding repeating myself.

Basically, that’s it. As Chromarict pointed out, if I need to change a behavior exclusively on NPCs or PlayableCharacters, it’s as simple as override a function and we’re good to go.

Lastly, before we wrap up, thank you again Chromarict. Different from various sources I’ve read about designing state, you were the one showing several possibilities, always presenting the pros and cons of each one of them to make me argue with myself what’d be the ideal choice based on the reality of my project.

Cheers!

2 Likes

Hi chief, thanks for taking the time to post your thought process on this. I’m having a similar quandary, so reading this really helps.

Composition over Inheritence is an important topic, especially for game code.

While suggested approach is based on having a base class with shared parameters, this isn’t the most scalable approach and often leads to over bloated base classes and non-sensical inheritence hierarchies in the long run.

In general it is much better to encapsulate shared logic in their own components and attach components as they needed. This gives you power of flexible architecture where you can add/remove features from actors as needed. And it keeps your base classes clean and moves related logic to their own individual components.

Here is a nice tutorial about all this.
What are Components? - Breaking Down the Components of Gameplay (epicgames.com)