LoginSignup
2
0

More than 1 year has passed since last update.

ラストのまほう 第4話『画像の描画』

Last updated at Posted at 2022-12-03

ドット絵が描けましたので、いよいよそのドット絵をWASM-4で描画してみましょう。画像を出さないことにはゲーム開発が始まった感じがしません。

WASM-4で画像を読み込む

WASM-4はWASMの1モジュールにゲームのすべてを収めなければならず、ファイルシステムにアクセスすることはできません。外部ファイルの画像を読み込むようなことはできませんから、画像をコードに変換してゲームに組み込みます。

WASM-4にはそのためのコマンドラインツール w4 png2srcが備わっており、PNG画像を各種のプログラミング言語のソースコードへと変換することができます。Rustの場合は --rustオプションを付けて次のように実行します。

$ w4 png2src --rust player.png

こうすると、次のようなソースコードが標準出力に吐き出されます。

// player
const PLAYER_WIDTH: u32 = 8;
const PLAYER_HEIGHT: u32 = 16;
const PLAYER_FLAGS: u32 = BLIT_2BPP;
const PLAYER: [u8; 32] = [ 0x00,0x00, ... ,0x04,0x10 ];

ここでは、PLAYERという32バイトの配列に画像データが入っているわけです。1ピクセルが2ビットで、この画像の大きさは8x16ピクセルなので、2bit * 8 * 16 = 156bit = 32bytesというわけです。これをコピペして適当なソースファイルに貼り付ければいいわけです。

テンプレートファイルを使う

画像はたくさんあるので、私は画像ごとに個別のクレートにわけるようにしています。しかし、上記の素のコマンドで生成されるコードでは単なるconstの変数に値が入っているので、クレートの外部からこのデータにアクセスできません。そんなわけでpub constにしたいです。また、画像の幅や高さ、ピクセルデータがバラバラだとuseが面倒なので、画像の幅や高さの情報もまとめた画像を表すデータ型 Imageとして取り扱いたいです。そんなときは、テンプレートファイルで生成する内容をカスタマイズできます。テンプレートファイルはmustacheです。私は次のように書きました。

{{#sprites}}
// {{name}}
use crate::wasm4::*;
use crate::image::Image;

const {{rustName}}_WIDTH: u32 = {{width}};
const {{rustName}}_HEIGHT: u32 = {{height}};
const {{rustName}}_FLAGS: u32 = {{flagsHumanReadable}};
const {{rustName}}: [u8; {{length}}] = [ {{bytes}} ];

pub const {{rustName}}_IMAGE: Image = Image {
    width: {{rustName}}_WIDTH,
    height: {{rustName}}_HEIGHT,
    flags: {{rustName}}_FLAGS,
    data: &{{rustName}},
};
{{/sprites}}

シェルスクリプトでなるべく自動化する

また、実際のゲーム開発では一度描いたドット絵を修正することはよくあります。そのたびに毎回コピーアンドペーストするのは面倒なので、コマンド一発ですべての画像に対してw4 png2srcを実行するシェルスクリプトで自動化することにしました。こんな感じです。

#!/bin/bash
for filename in ./image/*.png; do
    base=$(basename "$filename")
    name=${base%.*}
    out="./src/image/${name}.rs"
    w4 png2src --rust "$filename" --template template.mustache > "$out"
done

これで、画像を変更したり追加しても、シェルスクリプト一発でクレート化できます。

画像を描画する

あとはこの画像データをblit関数で描画するだけです。私の場合は、先述の Image構造体に drawメソッドを生やして、そこで描画するようにしました。

impl Image {
    pub fn draw(&self, x: i32, y: i32, flags: u32) {
        wasm4::blit(self.data, x, y, self.width, self.height, self.flags | flags);
    }
}

ちなみに、気をつけないといけないのは、描画色はまた別で指定しなければならないことです。画像の各ピクセルは03のインデックスを持っていますが、これがそのままバッファに書き込まれるわけではありません。DRAW_COLORSポインタの先にには、この0から3のインデックスの値が、カラーパレットのどの値として描き込まれるかの対応関係が入っています。この描画色の設定をしておかないと、意図したとおりに描画されません。次のような関数を用意しておくと便利です。

pub fn set_draw_color(idx: u16) {
    unsafe { *wasm4::DRAW_COLORS = idx }
}

画像を描画するときは、次のようにset_draw_colorを呼び出してから描画を行います。

set_draw_color(0x3210);
image.draw(x, y, 0);

ここでは、

  • 画像のインデックス00(透明色)、
  • 画像のインデックス1はカラーパレットの1
  • 画像のインデックス2はカラーパレットの2
  • 画像のインデックス3はカラーパレットの3

となるように指定していて、つまり画像のインデックス0のピクセルは透明色なので実際には描画されません。画像のピクセルは03の4つの値を取りますが、描画色は0(透明色)とカラーパレットの14までで合計5つの値をとるので、その対応関係をコード上で指定しなければならないわけです。ちょっと面倒ですね……。

次回予告

このあたりでざっとWASM-4のAPI全体を眺めてみます。とはいっても関数12個しかないので簡単です。

2
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
2
0