Implementing the Command abstraction between view and logic

    This site uses cookies. By continuing to browse this site, you are agreeing to our Cookie Policy.

    • Implementing the Command abstraction between view and logic

      Hi,

      I've started rereading sections of the book that I had trouble with after taking an extended break from game related stuff. If I understand the book correctly, every action that entities can perform should be represented by commands. I like the idea, but am running into a few implementation issues.

      1. Should the event system be used for sending commands to the game logic? I think this'd work if the command required no response to the caller (e.g, set accelerator to 80%) but it'd be messier if the sender wanted a response. For example, if the view got a "show inventory" command, it sends this off to game logic and needs a response telling it what the items are. A possible solution is to have game logic send yet another event with the required information in response. This would mean that the game logic must subscribe to every event representing a command that it could accept, and the views would need to register to the events that correspond to the response to these commands. That sounds like a lot of work.

      2. Another alternative is to explicitly have a CommandResult handleCommand(Command c) method in game logic. All commands inherit from Command, and all possible results from executing a command inherits from CommandResult. For example,

      class GetInventory : Command {
      int entity;

      }

      class InventoryCommandResult : CommandResult {
      // data members with item information here
      }

      Then human view would do a synchronous call like
      if (i key was pressed) {
      CommandResult inv = logic->execute(GetInventory(player));
      // downcast to the right type and display appropriately
      }

      3. Break the command abstraction for commands that senders need response for by implementing game logic methods for these specific commands; I'd rather not do that.

      What are good solutions to this problem?

      another related question: is this the right understanding of how I'd implement turn-based combat with this architecture?

      1. Something in game logic detects that you're in combat. Human view responds to the InCombat event emited by changing to a combat display.
      2. Game logic decides the turn order for each round. When it is either the player's or AI's turn, PlayerTurn/AITurn event is sent. It goes into a paused state while waiting for actions to be performed.
      a) If player's turn, human view sends command to logic when player selects option.
      b) If AI's turn, run code somewhere to choose an action (probably in AI component for an entity?). Send the same sort of commands human view might corresponding to selected action.
      3. If I used event-based approach for commands, Logic wakes up in response, performs the actions, and emits events about the results of that action (e.g, goblin dies). If synchronous approach, logic returns control to human view after its done. Either way, Human view responds by e.g, playing sound and somehow "pauses" only the human view till the sound is complete so that you don't try to show results of multiple actions concurrently. Need to think about how to do this.
      4. cycle repeats.

      The post was edited 1 time, last by Neurone ().

    • Thinking about it more, I think I may have come up with a better solution.

      I'd probably want a state machine for the game logic to represent the fact different things should be done depending on what mode the game is in (e.g combat mode, exploration) and that its fine for views to be coupled with logic as they need to know about the model to display the information anyway.

      As I'd have a lot of menus, I'd also need a state machine for the human view to represent various UI screens. Then depending on what state the view is in, views subscribe to events like entering combat and exiting combat so that they know when it needs to switch states. When logic sends these events out, it also sends a pointer to its current state and as views are coupled to logic, they can do the appropriate downcasting to access functionality appropriate to that state, supporting the example use case of an inventory screen.

      Commands would either be direct calls to the appropriate game logic methods or events (though since views are already coupled to logic, it may be easier just to do direct method calls).
    • Neurone wrote:

      Hi,
      1. Should the event system be used for sending commands to the game logic?

      Yes, probably. It depends on the types of commands. The event system presented in the book is a broadcaster style of event system. It's purpose is to broadcast some event and have a bunch of registered listeners respond to that event in some way. An example I often use (I'm pretty sure I used it in the book too) is death. When a Sim dies, we send an event so that the other systems can respond. The quest system can remove the sim from any active quests, the household can remove that sim from the household, etc. Another type of event system would be more targeted. I wouldn't use a broadcaster to send specific commands to specific game objects.


      I think this'd work if the command required no response to the caller (e.g, set accelerator to 80%) but it'd be messier if the sender wanted a response. For example, if the view got a "show inventory" command, it sends this off to game logic and needs a response telling it what the items are.

      You shouldn't need a response in most cases. Your own example is a great example of something that wouldn't actually require a response. I would expect an event to show the inventory, which is 100% a view task. The logic already knows about the inventory and doesn't need to be told what's in it. Showing the inventory only exists so the player can interact with it.


      A possible solution is to have game logic send yet another event with the required information in response. This would mean that the game logic must subscribe to every event representing a command that it could accept, and the views would need to register to the events that correspond to the response to these commands. That sounds like a lot of work.

      If you really need events to get responses, there are several solutions. The simplest is to require two-way events be sent synchronously through SendEvent() and have them communicate through the Event object. Having the receiver send a replying event is also reasonable, and is what we did on The Sims 4 to handle events between gameplay systems and the UI. This is a bit more complex, but it allows more a lot more flexibility. In my own engine, I do this for certain events. For example, when an entity wants to go somewhere, it will send a pathing request through an event. The actual pathfind can take multiple frames depending on the complexity of the graph and sends an event back to the entity when it's done with the path plan. It's worth noting that these two events aren't even coupled together. If the pathing system ignores the event, the entity will simply not move. The reply event isn't anything special, it just tells the entity to follow some path. I could easily write something outside of the pathing system that tells the entity to follow a specific path if I want (for example, to use a pregenerated path).


      2. Another alternative is to explicitly have a CommandResult handleCommand(Command c) method in game logic. All commands inherit from Command, and all possible results from executing a command inherits from CommandResult. For example,

      class GetInventory : Command {
      int entity;

      }

      class InventoryCommandResult : CommandResult {
      // data members with item information here
      }

      Then human view would do a synchronous call like
      if (i key was pressed) {
      CommandResult inv = logic->execute(GetInventory(player));
      // downcast to the right type and display appropriately
      }

      So in this version, you'd have special objects? Perhaps, but it depends on what your true goal is. Why add this complexity? What does it buy you? I'm not saying it's the wrong approach, but what's the benefit? It looks just like another event system.


      3. Break the command abstraction for commands that senders need response for by implementing game logic methods for these specific commands; I'd rather not do that.

      This seems like the opposite of what you're proposing. ;)


      another related question: is this the right understanding of how I'd implement turn-based combat with this architecture?

      1. Something in game logic detects that you're in combat. Human view responds to the InCombat event emited by changing to a combat display.
      2. Game logic decides the turn order for each round. When it is either the player's or AI's turn, PlayerTurn/AITurn event is sent. It goes into a paused state while waiting for actions to be performed.
      a) If player's turn, human view sends command to logic when player selects option.
      b) If AI's turn, run code somewhere to choose an action (probably in AI component for an entity?). Send the same sort of commands human view might corresponding to selected action.
      3. If I used event-based approach for commands, Logic wakes up in response, performs the actions, and emits events about the results of that action (e.g, goblin dies). If synchronous approach, logic returns control to human view after its done. Either way, Human view responds by e.g, playing sound and somehow "pauses" only the human view till the sound is complete so that you don't try to show results of multiple actions concurrently. Need to think about how to do this.
      4. cycle repeats.

      Yeah, that seems reasonable. One change I would probably make is to pull out all the combat stuff from the game logic and put it in its own class which is owned by game logic. Game logic should really just manage the logical managers and nothing else. This keeps GameLogic from becoming too much of a blob class.

      -Rez
    • Neurone wrote:

      Thinking about it more, I think I may have come up with a better solution.I'd probably want a state machine for the game logic to represent the fact different things should be done depending on what mode the game is in (e.g combat mode, exploration) and that its fine for views to be coupled with logic as they need to know about the model to display the information anyway.As I'd have a lot of menus, I'd also need a state machine for the human view to represent various UI screens. Then depending on what state the view is in, views subscribe to events like entering combat and exiting combat so that they know when it needs to switch states. When logic sends these events out, it also sends a pointer to its current state and as views are coupled to logic, they can do the appropriate downcasting to access functionality appropriate to that state, supporting the example use case of an inventory screen.Commands would either be direct calls to the appropriate game logic methods or events (though since views are already coupled to logic, it may be easier just to do direct method calls).


      I can tell that I do something different for every game, depending on the needs of that game. I wrote a chess game that uses events to manage the player turns and moves. A player will send move requests to the game board which in turn would update the game state and then send an event to the player manager (the thing that managed turns) that the turn was over. It was all highly event driven. In my own projects with my own engine, the game logic typically serves to spin up the game itself. I start up the appropriate processes and manage a few events. Most of the gameplay logic is written in Lua which in turn is very event driven. Lua doesn't "tick", that is it doesn't get an Update() call every frame. It only responds to events. Things that happen per-frame are implemented as C++ processes.

      So for me, a new project in my engine consists mostly of writing events to marshal data around, writing processes to handle per-frame logic, and writing Lua systems to handle gameplay, and writing Lua entity components to attach special behavior to entities. This isn't always the case, but it's the typical path.

      Creating a state machine seems reasonable if you have multiple game modes since it allows you to decouple the logic from each one. You definitely want to decouple your view from your logic, at least by using an abstract interface (IView in GCC). As long as you do that, you're fine. The key is that the views need to be able to change independently of the logic. As long as the interface remains the same, it's all good.

      -Rez