# チュートリアル[シューティングゲームを作ってみましょう!] ### 目次 > #### Step0. ゲームを作る準備をしよう > #### Step1. ↑キーを押したら上に、↓キーを押したら下にクマが動くようにしよう > #### Step2. スペースキーを押したら弾を発射するようにしよう > #### Step3. 敵がこちらに近づいてくるようにしよう > #### Step4. ライフを実装しよう > #### Step5. 左に向かった敵をゲームから削除しよう > #### Step6. 敵を倒したときの処理を追加しよう > #### Step7. 敵の動きを複雑にしてみよう(直線) > #### Step8. 敵の動きを複雑にしてみよう(曲線) > #### Step9. 敵を複数種類作ってみよう ## Step0. ゲームを作る準備をしよう 今回はこのようなゲームを作成します。 ![](https://i.imgur.com/md8TJlm.gif) 一見、ただ敵がごちゃごちゃ動いているように見えますが下の画像を見てみてください。 ![](https://i.imgur.com/7PV6CiS.gif) これは同じゲームの背景のみを変えたものです。敵の動きは、複雑なように見えて、みなさんが学校で学んでいる簡単な数学の式を応用するだけで、簡単に実現ができます。今回は、このゲームを作ることを目標にプログラムを実装していきます。 それではまずは準備されたコードを確認してみてください。以下のようなコードになっていると思います。 ```javascript= enchant(); var game; var bear; var enemy; var bullet; var life; var context; var line; var surface; var GameWidth = 320; var GameHeight = 320; /*弾リスト*/ var bulletList = null; /*敵リスト*/ var enemyList = null; //敵のパラメータ var ENEMY_WIDTH = 32; var ENEMY_HEIGHT = 32; var ENEMY_CREATE_INTERVAL = 15; //Array 拡張 Array.prototype.erase = function(elm){ //渡されたelmが配列の何番目かを保存 var index = this.indexOf(elm); //渡されたelmから一つの要素を削除して前に詰める == elmを削除する this.splice(index,1); //削除された配列を返す return this; }; /* 独自クラスBearの定義 */ Bear = Class.create(Sprite, { initialize: function(x, y) { Sprite.call(this, 32, 32); this.image = game.assets['images/chara1.png']; this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.frame = 0; this.mutekitime = 0; this.mukekiflag = false; /* [space]キーをbボタンとして割り当てる */ game.keybind(32, "space"); // space game.rootScene.addChild(this); }, /* enterframeイベントのリスナーを設定 */ onenterframe: function() { this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); /* 敵とクマの衝突判定 */ var enemys; for(var i = 0, len = enemyList.length; i < len; i = i + 1){ enemys = enemyList[i]; //敵とくまとの衝突判定 if(enemys.within(this) && this.mutekiflag == false){ //弾を消去 enemys.destroy = true; this.mutekiflag = true; } } if(game.frame % game.fps % game.fps == 0){ this.mutekitime = this.mutekitime + 1; if(this.mutekitime >= 3){ this.mutekiflag = false; this.mutekitime = 0; } } if(this.mutekiflag){ this.opacity = 0.3; } else{ this.opacity = 1.0; } }, }); //弾クラス Bullet = Class.create(Sprite , { //初期化処理 initialize:function(x, y){ Sprite.call(this,8,16); //弾画像の登録 this.image = game.assets["images/bullet.png"]; //初期位置設定 this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //何かに当たった時にtrueにする this.destroy = false; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //弾の速さ this.mx = this.mx + 3 / 4; this.x = from_mat_to_enc_x(this.mx); //削除処理 if(this.mx > 9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //bulletListから削除する bulletList.erase(this); } } }); //敵クラス Enemy = Class.create(Sprite, { //初期化処理 initialize:function(x){ Sprite.call(this,ENEMY_WIDTH,ENEMY_HEIGHT); //敵画像の登録 this.image = game.assets["images/enemy.png"]; //画像をランダムに決める this.frame = 0; //敵のスピード this.speed = Math.random() * 5 + 1; //何かに当たった時にtrueにする this.destroy = false; //初期位置設定 this.mx = 6; this.my = 6; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.ox = this.mx; this.oy = this.my; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //線を描画する //Spriteを作ります //線の描画を始める context.beginPath(); //パスを開始 context.moveTo(from_mat_to_enc_x(this.ox) - 1, from_mat_to_enc_y(this.oy) - 1); context.lineTo(from_mat_to_enc_x(this.mx) - 1, from_mat_to_enc_y(this.my) - 1); context.closePath(); //パスを終了 context.stroke(); //パスを描画する //1つ前の座標を保存する this.ox = this.mx; this.oy = this.my; }, //1次関数でグラフを作る liner_graph : function(x){ var a = 1; var b = 0; var y = a * x + b; return y; }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.png', 'images/clear.png','images/bullet.png','images/enemy.png', 'images/masu_関数.png'); //弾リストの初期化 bulletList = []; //敵リストの初期化 enemyList = []; game.onload = function() { //背景画像を設定 //game.rootScene.backgroundColor = 'Black'; //背景画像が黒 var bg = new Sprite(320, 320); bg.image = game.assets["images/masu_関数.png"]; //背景画像が直交座標 game.rootScene.addChild(bg); bg.x = bg.x - 1; /* クマをつくる */ bear = new Bear(-7.5, 7.5); //線を作る line = new Sprite(320, 320); //Surfaceを作る surface = new Surface(320, 320); //spriteのimageにsurfaceを代入します line.image = surface; //コンテキストを取得します context = surface.context; //シーンにサーフェスを追加する ' game.rootScene.addChild(line); }; game.start(); //更新処理 game.rootScene.onenterframe = function(){ //敵の出現するタイミング if(game.frame % ENEMY_CREATE_INTERVAL == 0){ //敵を生成 enemy = new Enemy(8); //enemyListに敵を追加 enemyList.push(enemy); } } }; //x座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_x = function(mat_x) { var enc_x = mat_x * 20 + GameWidth / 2; return enc_x; }; //y座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_y = function(mat_y) { var enc_y = GameHeight / 2 - mat_y * 20; return enc_y; }; ``` それでは、実際に動いている様子を見てみましょう。このような画面になりましたか? ![](https://i.imgur.com/LLUx8ni.gif) 今回のプログラムでは、<b><font color="red">直交座標の座標系でプログラムを作成します(画面中央が(0,0))</font></b>。また、<b><font color="red">x座標を-8以上8以下、y座標も-8以上8以下</font></b>とします。また、<b><font color="red">座標を用いる際の変数をmx,myと固定します</font></b>。 いまのままではマウスをクリックしたりキーボードを押してもキャラクターは動くことも攻撃することもできません。それではここから、少しずつ完成を目指して進めていきましょう。 ## Step1. ↑キーを押したら上に、↓キーを押したら下にクマが動くようにしよう それではさっそくクマを動かせるようにします。このステップでは以下のようなゲームを実現するプログラムを作成します。 ![](https://i.imgur.com/hBHeMz6.gif) まずは↑キーを押したら上に上がるようにします。この命令はクマの動きなので、もちろん命令を追加する場所は、Bearクラスの定義の途中になります。また、キーを押したかどうかの判定は、フレームごとに行う必要があるため、命令を追加する場所は、onenterframeの中、つまり、<b><font color="red">サンプルコードでは61行目に追加する</font></b>ことになります。 ↑キーを押したときに行うことは、 ``` if(game.input.up){ ・ ・ ・ } ``` と記述し、「・・・」の部分に、↑キーを押した際に行う内容を記述します。上にあがる命令は、 ``` this.my = this.my + 1 / 5; ``` と書きます。こうすることで、<b><font color="red">『this(クマ自身の).my(y座標)』を『this(クマ自身の).my(y座標)』 + 1 / 5 にする</font></b>ということになります。 しかしながら、このままだとクマがずっと上に上がることができるようになります。よって、この命令は、クマのy座標が8以下の時のみ行えるようにします。まとめると、追加するべき命令は以下のようになります。 ``` if(game.input.up){ //もし↑キーを押したならば if(this.my <= 8) { //もしクマのy座標が8以下ならば this.my = this.my + 1 / 5; //クマのy座標を +(1 / 5) する } } ``` 同様に、↓キーを押した際に行う内容を記述します。↓キーを押したときに行うことは、 ``` if(game.input.down){ ・ ・ ・ } ``` と記述し、「・・・」の部分に、↓キーを押した際に行う内容を記述します。行う内容は、↑キーを押した際の命令を参考にすると ``` if(game.input.down){ //もし↓キーを押したならば if(this.my >= -6.5) { //もしクマのy座標が-6.5以上ならば this.my = this.my - 1 / 5; //クマのy座標を -(1 / 5) する } } ``` となります。 以上の2ブロックの命令を61行目に追加しましょう。 ```javascript= enchant(); var game; var bear; var enemy; var bullet; var life; var context; var line; var surface; var GameWidth = 320; var GameHeight = 320; /*弾リスト*/ var bulletList = null; /*敵リスト*/ var enemyList = null; //敵のパラメータ var ENEMY_WIDTH = 32; var ENEMY_HEIGHT = 32; var ENEMY_CREATE_INTERVAL = 15; //Array 拡張 Array.prototype.erase = function(elm){ //渡されたelmが配列の何番目かを保存 var index = this.indexOf(elm); //渡されたelmから一つの要素を削除して前に詰める == elmを削除する this.splice(index,1); //削除された配列を返す return this; }; /* 独自クラスBearの定義 */ Bear = Class.create(Sprite, { initialize: function(x, y) { Sprite.call(this, 32, 32); this.image = game.assets['images/chara1.png']; this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.frame = 0; this.mutekitime = 0; this.mukekiflag = false; /* [space]キーをbボタンとして割り当てる */ game.keybind(32, "space"); // space game.rootScene.addChild(this); }, /* enterframeイベントのリスナーを設定 */ onenterframe: function() { /* 移動処理 */ //ここを追加 if(game.input.up){ //↑キーを押した //ここを追加 if(this.my <= 8) { //ここを追加 this.my = this.my + 1 / 5; //ここを追加 } //ここを追加 } //ここを追加 if(game.input.down){ //↓キーを押した //ここを追加 if(this.my >= -6.5) { //ここを追加 this.my = this.my - 1 / 5; //ここを追加 } //ここを追加 } //ここを追加 this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); /* 敵とクマの衝突判定 */ var enemys; for(var i = 0, len = enemyList.length; i < len; i = i + 1){ enemys = enemyList[i]; //敵とくまとの衝突判定 if(enemys.within(this) && this.mutekiflag == false){ //弾を消去 enemys.destroy = true; this.mutekiflag = true; } } if(game.frame % game.fps % game.fps == 0){ this.mutekitime = this.mutekitime + 1; if(this.mutekitime >= 3){ this.mutekiflag = false; this.mutekitime = 0; } } if(this.mutekiflag){ this.opacity = 0.3; } else{ this.opacity = 1.0; } }, }); //弾クラス Bullet = Class.create(Sprite , { //初期化処理 initialize:function(x, y){ Sprite.call(this,8,16); //弾画像の登録 this.image = game.assets["images/bullet.png"]; //初期位置設定 this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //何かに当たった時にtrueにする this.destroy = false; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //弾の速さ this.mx = this.mx + 3 / 4; this.x = from_mat_to_enc_x(this.mx); //削除処理 if(this.mx > 9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //bulletListから削除する bulletList.erase(this); } } }); //敵クラス Enemy = Class.create(Sprite, { //初期化処理 initialize:function(x){ Sprite.call(this,ENEMY_WIDTH,ENEMY_HEIGHT); //敵画像の登録 this.image = game.assets["images/enemy.png"]; //画像をランダムに決める this.frame = 0; //敵のスピード this.speed = Math.random() * 5 + 1; //何かに当たった時にtrueにする this.destroy = false; //初期位置設定 this.mx = 6; this.my = 6; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.ox = this.mx; this.oy = this.my; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //線を描画する //Spriteを作ります //線の描画を始める context.beginPath(); //パスを開始 context.moveTo(from_mat_to_enc_x(this.ox) - 1, from_mat_to_enc_y(this.oy) - 1); context.lineTo(from_mat_to_enc_x(this.mx) - 1, from_mat_to_enc_y(this.my) - 1); context.closePath(); //パスを終了 context.stroke(); //パスを描画する //1つ前の座標を保存する this.ox = this.mx; this.oy = this.my; }, //1次関数でグラフを作る liner_graph : function(x){ var a = 1; var b = 0; var y = a * x + b; return y; }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.png', 'images/clear.png','images/bullet.png','images/enemy.png', 'images/masu_関数.png'); //弾リストの初期化 bulletList = []; //敵リストの初期化 enemyList = []; game.onload = function() { //背景画像を設定 //game.rootScene.backgroundColor = 'Black'; //背景画像が黒 var bg = new Sprite(320, 320); bg.image = game.assets["images/masu_関数.png"]; //背景画像が直交座標 game.rootScene.addChild(bg); bg.x = bg.x - 1; /* クマをつくる */ bear = new Bear(-7.5, 7.5); //線を作る line = new Sprite(320, 320); //Surfaceを作る surface = new Surface(320, 320); //spriteのimageにsurfaceを代入します line.image = surface; //コンテキストを取得します context = surface.context; //シーンにサーフェスを追加する ' game.rootScene.addChild(line); }; game.start(); //更新処理 game.rootScene.onenterframe = function(){ //敵の出現するタイミング if(game.frame % ENEMY_CREATE_INTERVAL == 0){ //敵を生成 enemy = new Enemy(8); //enemyListに敵を追加 enemyList.push(enemy); } } }; //x座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_x = function(mat_x) { var enc_x = mat_x * 20 + GameWidth / 2; return enc_x; }; //y座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_y = function(mat_y) { var enc_y = GameHeight / 2 - mat_y * 20; return enc_y; }; ``` ## Step2. スペースキーを押したら弾を発射するようにしよう 次は、クマがスペースキーを押すと弾が発射されるようにします。弾の動きのプログラムはすべて作ってあるので、スペースキーを押したら弾のスプライトがゲームに登場するようにしよう。 ![](https://i.imgur.com/W7jdp7T.gif) まず、スペースキーを押した際に行う命令の書き方ですが、先ほどの↑キーや↓キーと異なり、事前にプログラム内で登録を行っておく必要があります。少し説明が難しい箇所なのですが、今回はすでに最初に配布したソースコードに命令を書くことで登録はすんでいるので安心してください。場所としては、現状のサンプルコードでは52行目、53行目でこの処理を行っています。 ``` /* [space]キーをbボタンとして割り当てる */ game.keybind(32, "space"); // space ``` これで安心してスペースキーを押したときの処理を記述することができます。先ほどと同様、この命令はクマの動きなので、もちろん命令を追加する場所は、Bearクラスの定義の途中になります。また、キーを押したかどうかの判定は、フレームごとに行う必要があるため、命令を追加する場所は、onenterframeの中、つまり、<b><font color="red">現状のサンプルコードでは73行目に追加する</font></b>ことになります。 スペースキーを押したときに行うことは、 ``` if(game.input.space){ ・ ・ ・ } ``` と記述し、「・・・」の部分に、スペースキーを押した際に行う内容を記述します。スペースキーを押したときに行うことは、弾を作成することです。弾は現状のサンプルコードでは104行目でBulletクラスをすでに作成していますね。106行目を見るとinitialize:function(x,y)となっていますね。弾を作成するためには、このサンプルコードでは、 ``` bullet = new Bullet(x, y); ``` と表記することにします。こうすることで、(x, y)座標に弾を出現させることができます。どこに表示するかですが、今回は(『発射したときのクマのx座標 + 1』, 『発射したときのクマのy座標 - (1 / 2)』)とするとちょうどよく見えるのでそうしましょう。よって75行目に追加する命令は以下のようになります。 ``` /* 弾発射処理 */ if(game.input.space){ bullet = new Bullet(this.mx + 1,this.my - 1 / 2); //bulletListに今作った弾を追加 bulletList.push(bullet); } ``` bulletList.push(bullet); とありますが、こちらに関しては難しい内容なので省略させていただきます。これがないと、 弾が敵にあたった時の当たり判定のプログラムを作成するのに困ってしまう と思っていただければよいです。 ```javascript= enchant(); var game; var bear; var enemy; var bullet; var life; var context; var line; var surface; var GameWidth = 320; var GameHeight = 320; /*弾リスト*/ var bulletList = null; /*敵リスト*/ var enemyList = null; //敵のパラメータ var ENEMY_WIDTH = 32; var ENEMY_HEIGHT = 32; var ENEMY_CREATE_INTERVAL = 15; //Array 拡張 Array.prototype.erase = function(elm){ //渡されたelmが配列の何番目かを保存 var index = this.indexOf(elm); //渡されたelmから一つの要素を削除して前に詰める == elmを削除する this.splice(index,1); //削除された配列を返す return this; }; /* 独自クラスBearの定義 */ Bear = Class.create(Sprite, { initialize: function(x, y) { Sprite.call(this, 32, 32); this.image = game.assets['images/chara1.png']; this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.frame = 0; this.mutekitime = 0; this.mukekiflag = false; /* [space]キーをbボタンとして割り当てる */ game.keybind(32, "space"); // space game.rootScene.addChild(this); }, /* enterframeイベントのリスナーを設定 */ onenterframe: function() { /* 移動処理 */ if(game.input.up){ //↑キーを押した if(this.my <= 8) { this.my = this.my + 1 / 5; } } if(game.input.down){ //↓キーを押した if(this.my >= -6.5) { this.my = this.my - 1 / 5; } } this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); /* 弾発射処理 */ //ここを追加 if(game.input.space){ //ここを追加 bullet = new Bullet(this.mx + 1,this.my - 1 / 2); //ここを追加 //bulletListに今作った弾を追加 //ここを追加 bulletList.push(bullet); //ここを追加 } //ここを追加 /* 敵とクマの衝突判定 */ var enemys; for(var i = 0, len = enemyList.length; i < len; i = i + 1){ enemys = enemyList[i]; //敵とくまとの衝突判定 if(enemys.within(this) && this.mutekiflag == false){ //弾を消去 enemys.destroy = true; this.mutekiflag = true; } } if(game.frame % game.fps % game.fps == 0){ this.mutekitime = this.mutekitime + 1; if(this.mutekitime >= 3){ this.mutekiflag = false; this.mutekitime = 0; } } if(this.mutekiflag){ this.opacity = 0.3; } else{ this.opacity = 1.0; } }, }); //弾クラス Bullet = Class.create(Sprite , { //初期化処理 initialize:function(x, y){ Sprite.call(this,8,16); //弾画像の登録 this.image = game.assets["images/bullet.png"]; //初期位置設定 this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //何かに当たった時にtrueにする this.destroy = false; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //弾の速さ this.mx = this.mx + 3 / 4; this.x = from_mat_to_enc_x(this.mx); //削除処理 if(this.mx > 9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //bulletListから削除する bulletList.erase(this); } } }); //敵クラス Enemy = Class.create(Sprite, { //初期化処理 initialize:function(x){ Sprite.call(this,ENEMY_WIDTH,ENEMY_HEIGHT); //敵画像の登録 this.image = game.assets["images/enemy.png"]; //画像をランダムに決める this.frame = 0; //敵のスピード this.speed = Math.random() * 5 + 1; //何かに当たった時にtrueにする this.destroy = false; //初期位置設定 this.mx = 6; this.my = 6; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.ox = this.mx; this.oy = this.my; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //線を描画する //Spriteを作ります //線の描画を始める context.beginPath(); //パスを開始 context.moveTo(from_mat_to_enc_x(this.ox) - 1, from_mat_to_enc_y(this.oy) - 1); context.lineTo(from_mat_to_enc_x(this.mx) - 1, from_mat_to_enc_y(this.my) - 1); context.closePath(); //パスを終了 context.stroke(); //パスを描画する //1つ前の座標を保存する this.ox = this.mx; this.oy = this.my; }, //1次関数でグラフを作る liner_graph : function(x){ var a = 1; var b = 0; var y = a * x + b; return y; }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.png', 'images/clear.png','images/bullet.png','images/enemy.png', 'images/masu_関数.png'); //弾リストの初期化 bulletList = []; //敵リストの初期化 enemyList = []; game.onload = function() { //背景画像を設定 //game.rootScene.backgroundColor = 'Black'; //背景画像が黒 var bg = new Sprite(320, 320); bg.image = game.assets["images/masu_関数.png"]; //背景画像が直交座標 game.rootScene.addChild(bg); bg.x = bg.x - 1; /* クマをつくる */ bear = new Bear(-7.5, 7.5); //線を作る line = new Sprite(320, 320); //Surfaceを作る surface = new Surface(320, 320); //spriteのimageにsurfaceを代入します line.image = surface; //コンテキストを取得します context = surface.context; //シーンにサーフェスを追加する ' game.rootScene.addChild(line); }; game.start(); //更新処理 game.rootScene.onenterframe = function(){ //敵の出現するタイミング if(game.frame % ENEMY_CREATE_INTERVAL == 0){ //敵を生成 enemy = new Enemy(8); //enemyListに敵を追加 enemyList.push(enemy); } } }; //x座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_x = function(mat_x) { var enc_x = mat_x * 20 + GameWidth / 2; return enc_x; }; //y座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_y = function(mat_y) { var enc_y = GameHeight / 2 - mat_y * 20; return enc_y; }; ``` ## Step3. 敵がこちらに近づいてくるようにしよう 次は敵がこちらに近づいてくるようにします。 ![](https://i.imgur.com/sSnN5cM.gif) 弾が敵にあった時の判定は、まだ作っていないので何も起きませんが、敵がクマにあたった時は、クマが点滅するようになっています。この部分はすでに完成している部分になります。 敵の動きを記述する場所ですが、今回の命令は敵が発生する場所および敵の動きなので、命令を追加する場所は、BearではなくEnemyクラスの定義の途中になります。また、敵はフレームごとに動き続けるだけでなく、発生する際の座標を制御しなければならないため、命令を追加する場所は、onenterframeの中、つまり、サンプルコードでは<B><font color="red">171行目付近に追加するだけでなく</font></B>、initializeの中、サンプルコードでは<B><font color="red">147行目付近の命令を変更する</font></B>ことになります。 まず敵が発生した際のx座標ですが、今回の座標軸の一番大きな正の値は8ですから、8とすることにします。次にy座標ですが、こちらは現在のサンプルコードが6であるため、そのまま変更せず6とします。ここで、240行目を見てみます。240行目は ``` enemy = new Enemy(8); ``` となっており、ここで敵を出現させています。注目すべきは()の中ですが、ここには、敵が発生した際のx座標の値である8が使われています。 そのため、今回は敵が発生した際のx座標、つまり、initializeの中のthis.xをxとしましょう。 少し難しい方は、<B><font color="red">今回は、this.mx=xと命令することで、クマが発生する際のx座標を8にできる</font></B>と考えればよいかと思います。 159行目 変更前 ``` this.mx = 6; ``` 変更後 ``` this.mx = x; ``` つぎに、移動ですが、今回は<B><font color="red">フレームごとにx座標、y座標を常に指定するという方法</font></B>でこの動きを実現しようと思います。まずx座標ですが、先ほど説明したように、出現した最初の値は8です。今回はそこから1フレームごとに(1 / 10)ずつ減らしていくことにします。つぎにy座標ですが、先ほど説明したように、出現した最初の値は6です。このステップでは、簡単にするために、yの値はxの値に依存することなく常に6としましょう。追加すべき場所はもちろんonenterframeの中です。171行目に追加すべき命令は以下のようになります。 ``` //x座標設定 this.mx = this.mx - (1 / 10); //y座標設定 this.my = 6; ``` これで敵がこちらに向かってくるようになりました。 ```javascript= enchant(); var game; var bear; var enemy; var bullet; var life; var context; var line; var surface; var GameWidth = 320; var GameHeight = 320; /*弾リスト*/ var bulletList = null; /*敵リスト*/ var enemyList = null; //敵のパラメータ var ENEMY_WIDTH = 32; var ENEMY_HEIGHT = 32; var ENEMY_CREATE_INTERVAL = 15; //Array 拡張 Array.prototype.erase = function(elm){ //渡されたelmが配列の何番目かを保存 var index = this.indexOf(elm); //渡されたelmから一つの要素を削除して前に詰める == elmを削除する this.splice(index,1); //削除された配列を返す return this; }; /* 独自クラスBearの定義 */ Bear = Class.create(Sprite, { initialize: function(x, y) { Sprite.call(this, 32, 32); this.image = game.assets['images/chara1.png']; this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.frame = 0; this.mutekitime = 0; this.mukekiflag = false; /* [space]キーをbボタンとして割り当てる */ game.keybind(32, "space"); // space game.rootScene.addChild(this); }, /* enterframeイベントのリスナーを設定 */ onenterframe: function() { /* 移動処理 */ if(game.input.up){ //↑キーを押した if(this.my <= 8) { this.my = this.my + 1 / 5; } } if(game.input.down){ //↓キーを押した if(this.my >= -6.5) { this.my = this.my - 1 / 5; } } this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); /* 弾発射処理 */ if(game.input.space){ bullet = new Bullet(this.mx + 1,this.my - 1 / 2); //bulletListに今作った弾を追加 bulletList.push(bullet); } /* 敵とクマの衝突判定 */ var enemys; for(var i = 0, len = enemyList.length; i < len; i = i + 1){ enemys = enemyList[i]; //敵とくまとの衝突判定 if(enemys.within(this) && this.mutekiflag == false){ //弾を消去 enemys.destroy = true; this.mutekiflag = true; } } if(game.frame % game.fps % game.fps == 0){ this.mutekitime = this.mutekitime + 1; if(this.mutekitime >= 3){ this.mutekiflag = false; this.mutekitime = 0; } } if(this.mutekiflag){ this.opacity = 0.3; } else{ this.opacity = 1.0; } }, }); //弾クラス Bullet = Class.create(Sprite , { //初期化処理 initialize:function(x, y){ Sprite.call(this,8,16); //弾画像の登録 this.image = game.assets["images/bullet.png"]; //初期位置設定 this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //何かに当たった時にtrueにする this.destroy = false; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //弾の速さ this.mx = this.mx + 3 / 4; this.x = from_mat_to_enc_x(this.mx); //削除処理 if(this.mx > 9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //bulletListから削除する bulletList.erase(this); } } }); //敵クラス Enemy = Class.create(Sprite, { //初期化処理 initialize:function(x){ Sprite.call(this,ENEMY_WIDTH,ENEMY_HEIGHT); //敵画像の登録 this.image = game.assets["images/enemy.png"]; //画像をランダムに決める this.frame = 0; //敵のスピード this.speed = Math.random() * 5 + 1; //何かに当たった時にtrueにする this.destroy = false; //初期位置設定 this.mx = x; //ここを変更 this.my = 6; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.ox = this.mx; this.oy = this.my; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //x座標設定 //ここを追加 this.mx = this.mx - (1 / 10); //ここを追加 //y座標設定 //ここを追加 this.my = 6; //ここを追加 this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //線を描画する //Spriteを作ります //線の描画を始める context.beginPath(); //パスを開始 context.moveTo(from_mat_to_enc_x(this.ox) - 1, from_mat_to_enc_y(this.oy) - 1); context.lineTo(from_mat_to_enc_x(this.mx) - 1, from_mat_to_enc_y(this.my) - 1); context.closePath(); //パスを終了 context.stroke(); //パスを描画する //1つ前の座標を保存する this.ox = this.mx; this.oy = this.my; }, //1次関数でグラフを作る liner_graph : function(x){ var a = 1; var b = 0; var y = a * x + b; return y; }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.png', 'images/clear.png','images/bullet.png','images/enemy.png', 'images/masu_関数.png'); //弾リストの初期化 bulletList = []; //敵リストの初期化 enemyList = []; game.onload = function() { //背景画像を設定 //game.rootScene.backgroundColor = 'Black'; //背景画像が黒 var bg = new Sprite(320, 320); bg.image = game.assets["images/masu_関数.png"]; //背景画像が直交座標 game.rootScene.addChild(bg); bg.x = bg.x - 1; /* クマをつくる */ bear = new Bear(-7.5, 7.5); //線を作る line = new Sprite(320, 320); //Surfaceを作る surface = new Surface(320, 320); //spriteのimageにsurfaceを代入します line.image = surface; //コンテキストを取得します context = surface.context; //シーンにサーフェスを追加する ' game.rootScene.addChild(line); }; game.start(); //更新処理 game.rootScene.onenterframe = function(){ //敵の出現するタイミング if(game.frame % ENEMY_CREATE_INTERVAL == 0){ //敵を生成 enemy = new Enemy(8); //enemyListに敵を追加 enemyList.push(enemy); } } }; //x座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_x = function(mat_x) { var enc_x = mat_x * 20 + GameWidth / 2; return enc_x; }; //y座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_y = function(mat_y) { var enc_y = GameHeight / 2 - mat_y * 20; return enc_y; }; ``` ## Step4. ライフを実装しよう 次はライフを実装し、ゲームオーバーを作ります。 ![](https://i.imgur.com/Fal0Qyt.gif) 初期状態でライフは3あります。敵にあたるとライフが1減り、0になったらゲームオーバーという 仕様にします。なお、Step3で点滅していたのは、無敵状態を表していました。透明状態のプログラムや、無敵状態を司る命令などは少し難しいのですべて最初に渡したプログラムにすでに書いてあります。無敵状態を司る命令は、先ほどのサンプルコードでは83行目から100行目辺りになります。また透明を司る命令は101行目から106行目辺りになります。興味があれば考えてみてください。 無敵状態 ``` var enemys; for(var i = 0, len = enemyList.length; i < len; i = i + 1){ enemys = enemyList[i]; //敵とくまとの衝突判定 if(enemys.within(this) && this.mutekiflag == false){ //弾を消去 enemys.destroy = true; this.mutekiflag = true; } } if(game.frame % game.fps % game.fps == 0){ this.mutekitime = this.mutekitime + 1; if(this.mutekitime >= 3){ this.mutekiflag = false; this.mutekitime = 0; } } ``` 透明 ``` if(this.mutekiflag){ this.opacity = 0.3; } else{ this.opacity = 1.0; } ``` では本題に戻ります。まずライフですが、作り方が決まっており、以下の作り方で作ることができます。 ``` 変数 = new LifeLabel(x, y, "最大ライフ数"); ``` 冒頭では、今回のenchant.jsでは、中央を(0,0)とするといっていたのですが、ライフラベルの時のみ、座標系を左上(0, 0)で考えます。(ややこしくなってしまったのですが、これだけはもう割り切ってください)左上に表示したいので、今回は(10, 10)で表示します。また、最大ライフ数は3と決めているので、変数をlifeとして、 ``` life = new LifeLabel(10, 10, 3); ``` とします。lifeは特別な変数を持っており、 ``` life.life = 整数; ``` と記入することで、この命令が来た際の残りライフを指定することができます。また、 ``` game.rootScene.addChild(life); ``` とすることで、ゲーム上にライフを表示することができるようになります。それでは実際にライフを表示させるのですが、表示させる場所は、クマを表示した直後、先ほどのコードでいう223行目とします。 それでは223行目に以下のコードを追加しましょう。 ``` /* ライフをつくる */ life = new LifeLabel(10, 10, 3); //ライフを(10, 10)に最大ライフ3で準備 life.life = 3; //残りライフを3にする game.rootScene.addChild(life); //ライフをゲーム内に登場させる ``` また、クマが敵にあたった時にライフが減るように変更しましょう。記入する場所は、敵とクマが触れたときに命令を記述する場所、つまり87行目辺りになります。ライフを1減らす命令は、 ``` life.life = life.life - 1; ``` ですね。また、同時にライフがもしも0になったらクマをゲームから削除してゲームオーバーにしてしまいましょう。これらの命令をつなげると以下のようになります。 ``` life.life = life.life - 1; //ライフを1減らしてください if(life.life <= 0){ //もしライフが0以下ならば game.rootScene.removeChild(this); //this(ここではBearクラスの中に書いているのでクマのこと)をゲームから削除してください game.end(); //ゲームを終了してください } ``` この命令を91行目に追加しましょう ```javascript= enchant(); var game; var bear; var enemy; var bullet; var life; var context; var line; var surface; var GameWidth = 320; var GameHeight = 320; /*弾リスト*/ var bulletList = null; /*敵リスト*/ var enemyList = null; //敵のパラメータ var ENEMY_WIDTH = 32; var ENEMY_HEIGHT = 32; var ENEMY_CREATE_INTERVAL = 15; //Array 拡張 Array.prototype.erase = function(elm){ //渡されたelmが配列の何番目かを保存 var index = this.indexOf(elm); //渡されたelmから一つの要素を削除して前に詰める == elmを削除する this.splice(index,1); //削除された配列を返す return this; }; /* 独自クラスBearの定義 */ Bear = Class.create(Sprite, { initialize: function(x, y) { Sprite.call(this, 32, 32); this.image = game.assets['images/chara1.png']; this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.frame = 0; this.mutekitime = 0; this.mukekiflag = false; /* [space]キーをbボタンとして割り当てる */ game.keybind(32, "space"); // space game.rootScene.addChild(this); }, /* enterframeイベントのリスナーを設定 */ onenterframe: function() { /* 移動処理 */ if(game.input.up){ //↑キーを押した if(this.my <= 8) { this.my = this.my + 1 / 5; } } if(game.input.down){ //↓キーを押した if(this.my >= -6.5) { this.my = this.my - 1 / 5; } } this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); /* 弾発射処理 */ if(game.input.space){ bullet = new Bullet(this.mx + 1,this.my - 1 / 2); //bulletListに今作った弾を追加 bulletList.push(bullet); } /* 敵とクマの衝突判定 */ var enemys; for(var i = 0, len = enemyList.length; i < len; i = i + 1){ enemys = enemyList[i]; //敵とくまとの衝突判定 if(enemys.within(this) && this.mutekiflag == false){ //弾を消去 enemys.destroy = true; this.mutekiflag = true; life.life = life.life - 1; //ここを追加 if(life.life <= 0){ //ここを追加 game.rootScene.removeChild(this); //ここを追加 game.end(); //ここを追加 } } } if(game.frame % game.fps % game.fps == 0){ this.mutekitime = this.mutekitime + 1; if(this.mutekitime >= 3){ this.mutekiflag = false; this.mutekitime = 0; } } if(this.mutekiflag){ this.opacity = 0.3; } else{ this.opacity = 1.0; } }, }); //弾クラス Bullet = Class.create(Sprite , { //初期化処理 initialize:function(x, y){ Sprite.call(this,8,16); //弾画像の登録 this.image = game.assets["images/bullet.png"]; //初期位置設定 this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //何かに当たった時にtrueにする this.destroy = false; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //弾の速さ this.mx = this.mx + 3 / 4; this.x = from_mat_to_enc_x(this.mx); //削除処理 if(this.mx > 9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //bulletListから削除する bulletList.erase(this); } } }); //敵クラス Enemy = Class.create(Sprite, { //初期化処理 initialize:function(x){ Sprite.call(this,ENEMY_WIDTH,ENEMY_HEIGHT); //敵画像の登録 this.image = game.assets["images/enemy.png"]; //画像をランダムに決める this.frame = 0; //敵のスピード this.speed = Math.random() * 5 + 1; //何かに当たった時にtrueにする this.destroy = false; //初期位置設定 this.mx = x; this.my = 6; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.ox = this.mx; this.oy = this.my; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //x座標設定 this.mx = this.mx - (1 / 10); //y座標設定 this.my = 6; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //線を描画する //Spriteを作ります //線の描画を始める context.beginPath(); //パスを開始 context.moveTo(from_mat_to_enc_x(this.ox) - 1, from_mat_to_enc_y(this.oy) - 1); context.lineTo(from_mat_to_enc_x(this.mx) - 1, from_mat_to_enc_y(this.my) - 1); context.closePath(); //パスを終了 context.stroke(); //パスを描画する //1つ前の座標を保存する this.ox = this.mx; this.oy = this.my; }, //1次関数でグラフを作る liner_graph : function(x){ var a = 1; var b = 0; var y = a * x + b; return y; }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.png', 'images/clear.png','images/bullet.png','images/enemy.png', 'images/masu_関数.png'); //弾リストの初期化 bulletList = []; //敵リストの初期化 enemyList = []; game.onload = function() { //背景画像を設定 //game.rootScene.backgroundColor = 'Black'; //背景画像が黒 var bg = new Sprite(320, 320); bg.image = game.assets["images/masu_関数.png"]; //背景画像が直交座標 game.rootScene.addChild(bg); bg.x = bg.x - 1; /* クマをつくる */ bear = new Bear(-7.5, 7.5); /* ライフをつくる */ //ここを追加 life = new LifeLabel(10, 10, 3); //ここを追加 life.life = 3; //ここを追加 game.rootScene.addChild(life); //ここを追加 //線を作る line = new Sprite(320, 320); //Surfaceを作る surface = new Surface(320, 320); //spriteのimageにsurfaceを代入します line.image = surface; //コンテキストを取得します context = surface.context; //シーンにサーフェスを追加する ' game.rootScene.addChild(line); }; game.start(); //更新処理 game.rootScene.onenterframe = function(){ //敵の出現するタイミング if(game.frame % ENEMY_CREATE_INTERVAL == 0){ //敵を生成 enemy = new Enemy(8); //enemyListに敵を追加 enemyList.push(enemy); } } }; //x座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_x = function(mat_x) { var enc_x = mat_x * 20 + GameWidth / 2; return enc_x; }; //y座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_y = function(mat_y) { var enc_y = GameHeight / 2 - mat_y * 20; return enc_y; }; ``` ## Step5. 左に向かった敵をゲームから削除しよう 次は左に向かった敵を削除するようにします。 ![](https://i.imgur.com/Fal0Qyt.gif) いったい何が変わったのかと思われた方もいると思います。見た目は全く変わっていません。しかし今このプログラムには問題点があります。それは、クマにあたらずに左側に向かっていった敵がゲームから消滅せずにどんどん増え続けている点です。 この動画の時間ならよいですが、長時間プレーすればするほど、奥に敵がたまります。奥に敵がたまりすぎると、ゲームが重くなったり、落ちたりするかもしれません。ゲームに関係なくなったキャラクターは削除しましょう。 今回は敵に対する命令を記述するので、Enemyクラスの中に記述します。また、敵が画面外に行ったかどうかはフレームごとにつねに確認しなければならないため。onenterframeの中、つまり175行目辺りに命令を追加しましょう。 敵が画面外に出る条件は、敵のx座標が-9未満になった時 とします。敵のx座標は常に小さくなっていくので、-9より小さくなったら、もうゲーム画面に戻ってくることはありません。 ``` if(this.mx < -9 || this.destroy == true){ } ``` としましょう。また、敵の消去の命令は以下のようになります。 ``` //親要素から自分を除外する this.parentNode.removeChild(this); //enemyListから削除 enemyList.erase(this); ``` 少し難しい表現ですが、ほかの命令との兼ね合いで、このような表現を行っています。とにかくこの4行で、『敵をゲームから除外しているんだ』と思ってください。 まとめると、この塊を、196行目に追加してください。 ``` //削除処理 if(this.mx < -9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //enemyListから削除 enemyList.erase(this); } ``` 目には見えませんが、これで、画面外の敵がゲームから消えるようになりました。 ```javascript= enchant(); var game; var bear; var enemy; var bullet; var life; var context; var line; var surface; var GameWidth = 320; var GameHeight = 320; /*弾リスト*/ var bulletList = null; /*敵リスト*/ var enemyList = null; //敵のパラメータ var ENEMY_WIDTH = 32; var ENEMY_HEIGHT = 32; var ENEMY_CREATE_INTERVAL = 15; //Array 拡張 Array.prototype.erase = function(elm){ //渡されたelmが配列の何番目かを保存 var index = this.indexOf(elm); //渡されたelmから一つの要素を削除して前に詰める == elmを削除する this.splice(index,1); //削除された配列を返す return this; }; /* 独自クラスBearの定義 */ Bear = Class.create(Sprite, { initialize: function(x, y) { Sprite.call(this, 32, 32); this.image = game.assets['images/chara1.png']; this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.frame = 0; this.mutekitime = 0; this.mukekiflag = false; /* [space]キーをbボタンとして割り当てる */ game.keybind(32, "space"); // space game.rootScene.addChild(this); }, /* enterframeイベントのリスナーを設定 */ onenterframe: function() { /* 移動処理 */ if(game.input.up){ //↑キーを押した if(this.my <= 8) { this.my = this.my + 1 / 5; } } if(game.input.down){ //↓キーを押した if(this.my >= -6.5) { this.my = this.my - 1 / 5; } } this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); /* 弾発射処理 */ if(game.input.space){ bullet = new Bullet(this.mx + 1,this.my - 1 / 2); //bulletListに今作った弾を追加 bulletList.push(bullet); } /* 敵とクマの衝突判定 */ var enemys; for(var i = 0, len = enemyList.length; i < len; i = i + 1){ enemys = enemyList[i]; //敵とくまとの衝突判定 if(enemys.within(this) && this.mutekiflag == false){ //弾を消去 enemys.destroy = true; this.mutekiflag = true; life.life = life.life - 1; if(life.life <= 0){ game.rootScene.removeChild(this); game.end(); } } } if(game.frame % game.fps % game.fps == 0){ this.mutekitime = this.mutekitime + 1; if(this.mutekitime >= 3){ this.mutekiflag = false; this.mutekitime = 0; } } if(this.mutekiflag){ this.opacity = 0.3; } else{ this.opacity = 1.0; } }, }); //弾クラス Bullet = Class.create(Sprite , { //初期化処理 initialize:function(x, y){ Sprite.call(this,8,16); //弾画像の登録 this.image = game.assets["images/bullet.png"]; //初期位置設定 this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //何かに当たった時にtrueにする this.destroy = false; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //弾の速さ this.mx = this.mx + 3 / 4; this.x = from_mat_to_enc_x(this.mx); //削除処理 if(this.mx > 9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //bulletListから削除する bulletList.erase(this); } } }); //敵クラス Enemy = Class.create(Sprite, { //初期化処理 initialize:function(x){ Sprite.call(this,ENEMY_WIDTH,ENEMY_HEIGHT); //敵画像の登録 this.image = game.assets["images/enemy.png"]; //画像をランダムに決める this.frame = 0; //敵のスピード this.speed = Math.random() * 5 + 1; //何かに当たった時にtrueにする this.destroy = false; //初期位置設定 this.mx = x; this.my = 6; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.ox = this.mx; this.oy = this.my; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //x座標設定 this.mx = this.mx - (1 / 10); //y座標設定 this.my = 6; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //線を描画する //Spriteを作ります //線の描画を始める context.beginPath(); //パスを開始 context.moveTo(from_mat_to_enc_x(this.ox) - 1, from_mat_to_enc_y(this.oy) - 1); context.lineTo(from_mat_to_enc_x(this.mx) - 1, from_mat_to_enc_y(this.my) - 1); context.closePath(); //パスを終了 context.stroke(); //パスを描画する //1つ前の座標を保存する this.ox = this.mx; this.oy = this.my; //削除処理 //ここを追加 if(this.mx < -9 || this.destroy == true){ //ここを追加 //親要素から自分を除外する //ここを追加 this.parentNode.removeChild(this); //ここを追加 //enemyListから削除 //ここを追加 enemyList.erase(this); //ここを追加 } //ここを追加 }, //1次関数でグラフを作る liner_graph : function(x){ var a = 1; var b = 0; var y = a * x + b; return y; }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.png', 'images/clear.png','images/bullet.png','images/enemy.png', 'images/masu_関数.png'); //弾リストの初期化 bulletList = []; //敵リストの初期化 enemyList = []; game.onload = function() { //背景画像を設定 //game.rootScene.backgroundColor = 'Black'; //背景画像が黒 var bg = new Sprite(320, 320); bg.image = game.assets["images/masu_関数.png"]; //背景画像が直交座標 game.rootScene.addChild(bg); bg.x = bg.x - 1; /* クマをつくる */ bear = new Bear(-7.5, 7.5); /* ライフをつくる */ //ここを追加 life = new LifeLabel(10, 10, 3); life.life = 3; //ここを追加 game.rootScene.addChild(life); //線を作る line = new Sprite(320, 320); //Surfaceを作る surface = new Surface(320, 320); //spriteのimageにsurfaceを代入します line.image = surface; //コンテキストを取得します context = surface.context; //シーンにサーフェスを追加する ' game.rootScene.addChild(line); }; game.start(); //更新処理 game.rootScene.onenterframe = function(){ //敵の出現するタイミング if(game.frame % ENEMY_CREATE_INTERVAL == 0){ //敵を生成 enemy = new Enemy(8); //enemyListに敵を追加 enemyList.push(enemy); } } }; //x座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_x = function(mat_x) { var enc_x = mat_x * 20 + GameWidth / 2; return enc_x; }; //y座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_y = function(mat_y) { var enc_y = GameHeight / 2 - mat_y * 20; return enc_y; }; ``` ## Step6. 敵を倒したときの処理を追加しよう いよいよ敵を倒したときの処理を追加します。 ![](https://i.imgur.com/OFRKsa6.gif) 敵にあたった時は敵だけでなく弾も削除してしまいましょう。 弾も敵も無数に存在するため、衝突判定はfor文を使わなければなりません。以下の文を203行目に追加しましょう ``` //全ての弾と敵との衝突判定 var bullets; for(var i = 0, len = bulletList.length; i < len; i = i + 1){ bullets = bulletList[i]; //敵と弾との衝突判定 if(bullets.within(this)){ //弾を消去 bullets.destroy = true; this.destroy = true; } } ``` 全ての弾と全ての敵の衝突判定を行うために敵のクラスに配列bulletListに入れているすべての弾と判定を行うようにしました。すべての弾と判定が終わった後に敵を消したいので.destoryをtrueにすることでぶつかった敵と弾を消します。 ```javascript= enchant(); var game; var bear; var enemy; var bullet; var life; var context; var line; var surface; var GameWidth = 320; var GameHeight = 320; /*弾リスト*/ var bulletList = null; /*敵リスト*/ var enemyList = null; //敵のパラメータ var ENEMY_WIDTH = 32; var ENEMY_HEIGHT = 32; var ENEMY_CREATE_INTERVAL = 15; //Array 拡張 Array.prototype.erase = function(elm){ //渡されたelmが配列の何番目かを保存 var index = this.indexOf(elm); //渡されたelmから一つの要素を削除して前に詰める == elmを削除する this.splice(index,1); //削除された配列を返す return this; }; /* 独自クラスBearの定義 */ Bear = Class.create(Sprite, { initialize: function(x, y) { Sprite.call(this, 32, 32); this.image = game.assets['images/chara1.png']; this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.frame = 0; this.mutekitime = 0; this.mukekiflag = false; /* [space]キーをbボタンとして割り当てる */ game.keybind(32, "space"); // space game.rootScene.addChild(this); }, /* enterframeイベントのリスナーを設定 */ onenterframe: function() { /* 移動処理 */ if(game.input.up){ //↑キーを押した if(this.my <= 8) { this.my = this.my + 1 / 5; } } if(game.input.down){ //↓キーを押した if(this.my >= -6.5) { this.my = this.my - 1 / 5; } } this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); /* 弾発射処理 */ if(game.input.space){ bullet = new Bullet(this.mx + 1,this.my - 1 / 2); //bulletListに今作った弾を追加 bulletList.push(bullet); } /* 敵とクマの衝突判定 */ var enemys; for(var i = 0, len = enemyList.length; i < len; i = i + 1){ enemys = enemyList[i]; //敵とくまとの衝突判定 if(enemys.within(this) && this.mutekiflag == false){ //弾を消去 enemys.destroy = true; this.mutekiflag = true; life.life = life.life - 1; if(life.life <= 0){ game.rootScene.removeChild(this); game.end(); } } } if(game.frame % game.fps % game.fps == 0){ this.mutekitime = this.mutekitime + 1; if(this.mutekitime >= 3){ this.mutekiflag = false; this.mutekitime = 0; } } if(this.mutekiflag){ this.opacity = 0.3; } else{ this.opacity = 1.0; } }, }); //弾クラス Bullet = Class.create(Sprite , { //初期化処理 initialize:function(x, y){ Sprite.call(this,8,16); //弾画像の登録 this.image = game.assets["images/bullet.png"]; //初期位置設定 this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //何かに当たった時にtrueにする this.destroy = false; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //弾の速さ this.mx = this.mx + 3 / 4; this.x = from_mat_to_enc_x(this.mx); //削除処理 if(this.mx > 9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //bulletListから削除する bulletList.erase(this); } } }); //敵クラス Enemy = Class.create(Sprite, { //初期化処理 initialize:function(x){ Sprite.call(this,ENEMY_WIDTH,ENEMY_HEIGHT); //敵画像の登録 this.image = game.assets["images/enemy.png"]; //画像をランダムに決める this.frame = 0; //敵のスピード this.speed = Math.random() * 5 + 1; //何かに当たった時にtrueにする this.destroy = false; //初期位置設定 this.mx = x; this.my = 6; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.ox = this.mx; this.oy = this.my; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //x座標設定 this.mx = this.mx - (1 / 10); //y座標設定 this.my = 6; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //線を描画する //Spriteを作ります //線の描画を始める context.beginPath(); //パスを開始 context.moveTo(from_mat_to_enc_x(this.ox) - 1, from_mat_to_enc_y(this.oy) - 1); context.lineTo(from_mat_to_enc_x(this.mx) - 1, from_mat_to_enc_y(this.my) - 1); context.closePath(); //パスを終了 context.stroke(); //パスを描画する //1つ前の座標を保存する this.ox = this.mx; this.oy = this.my; //削除処理 //ここを追加 if(this.mx < -9 || this.destroy == true){ //ここを追加 //親要素から自分を除外する //ここを追加 this.parentNode.removeChild(this); //ここを追加 //enemyListから削除 //ここを追加 enemyList.erase(this); //ここを追加 } //ここを追加 //全ての弾と敵との衝突判定 var bullets;//ここを追加 for(var i = 0, len = bulletList.length; i < len; i = i + 1){//ここを追加 bullets = bulletList[i];//ここを追加 //敵と弾との衝突判定 if(bullets.within(this)){//ここを追加 //弾を消去 bullets.destroy = true;//ここを追加 this.destroy = true;//ここを追加 }//ここを追加 }//ここを追加 }, //1次関数でグラフを作る liner_graph : function(x){ var a = 1; var b = 0; var y = a * x + b; return y; }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.png', 'images/clear.png','images/bullet.png','images/enemy.png', 'images/masu_関数.png'); //弾リストの初期化 bulletList = []; //敵リストの初期化 enemyList = []; game.onload = function() { //背景画像を設定 //game.rootScene.backgroundColor = 'Black'; //背景画像が黒 var bg = new Sprite(320, 320); bg.image = game.assets["images/masu_関数.png"]; //背景画像が直交座標 game.rootScene.addChild(bg); bg.x = bg.x - 1; /* クマをつくる */ bear = new Bear(-7.5, 7.5); /* ライフをつくる */ //ここを追加 life = new LifeLabel(10, 10, 3); life.life = 3; //ここを追加 game.rootScene.addChild(life); //線を作る line = new Sprite(320, 320); //Surfaceを作る surface = new Surface(320, 320); //spriteのimageにsurfaceを代入します line.image = surface; //コンテキストを取得します context = surface.context; //シーンにサーフェスを追加する ' game.rootScene.addChild(line); }; game.start(); //更新処理 game.rootScene.onenterframe = function(){ //敵の出現するタイミング if(game.frame % ENEMY_CREATE_INTERVAL == 0){ //敵を生成 enemy = new Enemy(8); //enemyListに敵を追加 enemyList.push(enemy); } } }; //x座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_x = function(mat_x) { var enc_x = mat_x * 20 + GameWidth / 2; return enc_x; }; //y座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_y = function(mat_y) { var enc_y = GameHeight / 2 - mat_y * 20; return enc_y; }; ``` ## Step7. 敵の動きを複雑にしてみよう(直線) 次は敵の動きを複雑にしてみよう。まずは直線です。 ![](https://i.imgur.com/ORR5AHT.gif) 今までは敵が真横にまっすぐ進んでいましたが、斜めに進むようになりました。 もう3日目なので気づいたと思いますが、プログラミングでゲームを作成するためにたくさんの支援ツールが存在しますが、その多くが座標系で出現場所を表しており、グラフととても相性が良いです。 数学では直線はすべて1次関数で表すことができます。そのため、1次関数を利用して敵の動きを実現してみました。 <details><summary><B><font color="blue">1次関数についてはこちらをクリックすると、説明が出てきます。</font></B></summary> #### 1次関数とは 2つの変数xとyがあり、yの値がxの値にともなって変化し,xの値を定めるとyの値がただ一つに決まる場合yはxの関数であるといいます。 その中でも,y=x,y=3xのようにyをxの1次式で表せる関数を1次関数と呼びます。 1次関数の式は以下のように表せます。 > y=ax+b 例えば、y=2xのとき、xが-5から5の間において、以下の表のようになります。 ![](https://i.imgur.com/FZL1WuS.png) 例えば,xが-5のとき、y=2xということは、y=2×(-5)となりy=-10となります。 また、この関数をx,y座標上のグラフで表すと、以下のようになります。 ![](https://i.imgur.com/7yuArQb.png) </details> </br> それではコードを確認してみましょう。敵が斜めに動くとき、y座標の値はx座標の値に依存します。例では、x座標が5の時、y座標は5を通過します。x座標が3の時、y座標は3を通過します。これはプログラミングの関数を用いることで実現することができます。 217行目を見てみましょう、 ``` liner_graph : function(x){ var a = 1; var b = 0; var y = a * x + b; return y; } ``` という記述があります。このfunctionというのがプログラミングの関数を表し、関数の作り方は、 ``` 関数の名前 : function(入ってくる数を代入するための変数){ 関数が使われたときの処理 return 出ていく値 } ``` となります。 読み方ですが、まず、function()の「()の中身」に入ってきた数が代入されます(ここではx)。a = 1、b = 0と書いています。そしてy = a * x + bと書かれています。今aは1、bは0であるため、y = 1 * x + 0、つまりy = xとかいてあります。先ほど説明した通り、入ってきた変数はxに代入されるため、もし5がこの関数に入ってきた場合、y = 5となります。最後のreturn y;ですがreturn 〇;とは関数から出ていくとき、出ていく数字は〇だよ、という意味になります。つまり今はyがでていくということになります。つまり、もし5が入ってきた場合、 入ってきたものがx,つまりx=5 ↓ a=1 ↓ b=0 ↓ y=a * x + b,つまりy=x,つまりy=5 ↓ yがでていく,つまり5がでていく となります。これは思い切り数学でいうy = xと同じだとわかっていただけると思います。 それでは関数の作り方が分かったところで、次は関数の使い方を説明します。関数の使い方は簡単で、 ``` 出ていく値を入れる変数 = 関数名(入れる変数) ``` となります。今、作った関数名はliner_graphでしたね。つまりもし、 ``` y = liner_graph(5); ``` と書くとyに5が入ることになります。 これをうまく活用して、165行目、179行目をこのように書き換えます。 ``` this.my = this.liner_graph(this.mx); ``` <B><font color="red">liner_graph関数に入れる変数は、今の自分のx座標の値です。そして関数から出てきた数を今の自分のy座標の値に入れることになります</font></B>。 これにより、liner_graphで指定した関数上の点を、敵が動いてくれることになります。 ```javascript= enchant(); var game; var bear; var enemy; var bullet; var life; var context; var line; var surface; var GameWidth = 320; var GameHeight = 320; /*弾リスト*/ var bulletList = null; /*敵リスト*/ var enemyList = null; //敵のパラメータ var ENEMY_WIDTH = 32; var ENEMY_HEIGHT = 32; var ENEMY_CREATE_INTERVAL = 15; //Array 拡張 Array.prototype.erase = function(elm){ //渡されたelmが配列の何番目かを保存 var index = this.indexOf(elm); //渡されたelmから一つの要素を削除して前に詰める == elmを削除する this.splice(index,1); //削除された配列を返す return this; }; /* 独自クラスBearの定義 */ Bear = Class.create(Sprite, { initialize: function(x, y) { Sprite.call(this, 32, 32); this.image = game.assets['images/chara1.png']; this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.frame = 0; this.mutekitime = 0; this.mukekiflag = false; /* [space]キーをbボタンとして割り当てる */ game.keybind(32, "space"); // space game.rootScene.addChild(this); }, /* enterframeイベントのリスナーを設定 */ onenterframe: function() { /* 移動処理 */ if(game.input.up){ //↑キーを押した if(this.my <= 8) { this.my = this.my + 1 / 5; } } if(game.input.down){ //↓キーを押した if(this.my >= -6.5) { this.my = this.my - 1 / 5; } } this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); /* 弾発射処理 */ if(game.input.space){ bullet = new Bullet(this.mx + 1,this.my - 1 / 2); //bulletListに今作った弾を追加 bulletList.push(bullet); } /* 敵とクマの衝突判定 */ var enemys; for(var i = 0, len = enemyList.length; i < len; i = i + 1){ enemys = enemyList[i]; //敵とくまとの衝突判定 if(enemys.within(this) && this.mutekiflag == false){ //弾を消去 enemys.destroy = true; this.mutekiflag = true; life.life = life.life - 1; if(life.life <= 0){ game.rootScene.removeChild(this); game.end(); } } } if(game.frame % game.fps % game.fps == 0){ this.mutekitime = this.mutekitime + 1; if(this.mutekitime >= 3){ this.mutekiflag = false; this.mutekitime = 0; } } if(this.mutekiflag){ this.opacity = 0.3; } else{ this.opacity = 1.0; } }, }); //弾クラス Bullet = Class.create(Sprite , { //初期化処理 initialize:function(x, y){ Sprite.call(this,8,16); //弾画像の登録 this.image = game.assets["images/bullet.png"]; //初期位置設定 this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //何かに当たった時にtrueにする this.destroy = false; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //弾の速さ this.mx = this.mx + 3 / 4; this.x = from_mat_to_enc_x(this.mx); //削除処理 if(this.mx > 9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //bulletListから削除する bulletList.erase(this); } } }); //敵クラス Enemy = Class.create(Sprite, { //初期化処理 initialize:function(x){ Sprite.call(this,ENEMY_WIDTH,ENEMY_HEIGHT); //敵画像の登録 this.image = game.assets["images/enemy.png"]; //画像をランダムに決める this.frame = 0; //敵のスピード this.speed = Math.random() * 5 + 1; //何かに当たった時にtrueにする this.destroy = false; //初期位置設定 this.mx = x; this.my = this.liner_graph(this.mx); //ここを変更 this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.ox = this.mx; this.oy = this.my; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //x座標設定 this.mx = this.mx - (1 / 10); //y座標設定 this.my = this.liner_graph(this.mx); //ここを変更 this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //線を描画する //Spriteを作ります //線の描画を始める context.beginPath(); //パスを開始 context.moveTo(from_mat_to_enc_x(this.ox) - 1, from_mat_to_enc_y(this.oy) - 1); context.lineTo(from_mat_to_enc_x(this.mx) - 1, from_mat_to_enc_y(this.my) - 1); context.closePath(); //パスを終了 context.stroke(); //パスを描画する //1つ前の座標を保存する this.ox = this.mx; this.oy = this.my; //削除処理 if(this.mx < -9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //enemyListから削除 enemyList.erase(this); } //全ての弾と敵との衝突判定 var bullets; for(var i = 0, len = bulletList.length; i < len; i = i + 1){ bullets = bulletList[i]; //敵と弾との衝突判定 if(bullets.within(this)){ //弾を消去 bullets.destroy = true; this.destroy = true; } } }, //1次関数でグラフを作る liner_graph : function(x){ var a = 1; var b = 0; var y = a * x + b; return y; }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.png', 'images/clear.png','images/bullet.png','images/enemy.png', 'images/masu_関数.png'); //弾リストの初期化 bulletList = []; //敵リストの初期化 enemyList = []; game.onload = function() { //背景画像を設定 //game.rootScene.backgroundColor = 'Black'; //背景画像が黒 var bg = new Sprite(320, 320); bg.image = game.assets["images/masu_関数.png"]; //背景画像が直交座標 game.rootScene.addChild(bg); bg.x = bg.x - 1; /* クマをつくる */ bear = new Bear(-7.5, 7.5); /* ライフをつくる */ //ここを追加 life = new LifeLabel(10, 10, 3); life.life = 3; //ここを追加 game.rootScene.addChild(life); //線を作る line = new Sprite(320, 320); //Surfaceを作る surface = new Surface(320, 320); //spriteのimageにsurfaceを代入します line.image = surface; //コンテキストを取得します context = surface.context; //シーンにサーフェスを追加する ' game.rootScene.addChild(line); }; game.start(); //更新処理 game.rootScene.onenterframe = function(){ //敵の出現するタイミング if(game.frame % ENEMY_CREATE_INTERVAL == 0){ //敵を生成 enemy = new Enemy(8); //enemyListに敵を追加 enemyList.push(enemy); } } }; //x座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_x = function(mat_x) { var enc_x = mat_x * 20 + GameWidth / 2; return enc_x; }; //y座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_y = function(mat_y) { var enc_y = GameHeight / 2 - mat_y * 20; return enc_y; }; ``` ## Step8. 敵の動きを複雑にしてみよう(曲線) それでは次は曲線の動きを作ってみましょう。 ![](https://i.imgur.com/IwqvUPX.gif) 曲線は2次関数を利用して実現してみました。 <details><summary><B><font color="blue">2次関数についてはこちらをクリックすると、説明が出てきます。</font></B></summary> ### 2次関数とは 2次関数とはy=x²、y=3x²+2のようにyをxの2次式で表せる関数です。式は以下のように表せます。 y=ax²+bx+c y=x²のxとyの関係表は以下のようになります ![](https://i.imgur.com/LzPRmOi.png) </details> それでは、変更点を見ていきます。が変更点は、liner_graphの中身だけです。どう変更したか見てみましょう。 ``` var a = 1 / 8; var b = 0; var c = -2; var y = a * (x + b) * (x + b) + c; return y; ``` 今回はy=a(x+b)²+cという一般的な2次関数の式の形にして、aに1/8,bに0,cに-2を代入してみました。 ```javascript= enchant(); var game; var bear; var enemy; var bullet; var life; var context; var line; var surface; var GameWidth = 320; var GameHeight = 320; /*弾リスト*/ var bulletList = null; /*敵リスト*/ var enemyList = null; //敵のパラメータ var ENEMY_WIDTH = 32; var ENEMY_HEIGHT = 32; var ENEMY_CREATE_INTERVAL = 15; //Array 拡張 Array.prototype.erase = function(elm){ //渡されたelmが配列の何番目かを保存 var index = this.indexOf(elm); //渡されたelmから一つの要素を削除して前に詰める == elmを削除する this.splice(index,1); //削除された配列を返す return this; }; /* 独自クラスBearの定義 */ Bear = Class.create(Sprite, { initialize: function(x, y) { Sprite.call(this, 32, 32); this.image = game.assets['images/chara1.png']; this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.frame = 0; this.mutekitime = 0; this.mukekiflag = false; /* [space]キーをbボタンとして割り当てる */ game.keybind(32, "space"); // space game.rootScene.addChild(this); }, /* enterframeイベントのリスナーを設定 */ onenterframe: function() { /* 移動処理 */ if(game.input.up){ //↑キーを押した if(this.my <= 8) { this.my = this.my + 1 / 5; } } if(game.input.down){ //↓キーを押した if(this.my >= -6.5) { this.my = this.my - 1 / 5; } } this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); /* 弾発射処理 */ if(game.input.space){ bullet = new Bullet(this.mx + 1,this.my - 1 / 2); //bulletListに今作った弾を追加 bulletList.push(bullet); } /* 敵とクマの衝突判定 */ var enemys; for(var i = 0, len = enemyList.length; i < len; i = i + 1){ enemys = enemyList[i]; //敵とくまとの衝突判定 if(enemys.within(this) && this.mutekiflag == false){ //弾を消去 enemys.destroy = true; this.mutekiflag = true; life.life = life.life - 1; if(life.life <= 0){ game.rootScene.removeChild(this); game.end(); } } } if(game.frame % game.fps % game.fps == 0){ this.mutekitime = this.mutekitime + 1; if(this.mutekitime >= 3){ this.mutekiflag = false; this.mutekitime = 0; } } if(this.mutekiflag){ this.opacity = 0.3; } else{ this.opacity = 1.0; } }, }); //弾クラス Bullet = Class.create(Sprite , { //初期化処理 initialize:function(x, y){ Sprite.call(this,8,16); //弾画像の登録 this.image = game.assets["images/bullet.png"]; //初期位置設定 this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //何かに当たった時にtrueにする this.destroy = false; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //弾の速さ this.mx = this.mx + 3 / 4; this.x = from_mat_to_enc_x(this.mx); //削除処理 if(this.mx > 9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //bulletListから削除する bulletList.erase(this); } } }); //敵クラス Enemy = Class.create(Sprite, { //初期化処理 initialize:function(x){ Sprite.call(this,ENEMY_WIDTH,ENEMY_HEIGHT); //敵画像の登録 this.image = game.assets["images/enemy.png"]; //画像をランダムに決める this.frame = 0; //敵のスピード this.speed = Math.random() * 5 + 1; //何かに当たった時にtrueにする this.destroy = false; //初期位置設定 this.mx = x; this.my = this.liner_graph(this.mx); this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.ox = this.mx; this.oy = this.my; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //x座標設定 this.mx = this.mx - (1 / 10); //y座標設定 this.my = this.liner_graph(this.mx); this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //線を描画する //Spriteを作ります //線の描画を始める context.beginPath(); //パスを開始 context.moveTo(from_mat_to_enc_x(this.ox) - 1, from_mat_to_enc_y(this.oy) - 1); context.lineTo(from_mat_to_enc_x(this.mx) - 1, from_mat_to_enc_y(this.my) - 1); context.closePath(); //パスを終了 context.stroke(); //パスを描画する //1つ前の座標を保存する this.ox = this.mx; this.oy = this.my; //削除処理 if(this.mx < -9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //enemyListから削除 enemyList.erase(this); } //全ての弾と敵との衝突判定 var bullets; for(var i = 0, len = bulletList.length; i < len; i = i + 1){ bullets = bulletList[i]; //敵と弾との衝突判定 if(bullets.within(this)){ //弾を消去 bullets.destroy = true; this.destroy = true; } } }, //2次関数でグラフを作る //ここを変更 liner_graph : function(x){ var a = 1 / 8; //ここを変更 var b = 0; //ここを変更 var c = -2; //ここを変更 var y = a * (x + b) * (x + b) + c; //ここを変更 return y; //ここを変更 }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.png', 'images/clear.png','images/bullet.png','images/enemy.png', 'images/masu_関数.png'); //弾リストの初期化 bulletList = []; //敵リストの初期化 enemyList = []; game.onload = function() { //背景画像を設定 //game.rootScene.backgroundColor = 'Black'; //背景画像が黒 var bg = new Sprite(320, 320); bg.image = game.assets["images/masu_関数.png"]; //背景画像が直交座標 game.rootScene.addChild(bg); bg.x = bg.x - 1; /* クマをつくる */ bear = new Bear(-7.5, 7.5); /* ライフをつくる */ //ここを追加 life = new LifeLabel(10, 10, 3); life.life = 3; //ここを追加 game.rootScene.addChild(life); //線を作る line = new Sprite(320, 320); //Surfaceを作る surface = new Surface(320, 320); //spriteのimageにsurfaceを代入します line.image = surface; //コンテキストを取得します context = surface.context; //シーンにサーフェスを追加する ' game.rootScene.addChild(line); }; game.start(); //更新処理 game.rootScene.onenterframe = function(){ //敵の出現するタイミング if(game.frame % ENEMY_CREATE_INTERVAL == 0){ //敵を生成 enemy = new Enemy(8); //enemyListに敵を追加 enemyList.push(enemy); } } }; //x座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_x = function(mat_x) { var enc_x = mat_x * 20 + GameWidth / 2; return enc_x; }; //y座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_y = function(mat_y) { var enc_y = GameHeight / 2 - mat_y * 20; return enc_y; }; ``` そのほかにも、様々な動きの関数を作ることができます。今回は、3次関数、三角関数を準備してみました。これらの命令をliner_graphの中に書いてみてください。 3次関数:ax³+bx²+cx+d ``` var a = 1 / 2; var b = 1 / 2; var c = 1; var d = 0; var y = a * x * x * x + b * x * x + c * x + d; return y; ``` 三角関数:a*sin(bx+c) ``` var a = 1; var b = 1; var c = 0; var y = a * Math.sin(b * x + c); return y ; ``` 三角関数:a*cos(bx+c) ``` var a = 1; var b = 1; var c = 0; var y = a * Math.cos(b * x + c); return y ; ``` <details><summary><B><font color="blue">三角関数についてはこちらをクリックすると、説明が出てきます。</font></B></summary> ### 三角関数(正弦とは) 三角関数とは,平面三角法における,角の大きさと線分の長さの関係を記述する関数の族および,それらを拡張して得られる関数の総称である.三角関数の一つがsin(正弦)である.sinθは下図の場合b/cで求めることができる. ![](https://i.imgur.com/pP9Hxy5.png) 表に主なものをまとめる(πは円周率である) ![](https://i.imgur.com/ul56esh.png) sinθなどの三角関数を定義するとき,θは角度を意味している. しかし,三角関数を,ある数θに対応する数を与える式,とより抽象的にみるならば, θの意味を角度に限定する必要はない. このため,変数をθで表さず,y=sinxのようにxで表すことも多く, 以下でもそのように扱っていく. θの単位はラジアンであり,πラジアン=180°となっている. そのため,-π/2は-90°,π/6は30°を示す. また、三角関数を表す際には,sin(-π/2)=-1,sin180°= 0のように表記することができる. ## 三角関数(余弦とは) 三角関数とは,平面三角法における,角の大きさと線分の長さの関係を記述する関数の族および,それらを拡張して得られる関数の総称である.三角関数の一つがcos(余弦)である.cosθは下図の場合a/cで求めることができる. ![](https://i.imgur.com/pP9Hxy5.png) 表に主なものをまとめる(πは円周率である) ![](https://i.imgur.com/DTl3s6f.png) y=sinxのグラフをx軸方向に−π/2平行移動させた表になっていることが分かる. cosθなどの三角関数を定義するとき,θは角度を意味している. しかし,三角関数を,ある数θに対応する数を与える式,とより抽象的にみるならば, θの意味を角度に限定する必要はない. このため,変数をθで表さず,y=cosxのようにxで表すことも多く, 以下でもそのように扱っていく. θの単位はラジアンであり,πラジアン=180°となっている. そのため,-π/2は-90°,π/6は30°を示す. また、三角関数を表す際には,cos(-π/2)= 0,cos180°= -1のように表記することができる. </details> 他にも絶対値のグラフを作ってみましょう 絶対値1次関数:|x-1| ``` var a = 1; var b = -1; var y = Math.abs(a*x+b); return y; ``` 絶対値2次関数:|x²-4x+3| ``` var a = 1; var b = -4; var c = 3; var y = Math.abs(ax*x+b*x+c); return y; ``` <details><summary><B><font color="blue">絶対値についてはこちらをクリックすると、説明が出てきます。</font></B></summary> 実数xの絶対値は、その符号を無視して得られる非負の値を言う。つまり正数 x に対して |x| = x および負数 x に対して |x| = −x(このとき −x は正)であり、また |0| = 0 である。数の絶対値は零からの距離と考えられる 絶対値の記号を外すには場合分けが必要である。 ``` f(x)>0 のとき|f(x)|=f(x) f(x)<0 のとき|f(x)|=-f(x) ``` 例 y=|x-2| x-2≧0 すなわち x≧2のとき y=x-2 x-2<0 すなわち x<2のとき y=-x+2 ![](https://i.imgur.com/UWER7Z9.png) 例 y=|x2−2x−3| x²−2x−3≧0のとき すなわち x≦−1, 3≦xのとき y=x²−2x−3 x²−2x−3<0のとき すなわち −1<x<3のとき y=−x²+2x+3 ![](https://i.imgur.com/iKhidul.png) </details> ## Step9. 敵を複数種類作ってみよう 最後に敵を複数種類(5種類)作ってみよう。まず、敵キャラクターのフレームは今まで0で固定でしたが、ランダム(0 ~ 4)で決定します。 157行目 変更前 ``` this.frame = 0; ``` 変更後 ``` this.frame = Math.floor(Math.random() * 6); ``` 次に、y座標の値(this.my)を、frameの値に応じて変更します。変更にはswitch文を使います。初めて説明しますが、if文のようなものだと思ってください。もしthis.frameが1ならばcase1の部分の命令を行うことができます。3ならばcase3の部分の命令を行うことができます。<B><font color="red">165行目と179行目の2か所を変更しなければならないことに注意してください</font></B>。 変更前 ``` this.my = this.liner_graph(this.mx); ``` 変更後 ``` switch (this.frame){ case 0: this.my = this.liner_graph(this.mx); break; case 1: this.my = this.quadratic_graph(this.mx); break case 2: this.my = this.cubic_graph(this.mx); break; case 3: this.my = this.sin_graph(this.mx) ; break; case 4: this.my = this.cos_graph(this.mx); break; case 5: this.my = this.abs_graph(this.mx); break; } ``` 最後は関数です。今追加した部分に、this.liner_graph(this.mx)以外に新たに、 this.quadratic_graph(this.mx); this.cubic_graph(this.mx); this.sin_graph(this.mx) ; this.cos_graph(this.mx); this.abs_graph(this.mx); という関数が出てきています。これらは別々の関数として作ることができます。早速作ってみましょう。今回は、2次関数、3次関数、sin、cosで作ってみました。 226行目に追加 ``` //2次関数でグラフを作る quadratic_graph : function(x){ var a = 1 / 8; var b = 0; var c = -2; var y = a * (x + b) * (x + b) + c; return y; }, //3次関数でグラフを作る cubic_graph : function(x){ var a = 1 / 2; var b = 1 / 2; var c = 1; var d = 0; var y = a * x * x * x + b * x * x + c * x + d; return y; }, //三角関数でグラフを作る //sin関数の場合 sin_graph : function(x){ var a = 1; var b = 1; var c = 0; var y = a * Math.sin(b * x + c); return y ; }, //cos関数の場合 cos_graph : function(x){ var a = 1; var b = 1; var c = 0; var y = a * Math.cos(b * x + c); return y ; }, //絶対値関数の場合 abs_graph : function(x){ var a = 1; var b = 1; var c = 0; var y = Math.abs(a*x+b); return y ; }, ``` これで完成です ```javascript= enchant(); var game; var bear; var enemy; var bullet; var life; var context; var line; var surface; var GameWidth = 320; var GameHeight = 320; /*弾リスト*/ var bulletList = null; /*敵リスト*/ var enemyList = null; //敵のパラメータ var ENEMY_WIDTH = 32; var ENEMY_HEIGHT = 32; var ENEMY_CREATE_INTERVAL = 15; //Array 拡張 Array.prototype.erase = function(elm){ //渡されたelmが配列の何番目かを保存 var index = this.indexOf(elm); //渡されたelmから一つの要素を削除して前に詰める == elmを削除する this.splice(index,1); //削除された配列を返す return this; }; /* 独自クラスBearの定義 */ Bear = Class.create(Sprite, { initialize: function(x, y) { Sprite.call(this, 32, 32); this.image = game.assets['images/chara1.png']; this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.frame = 0; this.mutekitime = 0; this.mukekiflag = false; /* [space]キーをbボタンとして割り当てる */ game.keybind(32, "space"); // space game.rootScene.addChild(this); }, /* enterframeイベントのリスナーを設定 */ onenterframe: function() { /* 移動処理 */ if(game.input.up){ //↑キーを押した if(this.my <= 8) { this.my = this.my + 1 / 5; } } if(game.input.down){ //↓キーを押した if(this.my >= -6.5) { this.my = this.my - 1 / 5; } } this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); /* 弾発射処理 */ if(game.input.space){ bullet = new Bullet(this.mx + 1,this.my - 1 / 2); //bulletListに今作った弾を追加 bulletList.push(bullet); } /* 敵とクマの衝突判定 */ var enemys; for(var i = 0, len = enemyList.length; i < len; i = i + 1){ enemys = enemyList[i]; //敵とくまとの衝突判定 if(enemys.within(this) && this.mutekiflag == false){ //弾を消去 enemys.destroy = true; this.mutekiflag = true; life.life = life.life - 1; if(life.life <= 0){ game.rootScene.removeChild(this); game.end(); } } } if(game.frame % game.fps % game.fps == 0){ this.mutekitime = this.mutekitime + 1; if(this.mutekitime >= 3){ this.mutekiflag = false; this.mutekitime = 0; } } if(this.mutekiflag){ this.opacity = 0.3; } else{ this.opacity = 1.0; } }, }); //弾クラス Bullet = Class.create(Sprite , { //初期化処理 initialize:function(x, y){ Sprite.call(this,8,16); //弾画像の登録 this.image = game.assets["images/bullet.png"]; //初期位置設定 this.mx = x; this.my = y; this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //何かに当たった時にtrueにする this.destroy = false; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //弾の速さ this.mx = this.mx + 3 / 4; this.x = from_mat_to_enc_x(this.mx); //削除処理 if(this.mx > 9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //bulletListから削除する bulletList.erase(this); } } }); //敵クラス Enemy = Class.create(Sprite, { //初期化処理 initialize:function(x){ Sprite.call(this,ENEMY_WIDTH,ENEMY_HEIGHT); //敵画像の登録 this.image = game.assets["images/enemy.png"]; //画像をランダムに決める this.frame = Math.floor(Math.random() * 6); //ここを変更 //敵のスピード this.speed = Math.random() * 5 + 1; //何かに当たった時にtrueにする this.destroy = false; //初期位置設定 this.mx = x; switch (this.frame){ //ここを変更 case 0: //ここを変更 this.my = this.liner_graph(this.mx); //ここを変更 break; //ここを変更 case 1: //ここを変更 this.my = this.quadratic_graph(this.mx); //ここを変更 break //ここを変更 case 2: //ここを変更 this.my = this.cubic_graph(this.mx); //ここを変更 break; //ここを変更 case 3: //ここを変更 this.my = this.sin_graph(this.mx) ; //ここを変更 break; //ここを変更 case 4: //ここを変更 this.my = this.cos_graph(this.mx); //ここを変更 break; //ここを変更 case 5: //ここを変更 this.my = this.abs_graph(this.mx); //ここを変更 break; //ここを変更 } this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); this.ox = this.mx; this.oy = this.my; game.rootScene.addChild(this); }, //更新処理 onenterframe : function(){ //x座標設定 this.mx = this.mx - (1 / 10); //y座標設定 switch (this.frame){ //ここを変更 case 0: //ここを変更 this.my = this.liner_graph(this.mx); //ここを変更 break; //ここを変更 case 1: //ここを変更 this.my = this.quadratic_graph(this.mx); //ここを変更 break //ここを変更 case 2: //ここを変更 this.my = this.cubic_graph(this.mx); //ここを変更 break; //ここを変更 case 3: //ここを変更 this.my = this.sin_graph(this.mx) ; //ここを変更 break; //ここを変更 case 4: //ここを変更 this.my = this.cos_graph(this.mx); //ここを変更 break; //ここを変更 case 5: //ここを変更 this.my = this.abs_graph(this.mx); //ここを変更 break; //ここを変更 } this.x = from_mat_to_enc_x(this.mx); this.y = from_mat_to_enc_y(this.my); //線を描画する //Spriteを作ります //線の描画を始める context.beginPath(); //パスを開始 context.moveTo(from_mat_to_enc_x(this.ox) - 1, from_mat_to_enc_y(this.oy) - 1); context.lineTo(from_mat_to_enc_x(this.mx) - 1, from_mat_to_enc_y(this.my) - 1); context.closePath(); //パスを終了 context.stroke(); //パスを描画する //1つ前の座標を保存する this.ox = this.mx; this.oy = this.my; //削除処理 if(this.mx < -9 || this.destroy == true){ //親要素から自分を除外する this.parentNode.removeChild(this); //enemyListから削除 enemyList.erase(this); } //全ての弾と敵との衝突判定 var bullets; for(var i = 0, len = bulletList.length; i < len; i = i + 1){ bullets = bulletList[i]; //敵と弾との衝突判定 if(bullets.within(this)){ //弾を消去 bullets.destroy = true; this.destroy = true; } } }, //1次関数でグラフを作る liner_graph : function(x){ var a = 1; var b = 0; var y = a * x + b; return y; }, //2次関数でグラフを作る //ここを追加 quadratic_graph : function(x){ //ここを追加 var a = 1 / 8; //ここを追加 var b = 0; //ここを追加 var c = -2; //ここを追加 var y = a * (x + b) * (x + b) + c; //ここを追加 return y; //ここを追加 }, //ここを追加 //3次関数でグラフを作る //ここを追加 cubic_graph : function(x){ //ここを追加 var a = 1 / 2; //ここを追加 var b = 1 / 2; //ここを追加 var c = 1; //ここを追加 var d = 0; //ここを追加 var y = a * x * x * x + b * x * x + c * x + d; //ここを追加 return y; //ここを追加 }, //ここを追加 //三角関数でグラフを作る //ここを追加 //sin関数の場合 //ここを追加 sin_graph : function(x){ //ここを追加 var a = 1; //ここを追加 var b = 1; //ここを追加 var c = 0; //ここを追加 var y = a * Math.sin(b * x + c); //ここを追加 return y; //ここを追加 }, //ここを追加 //cos関数の場合 //ここを追加 cos_graph : function(x){ //ここを追加 var a = 1; //ここを追加 var b = 1; //ここを追加 var c = 0; //ここを追加 var y = a * Math.cos(b * x + c); //ここを追加 return y; //ここを追加 }, //ここを追加 //絶対値関数の場合 abs_graph : function(x){ //ここを追加 var a = 1; //ここを追加 var b = 1; //ここを追加 var c = 0; //ここを追加 var y = Math.abs(a*x+b); //ここを追加 return y ; //ここを追加 }, //ここを追加 }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.png', 'images/clear.png','images/bullet.png','images/enemy.png', 'images/masu_関数.png'); //弾リストの初期化 bulletList = []; //敵リストの初期化 enemyList = []; game.onload = function() { //背景画像を設定 game.rootScene.backgroundColor = 'Black'; //背景画像が黒 //var bg = new Sprite(320, 320); //bg.image = game.assets["images/masu_関数.png"]; //背景画像が直交座標 //game.rootScene.addChild(bg); //bg.x = bg.x - 1; /* クマをつくる */ bear = new Bear(-7.5, 7.5); /* ライフをつくる */ life = new LifeLabel(10, 10, 3); life.life = 3; game.rootScene.addChild(life); //線を作る line = new Sprite(320, 320); //Surfaceを作る surface = new Surface(320, 320); //spriteのimageにsurfaceを代入します line.image = surface; //コンテキストを取得します context = surface.context; //シーンにサーフェスを追加する ' game.rootScene.addChild(line); }; game.start(); //更新処理 game.rootScene.onenterframe = function(){ //敵の出現するタイミング if(game.frame % ENEMY_CREATE_INTERVAL == 0){ //敵を生成 enemy = new Enemy(8); //enemyListに敵を追加 enemyList.push(enemy); } } }; //x座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_x = function(mat_x) { var enc_x = mat_x * 20 + GameWidth / 2; return enc_x; }; //y座標の値を直交座標系からenchantの座標系に変換 from_mat_to_enc_y = function(mat_y) { var enc_y = GameHeight / 2 - mat_y * 20; return enc_y; }; ```