playcanvas
Version:
PlayCanvas WebGL game engine
427 lines (424 loc) • 13.2 kB
JavaScript
import { AnimClip } from '../../anim/evaluator/anim-clip.js';
import { AnimEvaluator } from '../../anim/evaluator/anim-evaluator.js';
import { AnimTrack } from '../../anim/evaluator/anim-track.js';
import { DefaultAnimBinder } from '../../anim/binder/default-anim-binder.js';
import { Skeleton } from '../../../scene/animation/skeleton.js';
import { Asset } from '../../asset/asset.js';
import { Component } from '../component.js';
class AnimationComponent extends Component {
set animations(value) {
this._animations = value;
this.onSetAnimations();
}
get animations() {
return this._animations;
}
set assets(value) {
const assets = this._assets;
if (assets && assets.length) {
for(let i = 0; i < assets.length; i++){
if (assets[i]) {
const asset = this.system.app.assets.get(assets[i]);
if (asset) {
asset.off('change', this.onAssetChanged, this);
asset.off('remove', this.onAssetRemoved, this);
const animName = this.animationsIndex[asset.id];
if (this.currAnim === animName) {
this._stopCurrentAnimation();
}
delete this.animations[animName];
delete this.animationsIndex[asset.id];
}
}
}
}
this._assets = value;
const assetIds = value.map((value)=>{
return value instanceof Asset ? value.id : value;
});
this.loadAnimationAssets(assetIds);
}
get assets() {
return this._assets;
}
set currentTime(currentTime) {
if (this.skeleton) {
this.skeleton.currentTime = currentTime;
this.skeleton.addTime(0);
this.skeleton.updateGraph();
}
if (this.animEvaluator) {
const clips = this.animEvaluator.clips;
for(let i = 0; i < clips.length; ++i){
clips[i].time = currentTime;
}
}
}
get currentTime() {
if (this.skeleton) {
return this.skeleton._time;
}
if (this.animEvaluator) {
const clips = this.animEvaluator.clips;
if (clips.length > 0) {
return clips[clips.length - 1].time;
}
}
return 0;
}
get duration() {
if (this.currAnim) {
return this.animations[this.currAnim].duration;
}
return 0;
}
set loop(value) {
this._loop = value;
if (this.skeleton) {
this.skeleton.looping = value;
}
if (this.animEvaluator) {
for(let i = 0; i < this.animEvaluator.clips.length; ++i){
this.animEvaluator.clips[i].loop = value;
}
}
}
get loop() {
return this._loop;
}
play(name, blendTime = 0) {
if (!this.enabled || !this.entity.enabled) {
return;
}
if (!this.animations[name]) {
return;
}
this.prevAnim = this.currAnim;
this.currAnim = name;
if (this.model) {
if (!this.skeleton && !this.animEvaluator) {
this._createAnimationController();
}
const prevAnim = this.animations[this.prevAnim];
const currAnim = this.animations[this.currAnim];
this.blending = blendTime > 0 && !!this.prevAnim;
if (this.blending) {
this.blend = 0;
this.blendSpeed = 1 / blendTime;
}
if (this.skeleton) {
if (this.blending) {
this.fromSkel.animation = prevAnim;
this.fromSkel.addTime(this.skeleton._time);
this.toSkel.animation = currAnim;
} else {
this.skeleton.animation = currAnim;
}
}
if (this.animEvaluator) {
const animEvaluator = this.animEvaluator;
if (this.blending) {
while(animEvaluator.clips.length > 1){
animEvaluator.removeClip(0);
}
} else {
this.animEvaluator.removeClips();
}
const clip = new AnimClip(this.animations[this.currAnim], 0, 1.0, true, this.loop);
clip.name = this.currAnim;
clip.blendWeight = this.blending ? 0 : 1;
clip.reset();
this.animEvaluator.addClip(clip);
}
}
this.playing = true;
}
getAnimation(name) {
return this.animations[name];
}
setModel(model) {
if (model !== this.model) {
this._resetAnimationController();
this.model = model;
if (this.animations && this.currAnim && this.animations[this.currAnim]) {
this.play(this.currAnim);
}
}
}
onSetAnimations() {
const modelComponent = this.entity.model;
if (modelComponent) {
const m = modelComponent.model;
if (m && m !== this.model) {
this.setModel(m);
}
}
if (!this.currAnim && this.activate && this.enabled && this.entity.enabled) {
const animationNames = Object.keys(this._animations);
if (animationNames.length > 0) {
this.play(animationNames[0]);
}
}
}
_resetAnimationController() {
this.skeleton = null;
this.fromSkel = null;
this.toSkel = null;
this.animEvaluator = null;
}
_createAnimationController() {
const model = this.model;
const animations = this.animations;
let hasJson = false;
let hasGlb = false;
for(const animation in animations){
if (animations.hasOwnProperty(animation)) {
const anim = animations[animation];
if (anim.constructor === AnimTrack) {
hasGlb = true;
} else {
hasJson = true;
}
}
}
const graph = model.getGraph();
if (hasJson) {
this.fromSkel = new Skeleton(graph);
this.toSkel = new Skeleton(graph);
this.skeleton = new Skeleton(graph);
this.skeleton.looping = this.loop;
this.skeleton.setGraph(graph);
} else if (hasGlb) {
this.animEvaluator = new AnimEvaluator(new DefaultAnimBinder(this.entity));
}
}
loadAnimationAssets(ids) {
if (!ids || !ids.length) {
return;
}
const assets = this.system.app.assets;
const onAssetReady = (asset)=>{
if (asset.resources.length > 1) {
for(let i = 0; i < asset.resources.length; i++){
this.animations[asset.resources[i].name] = asset.resources[i];
this.animationsIndex[asset.id] = asset.resources[i].name;
}
} else {
this.animations[asset.name] = asset.resource;
this.animationsIndex[asset.id] = asset.name;
}
this.animations = this.animations;
};
const onAssetAdd = (asset)=>{
asset.off('change', this.onAssetChanged, this);
asset.on('change', this.onAssetChanged, this);
asset.off('remove', this.onAssetRemoved, this);
asset.on('remove', this.onAssetRemoved, this);
if (asset.resource) {
onAssetReady(asset);
} else {
asset.once('load', onAssetReady, this);
if (this.enabled && this.entity.enabled) {
assets.load(asset);
}
}
};
for(let i = 0, l = ids.length; i < l; i++){
const asset = assets.get(ids[i]);
if (asset) {
onAssetAdd(asset);
} else {
assets.on(`add:${ids[i]}`, onAssetAdd);
}
}
}
onAssetChanged(asset, attribute, newValue, oldValue) {
if (attribute === 'resource' || attribute === 'resources') {
if (attribute === 'resources' && newValue && newValue.length === 0) {
newValue = null;
}
if (newValue) {
let restarted = false;
if (newValue.length > 1) {
if (oldValue && oldValue.length > 1) {
for(let i = 0; i < oldValue.length; i++){
delete this.animations[oldValue[i].name];
}
} else {
delete this.animations[asset.name];
}
restarted = false;
for(let i = 0; i < newValue.length; i++){
this.animations[newValue[i].name] = newValue[i];
if (!restarted && this.currAnim === newValue[i].name) {
if (this.playing && this.enabled && this.entity.enabled) {
restarted = true;
this.play(newValue[i].name);
}
}
}
if (!restarted) {
this._stopCurrentAnimation();
this.onSetAnimations();
}
} else {
if (oldValue && oldValue.length > 1) {
for(let i = 0; i < oldValue.length; i++){
delete this.animations[oldValue[i].name];
}
}
this.animations[asset.name] = newValue[0] || newValue;
restarted = false;
if (this.currAnim === asset.name) {
if (this.playing && this.enabled && this.entity.enabled) {
restarted = true;
this.play(asset.name);
}
}
if (!restarted) {
this._stopCurrentAnimation();
this.onSetAnimations();
}
}
this.animationsIndex[asset.id] = asset.name;
} else {
if (oldValue.length > 1) {
for(let i = 0; i < oldValue.length; i++){
delete this.animations[oldValue[i].name];
if (this.currAnim === oldValue[i].name) {
this._stopCurrentAnimation();
}
}
} else {
delete this.animations[asset.name];
if (this.currAnim === asset.name) {
this._stopCurrentAnimation();
}
}
delete this.animationsIndex[asset.id];
}
}
}
onAssetRemoved(asset) {
asset.off('remove', this.onAssetRemoved, this);
if (this.animations) {
if (asset.resources.length > 1) {
for(let i = 0; i < asset.resources.length; i++){
delete this.animations[asset.resources[i].name];
if (this.currAnim === asset.resources[i].name) {
this._stopCurrentAnimation();
}
}
} else {
delete this.animations[asset.name];
if (this.currAnim === asset.name) {
this._stopCurrentAnimation();
}
}
delete this.animationsIndex[asset.id];
}
}
_stopCurrentAnimation() {
this.currAnim = null;
this.playing = false;
if (this.skeleton) {
this.skeleton.currentTime = 0;
this.skeleton.animation = null;
}
if (this.animEvaluator) {
for(let i = 0; i < this.animEvaluator.clips.length; ++i){
this.animEvaluator.clips[i].stop();
}
this.animEvaluator.update(0);
this.animEvaluator.removeClips();
}
}
onEnable() {
super.onEnable();
const assets = this.assets;
const registry = this.system.app.assets;
if (assets) {
for(let i = 0, len = assets.length; i < len; i++){
let asset = assets[i];
if (!(asset instanceof Asset)) {
asset = registry.get(asset);
}
if (asset && !asset.resource) {
registry.load(asset);
}
}
}
if (this.activate && !this.currAnim) {
const animationNames = Object.keys(this.animations);
if (animationNames.length > 0) {
this.play(animationNames[0]);
}
}
}
onBeforeRemove() {
for(let i = 0; i < this.assets.length; i++){
let asset = this.assets[i];
if (typeof asset === 'number') {
asset = this.system.app.assets.get(asset);
}
if (!asset) continue;
asset.off('change', this.onAssetChanged, this);
asset.off('remove', this.onAssetRemoved, this);
}
this.skeleton = null;
this.fromSkel = null;
this.toSkel = null;
this.animEvaluator = null;
}
update(dt) {
if (this.blending) {
this.blend += dt * this.blendSpeed;
if (this.blend >= 1) {
this.blend = 1;
}
}
if (this.playing) {
const skeleton = this.skeleton;
if (skeleton !== null && this.model !== null) {
if (this.blending) {
skeleton.blend(this.fromSkel, this.toSkel, this.blend);
} else {
const delta = dt * this.speed;
skeleton.addTime(delta);
if (this.speed > 0 && skeleton._time === skeleton.animation.duration && !this.loop) {
this.playing = false;
} else if (this.speed < 0 && skeleton._time === 0 && !this.loop) {
this.playing = false;
}
}
if (this.blending && this.blend === 1) {
skeleton.animation = this.toSkel.animation;
}
skeleton.updateGraph();
}
}
const animEvaluator = this.animEvaluator;
if (animEvaluator) {
for(let i = 0; i < animEvaluator.clips.length; ++i){
const clip = animEvaluator.clips[i];
clip.speed = this.speed;
if (!this.playing) {
clip.pause();
} else {
clip.resume();
}
}
if (this.blending && animEvaluator.clips.length > 1) {
animEvaluator.clips[1].blendWeight = this.blend;
}
animEvaluator.update(dt);
}
if (this.blending && this.blend === 1) {
this.blending = false;
}
}
constructor(...args){
super(...args), this._animations = {}, this._assets = [], this._loop = true, this.animEvaluator = null, this.model = null, this.skeleton = null, this.fromSkel = null, this.toSkel = null, this.animationsIndex = {}, this.prevAnim = null, this.currAnim = null, this.blend = 0, this.blending = false, this.blendSpeed = 0, this.activate = true, this.speed = 1;
}
}
export { AnimationComponent };