Player input

Player input in Hology can be handled by a built-in system for managing keybinds and propagating events to functions to react to input events. This system separates the configuraiton of keybinds from the effects of the player's action to be more flexible.

Configuring actions

Action identifiers

Your players intent is identified with an action identifier. This is key to later configure keybinds and triggering effects in your game as those actions are triggered by the player for example pressing keys or moving a mouse.

Action identifiers can be defined using a custom enum but also any number of string works. In the example below, we can see various actions be defined.

enum InputAction {
  moveForward,
  moveBackward,
  moveLeft,
  moveRight,
  jump,
  sprint,
  rotate
}

Configuring keybinds

Keybinds can be set for your actions on the InputService. This enables you to dynamically configure what keys trigger what action.

Keyboard and mouse presses

Pressing a key on a keyboard or a mouse can trigger actions by using the setKeybind method.

this.inputService.setKeybind(InputAction.moveForward, new Keybind("w"))

Keybind arguments

  • Key: The key pressed expressed as a string. Valid values can be found in KeyboardEvent.key

  • Shift: True if the shift key has to be pressed while pressing the main key.

  • Ctrl: True if the ctrl key has to be pressed while pressing the main key.

Mouse movement

Moving the mouse can be used to trigger an action.

this.inputService.setMousebind(InputAction.rotate, new Mousebind(0.01, true, "x"))

The mousebind takes 3 arguments.

  • Sensitivity: A multiplier for the mouse movement's difference in position (delta)

  • Flipped: If the movement of the mouse should be interpreted in a flipped axis.

  • Axis: The axis of mouse movement connected to this action. Either 'x' or 'y'.

Mouse wheel movement

The input used for scrolling such as a mouse wheel or some gesture on a touch pad can be configured using the setWheelbind method.

this.inputService.setWheelbind(InputAction.zoomCamera, new Wheelbind(0.0003, false))

The wheelbind takes 2 arguments.

  • Sensitivity: A multiplier for the wheel's movement in either direction.

  • Flipped: If the movement of the wheel should be interpreted in a flipped axis.

Binding actions to callbacks

Now that actions can be triggered by player input, they also need to somehow affect the game. This can be done by using methods on the InputService for to bind various callback methods to actions.

Toggle

Some behaviour can be triggered by a boolean for either being true or false. This is appropriate for for example a character's movement direction or if the character is sprinting as opposed to walking.

this.inputService.bindToggle(InputAction.sprint, player.toggleSprint)

Delta

Some behaviour is better represented by the difference in a value. For example, as you move your mouse you may want to the player's character to rotate in the direction of the mouse movement. This can be done by using the bindDelta method which takes a callback which receives a number.

this.inputService.bindDelta(InputAction.rotate, player.rotateY)

Player controllers

To organize your code, it is suitable to put the code related to your actions inside a player controller service. In the example below, we can see the implementation of a player controller.

The inputs are set up in the constructor. These may instead be read from some persistent storage of the player's configuration to enable the player to reconfigure these. Remember to also start the input service to start capturing the player's input using the start method.

The player controller has a method to possess a character. This gives the controller a reference to a character which in this example is an actor.

enum InputAction {
  moveForward,
  moveBackward,
  moveLeft,
  moveRight,
  jump,
  sprint,
  crouch,
  rotate,
  rotateCamera,
  zoomCamera
}


@Service()
class PlayerController {
  private inputService = inject(InputService)
  private character: Character
  
  constructor() {
    this.inputService.setKeybind(InputAction.jump, new Keybind(" "))
    this.inputService.setKeybind(InputAction.sprint, new Keybind("Shift"))
    this.inputService.setKeybind(InputAction.moveForward, new Keybind("w"))
    this.inputService.setKeybind(InputAction.moveBackward, new Keybind("s"))
    this.inputService.setKeybind(InputAction.moveLeft, new Keybind("a"))
    this.inputService.setKeybind(InputAction.moveRight, new Keybind("d"))
    this.inputService.setMousebind(
      InputAction.rotate,
      new Mousebind(0.01, true, "x")
    )
    this.inputService.setMousebind(
      InputAction.rotateCamera,
      new Mousebind(0.003, false, "y")
    )
    this.inputService.setWheelbind(
      InputAction.zoomCamera,
      new Wheelbind(0.0003, false)
    )
    
    this.inputService.start()
  }

  public possess(character: Character) {
    this.character = character
    this.bindCharacterInput()
  }

  private bindCharacterInput() {
    const playerMove = this.character.movement.directionInput
    const playerJump = this.character.movement.jumpInput
    const playerSprint = this.character.movement.sprintInput

    this.inputService.bindToggle(InputAction.jump, playerJump.toggle)
    this.inputService.bindToggle(InputAction.sprint, playerSprint.toggle)
    this.inputService.bindToggle(InputAction.moveForward, playerMove.togglePositiveY)
    this.inputService.bindToggle(InputAction.moveBackward, playerMove.toggleNegativeY)
    this.inputService.bindToggle(InputAction.moveLeft, playerMove.toggleNegativeX)
    this.inputService.bindToggle(InputAction.moveRight, playerMove.togglePositiveX)
    this.inputService.bindDelta(
      InputAction.rotate,
      this.character.movement.rotationInput.rotateY
    )
    this.inputService.bindDelta(
      InputAction.rotateCamera,
      this.character.thirdPartyCamera.rotationInput.rotateX
    )
    this.inputService.bindDelta(
      InputAction.zoomCamera,
      this.character.thirdPartyCamera.zoomInput.increment
    )
  }
}

Last updated