enchant.jsを使ってガンシューティングゲームの開発を行なっていきます。
enchant.jsはゲームを作るのに特化したJavaScriptフレームワークです。
これを使うことで、JavaScriptで簡単にゲームを作成することができます。
今回はガンシューティングゲームを作っていきたいと思います。
勿論、スマホでも遊べるようにしていこうと思います。
- 今回enchant.jsで作成するゲーム
- enchant.js でゲームを作る為の準備
- enchant.jsでのシーンの作り方
- enchant.jsで画像(スプライト)を表示する方法
- enchant.jsによるキャラクター(プレイヤー)の移動
- enchant.jsによるキャラクター(プレイヤー)のアニメーション
- クラスを使ってみよう
- 敵キャラを表示してみよう
- 背景を表示とスクロール
- プレイヤーの移動範囲と初期位置
- 敵キャラの初期位置と移動
- 弾を撃てるようにする
- enchant.jsによるスプライトの削除方法。画面外に出た画像は削除する
- スプライトの重なり順を変更する
- enchant.jsでの当たり判定の作り方
- Googleフォントを使う準備
- プレイヤーのHPを作る
- enchant.jsでのシーンの作り方。ゲームオーバーのシーンの作成
- enchant.jsでのシーンを切り替える方法
- 得点を表示する
- 変数の初期値をリセットする
- 敵と当たった後、一定時間プレイヤーを無敵状態にする
- スタート画面を作る
- 全体のプログラム
- まとめ
今回enchant.jsで作成するゲーム
まず、今回はこのようなゲームを作成していきます。
キーボードで操作する時は、枠内の白い部分をクリックしてから始めてください。
Zキー(タップ) : 決定、撃つ
方向キー : 移動
enchant.js でゲームを作る為の準備
さて、 enchant.js によるゲーム開発にとりかかります。
まずは準備。何事でも準備をすることはとても大切です。替えのパンツを用意しておかなかったら、下半身丸出しのまま辺りを彷徨うこととなります。
ゲーム開発に必要なデータをダウンロード
ではまず、enchant.js のページから必要なものをダウンロードしていきましょう。
このページのDownloadから「Download Zip file」をクリックし、ダウンロードします。
ダウンロードしたフォルダの中にはゲーム制作に使うことができる素材も入っており、ダウンロードして直ぐにゲーム制作をすることが可能です。
この中に入っているクマさんがとても可愛いのですが、今回はこのキャラは使いません。
使用する画像はこちらです。
今回のゲーム制作でのディレクトリ構成
ではゲーム制作用のフォルダを作り、その中に必要なファイルを入れていきます。
フォルダ名は「game」としました。
ダウンロードしたものの中から、「enchant.js-builds-0.8.2-b」→「build」→「enchant.js」を探しておいてください。
そして、以下のようなディレクトリ構成にします。
game ├ index.html ├ img/ │ ├ bullet.png │ ├ bg/ │ │ ├ field.png │ │ ├ rock.png │ │ └ sky.png │ └ character/ │ ├ enemy.png │ └ player.png ├ inc/ │ └ enchant.js └ js/ └ main.js
ではまず、index.htmlに以下を記述していきましょう。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Snipe at Monsters</title> <script src="inc/enchant.js"></script> <script src="js/main.js"></script> <style> body { margin: 0; padding: 0; } </style> </head> <body> </body> </html>
また上のようにスタイルを指定しておかないと、ゲームの周りに隙間ができてしまいます。
ここまで作成したらmain.jsを編集してゲームを作っていきます。
main.jsは以下のように入力してください。
//おまじない enchant(); //変数宣言 var game; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { //ここにメインの処理を作っていく } ); game.start(); //ゲームスタート } );
enchant.jsでのシーンの作り方
さて、準備も整ったところで、まずはシーンを作ってみましょう。
シーンを使えば、例えばタイトルシーン、メインゲームシーン、戦闘シーンといったそれぞれのシーンごとに作っておき、それを切り替えることで、一つのゲームになります。
今回はメインゲームから作成していきたいので、メインゲームシーンを作成してみます。
シーンの名前は「mainScene」とします。追加したのは13行目、16〜21行目です。
//おまじない enchant(); //変数宣言 var game; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす return scene; } game.start(); //ゲームスタート } );
これで動作を確認してみます。
このように黒い正方形が表示されていれば成功です。
enchant.jsで画像(スプライト)を表示する方法
画像(スプライト)を読み込んでみましょう。
プリロードについて
画像を読み込むには、まずプリロードという作業が必要です。
プリロードにはpreload
というコマンドを使い、ゲームオブジェクトに対して使用します。
以下の10行目のように使います。
//おまじない enchant(); //グローバル変数宣言 var game; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす return scene; } game.start(); //ゲームスタート } );
画像の表示
プリロードが済んだら、実際にスプライトを表示してみましょう。
今回はプレイヤーのスプライトを表示してみます。スプライトの表示は、22〜25行目のようにします。
//おまじない enchant(); //変数宣言 var game; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**スプライトの表示**/ var playerSprite = new Sprite( 51, 55 ); //スプライトを作成 playerSprite.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 scene.addChild( playerSprite ); //スプライトをシーンに追加(表示) return scene; } game.start(); //ゲームスタート } );
23行目、Spriteオブジェクトの引数には、画像の表示したいサイズを入力します。
今回のキャラの画像は4枚が1枚の画像になっているので、その1枚分のサイズを入力しました。
画像の位置を変更
つづいて、画像の位置を変更してみましょう。
例えば、playerSpriteの位置を変更したい場合はこのようにします。
playerSprite.x = 30; playerSprite.y = 50;
これで、スプライトのX軸、Y軸の位置を指定できます。
また、moveTo()
という関数を使っても、同じ動きになります。
playerSprite.moveTo( 30, 50 );
25行目に追加しました。
//おまじない enchant(); //変数宣言 var game; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**スプライトの表示**/ var playerSprite = new Sprite( 51, 55 ); //スプライトを作成 playerSprite.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 playerSprite.moveTo( 30, 50 ); //スプライトの位置を変更 scene.addChild( playerSprite ); //スプライトをシーンに追加(表示) return scene; } game.start(); //ゲームスタート } );
画像の向きやサイズを変更する
画像の位置を変更したときと同じように、向きやサイズを変更することもできます。
//横に3倍、縦に2倍の大きさにする playerSprite.scale( 3, 2 ); //80度回転させる playerSprite.rotate( 80 );
こういった機能を使って、ゲームを作成していくのです。
enchant.jsによるキャラクター(プレイヤー)の移動
キー入力やバーチャルパッドによってキャラクターを移動させてみましょう。
これで彼女はあなたの思うままになります。。。
キーボード入力によるプレイヤーの移動
まず、キーボードによるキャラクターの操作を行えるようにしてみたいと思います。
playerSprite.addEventListener('enterframe', function() { var speed = 14; if (game.input.left) this.x -= speed; if (game.input.right) this.x += speed; if (game.input.up) this.y -= speed; if (game.input.down) this.y += speed; });
addEventListenerはイベントが発生した時に呼ばれるものです。enterframe、つまり毎フレームごとに呼び出されます。
呼ばれる命令を中に書いています。ここではキーボード入力による、thisつまりplayerSpriteの移動の命令が書かれています。
上のソースを以下のように、26〜35行目に入れます。
//おまじない enchant(); //変数宣言 var game; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**スプライトの表示**/ var playerSprite = new Sprite( 51, 55 ); //スプライトを作成 playerSprite.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 /**プレイヤーの移動**/ playerSprite.addEventListener( 'enterframe', function() { var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) this.x -= speed; if ( game.input.right ) this.x += speed; if ( game.input.up ) this.y -= speed; if ( game.input.down ) this.y += speed; } ); scene.addChild( playerSprite ); //スプライトをシーンに追加(表示) return scene; } game.start(); //ゲームスタート } );
パーチャルバッドによるキャラクターの移動
続いて、バーチャルパッドでキャラクターを移動できるようにしてみましょう。
しかしその前に少し準備があります。スマホに対応させるにはライブラリと画像が必要です。
それらをincフォルダにコピペします。
「enchant.js-builds-0.8.2-b」→「build」→「plugins」から、ui.enchant.jsファイルをincフォルダの中にコピペします。
さらに、「enchant.js-builds-0.8.2-b」→「images」から、「apad.png」、「font0.png」、「icon0.png」、「pad.png」の4つのファイルを、gameフォルダにコピペしましょう。
一見必要無いと思われるファイルも、無いと上手く実行されません。
「apad.png」、「font0.png」、「icon0.png」、「pad.png」は、index.htmlのある場所に置いておく必要があります。
game ├ index.html ├ apad.png ├ font0.png ├ icon0.png ├ pad.png ├ img/ │ ├ bullet.png │ ├ bg/ │ │ ├ field.png │ │ ├ rock.png │ │ └ sky.png │ └ character/ │ ├ enemy.png │ └ player.png ├ inc/ │ ├ enchant.js │ └ ui.enchant.js └ js/ └ main.js
では、次にindex.htmlファイルを編集します。
<script src=”inc/ui.enchant.js”></script>
を、
<script src=”inc/enchant.js”></script>
の後ろに書き込みます。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Snipe at Monsters</title> <script src="inc/enchant.js"></script> <script src="inc/ui.enchant.js"></script> <script src="js/main.js"></script> <style> body { margin: 0; padding: 0; } </style> </head> <body> </body> </html>
こんな感じで、準備は完了です。
では、アナログパッドを表示してみましょう。
ui.enchant.jsを使えば、アナログパッドで操作させることが簡単にできるようになります。
アナログパッドといっても、画面上のアナログパッドです。
まずはアナログパッドを画面上に表示させてみます。
main.jsを編集していきましょう。
以下をgame.addEventListener( 'load', function() {} );
の中に書いていきます。
var pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad );
さらに、アナログパッドを操作した時の、キャラクターの移動処理も作っていきます。
以下を「playerSprite.addEventListener( ‘enterframe’, function() {};」の中に書きます。
if (pad.isTouched) { this.x += pad.vx * speed; this.y += pad.vy * speed; }
pad.vxやpad.vyは、パッドがどれだけ動いたかという事です。
そこにspeedの値をかければ、パッドの移動量によってキャラの速度を計算できるわけです。
ただ、バーチャルパッドは、プレイヤーの移動の処理からも呼び出されることを考え、グローバルに変数を宣言します。
6行目にグローバル変数宣言、27〜31行目、43〜47行目に上記の処理を少し修正を加えて追加しました。
//おまじない enchant(); //変数宣言 var game; var pad; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**スプライトの表示**/ var playerSprite = new Sprite( 51, 55 ); //スプライトを作成 playerSprite.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); /**プレイヤーの移動**/ playerSprite.addEventListener( 'enterframe', function() { var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) this.x -= speed; if ( game.input.right ) this.x += speed; if ( game.input.up ) this.y -= speed; if ( game.input.down ) this.y += speed; /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.x += pad.vx * speed; this.y += pad.vy * speed; } } ); scene.addChild( playerSprite ); //スプライトをシーンに追加(表示) return scene; } game.start(); //ゲームスタート } );
ブラウザで確認すると、このようになります。(バーチャルパッドが分かりやすいように、背景の色を変更しております)
enchant.jsによるキャラクター(プレイヤー)のアニメーション
つづいて、プレイヤーのアニメーションをさせてみましょう。
addEventListener( 'enterframe', function() {} );
の中に書かれた内容は、毎フレームごとに呼び出されるんでしたね。
そのことを踏まえ、キャラのアニメーションを作っていきたいと思います。
では、⓪〜③の画像を順番に表示させてみましょう。
addEventListener( 'enterframe', function() {} );
の中に、命令を書いていきます。
先ほど、キャラクターのスプライトをvar playerSprite = new Sprite( 51, 55 );
という命令で作りました。
これは51*55の画像で作るという命令なので、画像内ではキャラクターが4つあるにもかかわらず、キャラクターは一番左の画像しか表示されていませんね。
スプライトのアニメーションの基本
じゃあ左から2番目の画像を表示するにはどうすればいいんでしょうか。
前回作ったソース内に、this.frame = 1;
と書き込んでみましょう。書き込む場所は、playerSprite.addEventListener( 'enterframe', function() {} );
の中です。
では、プレイヤーの移動部分のソースを確認してみましょう。
/**プレイヤーの移動**/ playerSprite.addEventListener( 'enterframe', function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) this.x -= speed; if ( game.input.right ) this.x += speed; if ( game.input.up ) this.y -= speed; if ( game.input.down ) this.y += speed; /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.x += pad.vx * speed; this.y += pad.vy * speed; } } );
this.frame = 1;
のthisは、今回の場合、playerSpriteを表しています。
つまり、playerSpriteのフレームが1、つまり左から2番目という意味になります。
ではこれを順番に切り替え、アニメーションさせるにはどのようにすればよいのでしょうか。
enchant.jsには、スプライトが画面に表示されてからのフレーム数を表すageというものがあります。
アニメーションにはこのageを使います。
画面が更新されるごとに、キャラのイラストを変えてあげるのです。
フレーム数は、1ずつ増えていきます。
画像は4枚。つまり、フレーム数を4で割った余りをthis.frameに代入すればいいのです。
割った余りを表す%を使いましょう。
this.frame = this.age%4;
これで、フレーム数を4で割った余り、つまり0〜3が、this.frameに代入されます。
しかし今回のキャラの画像は、順番に表示させてもちゃんとしたアニメーションになりません。
実はこのイラストは、歩いた時のアニメーションと走った時のアニメーションの2つが合わさったものです。
なので、4枚を順番に表示させても、ちゃんとしたアニメーションはできないのです。
では、キャラが歩いているときは⓪と①を交互に、走っているときは②と③を交互にアニメーションさせるようにプログラムを作り変えていきましょう。
まず、キャラが歩いているときのアニメーションを作ります。最終的にはアナログパッドを少しだけ傾けた時に歩くという感じに作りたいと思います。
まずは⓪と①の二つのみをアニメーションさせる方法です。
⓪と①のみのアニメーションということは、2枚の画像を交互に表示するということなので、this.ageを2で割った余りを表示するということになります。
この辺は簡単ですね。
this.frame = this.age%2;
となります。
では②と③を交互に表示するにはどうすればいいのでしょうか。
最終的に2枚のアニメーションを交互に表示するということには変わりないので、this.ageを2で割った余りというのは間違いなさそうです。
ではここに2をプラスすると、0〜1 +2= 2〜3となりますね。つまり……
this.frame = this.age%2+2;
とすればいいことになります。
キャラが移動している時のみアニメーションさせる
では、実際にプログラムに組み込んでみたいわけですが、歩いている時と、走っている時の区別はどうやってやればいいのと思う人もいるでしょう。
パッドがどれだけ動いたかという値をとる変数がありましたよね。
しかしまずは、キャラが移動している時のみ走らせるプログラムを作ってみましょう。
キャラが移動……つまり方向キーが押されたり、アナログパッドを傾けたりした場合にのみアニメーションさせます。
では先ほどのthis.frame = this.age%2+2;
という命令をプレイヤーの移動部分のソースに組み込んでみたいと思います。
/**プレイヤーの移動**/ playerSprite.addEventListener( 'enterframe', function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.frame = this.age%2+2; //アニメーション this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション this.x += pad.vx * speed; this.y += pad.vy * speed; } } );
何も操作していないときは、3行目のthis.frame = 1;
で常に①の画像を表示しておきます。
これで移動中のみ走るプログラムができました。
ゆっくり移動しているときは歩くアニメーション
ゆっくり移動させるのは、アナログパッドを少し傾けた時です。
pad.vx や pad.vy は、アナログパッドがどれだけ傾いたという値を表すものです。
アナログパッドの傾きによって、-1〜1までの値が入ります。
また、Math.abs();
は、絶対値を返す関数です。
今回は画像が横向きのみなので、横にゆっくり移動している時のみ歩かせるという感じで作っていきましょう。
なので、 pad.vx の値の絶対値が0.5以下の場合、歩かせるというプログラムにしてみましょう。
また、アニメーションの速度も考えて、フレーム数を8で割った時の余りが4以下の場合⓪を表示、それ以外の場合①を表示するというプログラムにしてみましょう。
30〜34行目を追加しました。
/**プレイヤーの移動**/ playerSprite.addEventListener( 'enterframe', function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.frame = this.age%2+2; //アニメーション this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if (pad.isTouched) { this.frame = this.age%2+2; //アニメーション this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; } } );
左方向へ移動している時は画像を反転
今の状態では、アナログパッドを左方向へ倒した時、キャラクターは後ろ向きに進むことになります。
なので、左方向へ移動している時は、キャラの画像を反転させることにしましょう。
反転といっても、X軸方向に反転です。
そんなときは、X方向に-1拡大してあげると、X軸方向に反転します。
このようにします。
this.scaleX = -1;
これをプレイヤー移動のところに追加してあげましょう。
追加したのは8行目、13行目、38〜40行目です。
/**プレイヤーの移動**/ playerSprite.addEventListener( 'enterframe', function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } } );
こちらが全体のソースです。
//おまじない enchant(); //変数宣言 var game; var pad; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**スプライトの表示**/ var playerSprite = new Sprite( 51, 55 ); //スプライトを作成 playerSprite.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); /**プレイヤーの移動**/ playerSprite.addEventListener( 'enterframe', function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } } ); scene.addChild( playerSprite ); //スプライトをシーンに追加(表示) return scene; } game.start(); //ゲームスタート } );
クラスを使ってみよう
さて、ソースが随分と長くなってしまいました。
どこに何が書いてあるのか、分かりにくいですよね。
そんな時はクラスを使うととても便利です。
クラスというのは、型のようなものです。
では実際に使ってみましょう。
今回はプレイヤーのクラス(Player)を作ってみます。
プレイヤーはスプライトですので、Spriteクラスを継承して作ります。
まずenchant.jsでのクラスの作り方は以下のようにします。これでSpriteクラスを継承したPlayerクラスを作成することができます。
var Player = Class.create( Sprite, { //ここにプレイヤーの処理 } );
ではプレイヤーの処理を作っていきます。
まず、プレイヤーの初期化はinitialize: function() {}
の中に書いていきます。
さらに、毎フレームごとに呼び出される処理はonenterframe:function() {}
の中に書いていきます。
var Player = Class.create( Sprite, { initialize: function() { //ここに初期化処理 }, onenterframe: function() { //毎フレームごとの処理 } } );
では初期化処理を作ってみましょう。
var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { //毎フレームごとの処理 } } );
では、先ほどのプレイヤーの処理をこちらのクラスに移動させ、動作するように書き換えてみましょう。
var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } } } );
プレイヤーのクラスを呼び出すには、以下のようにします。
(playerはグローバル変数として宣言することにします)
player = new Player(); mainScene.addChild( player );
動作は変わりませんが、クラスごとにソースを書くことができるので、管理が楽になります。
全体のコードは以下のようになります。
//おまじない enchant(); //変数宣言 var game; var pad; var player; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**プレイヤーの作成**/ player = new Player(); scene.addChild( player ); /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } } } );
敵キャラを表示してみよう
続いて、敵キャラを表示してみます。
敵キャラも、プレイヤーと同様にクラスを作って作成します。
var Enemy = Class.create( Sprite, { initialize: function() { Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { } } );
さらに敵キャラのクラスを呼び出すには以下のようにします。
(enemyはグローバル変数として宣言することにします)
enemy = new Enemy(); scene.addChild( enemy );
初期位置の設定をしていないので、このように重なって表示されます。
全体のコードは以下のようになります。
//おまじない enchant(); //変数宣言 var game; var pad; var player, enemy; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png', 'img/character/enemy.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**プレイヤーの作成**/ player = new Player(); scene.addChild( player ); /**敵キャラの作成**/ enemy = new Enemy(); scene.addChild( enemy ); /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } } } ); /**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { } } );
背景を表示とスクロール
つづいて、背景を表示させます。
プレイヤーや敵キャラの初期位置、移動は、背景を表示させた後に行なっていきたいと思います。
背景は3重構造にします。
では、背景の関数(bg)を作って、背景を表示してみましょう。
(rock, fieldは、グローバル変数として宣言します)
var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock = new Sprite( 320, 320 ); rock.image = game.assets[ 'img/bg/rock.png' ]; scene.addChild( rock ); /**地面のスプライトを表示**/ field = new Sprite( 320, 320 ); field.image = game.assets[ 'img/bg/field.png' ]; scene.addChild( field ); }
以下の命令で関数を呼び出します。
背景は一番後ろに表示したいので、シーンを作成したすぐ後に入れましょう。
bg( mainScene );
実際にブラウザで確認すると、このようになります。
さて、どうして背景を3重にして作ったのかといいますと、キャラの移動によってそれぞれ別の速さでスクロールさせたかったからです。
今回は、プレイヤーがX座標中央の位置に来て、さらに右に移動しようとすると背景をスクロールさせるというふうにしたいと思います。
まず背景のスクロールなのですが、これは同じ背景を2つ横に並べ、交互に表示する方法で作ってみましょう。
そこで先ほど作成した背景の関数を、次のように変更します。
(グローバル変数宣言部分も直します)
var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock1 = new Sprite( 320, 320 ); rock1.image = game.assets[ 'img/bg/rock.png' ]; rock2 = new Sprite( 320, 320 ); rock2.image = game.assets[ 'img/bg/rock.png' ]; rock2.x = 320; scene.addChild( rock1 ); scene.addChild( rock2 ); /**地面のスプライトを表示**/ field1 = new Sprite( 320, 320 ); field1.image = game.assets[ 'img/bg/field.png' ]; field2 = new Sprite( 320, 320 ); field2.image = game.assets[ 'img/bg/field.png' ]; field2.x = 320; scene.addChild( field1 ); scene.addChild( field2 ); }
さらに、プレイヤーのクラスを以下のように変更します。
21〜32行目、47〜58行目を変更します。
/**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; } else this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } } } );
キャラが中央より右に行こうとすると、背景がスクロールするようになりました。
プログラム全体はこのようになります。
//おまじない enchant(); //変数宣言 var game; var pad; var player, enemy; var rock1, rock2, field1, field2; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png', 'img/character/enemy.png', 'img/bg/sky.png', 'img/bg/rock.png', 'img/bg/field.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**背景の作成**/ bg( scene ); /**プレイヤーの作成**/ player = new Player(); scene.addChild( player ); /**敵キャラの作成**/ enemy = new Enemy(); scene.addChild( enemy ); /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; } else this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } } } ); /**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { } } ); var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock1 = new Sprite( 320, 320 ); rock1.image = game.assets[ 'img/bg/rock.png' ]; rock2 = new Sprite( 320, 320 ); rock2.image = game.assets[ 'img/bg/rock.png' ]; rock2.x = 320; scene.addChild( rock1 ); scene.addChild( rock2 ); /**地面のスプライトを表示**/ field1 = new Sprite( 320, 320 ); field1.image = game.assets[ 'img/bg/field.png' ]; field2 = new Sprite( 320, 320 ); field2.image = game.assets[ 'img/bg/field.png' ]; field2.x = 320; scene.addChild( field1 ); scene.addChild( field2 ); }
プレイヤーの移動範囲と初期位置
続いて、プレイヤーの移動範囲と初期位置を設定していきます。
中央より右に行けないというのはここでは置いておいて、本来プレイヤーが移動できるのはこの範囲です。
プレイヤークラスの、onenterframe: function() {}
の一番下に、以下のように追加します。
if ( this.x < 0 ) this.x = 0; if ( this.y < 100 ) this.y = 100; if ( this.y > 260 ) this.y = 260;
x方向へは半分までしか行くことができないので、右にはみ出してしまう時の処理は作っていません。
さらにプレイヤーの初期位置を設定します。
プレイヤークラスの、initialize: function() {}
に、以下のように追加します。
this.moveTo( 60, 180 ); //プレイヤーの初期位置
プログラム全体はこのようになります。
//おまじない enchant(); //変数宣言 var game; var pad; var player, enemy; var rock1, rock2, field1, field2; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png', 'img/character/enemy.png', 'img/bg/sky.png', 'img/bg/rock.png', 'img/bg/field.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**背景の作成**/ bg( scene ); /**プレイヤーの作成**/ player = new Player(); scene.addChild( player ); /**敵キャラの作成**/ enemy = new Enemy(); scene.addChild( enemy ); /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.moveTo( 60, 180 ); //プレイヤーの初期位置 this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; } else this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } if ( this.x < 0 ) this.x = 0; if ( this.y < 100 ) this.y = 100; if ( this.y > 260 ) this.y = 260; } } ); /**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { } } ); var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock1 = new Sprite( 320, 320 ); rock1.image = game.assets[ 'img/bg/rock.png' ]; rock2 = new Sprite( 320, 320 ); rock2.image = game.assets[ 'img/bg/rock.png' ]; rock2.x = 320; scene.addChild( rock1 ); scene.addChild( rock2 ); /**地面のスプライトを表示**/ field1 = new Sprite( 320, 320 ); field1.image = game.assets[ 'img/bg/field.png' ]; field2 = new Sprite( 320, 320 ); field2.image = game.assets[ 'img/bg/field.png' ]; field2.x = 320; scene.addChild( field1 ); scene.addChild( field2 ); }
敵キャラの初期位置と移動
続いて、敵キャラの初期位置と移動のプログラムを作っていきたいと思います。
では敵キャラのクラスを以下のようにします。
6行目で初期位置の設定、9行目で移動の設定をしています。
/**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 this.moveTo( 330, 200 ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 } } );
しかしこのままでは敵は1体しかでてきません。
では複数の敵を表示させてみましょう。
複数の敵を表示させるには配列を使います。
通常enemyListなどといった配列を使うと思いますが、今回はプレイヤーも敵も、全てを一つにまとめて配列にしたいので、spriteListという配列を作ります。
なぜ一つにまとめたいのかというと、それぞれのスプライトの重なり順を変更するときに、そのほうが作りやすいからです。
プレイヤーの作成部分と、敵キャラの作成部分を変更し、その下にスプライトを表示するソースを加えます。
次の3行目と8行目に当たる部分を削除し、4行目、9行目を追加します。
さらに11〜14行目で、spriteListにプッシュされたスプライトを全て表示するようにします。
/**プレイヤーの作成**/ player = new Player(); //mainScene.addChild( player ); 消す spriteList.push( player ); //spriteListにプレイヤーをプッシュ /**敵キャラの作成**/ enemy = new Enemy(); //mainScene.addChild( enemy ); 消す spriteList.push( enemy ); //spriteListに敵をプッシュ /**プレイヤーや敵などのスプライトを表示する**/ for(var i=0; i<spriteList.length; ++i) { scene.addChild( spriteList[i] ); }
これでもまだ結果は変わりません。
ではシーン更新ごとに敵キャラを作成するようにしてみましょう。
敵キャラ作成と、スプライト表示の部分を、scene.onenterframe = function() {}
に入れるように変更します。
/**プレイヤーの作成**/ player = new Player(); spriteList.push( player ); //spriteListにプレイヤーをプッシュ /**シーン更新ごとに呼び出す**/ mainScene.onenterframe = function() { /**敵キャラの作成**/ enemy = new Enemy(); spriteList.push( enemy ); //spriteListに敵をプッシュ /**プレイヤーや敵などのスプライトを表示する**/ for(var i=0; i<spriteList.length; ++i) { mainScene.addChild( spriteList[i] ); } }
これは敵をシーン更新ごとに呼び出しているからですので、一定の間隔で呼び出すように変更してみたいと思います。
敵キャラの作成部分をif (game.frame % 15 === 0) {}
に入れるように書き換えます。
/**プレイヤーの作成**/ player = new Player(); spriteList.push( player ); //spriteListにプレイヤーをプッシュ /**シーン更新ごとに呼び出す**/ scene.onenterframe = function() { //15フレーム毎に敵を生成する if (game.frame % 15 === 0) { /**敵キャラの作成**/ enemy = new Enemy(); spriteList.push( enemy ); //spriteListに敵をプッシュ } /**プレイヤーや敵などのスプライトを表示する**/ for(var i=0; i<spriteList.length; ++i) { mainScene.addChild( spriteList[i] ); } }
しかし、一列に並んでいてはゲームとして面白くありませんので、Y座標をランダムにして敵キャラを表示するようにしてみましょう。
敵キャラのクラスを以下のようにします。
6〜7行目を追加しました。
/**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 } } );
するとこのように、Y座標がランダムになって敵が出現するようになります。
敵のスプライトをアニメーションさせるようにもしておきましょう。
12〜14行目を追加します。
/**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 /**敵キャラのアニメーション**/ if (this.age%8 < 4) this.frame = 0; else this.frame = 1; } } );
しかしこのままでは、プレイヤーが動いて背景がスクロールした時に、敵キャラと背景が別々に動いているようになってしまいます。
なので、背景のスクロールと一緒に敵キャラもスクロールさせる必要があります。
まず、プレイヤーのクラスで、→が押された時の処理のところに以下を追加します。
/**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= speed; }
またアナログパッドの部分は速度の差がありますので、以下のようにします。
/**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= pad.vx * speed; }
プログラム全体はこのようになります。
//おまじない enchant(); //変数宣言 var game; var pad; var player, enemy; var rock1, rock2, field1, field2; var spriteList = []; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png', 'img/character/enemy.png', 'img/bg/sky.png', 'img/bg/rock.png', 'img/bg/field.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**背景の作成**/ bg( scene ); /**プレイヤーの作成**/ player = new Player(); spriteList.push( player ); //spriteListにプレイヤーをプッシュ /**シーン更新ごとに呼び出す**/ scene.onenterframe = function() { //15フレーム毎に敵を生成する if (game.frame % 15 === 0) { /**敵キャラの作成**/ enemy = new Enemy(); spriteList.push( enemy ); //spriteListに敵をプッシュ } /**プレイヤーや敵などのスプライトを表示する**/ for(var i=0; i<spriteList.length; ++i) { scene.addChild( spriteList[i] ); } } /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.moveTo( 60, 180 ); //プレイヤーの初期位置 this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= speed; } } else this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= pad.vx * speed; } } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } if ( this.x < 0 ) this.x = 0; if ( this.y < 100 ) this.y = 100; if ( this.y > 260 ) this.y = 260; } } ); /**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 /**敵キャラのアニメーション**/ if (this.age%8 < 4) this.frame = 0; else this.frame = 1; } } ); var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock1 = new Sprite( 320, 320 ); rock1.image = game.assets[ 'img/bg/rock.png' ]; rock2 = new Sprite( 320, 320 ); rock2.image = game.assets[ 'img/bg/rock.png' ]; rock2.x = 320; scene.addChild( rock1 ); scene.addChild( rock2 ); /**地面のスプライトを表示**/ field1 = new Sprite( 320, 320 ); field1.image = game.assets[ 'img/bg/field.png' ]; field2 = new Sprite( 320, 320 ); field2.image = game.assets[ 'img/bg/field.png' ]; field2.x = 320; scene.addChild( field1 ); scene.addChild( field2 ); }
弾を撃てるようにする
続いて、弾を撃てるようにしていきましょう。
弾の出かたはゲームによっても違いますが、まずは弾幕ゲームによくある、zを押したままにすると弾が出続けるという方法を紹介します。
しかしこの方法は、今回のゲームにはあまり適していないので、方法のみの紹介とします。
まずはキーバインドの方法です。
zをaボタンとしてキーバインドします。
//Zキー入力をaボタンとする game.keybind( 'Z'.charCodeAt(0), 'a' );
さらにscene.onenterframe = function() {}
の中にaボタン(Zキー)が押された時の処理を書いていきます。
//aボタン(Zキー)が押された時 if ( game.input.a ) hitABullet(); //画面をタップされた時 this.addEventListener( 'touchstart', hitABullet ); var bullet; function hitABullet() { //弾を作成 bullet = new Bullet(); spriteList.push( bullet ); }
さらに弾のクラスを作成し、弾の初期位置や移動処理を作っていきます。
/**弾のクラス**/ var Bullet = Class.create( Sprite, { initialize: function() { var bulletX, bulletY; //弾のX座標とY座標 Sprite.call( this, 6, 2 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/bullet.png' ]; //スプライトの画像ファイルを指定 //プレイヤーの向きによって弾の位置や動かす方向を変える if ( player.scaleX >= 0 ) { this.speed = 10; bulletX = player.x + 50; } else { this.speed = -10; bulletX = player.x - 5; } bulletY = player.y + 19; this.moveTo( bulletX, bulletY ); //弾の位置 }, onenterframe: function() { this.x += this.speed; //弾の移動 } } );
しかし弾の数が多すぎるので、敵キャラを表示したときのように、何フレームかごとに弾を撃つようにします。
//5フレーム毎に弾を撃つ if ( game.frame % 5 === 0 ) { //aボタン(Zキー)が押された時 if ( game.input.a ) hitABullet(); } //画面をタップされた時 this.addEventListener( 'touchstart', hitABullet ); var bullet; function hitABullet() { //弾を作成 bullet = new Bullet(); spriteList.push( bullet ); }
しかし先ほども書きましたが、このままでは問題があります。
5フレーム毎に弾を出すようにすると、aボタン(Zキー)を長押しする場合はいいのですが、一瞬だけ押した時に弾が出るときと出ないときがあることになります。
弾幕シューティングなら問題ないですが、今回のように敵をよく狙って撃つゲームだとこれは致命的です。
そしてもう一つ。
スマホでは長押しができないのです。(長押しで連続弾が出るようにしてしまうと、キャラの移動操作中にずっと弾が出ることになってしまいます)
パソコンのみ連続で撃てるとなれば、明らかにパソコンでプレイした方が簡単ということになってしまいます。
そこで、aボタン(Zキー)を押した時のみ弾を撃つように書き換えてみましょう。
6〜7行目でグローバル変数を宣言し、12〜20行目で弾を作成する関数を作ります。
また、30〜35行目にaボタン(Zキー)で弾を撃つ処理を加えます。
//おまじない enchant(); //〜省略〜 var player, bullet, enemy; var isPushA = false; addEventListener( 'load', function() { //〜省略〜 function hitABullet() { //弾を作成 bullet = new Bullet(); spriteList.push( bullet ); isPushA = true; } game.addEventListener( 'load', function() { //〜省略〜 mainScene.onenterframe = function() { //〜省略〜 /**aボタンが押された瞬間のみ弾を撃つ**/ if ( isPushA === false ) mainScene.addEventListener( 'abuttondown', hitABullet ) else mainScene.removeEventListener('abuttondown', hitABullet ); mainScene.addEventListener('abuttonup', function() { isPushA = false; } ); //〜省略〜 } //〜省略〜 } ); game.start(); //ゲームスタート } ); //〜省略〜
プログラム全体はこのようになります。
//おまじない enchant(); //変数宣言 var game; var pad; var player, bullet, enemy; var rock1, rock2, field1, field2; var spriteList = []; var isPushA = false; //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png', 'img/character/enemy.png', 'img/bg/sky.png', 'img/bg/rock.png', 'img/bg/field.png', 'img/bullet.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //Zキー入力をaボタンとする game.keybind( 'Z'.charCodeAt(0), 'a' ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**背景の作成**/ bg( scene ); /**プレイヤーの作成**/ player = new Player(); spriteList.push( player ); //spriteListにプレイヤーをプッシュ var hitABullet = function() { //弾を作成 bullet = new Bullet(); spriteList.push( bullet ); isPushA = true; } /**シーン更新ごとに呼び出す**/ scene.onenterframe = function() { //15フレーム毎に敵を生成する if (game.frame % 15 === 0) { /**敵キャラの作成**/ enemy = new Enemy(); spriteList.push( enemy ); //spriteListに敵をプッシュ } /**aボタンが押された瞬間のみ弾を撃つ**/ if ( isPushA === false ) scene.addEventListener( 'abuttondown', hitABullet ) else scene.removeEventListener('abuttondown', hitABullet ); scene.addEventListener('abuttonup', function() { isPushA = false; } ); //画面をタップされた時 this.addEventListener( 'touchstart', hitABullet ); /**プレイヤーや敵などのスプライトを表示する**/ for(var i=0; i<spriteList.length; ++i) { scene.addChild( spriteList[i] ); } } /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.moveTo( 60, 180 ); //プレイヤーの初期位置 this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= speed; } } else this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= pad.vx * speed; } } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } if ( this.x < 0 ) this.x = 0; if ( this.y < 100 ) this.y = 100; if ( this.y > 260 ) this.y = 260; } } ); /**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 /**敵キャラのアニメーション**/ if (this.age%8 < 4) this.frame = 0; else this.frame = 1; } } ); var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock1 = new Sprite( 320, 320 ); rock1.image = game.assets[ 'img/bg/rock.png' ]; rock2 = new Sprite( 320, 320 ); rock2.image = game.assets[ 'img/bg/rock.png' ]; rock2.x = 320; scene.addChild( rock1 ); scene.addChild( rock2 ); /**地面のスプライトを表示**/ field1 = new Sprite( 320, 320 ); field1.image = game.assets[ 'img/bg/field.png' ]; field2 = new Sprite( 320, 320 ); field2.image = game.assets[ 'img/bg/field.png' ]; field2.x = 320; scene.addChild( field1 ); scene.addChild( field2 ); } /**弾のクラス**/ var Bullet = Class.create( Sprite, { initialize: function() { var bulletX, bulletY; //弾のX座標とY座標 Sprite.call( this, 6, 2 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/bullet.png' ]; //スプライトの画像ファイルを指定 //プレイヤーの向きによって弾の位置や動かす方向を変える if ( player.scaleX >= 0 ) { this.speed = 20; bulletX = player.x + 50; } else { this.speed = -20; bulletX = player.x - 5; } bulletY = player.y + 19; this.moveTo( bulletX, bulletY ); //弾の位置 }, onenterframe: function() { this.x += this.speed; //弾の移動 } } );
enchant.jsによるスプライトの削除方法。画面外に出た画像は削除する
画面の外に出たスプライトが表示されたままだと、ゲームが重たくなってしまうので、これを削除していきたいと思います。
enchant.jsでスプライトを削除するには、削除用のメソッドを作成する必要があります。
このように作ります。
/**配列の要素を削除するメソッド*/ Array.prototype.remove = function( elm ) { var index = this.indexOf( elm ); this.splice( index, 1 ); return this; }
これを敵キャラのクラスから呼び出します。
16〜20行目に追加しました。
/**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 /**敵キャラのアニメーション**/ if (this.age%8 < 4) this.frame = 0; else this.frame = 1; /**敵のX座標が-50以下になったら削除**/ if ( this.x < -50 ) { this.parentNode.removeChild(this); spriteList.remove( this ); } } } );
同じように、弾のクラスからも呼び出し、弾が画面の外に出たら消すようにします。
23〜27行目を追加します。
/**弾のクラス**/ var Bullet = Class.create( Sprite, { initialize: function() { var bulletX, bulletY; //弾のX座標とY座標 Sprite.call( this, 6, 2 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/bullet.png' ]; //スプライトの画像ファイルを指定 //プレイヤーの向きによって弾の位置や動かす方向を変える if ( player.scaleX >= 0 ) { this.speed = 20; bulletX = player.x + 50; } else { this.speed = -20; bulletX = player.x - 5; } bulletY = player.y + 19; this.moveTo( bulletX, bulletY ); //弾の位置 }, onenterframe: function() { this.x += this.speed; //弾の移動 /**弾が画面の外にでたら削除**/ if ( this.x < -10 || this.x > 330) { this.parentNode.removeChild(this); spriteList.remove( this ); } } } );
見た目上は分かりませんが、スプライトが画面の外に出ると消えているはずです。
もし不安ならば、スプライトの表示範囲を画面内に変更して、消えるのが確認してから、表示範囲を画面外に戻すといいと思います。
プログラム全体はこのようになります。
//おまじない enchant(); //変数宣言 var game; var pad; var player, bullet, enemy; var rock1, rock2, field1, field2; var spriteList = []; var isPushA = false; /**配列の要素を削除するメソッド*/ Array.prototype.remove = function( elm ) { var index = this.indexOf( elm ); this.splice( index, 1 ); return this; } //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png', 'img/character/enemy.png', 'img/bg/sky.png', 'img/bg/rock.png', 'img/bg/field.png', 'img/bullet.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //Zキー入力をaボタンとする game.keybind( 'Z'.charCodeAt(0), 'a' ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**背景の作成**/ bg( scene ); /**プレイヤーの作成**/ player = new Player(); spriteList.push( player ); //spriteListにプレイヤーをプッシュ var hitABullet = function() { //弾を作成 bullet = new Bullet(); spriteList.push( bullet ); isPushA = true; } /**シーン更新ごとに呼び出す**/ scene.onenterframe = function() { //15フレーム毎に敵を生成する if (game.frame % 15 === 0) { /**敵キャラの作成**/ enemy = new Enemy(); spriteList.push( enemy ); //spriteListに敵をプッシュ } /**aボタンが押された瞬間のみ弾を撃つ**/ if ( isPushA === false ) scene.addEventListener( 'abuttondown', hitABullet ) else scene.removeEventListener('abuttondown', hitABullet ); scene.addEventListener('abuttonup', function() { isPushA = false; } ); //画面をタップされた時 this.addEventListener( 'touchstart', hitABullet ); /**プレイヤーや敵などのスプライトを表示する**/ for(var i=0; i<spriteList.length; ++i) { scene.addChild( spriteList[i] ); } } /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.moveTo( 60, 180 ); //プレイヤーの初期位置 this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= speed; } } else this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= pad.vx * speed; } } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } if ( this.x < 0 ) this.x = 0; if ( this.y < 100 ) this.y = 100; if ( this.y > 260 ) this.y = 260; } } ); /**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 /**敵キャラのアニメーション**/ if (this.age%8 < 4) this.frame = 0; else this.frame = 1; /**敵のX座標が-50以下になったら削除**/ if ( this.x < -50 ) { this.parentNode.removeChild(this); spriteList.remove( this ); } } } ); var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock1 = new Sprite( 320, 320 ); rock1.image = game.assets[ 'img/bg/rock.png' ]; rock2 = new Sprite( 320, 320 ); rock2.image = game.assets[ 'img/bg/rock.png' ]; rock2.x = 320; scene.addChild( rock1 ); scene.addChild( rock2 ); /**地面のスプライトを表示**/ field1 = new Sprite( 320, 320 ); field1.image = game.assets[ 'img/bg/field.png' ]; field2 = new Sprite( 320, 320 ); field2.image = game.assets[ 'img/bg/field.png' ]; field2.x = 320; scene.addChild( field1 ); scene.addChild( field2 ); } /**弾のクラス**/ var Bullet = Class.create( Sprite, { initialize: function() { var bulletX, bulletY; //弾のX座標とY座標 Sprite.call( this, 6, 2 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/bullet.png' ]; //スプライトの画像ファイルを指定 //プレイヤーの向きによって弾の位置や動かす方向を変える if ( player.scaleX >= 0 ) { this.speed = 20; bulletX = player.x + 50; } else { this.speed = -20; bulletX = player.x - 5; } bulletY = player.y + 19; this.moveTo( bulletX, bulletY ); //弾の位置 }, onenterframe: function() { this.x += this.speed; //弾の移動 /**弾が画面の外にでたら削除**/ if ( this.x < -10 || this.x > 330) { this.parentNode.removeChild(this); spriteList.remove( this ); } } } );
スプライトの重なり順を変更する
続いて、スプライトの重なり順を変更していきたいと思います。
今の状態では、プレイヤーよりも敵の方が必ず手前に重なってしまうので、明らかにプレイヤーが手前にいても、敵キャラの方が上に重なってしまいます。
さて、これまでのプログラムではプレイヤーや敵のスプライトは全てspriteListの中にプッシュしています。
つまりspriteListの要素を、それぞれのスプライトのY座標の順番に並び替えれば、重なり順を変更できるようになります。
並び替えることをソートといいます。
ではそれぞれのスプライトの足の位置のY座標で、昇順ソートをしてみましょう。
//スプライトの足の位置でソート spriteList.sort( function( _spriteA, _spriteB ) { if ( _spriteA.y + _spriteA.height > _spriteB.y + _spriteB.height ) return 1; if ( _spriteA.y + _spriteA.height < _spriteB.y + _spriteB.height ) return -1; return 0; } );
(spriteListには弾も含まれますが、小さくて重なり順が分からないほどのものなので、今回は考えないものとします)
これでスプライトの重なり順がその時に応じて変わるようになりました。
プログラム全体ではこのようになります。
75〜80行目に追加しています。
//おまじない enchant(); //変数宣言 var game; var pad; var player, bullet, enemy; var rock1, rock2, field1, field2; var spriteList = []; var isPushA = false; /**配列の要素を削除するメソッド*/ Array.prototype.remove = function( elm ) { var index = this.indexOf( elm ); this.splice( index, 1 ); return this; } //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png', 'img/character/enemy.png', 'img/bg/sky.png', 'img/bg/rock.png', 'img/bg/field.png', 'img/bullet.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //Zキー入力をaボタンとする game.keybind( 'Z'.charCodeAt(0), 'a' ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**背景の作成**/ bg( scene ); /**プレイヤーの作成**/ player = new Player(); spriteList.push( player ); //spriteListにプレイヤーをプッシュ var hitABullet = function() { //弾を作成 bullet = new Bullet(); spriteList.push( bullet ); isPushA = true; } /**シーン更新ごとに呼び出す**/ scene.onenterframe = function() { //15フレーム毎に敵を生成する if (game.frame % 15 === 0) { /**敵キャラの作成**/ enemy = new Enemy(); spriteList.push( enemy ); //spriteListに敵をプッシュ } /**aボタンが押された瞬間のみ弾を撃つ**/ if ( isPushA === false ) scene.addEventListener( 'abuttondown', hitABullet ) else scene.removeEventListener('abuttondown', hitABullet ); scene.addEventListener('abuttonup', function() { isPushA = false; } ); //画面をタップされた時 this.addEventListener( 'touchstart', hitABullet ); //スプライトの足の位置でソート spriteList.sort( function( _spriteA, _spriteB ) { if ( _spriteA.y + _spriteA.height > _spriteB.y + _spriteB.height ) return 1; if ( _spriteA.y + _spriteA.height < _spriteB.y + _spriteB.height ) return -1; return 0; } ); /**プレイヤーや敵などのスプライトを表示する**/ for(var i=0; i<spriteList.length; ++i) { scene.addChild( spriteList[i] ); } } /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.moveTo( 60, 180 ); //プレイヤーの初期位置 this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= speed; } } else this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= pad.vx * speed; } } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } if ( this.x < 0 ) this.x = 0; if ( this.y < 100 ) this.y = 100; if ( this.y > 260 ) this.y = 260; } } ); /**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 /**敵キャラのアニメーション**/ if (this.age%8 < 4) this.frame = 0; else this.frame = 1; /**敵のX座標が-50以下になったら削除**/ if ( this.x < -50 ) { this.parentNode.removeChild(this); spriteList.remove( this ); } } } ); var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock1 = new Sprite( 320, 320 ); rock1.image = game.assets[ 'img/bg/rock.png' ]; rock2 = new Sprite( 320, 320 ); rock2.image = game.assets[ 'img/bg/rock.png' ]; rock2.x = 320; scene.addChild( rock1 ); scene.addChild( rock2 ); /**地面のスプライトを表示**/ field1 = new Sprite( 320, 320 ); field1.image = game.assets[ 'img/bg/field.png' ]; field2 = new Sprite( 320, 320 ); field2.image = game.assets[ 'img/bg/field.png' ]; field2.x = 320; scene.addChild( field1 ); scene.addChild( field2 ); } /**弾のクラス**/ var Bullet = Class.create( Sprite, { initialize: function() { var bulletX, bulletY; //弾のX座標とY座標 Sprite.call( this, 6, 2 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/bullet.png' ]; //スプライトの画像ファイルを指定 //プレイヤーの向きによって弾の位置や動かす方向を変える if ( player.scaleX >= 0 ) { this.speed = 20; bulletX = player.x + 50; } else { this.speed = -20; bulletX = player.x - 5; } bulletY = player.y + 19; this.moveTo( bulletX, bulletY ); //弾の位置 }, onenterframe: function() { this.x += this.speed; //弾の移動 /**弾が画面の外にでたら削除**/ if ( this.x < -10 || this.x > 330) { this.parentNode.removeChild(this); spriteList.remove( this ); } } } );
enchant.jsでの当たり判定の作り方
さて、ここまででゲームの形がほとんど完成しました。
ここで当たり判定を作っていこうと思います。
今回作る当たり判定は次の二つです。
- 敵と弾の当たり判定
- プレイヤーと敵の当たり判定
まず、敵と弾の当たり判定を作ってみましょう。
敵に弾が当たると敵が倒れる(消える)というプログラムを作ります。
そんなわけで、存在をオンオフするためのプロパティexistenceを、弾や敵に対して作ります。
つまり enemy.existence が0の時は存在せず(消す)、 enemy.existence が1の時は存在する(表示する)というふうにします。
敵クラスはこのようにします。
4行目で敵が存在するかどうかのプロパティthis.existenceに1を代入し、17〜21行目で、敵が画面の外に出た時の処理と一緒に、this.existenceが0の時も削除できるようにしました。
/**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { this.existence = 1; //敵が存在する Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 /**敵キャラのアニメーション**/ if (this.age%8 < 4) this.frame = 0; else this.frame = 1; /**敵のX座標が-50以下になったり、存在が0になったら削除**/ if ( this.x < -50 || this.existence === 0) { this.parentNode.removeChild(this); spriteList.remove( this ); } } } );
また、当たり判定には3つの方法があります。
intersectメソッドを使う方法、withinメソッドを使う方法、そして自分で作ってしまう方法です。
intersectメソッドは、スプライト同士がぶつかっているかを判定します。画像のサイズで判定するので、透明部分も当たり判定に含まれます。
withinメソッドはスプライトの中央からの距離で当たり判定をします。
弾と敵の当たり判定では少しでも当たっていれば当たりとしたいので、intersectメソッドを使うことにします。
弾クラスのonenterframeの中に、以下を書いていきます。
/**弾と敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === player ) continue; if ( sprite === this ) continue; if ( this.intersect( sprite ) ) { sprite.existence = 0; this.existence = 0; } }
弾クラス全体はこうなります。
30〜39行目に追加しました。
/**弾のクラス**/ var Bullet = Class.create( Sprite, { initialize: function() { var bulletX, bulletY; //弾のX座標とY座標 this.existence = 1; //弾が存在する Sprite.call( this, 6, 2 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/bullet.png' ]; //スプライトの画像ファイルを指定 //プレイヤーの向きによって弾の位置や動かす方向を変える if ( player.scaleX >= 0 ) { this.speed = 20; bulletX = player.x + 50; } else { this.speed = -20; bulletX = player.x - 5; } bulletY = player.y + 19; this.moveTo( bulletX, bulletY ); //弾の位置 }, onenterframe: function() { this.x += this.speed; //弾の移動 /**弾が画面の外に出たり、存在が0になったら削除**/ if ( this.x < -10 || this.x > 330 || this.existence === 0 ) { this.parentNode.removeChild(this); spriteList.remove( this ); } /**弾と敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === player ) continue; if ( sprite === this ) continue; if ( this.intersect( sprite ) ) { sprite.existence = 0; this.existence = 0; } } } } );
これで敵に弾が当たったとき、弾と敵が消えるようになりました。
つづいて、プレイヤーと敵の当たり判定を作ります。
しかし、まだゲームオーバー画面を作っていませんので、敵とぶつかった時は初期位置に戻すというふうにしてみます。
また、プレイヤーと敵との当たり判定は、敵と弾の当たり判定よりも重要なので、within()
を使って作成していきます。
プレイヤークラスのonenterframeに、以下のように書いていきます。
/**プレイヤーと敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; if ( sprite === bullet ) continue; if ( this.within( sprite, 20 ) ) { this.moveTo( 60, 180 ); } }
プログラム全体です。
//おまじない enchant(); //変数宣言 var game; var pad; var player, bullet, enemy; var rock1, rock2, field1, field2; var spriteList = []; var isPushA = false; /**配列の要素を削除するメソッド*/ Array.prototype.remove = function( elm ) { var index = this.indexOf( elm ); this.splice( index, 1 ); return this; } //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png', 'img/character/enemy.png', 'img/bg/sky.png', 'img/bg/rock.png', 'img/bg/field.png', 'img/bullet.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //Zキー入力をaボタンとする game.keybind( 'Z'.charCodeAt(0), 'a' ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**背景の作成**/ bg( scene ); /**プレイヤーの作成**/ player = new Player(); spriteList.push( player ); //spriteListにプレイヤーをプッシュ var hitABullet = function() { //弾を作成 bullet = new Bullet(); spriteList.push( bullet ); isPushA = true; } /**シーン更新ごとに呼び出す**/ scene.onenterframe = function() { //15フレーム毎に敵を生成する if (game.frame % 15 === 0) { /**敵キャラの作成**/ enemy = new Enemy(); spriteList.push( enemy ); //spriteListに敵をプッシュ } /**aボタンが押された瞬間のみ弾を撃つ**/ if ( isPushA === false ) scene.addEventListener( 'abuttondown', hitABullet ) else scene.removeEventListener('abuttondown', hitABullet ); scene.addEventListener('abuttonup', function() { isPushA = false; } ); //画面をタップされた時 this.addEventListener( 'touchstart', hitABullet ); //スプライトの足の位置でソート spriteList.sort( function( _spriteA, _spriteB ) { if ( _spriteA.y + _spriteA.height > _spriteB.y + _spriteB.height ) return 1; if ( _spriteA.y + _spriteA.height < _spriteB.y + _spriteB.height ) return -1; return 0; } ); /**プレイヤーや敵などのスプライトを表示する**/ for(var i=0; i<spriteList.length; ++i) { scene.addChild( spriteList[i] ); } } /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.moveTo( 60, 180 ); //プレイヤーの初期位置 this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= speed; } } else this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= pad.vx * speed; } } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } /**移動制限**/ if ( this.x < 0 ) this.x = 0; if ( this.y < 100 ) this.y = 100; if ( this.y > 260 ) this.y = 260; /**プレイヤーと敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; if ( sprite === bullet ) continue; if ( this.within( sprite, 20 ) ) { this.moveTo( 60, 180 ); } } } } ); /**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { this.existence = 1; //敵が存在する Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 /**敵キャラのアニメーション**/ if (this.age%8 < 4) this.frame = 0; else this.frame = 1; /**敵のX座標が-50以下になったり、存在が0になったら削除**/ if ( this.x < -50 || this.existence === 0) { this.parentNode.removeChild(this); spriteList.remove( this ); } } } ); var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock1 = new Sprite( 320, 320 ); rock1.image = game.assets[ 'img/bg/rock.png' ]; rock2 = new Sprite( 320, 320 ); rock2.image = game.assets[ 'img/bg/rock.png' ]; rock2.x = 320; scene.addChild( rock1 ); scene.addChild( rock2 ); /**地面のスプライトを表示**/ field1 = new Sprite( 320, 320 ); field1.image = game.assets[ 'img/bg/field.png' ]; field2 = new Sprite( 320, 320 ); field2.image = game.assets[ 'img/bg/field.png' ]; field2.x = 320; scene.addChild( field1 ); scene.addChild( field2 ); } /**弾のクラス**/ var Bullet = Class.create( Sprite, { initialize: function() { var bulletX, bulletY; //弾のX座標とY座標 this.existence = 1; //弾が存在する Sprite.call( this, 6, 2 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/bullet.png' ]; //スプライトの画像ファイルを指定 //プレイヤーの向きによって弾の位置や動かす方向を変える if ( player.scaleX >= 0 ) { this.speed = 20; bulletX = player.x + 50; } else { this.speed = -20; bulletX = player.x - 5; } bulletY = player.y + 19; this.moveTo( bulletX, bulletY ); //弾の位置 }, onenterframe: function() { this.x += this.speed; //弾の移動 /**弾が画面の外に出たり、存在が0になったら削除**/ if ( this.x < -10 || this.x > 330 || this.existence === 0 ) { this.parentNode.removeChild(this); spriteList.remove( this ); } /**弾と敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === player ) continue; if ( sprite === this ) continue; if ( this.intersect( sprite ) ) { sprite.existence = 0; this.existence = 0; } } } } );
Googleフォントを使う準備
さて、ここからはプレイヤーのHPを表示したり、ゲームオーバーの文字を表示したりと、フォントを使うところが出てきます。
このフォントが少し困るのです。
やはり全てのマシンで同じフォントを出したいのです。
そこでGoogleのWEBフォントを使ってみましょう。
今回はRusso Oneというフォントを使ってみようと思います。
これを使う準備として、index.htmlの6行目に以下のように追加します。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Snipe at Monsters</title> <link href="https://fonts.googleapis.com/css?family=Russo+One&display=swap" rel="stylesheet"> <script src="inc/enchant.js"></script> <script src="inc/ui.enchant.js"></script> <script src="js/main.js"></script> <style> body { margin: 0; padding: 0; } </style> </head> <body> </body> </html>
これでRusso Oneが使えるようになりました。
では続きを作っていきましょう。
プレイヤーのHPを作る
つづいて、プレイヤーのHPを作っていきましょう。
プレイヤークラスの初期化部分でHPを2に設定し、敵とぶつかったら1ずつ引いていきます。
4行目、20行目を追加しました。
/**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { this.hp = 2; //プレイヤーのHP Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.moveTo( 60, 180 ); //プレイヤーの初期位置 this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { //〜省略〜 /**プレイヤーと敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; if ( sprite === bullet ) continue; if ( this.within( sprite, 20 ) ) { this.moveTo( 60, 180 ); this.hp--; //プレイヤーのHPから1ずつ引いていく } } } } );
このままではプレイヤーのHPが分からないので、先ほど準備したGoogleフォントを使ってHPを表示させてみましょう。
このように作ります。
/**HPラベルを作成**/ var hpLabel = new Label(); hpLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定
/*プレイヤーのHPを表示**/ hpLabel.text = 'HP : ' + player.hp; mainScene.addChild( hpLabel );
全体ではこのようになります。
44〜46行目でHPラベルの作成、
//おまじない enchant(); //変数宣言 var game; var pad; var player, bullet, enemy; var rock1, rock2, field1, field2; var spriteList = []; var isPushA = false; /**配列の要素を削除するメソッド*/ Array.prototype.remove = function( elm ) { var index = this.indexOf( elm ); this.splice( index, 1 ); return this; } //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png', 'img/character/enemy.png', 'img/bg/sky.png', 'img/bg/rock.png', 'img/bg/field.png', 'img/bullet.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //Zキー入力をaボタンとする game.keybind( 'Z'.charCodeAt(0), 'a' ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**背景の作成**/ bg( scene ); /**プレイヤーの作成**/ player = new Player(); spriteList.push( player ); //spriteListにプレイヤーをプッシュ /**HPラベルを作成**/ var hpLabel = new Label(); hpLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 var hitABullet = function() { //弾を作成 bullet = new Bullet(); spriteList.push( bullet ); isPushA = true; } /**シーン更新ごとに呼び出す**/ scene.onenterframe = function() { //15フレーム毎に敵を生成する if (game.frame % 15 === 0) { /**敵キャラの作成**/ enemy = new Enemy(); spriteList.push( enemy ); //spriteListに敵をプッシュ } /**aボタンが押された瞬間のみ弾を撃つ**/ if ( isPushA === false ) scene.addEventListener( 'abuttondown', hitABullet ) else scene.removeEventListener('abuttondown', hitABullet ); scene.addEventListener('abuttonup', function() { isPushA = false; } ); //画面をタップされた時 this.addEventListener( 'touchstart', hitABullet ); //スプライトの足の位置でソート spriteList.sort( function( _spriteA, _spriteB ) { if ( _spriteA.y + _spriteA.height > _spriteB.y + _spriteB.height ) return 1; if ( _spriteA.y + _spriteA.height < _spriteB.y + _spriteB.height ) return -1; return 0; } ); /**プレイヤーや敵などのスプライトを表示する**/ for(var i=0; i<spriteList.length; ++i) { scene.addChild( spriteList[i] ); } /*プレイヤーのHPを表示**/ hpLabel.text = 'HP : ' + player.hp; scene.addChild( hpLabel ); } /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.hp = 2; //プレイヤーのHP this.moveTo( 60, 180 ); //プレイヤーの初期位置 this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= speed; } } else this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= pad.vx * speed; } } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } /**移動制限**/ if ( this.x < 0 ) this.x = 0; if ( this.y < 100 ) this.y = 100; if ( this.y > 260 ) this.y = 260; /**プレイヤーと敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; if ( sprite === bullet ) continue; if ( this.within( sprite, 20 ) ) { this.moveTo( 60, 180 ); this.hp--; //プレイヤーのHPから1ずつ引いていく } } } } ); /**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { this.existence = 1; //敵が存在する Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 /**敵キャラのアニメーション**/ if (this.age%8 < 4) this.frame = 0; else this.frame = 1; /**敵のX座標が-50以下になったり、存在が0になったら削除**/ if ( this.x < -50 || this.existence === 0) { this.parentNode.removeChild(this); spriteList.remove( this ); } } } ); var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock1 = new Sprite( 320, 320 ); rock1.image = game.assets[ 'img/bg/rock.png' ]; rock2 = new Sprite( 320, 320 ); rock2.image = game.assets[ 'img/bg/rock.png' ]; rock2.x = 320; scene.addChild( rock1 ); scene.addChild( rock2 ); /**地面のスプライトを表示**/ field1 = new Sprite( 320, 320 ); field1.image = game.assets[ 'img/bg/field.png' ]; field2 = new Sprite( 320, 320 ); field2.image = game.assets[ 'img/bg/field.png' ]; field2.x = 320; scene.addChild( field1 ); scene.addChild( field2 ); } /**弾のクラス**/ var Bullet = Class.create( Sprite, { initialize: function() { var bulletX, bulletY; //弾のX座標とY座標 this.existence = 1; //弾が存在する Sprite.call( this, 6, 2 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/bullet.png' ]; //スプライトの画像ファイルを指定 //プレイヤーの向きによって弾の位置や動かす方向を変える if ( player.scaleX >= 0 ) { this.speed = 20; bulletX = player.x + 50; } else { this.speed = -20; bulletX = player.x - 5; } bulletY = player.y + 19; this.moveTo( bulletX, bulletY ); //弾の位置 }, onenterframe: function() { this.x += this.speed; //弾の移動 /**弾が画面の外に出たり、存在が0になったら削除**/ if ( this.x < -10 || this.x > 330 || this.existence === 0 ) { this.parentNode.removeChild(this); spriteList.remove( this ); } /**弾と敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === player ) continue; if ( sprite === this ) continue; if ( this.intersect( sprite ) ) { sprite.existence = 0; this.existence = 0; } } } } );
しかしこのままではHPがどんどん減っていくだけでゲームオーバーにはなりません。
そこでゲームオーバーのシーンを作成し、HPが0になったらゲームオーバーというふうにしていきましょう。
enchant.jsでのシーンの作り方。ゲームオーバーのシーンの作成
つづいて、ゲームオーバーのシーンを作ってみましょう。
今までmainSceneというシーンにゲームを作ってきました。
今度はgameOverSceneというシーンを作成します。
ゲームオーバーのシーンは、真っ黒な画面に「GAME OVER」と表示したいのです。
では、ゲームオーバーシーンを作成していきます。
/**ゲームオーバーシーン**/ game.gameOverScene = function() { var scene = new Scene(); scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす var gameOverLabel = new Label( 'GAME OVER' ); //GAME OVERのラベルを作成 gameOverLabel.color = 'white'; //フォントの色を白にする gameOverLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 gameOverLabel.moveTo( 65, 150 ); //ラベルの位置 scene.addChild( gameOverLabel ); //ラベルをgameOverSceneに追加 return scene; }
ゲームオーバーシーンを見るにはシーンを切り替える必要があります。
シーンの切り替えはこのあと書いていきますが、実際にゲームオーバーシーンを見てみると、このようになります。
enchant.jsでのシーンを切り替える方法
それでは、プレイヤーのHPが0になった場合に、ゲームオーバーシーンに切り替えてみましょう。
/**プレイヤーのHPが0未満ならばシーンを切り替える**/ if ( player.hp === 0 ) game.replaceScene( game.gameOverScene() );
これでシーンを切り替えることができます。
全体のコードはこちらです。
//おまじない enchant(); //変数宣言 var game; var pad; var player, bullet, enemy; var rock1, rock2, field1, field2; var spriteList = []; var isPushA = false; /**配列の要素を削除するメソッド*/ Array.prototype.remove = function( elm ) { var index = this.indexOf( elm ); this.splice( index, 1 ); return this; } //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png', 'img/character/enemy.png', 'img/bg/sky.png', 'img/bg/rock.png', 'img/bg/field.png', 'img/bullet.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //Zキー入力をaボタンとする game.keybind( 'Z'.charCodeAt(0), 'a' ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**背景の作成**/ bg( scene ); /**プレイヤーの作成**/ player = new Player(); spriteList.push( player ); //spriteListにプレイヤーをプッシュ /**HPラベルを作成**/ var hpLabel = new Label(); hpLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 var hitABullet = function() { //弾を作成 bullet = new Bullet(); spriteList.push( bullet ); isPushA = true; } /**シーン更新ごとに呼び出す**/ scene.onenterframe = function() { //15フレーム毎に敵を生成する if (game.frame % 15 === 0) { /**敵キャラの作成**/ enemy = new Enemy(); spriteList.push( enemy ); //spriteListに敵をプッシュ } /**aボタンが押された瞬間のみ弾を撃つ**/ if ( isPushA === false ) scene.addEventListener( 'abuttondown', hitABullet ) else scene.removeEventListener('abuttondown', hitABullet ); scene.addEventListener('abuttonup', function() { isPushA = false; } ); //画面をタップされた時 this.addEventListener( 'touchstart', hitABullet ); //スプライトの足の位置でソート spriteList.sort( function( _spriteA, _spriteB ) { if ( _spriteA.y + _spriteA.height > _spriteB.y + _spriteB.height ) return 1; if ( _spriteA.y + _spriteA.height < _spriteB.y + _spriteB.height ) return -1; return 0; } ); /**プレイヤーや敵などのスプライトを表示する**/ for(var i=0; i<spriteList.length; ++i) { scene.addChild( spriteList[i] ); } /*プレイヤーのHPを表示**/ hpLabel.text = 'HP : ' + player.hp; scene.addChild( hpLabel ); /**プレイヤーのHPが0ならばシーンを切り替える**/ if ( player.hp === 0 ) game.replaceScene( game.gameOverScene() ); } /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } /**ゲームオーバーシーン**/ game.gameOverScene = function() { var scene = new Scene(); scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす var gameOverLabel = new Label( 'GAME OVER' ); //GAME OVERのラベルを作成 gameOverLabel.color = 'white'; //フォントの色を白にする gameOverLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 gameOverLabel.moveTo( 65, 150 ); //ラベルの位置 scene.addChild( gameOverLabel ); //ラベルをgameOverSceneに追加 return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.hp = 2; //プレイヤーのHP this.moveTo( 60, 180 ); //プレイヤーの初期位置 this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= speed; } } else this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= pad.vx * speed; } } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } /**移動制限**/ if ( this.x < 0 ) this.x = 0; if ( this.y < 100 ) this.y = 100; if ( this.y > 260 ) this.y = 260; /**プレイヤーと敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; if ( sprite === bullet ) continue; if ( this.within( sprite, 20 ) ) { this.moveTo( 60, 180 ); this.hp--; //プレイヤーのHPから1ずつ引いていく } } } } ); /**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { this.existence = 1; //敵が存在する Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 /**敵キャラのアニメーション**/ if (this.age%8 < 4) this.frame = 0; else this.frame = 1; /**敵のX座標が-50以下になったり、存在が0になったら削除**/ if ( this.x < -50 || this.existence === 0) { this.parentNode.removeChild(this); spriteList.remove( this ); } } } ); var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock1 = new Sprite( 320, 320 ); rock1.image = game.assets[ 'img/bg/rock.png' ]; rock2 = new Sprite( 320, 320 ); rock2.image = game.assets[ 'img/bg/rock.png' ]; rock2.x = 320; scene.addChild( rock1 ); scene.addChild( rock2 ); /**地面のスプライトを表示**/ field1 = new Sprite( 320, 320 ); field1.image = game.assets[ 'img/bg/field.png' ]; field2 = new Sprite( 320, 320 ); field2.image = game.assets[ 'img/bg/field.png' ]; field2.x = 320; scene.addChild( field1 ); scene.addChild( field2 ); } /**弾のクラス**/ var Bullet = Class.create( Sprite, { initialize: function() { var bulletX, bulletY; //弾のX座標とY座標 this.existence = 1; //弾が存在する Sprite.call( this, 6, 2 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/bullet.png' ]; //スプライトの画像ファイルを指定 //プレイヤーの向きによって弾の位置や動かす方向を変える if ( player.scaleX >= 0 ) { this.speed = 20; bulletX = player.x + 50; } else { this.speed = -20; bulletX = player.x - 5; } bulletY = player.y + 19; this.moveTo( bulletX, bulletY ); //弾の位置 }, onenterframe: function() { this.x += this.speed; //弾の移動 /**弾が画面の外に出たり、存在が0になったら削除**/ if ( this.x < -10 || this.x > 330 || this.existence === 0 ) { this.parentNode.removeChild(this); spriteList.remove( this ); } /**弾と敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === player ) continue; if ( sprite === this ) continue; if ( this.intersect( sprite ) ) { sprite.existence = 0; this.existence = 0; } } } } );
得点を表示する
続いて、得点を表示したいと思います。
今回のゲームの得点計算の方法は、「進んだ距離 + 倒した敵のポイント」としたいと思います。
まずグローバル変数としてスコアを宣言します。
まだ変数の初期化処理を作っていないので、あらかじめ0を代入しておきましょう。
var score = 0;
敵キャラを倒した時の処理(弾のクラス)で、スコアを加算します。
/**弾と敵の当たり判定**/ for ( var i=0; i<spritelist.length; i++ ) { var sprite = spritelist[i]; if ( sprite === player ) continue; if ( sprite === this ) continue; if ( this.intersect( sprite ) ) { sprite.existence = 0; this.existence = 0; score += 200; //scoreに200を加算 } }
さらに移動した距離(プレイヤーのクラス)で、背景のスクロールと一緒にスコアを加算します。
if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= speed; } score += 5; //進めば進むほどスコアが加算される } else this.x += speed; }
同じようにアナログパッドで右に移動したときのスコアも加算していきます。
/**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= pad.vx * speed; } score += 5; //進めば進むほどスコアが加算される } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; }
さらにスコア用のラベルを作成します。
/**スコアラベルを作成**/ var scoreLabel = new Label(); scoreLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定
スコアの表示は以下のようにします。
/**スコアを表示**/ scoreLabel.text = 'SCORE : ' + score; scoreLabel.y = 30; scene.addChild( scoreLabel );
プログラム全体はこのようになります。
//おまじない enchant(); //変数宣言 var game; var pad; var player, bullet, enemy; var rock1, rock2, field1, field2; var spriteList = []; var isPushA = false; var score = 0; /**配列の要素を削除するメソッド*/ Array.prototype.remove = function( elm ) { var index = this.indexOf( elm ); this.splice( index, 1 ); return this; } //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png', 'img/character/enemy.png', 'img/bg/sky.png', 'img/bg/rock.png', 'img/bg/field.png', 'img/bullet.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.mainScene() ); //シーンをゲームに追加する } ); //Zキー入力をaボタンとする game.keybind( 'Z'.charCodeAt(0), 'a' ); //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**背景の作成**/ bg( scene ); /**プレイヤーの作成**/ player = new Player(); spriteList.push( player ); //spriteListにプレイヤーをプッシュ /**HPラベルを作成**/ var hpLabel = new Label(); hpLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 /**スコアラベルを作成**/ var scoreLabel = new Label(); scoreLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 var hitABullet = function() { //弾を作成 bullet = new Bullet(); spriteList.push( bullet ); isPushA = true; } /**シーン更新ごとに呼び出す**/ scene.onenterframe = function() { //15フレーム毎に敵を生成する if (game.frame % 15 === 0) { /**敵キャラの作成**/ enemy = new Enemy(); spriteList.push( enemy ); //spriteListに敵をプッシュ } /**aボタンが押された瞬間のみ弾を撃つ**/ if ( isPushA === false ) scene.addEventListener( 'abuttondown', hitABullet ) else scene.removeEventListener('abuttondown', hitABullet ); scene.addEventListener('abuttonup', function() { isPushA = false; } ); //画面をタップされた時 this.addEventListener( 'touchstart', hitABullet ); //スプライトの足の位置でソート spriteList.sort( function( _spriteA, _spriteB ) { if ( _spriteA.y + _spriteA.height > _spriteB.y + _spriteB.height ) return 1; if ( _spriteA.y + _spriteA.height < _spriteB.y + _spriteB.height ) return -1; return 0; } ); /**プレイヤーや敵などのスプライトを表示する**/ for(var i=0; i<spriteList.length; ++i) { scene.addChild( spriteList[i] ); } /*プレイヤーのHPを表示**/ hpLabel.text = 'HP : ' + player.hp; scene.addChild( hpLabel ); /**スコアを表示**/ scoreLabel.text = 'SCORE : ' + score; scoreLabel.y = 30; scene.addChild( scoreLabel ); /**プレイヤーのHPが0ならばシーンを切り替える**/ if ( player.hp === 0 ) game.replaceScene( game.gameOverScene() ); } /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } /**ゲームオーバーシーン**/ game.gameOverScene = function() { var scene = new Scene(); scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす var gameOverLabel = new Label( 'GAME OVER' ); //GAME OVERのラベルを作成 gameOverLabel.color = 'white'; //フォントの色を白にする gameOverLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 gameOverLabel.moveTo( 65, 150 ); //ラベルの位置 scene.addChild( gameOverLabel ); //ラベルをgameOverSceneに追加 return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.hp = 2; //プレイヤーのHP this.moveTo( 60, 180 ); //プレイヤーの初期位置 this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= speed; } score += 5; //進めば進むほどスコアが加算される } else this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= pad.vx * speed; } score += 5; //進めば進むほどスコアが加算される } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } /**移動制限**/ if ( this.x < 0 ) this.x = 0; if ( this.y < 100 ) this.y = 100; if ( this.y > 260 ) this.y = 260; /**プレイヤーと敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; if ( sprite === bullet ) continue; if ( this.within( sprite, 20 ) ) { this.moveTo( 60, 180 ); this.hp--; //プレイヤーのHPから1ずつ引いていく } } } } ); /**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { this.existence = 1; //敵が存在する Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 /**敵キャラのアニメーション**/ if (this.age%8 < 4) this.frame = 0; else this.frame = 1; /**敵のX座標が-50以下になったり、存在が0になったら削除**/ if ( this.x < -50 || this.existence === 0) { this.parentNode.removeChild(this); spriteList.remove( this ); } } } ); var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock1 = new Sprite( 320, 320 ); rock1.image = game.assets[ 'img/bg/rock.png' ]; rock2 = new Sprite( 320, 320 ); rock2.image = game.assets[ 'img/bg/rock.png' ]; rock2.x = 320; scene.addChild( rock1 ); scene.addChild( rock2 ); /**地面のスプライトを表示**/ field1 = new Sprite( 320, 320 ); field1.image = game.assets[ 'img/bg/field.png' ]; field2 = new Sprite( 320, 320 ); field2.image = game.assets[ 'img/bg/field.png' ]; field2.x = 320; scene.addChild( field1 ); scene.addChild( field2 ); } /**弾のクラス**/ var Bullet = Class.create( Sprite, { initialize: function() { var bulletX, bulletY; //弾のX座標とY座標 this.existence = 1; //弾が存在する Sprite.call( this, 6, 2 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/bullet.png' ]; //スプライトの画像ファイルを指定 //プレイヤーの向きによって弾の位置や動かす方向を変える if ( player.scaleX >= 0 ) { this.speed = 20; bulletX = player.x + 50; } else { this.speed = -20; bulletX = player.x - 5; } bulletY = player.y + 19; this.moveTo( bulletX, bulletY ); //弾の位置 }, onenterframe: function() { this.x += this.speed; //弾の移動 /**弾が画面の外に出たり、存在が0になったら削除**/ if ( this.x < -10 || this.x > 330 || this.existence === 0 ) { this.parentNode.removeChild(this); spriteList.remove( this ); } /**弾と敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === player ) continue; if ( sprite === this ) continue; if ( this.intersect( sprite ) ) { sprite.existence = 0; this.existence = 0; score += 200; //scoreに200を加算 } } } } );
ついでにゲームオーバー画面でもスコアを表示できるようにします。
ゲームオーバーシーンを次のように書き換えます。
/**ゲームオーバーシーン**/ game.gameOverScene = function() { var scene = new Scene(); scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす var gameOverLabel = new Label( 'GAME OVER' ); //GAME OVERのラベルを作成 gameOverLabel.color = 'white'; //フォントの色を白にする gameOverLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 gameOverLabel.moveTo( 65, 150 ); //ラベルの位置 scene.addChild( gameOverLabel ); //ラベルをgameOverSceneに追加 /**スコアを表示**/ var gameOverScoreLabel = new Label(); gameOverScoreLabel.text = 'SCORE : ' + score; gameOverScoreLabel.moveTo( 180, 250 ); gameOverScoreLabel.font = "16px 'Russo One', sans-serif"; //フォントの設定 gameOverScoreLabel.color = 'white'; scene.addChild( gameOverScoreLabel ); /**aボタンかタップでメインゲームに戻る**/ scene.addEventListener( 'abuttondown', returnGame ); scene.addEventListener( 'touchstart', returnGame ); function returnGame() { init(); //初期化 game.replaceScene( game.mainScene() ); //シーンの切り替え game.removeEventListener( 'abuttondown', returnGame ); } return scene; }
変数の初期値をリセットする
さて、ゲームオーバーになった時、ゲーム画面に戻ろうとしても変数の値がそのままなので、再びゲームを始めることができません。
そこで変数を初期化する関数を作りましょう。
といっても、今回初期化する変数はたった3つです。
このような関数を作ります。
var init = function() { score = 0; player.hp = 2; spriteList = []; }
これをゲームオーバー画面からメインゲームに戻る直前に呼び出します。
しかしまだ、ゲームオーバー画面からメインゲームに戻る処理を作っていないので、そちらも同時に作りましょう。
ゲームオーバーシーンを以下のようにします。
15行目で初期化しております。
/**ゲームオーバーシーン**/ game.gameOverScene = function() { var scene = new Scene(); scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす var gameOverLabel = new Label( 'GAME OVER' ); //GAME OVERのラベルを作成 gameOverLabel.color = 'white'; //フォントの色を白にする gameOverLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 gameOverLabel.moveTo( 65, 150 ); //ラベルの位置 scene.addChild( gameOverLabel ); //ラベルをgameOverSceneに追加 /**aボタンかタップでメインゲームに戻る**/ scene.addEventListener( 'abuttondown', returnGame ); scene.addEventListener( 'touchstart', returnGame ); function returnGame() { init(); //初期化 game.replaceScene( game.mainScene() ); //シーンの切り替え game.removeEventListener( 'abuttondown', returnGame ); } return scene; }
これでゲームオーバーから再スタート時に初期化されるようになりました。
敵と当たった後、一定時間プレイヤーを無敵状態にする
敵と当たった後、一定時間プレイヤーを無敵状態にしてみましょう。
まずプレイヤークラスの初期化部分で、無敵時間のプロパティを初期化します。
this.unrivaledTime = 0; //プレイヤーの無敵時間
さらにプレイヤークラスの敵との当たり判定の部分で、プレイヤーを点滅させる処理と、プレイヤーを無敵にする処理を加えます。
/**プレイヤーと敵の当たり判定**/ if ( this.unrivaledTime === 0 ) { for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; if ( sprite === bullet ) continue; if ( this.within( sprite, 20 ) ) { this.moveTo( 60, 180 ); this.hp--; //プレイヤーのHPから1ずつ引いていく this.unrivaledTime = 30 //無敵時間 } } } else { this.unrivaledTime--; this.opacity = this.unrivaledTime%3 }
スタート画面を作る
最後の仕上げとして、スタート画面を作ります。
今の状態では、開いてすぐにゲームが始まってしまうので、「始めるぞー」という緊張感もなく、「あれ? もう始まってる」というふうになってしまいます。
それではシーンを作成して、スタート画面をつくりましょう。
//スタート(タイトル)シーン game.startScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす var titleLabel = new Label( 'Snipe at Monsters' ); //タイトルのラベルを作成 titleLabel.color = 'white'; //フォントの色を白にする titleLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 titleLabel.moveTo( 10, 100 ); //ラベルの位置 scene.addChild( titleLabel ); //ラベルをsceneに追加 var descriptionLabel = new Label( 'Tap or press Z' ); //説明のラベルを作成 descriptionLabel.color = 'white'; //フォントの色を白にする descriptionLabel.font = "16px 'Russo One', sans-serif"; //フォントの設定 descriptionLabel.moveTo( 105, 200 ); //ラベルの位置 scene.addChild( descriptionLabel ); //ラベルをsceneに追加 /**aボタンかタップでメインゲームへ進む**/ scene.addEventListener( 'abuttondown', goToTheGame ); scene.addEventListener( 'touchstart', goToTheGame ); function goToTheGame() { game.replaceScene( game.mainScene() ); //シーンの切り替え game.removeEventListener( 'abuttondown', goToTheGame ); } return scene; }
これでタイトルシーンが作成されました。
もちろん、ゲーム開始時にはタイトルシーンを表示するように書き換える必要があります。
//ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.startScene() ); //シーンをゲームに追加する } );
全体のプログラム
最後に、今回作成した全体のプログラムを見ておきましょう。
//おまじない enchant(); //変数宣言 var game; var pad; var player, bullet, enemy; var rock1, rock2, field1, field2; var spriteList = []; var isPushA = false; var score = 0; var init = function() { score = 0; player.hp = 2; spriteList = []; } /**配列の要素を削除するメソッド*/ Array.prototype.remove = function( elm ) { var index = this.indexOf( elm ); this.splice( index, 1 ); return this; } //Webページが読み込まれたら addEventListener( 'load', function() { game = new Game(320,320); //ゲームオブジェクトの作成 game.preload( 'img/character/player.png', 'img/character/enemy.png', 'img/bg/sky.png', 'img/bg/rock.png', 'img/bg/field.png', 'img/bullet.png' ); //画像をプリロード //ゲームオブジェクトが読み込まれたら game.addEventListener( 'load', function() { game.pushScene( game.startScene() ); //シーンをゲームに追加する } ); //Zキー入力をaボタンとする game.keybind( 'Z'.charCodeAt(0), 'a' ); //スタート(タイトル)シーン game.startScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす var titleLabel = new Label( 'Snipe at Monsters' ); //タイトルのラベルを作成 titleLabel.color = 'white'; //フォントの色を白にする titleLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 titleLabel.moveTo( 10, 100 ); //ラベルの位置 scene.addChild( titleLabel ); //ラベルをsceneに追加 var descriptionLabel = new Label( 'Tap or press Z' ); //説明のラベルを作成 descriptionLabel.color = 'white'; //フォントの色を白にする descriptionLabel.font = "16px 'Russo One', sans-serif"; //フォントの設定 descriptionLabel.moveTo( 105, 200 ); //ラベルの位置 scene.addChild( descriptionLabel ); //ラベルをsceneに追加 /**aボタンかタップでメインゲームへ進む**/ scene.addEventListener( 'abuttondown', goToTheGame ); scene.addEventListener( 'touchstart', goToTheGame ); function goToTheGame() { game.replaceScene( game.mainScene() ); //シーンの切り替え game.removeEventListener( 'abuttondown', goToTheGame ); } return scene; } //メインシーン game.mainScene = function() { var scene = new Scene(); //シーンを作成 scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす /**背景の作成**/ bg( scene ); /**プレイヤーの作成**/ player = new Player(); spriteList.push( player ); //spriteListにプレイヤーをプッシュ /**HPラベルを作成**/ var hpLabel = new Label(); hpLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 /**スコアラベルを作成**/ var scoreLabel = new Label(); scoreLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 var hitABullet = function() { //弾を作成 bullet = new Bullet(); spriteList.push( bullet ); isPushA = true; } /**シーン更新ごとに呼び出す**/ scene.onenterframe = function() { //15フレーム毎に敵を生成する if (game.frame % 15 === 0) { /**敵キャラの作成**/ enemy = new Enemy(); spriteList.push( enemy ); //spriteListに敵をプッシュ } /**aボタンが押された瞬間のみ弾を撃つ**/ if ( isPushA === false ) scene.addEventListener( 'abuttondown', hitABullet ) else scene.removeEventListener('abuttondown', hitABullet ); scene.addEventListener('abuttonup', function() { isPushA = false; } ); //画面をタップされた時 this.addEventListener( 'touchstart', hitABullet ); //スプライトの足の位置でソート spriteList.sort( function( _spriteA, _spriteB ) { if ( _spriteA.y + _spriteA.height > _spriteB.y + _spriteB.height ) return 1; if ( _spriteA.y + _spriteA.height < _spriteB.y + _spriteB.height ) return -1; return 0; } ); /**プレイヤーや敵などのスプライトを表示する**/ for(var i=0; i<spriteList.length; ++i) { scene.addChild( spriteList[i] ); } /*プレイヤーのHPを表示**/ hpLabel.text = 'HP : ' + player.hp; scene.addChild( hpLabel ); /**スコアを表示**/ scoreLabel.text = 'SCORE : ' + score; scoreLabel.y = 30; scene.addChild( scoreLabel ); /**プレイヤーのHPが0ならばシーンを切り替える**/ if ( player.hp === 0 ) game.replaceScene( game.gameOverScene() ); } /**アナログパッドの表示**/ pad = new APad(); pad.x = 20; pad.y = 220; scene.addChild( pad ); return scene; } /**ゲームオーバーシーン**/ game.gameOverScene = function() { var scene = new Scene(); scene.backgroundColor = 'black'; //シーンを黒く塗りつぶす var gameOverLabel = new Label( 'GAME OVER' ); //GAME OVERのラベルを作成 gameOverLabel.color = 'white'; //フォントの色を白にする gameOverLabel.font = "32px 'Russo One', sans-serif"; //フォントの設定 gameOverLabel.moveTo( 65, 150 ); //ラベルの位置 scene.addChild( gameOverLabel ); //ラベルをgameOverSceneに追加 /**スコアを表示**/ var gameOverScoreLabel = new Label(); gameOverScoreLabel.text = 'SCORE : ' + score; gameOverScoreLabel.moveTo( 180, 250 ); gameOverScoreLabel.font = "16px 'Russo One', sans-serif"; //フォントの設定 gameOverScoreLabel.color = 'white'; scene.addChild( gameOverScoreLabel ); /**aボタンかタップでメインゲームに戻る**/ scene.addEventListener( 'abuttondown', returnGame ); scene.addEventListener( 'touchstart', returnGame ); function returnGame() { init(); //初期化 game.replaceScene( game.mainScene() ); //シーンの切り替え game.removeEventListener( 'abuttondown', returnGame ); } return scene; } game.start(); //ゲームスタート } ); /**プレイヤーのクラス**/ var Player = Class.create( Sprite, { initialize: function() { Sprite.call( this, 51, 55 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.hp = 2; //プレイヤーのHP this.moveTo( 60, 180 ); //プレイヤーの初期位置 this.image = game.assets[ 'img/character/player.png' ]; //スプライトの画像ファイルを指定 this.unrivaledTime = 0; //プレイヤーの無敵時間 }, onenterframe: function() { this.frame = 1; //プレイヤーの画像を左から2番目に変更 var speed = 14; //プレイヤーの動く速度 /**キー入力があった時のプレイヤーの移動**/ if ( game.input.left ) { this.scaleX = -1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション this.x -= speed; } if ( game.input.right ) { this.scaleX = 1; //キャラ画像をX方向に反転 this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width/2 ) { field1.x -= speed; //地面をスクロール field2.x -= speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= speed/2; //岩をスクロール rock2.x -= speed/2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= speed; } score += 5; //進めば進むほどスコアが加算される } else this.x += speed; } if ( game.input.up ) { this.frame = this.age%2+2; //アニメーション this.y -= speed; } if ( game.input.down ) { this.frame = this.age%2+2; //アニメーション this.y += speed; } /**アナログパッドでのプレイヤーの移動**/ if ( pad.isTouched ) { this.frame = this.age%2+2; //アニメーション //プレイヤーのX座標が、半分の位置より大きい時 if ( this.x > 160 - this.width / 2 && pad.vx > 0) { field1.x -= pad.vx * speed; //地面をスクロール field2.x -= pad.vx * speed; //地面をスクロール if ( field1.x <= -320 ) field1.x = field2.x + 320; if ( field2.x <= -320 ) field2.x = field1.x + 320; rock1.x -= pad.vx * speed / 2; //岩をスクロール rock2.x -= pad.vx * speed / 2; //岩をスクロール if ( rock1.x <= -320 ) rock1.x = rock2.x + 320; if ( rock2.x <= -320 ) rock2.x = rock1.x + 320; /**敵キャラのスクロール**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; sprite.x -= pad.vx * speed; } score += 5; //進めば進むほどスコアが加算される } else this.x += pad.vx * speed; this.y += pad.vy * speed; /**X方向にゆっくり移動しているときは歩かせる**/ if ( Math.abs( pad.vx ) < 0.5 ) { if ( this.age%8 < 4 ) this.frame = 0; else this.frame = 1; } else this.frame = this.age%2+2; /**キャラが横に移動した時、キャラ画像をX方向に反転**/ if ( pad.vx < 0 ) this.scaleX = -1; else this.scaleX = 1; } /**移動制限**/ if ( this.x < 0 ) this.x = 0; if ( this.y < 100 ) this.y = 100; if ( this.y > 260 ) this.y = 260; this.opacity = 1; /**プレイヤーと敵の当たり判定**/ if ( this.unrivaledTime === 0 ) { for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === this ) continue; if ( sprite === bullet ) continue; if ( this.within( sprite, 20 ) ) { this.moveTo( 60, 180 ); this.hp--; //プレイヤーのHPから1ずつ引いていく this.unrivaledTime = 30 //無敵時間 } } } else { this.unrivaledTime--; this.opacity = this.unrivaledTime%3 } } } ); /**敵キャラのクラス**/ var Enemy = Class.create( Sprite, { initialize: function() { this.existence = 1; //敵が存在する Sprite.call( this, 38, 47 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/character/enemy.png' ]; //スプライトの画像ファイルを指定 var rnd = Math.random() * ( 170 ); //0〜169までのランダムな数値を作成 this.moveTo( 330, 100 + rnd ); //敵キャラの初期位置 }, onenterframe: function() { this.x -= 3; //敵キャラの移動 /**敵キャラのアニメーション**/ if (this.age%8 < 4) this.frame = 0; else this.frame = 1; /**敵のX座標が-50以下になったり、存在が0になったら削除**/ if ( this.x < -50 || this.existence === 0) { this.parentNode.removeChild(this); spriteList.remove( this ); } } } ); var bg = function( scene ) { /**空のスプライトを表示**/ var sky = new Sprite( 320, 320 ); sky.image = game.assets[ 'img/bg/sky.png' ]; scene.addChild( sky ); /**岩のスプライトを表示**/ rock1 = new Sprite( 320, 320 ); rock1.image = game.assets[ 'img/bg/rock.png' ]; rock2 = new Sprite( 320, 320 ); rock2.image = game.assets[ 'img/bg/rock.png' ]; rock2.x = 320; scene.addChild( rock1 ); scene.addChild( rock2 ); /**地面のスプライトを表示**/ field1 = new Sprite( 320, 320 ); field1.image = game.assets[ 'img/bg/field.png' ]; field2 = new Sprite( 320, 320 ); field2.image = game.assets[ 'img/bg/field.png' ]; field2.x = 320; scene.addChild( field1 ); scene.addChild( field2 ); } /**弾のクラス**/ var Bullet = Class.create( Sprite, { initialize: function() { var bulletX, bulletY; //弾のX座標とY座標 this.existence = 1; //弾が存在する Sprite.call( this, 6, 2 ); //Spriteクラスのメソッドを、thisでも使えるようにする this.image = game.assets[ 'img/bullet.png' ]; //スプライトの画像ファイルを指定 //プレイヤーの向きによって弾の位置や動かす方向を変える if ( player.scaleX >= 0 ) { this.speed = 20; bulletX = player.x + 50; } else { this.speed = -20; bulletX = player.x - 5; } bulletY = player.y + 19; this.moveTo( bulletX, bulletY ); //弾の位置 }, onenterframe: function() { this.x += this.speed; //弾の移動 /**弾が画面の外に出たり、存在が0になったら削除**/ if ( this.x < -10 || this.x > 330 || this.existence === 0 ) { this.parentNode.removeChild(this); spriteList.remove( this ); } /**弾と敵の当たり判定**/ for ( var i=0; i<spriteList.length; i++ ) { var sprite = spriteList[i]; if ( sprite === player ) continue; if ( sprite === this ) continue; if ( this.intersect( sprite ) ) { sprite.existence = 0; this.existence = 0; score += 200; //scoreに200を加算 } } } } );
まとめ
今回はenchant.jsを使ってガンアクションゲームを作成してみました。
今回作成したのは基本的なものですが、この他にも、効果音を入れたり、音楽を入れたり、もっと新たな機能を加えたりなど、工夫次第でどんどんいいものになっていきます。
自分だけのオリジナルゲームを作っていきましょう!
返信ありがとうございます。
解決しました。大変助かりました。
また、何かありましたら宜しくお願いします。