356 lines
10 KiB
JavaScript
356 lines
10 KiB
JavaScript
// DarkPlasma_CharacterAfterimage 1.0.1
|
||
// Copyright (c) 2024 DarkPlasma
|
||
// This software is released under the MIT license.
|
||
// http://opensource.org/licenses/mit-license.php
|
||
|
||
/**
|
||
* 2024/03/07 1.0.1 特定のプラグインと併用するとエラーが発生する不具合を修正
|
||
* 2024/03/01 1.0.0 公開
|
||
*/
|
||
|
||
/*:
|
||
* @plugindesc マップ上のキャラクターに残像を表示する
|
||
* @author DarkPlasma
|
||
* @license MIT
|
||
*
|
||
* @target MZ
|
||
* @url https://github.com/elleonard/DarkPlasma-MZ-Plugins/tree/release
|
||
*
|
||
* @param duration
|
||
* @text 表示時間(フレーム数)
|
||
* @type number
|
||
* @default 30
|
||
*
|
||
* @param generationInterval
|
||
* @text 生成間隔(フレーム数)
|
||
* @type number
|
||
* @default 4
|
||
*
|
||
* @param opacity
|
||
* @text 不透明度
|
||
* @type number
|
||
* @max 255
|
||
* @default 255
|
||
*
|
||
* @param colorTone
|
||
* @text 色調
|
||
* @type struct<ColorTone>
|
||
* @default {"red":"0","blue":"0","green":"0","alpha":"0"}
|
||
*
|
||
* @param blendMode
|
||
* @text 合成方法
|
||
* @type select
|
||
* @option 通常
|
||
* @value 0
|
||
* @option 加算
|
||
* @value 1
|
||
* @option 乗算
|
||
* @value 2
|
||
* @option スクリーン
|
||
* @value 3
|
||
* @default 0
|
||
*
|
||
* @command startAfterimage
|
||
* @text 残像の表示
|
||
* @desc プレイヤーまたはイベントに対して残像を表示します。
|
||
* @arg target
|
||
* @desc 残像の表示対象を選択します。
|
||
* @text 対象
|
||
* @type select
|
||
* @option プレイヤー
|
||
* @value player
|
||
* @option このイベント
|
||
* @value thisEvent
|
||
* @option 他のイベント
|
||
* @value otherEvent
|
||
* @default player
|
||
* @arg eventId
|
||
* @desc 対象が他のイベントの場合に、イベントIDを指定します。
|
||
* @text イベントID
|
||
* @type number
|
||
* @default 0
|
||
*
|
||
* @command clearAfterimage
|
||
* @text 残像の消去
|
||
* @desc プレイヤーまたはイベントに対して表示している残像を消去します。
|
||
* @arg target
|
||
* @desc 残像の表示対象を選択します。
|
||
* @text 対象
|
||
* @type select
|
||
* @option プレイヤー
|
||
* @value player
|
||
* @option このイベント
|
||
* @value thisEvent
|
||
* @option 他のイベント
|
||
* @value otherEvent
|
||
* @default player
|
||
* @arg eventId
|
||
* @desc 対象が他のイベントの場合に、イベントIDを指定します。
|
||
* @text イベントID
|
||
* @type number
|
||
* @default 0
|
||
*
|
||
* @help
|
||
* version: 1.0.1
|
||
* マップ上のキャラクターに残像を表示します。
|
||
*
|
||
* 本プラグインはセーブデータを拡張します。
|
||
* マップ上の残像を表示している各キャラクターについて、
|
||
* その表示状態をセーブデータに追加します。
|
||
*/
|
||
/*~struct~ColorTone:
|
||
* @param red
|
||
* @text 赤
|
||
* @type number
|
||
* @max 255
|
||
* @default 0
|
||
*
|
||
* @param green
|
||
* @text 緑
|
||
* @type number
|
||
* @max 255
|
||
* @default 0
|
||
*
|
||
* @param blue
|
||
* @text 青
|
||
* @type number
|
||
* @max 255
|
||
* @default 0
|
||
*
|
||
* @param alpha
|
||
* @text α
|
||
* @type number
|
||
* @max 255
|
||
* @default 0
|
||
*/
|
||
(() => {
|
||
'use strict';
|
||
|
||
const pluginName = document.currentScript.src.replace(/^.*\/(.*).js$/, function () {
|
||
return arguments[1];
|
||
});
|
||
|
||
const pluginParametersOf = (pluginName) => PluginManager.parameters(pluginName);
|
||
|
||
const pluginParameters = pluginParametersOf(pluginName);
|
||
|
||
const settings = {
|
||
duration: Number(pluginParameters.duration || 30),
|
||
generationInterval: Number(pluginParameters.generationInterval || 4),
|
||
opacity: Number(pluginParameters.opacity || 255),
|
||
colorTone: pluginParameters.colorTone
|
||
? ((parameter) => {
|
||
const parsed = JSON.parse(parameter);
|
||
return {
|
||
red: Number(parsed.red || 0),
|
||
green: Number(parsed.green || 0),
|
||
blue: Number(parsed.blue || 0),
|
||
alpha: Number(parsed.alpha || 0),
|
||
};
|
||
})(pluginParameters.colorTone)
|
||
: { red: 0, blue: 0, green: 0, alpha: 0 },
|
||
blendMode: Number(pluginParameters.blendMode || 0),
|
||
};
|
||
|
||
function parseArgs_startAfterimage(args) {
|
||
return {
|
||
target: String(args.target || `player`),
|
||
eventId: Number(args.eventId || 0),
|
||
};
|
||
}
|
||
|
||
const command_startAfterimage = 'startAfterimage';
|
||
|
||
const command_clearAfterimage = 'clearAfterimage';
|
||
|
||
PluginManager.registerCommand(pluginName, command_startAfterimage, function (args) {
|
||
const parsedArgs = parseArgs_startAfterimage(args);
|
||
const target = (() => {
|
||
switch (parsedArgs.target) {
|
||
case 'player':
|
||
return $gamePlayer;
|
||
case 'thisEvent':
|
||
return this.character(0);
|
||
case 'otherEvent':
|
||
return this.character(parsedArgs.eventId);
|
||
default:
|
||
return null;
|
||
}
|
||
})();
|
||
target?.requestStartAfterimage();
|
||
});
|
||
PluginManager.registerCommand(pluginName, command_clearAfterimage, function (args) {
|
||
const parsedArgs = parseArgs_startAfterimage(args);
|
||
const target = (() => {
|
||
switch (parsedArgs.target) {
|
||
case 'player':
|
||
return $gamePlayer;
|
||
case 'thisEvent':
|
||
return this.character(0);
|
||
case 'otherEvent':
|
||
return this.character(parsedArgs.eventId);
|
||
default:
|
||
return null;
|
||
}
|
||
})();
|
||
target?.clearAfterimage();
|
||
});
|
||
function Game_CharacterBase_AfterimageMixIn(gameCharacterBase) {
|
||
const _initMembers = gameCharacterBase.initMembers;
|
||
gameCharacterBase.initMembers = function () {
|
||
_initMembers.call(this);
|
||
this._afterimage = undefined;
|
||
};
|
||
gameCharacterBase.hasAfterimage = function () {
|
||
return !!this._afterimage;
|
||
};
|
||
gameCharacterBase.afterimage = function () {
|
||
return this._afterimage;
|
||
};
|
||
gameCharacterBase.requestStartAfterimage = function () {
|
||
this._afterimage = new Game_CharacterAfterimage();
|
||
};
|
||
gameCharacterBase.clearAfterimage = function () {
|
||
this._afterimage = undefined;
|
||
};
|
||
const _update = gameCharacterBase.update;
|
||
gameCharacterBase.update = function () {
|
||
_update.call(this);
|
||
this._afterimage?.update();
|
||
};
|
||
}
|
||
Game_CharacterBase_AfterimageMixIn(Game_CharacterBase.prototype);
|
||
class Game_CharacterAfterimage {
|
||
constructor() {
|
||
this._interval = settings.generationInterval;
|
||
this._generateAfterimageFrameCount = settings.generationInterval;
|
||
this._duration = settings.duration;
|
||
this._initialOpacity = settings.opacity;
|
||
this._colorTone = [
|
||
settings.colorTone.red,
|
||
settings.colorTone.green,
|
||
settings.colorTone.blue,
|
||
settings.colorTone.alpha,
|
||
];
|
||
this._blendMode = settings.blendMode;
|
||
}
|
||
update() {
|
||
this._generateAfterimageFrameCount++;
|
||
}
|
||
resetGenerationFrameCount() {
|
||
this._generateAfterimageFrameCount = 0;
|
||
}
|
||
mustGenerateSprite() {
|
||
return this._generateAfterimageFrameCount >= this._interval;
|
||
}
|
||
duration() {
|
||
return this._duration;
|
||
}
|
||
initialOpacity() {
|
||
return this._initialOpacity;
|
||
}
|
||
colorTone() {
|
||
return this._colorTone;
|
||
}
|
||
blendMode() {
|
||
return this._blendMode;
|
||
}
|
||
}
|
||
function Spriteset_Map_AfterimageMixIn(spritesetMap) {
|
||
const _update = spritesetMap.update;
|
||
spritesetMap.update = function () {
|
||
_update.call(this);
|
||
this._characterSprites.forEach((character) => character.clearReservedAfterimages());
|
||
};
|
||
}
|
||
Spriteset_Map_AfterimageMixIn(Spriteset_Map.prototype);
|
||
function Sprite_Character_AfterimageMixIn(spriteCharacter) {
|
||
const _initialize = spriteCharacter.initialize;
|
||
spriteCharacter.initialize = function (character) {
|
||
_initialize.call(this, character);
|
||
this._afterimageSprites = [];
|
||
this._clearReservedAfterimageSprites = [];
|
||
};
|
||
spriteCharacter.createAfterimage = function () {
|
||
const afterimage = new Sprite_CharacterAfterimage(this._character)
|
||
.withPosition(this.x, this.y, this.z - 0.1)
|
||
.withScale(this.scale)
|
||
.withRotation(this.rotation)
|
||
.withAnchor(this.anchor.x, this.anchor.y);
|
||
this._afterimageSprites.push(afterimage);
|
||
this.parent.addChild(afterimage);
|
||
};
|
||
spriteCharacter.clearAfterimage = function () {
|
||
this._afterimageSprites.forEach((afterimage) => this.parent.removeChild(afterimage));
|
||
this._afterimageSprites = [];
|
||
};
|
||
const _update = spriteCharacter.update;
|
||
spriteCharacter.update = function () {
|
||
_update.call(this);
|
||
this.updateAfterimage();
|
||
};
|
||
spriteCharacter.updateAfterimage = function () {
|
||
/**
|
||
* ここでremoveChildしてしまうとtilemapの子spriteの数が変動し、updateがスキップされてしまうspriteが現れる
|
||
* そのため、removeChildの予約だけしておく
|
||
*/
|
||
this._clearReservedAfterimageSprites = this._afterimageSprites.filter((afterimage) => afterimage.opacity <= 0);
|
||
this._afterimageSprites = this._afterimageSprites.filter((afterimage) => afterimage.opacity > 0);
|
||
if (this._character.afterimage()?.mustGenerateSprite()) {
|
||
this.createAfterimage();
|
||
this._character.afterimage()?.resetGenerationFrameCount();
|
||
}
|
||
};
|
||
spriteCharacter.clearReservedAfterimages = function () {
|
||
this._clearReservedAfterimageSprites.forEach((sprite) => this.parent.removeChild(sprite));
|
||
this._clearReservedAfterimageSprites = [];
|
||
};
|
||
}
|
||
Sprite_Character_AfterimageMixIn(Sprite_Character.prototype);
|
||
class Sprite_CharacterAfterimage extends Sprite_Character {
|
||
constructor(character) {
|
||
super(character);
|
||
const afterimage = character.afterimage();
|
||
if (!afterimage) {
|
||
throw new Error(`対象キャラクターに残像情報が存在しません。`);
|
||
}
|
||
/**
|
||
* この時点でのキャラクター情報をコピーしておく
|
||
*/
|
||
this._character = JsonEx.makeDeepCopy(character);
|
||
this._afterimage = afterimage;
|
||
this.setColorTone(afterimage.colorTone());
|
||
this.blendMode = afterimage.blendMode();
|
||
this.updateBitmap();
|
||
this.updateFrame();
|
||
}
|
||
withPosition(x, y, z) {
|
||
this.x = x;
|
||
this.y = y;
|
||
this.z = z;
|
||
return this;
|
||
}
|
||
withScale(scale) {
|
||
this.scale = scale;
|
||
return this;
|
||
}
|
||
withRotation(rotation) {
|
||
this.rotation = rotation;
|
||
return this;
|
||
}
|
||
withAnchor(x, y) {
|
||
this.anchor.x = x;
|
||
this.anchor.y = y;
|
||
return this;
|
||
}
|
||
update() {
|
||
this.opacity -= this._afterimage.initialOpacity() / this._afterimage.duration();
|
||
this.updatePosition();
|
||
}
|
||
updatePosition() {
|
||
super.updatePosition();
|
||
this.z -= 0.1;
|
||
}
|
||
}
|
||
globalThis.Game_CharacterAfterimage = Game_CharacterAfterimage;
|
||
})();
|