立ち絵プラグイン
//=============================================================================
// Lunatlazur_Tachie.js
// ----------------------------------------------------------------------------
// Copyright (c) 2018 Taku Aoi
// This plugin is released under the zlib License.
// https://zlib.net/zlib_license.html
// ----------------------------------------------------------------------------
// Version
// 1.0.0 2018-04-01
// ----------------------------------------------------------------------------
// [Web] : https://lunatlazur.com
// [Twitter]: https://twitter.com/aoitaku
// [GitHub] : https://github.com/lunatlazur
//=============================================================================
/*:
* @plugindesc Manage visual novel style character sprite
* @author Taku Aoi
* @help This plugin provides a function to display the standing picture of the
* character.
*
* Usage
* =====
*
* 1. Place a character sprite file with the following format in the Picture folder.
*
* characterA1.png
* characterA2.png
* characterA3.png
* characterA4.png
* characterA5.png
* characterB1.png
* characterB2.png
* :
* :
*
* 2. Create a character sprite definition file in the Data folder namedTachie.csv
* in the following format.
*
* characterA, CharactarA, smile, anger, disapointment, discontent
* characterB, CharacterB, smile, anger, disapointment, discontent
* :
* :
*
* - In the 1st column, specify the part obtained by removing the extension and
* the number at the end from the character sprite image file name placed in
* the Picture folder.
* - The 2nd column is the name of the character.
* Use this name when specifying the character sprite file from the plugin
* command.
* - The 3rd and subsequent columns are the names of the expressions in the
* character sprite image file.
* Use this name when specifying facial expressions from plugin commands.
* Please name the expression difference after character image numbered in 2.
* (Character image numbered in 1 is used when expression is not specified.)
*
* In the above example, characterA2.png is set to smile for character A,
* and characterA3.png is set to anger for character A.
*
* 3. Input the character sprite display command to the plug-in command to display
* the character sprite. Refer to the following plug-in command list for details
* of plug-in commands.
*
*
* Plug-in command list
* ====================
*
* SHOW_TACHIE
* -----------
*
* SHOW_TACHIE
*
*
* Show character sprite.
*
*
* string
*
* The name of the character set in the character sprite definition file.
*
*
* string
*
* Expression of character set in character sprite definition file.
* If unspecified, the expression of the character numbered 1 will be used.
*
*
* { left | center | right }
*
* Set X coordinate of character sprite according to position parameter.
* If unspecified, set to center position.
*
*
* { near | middle | far }
*
* Set the magnification and Y coordinate of the character sprite to values
* according to the distance parameter.
* If unspecified, set to middle distance.
*
*
* x:%d
*
* Add specified value to X coordinate of character sprite.
*
*
* y:%d
*
* Add specified number to Y coordinate of character sprite.
*
*
* slide-x:%d
*
* Subtract the specified value from the X coordinate at the start of character
* sprite display.
*
*
* slide-y:%d
*
* Subtract the specified value from the Y coordinate at the start of character
* sprite display.
*
*
* %dF
*
* Specify the display time in frames.
* If unspecified, it will be displayed instantly.
*
*
* { wait | no-wait }
*
* Wait until display is complete.
* If unspecified, set to no wait.
* If duration is unspecified, it should be ignored.
*
*
* Example
*
* SHOW_TACHIE characterA near
*
* Character A is instantly displayed in the center of the screen with expression
* numbered in 1 at a near distance.
*
*
* SHOW_TACHIE characterA expression2 left x:5 y:-5 slide-x:20 60F wait
*
* Character A is displayed with a expression numbered in 2 at the middle distance
* for 60 frames, with sliding 20px from right to the position shifted 5px left and
* 5px upward from left side of screen.
* Wait until display is complete.
*
*
* CHANGE_TACHIE
* -------------
*
* CHANGE_TACHIE
*
*
* Change the character sprite.
* Mainly used to switch between facial expression differences and costume
* differences.
*
*
* string
*
* The name of the character set in the character sprite definition file.
*
*
* string
*
* The name of the character set in the character sprite definition file.
* Use the same expression set as the current character if not specified.
*
*
* string
*
* Expression of character set in character sprite definition file.
* If unspecified, set to expression numbered in 1.
*
*
* n%d
*
* Specify the display time in frames.
* If unspecified, it will be displayed instantly.
*
*
* { wait | no-wait }
*
* Wait until display is complete.
* If unspecified, set to no wait.
* If duration is unspecified, it should be ignored.
*
*
* Example
*
* CHANGE_TACHIE characterA expression2 30F
*
* Change character A to expression numbered in 2 for 30 frames.
*
*
* CHANGE_TACHIE characterA characterB 30F
*
* Change character A to expression numbered in 1 of character B for 30 frames.
*
*
* MOVE_TACHIE
* -----------
*
* MOVE_TACHIE
*
* Move the character sprite.
*
*
* String
*
* The name of the character set in the character sprite definition file
*
*
* { left | middle | right }
*
* Set X coordinate of character sprite according to position.
* If unspecified, set to center
*
*
* x:%d
*
* Add specified value to X coordinate of character sprite.
*
*
* y:%d
*
* Add specified number to Y coordinate of character sprite.
*
*
* %dF
*
* Specify the display time in frames.
* If unspecified, it will be moved instantly.
*
*
* { wait | no-wait }
*
* Wait until display is complete.
* If unspecified, set to no wait.
* If duration is unspecified, it should be ignored.
*
*
* Example
*
* TACHIE_MOVE characterA right x:-15
*
* Move character A to the position shifted 15px rightward on the right side
* of the screen for 30 frames.
*
*
* ERASE_TACHIE
* ------------
*
* ERASE_TACHIE
*
* Erase character sprite.
*
*
* String
*
* The name of the character set in the character sprite definition file
*
*
* slide-x:%d
*
* Subtract the specified value from the X coordinate at the start of character
* sprite display.
*
*
* slide-y:%d
*
* Subtract the specified value from the Y coordinate at the start of character
* sprite display.
*
*
* %dF
*
* Specify the display time in frames.
* If unspecified, it will be moved instantly.
*
*
* { wait | no-wait }
*
* Wait until display is complete.
* If unspecified, set to no wait.
* If duration is unspecified, it should be ignored.
*
*
* Example
*
* ERASE_TACHIE characterA 30f
*
* Delete characterA for 30 frames.
*
*
* Q & A
* =====
*
* Q. character sprite is not displayed even though the plug-in command is correct.
* A. Even if the plugin is enabled, the plugin command will not be recognized
* unless the project is saved. Save the project and try again.
*
* Q. I want to change character's expression.
* A. Use `CHANGE_TACHIE` command.
*
* Q. I want to change the distance of the displayed character.
* A. Combine `ERASE_TACHIE` and `CHANGE_TACHIE`.
*
* Q. Can't specify transparency.
* A. Unimplemented function. See upcoming implementation plans.
*
* Q. I want to display multiple characters at the same distance and position.
* A. Unimplemented function. See upcoming implementation plans.
*
* Q. I want to control the display order of characters.
* A. Unimplemented function. See upcoming implementation plans.
*
*
* Future plans
* ============
*
* - Transparency specification when displaying and moving
* Transparency can be specified by SHOW_TACHIE / MOVE_TACHIE command.
*
* - Display multiple characters at the same distance and same position
* Multiple characters can be placed at the same distance and in the same
* position.
*
* - Control of display order
* Character display order can be explicitly specified.
*
* - Supports preloading of images specified by plug-in commands
* Preload images specified by plug-in commands at map transition.
*
* - Set various values by plug-in parameters
* The picture number etc. used in the character sprite plug-in can be
* specified by the plug-in parameter.
*
*
* Changelog
* =========
*
* 1.0.0 2018-04-01
* ----------------
* - Published.
*
*/
/*:ja
* @plugindesc 立ち絵プラグイン
* @author あおいたく
* @help このプラグインはキャラクターの立ち絵を表示できるようにします。
*
* 使い方
* ======
*
* 1. 以下形式の名前の立ち絵画像ファイルを Picture フォルダに配置します。
*
* characterA1.png
* characterA2.png
* characterA3.png
* characterA4.png
* characterA5.png
* characterB1.png
* characterB2.png
* :
* :
*
* 2. Data フォルダに Tachie.csv という名前で以下形式の立ち絵定義ファイルを
* 作成してください。
*
* characterA, キャラA, 笑い, 怒り, 呆れ, 不満
* characterB, キャラB, 笑い, 緊張, 呆れ, 不満
* :
* :
*
* - 一列目は Picture フォルダに配置した立ち絵画像ファイル名から拡張子と
* 末尾の数字を取り除いた部分です
* - 二列目はキャラクターの名前です
* プラグインコマンドから立ち絵画像ファイルを指定するときに、この名前で
* 指定します
* - 三列目以降は、立ち絵画像ファイルの表情の名前です
* プラグインコマンドから表情を指定するときに、この名前で指定します
* `{キャラクター名}2` 以降の表情差分に名前をつけてください
* (`{キャラクター名}1` は表情未指定時に使われます)
*
* 上記例では characterA2.png が キャラA の 笑い、characterA3.png が
* キャラA の 怒り という設定になっています。
*
* 3. プラグインコマンドに立ち絵表示コマンドを入力して立ち絵を表示を行います。
* プラグインコマンドの詳細は下記のプラグインコマンド一覧を参照してください。
*
*
* プラグインコマンド一覧
* ======================
*
* 立ち絵表示
* ----------
*
* 立ち絵表示 <キャラクター名> <表情> <位置> <距離> <横位置調整> <縦位置調整>
* <スライド横> <スライド縦> <表示にかける時間> <完了までウェイト>
*
* 立ち絵を表示します。
*
*
* <キャラクター名> 文字列
*
* 立ち絵定義ファイルで設定したキャラクターの名前
*
*
* <表情> 文字列
*
* 立ち絵定義ファイルで設定したキャラクターの表情
* 未指定で1番目の表情
*
*
* <位置> { 左 | 中 | 右 }
*
* 立ち絵の X 座標を位置に応じた値にする
* 未指定で中央
*
*
* <距離> { 近 | 中 | 遠 }
*
* 立ち絵の拡大率と Y 座標を距離に応じた値にする
* 未指定で中距離
*
*
* <横位置調整> { 横位置調整:n | 横:n }
*
* 立ち絵の X 座標に指定した数値を加える
*
*
* <縦位置調整> { 縦位置調整:n | 縦:n }
*
* 立ち絵の Y 座標に指定した数値を加える
*
*
* <スライド横> スライド横:n
*
* 立ち絵の表示開始時の X 座標から指定した数値を引く
*
*
* <スライド縦> スライド縦:n
*
* 立ち絵の表示開始時の Y 座標から指定した数値を引く
*
*
* <表示にかける時間> nフレーム
*
* 表示にかける時間をフレーム数で指定する
* 未指定で瞬間表示
*
*
* <完了までウェイト> { ウェイトあり | ウェイトなし }
*
* 表示が完了するまでウェイトする
* 未指定でウェイトなし
* 瞬間表示の場合は無視される
*
*
* 呼び出し例
*
* 立ち絵表示 キャラA 近
*
* キャラAを表情1で画面中央、近距離に瞬間表示
*
*
* 立ち絵表示 キャラA 表情2 左 横:5 縦:-5 スライド横:20 60フレーム ウェイトあり
*
* キャラAを表情2で画面左、中距離、左に5px、上に5pxずらした位置に
* 20px左にスライドしながら60フレームかけて表示する
* 表示が完了するまでウェイトする
*
*
* 立ち絵変更
* ----------
*
* 立ち絵変更 <変更前キャラクター名> <変更後キャラクター名> <表情>
* <変更にかける時間> <完了までウェイト>
*
* 立ち絵を変更します。
* 主に、表情差分や衣装差分の切り替えを行うためのコマンドです。
*
*
* <変更前キャラクター名> 文字列
*
* 立ち絵定義ファイルで設定したキャラクターの名前
*
*
* <変更後キャラクター名> 文字列
*
* 立ち絵定義ファイルで設定したキャラクターの名前
* 未指定で現在と同じキャラクターの表情セットを使う
*
*
* <表情> 文字列
*
* 立ち絵定義ファイルで設定したキャラクターの表情
* 未指定で1番目の表情
*
*
* <表示にかける時間> nフレーム
*
* 表示にかける時間をフレーム数で指定する
* 未指定で瞬間表示
*
*
* <完了までウェイト> { ウェイトあり | ウェイトなし }
*
* 表示が完了するまでウェイトする
* 未指定でウェイトなし
* 瞬間表示の場合は無視される
*
*
* 呼び出し例
*
* 立ち絵変更 キャラA 表情2 30フレーム
*
* キャラAを表情2に30フレームかけて変更する
*
*
* 立ち絵変更 キャラA キャラB 30フレーム
*
* キャラAをキャラBの表情1に30フレームかけて変更する
*
*
* 立ち絵移動
* ----------
*
* 立ち絵移動 <キャラクター名> <位置> <横位置調整> <縦位置調整>
* <移動にかける時間> <完了までウェイト>
*
* 立ち絵を移動します
*
*
* <キャラクター名> 文字列
*
* 立ち絵定義ファイルで設定したキャラクターの名前
*
*
* <位置> { 左 | 中 | 右 }
*
* 立ち絵の X 座標を位置に応じた値にする
* 未指定で中央
*
*
* <横位置調整> { 横位置調整:n | 横:n }
*
* 立ち絵の X 座標に指定した数値を加える
*
*
* <縦位置調整> { 縦位置調整:n | 縦:n }
*
* 立ち絵の Y 座標に指定した数値を加える
*
*
* <表示にかける時間> nフレーム
*
* 表示にかける時間をフレーム数で指定する
* 未指定で瞬間表示
*
*
* <完了までウェイト> { ウェイトあり | ウェイトなし }
*
* 表示が完了するまでウェイトする
* 未指定でウェイトなし
* 瞬間表示の場合は無視される
*
*
* 呼び出し例
*
* 立ち絵移動 キャラA 右 横:-15
*
* キャラAを画面右、右に15pxずらした位置に30フレームかけて移動させる
*
*
* 立ち絵消去
* ----------
*
* 立ち絵消去 <キャラクター名> <スライド横> <スライド縦> <消去にかける時間>
* <完了までウェイト>
*
* 立ち絵を消去します
*
*
* <キャラクター名> 文字列
*
* 立ち絵定義ファイルで設定したキャラクターの名前
*
*
* <スライド横> スライド横:n
*
* 立ち絵の表示開始時の X 座標から指定した数値を引く
*
*
* <スライド縦> スライド縦:n
*
* 立ち絵の表示開始時の Y 座標から指定した数値を引く
*
*
* <表示にかける時間> nフレーム
*
* 表示にかける時間をフレーム数で指定する
* 未指定で瞬間表示
*
*
* <完了までウェイト> { ウェイトあり | ウェイトなし }
*
* 表示が完了するまでウェイトする
* 未指定でウェイトなし
* 瞬間表示の場合は無視される
*
*
* 呼び出し例
*
* 立ち絵消去 キャラA 30フレーム
*
* キャラAを30フレームかけて消去する
*
*
* よくある質問
* ============
*
* Q. プラグインコマンドは間違ってないのに立ち絵が表示されない
* A. プラグインを有効化しても、プロジェクトを保存しないとプラグインコマンドが
* 認識されないようです。一度プロジェクトを保存してやり直してみてください。
*
* Q. 表情を変更したい
* A. 立ち絵変更コマンドを使ってください。
*
* Q. 表示しているキャラの距離を変更したい
* A. 消去と表示を組み合わせてください。
*
* Q. 透明度の指定ができない
* A. 未実装の機能です。今後の実装予定を参照してください。
*
* Q. 同距離、同位置に複数のキャラクターを表示したい
* A. 未実装の機能です。今後の実装予定を参照してください。
*
* Q. キャラクターの重なり順を制御したい
* A. 未実装の機能です。今後の実装予定を参照してください。
*
*
* 今後の予定
* ==========
*
* - 表示、移動時の透明度指定
* 表示、移動コマンドで透明度を指定できるように
*
* - 同距離、同位置の複数キャラクター表示
* 同じ距離、同じ位置に複数のキャラクターを配置できるように
*
* - 重なり順の制御
* キャラクターの重なり順を明示的に指定できるように
*
* - プラグインコマンドで指定した画像のプリロード対応
* プラグインコマンドで指定した画像をマップ遷移時にプリロードするように
*
* - プラグインパラメータによる各種の値の設定
* 立ち絵プラグインで使用するピクチャ番号などをプラグインパラメータで
* 指定できるように
*
*
* 変更履歴
* ========
*
* 1.0.0 2018-04-01
* ----------------
* - 公開
*
*/
(function () {
'use strict';
const _DataManager_createGameObjects = DataManager.createGameObjects;
DataManager.createGameObjects = function () {
_DataManager_createGameObjects.call(this);
TachieManager.characters = [];
};
const _DataManager_makeSaveContents = DataManager.makeSaveContents;
DataManager.makeSaveContents = function () {
const contents = _DataManager_makeSaveContents.call(this);
contents.tachieCharacters = TachieManager.characters;
return contents;
};
const _DataManager_extractSaveContents = DataManager.extractSaveContents;
DataManager.extractSaveContents = function (contents) {
_DataManager_extractSaveContents.apply(this, arguments);
TachieManager.characters = contents.tachieCharacters || [];
};
const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function (command, args) {
if (TachieManager.isTachieCommand(command)) {
const commandProcessor = new Tachie_CommandProcessor(this);
if (commandProcessor.processPluginCommand(command, args)) {
return;
}
}
_Game_Interpreter_pluginCommand.apply(this, arguments);
};
const _Sprite_Picture_updateOrigin = Sprite_Picture.prototype.updateOrigin;
Sprite_Picture.prototype.updateOrigin = function () {
if (this.picture().origin() === 2) {
this.anchor.x = TachieManager.config.anchorX;
this.anchor.y = TachieManager.config.anchorY;
}
else {
_Sprite_Picture_updateOrigin.call(this);
}
};
const _Game_Picture_updateMove = Game_Picture.prototype.updateMove;
Game_Picture.prototype.updateMove = function () {
_Game_Picture_updateMove.call(this);
if (this._onMoveCompleted && this._duration === 0) {
this._onMoveCompleted();
this._onMoveCompleted = null;
}
};
const _Scene_Boot_create = Scene_Boot.prototype.create;
Scene_Boot.prototype.create = function () {
_Scene_Boot_create.call(this);
TachieManager.loadDataFile();
};
function find(items, predicate) {
let found;
items.forEach((item) => {
if (predicate(item)) {
found = item;
}
});
return found;
}
class TachieCommandParser {
static parsePositionParameter(...args) {
const [parameter] = args;
switch (parameter) {
case '左':
case 'left':
return [{ 'position': 'left' }, true];
case '右':
case 'right':
return [{ 'position': 'right' }, true];
case '中':
case 'center':
return [{ 'position': 'center' }, true];
}
return [{ 'position': 'center' }, false];
}
static parseDistanceParameter(...args) {
const [parameter] = args;
switch (parameter) {
case '遠':
case 'far':
return [{ 'distance': 'far' }, true];
case '近':
case 'near':
return [{ 'distance': 'near' }, true];
case '中':
case 'middle':
return [{ 'distance': 'middle' }, true];
}
return [{ 'distance': 'middle' }, false];
}
static parseOffsetXParameter(...args) {
const [parameter, arg] = args;
const value = parseInt(arg, 10);
switch (parameter) {
case '横':
case '横位置調整':
case 'x':
return [{ 'offsetX': value }, true];
}
return [{ 'offsetX': 0 }, false];
}
static parseOffsetYParameter(...args) {
const [parameter, arg] = args;
const value = parseInt(arg, 10);
switch (parameter) {
case '縦':
case '縦位置調整':
case 'y':
return [{ 'offsetY': value }, true];
}
return [{ 'offsetY': 0 }, false];
}
static parseSlideXParameter(...args) {
const [parameter, arg] = args;
const value = parseInt(arg, 10);
switch (parameter) {
case 'スライド横':
case 'slide-x':
return [{ 'slideX': value }, true];
}
return [{ 'slideX': 0 }, false];
}
static parseSlideYParameter(...args) {
const [parameter, arg] = args;
const value = parseInt(arg, 10);
switch (parameter) {
case 'スライド縦':
case 'slide-y':
return [{ 'slideY': value }, true];
}
return [{ 'slideY': 0 }, false];
}
static parseDurationParameter(...args) {
const [parameter] = args;
const matched = parameter.match(/^(\d+)(フレーム|F)$/);
if (matched) {
return [{ 'duration': parseInt(matched[1], 10) }, true];
}
return [{ 'duration': 0 }, false];
}
static parseWaitParameter(...args) {
const [parameter] = args;
if (parameter === 'ウェイトあり' || parameter === 'wait') {
return [{ 'wait': true }, true];
}
else if (parameter === 'ウェイトなし' || parameter === 'no-wait') {
return [{ 'wait': false }, true];
}
return [{ 'wait': false }, false];
}
static parseShowCommandParameters(parameters) {
const parsers = [
this.parseWaitParameter,
this.parseDurationParameter,
this.parseSlideYParameter,
this.parseSlideXParameter,
this.parseOffsetYParameter,
this.parseOffsetXParameter,
this.parseDistanceParameter,
this.parsePositionParameter,
];
let index = 1;
const parameterObject = parsers.reduce((prev, parser) => {
const parameter = parameters.slice(-index)[0];
const [parsed, consumed] = parser(parameter.value, parameter.option);
if (consumed) {
index++;
}
return {
...prev,
...parsed,
};
}, {
expression: '',
position: 'center',
distance: 'middle',
offsetX: 0,
offsetY: 0,
slideX: 0,
slideY: 0,
duration: 0,
wait: false,
});
const [expression] = index === 1 ? parameters : parameters.slice(0, -index + 1);
if (expression) {
parameterObject.expression = expression.value;
}
return parameterObject;
}
static parseChangeCommandParameters(parameters) {
const parsers = [
this.parseWaitParameter,
this.parseDurationParameter,
];
let index = 1;
const parameterObject = parsers.reduce((prev, parser) => {
const parameter = parameters.slice(-index)[0];
const [parsed, consumed] = parser(parameter.value, parameter.option);
if (consumed) {
index++;
}
return {
...prev,
...parsed,
};
}, {
name: '',
expression: '',
duration: 0,
wait: false,
});
let [nameOrExpression, expression] = index === 1 ? parameters : parameters.slice(0, -index + 1);
if (expression) {
parameterObject.name = nameOrExpression.value;
parameterObject.expression = expression.value;
}
else if (nameOrExpression) {
const chara = TachieManager.findCharacterData(nameOrExpression.value);
if (chara) {
parameterObject.name = nameOrExpression.value;
}
else {
parameterObject.expression = nameOrExpression.value;
}
}
return parameterObject;
}
static parseMoveCommandParameters(parameters) {
const parsers = [
this.parseWaitParameter,
this.parseDurationParameter,
this.parseOffsetYParameter,
this.parseOffsetXParameter,
this.parsePositionParameter,
];
let index = 1;
const parameterObject = parsers.reduce((prev, parser) => {
const parameter = parameters.slice(-index)[0];
const [parsed, consumed] = parser(parameter.value, parameter.option);
if (consumed) {
index++;
}
return {
...prev,
...parsed,
};
}, {
position: 'center',
offsetX: 0,
offsetY: 0,
duration: 0,
wait: false,
});
return parameterObject;
}
static parseEraseCommandParameters(parameters) {
const parsers = [
this.parseWaitParameter,
this.parseDurationParameter,
this.parseSlideYParameter,
this.parseSlideXParameter,
];
let index = 1;
const parameterObject = parsers.reduce((prev, parser) => {
const parameter = parameters.slice(-index)[0];
const [parsed, consumed] = parser(parameter.value, parameter.option);
if (consumed) {
index++;
}
return {
...prev,
...parsed,
};
}, {
slideX: 0,
slideY: 0,
duration: 0,
wait: false,
});
return parameterObject;
}
}
class TachieDataLoader {
static load() {
const xhr = new XMLHttpRequest();
const url = 'data/Tachie.csv';
xhr.open('GET', url);
xhr.onload = (() => {
if (xhr.status < 400) {
TachieManager.onDataLoaded(xhr.responseText);
}
});
xhr.onerror = (() => {
this._errorUrl = this._errorUrl || url;
});
xhr.send();
}
}
class Tachie_Config {
constructor(width, height) {
this.anchorX = 0.5;
this.anchorY = 1.0;
this.xByPosition = {
left: width / 5 * 1,
center: width / 2,
right: width / 5 * 4,
};
this.yByDistance = {
near: height / 3 * 4,
middle: height / 6 * 7,
far: height,
};
this.scaleByDistance = {
near: 100,
middle: 80,
far: 60,
};
this.pictureNumberForDistance = {
near: 30,
middle: 20,
far: 10,
};
this.pictureNumberForPosition = {
left: 8,
right: 6,
center: 0,
};
}
picturePosition(position, distance) {
return {
x: this.xByPosition[position],
y: this.yByDistance[distance],
};
}
pictureNumber(position, distance) {
return this.pictureNumberForDistance[distance] + this.pictureNumberForPosition[position];
}
}
class TachieManager {
static get data() {
return this._data;
}
static get characters() {
return this._characters;
}
static set characters(characters) {
this._characters = characters;
}
static get config() {
return this._config;
}
static isTachieCommand(command) {
switch ((command || '').toUpperCase()) {
case 'SHOW_TACHIE':
case '立ち絵表示':
case 'CHANGE_TACHIE':
case '立ち絵変更':
case 'MOVE_TACHIE':
case '立ち絵移動':
case 'ERASE_TACHIE':
case '立ち絵消去':
return true;
}
return false;
}
static loadDataFile() {
TachieDataLoader.load();
}
static onDataLoaded(response) {
this._data = response.trim().split("\n").map((row) => {
const [filename, name, ...expressions] = row.split(',').map((column) => {
return column.trim();
});
return {
filename,
name,
expressions,
};
});
this.setup();
}
static setup() {
this._config = new Tachie_Config(Graphics.width, Graphics.height);
}
static findCharacterData(name) {
return find(this.data, (chara) => chara.name === name);
}
static findCharacter(name) {
return find(this.characters, (chara) => chara.name === name);
}
static addCharacter(character) {
this._characters.push(character);
}
static removeCharacter(character) {
this._characters.splice(this._characters.indexOf(character), 1);
}
}
TachieManager._characters = [];
class Tachie_CommandProcessor {
constructor(interpreter) {
this._interpreter = interpreter;
}
static normalizeParameters(parameters) {
return parameters.join(' ').trim().replace(/\s+/g, ' ').replace(/: /g, ':').split(' ');
}
static transformParameters(parameters) {
return parameters.map((parameter) => {
const [value, option] = parameter.split(':');
return { value, option };
});
}
processPluginCommand(command, args) {
const [name, ...rawParameters] = args;
const parameters = Tachie_CommandProcessor.transformParameters(Tachie_CommandProcessor.normalizeParameters(rawParameters));
switch ((command || '').toUpperCase()) {
case 'SHOW_TACHIE':
case '立ち絵表示':
this.processShowCommand(name, ...parameters);
break;
case 'CHANGE_TACHIE':
case '立ち絵変更':
this.processChangeCommand(name, ...parameters);
break;
case 'MOVE_TACHIE':
case '立ち絵移動':
this.processMoveCommand(name, ...parameters);
break;
case 'ERASE_TACHIE':
case '立ち絵消去':
this.processEraseCommand(name, ...parameters);
break;
default:
return false;
}
return true;
}
processShowCommand(name, ...args) {
const charaData = TachieManager.findCharacterData(name);
if (!charaData) {
return;
}
const { expression, position, offsetX, offsetY, slideX, slideY, distance, duration, wait, } = TachieCommandParser.parseShowCommandParameters(args);
const chara = {
id: TachieManager.config.pictureNumber(position, distance),
name,
expression,
filename: Tachie_CommandProcessor.filenameWithExpression(charaData, expression),
...TachieManager.config.picturePosition(position, distance),
scale: TachieManager.config.scaleByDistance[distance],
position,
distance,
};
chara.x += offsetX;
chara.y += offsetY;
TachieManager.addCharacter(chara);
if (duration > 0) {
this._showPicture(chara.id, chara.filename, chara.x - slideX, chara.y - slideY, chara.scale, chara.scale, 0);
this._movePicture(chara.id, chara.x, chara.y, chara.scale, chara.scale, 255, duration);
if (wait) {
this._wait(duration);
}
}
else {
this._showPicture(chara.id, chara.filename, chara.x, chara.y, chara.scale, chara.scale, 255);
}
}
processChangeCommand(target, ...args) {
const chara = TachieManager.findCharacter(target);
if (!chara) {
return;
}
const { name, expression, duration, wait, } = TachieCommandParser.parseChangeCommandParameters(args);
const charaData = name !== '' ? TachieManager.findCharacterData(name) : TachieManager.findCharacterData(chara.name);
const filename = Tachie_CommandProcessor.filenameWithExpression(charaData, expression);
const pictureId = chara.id;
if (duration > 0) {
this._showPicture(pictureId + 1, filename, chara.x, chara.y, chara.scale, chara.scale, 0);
this._movePicture(pictureId + 1, chara.x, chara.y, chara.scale, chara.scale, 255, duration);
this._movePicture(pictureId, chara.x, chara.y, chara.scale, chara.scale, 0, duration);
if (wait) {
this._wait(duration);
}
}
else {
this._showPicture(pictureId + 1, filename, chara.x, chara.y, chara.scale, chara.scale, 255);
this._movePicture(pictureId + 1, chara.x, chara.y, chara.scale, chara.scale, 255, 1);
}
this._setHandlerForPictureMoveCompleted(pictureId + 1, () => {
this._showPicture(pictureId, filename, chara.x, chara.y, chara.scale, chara.scale, 255);
this._erasePicture(pictureId + 1);
});
if (name !== '') {
chara.name = name;
}
}
processMoveCommand(name, ...args) {
const chara = TachieManager.findCharacter(name);
if (!chara) {
return;
}
const { position, offsetX, offsetY, duration, wait, } = TachieCommandParser.parseMoveCommandParameters(args);
if (chara.position !== position) {
const oldId = chara.id;
chara.id = TachieManager.config.pictureNumber(position, chara.distance);
const pictureId = chara.id;
const { x, y } = TachieManager.config.picturePosition(position, chara.distance);
chara.x = x + offsetX;
chara.y = y + offsetY;
chara.position = position;
if (duration > 0) {
this._movePicture(oldId, chara.x, chara.y, chara.scale, chara.scale, 255, duration);
this._setHandlerForPictureMoveCompleted(oldId, () => {
this._showPicture(pictureId, chara.filename, chara.x, chara.y, chara.scale, chara.scale, 255);
this._erasePicture(oldId);
});
if (wait) {
this._wait(duration);
}
}
else {
this._showPicture(pictureId, chara.filename, chara.x, chara.y, chara.scale, chara.scale, 255);
this._erasePicture(oldId);
}
}
else {
const pictureId = chara.id;
chara.x += offsetX;
chara.y += offsetY;
this._movePicture(pictureId, chara.x, chara.y, chara.scale, chara.scale, 255, duration);
if (duration > 0 && wait) {
this._wait(duration);
}
}
}
processEraseCommand(name, ...args) {
const chara = TachieManager.findCharacter(name);
if (!chara) {
return;
}
const { slideX, slideY, duration, wait, } = TachieCommandParser.parseEraseCommandParameters(args);
const pictureId = chara.id;
TachieManager.removeCharacter(chara);
if (duration > 0) {
this._movePicture(pictureId, chara.x + slideX, chara.y + slideY, chara.scale, chara.scale, 0, duration);
this._setHandlerForPictureMoveCompleted(pictureId, () => {
this._erasePicture(pictureId);
});
if (wait) {
this._wait(duration);
}
}
else {
this._erasePicture(pictureId);
}
}
_wait(duration) {
this._interpreter.wait(duration);
}
_showPicture(id, name, x, y, scaleX, scaleY, opacity) {
$gameScreen.showPicture(id, name, 2, x, y, scaleX, scaleX, opacity, 0);
}
_movePicture(id, x, y, scaleX, scaleY, opacity, duration) {
$gameScreen.movePicture(id, 2, x, y, scaleX, scaleX, opacity, 0, duration);
}
_erasePicture(id) {
$gameScreen.erasePicture(id);
}
static filenameWithExpression(chara, expression) {
const expressionId = chara.expressions.indexOf(expression);
return `${chara.filename}${expressionId + 2}`;
}
_setHandlerForPictureMoveCompleted(pictureId, handler) {
const picture = $gameScreen.picture(pictureId);
if (picture) {
picture._onMoveCompleted = handler;
}
}
}
const Game_Interpreter_requestImages = Game_Interpreter.requestImages;
Game_Interpreter.requestImages = function (list, commonList) {
if (!list) {
return;
}
list.filter(({ code }) => code === 356).forEach(({ parameters }) => {
const args = parameters[0].split(' ');
const command = args.shift().toUpperCase();
if (command === 'SHOW_TACHIE' ||
command === '立ち絵表示' ||
command === 'CHANGE_TACHIE' ||
command === '立ち絵変更') {
let filename = null;
const [charaName, ...rawParameters] = args;
const parameters = Tachie_CommandProcessor.transformParameters(Tachie_CommandProcessor.normalizeParameters(rawParameters));
if (command === 'SHOW_TACHIE' || command === '立ち絵表示') {
const { expression } = TachieCommandParser.parseShowCommandParameters(parameters);
const character = TachieManager.findCharacterData(charaName);
if (character) {
filename = Tachie_CommandProcessor.filenameWithExpression(character, expression);
}
}
else {
const { name, expression } = TachieCommandParser.parseChangeCommandParameters(parameters);
const character = name ? TachieManager.findCharacterData(name) : TachieManager.findCharacterData(charaName);
if (character) {
filename = Tachie_CommandProcessor.filenameWithExpression(character, expression);
}
}
if (filename) {
ImageManager.requestPicture(filename, 0);
}
}
});
Game_Interpreter_requestImages.apply(this, arguments);
};
}());
このプラグインはキャラクターの立ち絵を表示できるようにします。
スクリーンショットで使われているキャラクター画像、背景画像はプラグインに含まれません。
利用方法
-
以下形式の名前の立ち絵画像ファイルを Picture フォルダに配置します。
characterA1.png characterA2.png characterA3.png characterA4.png characterA5.png characterB1.png characterB2.png : :
-
Data フォルダに Tachie.csv という名前で以下形式の立ち絵定義ファイルを作成してください。
characterA, キャラA, 笑い, 怒り, 呆れ, 不満 characterB, キャラB, 笑い, 緊張, 呆れ, 不満 : :
- 一列目は Picture フォルダに配置した立ち絵画像ファイル名から拡張子と末尾の数字を取り除いた部分です
- 二列目はキャラクターの名前です
プラグインコマンドから立ち絵画像ファイルを指定するときに、この名前で指定します - 三列目以降は、立ち絵画像ファイルの表情の名前です
プラグインコマンドから表情を指定するときに、この名前で指定します
<キャラクター名>2
以降の表情差分に名前をつけてください
(<キャラクター名>1
は表情未指定時に使われます)
上記例では
characterA2.png
がキャラA
の笑い
、characterA3.png
がキャラA
の怒り
という設定になっています。 -
プラグインコマンドに立ち絵表示コマンドを入力して立ち絵を表示を行います。
プラグインコマンドの詳細はプラグインコマンド一覧を参照してください。
プラグインコマンド一覧
立ち絵表示
立ち絵を表示します。
- 呼び出し形式
-
立ち絵表示 <キャラクター名> [<表情> <位置> <距離> <横位置調整> <縦位置調整> <スライド横> <スライド縦> <表示にかける時間> <完了までウェイト>]
- パラメータ
-
キャラクター名
<名前>
立ち絵定義ファイルで設定したキャラクターの名前
表情
[ <名前> ]
立ち絵定義ファイルで設定したキャラクターの表情
未指定で1番目の表情位置
[ 左 | 中 | 右 ]
立ち絵の X 座標を位置に応じた値にする
未指定で中央左
- 立ち絵を画面左側に配置する
中
- 立ち絵を画面中央に配置する
右
- 立ち絵を画面右側に配置する
距離
[ 近 | 中 | 遠 ]
立ち絵の拡大率と Y 座標を距離に応じた値にする
未指定で中距離近
- 立ち絵を画面手前に配置する
中
- 立ち絵を画面中間に配置する
遠
- 立ち絵を画面奥に配置する
横位置調整
[ 横位置調整:<数値> | 横:<数値> ]
立ち絵の X 座標に指定した数値を加える
縦位置調整
[ 縦位置調整:<数値> | 縦:<数値> ]
立ち絵の Y 座標に指定した数値を加える
スライド横
[ スライド横:<数値> ]
立ち絵の表示開始時の X 座標から指定した数値を引く
スライド縦
[ スライド縦:<数値> ]
立ち絵の表示開始時の Y 座標から指定した数値を引く
表示にかける時間
[ <数値>フレーム ]
表示にかける時間をフレーム数で指定する
未指定で瞬間表示完了までウェイト
[ ウェイトあり | ウェイトなし ]
表示が完了するまでウェイトする
未指定でウェイトなし
瞬間表示の場合は無視される - 呼び出し例
-
立ち絵表示 キャラA 近
キャラA
を表情1
で画面中央、近距離の位置に瞬間表示 -
立ち絵表示 キャラA 表情2 左 横:5 縦:-5 スライド横:20 60フレーム ウェイトあり
キャラA
を表情2
で画面左側、中距離の、左方向に5
px、上に5
pxずらした位置に20
px 左にスライドしながら60
フレームかけて表示する
表示が完了するまでウェイトする
立ち絵変更
立ち絵を変更します。
主に、表情差分や衣装差分の切り替えを行うためのコマンドです。
- 呼び出し形式
-
立ち絵変更 <変更前キャラクター名> [<変更後キャラクター名> <表情> <変更にかける時間> <完了までウェイト>]
- パラメータ
-
変更前キャラクター名
<名前>
立ち絵定義ファイルで設定したキャラクターの名前
変更後キャラクター名
[ <名前> ]
立ち絵定義ファイルで設定したキャラクターの名前
未指定で現在と同じキャラクターの表情セットを使う表情
[ <名前> ]
立ち絵定義ファイルで設定したキャラクターの表情
未指定で1番目の表情表示にかける時間
[ <数値>フレーム ]
表示にかける時間をフレーム数で指定する
未指定で瞬間表示完了までウェイト
[ ウェイトあり | ウェイトなし ]
表示が完了するまでウェイトする
未指定でウェイトなし
瞬間表示の場合は無視される - 呼び出し例
-
立ち絵変更 キャラA 表情2 30フレーム
キャラA
を表情2
に30
フレームかけて変更する -
立ち絵変更 キャラA キャラB 30フレーム
キャラA
をキャラB
の表情1
に30
フレームかけて変更する
立ち絵移動
立ち絵を移動します
- 呼び出し形式
-
立ち絵移動 <キャラクター名> [<位置> <横位置調整> <縦位置調整> <移動にかける時間> <完了までウェイト>]
- パラメータ
-
キャラクター名
<名前>
立ち絵定義ファイルで設定したキャラクターの名前
位置
[ 左 | 中 | 右 ]
立ち絵の X 座標を位置に応じた値にする
未指定で中央左
- 立ち絵を画面左側に配置する
中
- 立ち絵を画面中央に配置する
右
- 立ち絵を画面右側に配置する
横位置調整
[ 横位置調整:<数値> | 横:<数値> ]
立ち絵の X 座標に指定した数値を加える
縦位置調整
[ 縦位置調整:<数値> | 縦:<数値> ]
立ち絵の Y 座標に指定した数値を加える
移動にかける時間
[ <数値>フレーム ]
移動にかける時間をフレーム数で指定する
未指定で瞬間移動完了までウェイト
[ ウェイトあり | ウェイトなし ]
移動が完了するまでウェイトする
未指定でウェイトなし
瞬間移動の場合は無視される - 呼び出し例
-
立ち絵移動 キャラA 右 横:-15
キャラA
を画面右側の、右方向に15
pxずらした位置に30
フレームかけて移動させる
立ち絵消去
立ち絵を消去します
- 呼び出し形式
-
立ち絵消去 <キャラクター名> [<スライド横> <スライド縦> <消去にかける時間> <完了までウェイト>]
- パラメータ
-
キャラクター名
<名前>
立ち絵定義ファイルで設定したキャラクターの名前
スライド横
[ スライド横:<数値> ]
立ち絵の消去開始時の X 座標から指定した数値を引く
スライド縦
[ スライド縦:<数値> ]
立ち絵の消去開始時の Y 座標から指定した数値を引く
消去にかける時間
[ <数値>フレーム ]
消去にかける時間をフレーム数で指定する
未指定で瞬間消去完了までウェイト
[ ウェイトあり | ウェイトなし ]
消去が完了するまでウェイトする
未指定でウェイトなし
瞬間消去の場合は無視される - 呼び出し例
-
立ち絵消去 キャラA 30フレーム
キャラA
を30
フレームかけて消去する
よくある質問
Q. CSV を用意しなければいけないのが手間
A. 本プラグインは 差分画像に任意の名前をつけ、プラグインコマンドから名前で参照できるようにする ことを目的としています。
差分画像を管理するにあたってツクールのプラグインパラメータの編集画面は不向きと考えているため、CSV で管理する仕様になっています。
Q. プラグインコマンドは間違っていないのに立ち絵が表示されない
A. プラグインを有効化しても、プロジェクトを保存しないとプラグインコマンドが認識されません。一度プロジェクトを保存してやり直してみてください。
Q. 表情を変更したい
A. 立ち絵変更コマンドを使ってください。
Q. 表示しているキャラの距離を変更したい
A. 消去と表示を組み合わせてください。
Q. 透明度の指定ができない
A. 未実装の機能です。
Q. 同距離、同位置に複数のキャラクターを表示したい
A. 未実装の機能です。
Q. キャラクターの重なり順を制御したい
A. 未実装の機能です。
クレジット(敬称略)
スクリーンショットには下記の素材を利用させていただきました。
キャラクター画像
- RPGツクールMV:パッケージキャラクター素材 by KADOKAWA
- RPGツクールMVパッケージキャラクター:フェイス・カットイン素材 by Vibrato(P3X-774)
背景画像
- ファンタジー フィールド by きまぐれアフター
変更履歴
1.0.0 2018-04-01
- 公開
ライセンス
このプラグインは zlib ライセンス のもと配布されます。
Copyright © 2018 あおいたく / Lunatlazur
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
- This notice may not be removed or altered from any source distribution.