I’m implementing a head follow/look at target for a few of my animated characters. I need to figure out of if there is a a callback for after an animation is applied to the skeleton or should I do that on something like a layer’s prerender?
Thanks again!
You can do it in the postUpdate part of any script - at that point the animation has affected the bones already, and you can modify them.
We have some test internal projects, and it’s not a very clean code to release as an example, but feel free to use it as a motivation, especially the end part that handles the look at part.
var Locomotion = pc.createScript('locomotion');
window.characterDirection = new pc.Vec3(1, 0, 0);
window.targetPosition = new pc.Vec3(2,0,1.5);
window.jumpCurve = new pc.Curve([
0, 0,
]);
window.jumpCurve.type = pc.CURVE_SPLINE;
window.jumpTime = 0;
window.runSpeed = 4.0;
// initialize code called once per entity
Locomotion.prototype.initialize = function() {
this.app.mouse.disableContextMenu();
this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
};
Locomotion.prototype.onMouseDown = function (event) {
if (event.button !== 0) return;
// Set the character target position to a position on the plane that the user has clicked
var cameraEntity = this.app.root.findByName('Camera');
var near = cameraEntity.camera.screenToWorld(event.x, event.y, 0.1);
var far = cameraEntity.camera.screenToWorld(event.x, event.y, 1000.0);
var result = this.app.systems.rigidbody.raycastFirst(far, near);
if (result) {
window.targetPosition = new pc.Vec3(result.point.x, 0, result.point.z);
}
};
// update code called every frame
Locomotion.prototype.postUpdate = function(dt) {
if(this.app.keyboard.wasPressed(pc.KEY_SPACE)) {
var isJumping = this.entity.anim.baseLayer.activeState === 'Jump';
if (!isJumping) {
window.jumpTime = 0;
this.entity.anim.setTrigger('jump');
}
this.entity.anim.setTrigger('jump');
}
// Move the character along X & Z axis based on click target position & make character face click direction
if (!this.entity.position.equals(window.targetPosition)) {
var prevMoveSpeed = 0.0;
var activeMoveSpeed = 0.0;
if (this.entity.anim.baseLayer.previousState === 'Walk') {
prevMoveSpeed = window.runSpeed;
} else if (this.entity.anim.baseLayer.previousState === 'Idle') {
prevMoveSpeed = 0.0;
} else if (this.entity.anim.baseLayer.previousState === 'Jump') {
prevMoveSpeed = 0.0;
}
if (this.entity.anim.baseLayer.activeState === 'Walk') {
activeMoveSpeed = window.runSpeed;
} else if (this.entity.anim.baseLayer.activeState === 'Idle') {
activeMoveSpeed = 0.0;
} else if (this.entity.anim.baseLayer.activeState === 'Jump') {
activeMoveSpeed = 0.0;
}
var totalMoveSpeed = activeMoveSpeed;
if (this.entity.anim.baseLayer.transitioning) {
var progress = this.entity.anim.baseLayer.transitionProgress;
totalMoveSpeed = (prevMoveSpeed * (1.0 - progress)) + (activeMoveSpeed * progress);
}
this.entity.anim.setInteger('speed', 1);
var distance = window.targetPosition.clone().sub(this.entity.position);
var direction = distance.clone().normalize();
window.characterDirection = new pc.Vec3().sub(direction);
var movement = direction.clone().scale(dt * totalMoveSpeed);
if (movement.length() < distance.length()) {
this.entity.setPosition(this.entity.position.add(movement));
} else {
this.entity.position = window.targetPosition;
}
} else {
this.entity.anim.setInteger('speed', 0);
}
this.entity.lookAt(this.entity.position.clone().add(window.characterDirection));
// Make character look at the block if facing it
var block = pc.app.root.findByName('Block');
var head = pc.app.root.findByName('C_head0001_bind_JNT');
var headToBlockPlaneDistance = new pc.Vec3(this.entity.position.x, 0, this.entity.position.z).sub(new pc.Vec3(block.localPosition.x, 0, block.localPosition.z));
var headToBlockPlaneDirection = headToBlockPlaneDistance.clone().normalize();
var prevLocalRotation = head.localRotation.clone();
var isFacingBlock = window.characterDirection.clone().dot(headToBlockPlaneDirection) > 0.5;
var isNotNearBlock = headToBlockPlaneDistance.length() > 0.6;
var isBlinking = this.entity.anim.findAnimationLayer('face').activeState === 'idle-blink';
if (isFacingBlock && isNotNearBlock) {
head.lookAt(head.position.clone().add(head.position.clone().sub(block.localPosition)));
window.lookAtBlockRotation = head.localRotation.clone();
if (!isBlinking) this.entity.anim.findAnimationLayer('face').play('cheerful');
} else {
window.lookAtBlockRotation = head.localRotation.clone();
if (!isBlinking) this.entity.anim.findAnimationLayer('face').play('idle');
}
if (!window.interpDirection) {
window.interpDirection = prevLocalRotation;
}
var a = window.interpDirection;
var b = window.lookAtBlockRotation;
var rotationDistance = Math.acos(2.0 * Math.pow(a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w, 2) - 1);
if (!Number.isNaN(rotationDistance) && rotationDistance > 0.001) {
var interp = (1.0 / rotationDistance) * dt * 4;
window.interpDirection = new pc.Quat().slerp(a, b, interp > 1.0 ? 1.0 : interp);
}
head.setLocalRotation(window.interpDirection);
};
// swap method called for script hot-reloading
// inherit your script state here
// Locomotion.prototype.swap = function(old) { };
// to learn more about script anatomy, please read:
// http://developer.playcanvas.com/en/user-manual/scripting/
Thanks you so much! This is awesome 