UNPKG

hzengine-core

Version:

An Visual Novel Engine for Zepp OS

441 lines (440 loc) 17.6 kB
/** * animation.js * @description A library for providing multi-track animations for HZEngine. 一个用于在HZEngine中提供简单动画的库 * @date 2024/10/2 * @author CuberQAQ */ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var _ActiveTrackNode_currentSection, _ActiveTrackNode_currentIndex, _ActiveTrackNode_currentTrack; export class Animation { constructor(profile, options) { this.initProps = {}; this.activeRootTrack = null; this.lastTime = 0; this.onFrame = null; this.onEnd = null; this.customWrappers = {}; this.onStop = null; this.normalizedProfile = Profile.normalize(profile); console.log(`Animation: ${JSON.stringify(profile)}`); if (options) { options.initProps && (this.initProps = options.initProps); options.wrappers && (this.customWrappers = options.wrappers); } } serialize() { var _a; return { normalizedProfile: this.normalizedProfile, initProps: this.initProps, activeRootTrack: (_a = this.activeRootTrack) === null || _a === void 0 ? void 0 : _a.serialize(), lastTime: this.lastTime, }; } static unserialize(serialized, wrappers) { let profile = serialized.normalizedProfile; let animation = new Animation(profile, { initProps: serialized.initProps, wrappers, }); animation.lastTime = serialized.lastTime; serialized.activeRootTrack && (animation.activeRootTrack = ActiveTrackNode.unserialize(animation, serialized.activeRootTrack)); return animation; } timerCb() { } init() { this.activeRootTrack = new ActiveTrackNode(this, { accessKeys: [], index: 0, initProps: this.initProps, }); } goto(time) { if (!this.activeRootTrack) { this.init(); } if (time < 0) time = 0; if (time < this.lastTime) { this.init(); // reset the tracknode try { this.activeRootTrack.step(time); } catch (e) { console.log(e); this.activeRootTrack.status = ActiveTrackNode.Status.Pause; } if (this.activeRootTrack.status === ActiveTrackNode.Status.Pause) { if (this.onEnd) this.onEnd(); } } else { if (this.activeRootTrack.status !== ActiveTrackNode.Status.Pause) { try { this.activeRootTrack.step(time - this.lastTime); } catch (e) { console.log(e); this.activeRootTrack.status = ActiveTrackNode.Status.Pause; } } } this.lastTime = time; if (this.onFrame) { // console.log("on frame", JSON.stringify(this.calcProps())); this.onFrame(this.calcProps()); } if (this.activeRootTrack.status === ActiveTrackNode.Status.Pause) { if (this.onEnd) this.onEnd(); } } step(delta_time) { this.goto(this.lastTime + delta_time); } calcProps() { return this.activeRootTrack.calcProps(); } reverse() { } stop() { } _accessProfile(accessKeys) { let section = this.normalizedProfile; for (let i = 0; i < accessKeys.length; i++) { section = section[accessKeys[i]]; } return section; } _getWrapper(wrapper) { var _a, _b; if (typeof wrapper === "string") { return ((_b = (_a = this.customWrappers[wrapper]) !== null && _a !== void 0 ? _a : buildInWrappers[wrapper]) !== null && _b !== void 0 ? _b : buildInWrappers.none); } else if (wrapper === undefined) { return buildInWrappers.none; } else return wrapper; } _calcWrappedProps(former_props, props, wrapper, progress) { let _former_props = Object.assign({}, former_props); let wrapper_func = this._getWrapper(wrapper); let wraped_prog = wrapper_func(progress); let res = Object.assign({}, _former_props); for (let key in props) { if (typeof props[key] === "number") { if (_former_props[key] == undefined) _former_props[key] = 0; res[key] = _former_props[key] + wraped_prog * (props[key] - _former_props[key]); } else if (typeof props[key] === "boolean") { if (_former_props[key] == undefined) _former_props[key] = false; res[key] = wraped_prog === 1 ? props[key] : _former_props[key]; } else if (typeof props[key] === "string") { if (_former_props[key] == undefined) _former_props[key] = ""; res[key] = wraped_prog === 1 ? props[key] : _former_props[key]; } else { res[key] = props[key]; } } return res; } } export var Profile; (function (Profile) { function normalize(profile) { if (!Array.isArray(profile)) { // section return [profile]; } else if (profile.length === 0) { // empty track/tracklist return []; } else if (!Array.isArray(profile[0])) { // track return profile; } else { // tracklist return [ { syncs: profile, }, ]; } } Profile.normalize = normalize; })(Profile || (Profile = {})); export class ActiveTrackNode { get currentSection() { if (__classPrivateFieldGet(this, _ActiveTrackNode_currentSection, "f") === null) { if (this.currentIndex === null) { __classPrivateFieldSet(this, _ActiveTrackNode_currentSection, this.animation._accessProfile(this.accessKeys), "f"); } else { __classPrivateFieldSet(this, _ActiveTrackNode_currentSection, this.currentTrack[this.currentIndex], "f"); } __classPrivateFieldSet(this, _ActiveTrackNode_currentIndex, this.currentIndex, "f"); } else if (this.currentIndex !== __classPrivateFieldGet(this, _ActiveTrackNode_currentIndex, "f")) { __classPrivateFieldSet(this, _ActiveTrackNode_currentSection, this.currentTrack[this.currentIndex], "f"); __classPrivateFieldSet(this, _ActiveTrackNode_currentIndex, this.currentIndex, "f"); } return __classPrivateFieldGet(this, _ActiveTrackNode_currentSection, "f"); } get currentTrack() { if (__classPrivateFieldGet(this, _ActiveTrackNode_currentTrack, "f") === null && this.currentIndex !== null) { __classPrivateFieldSet(this, _ActiveTrackNode_currentTrack, this.animation._accessProfile(this.accessKeys), "f"); } return __classPrivateFieldGet(this, _ActiveTrackNode_currentTrack, "f"); } constructor(animation, { accessKeys, index, initProps, }) { this.animation = animation; this.repeated = 0; this.status = ActiveTrackNode.Status.Playing; this.now_time = 0; _ActiveTrackNode_currentSection.set(this, null); _ActiveTrackNode_currentIndex.set(this, null); _ActiveTrackNode_currentTrack.set(this, null); this.lastProps = {}; this.nowProps = {}; this.activeSyncs = []; this.accessKeys = accessKeys; this.currentIndex = index; this.lastProps = Object.assign({}, initProps); this.nowProps = Object.assign({}, initProps); } serialize() { return { accessKeys: this.accessKeys, currentIndex: this.currentIndex, repeated: this.repeated, status: this.status, now_time: this.now_time, lastProps: this.lastProps, nowProps: this.nowProps, activeSyncs: this.activeSyncs.map((sync) => sync.serialize()), }; } static unserialize(animation, serialized) { let node = new ActiveTrackNode(animation, { accessKeys: serialized.accessKeys, index: serialized.currentIndex, initProps: serialized.lastProps, }); node.lastProps = serialized.lastProps; node.nowProps = serialized.nowProps; node.status = serialized.status; node.repeated = serialized.repeated; node.now_time = serialized.now_time; node.activeSyncs = serialized.activeSyncs.map((sync) => ActiveTrackNode.unserialize(animation, sync)); return node; } /** * * @param delta_time * @returns 剩餘的時間 */ step(delta_time) { // console.log(`node step: ${delta_time}`); var _a; // if (this.status === ActiveTrackNode.Status.Pause) return delta_time; // because we should proceed async list // normal if (!this.currentSection) { this.status = ActiveTrackNode.Status.Pause; return delta_time; } while (true) { // constant field: time, wrapper, frame // instant filed: syncs, asyncs if (this.status === ActiveTrackNode.Status.Playing) { if (this.currentSection.time === undefined || this.currentSection.time <= 0) { this.nowProps = Object.assign(Object.assign({}, this.nowProps), this.currentSection.frame); } else { if (this.now_time + delta_time >= this.currentSection.time) { // this.now_time = this.currentSection.time delta_time -= this.currentSection.time - this.now_time; this.nowProps = Object.assign(Object.assign({}, this.nowProps), this.currentSection.frame); } else { this.now_time += delta_time; delta_time = 0; // calc let progress = this.now_time / this.currentSection.time; // console.log(`progress: ${progress}`); this.nowProps = this.animation._calcWrappedProps(this.lastProps, (_a = this.currentSection.frame) !== null && _a !== void 0 ? _a : {}, this.currentSection.wrapper, progress); break; } } // sync async if (this.currentSection.syncs && this.currentSection.syncs.length > 0) { this.initSyncs(); this.status = ActiveTrackNode.Status.WaitingSyncs; } } // step and check sync if (this.status === ActiveTrackNode.Status.WaitingSyncs) { delta_time = this.stepSyncs(delta_time); // returns lefted time this.nowProps = Object.assign(this.nowProps, this.getSyncsProps()); if (this.checkSyncsFinished()) { this.status = ActiveTrackNode.Status.Playing; this.activeSyncs = []; } else { // debug if (delta_time !== 0) throw "syncs not finished while lefted time != 0. may be bugs"; break; } } // check repeat , finish if (this.currentSection.repeat !== undefined) { this.repeated++; if (this.repeated >= this.currentSection.repeat) { // finish and proceed next section // TODO if (this._switchNextSection()) continue; else break; } else { // continue this section this.now_time = 0; this.nowProps = Object.assign({}, this.lastProps); continue; } } else { if (this._switchNextSection()) continue; else break; } } // waiting syncs return delta_time; } calcProps() { return this.nowProps; } _switchNextSection() { if (this.currentIndex === null || this.currentTrack.length === this.currentIndex + 1) { // already reach the end this.status = ActiveTrackNode.Status.Pause; return false; } else { this.lastProps = Object.assign({}, this.nowProps); this.repeated = 0; this.now_time = 0; this.currentIndex++; return true; } } initSyncs() { // console.log("init syncs"); let activeSyncs = []; // if (this.currentSection.syncs == null) return; let subprofile = this.currentSection.syncs; let activeTrackNode; if (Array.isArray(subprofile)) { if (subprofile.length > 0 && Array.isArray(subprofile[0])) { // tracklist for (let j = 0; j < subprofile.length; ++j) { activeTrackNode = new ActiveTrackNode(this.animation, { accessKeys: [ ...this.accessKeys, "" + this.currentIndex, "syncs", "" + j, ], index: 0, initProps: this.nowProps, // TODO 因为 lastProps在切换的时候才会更新,所以用nowProps }); activeSyncs.push(activeTrackNode); } } else { // Track activeTrackNode = new ActiveTrackNode(this.animation, { accessKeys: [...this.accessKeys, "" + this.currentIndex, "syncs"], index: 0, initProps: this.nowProps, // TODO 因为 lastProps在切换的时候才会更新,所以用nowProps }); activeSyncs.push(activeTrackNode); console .log(); } } else { // section activeTrackNode = new ActiveTrackNode(this.animation, { accessKeys: [...this.accessKeys, "" + this.currentIndex, "syncs"], index: null, initProps: this.nowProps, // TODO 因为 lastProps在切换的时候才会更新,所以用nowProps }); activeSyncs.push(activeTrackNode); } // activeTrackNode?.step(0); // TODO this.activeSyncs = activeSyncs; } stepSyncs(delta_time) { let lefted_time = delta_time; for (let i = 0; i < this.activeSyncs.length; ++i) { lefted_time = Math.min(this.activeSyncs[i].step(delta_time), lefted_time); } return lefted_time; } checkSyncsFinished() { for (let i = 0; i < this.activeSyncs.length; ++i) { if (this.activeSyncs[i].status !== ActiveTrackNode.Status.Pause) return false; } return true; } getSyncsProps() { let res = {}; for (let i = 0; i < this.activeSyncs.length; ++i) { Object.assign(res, this.activeSyncs[i].calcProps()); } return res; } } _ActiveTrackNode_currentSection = new WeakMap(), _ActiveTrackNode_currentIndex = new WeakMap(), _ActiveTrackNode_currentTrack = new WeakMap(); (function (ActiveTrackNode) { let Status; (function (Status) { Status[Status["Playing"] = 0] = "Playing"; Status[Status["WaitingSyncs"] = 1] = "WaitingSyncs"; Status[Status["Pause"] = 2] = "Pause"; })(Status = ActiveTrackNode.Status || (ActiveTrackNode.Status = {})); })(ActiveTrackNode || (ActiveTrackNode = {})); const buildInWrappers = { none: (x) => (x < 1 ? 0 : 1), linear: (x) => x, easeout: (x) => Math.pow(x, 3), easein: (x) => 1 - Math.pow(1 - x, 3), easeinout: (x) => x < 0.5 ? 0.5 * Math.pow(x, 3) : 1 - 0.5 * Math.pow(1 - x, 3), };