【Scratch拡張機能をJavascriptで自作】svg画像からRPG風のマップを生成する拡張機能を作る〜マップ作成編②


※ 当ページには【広告/PR】を含む場合があります。
2021/02/02
【Scratch拡張機能をJavascriptで自作】svg画像からRPG風のマップを生成する拡張機能を作る〜準備編①
【Scratch拡張機能をJavascriptで自作】svg画像からRPG風のマップを生成する拡張機能を作る〜拡張機能の実装③

前回に引き続き、タイル画像からRPG風マップを構成するためのテクニックを解説していきます。

この記事ではsvgマップの種となるcsvファイルを使って、Base64化したタイルの画像を所定の座標に張り合わせていくようなプログラムを作ります。

合同会社タコスキングダム|タコキンのPスクール
【Scratch拡張機能をJavascriptで自作】svg画像からRPG風のマップを生成する拡張機能を作る〜準備編①

Scratchの自作拡張機能の例として、svg画像からRPGマップを直接内部でレンダリングする高度な拡張機能を作ってみます。


JavaScriptとHTMLで「レトロ風RPG」を作ろう 全コード解説

【Svelte.js入門】ReactやVueに挫折した人でも大丈夫!Svelteとfirebaseでシンプルアプリ開発

合同会社タコスキングダム|タコキンのPスクール【Html&Cssアプリ】Html/JavascriptとSassを使った四択クイズゲームの作り方

タイル画像を張り合わせるスクリプト

SVG画像の利点は、ラスター系のバイナリデータ画像と違って、テキストエディタ等で自在に変形できることが挙げられます。

ですので、タイル画像をテキストとして予め読み込んでおいて、正しい座標に上手く張り合わせることで、全体のマップ画像を生成することが容易にできます。

そこで今回の内容ではチョチョイと適当なスクリプト言語でタイル画像を張り合わせることを念頭においているのですが、世の中のスクリプト言語といえばpython/perl/php...様々な選択肢があり、人によってはそれぞれ得意不得意があると思います。

とはいえ今回のようなスクリプトは座標の計算と文字列を張り合わせる程度ですので、この程度ならばシェルスクリプトの能力で十分作成可能です。

以降ではどのような開発環境でも使えることを考慮して、汎用性の高いシェルスクリプト(bash)を用いたスクリプトを例に説明していきます。


JavaScriptとHTMLで「レトロ風RPG」を作ろう 全コード解説

【Svelte.js入門】ReactやVueに挫折した人でも大丈夫!Svelteとfirebaseでシンプルアプリ開発

合同会社タコスキングダム|タコキンのPスクール【Html&Cssアプリ】Html/JavascriptとSassを使った四択クイズゲームの作り方

タイル張り合わせ用スクリプトの作成

まず前回で使わせて頂いたサンプル用のタイル画像のうちで、草原のタイルと壁のタイルの2つを使って説明します。

合同会社タコスキングダム|タコキンのPスクール

簡単な例として全体の画像を4x4マスとしておいて、以下のようなcsvファイル(
sample.csv)でタイルの配置を定義してみましょう。

            
            1,1,2,1
2,1,2,1
2,2,2,1
1,1,2,2
        
この中身の数値に応じて、各マスに1の時に草原のタイルをセットし、2の時には壁のタイルをセットするようにします。

このcsvファイル(
sample.csv)を読み込み、4x4マスのsvgファイル(sample.svg)を出力するシェルスクリプトは以下のようになります。

            
            #!/bin/bash

CANVAS_WIDTH=96
CANVAS_HEIGHT=96
TILE_SIZE=24

cat << EOF > ./sample.svg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${CANVAS_WIDTH}" height="${CANVAS_HEIGHT}" version="1.1" viewBox="0 0 ${CANVAS_WIDTH} ${CANVAS_HEIGHT}">
<defs>
<image id="tile1" x="0" y="0" width="${TILE_SIZE}" height="${TILE_SIZE}" preserveAspectRatio="none" xlink:href="data:image/png;base64,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"/>
<image id="tile2" x="0" y="0" width="${TILE_SIZE}" height="${TILE_SIZE}" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAABPSURBVEhLYzx8+PB/BhoCxvr6eqItcHFxYdizZw+URxiA1DNB2TQDoxYQBAOTishJLbjUj0YyQTBMUxEuMFoW0QQM01Q0WhYhg6FuAQMDAMbWKtE8sR+/AAAAAElFTkSuQmCC"/>
</defs>
EOF

cat ./sample.csv | awk -F"," '
    BEGIN {
        OFS=",";
        row_count=1;
    }
    {
        for(i=1;i<=NF;i++) {
            print i, row_count, $i;
        }
        row_count++;
    }
' | awk -F"," -v tile_size=${TILE_SIZE} '
    BEGIN {
        row_count=0;
    }
    {
        y_pos = tile_size*($2 - 1);
        x_pos = tile_size*($1 - 1);
        use_arr[row_count] = "<use xlink:href=\\\"#tile" $3 "\\\" x=\\\"" x_pos "\\\" y=\\\"" y_pos "\\\"/>";
        row_count++;
    }
    END {
        bundle="";
        for (i=0;i<row_count;i++) {
            bundle = bundle use_arr[i] "\\\n";
            #print use_arr[i];
        }
        bundle = bundle "</svg>";
        print bundle;
    }
' | xargs echo >> ./sample.svg
        
という内容で最低限のスクリプトの名前をcsv2map.shとでもしておいて、sample.csvと同じフォルダに置いておきます。

では早速これを現在のフォルダ上でターミナルを開き、実行させてみますと、

            
            $ chmod +x csv2map.sh
$ ./csv2map.sh
$ cat sample.svg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="96" height="96" version="1.1" viewBox="0 0 96 96">
<defs>
<image id="tile1" x="0" y="0" width="24" height="24" preserveAspectRatio="none" xlink:href="data:image/png;base64,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"/>
<image id="tile2" x="0" y="0" width="24" height="24" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAABPSURBVEhLYzx8+PB/BhoCxvr6eqItcHFxYdizZw+URxiA1DNB2TQDoxYQBAOTishJLbjUj0YyQTBMUxEuMFoW0QQM01Q0WhYhg6FuAQMDAMbWKtE8sR+/AAAAAElFTkSuQmCC"/>
</defs>
<use xlink:href="#tile1" x="0" y="0"/>
<use xlink:href="#tile1" x="0" y="24"/>
<use xlink:href="#tile2" x="0" y="48"/>
<use xlink:href="#tile1" x="0" y="72"/>
<use xlink:href="#tile2" x="24" y="0"/>
<use xlink:href="#tile1" x="24" y="24"/>
<use xlink:href="#tile2" x="24" y="48"/>
<use xlink:href="#tile1" x="24" y="72"/>
<use xlink:href="#tile2" x="48" y="0"/>
<use xlink:href="#tile2" x="48" y="24"/>
<use xlink:href="#tile2" x="48" y="48"/>
<use xlink:href="#tile1" x="48" y="72"/>
<use xlink:href="#tile1" x="72" y="0"/>
<use xlink:href="#tile1" x="72" y="24"/>
<use xlink:href="#tile2" x="72" y="48"/>
<use xlink:href="#tile2" x="72" y="72"/>
</svg>
        
というsvg画像が生成されていたら成功です。

なお、タイルの画像の扱いに関しては
前回の記事でも解説しましたが、スクリプト内のdefs要素に予めBase64のインライン画像として仕込んでおく必要があります。

ここではあくまで簡単な例を挙げるために2つの画像(
id="tile1"が草原のタイル、id="tile2"が壁のタイル)として登録しております。

タイルの画像の種類を増やす場合は、
defs要素にbase64形式でテンプレート追加していくことで拡張していくことができます。


JavaScriptとHTMLで「レトロ風RPG」を作ろう 全コード解説

【Svelte.js入門】ReactやVueに挫折した人でも大丈夫!Svelteとfirebaseでシンプルアプリ開発

合同会社タコスキングダム|タコキンのPスクール【Html&Cssアプリ】Html/JavascriptとSassを使った四択クイズゲームの作り方

Scratchのレンダラーに描画

現時点では拡張機能のプログラミングが出来ていないので、スクリプトで組み上げたSVG画像を読み込めるか否かの確認はレンダラーだけを使うという選択肢があります。

ということで先程のスクリプトで吐き出したマップがスクラッチアプリ上で正しく表示されるかは、スクラッチ3のコアライブラリの一つである
scratch-renderを使ってサッと確認してみます。

なお、scratch-renderの立ち上げ・ビルド方法などは以前の記事で特集していますので、お手元で動作確認する場合にはご参考ください。

合同会社タコスキングダム|タコキンのPスクール
Scratchのディープな使い方〜レンダラー(scratch-render)だけを動かしてみる

Scratch-guiから、文字もしくは文字列からユニコード(16進数)に変換するための拡張機能を自作します。

今回のsvg画像を手っ取り早く試すのであれば、前回のscratch-renderで試したプロジェクトの
main.jsにsvgのインラインコードをそのままハードコードするだけで動作できます。

合同会社タコスキングダム|タコキンのPスクール

ハードコードするのが嫌でファイルとしてインポートしたい場合には、webpack側に
raw-loaderを仕込んで読み出すことも可能です。


JavaScriptとHTMLで「レトロ風RPG」を作ろう 全コード解説

【Svelte.js入門】ReactやVueに挫折した人でも大丈夫!Svelteとfirebaseでシンプルアプリ開発

合同会社タコスキングダム|タコキンのPスクール【Html&Cssアプリ】Html/JavascriptとSassを使った四択クイズゲームの作り方

まとめ

今回はタイルマップのシードファイル(csvファイル)とそこから生成済みのタイルマップ(svgファイル)の2つの構築方法を解説してみました。

この時点でも、この2つのファイルを使って、タイルマップを背景画像に、シードファイルをリストにセットするなどしてもRPG風なゲームが作れそうな気がします。

ここで満足されても良いのですが、よりスクラッチプログラミングの高みを目指すのが当初の目的でもあるので、次回以降では、このファイルを使って拡張機能化を図りたいと思います。