Previous 25 Next

Animation

We are going to implement frame based animation.

Multiple buffer sources connected to the global gain node that is connected to the audioContext.dest which goes to the speaker
Player idle animation

The animation above has six frames of the main character drawn with slight variation. By quickly looping through them the main character comes to life.

src/Frame.ts
import { Rectangle } from "./Rectangle";

export class Frame {
  constructor(
    readonly seconds: number,
    readonly sprite: Rectangle
  ) { }
}

We create the Frame class that holds information about how many seconds it should last and which sprite to draw.

src/Animation.ts
import { Frame } from "./Frame"

export enum AnimationMode {
  loop, once
}

export class Animation {
  constructor(
    public mode: AnimationMode,
    public frames: Frame[]
  ) { }
}

The Animation class contains a list of frames that should be drawn in sequence. The AnimationMode determines if the animations should loop or only play once and stop at the last frame. The looping mode is useful for things like running animations where we want to repeat the animation as long as the player is running. The play once mode can be used in cases when we want to stop at the last frame for example the player death animation.

src/AnimationPlayer.ts
import { Animation, AnimationMode } from "./Animation"

export class AnimationPlayer {
  private time = 0
  private currentFrame = 0

  constructor(
    public animation: Animation
  ) { }

  update(delta: number) {
    this.time += delta
    const frame = this.animation.frames[this.currentFrame]
    if (this.time >= frame.seconds) {
      this.time -= frame.seconds
      this.nextFrame()
    }
  }

  private nextFrame() {
    this.currentFrame++
    if (this.currentFrame >= this.animation.frames.length) {
      if (this.animation.mode == AnimationMode.once) {
        this.currentFrame = this.animation.frames.length - 1
      }
      else this.currentFrame = 0
    }
  }

  setAnimation(animation: Animation) {
    this.animation = animation
    this.reset()
  }

  currentSprite() {
    if (this.currentFrame >= this.animation.frames.length) {
      throw new Error("Tried to get frame outside the range of the animation")
    }
    return this.animation.frames[this.currentFrame].sprite
  }

  reset() {
    this.time = 0
    this.currentFrame = 0
  }
}

export function switchAnimation(
  animation: Animation,
  animationPlayer: AnimationPlayer
) {
  if (animationPlayer.animation != animation) {
    animationPlayer.setAnimation(animation)
  }
}

The AnimationPlayer will play the animation and keep track of the current frame depending on the mode of the animation.

We now have a functioning animation system and we only need to create the animations. Creating the animation shown above we can use the code below.

const idle = new Animation(AnimationMode.loop, [
  new Frame(0.1, SpriteSheet.guy0),
  new Frame(0.1, SpriteSheet.guy1),
  new Frame(0.1, SpriteSheet.guy2),
  new Frame(0.1, SpriteSheet.guy3),
  new Frame(0.1, SpriteSheet.guy4),
  new Frame(0.1, SpriteSheet.guy5),
])
const animationPlayer = new AnimationPlayer(idle)
Previous 25 Next