# チュートリアル[物理を利用したゲーム作り(ボール投げ)] ## 目次 > #### Step0 運動方程式とは > #### Step1 ゲームの骨組みを作ろう > #### Step2 的となるクマに動きをつけよう > #### Step3 ボールに動きをつけよう > #### Step4 ボールの変数を画面に表示させよう > #### Step5 クマとボールに当たり判定をつけよう ## Step0 運動方程式とは 運動方程式(うんどうほうていしき)とは、物理学において運動の従う法則を数式に表したもののことです。 今回はその中でボールの垂直投げ上げに注目していきます。 ## 垂直投げ上げの場合 鉛直投げ上げでは一般的に、上向きを正の値とします。ここで、重力加速度gは常に下向きの力でした。 鉛直投げ上げでは、上向きが正なので、下向きの力である重力加速度gは-gと表現されます。(下図参照) ※物体が上昇している間であろうと、重力加速度gは常に下向きの力です。なので、鉛直投げ上げにおける加速度は常に-gです。 このことから物体は等加速度直線運動をしていることが分かります よって式は以下のようになります [鉛直投げ上げ公式] ・速度 v = v0-gt ・変位 y = v0t-1/2gt² ・時間を含まない式 v²-v0² = -2gy これらの式をもとにボールの投げ上げの動きを作っていきましょう ![](https://i.imgur.com/Fs8I9uW.gif) ## Step1 ゲームの骨組みを作ろう ### コードの準備をしよう まずは下のコードのような骨組みを用意しましょう。 ```javascript= enchant(); var GameWidth = 320; var GameHeight = 640; //重量加速度 var g = 9.8; //ボールが動くかどうか var flag; //時間を測る var t =0; // ここで自作クラスBearをつくる Bear = Class.create(Sprite, // Spriteクラスを継承 { initialize: function(x, y) { //初期化する Sprite.call(this, 32, 32); //Spriteオブジェクトを初期化 this.image = game.assets['images/chara1.gif']; this.x = x; this.y = y; //クマの動きの速度 this.vx = 5; this.frame = 0; game.rootScene.addChild(this); }, //enterframeイベントのリスナーを定義する onenterframe: function() { } }); // ここで自作クラスBearをつくる Ball = Class.create(Sprite, // Spriteクラスを継承 { initialize: function(x, y) { //初期化する Sprite.call(this, 16, 16); //Spriteオブジェクトを初期化 this.image = game.assets['images/icon1.png']; this.x = x; this.y = y; //ボールの速さ(最初) this.v0 = 30; //ボールの速さ(途中) this.v = 0; //ボールの初期位置 this.ty = this.y; this.frame = 1; game.rootScene.addChild(this); }, //enterframeイベントのリスナーを定義する onenterframe: function() { }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.gif', 'images/icon1.png','images/clear.png'); game.onload = function() { ball = new Ball(GameWidth/2,GameHeight-32); bear = new Bear(0,GameHeight/2-32); game.onenterframe = function(){ }; } game.start(); //game.debug(); } ``` 画面にはクマとボールが表示されています。 これにクマとボールの動きを追加していきましょう クラスBallのパラメータはそれぞれ以下のような役割を持っています。 ```javascript= this.v0 = 30;//ボールの初速度 //ボールの速さ(途中) this.v = 0;//ボールの途中の速度 this.ty = this.y;//ボールの初期位置 ``` ## Step2 的となるクマに動きをつけよう まずはクマに動きをつけていきましょう。 左右に移動するようにしていきます。 クラスBearに移動の処理を加えていきましょう。 ```javascript= onenterframe: function() { this.x += this.vx; } ``` 画面の端に行ったらvxの値を正負逆にしてみましょう。 ```javascript= onenterframe: function() { this.x += this.vx; if(this.x >= GameWidth||this.x <=0){ this.vx *= -1; } } ``` これでクマの動きができました。 確認してみましょう ![](https://i.imgur.com/IOMcxkO.gif) <details> <summary>現状のコードは以下のようになります</summary> ```javascript= enchant(); var GameWidth = 320; var GameHeight = 640; //重量加速度 var g = 9.8; //ボールが動くかどうか var flag; //時間を測る var t =0; // ここで自作クラスBearをつくる Bear = Class.create(Sprite, // Spriteクラスを継承 { initialize: function(x, y) { //初期化する Sprite.call(this, 32, 32); //Spriteオブジェクトを初期化 this.image = game.assets['images/chara1.gif']; this.x = x; this.y = y; //クマの動きの速度 this.vx = 5; this.frame = 0; game.rootScene.addChild(this); }, //enterframeイベントのリスナーを定義する onenterframe: function() { this.x += this.vx; if(this.x >= GameWidth||this.x <=0){ this.vx *= -1; } } }); // ここで自作クラスBearをつくる Ball = Class.create(Sprite, // Spriteクラスを継承 { initialize: function(x, y) { //初期化する Sprite.call(this, 16, 16); //Spriteオブジェクトを初期化 this.image = game.assets['images/icon1.png']; this.x = x; this.y = y; //ボールの速さ(最初) this.v0 = 30; //ボールの速さ(途中) this.v = 0; //ボールの初期位置 this.ty = this.y; this.frame = 1; game.rootScene.addChild(this); }, //enterframeイベントのリスナーを定義する onenterframe: function() { }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.gif', 'images/icon1.png','images/clear.png'); game.onload = function() { ball = new Ball(GameWidth/2,GameHeight-32); bear = new Bear(0,GameHeight/2-32); game.onenterframe = function(){ }; } game.start(); //game.debug(); } ``` </details> ## Step3 ボールに動きをつけよう ではボールに動きをつけましょう ボールは画面をタッチしたときに動かしたいので、タッチしたときのイベントを先に書いておきます ```javascript= //ゲーム画面のどこかをタッチした時の処理 game.rootScene.addEventListener('touchend', function(event) { game.frame = 0; flag = true; }); ``` ではボールの動きを取得するために運動方程式の関数を作成します。 ```javascript= //運動方程式を出す EOM_y: function(v0,t) { var y = v0*t-1/2*g*t*t;//y=v0t-1/2gt² return y; }, EOM_v: function(v0,t) { var v =v0-g*t; //y=v0-gt return v/3; }, ``` これを使ってボールの動きをつけましょう ```javascript= onenterframe: function() { if(flag){ t = game.frame/game.fps; //時間を測る this.y = (GameHeight-32)-this.EOM_y(this.v0*3,t); this.v = this.EOM_v(this.v0*3,t); } }, ``` これでボールが動くはずです ![](https://i.imgur.com/XcwPAPf.gif) <details> <summary>ここまでのコードです</summary> ```javascript= enchant(); var GameWidth = 320; var GameHeight = 640; //重量加速度 var g = 9.8; //ボールが動くかどうか var flag; //時間を測る var t =0; // ここで自作クラスBearをつくる Bear = Class.create(Sprite, // Spriteクラスを継承 { initialize: function(x, y) { //初期化する Sprite.call(this, 32, 32); //Spriteオブジェクトを初期化 this.image = game.assets['images/chara1.gif']; this.x = x; this.y = y; //クマの動きの速度 this.vx = 5; this.frame = 0; game.rootScene.addChild(this); }, //enterframeイベントのリスナーを定義する onenterframe: function() { this.x += this.vx; if(this.x >= GameWidth||this.x <=0){ this.vx *= -1; } } }); // ここで自作クラスBearをつくる Ball = Class.create(Sprite, // Spriteクラスを継承 { initialize: function(x, y) { //初期化する Sprite.call(this, 16, 16); //Spriteオブジェクトを初期化 this.image = game.assets['images/icon1.png']; this.x = x; this.y = y; //ボールの速さ(最初) this.v0 = 30; //ボールの速さ(途中) this.v = 0; //ボールの初期位置 this.ty = this.y; this.frame = 1; game.rootScene.addChild(this); }, //enterframeイベントのリスナーを定義する onenterframe: function() { if(flag){ t = game.frame/game.fps; //時間を測る this.y = (GameHeight-32)-this.EOM_y(this.v0*3,t); this.v = this.EOM_v(this.v0*3,t); } }, //運動方程式を出す EOM_y: function(v0,t) { var y = v0*t-1/2*g*t*t;//y=v0t-1/2gt² return y; }, EOM_v: function(v0,t) { var v =v0-g*t; return v/3; }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.gif', 'images/icon1.png','images/clear.png'); game.onload = function() { ball = new Ball(GameWidth/2,GameHeight-32); bear = new Bear(0,GameHeight/2-32); //ゲーム画面のどこかをタッチした時の処理 game.rootScene.addEventListener('touchend', function(event) { //eventにタッチされた座標が入ってくる game.frame = 0; flag = true; }); game.onenterframe = function(){ }; } game.start(); //game.debug(); } ``` </details> ## Step4 ボールの変数を画面に表示させよう いままのままではボールがどのような状態なのかわかりにくいで。そこで画面上にラベルを置いて、画面にボールの速度等を表示させましょう まずはラベルを作ります。game.onload の中に以下のコードを書いてください ```javascript= // ラベルを作成 var GLabel = new Label("重量加速度:"+g+"m/s²"); GLabel.x = 20; // X座標 GLabel.y = 20; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(GLabel); // ラベルを作成 var V0Label = new Label("初速度:"+ball.v0+"m/s"); V0Label.x = 20; // X座標 V0Label.y = 40; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(V0Label); // ラベルを作成 var VLabel = new Label("速度:"+ball.v+"m/s"); VLabel.x = 20; // X座標 VLabel.y = 60; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(VLabel); // ラベルを作成 var mLabel = new Label("距離:"+(ball.y-ball.ty)+" mLabel.x = 20; // X座標 mLabel.y = 80; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(mLabel); // ラベルを作成 var tLabel = new Label("時間:"+t+"秒"); tLabel.x = 20; // X座標 tLabel.y = 100; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(tLabel); ``` 今の状態でも画面にラベルが表示されていると思いますが、値は変化しません ここから値を常に更新して出す形にします。 ![](https://i.imgur.com/HRu8zT9.png) game.onenterframeの中に処理を追加します。 ```javascript= v = ball.v * 100; VLabel.text = "速度:"+Math.round(v)/100+"m/s"; m = (ball.ty-ball.y)/3 * 100; mLabel.text = "距離:"+Math.round(m)/100+"m"; lt = t * 100; tLabel.text = "時間:"+Math.round(lt)/100+"秒"; ``` 数値は100倍した後に100で割っているのは小数点の最小値を2桁にするためです。 これで画面に常に速度の値が確認できている状態になりました。 初速度の値を20m/sにしたり値を変えてみてください、速度の値等の変化を確認できると思います。 ![](https://i.imgur.com/1ZlTBoc.gif) <details> <summary>ここまでのコードです</summary> ```javascript= enchant(); var GameWidth = 320; var GameHeight = 640; //重量加速度 var g = 9.8; //ボールが動くかどうか var flag; //時間を測る var t =0; // ここで自作クラスBearをつくる Bear = Class.create(Sprite, // Spriteクラスを継承 { initialize: function(x, y) { //初期化する Sprite.call(this, 32, 32); //Spriteオブジェクトを初期化 this.image = game.assets['images/chara1.gif']; this.x = x; this.y = y; //クマの動きの速度 this.vx = 5; this.frame = 0; game.rootScene.addChild(this); }, //enterframeイベントのリスナーを定義する onenterframe: function() { this.x += this.vx; if(this.x >= GameWidth||this.x <=0){ this.vx *= -1; } } }); // ここで自作クラスBearをつくる Ball = Class.create(Sprite, // Spriteクラスを継承 { initialize: function(x, y) { //初期化する Sprite.call(this, 16, 16); //Spriteオブジェクトを初期化 this.image = game.assets['images/icon1.png']; this.x = x; this.y = y; //ボールの速さ(最初) this.v0 = 30; //ボールの速さ(途中) this.v = 0; //ボールの初期位置 this.ty = this.y; this.frame = 1; game.rootScene.addChild(this); }, //enterframeイベントのリスナーを定義する onenterframe: function() { if(flag){ t = game.frame/game.fps; //時間を測る this.y = (GameHeight-32)-this.EOM_y(this.v0*3,t); this.v = this.EOM_v(this.v0*3,t); } }, //運動方程式を出す EOM_y: function(v0,t) { var y = v0*t-1/2*g*t*t;//y=v0t-1/2gt² return y; }, EOM_v: function(v0,t) { var v =v0-g*t; return v/3; }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.gif', 'images/icon1.png','images/clear.png'); game.onload = function() { ball = new Ball(GameWidth/2,GameHeight-32); bear = new Bear(0,GameHeight/2-32); // ラベルを作成 var GLabel = new Label("重量加速度:"+g+"m/s²"); GLabel.x = 20; // X座標 GLabel.y = 20; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(GLabel); // ラベルを作成 var V0Label = new Label("初速度:"+ball.v0+"m/s"); V0Label.x = 20; // X座標 V0Label.y = 40; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(V0Label); // ラベルを作成 var VLabel = new Label("速度:"+ball.v+"m/s"); VLabel.x = 20; // X座標 VLabel.y = 60; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(VLabel); // ラベルを作成 var mLabel = new Label("距離:"+(ball.y-ball.ty)+"m"); mLabel.x = 20; // X座標 mLabel.y = 80; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(mLabel); // ラベルを作成 var tLabel = new Label("時間:"+t+"秒"); tLabel.x = 20; // X座標 tLabel.y = 100; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(tLabel); //ゲーム画面のどこかをタッチした時の処理 game.rootScene.addEventListener('touchend', function(event) { //eventにタッチされた座標が入ってくる game.frame = 0; flag = true; }); game.onenterframe = function(){ v = ball.v * 100; VLabel.text = "速度:"+Math.round(v)/100+"m/s"; m = (ball.ty-ball.y)/3 * 100; mLabel.text = "距離:"+Math.round(m)/100+"m"; lt = t * 100; tLabel.text = "時間:"+Math.round(lt)/100+"秒"; }; } game.start(); //game.debug(); } ``` </details> ## Step5 クマとボールに当たり判定をつけよう それでは最後にゲームのクリア条件を追加しましょう。 今回は降りてきたボールにクマが触れたときとします。 やり方はシンプルに速度が負の方向になっているときです。 ```javascript= if(this.within(ball)){ if(ball.v<0){ /* クリア画面を出したいときは以下の記述 */ game.endScene = new SplashScene(); game.endScene.image = game.assets['images/clear.png']; game.end(); game.stop(); } } ``` これでゲームの完成です。クマの位置を変えたり、初速度を変えたりしてみましょう。 ![](https://i.imgur.com/aVhd5sU.png) それでは初速度の値this.v0を変更して以下の問題を解いてみてください 実際に紙に書いてみて答えが正しいか試してみましょう (1)初速度19.6m/sの時の速度が0になる秒数とボールの最高点 (2)最高点が78.4mのときの最高点に到達するときの秒数、また、初期地点に戻ったときの秒数 <details> <summary>完成品のコードです</summary> ```javascript= enchant(); var GameWidth = 320; var GameHeight = 640; //重量加速度 var g = 9.8; //ボールが動くかどうか var flag; //時間を測る var t =0; // ここで自作クラスBearをつくる Bear = Class.create(Sprite, // Spriteクラスを継承 { initialize: function(x, y) { //初期化する Sprite.call(this, 32, 32); //Spriteオブジェクトを初期化 this.image = game.assets['images/chara1.gif']; this.x = x; this.y = y; //クマの動きの速度 this.vx = 5; this.frame = 0; game.rootScene.addChild(this); }, //enterframeイベントのリスナーを定義する onenterframe: function() { this.x += this.vx; if(this.x >= GameWidth||this.x <=0){ this.vx *= -1; } if(this.within(ball)){ if(ball.v<0){ /* クリア画面を出したいときは以下の記述 */ game.endScene = new SplashScene(); game.endScene.image = game.assets['images/clear.png']; game.end(); game.stop(); } } } }); // ここで自作クラスBearをつくる Ball = Class.create(Sprite, // Spriteクラスを継承 { initialize: function(x, y) { //初期化する Sprite.call(this, 16, 16); //Spriteオブジェクトを初期化 this.image = game.assets['images/icon1.png']; this.x = x; this.y = y; //ボールの速さ(最初) this.v0 = 30; //ボールの速さ(途中) this.v = 0; //ボールの初期位置 this.ty = this.y; this.frame = 1; game.rootScene.addChild(this); }, //enterframeイベントのリスナーを定義する onenterframe: function() { if(flag){ t = game.frame/game.fps; //時間を測る this.y = (GameHeight-32)-this.EOM_y(this.v0*3,t); this.v = this.EOM_v(this.v0*3,t); } }, //運動方程式を出す EOM_y: function(v0,t) { var y = v0*t-1/2*g*t*t;//y=v0t-1/2gt² return y; }, EOM_v: function(v0,t) { var v =v0-g*t; return v/3; }, }); window.onload = function() { game = new Game(GameWidth, GameHeight); game.preload('images/chara1.gif', 'images/icon1.png','images/clear.png'); game.onload = function() { ball = new Ball(GameWidth/2,GameHeight-32); bear = new Bear(0,GameHeight/2-32); // ラベルを作成 var GLabel = new Label("重量加速度:"+g+"m/s²"); GLabel.x = 20; // X座標 GLabel.y = 20; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(GLabel); // ラベルを作成 var V0Label = new Label("初速度:"+ball.v0+"m/s"); V0Label.x = 20; // X座標 V0Label.y = 40; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(V0Label); // ラベルを作成 var VLabel = new Label("速度:"+ball.v+"m/s"); VLabel.x = 20; // X座標 VLabel.y = 60; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(VLabel); // ラベルを作成 var mLabel = new Label("距離:"+(ball.y-ball.ty)+"m"); mLabel.x = 20; // X座標 mLabel.y = 80; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(mLabel); // ラベルを作成 var tLabel = new Label("時間:"+t+"秒"); tLabel.x = 20; // X座標 tLabel.y = 100; // Y座標 // ラベルを画面に表示 game.rootScene.addChild(tLabel); //ゲーム画面のどこかをタッチした時の処理 game.rootScene.addEventListener('touchend', function(event) { //eventにタッチされた座標が入ってくる game.frame = 0; flag = true; }); game.onenterframe = function(){ v = ball.v * 100; VLabel.text = "速度:"+Math.round(v)/100+"m/s"; m = (ball.ty-ball.y)/3 * 100; mLabel.text = "距離:"+Math.round(m)/100+"m"; lt = t * 100; tLabel.text = "時間:"+Math.round(lt)/100+"秒"; }; } game.start(); //game.debug(); } ``` </details>