Character Animation

Animating a character requires seamlesslly blending between different animation based on what the character is doing. The CharacterAnimationComponent makes this easy allowing you to declaratively define how to transition between animation clips using state machines.

Playing a single animation

First, we will use the character animation component to simply play a single animation similarly to the example in the Animation system article.

import { Actor, AssetLoader, BaseActor, attach, inject } from '@hology/core/gameplay';
import { CharacterAnimationComponent } from '@hology/core/gameplay/actors';
import * as THREE from 'three';

@Actor()
class Character2 extends BaseActor {
  private assetLoader = inject(AssetLoader)
  private characterAnimation = attach(CharacterAnimationComponent)

  async onInit(): Promise<void> {
    const model = await this.assetLoader.getModelByAssetName('character-human')
    const mesh = model.scene
    this.object.add(mesh)    

    const clip = THREE.AnimationClip.findByName(model.animations, 'idle')
    
    this.characterAnimation.setup(mesh)
    this.characterAnimation.play(clip)
  }

}

export default Character2

Using animation state machines

To visualize what the character is doing we likely want to play different clips. This is easily done with an animation state machine which can transition between different animation clips based on state and defined transition rules.

The example below is a bit more involved to better illustrate how animation state machines can be used.

  1. First, we load the model and extract the animation clips we intend to use.

  2. Then we set up the character animation component with the mesh we intend to animate.

  3. To play different animation based on state changes, we set up some way for the player to provide input. We use an ActionInput to represent the state of the player wanting to walk. After that we configure the input service. You can read more about how this works in Player input. Note that this code may ideally belong somewhere outside the character actor so that the character actor also can be used without being affected by the player's input.

  4. Create animation states for each clip. These allow us to build up the state machine for how to transition between them.

  5. We use the transitionsBetween method with a predicate to transition to the walk state when walk input is activated. If the provided function later returns false, such as when the player no longer presses walk, the animation will return back to the idle animation.

  6. Create an animation state machine and pass in the initial state.

  7. Finally, instruct the character animation component to play the state machine.

import { Actor, AnimationState, AnimationStateMachine, AssetLoader, BaseActor, attach, inject } from '@hology/core/gameplay';
import { CharacterAnimationComponent } from '@hology/core/gameplay/actors';
import { ActionInput, InputService, Keybind } from '@hology/core/gameplay/input';
import * as THREE from 'three';

enum InputAction {
  walk
}

@Actor()
class Character3 extends BaseActor {
  private assetLoader = inject(AssetLoader)
  private characterAnimation = attach(CharacterAnimationComponent)
  private input = inject(InputService)

  async onInit(): Promise<void> {
    // 1
    const model = await this.assetLoader.getModelByAssetName('character-human')
    const mesh = model.scene
    this.object.add(mesh)    

    const idleClip = THREE.AnimationClip.findByName(model.animations, 'idle')
    const walkClip = THREE.AnimationClip.findByName(model.animations, 'walk')
    
    // 2
    this.characterAnimation.setup(mesh)

    // 3
    const pressingWalk = new ActionInput() 

    this.input.setKeybind(InputAction.walk, new Keybind('w'))
    this.input.bindToggle(InputAction.walk, pressingWalk.toggle)
    this.input.start()

    // 4
    const idle = new AnimationState(idleClip)
    const walk = new AnimationState(walkClip)

    // 5
    idle.transitionsBetween(walk, () => pressingWalk.activated)
    
    // 6 
    const asm = new AnimationStateMachine(idle)

    // 7
    this.characterAnimation.playStateMachine(asm)
  }

}

export default Character3

In the next article, we will explain animation state machines more in depth and how they can be used to represent various transitions.

Last updated