Design patterns for NPC behavior

(Elijah Voigt) #1

:wave: Hey all. This is my first post on the Amethyst Forums, so let me know if I need to read the guidelines or re-categorize the post or something. :smile:

I’m curious what design patterns people are aware of for creating Non Player Characters (NPC) which have unique behavior sets. e.g, a combat game may have some NPC which are aggressive and others which are timid.

I think I want my system to iterate over all NPC and call methods like npc.make_decision(some_information_about_the_world) and based on the different behavior defined for that NPC (entity?) that NPC makes different decisions.

This question was initially fueled by a card game I’m implementing with Specs. My pen-and-paper design to creating NPC with different behaviors is something like this:

let mut world = World::new();

world.register::<NPC>;
world.register::<Strategy::Defensive>;
world.register::<Strategy::Aggressive>;

world.create_entity().with(NPC).with(Strategy::Defensive).build();
world.create_entity().with(NPC).with(Personality::Aggressive).build();

But that’s about as far as I get. What should the System that interacts with these entities look like? What should the Strategy::* components look like? Is this design pattern completely off course? I’m very new to game dev and ECS so I might be over-looking something completely obvious to a more seasoned designer.

I plan on going down this rabbit hole and seeing where it takes me. If I find a way to make it work I’ll update the thread, but I’m also ready for my design to change drastically based on what y’all say! Reading material, examples, and lectures about ECS design patterns welcome!

1 Like

(Théo Degioanni) #2

It seems like you are describing two approaches in your post. Nonetheless, the strategy-as-component seems to be the best to me.
What I would do is a system that would each update the state of entities that have a specific kind of behavior (AgressiveUpdateSystem, DefensiveUpdateSystem). That way your code is organized, isolated, reduces the amount of unexpected branches and can potentially be parallelized.

1 Like

(Joël Lupien) #3

Usually my strategy would be to use something like this:

#[derive(Component)]
pub struct NPC {
    movement_strategy: MovementStrategy
}

pub enum MovementStrategy {
    Fight,
    Flee,
}

It comes back to what you proposed in your post, except that this method is slightly easier to serialize (save/load) and to manipulate (iterate over NPC instead of (NPC + Behavior)).

However, you lose a tiny bit of performance from doing a match over the MovementStrategy in each system.

2 Likes

(Karll) #4

It’s a “tiny bit” loss of performance assuming that all strategies use the same data.
But I believe it’s expected for different strategies to use different data, so wouldn’t this method result in cache misses?

To be fair, considering it’s a card game and there will be only one NPC, I don’t think performance is anything to worry about. Personally I would focus on readability and ease of use in this case and I like jojo’s approach for that and you won’t end up with several components. But that all will depend on how complicated the strategies will be.

1 Like