Documentation
Everything you need to know about using EasyRig to add professional-quality rigged, animated characters to your web games. Full support for twist bones, stretchy limbs, facial rigs, muscle systems, and game-ready export.
Installation
Add EasyRig to your project with a single script tag. The library is served from our CDN and requires Three.js as a peer dependency.
<!-- Load Three.js first -->
<script src="https://unpkg.com/three@0.160.0/build/three.min.js"></script>
<script src="https://unpkg.com/three@0.160.0/examples/js/loaders/GLTFLoader.js"></script>
<!-- Then load EasyRig -->
<script src="https://easy-rig.vercel.app/api/easyrig"></script>
If you're using ES modules:
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
// EasyRig attaches to window.EasyRig
await import('https://easy-rig.vercel.app/api/easyrig')
// Wait for ready event
document.addEventListener('easyrig:ready', (e) => {
console.log('EasyRig v' + e.detail.version + ' loaded')
})
Quick Start
Get a fully-featured character up and running in under a minute. This example loads a model, sets up advanced systems, and starts playing animations.
// Initialize EasyRig with full features
EasyRig.init({
debug: true,
config: {
enableTwistBones: true,
enableStretchyLimbs: true,
enableFootRoll: true,
enableFingerCurl: true,
enableFacialRig: true,
enableMuscleSystem: true
}
})
// Load your character model
const player = await EasyRig.load('player', '/models/character.glb')
// Apply animations
await player.applyAnimation('/animations/idle.glb', 'idle')
await player.applyAnimation('/animations/run.glb', 'run')
// Add to scene
scene.add(player.getModel())
// Play with smooth blending
player.play('idle')
// Set up finger poses
player.fingerCurl.setPose('left', 'open')
player.fingerCurl.setPose('right', 'fist')
// Set facial expression
player.facial.setExpression('smile', 0.8)
// Start auto-blink
player.facial.startAutoBlink()
// In your render loop
function animate() {
requestAnimationFrame(animate)
const delta = clock.getDelta()
EasyRig.update(delta) // Updates all characters
renderer.render(scene, camera)
}
animate()
Tip: Enable debug mode during development to see detailed logs about skeleton generation, bone mapping, and all advanced systems initialization.
Configuration
EasyRig offers extensive configuration options. Pass them during initialization to customize behavior.
Core Settings
defaultBoneSize: 0.05
Default size for generated bones
weightingAlgorithm: 'heatmap'
Algorithm for vertex weights
maxBonesPerVertex: 4
Maximum bone influences per vertex
autoNormalize: true
Auto-normalize vertex weights
ikIterations: 20
IK solver iteration count
ikTolerance: 0.0001
IK solver convergence tolerance
Twist Bone Settings
enableTwistBones: true
Enable automatic twist bones
twistBoneCount: 3
Number of twist bones per segment
twistDistribution: 'smooth'
Options: 'smooth', 'linear', 'exponential'
Stretchy Limb Settings
enableStretchyLimbs: true
Enable stretchy IK limbs
stretchLimit: 1.5
Maximum stretch ratio (150%)
squashLimit: 0.5
Minimum squash ratio (50%)
volumePreservation: true
Maintain volume when stretching
Foot & Hand Settings
enableFootRoll: true
Enable foot roll system
enableToePivot: true
Enable toe pivot control
enableFingerCurl: true
Enable finger curl system
fingerJoints: 3
Joints per finger
Advanced Settings
enableSpaceSwitching: true
Enable parent space switching
enableRibbonSpine: true
Enable ribbon spine deformation
enableFacialRig: true
Enable facial rig system
enableMuscleSystem: true
Enable muscle deformation
enableAdvancedShoulder: true
Auto clavicle rotation
spineSegments: 5
Ribbon spine segment count
Game Export Settings
gameExportOptimization: true
Enable export optimizer
maxExportBones: 75
Max bones for game export
bakeTwistBones: false
Bake twist into weights
removeHelperBones: true
Remove control bones on export
Requirements
- Three.js r140 or higher (tested up to r160)
- GLTFLoader from Three.js examples
- For ragdoll physics: Cannon.js, Ammo.js, or Rapier
- For GLTF export: GLTFExporter from Three.js examples
- Models in GLB or GLTF format
Loading Models
EasyRig accepts models in several ways. The most common is loading from a URL, but you can also pass existing Three.js objects.
From URL
const character = await EasyRig.load('hero', '/models/hero.glb')
// Listen to load progress
character.on('loadProgress', (data) => {
console.log('Loading:', data.percent + '%')
})
From Three.js Object
// If you already have a loaded mesh
const character = await EasyRig.load('hero', existingMesh)
From Geometry
// From raw geometry
const geometry = new THREE.BoxGeometry(1, 2, 1)
const character = await EasyRig.load('box', geometry)
Skeleton Generation
When you load a model without an existing skeleton, EasyRig analyzes the mesh geometry to generate an appropriate bone structure.
Model Type Detection
EasyRig automatically detects the type of model based on its proportions:
| Type | Detection Criteria | Skeleton Structure |
|---|---|---|
humanoid |
Height/width ratio 1.5 - 4.0 | Full human skeleton with fingers |
quadruped |
Depth > width × 1.5 | Four-legged skeleton with tail |
creature |
Ratio 0.5 - 1.5 | Simplified central skeleton |
vehicle |
Height/width < 0.5 | Wheel and axle skeleton |
custom |
Other proportions | Adaptive point-based skeleton |
Twist Bones New
Twist bones prevent the "candy wrapper" effect on forearms and upper arms by distributing rotation across multiple bones.
// Twist bones are auto-created for arms when enabled
// Access the twist system directly for custom control
// Create custom twist chain
player.twistSystem.create(
player.getBone('LeftUpperArm'),
player.getBone('LeftForeArm'),
'left_upper_arm'
)
// Twist distribution options:
// 'smooth' - cubic smoothstep (default, best for skin)
// 'linear' - even distribution
// 'exponential' - more twist near end
Stretchy Limbs New
Enable cartoon-style stretchy limbs that maintain volume while extending beyond their rest length.
// Create stretchy limb
player.stretchyLimbs.create(
[shoulder, elbow, wrist],
'left_arm',
{
stretchLimit: 1.5, // 150% max stretch
squashLimit: 0.5, // 50% min squash
volumePreservation: true
}
)
// Set target position (limb stretches to reach)
player.stretchyLimbs.setTarget('left_arm', targetPosition)
// Reset to rest length
player.stretchyLimbs.reset('left_arm')
Volume Preservation: When enabled, limbs will get thinner as they stretch and thicker as they compress, maintaining visual volume.
Foot Roll New
Professional foot roll system with heel, ball, and toe pivots for natural walking and running animations.
// Foot roll is auto-created when enabled
// Control foot roll angle (-30 to 90 degrees)
player.footRoll.setRoll('left', 45) // heel up, ball pivot
player.footRoll.setRoll('left', -20) // heel plant, toe up
// Bank (side-to-side tilt)
player.footRoll.setBank('left', 15) // tilt outward
// Toe wiggle
player.footRoll.setToeWiggle('left', 10)
// Roll thresholds (customizable)
// heelRollStart: -30 (when heel lifts)
// ballRollStart: 0 (when ball pivots)
// toeRollStart: 45 (when toe pivots)
Finger Curl New
Complete finger control system with individual curl, spread, and preset poses.
// Set curl on individual fingers (0-1)
player.fingerCurl.setCurl('left', 'index', 0.5)
player.fingerCurl.setCurl('left', 'thumb', 0.3)
// Set finger spread
player.fingerCurl.setSpread('left', 'index', 0.5)
// Make a fist (0-1)
player.fingerCurl.setFist('left', 1)
// Use preset poses
player.fingerCurl.setPose('left', 'open')
player.fingerCurl.setPose('left', 'fist')
player.fingerCurl.setPose('left', 'point')
player.fingerCurl.setPose('left', 'thumbsUp')
player.fingerCurl.setPose('left', 'peace')
player.fingerCurl.setPose('left', 'grab')
player.fingerCurl.setPose('left', 'pinch')
Space Switching New
Switch constraint spaces for IK targets with smooth blending between world, root, chest, head, or hand spaces.
// Register custom space
player.spaceSwitching.registerSpace('weapon', weaponBone)
// Create control on a bone
player.spaceSwitching.createControl('LeftHand', [
'world', 'root', 'chest', 'weapon'
])
// Switch space with smooth blend
player.spaceSwitching.switchSpace('LeftHand', 'chest', true, 0.3)
// Instant switch (no blend)
player.spaceSwitching.switchSpace('LeftHand', 'world', false)
// Default spaces available:
// 'world', 'root', 'chest', 'head', 'hand_l', 'hand_r'
Ribbon Spine New
Smooth spline-based spine deformation for fluid body movement.
// Ribbon spine is auto-created when enabled
// Bend the spine
player.ribbonSpine.setBend('spine',
0.3, // x bend (forward/back)
0.1 // z bend (side to side)
)
// Add twist along the spine
player.ribbonSpine.setTwist('spine', 0.2)
// Access control points for advanced manipulation
const ribbon = player.ribbonSpine.ribbons.get('spine')
ribbon.controlPoints[1].add(new THREE.Vector3(0, 0.1, 0))
Facial Rig New
Complete facial animation system supporting both bone-based and blendshape-based rigs.
Blendshapes
// Set individual blendshape values (0-1)
player.facial.setBlendshape('mouthSmile_L', 0.8)
player.facial.setBlendshape('mouthSmile_R', 0.8)
player.facial.setBlendshape('browUp_L', 0.5)
// Get current value
const value = player.facial.getBlendshape('mouthOpen')
Expressions
// Use preset expressions
player.facial.setExpression('smile', 1.0)
player.facial.setExpression('frown', 0.5)
player.facial.setExpression('surprise', 0.8)
player.facial.setExpression('angry', 1.0)
player.facial.setExpression('sad', 0.7)
// Register custom expression
player.facial.registerExpression('smirk', {
'mouthSmile_L': 0.8,
'mouthSmile_R': 0.2,
'browUp_L': 0.3
})
Eye Controls
// Make eyes look at target
player.facial.lookAt(targetPosition)
// Manual blink
player.facial.blink(0.15) // duration in seconds
// Wink
player.facial.setExpression('wink_l', 1)
// Start automatic blinking
player.facial.startAutoBlink(
4000, // base interval (ms)
2000 // random variance (ms)
)
Muscle System New
Simulate muscle bulging and jiggle physics for more realistic character deformation.
// Create muscle between two bones
player.muscles.create(
player.getBone('LeftUpperArm'),
player.getBone('LeftForeArm'),
'left_bicep',
{
influence: 0.3,
jiggleDecay: 0.9
}
)
// Bind muscle to mesh vertices
player.muscles.bindToMesh('left_bicep', characterMesh, [
123, 124, 125, 126 // vertex indices
])
// Add jiggle on impact
player.muscles.addJiggle('left_bicep', 0.05)
// Muscle automatically bulges when bone contracts
// and flattens when bone stretches
Advanced Shoulders New
Automatic clavicle rotation when arms are raised or moved forward.
// Advanced shoulders are auto-created when enabled
// Configuration options
EasyRig.init({
config: {
enableAdvancedShoulder: true,
clavicleAutoRotation: true,
shoulderRaise: 0.3 // how much clavicle follows arm
}
})
// Access shoulder data
const shoulder = player.advancedShoulder.shoulders.get('left')
shoulder.raiseAmount = 0.4 // increase clavicle motion
shoulder.armRaiseThreshold = 30 // degrees before clavicle activates
FK/IK Matching New
Seamlessly switch between forward kinematics and inverse kinematics with automatic pose matching.
// Create FK/IK chain
player.fkikMatching.create(
[shoulder, elbow, wrist],
'left_arm',
{
defaultWeight: 1, // start in IK mode
snapThreshold: 0.01
}
)
// Switch between modes
player.fkikMatching.setMode('left_arm', 'fk') // pose automatically matches
player.fkikMatching.setMode('left_arm', 'ik')
// Blend between FK and IK (0 = pure FK, 1 = pure IK)
player.fkikMatching.setBlend('left_arm', 0.5)
// Snap FK rotations to current IK pose
player.fkikMatching.snapFK('left_arm')
// Snap IK target to current FK pose
player.fkikMatching.snapIK('left_arm')
Game Export New
Optimize and export your rigged character for game engines with reduced bone counts and baked deformations.
// Optimize for game export
const exportData = player.gameExporter.optimize({
maxBones: 75, // max bones in final rig
bakeTwistBones: true, // bake twist into weights
removeHelperBones: true, // remove IK/control bones
simplifyWeights: true, // reduce weight influences
maxInfluences: 4 // max bones per vertex
})
console.log(exportData)
// { boneCount: 65, meshCount: 1, animationCount: 5, optimized: true }
// Export to GLTF/GLB
const glb = await player.gameExporter.exportToGLTF({
binary: true // false for .gltf
})
// Download the file
const blob = new Blob([glb], { type: 'model/gltf-binary' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'character.glb'
a.click()
Unity/Unreal Ready: Exported GLB files are compatible with Unity (via glTFast) and Unreal Engine (via glTF Runtime).
Animations
Load animations from external files and apply them to your character. EasyRig handles bone name mapping automatically.
// Load and name an animation
await player.applyAnimation('/anims/walk.glb', 'walk')
// Play with options
player.play('walk', {
loop: true,
fadeTime: 0.3,
speed: 1.2
})
// Stop animation
player.stop()
Animation Retargeting
When applying animations, EasyRig maps bones from the animation file to your character's skeleton. It handles different naming conventions automatically.
Supported naming conventions:
- Mixamo - mixamorigHips, mixamorigSpine, etc.
- Blender - Hips, Spine, LeftArm, etc.
- Maya - hips_jnt, spine_jnt, etc.
- Custom - Fuzzy matching based on bone names
Ragdoll Physics
Enable physics-based ragdoll with a single call. Requires a physics world from Cannon.js, Ammo.js, or Rapier.
import * as CANNON from 'cannon-es'
// Create physics world
const world = new CANNON.World()
world.gravity.set(0, -9.82, 0)
// Enable ragdoll on character
player.enableRagdoll(world)
// Activate on impact
function onHit(impactVelocity) {
player.activateRagdoll(impactVelocity)
}
// Deactivate and return to animation
function recover() {
player.deactivateRagdoll()
player.play('getup')
}
// Apply forces to specific bones
player.applyForce('Head', { x: 100, y: 50, z: 0 })
Inverse Kinematics
Built-in IK system for procedural animation, foot placement, and look-at targets.
// Enable IK
player.enableIK()
// Set IK target for left hand
player.setIKTarget('leftArm', [1, 1.5, 0.5])
// Foot placement on terrain
const groundY = terrain.getHeightAt(player.position)
player.setIKTarget('leftLeg', [player.position.x - 0.1, groundY, player.position.z])
player.setIKTarget('rightLeg', [player.position.x + 0.1, groundY, player.position.z])
Events
Listen to character events for gameplay integration.
// Load progress
player.on('loadProgress', (data) => {
console.log('Loading:', data.percent + '%')
})
// Animation events
player.on('animationStart', (data) => {
console.log('Started:', data.name, 'duration:', data.duration)
})
player.on('animationStop', () => {
console.log('Animation stopped')
})
// Ragdoll events
player.on('ragdollActivate', () => {
playSound('impact')
})
player.on('ragdollDeactivate', () => {
resetGameplay()
})
EasyRig API
Initialize the EasyRig library. Must be called before using other methods.
- options.debug
- Boolean. Enable detailed logging. Default: false
- options.config
- Object. Override default configuration settings. See Configuration section for all options.
Load a 3D model and create a character. Returns a Character instance.
- id
- String. Unique identifier for this character.
- source
- String | Object3D | Geometry. URL to model file, Three.js object, or geometry.
- options
- Object. Optional configuration for loading.
Retrieve a previously loaded character by ID.
Remove a character and dispose of its resources.
Update all managed characters. Alternative to calling update on each character individually.
Clear all cached skeletons, animations, geometries, and blendshapes.
Character API
Core Methods
Load and apply an animation to the character. Handles retargeting automatically.
- source
- String | AnimationClip | Array. URL to animation file or Three.js clip(s).
- name
- String. Optional name for the animation. Defaults to filename or clip name.
Play an animation with optional blending and speed control.
- name
- String. Name of the animation to play.
- options.loop
- Boolean. Whether to loop the animation. Default: true
- options.fadeTime
- Number. Crossfade duration in seconds. Default: 0.3
- options.speed
- Number. Playback speed multiplier. Default: 1.0
Stop current animation with fadeout.
Update character animations and all systems. Call every frame.
Get a bone by name (case-insensitive).
Get the Three.js model for adding to scene.
Get the character's skeleton.
Listen to character events.
Clean up and remove all resources.
System Access
Access advanced systems through character properties:
| Property | Type | Description |
|---|---|---|
character.twistSystem |
TwistBoneSystem | Twist bone controls |
character.stretchyLimbs |
StretchyLimbSystem | Stretchy limb controls |
character.footRoll |
FootRollSystem | Foot roll controls |
character.fingerCurl |
FingerCurlSystem | Finger pose controls |
character.spaceSwitching |
SpaceSwitchingSystem | Space switching controls |
character.ribbonSpine |
RibbonSpineSystem | Spine deformation controls |
character.facial |
FacialRigSystem | Facial animation controls |
character.muscles |
MuscleSystem | Muscle deformation controls |
character.advancedShoulder |
AdvancedShoulderSystem | Auto shoulder controls |
character.fkikMatching |
FKIKMatchingSystem | FK/IK blend controls |
character.gameExporter |
GameExportOptimizer | Export optimization |
Model Preparation
While EasyRig works with any model, following these guidelines gives the best results:
Recommended
- Export as GLB (binary GLTF) for smaller file size
- T-pose or A-pose for humanoid characters
- Clean geometry with manifold meshes
- Reasonable polygon count (5k-50k triangles)
- Proper scale (around 1-2 units tall)
- Include blendshapes for facial animation
Avoid
- Extreme non-uniform scaling
- Disconnected mesh parts (unless intentional)
- Inverted normals
- Multiple root objects
Using Mixamo
Mixamo is a free service that provides thousands of animations. Here's how to use them with EasyRig:
- Go to mixamo.com and select an animation
- Download as FBX for Unity (yes, even for web)
- Convert to GLB using online tools or Blender
- Apply to your character with
applyAnimation()
Note: You don't need to upload your model to Mixamo. Just download the animation with Mixamo's default character and EasyRig will retarget it.
Physics Engines
EasyRig's ragdoll system works with these physics libraries:
Cannon.js (Recommended)
import * as CANNON from 'cannon-es'
const world = new CANNON.World()
world.gravity.set(0, -9.82, 0)
world.broadphase = new CANNON.SAPBroadphase(world)
player.enableRagdoll(world)
Rapier
import RAPIER from '@dimforge/rapier3d'
await RAPIER.init()
const world = new RAPIER.World({ x: 0, y: -9.82, z: 0 })
player.enableRagdoll(world)
Performance
Tips for getting the best performance from EasyRig:
Model Optimization
- Keep polygon count reasonable (under 50k triangles per character)
- Use texture atlases to reduce draw calls
- Enable frustum culling on character models
- Use LOD (Level of Detail) for distant characters
Animation Optimization
- Limit active animations to 3-5 per character
- Use shorter fade times for quick transitions
- Pause animation updates for off-screen characters
- Clone characters instead of loading duplicates
Advanced Systems Optimization
- Disable twist bones for distant characters
- Reduce spine segments for background characters
- Disable muscle system for non-hero characters
- Use simpler facial expressions for crowds
- Disable auto-blink for off-camera characters
Physics Optimization
- Only enable ragdoll when needed (death, knockback)
- Limit active ragdolls to 5-10 at a time
- Use simpler collision shapes when possible
- Increase physics timestep for background ragdolls
Example: For a game with 50 characters, keep 10 fully animated with all systems, 30 with simple idle animations and basic systems, and 10 completely paused. Only 2-3 ragdolls active at once.
Performance Monitoring
// Access performance stats
console.log(player.performance)
// { lastUpdate: 1.23, avgUpdateTime: 0.45, updateCount: 1000 }
// Disable systems dynamically based on distance
function updateCharacterLOD(character, distanceToCamera) {
if (distanceToCamera > 50) {
// Disable expensive systems for far characters
character.twistSystem = null
character.muscles = null
character.facial = null
} else if (distanceToCamera > 20) {
// Disable some systems for medium distance
character.muscles = null
}
}
Troubleshooting
Common Issues
Model doesn't animate
- Ensure you're calling
character.update(delta)in your render loop - Check that the animation was loaded successfully with debug mode
- Verify bone names match between model and animation
Skeleton not generated
- Model may be too small or too large - try scaling
- Ensure mesh has proper vertex normals
- Check console for geometry errors
Animations look wrong
- Model might not be in T-pose - repose in Blender
- Scale mismatch - ensure model is ~2 units tall
- Bone orientation differences - EasyRig handles most cases but extreme differences may need manual adjustment
Facial rig not working
- Ensure model has morph targets/blendshapes
- Check blendshape names match expected patterns
- Verify
enableFacialRig: truein config
Performance issues
- Disable unused systems in config
- Reduce twist bone count
- Lower spine segments
- Use game export optimizer to reduce bone count
Debug Mode
// Enable comprehensive logging
EasyRig.init({ debug: true })
// Logs include:
// - Model loading progress
// - Skeleton generation details
// - Bone mapping results
// - Animation retargeting info
// - System initialization status
// - Performance warnings
Complete Examples
Basic Character Setup
// Complete setup with all features
async function setupCharacter() {
// Initialize
EasyRig.init({
debug: true,
config: {
enableTwistBones: true,
enableStretchyLimbs: true,
enableFootRoll: true,
enableFingerCurl: true,
enableFacialRig: true,
enableMuscleSystem: true,
enableAdvancedShoulder: true,
enableRibbonSpine: true,
enableSpaceSwitching: true
}
})
// Load character
const player = await EasyRig.load('player', '/models/hero.glb')
// Load 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')
// Add to scene
scene.add(player.getModel())
// Setup facial
player.facial.startAutoBlink()
player.facial.setExpression('smile', 0.3)
// Setup hands
player.fingerCurl.setPose('left', 'open')
player.fingerCurl.setPose('right', 'open')
// Start idle
player.play('idle')
// Setup events
player.on('animationStart', (data) => {
console.log('Playing:', data.name)
})
return player
}
// Game loop
const clock = new THREE.Clock()
let player
setupCharacter().then(p => player = p)
function animate() {
requestAnimationFrame(animate)
const delta = clock.getDelta()
if (player) {
player.update(delta)
// Make character look at mouse
player.facial.lookAt(mouseWorldPosition)
}
renderer.render(scene, camera)
}
animate()
Character Controller with State Machine
class CharacterController {
constructor(player) {
this.player = player
this.state = 'idle'
this.velocity = new THREE.Vector3()
this.grounded = true
}
setState(newState) {
if (this.state === newState) return
const oldState = this.state
this.state = newState
// Handle state transitions
switch (newState) {
case 'idle':
this.player.play('idle', { fadeTime: 0.3 })
this.player.facial.setExpression('smile', 0.3)
break
case 'walk':
this.player.play('walk', { fadeTime: 0.2 })
break
case 'run':
this.player.play('run', { fadeTime: 0.2 })
this.player.facial.setExpression('surprise', 0.2)
break
case 'jump':
this.player.play('jump', { loop: false, fadeTime: 0.1 })
this.player.facial.setExpression('surprise', 0.5)
break
case 'punch':
this.player.play('punch', { loop: false, fadeTime: 0.1 })
this.player.fingerCurl.setPose('right', 'fist')
this.player.facial.setExpression('angry', 0.8)
break
}
}
update(input, delta) {
const speed = input.shift ? 10 : 5
// Movement
if (input.forward || input.back || input.left || input.right) {
this.setState(input.shift ? 'run' : 'walk')
} else if (this.grounded) {
this.setState('idle')
}
// Jump
if (input.jump && this.grounded) {
this.setState('jump')
this.velocity.y = 8
this.grounded = false
}
// Attack
if (input.attack) {
this.setState('punch')
}
// Foot placement on terrain
if (this.player.footRoll) {
const leftY = terrain.getHeightAt(this.leftFootPos)
const rightY = terrain.getHeightAt(this.rightFootPos)
// Adjust foot roll based on terrain slope
const slopeDiff = leftY - rightY
this.player.footRoll.setBank('left', slopeDiff * 10)
this.player.footRoll.setBank('right', -slopeDiff * 10)
}
}
}
Facial Animation System
class FacialAnimator {
constructor(player) {
this.player = player
this.facial = player.facial
this.currentMood = 'neutral'
this.talkingWeight = 0
this.lookTarget = null
}
setMood(mood, intensity = 1) {
this.currentMood = mood
// Reset previous expressions
this.facial.setExpression('smile', 0)
this.facial.setExpression('frown', 0)
this.facial.setExpression('angry', 0)
this.facial.setExpression('sad', 0)
this.facial.setExpression('surprise', 0)
// Apply new mood
switch (mood) {
case 'happy':
this.facial.setExpression('smile', intensity)
break
case 'sad':
this.facial.setExpression('sad', intensity)
break
case 'angry':
this.facial.setExpression('angry', intensity)
break
case 'surprised':
this.facial.setExpression('surprise', intensity)
break
}
}
startTalking() {
this.talking = true
}
stopTalking() {
this.talking = false
}
update(delta) {
// Talking animation
if (this.talking) {
// Procedural mouth movement
const mouthOpen = (Math.sin(Date.now() * 0.01) + 1) * 0.3
this.facial.setBlendshape('mouthOpen', mouthOpen)
// Slight jaw movement
const jawMove = Math.sin(Date.now() * 0.008) * 0.2
this.facial.setBlendshape('jawOpen', jawMove + 0.2)
} else {
this.facial.setBlendshape('mouthOpen', 0)
this.facial.setBlendshape('jawOpen', 0)
}
// Look at target
if (this.lookTarget) {
this.facial.lookAt(this.lookTarget)
}
}
}
// Usage
const facialAnimator = new FacialAnimator(player)
facialAnimator.setMood('happy', 0.8)
facialAnimator.lookTarget = camera.position
// In dialogue
facialAnimator.startTalking()
setTimeout(() => facialAnimator.stopTalking(), 3000)
Ragdoll on Death
class RagdollController {
constructor(player, physicsWorld) {
this.player = player
this.world = physicsWorld
this.isRagdoll = false
this.recoveryTime = 0
// Enable ragdoll system
player.enableRagdoll(physicsWorld)
// Listen for ragdoll events
player.on('ragdollActivate', () => {
this.isRagdoll = true
this.playImpactSound()
})
player.on('ragdollDeactivate', () => {
this.isRagdoll = false
})
}
takeDamage(amount, impactPoint, impactForce) {
if (amount > 50) {
// Big hit - go ragdoll
this.player.activateRagdoll()
// Apply impact force
this.player.applyForce('Chest', impactForce)
// Schedule recovery
this.recoveryTime = 3
}
}
die(impactForce) {
this.player.stop()
this.player.activateRagdoll()
if (impactForce) {
this.player.applyForce('Chest', impactForce)
}
// No recovery - permanent ragdoll
this.recoveryTime = -1
}
update(delta) {
if (this.recoveryTime > 0) {
this.recoveryTime -= delta
if (this.recoveryTime <= 0) {
this.recover()
}
}
}
recover() {
this.player.deactivateRagdoll()
this.player.play('getup', { loop: false })
}
playImpactSound() {
// Play sound effect
audio.play('impact')
}
}
Export for Unity/Unreal
async function exportForGameEngine(player, engineType) {
const exporter = player.gameExporter
// Optimize based on target engine
const options = engineType === 'unity' ? {
maxBones: 75,
bakeTwistBones: true,
removeHelperBones: true,
maxInfluences: 4
} : {
maxBones: 256, // Unreal supports more
bakeTwistBones: false,
removeHelperBones: true,
maxInfluences: 8
}
// Optimize
const stats = exporter.optimize(options)
console.log('Optimized:', stats)
// Export
const glbData = await exporter.exportToGLTF({ binary: true })
// Create download
const blob = new Blob([glbData], { type: 'model/gltf-binary' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = 'character_' + engineType + '.glb'
link.click()
URL.revokeObjectURL(url)
return stats
}
// Export buttons
document.getElementById('exportUnity').onclick = () => {
exportForGameEngine(player, 'unity')
}
document.getElementById('exportUnreal').onclick = () => {
exportForGameEngine(player, 'unreal')
}
Changelog
Version 1.0.0
- New: Twist Bone System - Automatic twist distribution for arms and legs
- New: Stretchy Limbs - Cartoon-style stretch and squash with volume preservation
- New: Foot Roll System - Professional heel/ball/toe pivot controls
- New: Finger Curl System - Individual finger control with preset poses
- New: Space Switching - Smooth parent space transitions
- New: Ribbon Spine - Smooth spline-based spine deformation
- New: Facial Rig System - Blendshape and bone-based facial animation
- New: Muscle System - Dynamic muscle bulge and jiggle
- New: Advanced Shoulder - Automatic clavicle rotation
- New: FK/IK Matching - Seamless blend between animation modes
- New: Game Export Optimizer - One-click optimization for Unity/Unreal
- New: Auto-blink system for facial animation
- New: Preset finger poses (fist, point, peace, thumbsUp, etc.)
- New: Default expression presets (smile, frown, surprise, angry, sad)
- Improved: Better bone detection and mapping
- Improved: More efficient animation retargeting
- Improved: Enhanced weight painting algorithm
Support
Need help? Here are your options:
- Documentation: You're reading it!
- Examples: Check out the Examples page for working demos
- GitHub: Open an issue on our repository
- Discord: Join our community for real-time help
Pro Tip: Enable debug mode and check the console first. Most issues are clearly logged with suggestions for fixes.