0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Data URL使ってセーブ画面にサムネイル表示したった【RPGツクールMZ】

Last updated at Posted at 2022-02-21

「セーブデータとセーブ画面・ロード画面の変更」プラグイン備忘録

通称「自分用セーブプラグイン」
ソースコードは以下にて公開中。
自分用なので他者環境で動作するかは不明。

サムネイル表示

2022年2月21日にサムネイル表示を追加した。
本書はその際のやったことメモである。

save.png

機能概要

セーブデータごとにセーブ時のマップ画像を表示する機能である。

必要な処理

サムネイル表示に必要な処理は以下である。

  • 画面キャプチャする。
  • 画面キャプチャを保存する。
  • 画面キャプチャを読込する。
  • 画面キャプチャを表示する。

動作確認編

まずは各処理を作りながら動作確認をしていく。

画面キャプチャする

画面キャプチャは以下の関数がコアスクリプト(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) {
			//
		}
	}
};

以上。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?