3-4 開発ツールを作る

本格的なゲームは様々なツールを使って開発されます。 例えばゲームの世界を作るためのマップエディタや、キャラクターの動作を設定するツールなどです。 ゲーム開発用のツールは主にC++などの言語で作られたWindows上で動くソフトウェアですが、JavaScriptでも開発ツールを作ることができます。 本項ではゲーム制作でよく使われる「マップエディタ」の作り方を紹介します。 そのエディタで作ったマップ上でキャラクターを動かすソースコードも用意しました。


(1)マップエディタを作る

2Dタイプのゲームのマップ(例:RPGのフィールドやダンジョン)をデザインするツールです。 画面右に並んだマップチップをクリックして選択(赤枠が付く)、左側の領域をクリックするとそこにチップが置かれます。 クリックしたままマウスを移動させ、連続してチップを置くこともできます。
今回は横40マス×縦30マスの領域をデザインできるようにしてあります。 画面に見えているのは15×15マスの範囲で、カーソルキーでこの範囲を動かすことができます。 マップ全体の領域と現在見えている範囲が右下に表示されます。 Aキーを押すと見えている範囲を選択しているチップで埋めることができます。 「出力」をクリックするとマップのデータが画面下部のテキストエリアに出力されます。

ピンクの枠の部分が canvas 、水色の枠の部分が textarea で構成されています。

mapEditor.html ← 動作の確認 ※パソコンでご覧下さい
ソースコードの確認 ※HTMLと2つのJavaScriptのファイルです

一般的な開発ツールは、作ったデータをパソコンに保存し、読み込むことができます。 JavaScriptはローカル(パソコン)上のファイルの読み込みには対応していますが、 2018年現在、ローカルにファイルを保存する処理に正式には対応していません。 (色々なテクニックを使いローカルに保存する方法はあり、それらを解説するサイトもあります) 本講座はソースコードを複雑にしないよう、マップデータをテキストエリアに出力し、コピペで利用するようにしました。 マップデータの利用の仕方は下の(2)をご覧下さい。


マップチップの画像を何種類読み込むか、画像のサイズ、マップの縦横のマスの数を、次の変数で定義しています。 プログラム実行後に値を変えることのないこのような変数を定数といいます。 プログラミングでは定数は全て大文字で記述するのが一般的です。
定数名用途
CHIP_MAXマップチップを何種類読み込むか
CHIP_SIZEマップチップの画像サイズ(ドット数)
FIELD_Wマップは横に何マスあるか
FIELD_Hマップは縦に何マスあるか
今回はこれらの値を me_data.js というファイルで定義し、 マップエディタの処理は ms_program.js に記述しました。
例えばワールドマップは200×200マス、町の中は50×50マスでデザインする場合、me_data.js 内の定数だけを書き換えます。 なぜこのように定数を別ファイルとしたのでしょうか? それは変更する可能性のある箇所と、変更する必要の無いソースコードを分けることで、 後々の変更作業が楽になり、また重要な処理の部分を間違えて書き換えてしまうようなミスを防ぐためです。

2Dタイプのゲームの背景は、一般的にマップチップと呼ばれるパーツを並べて構成されます。 今回は14枚のマップチップを読み込んでいます。

※JavaScriptで複数の画像を扱う場合は 1-8 で説明しましたように、 画像をできるだけ1枚のファイルにまとめ、切り出し表示する方法が安全ですが、 本項ではシンプルで判りやすいソースコードを提供する意味で、画像をまとめていません。


画像のファイル名は 000.png から 013.png まで連番になっています。 連番のファイルを扱うために「数字を指定する桁数にする関数」を用意しています。

function placeNum( val, pla ) {
 return ("0000000000"+val).slice(-pla);
}

slice命令は、文字列のn番目の位置からその文字列の最後までを切り出します。
例1) "0123456789".slice(7) → "789"
例2) "あいうえお".slice(3) → "えお"
引数にマイナスの値を与えると、文字列の最後から数えてn個を切り出します。
例3) "0123456789".slice(-3) → "789"
例4) "あいうえお".slice(-2) → "えお"


(2)マップ上でキャラクターを動かす

マップエディタで作ったマップ上でキャラクターを動かすサンプルです。 カーソルキーでキャラクターを4方向に動かすことができます。
moveChr.html ← 動作の確認 ※パソコンでご覧下さい
ソースコードは次のようになります。
01<!DOCTYPE html>
02<html lang="ja">
03<head>
04<meta charset="utf-8">
05<title>キャラクターの移動</title>
06</head>
07<body style="text-align:center; background-color:black;">
08<canvas id="bg"></canvas>
09
10<script src="me_data.js"></script>
11<script src="me_worldmap.js"></script>
12
13<script>
14
15//キャンバスの設定
16var winW = window.innerWidth;
17var winH = window.innerHeight;
18var canvas = document.getElementById("bg");
19var cnt = canvas.getContext("2d");
20var SCALE;
21var CWIDTH  = 480;
22var CHEIGHT = 480;
23if( winH < winW*CHEIGHT/CWIDTH )
24 winW = toInt(winH*CWIDTH/CHEIGHT);
25else
26 winH = toInt(CHEIGHT*winW/CWIDTH);
27canvas.width = winW;
28canvas.height = winH;
29SCALE = winW / CWIDTH;
30cnt.scale( SCALE, SCALE );
31cnt.textAlign = "center";
32cnt.textBaseline = "middle";
33
34//キー入力
35var key = 0;
36window.onkeydown = function(event) { key = event.keyCode; }
37window.onkeyup = function(event) { key = 0; }
38
39function toInt( val ) {//整数を返す関数
40 return parseInt(val);
41}
42
43function placeNum( val, pla ) {//数字を指定する桁数にする関数
44 return ("0000000000"+val).slice(-pla);
45}
46
47//画像ファイルの処理
48var img = [], imgPre = [];
49
50function loadImg( n ) {//画像を読み込む
51 imgPre[n] = false;
52 img[n] = new Image();
53 img[n].src = "chip/" + placeNum(n,3) + ".png";
54 img[n].onload = function() { imgPre[n] = true; }
55}
56
57function drawChip( cn, dx, dy ) {//マップチップを表示
58 if( imgPre[cn] == true ) cnt.drawImage( img[cn], dx, dy );
59}
60
61function drawChr( cn, dx, dy, dir ) {//キャラクターを表示
62 if( imgPre[cn] == true ) {
63  var sx = 32*(toInt(tmr/4)%2);
64  var sy = 48*dir;
65  cnt.drawImage( img[cn], sx, sy, 32, 48, dx, dy, 32, 48 );
66 }
67}
68
69//変数の宣言
70var idx = 0, tmr = 0;
71var plX = 7, plY = 7, plD = 0;
72
73function getChip( x, y ) {//マップチップの値を返す
74 if( x < 0 || x >= FIELD_W || y < 0 || y >= FIELD_H ) return 4;
75 return MAPDAT[x+y*FIELD_W];
76}
77
78window.onload = mainProc();
79function mainProc() {
80 var i, x, y;
81 tmr ++;
82
83 switch( idx ) {
84  case 0://初期化処理
85  for( i = 0; i < CHIP_MAX+1; i ++ ) loadImg(i);
86  idx = 1;
87  break;
88
89  case 1:
90  //キャラクターの移動
91  if( key == 38 ) { plD = 0; if( getChip(plX,plY-1) <= 3 ) plY --; }
92  if( key == 40 ) { plD = 1; if( getChip(plX,plY+1) <= 3 ) plY ++; }
93  if( key == 37 ) { plD = 2; if( getChip(plX-1,plY) <= 3 ) plX --; }
94  if( key == 39 ) { plD = 3; if( getChip(plX+1,plY) <= 3 ) plX ++; }
95  //フィールドを描く
96  for( y = 0; y < 15; y ++ ) {
97   for( x = 0; x < 15; x ++ ) drawChip( getChip(plX+x-7,plY+y-7), x*CHIP_SIZE, y*CHIP_SIZE );
98  }
99  drawChr( CHIP_MAX, 7*CHIP_SIZE, 7*CHIP_SIZE-16, plD );//勇者の表示
100  break;
101 }
102
103 setTimeout( mainProc, 120 );
104}
105
106</script>
107<br>
108<a href="../../jsh5_034.html">講座に戻る</a>
109</body>
110</html>
※10行で読み込んでいる me_data.js は(1)と共通のものです。

(1)のエディタでは map[y][x] という二次元の配列で管理していたマップデータを me_worldmap.js で var MAPDAT = [ 4,4,4,4,4,‥‥‥‥‥ ]; と一次元の配列で定義しています。 エディタで作ったマップデータ(テキストエリアに出力したもの)をコピー&ペーストでこの部分と差し替えれば、 新しいマップ上でキャラクターを動かすことができます。

今回のソースコードでは、MAPDATの一次元配列から、座標(x,y)の位置のチップの値を返す、次のような関数を用意しています。 配列外の範囲は山のマップチップの値 4 を返すようにしています。

function getChip( x, y ) {
 if( x < 0 || x >= FIELD_W || y < 0 || y >= FIELD_H ) return 4;
 return MAPDAT[x+y*FIELD_W];
}


今回のソースコードのキャラクターの移動処理は 2-4(2) で解説した内容より簡略化してあること、 今回は画面をスクロールさせることができることを補足しておきます。

次のリンクから本項で扱っているファイル一式をダウンロードできますので、 ぜひ新しいマップを作り、そのマップ上でキャラクターを動かしてみて下さい。
◆マップエディタ&キャラ移動のソース一式
※クリックでダウンロードできないブラウザでは、右クリックで「対象をファイルに保存」して下さい


前のページへ

お気軽にお問い合わせ下さい →