0
\$\begingroup\$

The problem

Suppose I have a game where there are multiple Input Controllers:

  • LocalPlayerController: Reads inputs from the keyboard/mouse/gamepad.
  • RemotePlayerController: Reads input from the network packets.
  • AiController: Takes decisions based on the environment.

My Character entitity could be a human player, or an NPC. It currently exposes methods like moveForward, moveBack, strafeLeft, strafeRight.

Those methods are called by each controller acordingly:

class LocalPlayerController {
    public void update(Character character) {
        if (Input.getKeyDown(W)) character.moveForward();
        if (Input.getKeyDown(S)) character.moveBack();
        if (Input.getKeyDown(A)) character.strafeLeft();
        if (Input.getKeyDown(A)) character.strafeRight();
    }
}

The same for the other kind of controllers:

class RemotePlayerController {
    public void update(Character character) {
        // Read network packets and call those 4 methods
    }
}
class AiController {
    public void update(Character character) {
        // Makes a decision based on the environment and call those 4 methods
    }
}

The question

I know I can abstract the character movement (moveForward(), moveLeft(), etc) and interaction (shoot(), openDoor(door), etc).

Question: What would be the best approach to abstract the eyes/head looking direction?

Options:

  • Option 1: Have methods/properties that move the head direction based on yaw/pitch. For example: setYaw(yaw), setPitch(pitch), getYaw() and getPitch(). Inside each setter, they would limit the pitch and yaw to a maximum per game tick like delta_yaw = max(delta_yaw, MAX_DELTA_YAW)
  • Option 2: Have methods/properties that set the look at target in world space where the head should look at. For example: setHeadLookAt(target), getHeadLokAt(). Inside the class would store the current look at target xyz and rotate towards that point acording to a MAX_DELTA_YAW, MAX_DELTA_PITCH, etc.
  • Option 3: Another different approach?

The first option seems suited for a LocalPlayerController because that's the naive way you normally read the mouse coordinates in the screen and adjust the deltaYaw and deltaPitch based on the deltaX and deltaY of the mouse.

The second option seems more suited for an AiController because normally you want your Ai to look to a certain target.

What would it be the best option? Please justify your answer with examples of pros and cons of each option acording to different input methods (mouse, AI, network).

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

In general, it depends on the game. You say:

The first option seems suited for a LocalPlayerController because that's the naive way you normally read the mouse coordinates in the screen and adjust the deltaYaw and deltaPitch based on the deltaX and deltaY of the mouse.

But not on every game you don't.


Let us keep in mind that both sound like high level abstraction to manipulate some transforms, plus animation. In fact, that is likely also true for the other movement methods.

Now, there is a common ground between setYaw(yaw) + setPitch(pitch) and setHeadLookAt(target), and that is setHeadLookDirection(direction). You can easily make a direction vector from yaw and pitch, and you can easily make a direction vector from a target. So you can implement your options 1 and 2 on top of it.

I'm not claiming this is the best option. I say it depends on the game and stand by it. However, if you are in a situation where both make sense. Well, both make sense, so have both, except have them as abstraction over a common ground.

Following what I can see of your architecture, you could wrap the character in another object - or otherwise define extensions for it - that allow you to use the preferred approach for the controller.


With that said, I'm not convinced this is the best way to decouple an entity from whatever controls it. If you change the methods on your character, you need to update the controller that use them, so what you have is loosely coupled, not decoupled. If they were decoupled, they could change independently. And, to be clear, loosely coupled is OK. But if you want truly decoupled, keep reading.

By the way, upon reading what I wrote below, I think I might be overengineering this - again, it depends on the game, I'm imagining a battle with many AI agents and cooperative multiplayer. Which might not be what you had in mind, so take it with a grain of salt.

This is the idea:

  • Make all the attributes of the character read-only. No setters.
  • Have the controllers create and send command objects. These could be serialized to be send to the network, or for storage and playback. This also means it does not matter from where the command objects come from.
  • Then the engine will queue these command objects for the character, or characters. This also means there can be multiple controllers for a character - if that is something you want. Or a controller can control multiple characters at once.
  • Have the character pull command objects from the queue. Some command objects will be primitive for the character, and the character executes them right away. For the rest, the character will give them to a translation service that queues other commands. So you can have commands as high level as necessary.
  • Have a service locator that will allow the character to pick the service to translate commands. And the service locator could be injected on the creation of the character. This means that different characters might translate the same commands differently. Some might translate them to nothing, i.e. ignore the commands.

Now, the Director AI can create and send an command object that says "attack position X", and the individual characters can translate that to "go to position X" and "attack nearby enemies". And translate "go to position X" - via a path-finding service - to a series of move commands, or it could be ignored if the character cannot move or is too far away. And "attack nearby enemies" could translate to "if there is an enemy (find cover and attack)", which would make the character remember to execute "find cover" and "attack" when it becomes aware of an enemy (and, if you think about it, we might use the same command system as sensor system). And "find cover" can translate to another series of move commands via a cover service, and "attack" can translate to "aim" and "shoot" commands. So that "attack position X" can boil down to the same actions a player could make.

Consider also that you may even implement a chain of command where a character sends command objects to other characters. And it would just work that killing the commander will imply the soldiers don't get new orders.

That is how decoupled looks like.

\$\endgroup\$
1
  • \$\begingroup\$ Amazing answer. I marked it as accepted. I still accept more answers or ideas respect this topic. But I think your answer is very complete. \$\endgroup\$
    – Ciberman
    Jan 18, 2022 at 21:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .