/*: * @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); }; })();