Code examples

Real-world examples showing how to use EasyRig in different scenarios. Copy and adapt for your game.

Basic character setup

Load a character model, apply animations, and integrate with a Three.js scene. The simplest possible setup to get started.

beginner three.js
import * as THREE from 'three'

// basic scene setup
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

// initialize EasyRig
EasyRig.init({ debug: true })

// load character
const player = await EasyRig.load('player', '/models/character.glb')

// load animations
await player.applyAnimation('/animations/idle.glb', 'idle')
await player.applyAnimation('/animations/walk.glb', 'walk')

// add to scene
scene.add(player.getModel())

// play idle
player.play('idle')

// render loop
const clock = new THREE.Clock()

function animate() {
  requestAnimationFrame(animate)
  
  const delta = clock.getDelta()
  player.update(delta)
  
  renderer.render(scene, camera)
}

animate()

Animation blending

Smooth transitions between idle, walk, and run based on player input. Shows how to handle multiple animations and crossfading.

intermediate animation
// load all animations
await player.applyAnimation('/anims/idle.glb', 'idle')
await player.applyAnimation('/anims/walk.glb', 'walk')
await player.applyAnimation('/anims/run.glb', 'run')
await player.applyAnimation('/anims/jump.glb', 'jump')

let currentState = 'idle'
let velocity = 0

function updateAnimation() {
  let targetState = 'idle'
  
  if (keys.space && isGrounded) {
    targetState = 'jump'
  } else if (velocity > 5) {
    targetState = 'run'
  } else if (velocity > 0.1) {
    targetState = 'walk'
  }
  
  if (targetState !== currentState) {
    player.crossFade(targetState, 0.3)
    currentState = targetState
  }
}

function update(delta) {
  // calculate velocity from input
  velocity = Math.sqrt(moveX * moveX + moveZ * moveZ)
  
  // update animation based on state
  updateAnimation()
  
  player.update(delta)
}

Ragdoll physics

Enable physics ragdoll on character death or impact. Shows integration with Cannon.js physics engine.

intermediate physics cannon.js
import * as CANNON from 'cannon-es'

// setup physics world
const world = new CANNON.World()
world.gravity.set(0, -9.82, 0)
world.broadphase = new CANNON.SAPBroadphase(world)
world.solver.iterations = 10

// add ground plane
const groundShape = new CANNON.Plane()
const groundBody = new CANNON.Body({ mass: 0 })
groundBody.addShape(groundShape)
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0)
world.addBody(groundBody)

// enable ragdoll on character
player.enableRagdoll(world)

// activate on hit
function onCharacterHit(impactPoint, impactForce) {
  // switch from animation to physics
  player.activateRagdoll({ 
    x: impactForce.x * 5, 
    y: impactForce.y * 5, 
    z: impactForce.z * 5 
  })
  
  // apply extra force to specific bone
  player.applyForce('Head', { 
    x: impactForce.x * 10, 
    y: impactForce.y * 10, 
    z: impactForce.z * 10 
  })
  
  // recover after delay
  setTimeout(() => {
    player.deactivateRagdoll()
    player.play('getup')
  }, 3000)
}

// update loop
function update(delta) {
  world.step(1 / 60, delta, 3)
  player.update(delta)
}

Inverse kinematics

Use IK for foot placement on uneven terrain and hand positions for grabbing objects. Shows practical IK usage.

advanced ik
// enable IK system
player.enableIK()

// foot placement on terrain
function updateFootPlacement() {
  const model = player.getModel()
  const pos = model.position
  
  // get terrain height under each foot
  const leftFootY = terrain.getHeightAt(pos.x - 0.1, pos.z)
  const rightFootY = terrain.getHeightAt(pos.x + 0.1, pos.z)
  
  // set IK targets
  player.setIKTarget('leftLeg', [
    pos.x - 0.1,
    leftFootY,
    pos.z
  ])
  
  player.setIKTarget('rightLeg', [
    pos.x + 0.1,
    rightFootY,
    pos.z
  ])
}

// hand IK for grabbing object
function grabObject(object) {
  const targetPos = object.position
  
  // move hand to object
  player.setIKTarget('rightArm', [
    targetPos.x,
    targetPos.y,
    targetPos.z
  ])
  
  // attach object to hand bone
  setTimeout(() => {
    const handBone = player.getBone('RightHand')
    handBone.add(object)
  }, 500)
}

// update in render loop
function update(delta) {
  updateFootPlacement()
  player.update(delta)
}

Multiple characters

Efficiently manage multiple characters in a scene using the clone feature. Shows performance optimization techniques.

intermediate optimization
// load base character with animations
const base = await EasyRig.load('base', '/models/npc.glb')
await base.applyAnimation('/anims/idle.glb', 'idle')
await base.applyAnimation('/anims/walk.glb', 'walk')

// clone for each NPC (shares animations)
const npcs = []

for (let i = 0; i < 50; i++) {
  const npc = base.clone(`npc_${i}`)
  
  // position
  const model = npc.getModel()
  model.position.set(
    Math.random() * 100 - 50,
    0,
    Math.random() * 100 - 50
  )
  
  scene.add(model)
  
  // random animation
  npc.play(Math.random() > 0.5 ? 'idle' : 'walk')
  
  npcs.push(npc)
}

// optimized update - only update visible characters
function update(delta) {
  npcs.forEach(npc => {
    const model = npc.getModel()
    
    // distance culling
    const dist = camera.position.distanceTo(model.position)
    
    if (dist < 50) {
      npc.update(delta)
    } else {
      // pause far away characters
      npc.pause()
    }
  })
}