「セーブデータとセーブ画面・ロード画面の変更」プラグイン備忘録
通称「自分用セーブプラグイン」
ソースコードは以下にて公開中。
自分用なので他者環境で動作するかは不明。
サムネイル表示
2022年2月21日にサムネイル表示を追加した。
本書はその際のやったことメモである。
機能概要
セーブデータごとにセーブ時のマップ画像を表示する機能である。
必要な処理
サムネイル表示に必要な処理は以下である。
- 画面キャプチャする。
- 画面キャプチャを保存する。
- 画面キャプチャを読込する。
- 画面キャプチャを表示する。
動作確認編
まずは各処理を作りながら動作確認をしていく。
画面キャプチャする
画面キャプチャは以下の関数がコアスクリプト(rmmz_managers.js)内で定義されているので、これを使用する。
SceneManager.snap()
この snap 関数を使うことで画面キャプチャした画像(Bitmap クラスのインスタンス)が得られる。
画面キャプチャを表示する
Bitmap クラスは blt 関数で表示することができる。
blt 関数はコアスクリプト(rmmz_core.js)内で定義されている。
その blt 関数内では drawImage 関数が使用されている。
Bitmap.prototype.blt()
CanvasRenderingContext2D.drawImage()
画面キャプチャした画像を、まずは試しに blt 関数を使用した。
問題なく表示できた。
画面キャプチャを保存する
画面キャプチャできた、その画像の表示もできたとなると、次は画像の保存である。
今回は、セーブ画面でのサムネイル表示に使うため、SavefileInfo に含めることで保存する。
以下の関数をオーバーライドし、 info に値を追加することとする。
DataManager.makeSavefileInfo()
さて、ここで問題が起こる。
Bitmapクラスのインスタンスを追加した所、セーブできない状態となった。
詳細は未確認だが、推測では、Bitmapクラスのインスタンスが大きすぎる、インスタンスを保存できていない等が思いつく。
つまり、Bitmapクラスのインスタンスを info 内に追加するのは難しいように感じる。
調査編
この時点で着目すべき点は以下である。
- SavefileInfo に含める必要がある。
- Bitmap は保存できないと思われる。
ここで、前述の blt 関数と内部の drawImage 関数を見たところ、drawImage 関数の引数として使うのは、canvas または image のデータである。
canvas または image のデータは、Bitmap 内の一部データなので、Bitmapよりもデータサイズが小さいと考えられる。
そこで、「Bitmap を blt 関数で表示する処理」を「canvas を drawImage 関数で表示する処理」に変え、これでも問題なく表示されることを確認した。
しかし、保存がうまくいかない。画像データが保存されていないような状況であった。
その時、Data URL という方法を知る。
Data URL によって、画像を Base64 でエンコードした文字列として、コード内で完全に定義できます。
HTMLCanvasElement.toDataURL()
Base64エンコードとは、画像などのバイナリデータを文字列に変える方法である。
つまり、文字列であれば、SavefileInfo に含めることができる可能性がある。
これを試した所、ファイルサイズが大きくなるが保存に成功した。
解決編
この時点で残る問題は以下である。
- セーブファイルサイズが大きい。
画面キャプチャする
セーブファイルサイズを小さくするため、jpeg を使うこととし、画質も落とした。
尚、snap 関数は改造しているが内容は割愛する。
Scene_Base.prototype.mapImage = function() {
if (USE_MAP_IMAGE) {
const scale = 0.125;
const width = Math.floor(Graphics.boxWidth * scale);
const height = Math.floor(Graphics.boxHeight * scale);
const bitmap = SceneManager.snapWH(width, height);
const image = new Image(width, height);
image.src = bitmap.canvas.toDataURL("image/jpeg", 0.1);
$gameTemp._mapImage = image;
} else {
$gameTemp._mapImage = null;
}
};
画面キャプチャを行うタイミングは、Scene_Map クラスの stop 関数とした。
具体的には、Scene_Menu および Scene_Battle への遷移前である、
このタイミングで画面キャプチャしてもその後にセーブしない場合が多いと思われるが、事前に取得しておかないと取り逃してしまうので、このタイミングとした。
尚、場所移動時のオートセーブは、タイミングの都合上、真っ暗の画面キャプチャか、移動前マップの画面キャプチャとなるので、今回はキャプチャなしとした。
補足であるが、Scene クラスのライフサイクルは initialize → create → start → update → stop → terminate となっている。
const KRD_Scene_Map_stop = Scene_Map.prototype.stop;
Scene_Map.prototype.stop = function() {
if (!SceneManager.isNextScene(Scene_Map)) {
this.mapImage();
} else {
$gameTemp._mapImage = null;
}
KRD_Scene_Map_stop.apply(this, arguments);
};
画面キャプチャを保存する
画面キャプチャの保存は info への追加により行う。
画像データとして src, width, height の3つが必要なので、それぞれ値を設定する。
const KRD_DataManager_makeSavefileInfo = DataManager.makeSavefileInfo;
DataManager.makeSavefileInfo = function() {
const info = KRD_DataManager_makeSavefileInfo.apply(this, arguments);
info.mapName = this.getMapName();
info.version = this.getVersion();
info.mapImage = this.getMapImage();
return info;
};
DataManager.getMapImage = function() {
const result = $gameTemp._mapImage ?
{
src: $gameTemp._mapImage.src,
width: $gameTemp._mapImage.width,
height: $gameTemp._mapImage.height,
} : null;
$gameTemp._mapImage = null;
return result;
};
画面キャプチャを読込する
画像の読込は非同期で行われるため、確実に表示するには待つ必要がある。
addEventListener がそのための処理である。
Window_SavefileList.prototype.drawContents = function(info, rect) {
if (USE_MAP_IMAGE && info.mapImage) {
const image = new Image(info.mapImage.width, info.mapImage.height);
image.src = info.mapImage.src;
image.addEventListener("load", element => {
this.drawMapImage(rect, image);
this.drawMainContents(info, rect);
});
} else {
this.drawMainContents(info, rect);
}
};
画面キャプチャを表示する
drawImage 関数を使って Image クラスの画像を表示する。
尚、try 文になっているのは、コアスクリプトを真似たためである。
Window_SavefileList.prototype.drawMapImage = function(rect, image) {
if (image) {
try {
const sx = 0;
const sy = 0;
const sw = image.width;
const sh = image.height;
const dw = sw;
const dh = sh;
const dx = rect.x + rect.width - dw;
const dy = rect.y + Math.floor((rect.height - dh) / 2);
this.contents.context.globalCompositeOperation = "source-over";
this.contents.context.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
this.contents._baseTexture.update();
} catch (exception) {
//
}
}
};
以上。