週刊アスキー

  • Facebookアイコン
  • Twitterアイコン
  • RSSフィード

enchant.jsでつくる“シロートが3時間でゲームをつくる”作り方

2012年03月03日 17時00分更新

文● シバタススム 編集●イッペイ イラスト●サダツグ

9leap高校のメンバーがゲームを解説
ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ
↑本誌連載記事『9leap高校ゲーム開発部』のメンバーがソースコードを解説。
ダースー
ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ
ゲーム開発部の2年生で部長。四六時中マスクをかぶっており、どこの方言だか不明な独特のなまりがあるが、プログラミングテクはなかなかのもの。本名は須田。
安藤ルービン先生
ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ
ゲーム開発部の2代目顧問。怪しげなお面をかぶっているが変質者ではない。毎週、部員達にゲームに関するお題を出す。口癖は「プログラミングやらないか?」。
シュウ君
ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ
ゲーム開発部の1年生。ゲームが好きでアス君と一緒に入部したものの、プログラミング経験はない初心者。しゃべり方はやや乱暴でいい加減に見えるが、プログラミングに関する勉強は熱心だ。
アス君
ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ
普段からゲーム作りにいそしむプログラミング好きなゲーム開発部の1年生。ハイパーオリンピックなど古いゲームの造詣が深く、おっさん受けがいい。萌えキャラなどの秋葉系文化も大好き。
委員長
ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ
実は部員ではないものの、暇なときはゲーム開発部に入り浸っている。絵を描くのが好きで、たまにゲームキャラのデザインを任されたりする。ダースーが作った『いいんちょ+』というゲームのせいで、性格が悪いと勘違いされているのが悩み。

■安藤ルービン先生の今週のお題

「週刊アスキー3月6日号(2月21日発売)掲載の特集『ゲームエンジンを使えば超簡単っ!! 3時間でスマホゲームを作る』に掲載されたソースコードを解説してみないか?」

ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ

ダースー:よっす! みんな元気にプログラミングしているずらか? 今日はワスたちと一緒にゲームエンジン“enchant.js”を使ったプログラミングについて勉強するずら~。ソースコードが長くて、全部読むのはくじけそうになるけど、記事の最後にはお宝画像があるから頑張るずら!

アス君:お宝画像って……?

ダースー:委員長のソロ壁紙ずら。

委員長:ふぇ~。勝手に配らないでくださいよぉ~。

シュウ君:掲載したゲーム『コインスター拾い』は9leapで遊べるぜ。そんで自分でソースコードを改造するなら、開発支援ツール『jsdo.it』でアカウントを作って、“Fork”して自分の編集画面にコピーにしてくれよな! JavaScriptタブのソースコードの数字をちょっといじるだけでも勉強になるぜ。

『コインスター拾い』
ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ
↑落ちてくる爆弾を避けながら、コインやスターを拾ってスコアーを稼ごう! スマホではタッチ操作、PCではマウスのドラッグ操作で遊べる。特集では57行で記載したが、今回はウェブ用に75行で見やすくまとめている。
開発支援ツール『jsdo.it』
ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ
↑ほかの人が作ったプログラムを“Fork”ボタンでコピーして、自分の編集ページで改変できる開発支援ツール。テキストエディターがなくてもウェブサイト上で直接編集でき、プレビュー画面でゲームの挙動もチェックできるぞ。

JavaScriptで書かれたソースコードはたったの75行

window.onload = function() {     game = new Game(320, 320);     game.fps = 24;     game.life = 3;     game.time  = 60;     game.score = 000;     var label;     game.preload(['http://jsrun.it/assets/k/r/t/X/krtXz.gif','http://jsrun.it/assets/v/1/a/l/v1alF.gif','http://jsrun.it/assets/e/B/C/G/eBCGr.gif']);     game.onload = function() {         bear = new Sprite(32, 32);         bear.image = game.assets['http://jsrun.it/assets/k/r/t/X/krtXz.gif'];         bear.x = 160;         bear.y = 200;         bear.frame = 0;         game.rootScene.addChild(bear);         label = new Label("");         label.x =0;         label.y =17;         game.rootScene.addChild(label);         game.time = game.fps * game.time;         game.rootScene.addEventListener('touchmove', function(move){             bear.y = move.localY -50;             bear.x = move.localX -20;         });         count = 0;         game.rootScene.addEventListener('enterframe',function(){             if(game.frame % 4 == 0){ bear.frame = game.frame % 3;}             if(game.frame % 10 == 0){                 if (game.frame % 3 == 0) {additem(14);}                 else if (game.frame % 7 == 0) {additem(30);}                 else{additem(24);}                 label.text = "スコア" + game.score + "<br>残り時間" + Math.floor(game.time / game.fps) +"秒";                 }             game.time --;             game.frameCount ++;             if (game.life == 0){game.end(game.score, "あなたのスコアは" + game.score);}             if (game.time == 0){game.end(game.score, "あなたのスコアは" + game.score);}});             setLifes();             }     game.start(); } function additem(item_frame){     var item = new Sprite(16, 16);     item.image = game.assets['http://jsrun.it/assets/v/1/a/l/v1alF.gif'];     item.x = rand(304);     item.y = 0;     item.frame = item_frame;     item.addEventListener('enterframe', function() {         if(this.intersect(bear)){             if (item.frame == 14) {//CASE1                 game.rootScene.removeChild(this);                 game.score +=  10;             }             else if (item.frame == 30) {//CASE2                 game.rootScene.removeChild(this);                 game.score +=  50;             }             else if (item.frame == 24){//CASE3                 game.rootScene.removeChild(this);                 game.life --;                 life.width = 16 * game.life;}         }else{this.y += 2;}});     count++;     game.rootScene.addChild(item); } function rand(num){ return Math.floor(Math.random() * num); } function setLifes(){     life = new Sprite(16 * game.life,16);     life.image = game.assets['http://jsrun.it/assets/e/B/C/G/eBCGr.gif'];     life.set = function(num){         game.life = num;         this.width = 16 * 3;}     game.rootScene.addChild(life); }

ダースー:ゲームの骨格は、1フレーム毎にどんな処理をするのか、ユーザーが操作するクマと、特定の条件で出現するアイテムについて書いていくずら。

アス君:最初はゲームの初期設定ですよね?

ダースー:そうずら。まずは、これを見てくれずら。

window.onload = function() {     game = new Game(320, 320);     game.fps = 24;     game.life = 3;     game.time  = 60;     game.score = 000;     var label;     game.preload(['http://jsrun.it/assets/k/r/t/X/krtXz.gif','http://jsrun.it/assets/v/1/a/l/v1alF.gif','http://jsrun.it/assets/e/B/C/G/eBCGr.gif']);

 1行目の“enchant();”はおまじないみたいなもんずら。enchant.jsを使ってゲームを作る場合、必ず入れる決まりになっているずらよ。そして、“window.onload = function()”でゲームの画面を作っているずらけど、これもおまじないずらね。“game = new Game(320, 320);”はゲームの横と縦のピクセル数を指定しているずら。320ピクセルの正方形ずらね。9leapに投稿する場合は、スマホで遊ぶことも考えて、320×320ピクセルがオススメずらよ。“game.fps = 24;”はゲームのフレームレートずら。1秒間に24コマの処理をするように指定しているずら。

“game.life = 3;”はゲームのライフが3つという意味で、“game.time  = 60;”はタイムリミットが60秒という意味ずら。“game.score = 000;”は初期スコアーずら。こんなふうに指定しておくと、ゲームの難易度が簡単に調整できるずら。“var label;”はスコアーと制限時間をゲーム内に表示するための初期設定ずらね。

“game.preload”はやたらと長いURLが入ってるずらけど、ゲームで使う画像をあらかじめメモリーにロードする命令ずら。それぞれ、クマのキャラ画像、コイン、星、爆弾の画像、ライフに使用するハートの画像ずら。最初に読み込むぶん、ゲームの起動時間が遅くなるけど、アクションゲームだとその都度画像をロードするとゲームの挙動全体が遅くなるから、ここでやっておくずらよ。

game.onload = function() {

これはゲームを始めるときに行なう処理を書き始めるという指定ずらね。

シュウ君:次は主人公キャラの指定だな?

ダースー:そうずら。以下ではクマの初期設定をしているずら。

bear = new Sprite(32, 32);

新規に動かすキャラを“bear”という関数にして、32×32ピクセルで設定しているずら。関数って言うとわかりづらい人もいるずらけど、設定みたいなもんずら。ほかの行で“bear”て言ったら、ああ、32×32ピクセルのあいつのことねーってわからせるための処理だと思っておけばいいずらよ。

bear.image = game.assets['http://jsrun.it/assets/k/r/t/X/krtXz.gif'];
クマのgif画像を読み込み
ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ
↑指定するときの番号は上段左から0、1、2、3、4。中段左から5、6、7、8。下段左から9、10、11、12となるずらよ。

そんで、その“bear”に使う画像の読み込みと続くずらよ。どんどんキャラ設定が増えていくずらね。

bear.x = 160; bear.y = 200;

上のコードは“bear”の初期位置を画面上のX座標で160、Y座標で200の位置に指定しているずら。

bear.frame = 0;

クマの画像は1枚の画像の中にアニメーション用にいくつかパターンが入っていて、数字でどれを使うか指定できるずら。左上から、キャラ作成時に指定した32×32ピクセルごとに、0、1、2……と指定すると、左上から1番目、2番目、3番目のキャラ画像が使えるずらよ。

game.rootScene.addChild(bear);

これはゲーム内で関数“bear”が使えるようにするおまじないずら。

シュウ君:先輩、この“label”って部分はなんすか?

ダースー:これはゲームに出てくる“タイムリミット”と“スコアー”のことずら。

label = new Label(""); label.x =0; label.y =17;

スコアーとかタイムをX座標で0、Y座標で17の位置に表示するという指定ずら。

game.rootScene.addChild(label);

んでもって、これはゲーム内で関数“label”が使えるようにするおまじないずら。

game.time = game.fps * game.time;

この部分は最初に設定したfpsをゲーム内の進行時間に掛けてるずら。この処理をしないと、きちんとタイムが表示されないずらよ。

アス君:以下の操作部分をつかさどるコードがenchant.jsのスゴイところですよ。スマホでのタッチ操作とPCでのマウス操作がたったこれだけのコードでできてしまうんです。

game.rootScene.addEventListener('touchmove', function(move){       bear.y = move.localY -50;       bear.x = move.localX -20;  });

ダースー:ザッツライト! スマホでタッチ、もしくはマウスでドラッグした場所にクマのキャラクターが移動するという命令ずら。ポイントはスマホだとタッチした指がジャマになってキャラが見えなくなってしまうから、Y軸方向にマイナス50ドット、X軸方向にマイナス20ドットずらして調整しているずらよ。これでグッと遊びやすくなるずら。

座標調整前
ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ
座標調整後
ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ

シュウ君:次の部分が俺はよくわからないんだよな……。

count = 0; game.rootScene.addEventListener('enterframe',function(){    if(game.frame % 4 == 0){ bear.frame = game.frame % 3;}

ダースー:む~、確かにここは読み方を知らないと理解しずらいずらね。実はここはbearが歩いているように見せかける処理をしているずらよ。“game.frame”はゲームスタートから換算される総フレーム数のことずら。このゲームは24fps(1秒間に24コマ)なので、ゲームスタートから制限時間1分のゲームだと、コマ数は全部で1440コマになるずらね。なのでgame.frameは1~1440のいずれかの数字になるずら。

アス君:“if(game.frame % 4 == 0)”では、ゲーム開始からある時点のフレーム数を4で割った余りが0になるときに画像を変えるという条件です。そのあとに続く“{ bear.frame = game.frame % 3;}”は、そのとき使うbearの画像はそのフレーム数を3で割った余りの数(0、1、2)として、gif画像内のどの画像を使うかという指定になっています。フレームが進むごとにクマの画像は0(直立の画像)、1(右足前の画像)、2(左足前の画像)と1コマずつ変わり、アニメーションのようになるってわけです。

シュウ君:うーん、いまいちよくわkらんぜ……。

ダースー:具体的に言うと、ゲームスタートから100コマ目の場合はフレーム数は100なので、3で割ると33と余りは1、1は右足前のbear画像になるずら。同じように101コマ目の場合は、33と余り2だから左足前の画像、102コマ目の場合は34と余り0だから直立の画像になるずら、これを繰り返して、あたかもbearが歩いているように見せているずらよ。

if(game.frame % 10 == 0){

ここでは“game.frame”を10で割った余りが0になるごとにアイテムが出現すると定義しているずら。アイテムも同じようにフレーム数を元に出現頻度を設定できるずらよ。

if (game.frame % 3 == 0) {additem(14);}

“game.frame”を3で割った余りが0になるごとにゲーム上にコインを出現させる指定ずら。

else if (game.frame % 7 == 0) {additem(30);}

同じく、“game.frame”を7で割った余りが0になるごとにゲーム上にスターを出現させる指定ずら。スターはコインよりも高ポイントにしたいずらから、出現頻度を下げるために7で割った余りが0という条件にしているずら。3で割り切れる数よりも7で割り切れる数のほうが圧倒的に少ないからってことずらよ。こうやって数字を少し変えるだけで、難易度を調整できるは便利ずらね。

else{additem(24);}

そして、最後にコインとスターが出現する条件(3と7で割り切れるフレーム数の場合)以外のフレーム数のときには爆弾を出すという指定ずら。

アス君:ダースー先輩、熱い解説ありがとうございました。次はゲームに表示するスコアーと残り時間の部分ですね。

label.text = "スコア" + game.score + "<br>残り時間" + Math.floor(game.time / game.fps) +"秒";     }

ダースー:そうずら。ポイントは“Math.floor(game.time / game.fps)”の部分。ゲームの進行時間はfps単位だから、それを“秒”に修正しているのがこの部分ずら。

game.time --; game.frameCount ++;

ここでは、クマの動きとアイテムの出現が1巡したら、残り秒を引いて、ゲームを進行させるという指示をしているずら。

シュウ君:お、次のは俺でもわかるぜ。ゲームオーバーの処理だな。

if (game.life == 0){game.end(game.score, "あなたのスコアは" + game.score);} if (game.time == 0){game.end(game.score, "あなたのスコアは" + game.score);} });

ダースー:大正解ずら。ゲームのライフが0になるか、残り時間が0になったら、ゲームオーバーにしてスコアーを表示させるという意味ずら。

      setLifes();       } game.start();

ダースー:ここは設定したライフを表示させる部分ずらね。んで、初期に読み込む部分を終了して、ゲームを開始すると宣言しているずら。

アス君:次はアイテムの設定ですね。

} function additem(item_frame){     var item = new Sprite(16, 16);

ダースー:ここでアイテムを設定すると宣言しているずら。んで、アイテムの大きさを16×16ドットにしているずらよ~。

item.image = game.assets['http://jsrun.it/assets/v/1/a/l/v1alF.gif'];

それから、上のコードであらかじめ読み込んでおいたアイテムに使う画像を指定しているずら。

item.x = rand(304); item.y = 0;

次にアイテムの出現場所を、X軸座標は0~304の間でランダムに、Y軸座標は0、つまり画面の一番上から出現するようにしているずら。

item.frame = item_frame; item.addEventListener('enterframe', function() {

シュウ君:うーん。先輩、この部分もわかりにくいぜぇ。

ダースー:これはクマとアイテムがぶつかったときの処理を指定しているずらよ~。最初にゲーム内でこれこれこういう処理を追加しますよ~って宣言をしてるみたいなもんずらね。乱暴に言ってしまえば、プログラミングは自分こういう感じでいきますんでよろしく!的な宣言がたびたび必要になるずら。

if(this.intersect(bear)){         if (item.frame == 14){//CASE1        game.rootScene.removeChild(this);       game.score +=  10;                       }

上のコードは、クマとコインがぶつかったら、コインを消してスコアーを10足す処理ずら。

else if (item.frame == 30) {//CASE2      game.rootScene.removeChild(this);      game.score +=  50;                   }

んで、こちらがクマとスターがぶつかったら、スターを消してスコアーを50足す処理ずら。

else if (item.frame == 24){//CASE3              game.rootScene.removeChild(this);              game.life --;              life.width = 16 * game.life;}

上は、クマと爆弾がぶつかったら、爆弾を消して、ライフをひとつ減らす処理ずら~。

       }else{this.y += 2;}}); count++;

ここはアイテムが何にもぶつからなかった場合の処理ずらね。ぶつからなかったら、アイテムをY軸方向、つまり画面上から下にかけて+2ドット移動させるという意味ずら。これでアイテムがスクロールしているように見えるずら。で、“count++;”でゲームを進行させているずら。下のコードはこれも処理を閉じるおまじないずらね。

game.rootScene.addChild(item);

アス君:ここからはゲームの進行に必要な処理を指定する部分ですね。

ダースー:そうずら。まずはランダム数値の生成とライフの指定ずら。

function rand(num){ return Math.floor(Math.random() * num); }

こう書くことで、ランダムな数値を使えるようになるずら。んで、次はライフの処理ずらね。

function setLifes(){     life = new Sprite(16 * game.life,16);     life.image = game.assets['http://jsrun.it/assets/e/B/C/G/eBCGr.gif'];     life.set = function(num){         game.life = num;         this.width = 16 * 3;}     game.rootScene.addChild(life);

あらかじめ読み込んでおいた、16×16ドットのハートの画像を表示して、それを3つ並べているずら。ってところで以上、解説終了ずら!

シュウ君:うーん。俺にはやっぱりちょっとわかりにくいところ結構あったかも。

アス君:JavaScriptとenchant.jsの基本的な部分を勉強してないと、完全に理解することは厳しいかも知れませんね。

ダースー:そんなときにはこの本ずら!

ゲーム作りの初心者本として爆裂ヒット中!!
ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ
↑書店、各社書店サイトなどで絶賛発売中。ランキングも急上昇中です。Amazonで品切れの際は、楽天BOOKSなどほかの書店サイトでお買い求めください。

シュウ君:おお! これなら俺でもイチから始められそうだぜ~。

ダースー:さて、ここまで我慢して読んでくれたみんなに、そろそろお待ちかねの委員長のお宝画像ずら。

委員長:ちょっとぉ! まったく内容と関係ないじゃないですかぁ~。

ダースー:とか言いつつ案外ノリノリでやってたのワスは知ってるずら。

ソースコードの詳細を解説 これが3時間で作ったスマホゲームだっ
この記事をシェアしよう

週刊アスキーの最新情報を購読しよう