263 lines
10 KiB
JavaScript
263 lines
10 KiB
JavaScript
/*:
|
||
* @target MZ
|
||
* @plugindesc ピクチャを拡大/スライドしながらフェードさせる v1.8
|
||
* ⾃動ID割り当て (70〜80) & 完全⾃動消去対応
|
||
* @author IvI
|
||
* @help
|
||
* --------------------------------------------------------------
|
||
* ▼ 概要
|
||
* pictureId 引数を廃止。ID 70〜80 の空き番号を⾃動で使います。
|
||
* 演出が終わった瞬間にその ID を `$gameScreen.erasePicture()` で解放。
|
||
*
|
||
* ▼ プラグインコマンド
|
||
* ◆ playEffect : 拡大フェード
|
||
* ◆ playSlideEffect : 上昇+横移動+軽い拡大+フェードアウト
|
||
* どちらも数式( 880+\V[30] など)をそのまま入力可能。
|
||
*
|
||
* ▼ 自動 ID 割り当て仕様
|
||
* 1. 70〜80 を上から順に検索し、null の ID を使用
|
||
* 2. 空きが無ければ「演出が終了済み」のピクチャを erase して再利用
|
||
* 3. どうしても無ければ 70 を強制上書き(理論上ほぼ発生しません)
|
||
*
|
||
* ▼ ライセンス
|
||
* MIT
|
||
* --------------------------------------------------------------
|
||
*
|
||
* @command playEffect
|
||
* @text エフェクト再生
|
||
* @arg filename @text 画像ファイル @type file @dir img/pictures
|
||
* @arg x @text X座標 @type string @default 0
|
||
* @arg y @text Y座標 @type string @default 0
|
||
* @arg origin @text 原点 @type select
|
||
* @option 左上 @value 0
|
||
* @option 中央 @value 1
|
||
* @default 1
|
||
* @arg firstDuration @text フェードインF @type string @default 20
|
||
* @arg secondDuration @text フェードアウトF @type string @default 30
|
||
* @arg blendMode @text ブレンドモード @type select
|
||
* @option 通常 @value 0
|
||
* @option 加算 @value 1
|
||
* @default 0
|
||
* @arg wait @text 終了までウェイト @type boolean @default false
|
||
* --------------------------------------------------------------
|
||
* @command playSlideEffect
|
||
* @text スライドエフェクト再生
|
||
* @arg filename @text 画像ファイル @type file @dir img/pictures
|
||
* @arg x @text X座標 @type string @default 0
|
||
* @arg y @text Y座標 @type string @default 0
|
||
* @arg origin @text 原点 @type select
|
||
* @option 左上 @value 0
|
||
* @option 中央 @value 1
|
||
* @default 1
|
||
* @arg rise @text 上昇距離(px) @type string @default -60
|
||
* @arg slideX @text 横移動(px) @type string @default 20
|
||
* @arg scaleTo @text 最終倍率(%) @type string @default 120
|
||
* @arg firstDuration @text フェードインF @type string @default 20
|
||
* @arg secondDuration @text フェードアウトF @type string @default 40
|
||
* @arg blendMode @text ブレンドモード @type select
|
||
* @option 通常 @value 0
|
||
* @option 加算 @value 1
|
||
* @default 0
|
||
* @arg wait @text 終了までウェイト @type boolean @default false
|
||
* --------------------------------------------------------------
|
||
*/
|
||
|
||
(() => {
|
||
'use strict';
|
||
const PN = 'ZoomFadePicture';
|
||
const ID_MIN = 70;
|
||
const ID_MAX = 90;
|
||
|
||
/* ------------------------------------------------------------------
|
||
* 文字列→数値変換(\V[n] 置換+四則演算)
|
||
* ---------------------------------------------------------------- */
|
||
const exprToNumber = value => {
|
||
if (typeof value === 'number') return value;
|
||
if (value == null) return 0;
|
||
let expr = String(value).replace(/\\[Vv]\[(\d+)\]/g,
|
||
(_, id) => $gameVariables.value(Number(id)) ?? 0);
|
||
expr = expr.replace(/\s+/g, '');
|
||
try { return Number(Function(`"use strict";return(${expr})`)()); }
|
||
catch (e) {
|
||
console.warn(`${PN}: 式「${value}」を評価できません → 0`);
|
||
return 0;
|
||
}
|
||
};
|
||
|
||
/* ------------------------------------------------------------------
|
||
* 自動ピクチャ ID 取得
|
||
* ---------------------------------------------------------------- */
|
||
const allocId = () => {
|
||
// 1) 空きを探す
|
||
for (let id = ID_MIN; id <= ID_MAX; id++) {
|
||
if (!$gameScreen.picture(id)) return id;
|
||
}
|
||
// 2) 終了済みのピクチャを上書き
|
||
for (let id = ID_MIN; id <= ID_MAX; id++) {
|
||
const pic = $gameScreen.picture(id);
|
||
if (pic && (pic._zfpDone || pic._szfpDone)) {
|
||
$gameScreen.erasePicture(id);
|
||
return id;
|
||
}
|
||
}
|
||
// 3) 全部使用中なら強制上書き
|
||
console.warn(`${PN}: ID 70〜80 が全て使用中です。ID 70 を上書きします`);
|
||
$gameScreen.erasePicture(ID_MIN);
|
||
return ID_MIN;
|
||
};
|
||
|
||
/* ------------------------------------------------------------------
|
||
* プラグインコマンド
|
||
* ---------------------------------------------------------------- */
|
||
PluginManager.registerCommand(PN, 'playEffect', function (args) {
|
||
const id = allocId();
|
||
const file = String(args.filename || '');
|
||
const x = exprToNumber(args.x || 0);
|
||
const y = exprToNumber(args.y || 0);
|
||
const org = exprToNumber(args.origin || 1);
|
||
const d1 = exprToNumber(args.firstDuration || 20);
|
||
const d2 = exprToNumber(args.secondDuration || 30);
|
||
const bm = exprToNumber(args.blendMode || 0);
|
||
const wt = args.wait === 'true';
|
||
|
||
$gameScreen.showPicture(id, file, org, x, y, 0, 0, 0, bm);
|
||
const pic = $gameScreen.picture(id);
|
||
if (!pic) return;
|
||
pic._autoEraseId = id; // ← 自分の ID を保存
|
||
pic.startZoomFade(d1, d2);
|
||
|
||
if (wt) {
|
||
const tag = `ZFP-${id}`;
|
||
pic._waitTag = tag;
|
||
this.setWaitMode(tag);
|
||
}
|
||
});
|
||
|
||
PluginManager.registerCommand(PN, 'playSlideEffect', function (args) {
|
||
const id = allocId();
|
||
const file = String(args.filename || '');
|
||
const x = exprToNumber(args.x || 0);
|
||
const y = exprToNumber(args.y || 0);
|
||
const org = exprToNumber(args.origin || 1);
|
||
const rise = exprToNumber(args.rise || -60);
|
||
const dx = exprToNumber(args.slideX || 20);
|
||
const scTo = Math.max(exprToNumber(args.scaleTo || 120), 100);
|
||
const d1 = exprToNumber(args.firstDuration || 20);
|
||
const d2 = exprToNumber(args.secondDuration || 40);
|
||
const bm = exprToNumber(args.blendMode || 0);
|
||
const wt = args.wait === 'true';
|
||
|
||
$gameScreen.showPicture(id, file, org, x, y, 100, 100, 0, bm);
|
||
const pic = $gameScreen.picture(id);
|
||
if (!pic) return;
|
||
pic._autoEraseId = id;
|
||
pic.startSlideZoomFade(d1, d2, dx, rise, scTo);
|
||
|
||
if (wt) {
|
||
const tag = `ZFP-${id}`;
|
||
pic._waitTag = tag;
|
||
this.setWaitMode(tag);
|
||
}
|
||
});
|
||
|
||
/* ------------------------------------------------------------------
|
||
* Game_Picture 拡張
|
||
* ---------------------------------------------------------------- */
|
||
const _Game_Picture_update = Game_Picture.prototype.update;
|
||
Game_Picture.prototype.update = function () {
|
||
_Game_Picture_update.call(this);
|
||
if (this._zfpTotal) this._updateZoomFade();
|
||
if (this._szfpTotal) this._updateSlideZoomFade();
|
||
};
|
||
|
||
/* === 拡大フェード ============================================= */
|
||
Game_Picture.prototype.startZoomFade = function (d1, d2) {
|
||
this._zfpD1 = d1;
|
||
this._zfpD2 = d2;
|
||
this._zfpTotal = d1 + d2;
|
||
this._zfpCnt = 0;
|
||
this._zfpDone = false;
|
||
};
|
||
|
||
const easeOut = t => 1 - Math.pow(1 - t, 3);
|
||
|
||
Game_Picture.prototype._updateZoomFade = function () {
|
||
if (this._zfpDone) return;
|
||
this._zfpCnt++;
|
||
const t = this._zfpCnt / this._zfpTotal;
|
||
const r = this._zfpD1 / this._zfpTotal;
|
||
const s = easeOut(t);
|
||
|
||
/* scale 0→150% */
|
||
this._scaleX = this._scaleY = 150 * s;
|
||
|
||
/* opacity */
|
||
if (t <= r) this._opacity = Math.round(255 * easeOut(t / r));
|
||
else this._opacity = Math.round(255 * (1 - easeOut((t - r) / (1 - r))));
|
||
|
||
/* 終了判定+自動消去 */
|
||
if (this._zfpCnt >= this._zfpTotal) {
|
||
this._zfpDone = true;
|
||
if (this._autoEraseId != null && !$gameMessage.isBusy()) {
|
||
$gameScreen.erasePicture(this._autoEraseId);
|
||
}
|
||
}
|
||
};
|
||
|
||
/* === スライド+拡大フェード =================================== */
|
||
Game_Picture.prototype.startSlideZoomFade = function (d1, d2, dx, dy, scaleTo) {
|
||
this._szfpD1 = d1;
|
||
this._szfpD2 = d2;
|
||
this._szfpTotal = d1 + d2;
|
||
this._szfpCnt = 0;
|
||
this._szfpDone = false;
|
||
this._szfpDX = dx;
|
||
this._szfpDY = dy;
|
||
this._szfpSFrom = 100;
|
||
this._szfpSTo = scaleTo;
|
||
this._szfpBaseX = this._x;
|
||
this._szfpBaseY = this._y;
|
||
};
|
||
|
||
Game_Picture.prototype._updateSlideZoomFade = function () {
|
||
if (this._szfpDone) return;
|
||
this._szfpCnt++;
|
||
const t = this._szfpCnt / this._szfpTotal;
|
||
const r = this._szfpD1 / this._szfpTotal;
|
||
const ez = easeOut(t);
|
||
|
||
/* position */
|
||
this._x = Math.round(this._szfpBaseX + this._szfpDX * ez);
|
||
this._y = Math.round(this._szfpBaseY + this._szfpDY * ez);
|
||
|
||
/* scale */
|
||
const sc = this._szfpSFrom + (this._szfpSTo - this._szfpSFrom) * ez;
|
||
this._scaleX = this._scaleY = sc;
|
||
|
||
/* opacity */
|
||
if (t <= r) this._opacity = Math.round(255 * easeOut(t / r));
|
||
else this._opacity = Math.round(255 * (1 - easeOut((t - r) / (1 - r))));
|
||
|
||
/* 終了判定+自動消去 */
|
||
if (this._szfpCnt >= this._szfpTotal) {
|
||
this._szfpDone = true;
|
||
if (this._autoEraseId != null && !$gameMessage.isBusy()) {
|
||
$gameScreen.erasePicture(this._autoEraseId);
|
||
}
|
||
}
|
||
};
|
||
|
||
/* ------------------------------------------------------------------
|
||
* Interpreter:ZFP ウェイトモード
|
||
* ---------------------------------------------------------------- */
|
||
const _Game_Interpreter_updateWaitMode = Game_Interpreter.prototype.updateWaitMode;
|
||
Game_Interpreter.prototype.updateWaitMode = function () {
|
||
if (this._waitMode && this._waitMode.startsWith('ZFP-')) {
|
||
const id = Number(this._waitMode.split('-')[1]);
|
||
const pic = $gameScreen.picture(id);
|
||
if (!pic) { this._waitMode = ''; return false; }
|
||
return true;
|
||
}
|
||
return _Game_Interpreter_updateWaitMode.call(this);
|
||
};
|
||
})();
|