Cocos2d-JS 触り始めた2
前回の記事ではデモプロジェクトを少し触って大体の雰囲気を掴んだ。
今回はもう少し使い込んでみるために、以前作った簡単なゲームを Cocos2d-JS 上で動くよう移植してみることにする。
このゲームはスペースキーを押すとジャンプするだけのいわゆるところのワンキーゲームで、邪魔なブロックの登場パターンはランダムながら毎回同じシード値で生成しているので、練習するとその分だけスコアが伸びる感じのゲームになっている。
死んだらページをリロードするしかなくなったり、BGM を YouTube の動画で再生しているけどループしなかったりとか、読み込み完了前にゲームを開始すると動画再生が上手く始まらなかったりとか、つくりは色々投げやりであるものの、まあ枝葉の問題なので放置している。この記事で当時作っていた頃の変更履歴的なものを追える。
移植と言っても完全な再現とかではなく、似たような動きをする程度のものにしようと思う。
現在までの成果物はこんな感じ。
クリックやタップするとジャンプでき、ブロックを避けることができる。
プロジェクトを準備する
前回までの変更は一旦全て忘れて、改めて
今回のゲームは横画面の方が向いているので、前回行ったような画面の向きの変更などは行わない。
また、本格的にものを作るに当たり、今回からデータは git で管理することにする。
そのため、まず新規にプロジェクトを作成した直後の状態でコミットし、その後 Android 版のビルドを行い、生成されたファイルのうちコミットに含める必要がなさそうなファイルを
ゲームのメイン画面を作る
前回適当にいじくり回したことで、シーン切り替えの方法は概ね分かった。
タイトル画面を作ることに対しては特に難しい課題もないため、いきなりゲームのメイン画面を作っていくことにする。
自機を作る
猫のキャラを表示させるため NyanCat/res/nc.png にファイルを配置し、NyanCat/src/resource.js を以下のように改変した。
なので、NyanCat/src/app.js に
まだ静止状態なので動かしたいが、この自機はユーザーの入力に反応して動かす必要があるので、サンプルで動いていたアニメーションのように未来の動きを予め登録しておくようなことはできない。
そのためこの辺のチュートリアルでも使われている
毎フレームの処理によってアニメーションが一応できたし、後はユーザーの入力によって反応して動くようにすれば、基本的な自機の振る舞いは完成する。
これもチュートリアルを参考にして Scene のタップに反応できるようにする。
実行してみると、タップ位置が左下を
動きの流れのイメージとしてはシーンをタップしたらシーンが
期待していた流れが完成したので、あとは自機の動きの振る舞いを変えていく。
まずは自機の初期位置を左中央に変更する。
このコードでは
あとはジャンプと、そのついでにゲーム開始直後にいきなり落ち始める代わりに最初だけはちょっとジャンプっぽく見せるように変更する。
まだ衝突周りの判定はできていないが、自機としては概ねそれっぽい動きが完成した。
邪魔なブロックを作る
邪魔なブロックは、可変サイズの長方形プリミティブオブジェクト的な何かである。
ゲームの実行中に大きさを動的に決め、そのサイズでオブジェクトを作る必要がある。
このようなオブジェクトは cc.DrawNode を使って実現できるようだ。
まずは
次に長方形を描くのだが、大きさや色は
この長方形が描画されたノードを持つ
これを適当な大きさと色で複数生成して動くようにすればいいのだが、今の表示位置は丁度自機と衝突する場所でもあるので、動かす前に当たり判定に関する処理を作り、自機との衝突を検出できるようにしてみる。
衝突の判定は取りあえずインスタンス化した
ただし今回は自機の時とは違い
この点は地味ながらも非常に重要で、移動と衝突判定の順番がおかしいと表示される位置と実際に衝突する位置が噛み合わなくなってしまう。これは低速移動するオブジェクト同士ならあまり目立たないかもしれないが、高速なオブジェクトほど誤差が大きくなる。
実際に衝突判定をするにあたり注意すべき点は他にもあって、ゲームにおける当たり判定は必ずしもスプライトの表示領域とイコールである必要はなくて、少しめり込むぐらいが適しているシーンが多い。
なので、ここでは衝突判定用の領域は別に設け、そこを基準に判定していくことにする。
まずはレイヤーに
レイヤーは
これで当たり判定に使えるボックスが出来上がったので、実際に
当たった時にどう振る舞うのか、という実際のところについてはひとまず後回しにして、先にブロックがたくさん出てくる処理を書いていく。
この動きに関してはユーザーの入力に関わらず同じ振る舞いをさせられるので、その側面から見た場合
まずはブロックを100個に増やしてみる。
ここから更に、ブロックに移動機能をつけてみる。
移動機能が付いたが
これで実行すると、わりと濃い密度のブロックの大群が押し寄せてきて、大体避けられそうにない感じになる。
ただ、このままでは通り過ぎた後はもう二度と再登場することはないのであまりゲームにならない。
なので、画面の左端を抜けたブロックは右端のランダムな上下位置へワープさせて再登場させることにする。
これで地獄絵図みたいな状態が延々と続くようになった。少しブロック数を減らしたりして、ゲームバランスをマシにしてみる。
ダメージ感がないので、ブロックに接触した時にログにメッセージを出力しているのをやめて、同じシーンをもう一度読み込むようにしてゲームオーバー画面の代わりとしてみる。
その作業をしている時にひとつバグを見つけたので修正した。
シーンを切り替えても挙動がおかしいのが原因でこのバグに気付いた。
で、実際のシーン切り替えのコード。
次に
シーン切り替えのコードは前回試したコードと全く同じで、これ以上ループを処理必要がないのが明白なので最後はそのまま
この辺はもっと本格的にゲームオーバー画面とか、死亡時のエフェクトとか、何らかのリアクションを作り込む時に見なおしたほうが良さそうだ。
記事の最初の方でもリンクを置いているが、ここまでの成果物はこちら。
クリックやタップするとジャンプでき、ブロックを避けることができる。ブロックに衝突した場合は強制的にリセットされて最初からやり直しになる。
まだ最初から入っている HelloWorld の動きを一切消さずに書いてるので背景がカオスに華やか。
今回はもう少し使い込んでみるために、以前作った簡単なゲームを Cocos2d-JS 上で動くよう移植してみることにする。
このゲームはスペースキーを押すとジャンプするだけのいわゆるところのワンキーゲームで、邪魔なブロックの登場パターンはランダムながら毎回同じシード値で生成しているので、練習するとその分だけスコアが伸びる感じのゲームになっている。
死んだらページをリロードするしかなくなったり、BGM を YouTube の動画で再生しているけどループしなかったりとか、読み込み完了前にゲームを開始すると動画再生が上手く始まらなかったりとか、つくりは色々投げやりであるものの、まあ枝葉の問題なので放置している。この記事で当時作っていた頃の変更履歴的なものを追える。
移植と言っても完全な再現とかではなく、似たような動きをする程度のものにしようと思う。
現在までの成果物はこんな感じ。
クリックやタップするとジャンプでき、ブロックを避けることができる。
プロジェクトを準備する
前回までの変更は一旦全て忘れて、改めて
cocos new -l js NyanCat
として新規にプロジェクトを作成した。今回のゲームは横画面の方が向いているので、前回行ったような画面の向きの変更などは行わない。
また、本格的にものを作るに当たり、今回からデータは git で管理することにする。
そのため、まず新規にプロジェクトを作成した直後の状態でコミットし、その後 Android 版のビルドを行い、生成されたファイルのうちコミットに含める必要がなさそうなファイルを
.gitignore
で除外した上で作業を始める。ゲームのメイン画面を作る
前回適当にいじくり回したことで、シーン切り替えの方法は概ね分かった。
タイトル画面を作ることに対しては特に難しい課題もないため、いきなりゲームのメイン画面を作っていくことにする。
自機を作る
猫のキャラを表示させるため NyanCat/res/nc.png にファイルを配置し、NyanCat/src/resource.js を以下のように改変した。
diff --git a/res/nc.png b/res/nc.png new file mode 100644 index 0000000..10e8307 Binary files /dev/null and b/res/nc.png differ diff --git a/src/resource.js b/src/resource.js index 508554a..00cd775 100644 --- a/src/resource.js +++ b/src/resource.js @@ -1,14 +1,16 @@ var res = { HelloWorld_png : "res/HelloWorld.png", CloseNormal_png : "res/CloseNormal.png", - CloseSelected_png : "res/CloseSelected.png" + CloseSelected_png : "res/CloseSelected.png", + nc_png : "res/nc.png" }; var g_resources = [ //image res.HelloWorld_png, res.CloseNormal_png, - res.CloseSelected_png + res.CloseSelected_png, + res.nc_png //plistそして追加した画像を
HelloWorldScene
に自機用のレイヤーとして追加したい。なので、NyanCat/src/app.js に
MyShipLayer
を追加し、HelloWorldScene
に MyShipLayer
を追加するためのコードを追加。diff --git a/src/app.js b/src/app.js index 1f2857e..3a4c7e4 100644 --- a/src/app.js +++ b/src/app.js @@ -61,11 +61,22 @@ var HelloWorldLayer = cc.Layer.extend({ } }); +var MyShipLayer = cc.Layer.extend({ + sprite:null, + ctor:function () { + this._super(); + this.sprite = cc.Sprite.create(res.nc_png); + this.addChild(this.sprite, 0); + return true; + } +}); + var HelloWorldScene = cc.Scene.extend({ onEnter:function () { this._super(); var layer = new HelloWorldLayer(); this.addChild(layer); + var myShip = new MyShipLayer(); + this.addChild(myShip); } });この時点で画面の左下に自機が少し見えるようになった。
まだ静止状態なので動かしたいが、この自機はユーザーの入力に反応して動かす必要があるので、サンプルで動いていたアニメーションのように未来の動きを予め登録しておくようなことはできない。
そのためこの辺のチュートリアルでも使われている
update
メソッドを追加して、毎フレームの処理として動きを記述する。更にコンストラクタで this.scheduleUpdate
を呼んで、毎フレーム update
が呼ばれるようにする。diff --git a/src/app.js b/src/app.js index 3a4c7e4..23a3322 100644 --- a/src/app.js +++ b/src/app.js @@ -67,7 +67,12 @@ var MyShipLayer = cc.Layer.extend({ this._super(); this.sprite = cc.Sprite.create(res.nc_png); this.addChild(this.sprite, 0); + this.scheduleUpdate(); return true; + }, + update:function(dt) { + this.x += 1; + this.y += 1; } });移動処理は取りあえず適当に直線移動。実行すると、左下から右上に向かって毎フレーム 1px ずつ動くようになる。
毎フレームの処理によってアニメーションが一応できたし、後はユーザーの入力によって反応して動くようにすれば、基本的な自機の振る舞いは完成する。
これもチュートリアルを参考にして Scene のタップに反応できるようにする。
diff --git a/src/app.js b/src/app.js index 23a3322..2e324b7 100644 --- a/src/app.js +++ b/src/app.js @@ -83,5 +83,15 @@ var HelloWorldScene = cc.Scene.extend({ this.addChild(layer); var myShip = new MyShipLayer(); this.addChild(myShip); - } + cc.eventManager.addListener({ + event: cc.EventListener.TOUCH_ONE_BY_ONE, + swallowTouches: true, + onTouchBegan: this.onTap, + }, this) + }, + onTap:function(touch, event) { + var pos = touch.getLocation(); + cc.log(pos); + return true; + }, });ひとまず、タップされたら座標をログに吐くようにしてみた。
実行してみると、タップ位置が左下を
0, 0
とした値で取得できているのが確認できる。動きの流れのイメージとしてはシーンをタップしたらシーンが
MyShipLayer
にジャンプしろと伝達する感じなので、そうなるようにコードを修正していく。MyShipLayer
に jump
メソッドを新しく追加し、タップされた時にそのメソッドを呼び出すようにする。diff --git a/src/app.js b/src/app.js index 2e324b7..dddb3d2 100644 --- a/src/app.js +++ b/src/app.js @@ -73,16 +73,20 @@ var MyShipLayer = cc.Layer.extend({ update:function(dt) { this.x += 1; this.y += 1; + }, + jump:function() { + this.y += 10; } }); var HelloWorldScene = cc.Scene.extend({ + myShip:null, onEnter:function () { this._super(); var layer = new HelloWorldLayer(); this.addChild(layer); - var myShip = new MyShipLayer(); - this.addChild(myShip); + this.myShip = new MyShipLayer(); + this.addChild(this.myShip); cc.eventManager.addListener({ event: cc.EventListener.TOUCH_ONE_BY_ONE, swallowTouches: true, @@ -90,8 +94,7 @@ var HelloWorldScene = cc.Scene.extend({ }, this) }, onTap:function(touch, event) { - var pos = touch.getLocation(); - cc.log(pos); + event.getCurrentTarget().myShip.jump(); return true; }, });これで実行すると、右上に動き続けている自機がちょっと大きめに上に動くようになった。
期待していた流れが完成したので、あとは自機の動きの振る舞いを変えていく。
まずは自機の初期位置を左中央に変更する。
diff --git a/src/app.js b/src/app.js index dddb3d2..69e9ee8 100644 --- a/src/app.js +++ b/src/app.js @@ -87,6 +87,10 @@ var HelloWorldScene = cc.Scene.extend({ this.addChild(layer); this.myShip = new MyShipLayer(); this.addChild(this.myShip); + this.myShip.attr({ + x: 40, + y: cc.director.getWinSize().height / 2 + }); cc.eventManager.addListener({ event: cc.EventListener.TOUCH_ONE_BY_ONE, swallowTouches: true,次に動きを自由落下っぽく変更する。
diff --git a/src/app.js b/src/app.js index 69e9ee8..0d34a6d 100644 --- a/src/app.js +++ b/src/app.js @@ -63,6 +63,7 @@ var HelloWorldLayer = cc.Layer.extend({ var MyShipLayer = cc.Layer.extend({ sprite:null, + vy: 0, ctor:function () { this._super(); this.sprite = cc.Sprite.create(res.nc_png); @@ -71,8 +72,8 @@ var MyShipLayer = cc.Layer.extend({ return true; }, update:function(dt) { - this.x += 1; - this.y += 1; + this.y += this.vy; + this.vy -= 0.09; }, jump:function() { this.y += 10;
y
を加算すると上方向に移動することになるので、値が下がっていけば落下の動きが表現できる。このコードでは
vy
が 0.09
ずつ値が小さくなっていくので、結果的に落ちれば落ちるほど落下速度が上がっていく。0.09
という値は適当。あとはジャンプと、そのついでにゲーム開始直後にいきなり落ち始める代わりに最初だけはちょっとジャンプっぽく見せるように変更する。
diff --git a/src/app.js b/src/app.js index 0d34a6d..a7817fd 100644 --- a/src/app.js +++ b/src/app.js @@ -63,7 +63,7 @@ var HelloWorldLayer = cc.Layer.extend({ var MyShipLayer = cc.Layer.extend({ sprite:null, - vy: 0, + vy: 3, ctor:function () { this._super(); this.sprite = cc.Sprite.create(res.nc_png); @@ -76,7 +76,7 @@ var MyShipLayer = cc.Layer.extend({ this.vy -= 0.09; }, jump:function() { - this.y += 10; + this.vy = 3; } });どちらも単純に
vy
を 3
にするだけ。vy
は毎フレーム -0.09
されていくので、少し経てばまたすぐ落下傾向の動きになる。まだ衝突周りの判定はできていないが、自機としては概ねそれっぽい動きが完成した。
邪魔なブロックを作る
邪魔なブロックは、可変サイズの長方形プリミティブオブジェクト的な何かである。
ゲームの実行中に大きさを動的に決め、そのサイズでオブジェクトを作る必要がある。
このようなオブジェクトは cc.DrawNode を使って実現できるようだ。
まずは
BlockLayer
を作って HelloWorldScene
に追加してみることにする。diff --git a/src/app.js b/src/app.js index a7817fd..8059657 100644 --- a/src/app.js +++ b/src/app.js @@ -80,6 +80,16 @@ var MyShipLayer = cc.Layer.extend({ } }); +var BlockLayer = cc.Layer.extend({ + d:null, + ctor:function () { + this._super(); + this.d = cc.DrawNode.create(); + this.addChild(this.d, 0); + return true; + } +}); + var HelloWorldScene = cc.Scene.extend({ myShip:null, onEnter:function () { @@ -92,6 +102,8 @@ var HelloWorldScene = cc.Scene.extend({ x: 40, y: cc.director.getWinSize().height / 2 }); + var block = new BlockLayer(); + this.addChild(block); cc.eventManager.addListener({ event: cc.EventListener.TOUCH_ONE_BY_ONE, swallowTouches: true,最終的な登場個数は1個ではないが、取りあえず実験として1つだけ追加した。
次に長方形を描くのだが、大きさや色は
BlockLayer
の外から渡せるようにしたい。また、一度作られたもののサイズや色が変わる予定はないので、今回はコンストラクタで渡せるような形にする。diff --git a/src/app.js b/src/app.js index 8059657..5c6d1a0 100644 --- a/src/app.js +++ b/src/app.js @@ -82,9 +82,11 @@ var MyShipLayer = cc.Layer.extend({ var BlockLayer = cc.Layer.extend({ d:null, - ctor:function () { + ctor:function (width, height, fillcolor) { this._super(); this.d = cc.DrawNode.create(); + var hw = width * 0.5, hh = height * 0.5; + this.d.drawRect(cc.p(-hw, -hh), cc.p(hw, hh), fillcolor); this.addChild(this.d, 0); return true; } @@ -102,7 +104,7 @@ var HelloWorldScene = cc.Scene.extend({ x: 40, y: cc.director.getWinSize().height / 2 }); - var block = new BlockLayer(); + var block = new BlockLayer(100, 100, cc.color(128,128,128,255)); this.addChild(block); cc.eventManager.addListener({ event: cc.EventListener.TOUCH_ONE_BY_ONE,長方形の中央が
0, 0
になるように描画するようにした。この長方形が描画されたノードを持つ
BlockLayer
の位置も今のところ 0, 0
なので、この状態で実行すると画面の左下に 50x50
程度の大きさの正方形が現れる。これを適当な大きさと色で複数生成して動くようにすればいいのだが、今の表示位置は丁度自機と衝突する場所でもあるので、動かす前に当たり判定に関する処理を作り、自機との衝突を検出できるようにしてみる。
衝突の判定は取りあえずインスタンス化した
MyShipLayer
と BlockLayer
が視野にある HelloWorldScene
で行ってみることにする。diff --git a/src/app.js b/src/app.js index 5c6d1a0..36f3a69 100644 --- a/src/app.js +++ b/src/app.js @@ -111,9 +111,12 @@ var HelloWorldScene = cc.Scene.extend({ swallowTouches: true, onTouchBegan: this.onTap, }, this) + this.scheduleUpdateWithPriority(10); }, onTap:function(touch, event) { event.getCurrentTarget().myShip.jump(); return true; }, + update:function(dt) { + }, });まずはまた
update
メソッドを足し、毎フレーム呼ばれるようにする。ただし今回は自機の時とは違い
scheduleUpdate
ではなく cheduleUpdateWithPriority
を使う。scheduleUpdate
は cheduleUpdateWithPriority
を 0
で呼び出しているだけなのであまり大きな違いはないが、こうして優先順位を指定すると update
が呼び出される順番をコントロールできる。この点は地味ながらも非常に重要で、移動と衝突判定の順番がおかしいと表示される位置と実際に衝突する位置が噛み合わなくなってしまう。これは低速移動するオブジェクト同士ならあまり目立たないかもしれないが、高速なオブジェクトほど誤差が大きくなる。
実際に衝突判定をするにあたり注意すべき点は他にもあって、ゲームにおける当たり判定は必ずしもスプライトの表示領域とイコールである必要はなくて、少しめり込むぐらいが適しているシーンが多い。
なので、ここでは衝突判定用の領域は別に設け、そこを基準に判定していくことにする。
まずはレイヤーに
setContentSize
で当たり判定になるボックスのサイズを指定する。diff --git a/src/app.js b/src/app.js index 36f3a69..3a4628d 100644 --- a/src/app.js +++ b/src/app.js @@ -68,6 +68,9 @@ var MyShipLayer = cc.Layer.extend({ this._super(); this.sprite = cc.Sprite.create(res.nc_png); this.addChild(this.sprite, 0); + var bbox = cc.size(18, 12); + this.sprite.setPosition(bbox.width * 0.5, bbox.height * 0.5); + this.setContentSize(bbox); this.scheduleUpdate(); return true; }, @@ -88,6 +91,9 @@ var BlockLayer = cc.Layer.extend({ var hw = width * 0.5, hh = height * 0.5; this.d.drawRect(cc.p(-hw, -hh), cc.p(hw, hh), fillcolor); this.addChild(this.d, 0); + var bbox = cc.size(width, height); + this.d.setPosition(bbox.width * 0.5, bbox.height * 0.5); + this.setContentSize(bbox); return true; } });
MyShipLayer
では自機画像より少し小さいサイズ、BlockLayer
ではそのままのサイズとした。レイヤーは
setContentSize
をしなかった場合デフォルトではスクリーンと同じサイズになっているのだが、具体的に指定するとそのサイズとして振る舞うようになるようだ。これで当たり判定に使えるボックスが出来上がったので、実際に
cc.rectIntersectsRect
で判定を行ってみる。diff --git a/src/app.js b/src/app.js index 3a4628d..e8f4e8d 100644 --- a/src/app.js +++ b/src/app.js @@ -100,6 +100,7 @@ var BlockLayer = cc.Layer.extend({ var HelloWorldScene = cc.Scene.extend({ myShip:null, + block:null, onEnter:function () { this._super(); var layer = new HelloWorldLayer(); @@ -110,8 +111,8 @@ var HelloWorldScene = cc.Scene.extend({ x: 40, y: cc.director.getWinSize().height / 2 }); - var block = new BlockLayer(100, 100, cc.color(128,128,128,255)); - this.addChild(block); + this.block = new BlockLayer(100, 100, cc.color(128,128,128,255)); + this.addChild(this.block); cc.eventManager.addListener({ event: cc.EventListener.TOUCH_ONE_BY_ONE, swallowTouches: true, @@ -124,5 +125,8 @@ var HelloWorldScene = cc.Scene.extend({ return true; }, update:function(dt) { + if (cc.rectIntersectsRect(this.myShip.getBoundingBox(), this.block.getBoundingBox())) { + cc.log('hit'); + } }, });当たっている間だけ、毎フレーム
hit
というログが出続けるようになった。当たった時にどう振る舞うのか、という実際のところについてはひとまず後回しにして、先にブロックがたくさん出てくる処理を書いていく。
この動きに関してはユーザーの入力に関わらず同じ振る舞いをさせられるので、その側面から見た場合
update
で自分で位置を管理する必要はないが、フレーム落ちした時などに自機は落ちた分だけ遅くなるのにブロックは同じ速度で進行してくると不公平な感じがするので、その辺りの理由からこのブロックも update
で位置を更新していく形にしてみる。まずはブロックを100個に増やしてみる。
diff --git a/src/app.js b/src/app.js index e8f4e8d..5c133bd 100644 --- a/src/app.js +++ b/src/app.js @@ -100,7 +100,7 @@ var BlockLayer = cc.Layer.extend({ var HelloWorldScene = cc.Scene.extend({ myShip:null, - block:null, + blocks:[], onEnter:function () { this._super(); var layer = new HelloWorldLayer(); @@ -111,8 +111,22 @@ var HelloWorldScene = cc.Scene.extend({ x: 40, y: cc.director.getWinSize().height / 2 }); - this.block = new BlockLayer(100, 100, cc.color(128,128,128,255)); - this.addChild(this.block); + + for (var i = 0, bl; i < 100; ++i) { + bl = new BlockLayer( + 40+Math.random()*60, + 40+Math.random()*60, + cc.color( + 128+Math.random()*64, + 128+Math.random()*64, + 128+Math.random()*64, + 255 + ) + ); + this.addChild(bl); + this.blocks.push(bl); + } + cc.eventManager.addListener({ event: cc.EventListener.TOUCH_ONE_BY_ONE, swallowTouches: true, @@ -125,8 +139,11 @@ var HelloWorldScene = cc.Scene.extend({ return true; }, update:function(dt) { - if (cc.rectIntersectsRect(this.myShip.getBoundingBox(), this.block.getBoundingBox())) { + var myShipBB = this.myShip.getBoundingBox(); + for(var i = this.blocks.length - 1; i >= 0; --i) { + if (cc.rectIntersectsRect(myShipBB, this.blocks[i].getBoundingBox())) { cc.log('hit'); } + } }, });ランダムな大きさ、ランダムな色で100個のブロックを生成するようにした。まだ初期位置の指定や移動の処理が入っていないので、この段階では左下に大量のブロックが貯まるだけ。
ここから更に、ブロックに移動機能をつけてみる。
diff --git a/src/app.js b/src/app.js index 5c133bd..21c2666 100644 --- a/src/app.js +++ b/src/app.js @@ -85,6 +85,8 @@ var MyShipLayer = cc.Layer.extend({ var BlockLayer = cc.Layer.extend({ d:null, + vx: 0, + vy: 0, ctor:function (width, height, fillcolor) { this._super(); this.d = cc.DrawNode.create(); @@ -94,7 +96,12 @@ var BlockLayer = cc.Layer.extend({ var bbox = cc.size(width, height); this.d.setPosition(bbox.width * 0.5, bbox.height * 0.5); this.setContentSize(bbox); + this.scheduleUpdate(); return true; + }, + update:function(dt) { + this.x += this.vx; + this.y += this.vy; } });これは既に説明する必要がないほど既に通ったパターンなので省く。
移動機能が付いたが
vx
と vy
は 0
のままなので、初期化時に指定するようにする。diff --git a/src/app.js b/src/app.js index 21c2666..eb3d3ea 100644 --- a/src/app.js +++ b/src/app.js @@ -110,13 +110,14 @@ var HelloWorldScene = cc.Scene.extend({ blocks:[], onEnter:function () { this._super(); + var size = cc.director.getWinSize(); var layer = new HelloWorldLayer(); this.addChild(layer); this.myShip = new MyShipLayer(); this.addChild(this.myShip); this.myShip.attr({ x: 40, - y: cc.director.getWinSize().height / 2 + y: size.height / 2 }); for (var i = 0, bl; i < 100; ++i) { @@ -130,6 +131,12 @@ var HelloWorldScene = cc.Scene.extend({ 255 ) ); + bl.attr({ + x: size.width+Math.random()*size.width, + y: -bl.height+Math.random()*(size.height+bl.height), + vx: -1-Math.random()*3, + vy: -0.5+Math.random() + }); this.addChild(bl); this.blocks.push(bl); }
x
座標は同じ大きさの画面が横に2画面続いてるイメージで、見えてないエリアにだけランダムで配置している。y
座標は画面の範囲内のどこか。vx
と vy
は適当な値を指定した。これで実行すると、わりと濃い密度のブロックの大群が押し寄せてきて、大体避けられそうにない感じになる。
ただ、このままでは通り過ぎた後はもう二度と再登場することはないのであまりゲームにならない。
なので、画面の左端を抜けたブロックは右端のランダムな上下位置へワープさせて再登場させることにする。
diff --git a/src/app.js b/src/app.js index eb3d3ea..2119bc1 100644 --- a/src/app.js +++ b/src/app.js @@ -154,10 +154,18 @@ var HelloWorldScene = cc.Scene.extend({ }, update:function(dt) { var myShipBB = this.myShip.getBoundingBox(); - for(var i = this.blocks.length - 1; i >= 0; --i) { - if (cc.rectIntersectsRect(myShipBB, this.blocks[i].getBoundingBox())) { + var size = cc.director.getWinSize(); + for(var i = this.blocks.length - 1, bl; i >= 0; --i) { + bl = this.blocks[i]; + if (cc.rectIntersectsRect(myShipBB, bl.getBoundingBox())) { cc.log('hit'); } + if (bl.x+bl.width < 0) { + bl.attr({ + x: size.width, + y: -bl.height+Math.random()*(size.height+bl.height) + }); + } } }, });どこで実装するのか若干迷ったが、ブロックレイヤーが表示領域のサイズを見て移動する処理を行うのは何となく気持ち悪い気がしたのでひとまずシーン側の update で行うようにした。
これで地獄絵図みたいな状態が延々と続くようになった。少しブロック数を減らしたりして、ゲームバランスをマシにしてみる。
diff --git a/src/app.js b/src/app.js index 2119bc1..a00e2c4 100644 --- a/src/app.js +++ b/src/app.js @@ -76,7 +76,7 @@ var MyShipLayer = cc.Layer.extend({ }, update:function(dt) { this.y += this.vy; - this.vy -= 0.09; + this.vy -= 0.1; }, jump:function() { this.vy = 3; @@ -120,9 +120,9 @@ var HelloWorldScene = cc.Scene.extend({ y: size.height / 2 }); - for (var i = 0, bl; i < 100; ++i) { + for (var i = 0, bl; i < 16; ++i) { bl = new BlockLayer( - 40+Math.random()*60, + 30+Math.random()*60, 40+Math.random()*60, cc.color( 128+Math.random()*64, @@ -134,8 +134,8 @@ var HelloWorldScene = cc.Scene.extend({ bl.attr({ x: size.width+Math.random()*size.width, y: -bl.height+Math.random()*(size.height+bl.height), - vx: -1-Math.random()*3, - vy: -0.5+Math.random() + vx: -0.5-Math.random()*3, + vy: -0.25+Math.random()*0.5 }); this.addChild(bl); this.blocks.push(bl);少しマシになってきた。
ダメージ感がないので、ブロックに接触した時にログにメッセージを出力しているのをやめて、同じシーンをもう一度読み込むようにしてゲームオーバー画面の代わりとしてみる。
その作業をしている時にひとつバグを見つけたので修正した。
diff --git a/src/app.js b/src/app.js index a00e2c4..bbed316 100644 --- a/src/app.js +++ b/src/app.js @@ -107,7 +107,7 @@ var BlockLayer = cc.Layer.extend({ var HelloWorldScene = cc.Scene.extend({ myShip:null, - blocks:[], + blocks:null, onEnter:function () { this._super(); var size = cc.director.getWinSize(); @@ -120,6 +120,7 @@ var HelloWorldScene = cc.Scene.extend({ y: size.height / 2 }); + this.blocks = []; for (var i = 0, bl; i < 16; ++i) { bl = new BlockLayer( 30+Math.random()*60,
onEnter
のタイミングで blocks
を初期化せず、インスタンス化前に作った配列のインスタンスに push
してしまっていたので、全ての HelloWorldScene でこの配列が共有されてしまうバグがあった。シーンを切り替えても挙動がおかしいのが原因でこのバグに気付いた。
で、実際のシーン切り替えのコード。
diff --git a/src/app.js b/src/app.js index bbed316..28cd4bc 100644 --- a/src/app.js +++ b/src/app.js @@ -159,7 +159,10 @@ var HelloWorldScene = cc.Scene.extend({ for(var i = this.blocks.length - 1, bl; i >= 0; --i) { bl = this.blocks[i]; if (cc.rectIntersectsRect(myShipBB, bl.getBoundingBox())) { - cc.log('hit'); + this.unscheduleUpdate(); + cc.eventManager.removeListener(this); + cc.director.runScene(cc.TransitionFade.create(0.5, new HelloWorldScene())); + return; } if (bl.x+bl.width < 0) { bl.attr({まず
unscheduleUpdate
でこれ以上 update
が呼ばれないようにする。これをやらないと自機がブロックに衝突後そのままめり込んでいき何度も死亡判定されてしまう。次に
cc.eventManager.removeListener
でイベントリスナーを削除する。これをやらないと使わなくなっているのにずっと Listen したままになってしまう。シーン切り替えのコードは前回試したコードと全く同じで、これ以上ループを処理必要がないのが明白なので最後はそのまま
return
で抜けている。この辺はもっと本格的にゲームオーバー画面とか、死亡時のエフェクトとか、何らかのリアクションを作り込む時に見なおしたほうが良さそうだ。
記事の最初の方でもリンクを置いているが、ここまでの成果物はこちら。
クリックやタップするとジャンプでき、ブロックを避けることができる。ブロックに衝突した場合は強制的にリセットされて最初からやり直しになる。
まだ最初から入っている HelloWorld の動きを一切消さずに書いてるので背景がカオスに華やか。