1
\$\begingroup\$

I'm using an Entity-Component System and have an AI System that acts on certain entities (agents) within the system. My question is: how should the AI system interact with the world and the agents? I'm considering two opposite approaches:

If the AI deems something is supposed to happen (e.g. an agent is sleeping, which causes its Fatigue level to decrease):

  1. It does this directly. (e.g. the AI system mutates the fatigue property on the agent on each update)
  2. It adds one or more Components to the entities. (e.g. the AI system adds, once, a SleepNowComponent to the agent, and then waits for it to reach a terminal state on each update)

Pros/cons of #1: it's simple, however if I ever had another section of code that did basically the same thing, I'd have to either duplicate or factor it out into a common function.

Pros/cons of #2: it's more complicated -- if the agent's AI is in the "sleep" subroutine, the AI system will basically do nothing on that agent except check to make sure that the proper component is attached to the entity. When the component is finished (e.g. the fatigue property of the agent reaches 0), the AI will move onto the next task. On the bright side, if any part of the game needs the entity to sleep, it's as simple as adding the right component.

What are people's thoughts on this? I'm pretty evenly split between the #1 and #2 options right now.

\$\endgroup\$
1
\$\begingroup\$

Your second option seems like an over-engineered way to perform the first option. Consequently I'd advise you simply go with the first option.

An agent has an API, a public interface that dictates what can be asked of that agent by other systems in your code. It's perfectly reasonable (and in fact, perfectly clear) to manipulate an object through its public API. It's usually the best way to go about things.

Your second option, attaching some component to the agent, implies one of three things:

  1. either the component will directly tell the agent what to do (adjust the fatigue level) via the agent's public API
  2. or the agent will know to look for the component and if it exists, manipulate its fatigue privately
  3. or the component will manipulate the agent's fatigue privately via some back-door API (such as friend access in C++)

None of these are great, in my experience. With (1), you're just creating an unnecessary level of indirection between the AI deciding something should happen to the agent and the agent being made to respond accordingly.

(2) could be used if the agent didn't want to expose its fatigue publicly for whatever reason (more on this below), but means that this behavior needs to be written for any entity that might support "sleeping;" the AI system could easily attach a SleepNowComponent to an object that doesn't know about SleepNowComponent and will effectively do nothing. Now you're written to an assumed-public API that you have less certainty will actually have any effect.

(3) doesn't seem like the best use of friendship or similar to me; by connecting the component and the entity together in such a fashion behind the scenes you're not really saving yourself a ton of work or protecting yourself from an otherwise easy-to-make logical error. Fatigue should really be a public part of the API, obviating the need for the friendship and bringing us back to scenario (1).

So I think you should have the AI directly tell agents what to do when it is appropriate. I don't quite see how you think this would end up with a code-duplication problem, but perhaps I am missing something about the rest of your architecture or what you are thinking.


That said, for all that I went with your fatigue/sleeping example above, I don't think that example itself is a great idea. If being "asleep" means fatigue should decrease over time, this seems to be something that should be handled by the thing that is sleeping itself, otherwise it seems like you're building architectures where it's very easy for alleged invariants to be violated (e.g., "sleeping decrease fatigue" but only if something else tells a sleeping thing to remove fatigue).

I also think it smells funny to leverage a whole component to simply toggle a state (which is what the name of SleepNowComponent implies).

\$\endgroup\$
  • \$\begingroup\$ Thanks Josh. I do tend to overthink things, but that's why I'm asking :) The "code duplication" bit is...imagine if there are multiple reasons why an agent might want to sleep -- either it's tired or it's sick. In the #2 case above, it could just attach the SleepNowComponent in either case and hands off to the system that processes that component type. Or in the #1 case...well, actually I'd just create another class that encapsulates the go-to-sleep process anyway and use that in both scenarios. So, good point. \$\endgroup\$ – Max Dec 22 '16 at 6:18
  • \$\begingroup\$ The SleepNowComponent would be read by the SleepingSystem or something and the system would directly manipulate the fatigue of the agent; as opposed to a GoToBedComponent, which would move the agent to a location and then be superseded by a SleepNowComponent. \$\endgroup\$ – Max Dec 22 '16 at 6:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.