Skip to content

Movement system

Jerome Renaux edited this page Jan 31, 2020 · 4 revisions

The current movement system is point-and-click: you click on a tile and, if possible, the character goes there. The camera may or may not follow it during the movement depending on whether it is locked or not.

Thee are two constraints on the possible movements:

  • The destination must not be a collision tile (water, a rock, a tree...)
  • The path to get there must not be too long (hard-coded limit of 36 tiles for now)

In a nutshell, when a player moves, the path is computed client-side and sent to the server. The client starts moving immediately along the computed path. The server takes care of notifying the other clients of the movement; they receive the path and play it for the relevant character in their copy of the world.

The server doesn't send regular position updates about the players, it only communicates new paths, and the clients take care of moving the characters along the received paths. The main implication is that there is no tight synchronization between the movements of the players, the other players will always see the movements a bit later than the moving player will. This is not an issue so far since the gameplay doesn't rely on knowing the location of other players accurately over time.

The sections below describe in more detail the steps that take place when moving.

For a discussion about a possible keyboard-controlled movement system, see this issue.

Client

Marker

The mouse location is tracked by Engine.trackMouse() and used to make a square colored marker follow it. The marker logic is in Engine.updateMarker().

Collisions can be detected by Engine.checkCollision(tile.x,tile.y). If there is a collision at a given tile, the marker will be colored red, and white otherwise.

Moving to a location

Upon clicking on the ground (detected by Engine.handleClick()), Engine.moveToClick() is called, which begins the actual movement logic.

First, the "destination action" is set. This indicates which action should be performed at the end of the movement. When moving to a free spot on the ground, no action should be taken, so it is set to 0. In the case of moving to pick an item or enter a building, the value will be different (see below).

Then, the path that the character should follow is computed in Engine.computePath() by the Engine.pathFinder. In addition to the start and end tiles of the path, the following parameters are used:

  • seek: whether to use "seek mode" or not. In seek mode, the longest path (up to the maxLength path length limit) in the direction of the target is computed, instead of the path up to the target. This difference is used to make Civs travel long distances when hunting players without having to compute very long paths.
  • nextTo: whether to compute the path up to a cell right next to the target, or to the target itself. When picking up an object, the player should stop next to it, not onto it.

There are two reasons that the pathfinder may not find a path:

  • The destination is invalid, it's a collision tile
  • There is no path to the target shorter than the maxLength path length limit (for now hard-coded to 36 in Engine.maxPathLength)

If a path has been found, two more steps are taken before starting the movement:

  • A check is made to see if the path crosses an active battle area; if yes, the path is trimmed so as to stop when entering the battle area
  • Depending on the destination action, the last tile of the computed path is removed

Finally, the computed path is sent together with the destination action to the server to synchronize the movement with the other players. The character actually starts moving by calling Engine.player.queuePath(path).

Moving to an item or a building

These kinds of movement are pretty much the same. One difference is they are triggered when clicking on an item or building. In addition, a different destination action is set (the possible values are commented in Player.setDestinationAction()).

Server

Paths sent from clients are processed by GameServer.handlePath() and relayed to MovingEntity.setPath(). That method basically stores the path in the relevant object as a list of tiles to traverse.

MovingEntity.updateWalk() is called at regular intervals (defined in the config in updateRates.walk) for all MovingEntity instances. The role of MovingEntity.updateWalk() is to update the server-side coordinates of the entities at an interval that matches the entity's walking speed so that the server-side coordinates match the client-side coordinates (give or take a few hundred milliseconds).