371 lines
12 KiB
JavaScript
371 lines
12 KiB
JavaScript
//=============================================================================
|
|
// RPG Maker MZ - SmartAnimation
|
|
//
|
|
// Copyright 2024 Panzer-IV. All rights reserved.
|
|
// This source code or any portion thereof must not be
|
|
// reproduced or used without licensed in any manner whatsoever.
|
|
//=============================================================================
|
|
/*:
|
|
* @target MZ
|
|
* @plugindesc スマートアニメーション
|
|
* @author 四号戦車
|
|
* @base PluginCommonBase
|
|
* @base DebugSwitch
|
|
* @base TextResource
|
|
* @base PluginUtils
|
|
* @orderAfter PluginCommonBase
|
|
* @orderAfter DebugSwitch
|
|
* @orderAfter TextResource
|
|
* @orderAfter PluginUtils
|
|
*
|
|
* @help SmartAnimation.js
|
|
*
|
|
* Version: 0.0.33
|
|
*
|
|
* スマートアニメーションを実装するユーティリティプラグインです。
|
|
* フレームに合わせて複数の値を変更できます。
|
|
* 他のプラグインで使用する前提です。
|
|
*
|
|
*/
|
|
(() => {
|
|
"use strict";
|
|
|
|
//========================================================================
|
|
// プラグイン定義
|
|
//========================================================================
|
|
const pluginName = "SmartAnimation";
|
|
const script = document.currentScript;
|
|
const param = PluginManagerEx.createParameter(script);
|
|
|
|
//========================================================================
|
|
// 定数定義 (EasingType)
|
|
//========================================================================
|
|
const EasingType = {
|
|
LINEAR: 'linear',
|
|
EASE_IN: 'ease_in',
|
|
EASE_OUT: 'ease_out',
|
|
EASE_INOUT: 'ease_inout',
|
|
};
|
|
window.EasingType = Object.freeze(EasingType);
|
|
|
|
//========================================================================
|
|
// クラス定義 (SmartAnimation)
|
|
//========================================================================
|
|
class SmartAnimation {
|
|
constructor(name, object, property, repeat, start, end, callback, easing) {
|
|
this.name = name;
|
|
this.object = object;
|
|
this.startValue = start;
|
|
this.endValue = end;
|
|
this.targetProperty = property;
|
|
this.state = 'ready';
|
|
this.repeat = repeat;
|
|
|
|
if (!!easing) {
|
|
this.easing = easing;
|
|
} else {
|
|
this.easing = EasingType.LINEAR;
|
|
}
|
|
|
|
if (!!callback && typeof callback === 'function') {
|
|
this.listener = callback;
|
|
} else {
|
|
this.listener = null;
|
|
}
|
|
|
|
this.directionReverse = (end < start);
|
|
this.currentFrame = 0;
|
|
this.currentValue = start;
|
|
this.throttle = 0;
|
|
this.duration = 0;
|
|
this.inSkip = false;
|
|
}
|
|
|
|
setDuration(duration) {
|
|
this.duration = duration;
|
|
let amount = this.endValue - this.startValue;
|
|
if (this.directionReverse) {
|
|
amount = this.startValue - this.endValue;
|
|
}
|
|
this.throttle = Math.abs(amount / duration);
|
|
|
|
// Logger.d(`setDuration: throttle = ${this.throttle}, duration = ${duration}, amount = ${amount}`);
|
|
this.reset();
|
|
return this;
|
|
}
|
|
|
|
setThrottle(throttle) {
|
|
this.throttle = throttle;
|
|
let amount = this.endValue - this.startValue;
|
|
if (this.directionReverse) {
|
|
amount = this.startValue - this.endValue;
|
|
}
|
|
this.duration = amount / Math.abs(throttle);
|
|
// Logger.d(`setThrottle: throttle = ${throttle}, duration = ${this.duration}, amount = ${amount}`);
|
|
this.reset();
|
|
return this;
|
|
}
|
|
|
|
getDuration() {
|
|
return this.duration;
|
|
}
|
|
|
|
getThrottle() {
|
|
return this.throttle;
|
|
}
|
|
|
|
getState() {
|
|
return this.state;
|
|
}
|
|
|
|
start() {
|
|
this._setState('running');
|
|
}
|
|
|
|
setSkip(skip) {
|
|
this.inSkip = skip;
|
|
}
|
|
|
|
stop() {
|
|
if (this.state !== 'complete') {
|
|
this._setState('stopped');
|
|
this._callbackListener(true);
|
|
}
|
|
}
|
|
|
|
completeForce() {
|
|
if (this.state !== 'complete') {
|
|
this.currentFrame = this.duration;
|
|
this.update();
|
|
}
|
|
}
|
|
|
|
// Game_Picture.prototype.applyEasing = function(current, target) {
|
|
// const d = this._duration;
|
|
// const wd = this._wholeDuration;
|
|
// const lt = this.calcEasing((wd - d) / wd);
|
|
// const t = this.calcEasing((wd - d + 1) / wd);
|
|
// const start = (current - target * lt) / (1 - lt);
|
|
// return start + (target - start) * t;
|
|
// };
|
|
|
|
calcTarget() {
|
|
if (this.easing === EasingType.LINEAR) {
|
|
return (this.throttle * this.currentFrame);
|
|
} else {
|
|
const current = this.duration - this.currentFrame;
|
|
const lt = this.calcEasing(this.currentFrame / this.duration);
|
|
const t = this.calcEasing((this.currentFrame + 1) / this.duration);
|
|
}
|
|
}
|
|
|
|
calcEasing(t) {
|
|
const exponent = 2;
|
|
|
|
switch (this.easing) {
|
|
case EasingType.EASE_IN:
|
|
return Math.pow(t, exponent);
|
|
case EasingType.EASE_OUT:
|
|
return 1 - Math.pow(1 - t, exponent);
|
|
case EasingType.EASE_INOUT:
|
|
if (t < 0.5) {
|
|
return Math.pow((t * 2), exponent) / 2;
|
|
} else {
|
|
return (1 - Math.pow(1 - (t * 2 - 1), exponent)) / 2 + 0.5;
|
|
}
|
|
} // switch
|
|
return t;
|
|
}
|
|
|
|
update() {
|
|
if (this.state !== 'running') {
|
|
return;
|
|
}
|
|
|
|
let target = 0;
|
|
if (this.directionReverse) {
|
|
const currentValue = this.startValue - (this.throttle * this.currentFrame);
|
|
target = Math.max(currentValue, this.endValue);
|
|
} else {
|
|
const currentValue = this.startValue + (this.throttle * this.currentFrame);
|
|
target = Math.min(currentValue, this.endValue);
|
|
}
|
|
|
|
if (!!this.object && !!this.targetProperty) {
|
|
this.object[this.targetProperty] = target;
|
|
}
|
|
this.currentFrame++;
|
|
|
|
if (this.currentFrame >= this.duration || this.inSkip) {
|
|
if (!!this.listener) {
|
|
this.listener(this.name);
|
|
}
|
|
|
|
if (!!this.object && !!this.targetProperty) {
|
|
this.object[this.targetProperty] = this.endValue;
|
|
}
|
|
this._setState('complete');
|
|
if (this.repeat) {
|
|
this.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
reset() {
|
|
this.currentFrame = 0;
|
|
this.state = 'ready';
|
|
if (!!this.object && !!this.targetProperty && !!this.object[this.targetProperty]) {
|
|
this.object[this.targetProperty] = this.startValue;
|
|
}
|
|
}
|
|
|
|
inRunning() {
|
|
return this.state === 'running';
|
|
}
|
|
|
|
isComplete() {
|
|
return this.state === 'complete';
|
|
}
|
|
|
|
isRepeat() {
|
|
return this.repeat;
|
|
}
|
|
|
|
_callbackListener(byAbort) {
|
|
if (!!this.listener && typeof this.listener === 'function') {
|
|
this.listener(byAbort);
|
|
}
|
|
}
|
|
|
|
_setState(newState) {
|
|
this.state = newState;
|
|
}
|
|
}
|
|
window.SmartAnimation = SmartAnimation;
|
|
|
|
//========================================================================
|
|
// クラス定義 (SmartAnimationWait)
|
|
//========================================================================
|
|
class SmartAnimationWait extends SmartAnimation {
|
|
constructor(name, duration, callback) {
|
|
super(name, null, null, false, 0, 0, callback);
|
|
this.setDuration(duration);
|
|
}
|
|
}
|
|
window.SmartAnimationWait = SmartAnimationWait;
|
|
|
|
//========================================================================
|
|
// クラス定義 (SmartAnimationSequencial)
|
|
//========================================================================
|
|
class SmartAnimationSequencial extends SmartAnimation {
|
|
constructor(name, repeat, callback, ...values) {
|
|
super(name, null, null, repeat, 0, 0, callback, ...values);
|
|
|
|
this.values = !!values ? values : [];
|
|
this.targetFrame = values.reduce((accum, value) => {
|
|
return accum + value.getDuration();
|
|
}, 0);
|
|
this.currentIndex = 0;
|
|
}
|
|
|
|
update() {
|
|
if (this.state !== 'running') {
|
|
return;
|
|
}
|
|
|
|
const current = this.values[this.currentIndex];
|
|
if (!!current) {
|
|
if (current.isComplete()) {
|
|
this.currentIndex++;
|
|
if (this.currentIndex >= this.values.length) {
|
|
this._setState('complete');
|
|
this._callbackListener(false);
|
|
|
|
if (this.repeat) {
|
|
this.reset();
|
|
this.start();
|
|
}
|
|
} else {
|
|
this.currentValueFrame = 0;
|
|
this.values[this.currentIndex].update();
|
|
}
|
|
} else {
|
|
current.update();
|
|
}
|
|
}
|
|
}
|
|
|
|
reset() {
|
|
super.reset();
|
|
this.values.forEach(value => value.reset());
|
|
this.currentIndex = 0;
|
|
}
|
|
|
|
start() {
|
|
super.start();
|
|
this.values.forEach(value => value.start());
|
|
}
|
|
|
|
stop() {
|
|
super.stop();
|
|
this.values.forEach(value => value.stop());
|
|
}
|
|
}
|
|
window.SmartAnimationSequencial = SmartAnimationSequencial;
|
|
|
|
//========================================================================
|
|
// クラス定義 (SmartAnimationParallel)
|
|
//========================================================================
|
|
class SmartAnimationParallel extends SmartAnimation {
|
|
constructor(name, repeat, callback, ...values) {
|
|
super(name, null, null, repeat, 0, 0, callback, ...values);
|
|
this.values = !!values ? values : [];
|
|
this.targetFrame = values.reduce((accum, value) => {
|
|
const duration = value.getDuration();
|
|
if (accum < duration) {
|
|
return duration;
|
|
} else {
|
|
return accum;
|
|
}
|
|
}, 0);
|
|
this.currentFrame = 0;
|
|
}
|
|
|
|
update() {
|
|
if (this.state !== 'running') {
|
|
return;
|
|
}
|
|
|
|
this.values
|
|
.filter(value => value.isComplete() === false)
|
|
.forEach(value => value.update());
|
|
|
|
this.currentFrame++;
|
|
if (this.currentFrame >= this.targetFrame) {
|
|
this._setState('complete');
|
|
this._callbackListener(false);
|
|
|
|
if (this.repeat) {
|
|
this.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
reset() {
|
|
super.reset();
|
|
this.values.forEach(value => value.reset());
|
|
}
|
|
|
|
start() {
|
|
super.start();
|
|
this.values.forEach(value => value.start());
|
|
}
|
|
|
|
stop() {
|
|
super.stop();
|
|
this.values.forEach(value => value.stop());
|
|
}
|
|
}
|
|
window.SmartAnimationParallel = SmartAnimationParallel;
|
|
|
|
})();
|