3
\$\begingroup\$

I'm trying to learn the Command design pattern and apply it to the game I'm working on. First I read about the general implementation, and I feel like I understand it pretty well. Now I want to know how to use it in games. I started reading this book about design patterns in games and in the section about the command pattern the author says:

We can use this same command pattern as the interface between the AI engine and the actors; the AI code simply emits Command objects.

and

The decoupling here between the AI that selects commands and the actor code that performs them gives us a lot of flexibility. We can use different AI modules for different actors.

I'm trying to figure out how this pattern would be used for game AI in practice. I assume there would be an AIEngine class or something like that with GenerateCommand() function. Then the GameActor would ask the AIEngine for a Command whenever it finishes executing the previous one.

What I don't understand is how exactly it would work. In order to generate commands the AIEngine needs to keep track of all the GameActor objects anyway (or at least of the one it's generating the commands for), so what's the point of using commands? At this point the AIEngine could just manage the GameActor objects directly.

I've been trying trying to wrap my head around this for quite a while. If someone could write a example implementation of the AIEngine interfacing with GameActor it would help me a lot. I don't think I'm able to understand it without seeing some code.

\$\endgroup\$
  • 3
    \$\begingroup\$ If after reading up extensively about a pattern, you don't understand why you'd use it, then don't use it. There's no law saying you have to implement AI this way, so use the strategy that makes the most sense to you in the context of the game you're making. If you're experiencing a problem in designing your AI, and you think this pattern might be useful to solve it, it's generally more effective to ask about the problem itself, with concrete examples from your attempts so far, and invite any good solution to that problem, rather than only those that use a specific pattern. \$\endgroup\$ – DMGregory Sep 29 '18 at 12:10
3
\$\begingroup\$

The key to why you would use the command pattern is in the text you quoted here.

The decoupling here between the AI that selects commands and the actor code that performs them gives us a lot of flexibility. We can use different AI modules for different actors.

The decoupling is key here. If you have the code for determining which action to perform (what command to execute) completely decoupled from your code for determining what to do given a certain action, then you can freely change one without having to think about the other.

For example: Imagine that you create an AIEngine subclass, call it SimpleAIEngine, that generates a command for the enemy unit to attack the player whenever that player is close enough to the enemy unit. All SimpleAIEngine would do is determine if the unit is close enough to the player and if so generate an AttackCommand and if not generate a DoNothing or ContinuePatrolling command. It might create the command with something like return AttackCommand(enemy, player);.

Now imagine you want the player to be able to cast a spell called "Confusion" that causes an enemy unit to attack a random enemy unit. You can use the same AttackCommand as before, but just pass it a random enemy unit instead of the player, doing something like return AttackCommand(enemy, randomEnemyUnit());.

The way that these commands are generated is completely different, but the behavior of the enemy when they receive the command is the same. Neither your SimpleAIEngine nor your Confusion spell logic has to dive into how an enemy attacks another unit, it just generates the command to be handled by the abstraction that does handle said logic. Because you don't call some sort of unit API call, to unit test either of these command generating methods you do not need to dive into the unit to see if it was appropriately told to attack another unit; you need simply check that the appropriate command was generated.

\$\endgroup\$
  • \$\begingroup\$ I understand why you would use the Command pattern if you wasn't to postpone the execution of the command or don't care who is going to execute it, but the use case you provided is what I don't really understand. Is there any advantage to using this approach apart form easier unit testing? Why wouldn't I create IActor interface which would be implemented by enemies and provide the same functionality as commands would. So basically, what's the advantage of using commands if I don't want to postpone the execution or change the actor it's executed on? \$\endgroup\$ – Wojtek Wencel Oct 9 '18 at 21:36
  • \$\begingroup\$ Because then your interfaces are not decoupled. If you change you actor's do-action interface you have to dive into your decide-what-action-to-do code. With the command pattern, your decide-what-action-to-do code doesn't even have to know what an actor looks like or even that the code for it is written in order to function. It is hard to overstate, for large projects (like games) the value of decoupling different modules/classes/abstractions/etc. \$\endgroup\$ – Garrett Gutierrez Oct 10 '18 at 11:46
  • \$\begingroup\$ If you change you actor's do-action interface you have to dive into your decide-what-action-to-do code. How is that different form changing a command? If you for example add a command you have to update your decide-what-action-to-do code as well, if you want to use it. I think I'm just misunderstanding something, could you give an example of when this would be different? \$\endgroup\$ – Wojtek Wencel Oct 10 '18 at 16:46
  • \$\begingroup\$ A command is radically more simple than an actor. The objective of your decide-what-to-do interface is to generate commands. If it was to generate ints (think about this as the ultimate form of simplicity) what you just asked would be like asking "what if the way ints work changed, or we changed from an int to a char?" You would have to deal with ints/chars, rather than entire Actor submodule. With a command setup, someone looking at Actor does not need to know anything about AI to know when Actor is modified. They only need to know about commands and when they are processed, which is simpler. \$\endgroup\$ – Garrett Gutierrez Oct 10 '18 at 17:17
  • \$\begingroup\$ Okay, so let's say I have a setup like this: an ICommand interface and a JumpCommand which implements this interface. On the other hand we have an IGameActor interface with JumpCommand() function and and an Actor class that implements it. If I were to change the logic of JumpCommand it wouldn't impact the AIEngine or whatever is calling it, since the actual logic is hidden in either case. Am I missing something? \$\endgroup\$ – Wojtek Wencel Oct 10 '18 at 17:38
3
\$\begingroup\$

I own a copy of this book and my interpretation of this example of the command pattern is to encapsulate an action (to be executed by an AI actor) and execute it at a later date. In doing this you could create a queue of these actions. An AI actor would then use this queue of actions as its behaviour.

Consider a turn based game in which the objective is to build and conquer. The AI opponent may need to build up some sort of base, gather resources, and eliminate enemies. The AI engine (opponent) would crank out a queue of commands to satisfy each of these requirements. Specialised AI actors would then execute the commands from a given queue when it is their turn.

The AI "engine" only needs to know how to turn objective requirements into commands. A command does not need to know how it was created or who will execute it, it just needs to encapsulate some logic. The AI actors only need to know how to execute commands given to them. By seperating the logic, the same code can also be used for player governed AI actors to carry out a queue of commands created by said player. It also makes unit testing a lot easier.

\$\endgroup\$
  • \$\begingroup\$ But at least in the book the command has reference to the AI Actor when it's created. That's the case that buggs me the most. \$\endgroup\$ – Wojtek Wencel Oct 9 '18 at 13:56
  • 1
    \$\begingroup\$ As the author states, "Patterns differ from single algorithms because they are open-ended. Each time you use a pattern, you’ll likely implement it differently." He's simply providing a very minimal workable concept so you can see it in action. His intent is never to say "this is precisely how this is done," because... well, there is no precise approach to any pattern. Even the singleton pattern has a lot of flexibility, and it's possibly the simplest "pattern" there is. \$\endgroup\$ – Jesse Williams Oct 9 '18 at 14:00
1
\$\begingroup\$

First off, GREAT book. It's one of my go-to books when I'm looking to revamp one of my patterns and make it tighter. Good call.

As for doling out actions, the nice thing about this is that you can interpret actions on the GameActor or Player or Object at any time (or simply ignore them). This decoupling allows the actor itself to determine which commands are valid for it in a given situation.

Think of it this way. Your AIEngine or AIManager might have very basic instructions to give, say: patrol, attack, defend, hide. These are assuming certain parameters (can see player, player in range, et cetera). Perhaps some actors don't play by those rules at all times. The AIEngine can tell an actor ATTACK but the actor itself, due to it's state can then say NUH-UH and carry on. This allows your AI to have deeper involvement of actor actions.

I need to move my AI to a command pattern, to make changes easier down the road. However, this is precisely why I'm doing so slowly. Right now each actor in my game manages it's own AI from a class - which is ugly and inefficient, but allows that granularity. The decoupling allows one to keep the granularity, but use command patterns for "high level" AI. If the AIEngine says it's time to jump, but the actor cannot, the actor can ignore that while letting the engine carry on. It also allows complex ML to be introduced for your AI without overriding other rules you may have in your game.

But, as DMGregory said above, it doesn't mean you have to go this route. My current system of AI uses a state machine, which does work fine. It just also adds a lot of duplication to my code and makes it somewhat less manageable in some situations. If I implement a command pattern and decide that it can't give me QUITE the level of granularity I want or need, I'll end up sticking with the current system and trying to flesh it out better.

\$\endgroup\$

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.