忘れないうちに、どうやって作ったかなど記録しておきたいと思います。(最近すぐ忘れてしまいます。やればやるほど、びっくりするくらいものすごい勢いで忘れてくので悲しいばかりです。新しいことを処理するのに、忘れないと間に合わないと海馬が判断しているのでしょうか…)
Contents
1.ゲームの内容
へびが動いていきます。へびは、進行方向に対して、右か左いずれかタップした方向に曲がります。(後方には曲がりません)へびは虹色の玉を落としていきます。
タイトル画面は、自由に動かせる練習画面。
ゲーム画面は、頭や玉が壁に当たらないように、ゴールの卵まで到達したらクリア。それには少しこつが要ります。上手に動かさないと、頭は当たらなくても玉が壁に当たってしまいます。
お手本画面としてオートマチックにクリアするものを作ってみました。
2.作ったきっかけ
ゲーム投稿サイトitch.ioの中で、Snakeというゲームがありました。その説明を訳します。
「Snakeというアーケードゲームがありました。それは、蛇を操作して、りんごを食べ、画面いっぱいに成長させなければなりませんが、壁や自分にブロックされるとアウトというゲームです。
Snakeは、1976年に、グレムリン社によって作成された、Blockadeを元に作られたものです。そこから、多くの模写や派生したバージョンが登場しています。
1998年には、ノキアによってSnakeとSnake2が携帯電話に搭載?されたことにより、一般により広く知られることとなりました。そうして、偉大な古典ゲームのひとつとなりました。
このバージョンのSnakeは、Snake2のレトロスタイルのリメイク版です。(以下略・・)」
要するに、古典的有名ゲームにSnakeというゲームがあって、そのリメイクですよということらしいです。偉大な古典ゲーム・・おそらく、他にはポン、ブロック崩し、テトリス(愛しの!)あたりのことをいうのでしょうか。息子がいろいろ教えてくれました。
さすがは偉大な古典ゲーム、私はさほど珍しさを感じなかったのですが、このレトロなゲームを息子がなぜかいたく気に入り(やはり人を惹きつけるものがあるのでしょう)、へびを作ろうということになりました。
3.試行錯誤の連続
動きとしては、頭があり、いくつかのブロックでできた体がその動きに規則正しく遅れてついていくというものです。一見簡単そうに見えましたが、これがなかなか難しい。直線で動かすのは簡単にできても、曲がる時や曲がった後にずれてしまいます。
どこかにこの動きのサンプルがないか探したのですが、見つけることができませんでした。
ちなみに、いままで作ったゲームはすべて授業で習ったことだけで、他の人が作ったプログラムを見て作ったものはなくすべて自分たちで一からコードを考えて作ってきたのですが、今回は何か参考にしたほうがいいだろうと思ったのです。しかし、どこにもそれらしいものがありませんでした。
そこで、最初にやったのは、出発点から到達点までを決めて、速度を一定にし、胴体のひとつひとつをそれぞれ出発点で送らせて同じ速度で動かすという方法です。直線は簡単にできても、クリックしてからの動きがうまくいきませんでした。
次に試したのは、・・・なんだっけ、いろいろあって忘れてしまいました。兎に角、あきらめて、すべて捨てました。最後に成功したのが以下の方法です。多分、すごく原始的なやり方だと思います。
4.作り方
作りたかった基本の動きは以下の二つです。
1.ほかっておくとまっすぐ進む。進行方向に対して、右か左いずれかクリックした方向に曲がる。(後ろに曲がることはできない)
2.画面の端まできたら、反対の端にまわって出現する。
3.へびが通ったあとにカラフルな玉が置かれていく。
⑴本体
まず、へびの頭(head)を出し、その後に16ピクセルずらして体(body1~5)を5個のブロックで作ります。
var head = new ExSprite(16, 16); head.image = core.assets["images/cf307/snakeheadene.png"]; //head.backgroundColor = "red"; head.x = 320 - 16 - 8; head.y = 480 - 16 * 5; snakeGroup.addChild(head); var body1 = new ExSprite(16, 16); body1.backgroundColor = "green"; body1.x = head.x + head.width / 2 - body1.width / 2; body1.y = head.y + 16; snakeGroup.addChild(body1); 以下body2~body5も同様につくる
⑵最初の基本の動き loop();を使って動かす
はじめは下から出て、上に向かって進むので、上むきの動きにします。
胴体のブロックをどの仕組みを使って動かすかが最大のポイントですが、最終的に、loop();を使って16ピクセル(胴体のブロック一つ分)ずつ動かすやり方に行き着きました。
// 最初の動き(上向き) head.tl.moveBy(0, -16, 8); head.tl.moveBy(0, -16, 8); head.tl.loop(); // 最初の方向(上向き) var houkou = 1; console.log(houkou); body1.tl.moveBy(0, -16, 8); body1.tl.moveBy(0, -16, 8); body1.tl.loop(); 以下body2~5も同様。
⑶クリックすると左右に曲がる
へびの向かっている方向としては、画面に向かって上むき、右向き、左向き、下向きの4通りがあるので、それぞれで分ける必要があります。そこで上むきで進んでいるときの方向を1と指定して、それぞれ指定しておきます。そして、
// 上向き(houkou=1) のとき
// 右向き(houkou=2) のとき
// 左向き(houkou=3) のとき
// 下向き(houkou=4) のとき
としてそれぞれタップしたとき、進行方向に対して左右に曲がるようにしました。
上むきのときを例に説明します。
上むきのときに、へびの頭より右側をタッチした場合は、head.x < e.x の場合なので、タッチされたらloopを停止し、曲がる方向に頭を回転させ、こんどはx軸の方向に8カウントで16ピクセル進むのを繰り返します。
bodyについても同様に、headから8カウント遅らせて、headの後を追う動きにします。
// 上向き(houkou=1) のとき scene.addEventListener(Event.TOUCH_START, function(e){ if(houkou == 1){ // 右側タッチで右に曲がる if(head.x < e.x){ head.tl.unloop(); head.tl.clear(); head.tl.rotateTo(90, 0); head.tl.then(function(){ //こうしないと上にむかうloopが効いてしまう head.tl.moveBy(16, 0, 8); head.tl.moveBy(16, 0, 8); head.tl.loop(); houkou = 2; // 右方向を2と指定 console.log(houkou); }); body1.tl.unloop(); body1.tl.clear(); body1.tl.moveTo(head.x, head.y, 8);// body一個分進んでからなので body1.tl.then(function(){ body1.tl.moveBy(16, 0, 8); body1.tl.moveBy(16, 0, 8); body1.tl.loop(); }); 以下body2~5についても同じようにする
同じように、左側をタッチした場合は、 if(head.x > e.x){};とし、 head.tl.moveBy(-16, 0, 8);と動かします。
あとは、右向き、左向き、下向きの場合も、同じように進行方向だけ変えれば書くことができます。
⑷画面の端まできたら、反対側にまわって出現させる
端まできたら、そのまま消えてしまうのではなくて、反対側から続いているように出現させることにしました。
それには、端に線を置いておいて、ぶつかったら反対側に移動するという動きにします。
上下左右にbarを配置します。
var bar = new ExSprite(320, 1); //bar.backgroundColor = "red"; bar.y = 0; scene.addChild(bar); bar.tag = "上のバー"; var barmigi = new ExSprite(1, 480); //barmigi.backgroundColor = "red"; barmigi.x = 319; scene.addChild(barmigi); barmigi.tag = "右のバー"; var barhidari = new ExSprite(1, 480); //barhidari.backgroundColor = "red"; barhidari.x = 0; scene.addChild(barhidari); barhidari.tag = "左のバー"; var barsita = new ExSprite(320, 1); //barsita.backgroundColor = "red"; barsita.y = 479; scene.addChild(barsita); barsita.tag = "下のバー";
衝突判定をして、当たったら反対側に移動します。動きはそのままなので、座標を指定して動かすだけです。
// バーに当たったら head.addCollision(bar); head.addCollision(barmigi); head.addCollision(barhidari); head.addCollision(barsita); head.addEventListener(Event.COLLISION, function(e) { if(e.collision.target.tag == "上のバー"){ console.log("ぶつかったよ"); console.log(head.y); head.y = 480 - 17; } if(e.collision.target.tag == "右のバー"){ head.x = 2; } if(e.collision.target.tag == "左のバー"){ head.x = 320 - 17; } if(e.collision.target.tag == "下のバー"){ head.y = 2; } });
bode1~5についても同様にします。
⑸問題発生(全部が移動しおわらないうちにタップすると、bodyの動きがおかしくなる)
しかし、これだけでは、例えば頭が端にきて頭、body1、body2と順番に移動をはじめますが、全部移動し終わらないうちにタップをすると、タップをしたときの命令が効いて、まだ移動していないbodyがワープして飛んできてしまいます。
そこで、barに当たってから移動し終わるまで、タッチしても無効になるように、カウンターを使ってタッチのイベントを無効化しました。headがbarに当たるとカウンターが1になるようにし、移動し終わるのが40カウント後なので、そうしたらカウンターを0に戻し、タッチイベントはカウンターが0の時に動くように設定しました。以下青字を挟んで解決しました。
head.addEventListener(Event.COLLISION, function(e) { counter = 1; scene.tl.delay(40).then(function(){ counter = 0; }); (以下略)
scene.addEventListener(Event.TOUCH_START, function(e){ if(counter == 0){ // 壁にぶつかったあと全部移動するまでタッチ無効にする為 (以下略)
⑹通った後に七色の玉が置かれていく
本当は、二匹のへびが対決するものにしたかったのですが、そこまでは無理だったので、カラフルな玉が出るようにしてみました。
body5の位置に、色を順番に変えながら出現するようにしました。
七色あるので、frame数を変数にした関数を使いました。
// へびの玉を作る関数 function makeMozi(f) { var mozi = new ExSprite(9, 9); mozi.image = core.assets["images/cf307/hebitama.png"]; mozi.x = body5.x + 8 - 4.5; mozi.y = body5.y + 8 - 4.5; moziGroup.addChild(mozi); mozi.frame = f; } snakeGroup.tl.delay(8).then(function(){ makeMozi(0); }); snakeGroup.tl.delay(8).then(function(){ makeMozi(1); }); snakeGroup.tl.delay(8).then(function(){ makeMozi(2); }); snakeGroup.tl.delay(8).then(function(){ makeMozi(3); }); snakeGroup.tl.delay(8).then(function(){ makeMozi(4); }); snakeGroup.tl.delay(8).then(function(){ makeMozi(5); }); snakeGroup.tl.delay(8).then(function(){ makeMozi(6); }); snakeGroup.tl.loop();
⑺ゲーム画面
ⅰ.壁の配置
画面は、32×10、32×15ピクセルに分けることができるので、32×32を一マスとして、壁の配置を考えました。そして、マスの位置で、配置と長さを指定していくことで、簡単に壁を配置することができました。
kabeは、幅をw,高さをh、位置のx座標をx、y座標をyとします。
var kabes = new Array(); function makeKabe(w, h, x, y) { var kabe = new ExSprite(w, h); kabe.backgroundColor = "red"; kabe.x = x; kabe.y = y; scene.addChild(kabe); kabes.push(kabe); }
縦の壁はw=1,横の壁はh=1とし、長さと位置をそれぞれ方眼用紙のマスを見ながら埋めていきます。
makeKabe(1, 480 - 64, 320 - 32, 64); makeKabe(320 - 32 * 2, 1, 32, 64); makeKabe(1, 480 - 32 * 3, 32, 64); makeKabe(320 - 32 * 3, 1, 32, 480 - 32); makeKabe(1, 32 * 5, 320 - 32 * 2, 32 * 9); // 5 ・・・(以下略)
ⅱ.壁の衝突判定
壁は配列になっているので、for文の中に衝突判定を入れます。
// 頭や体がかべに当たったらゲームオーバー for(var i=0; i<kabes.length; i++){ head.addCollision(kabes[i]); kabes[i].addCollision(moziGroup); kabes[i].addEventListener(Event.COLLISION, function(e){ //[i]があるからこれもforの中に入れる // 操作不能にするためのカウンター(そのままではゴールできてしまう) counter = 1; (以下略)
⑻お手本の画面
オートマチックに動かすために、最初に作った、タップしたら左右いずれかに動くという動きを、関数にして簡潔にしました。
動きとしては、4×2の8通りあるので、それぞれ
// 上向きから左に曲がる動き:function upLeft(){});
// 左向きから下に曲がる動き:function leftDown(){ });
// 左向きから下に曲がる動き:function leftDown(){ });
・・・
のように作っていきました。
そして、曲がらせたい箇所にきたら、いずれかを指定して、曲がるようにします。これも、先に挙げた方眼紙の絵を見ながら指定していくと簡単です。
scene.addEventListener(Event.ENTER_FRAME, function() { if(head.y == 32 + 8 && head.x == 320 - 16 - 8){ upLeft(); } if(head.y == 32 + 8 && head.x == 8){ leftDown(); } if(head.y == 32 * 14 + 8 && head.x == 8){ downRight(); } if(head.y == 32 * 14 + 8 && head.x == 32 * 8 + 8){ rightUp(); } if(head.y <= 32 * 8 + 8 && head.y > 32 * 8 && head.x == 32 * 8 + 8){ upLeft(); } ・・・(以下略)
⑼その他の問題
その他、以下の問題も解決して、完成しました。(メモ書きから)
barにも衝突判定があるのでゲームクリアになってしまうので修正が必要→tagをつけて解決
ゲームオーバーになっても強行突破できてしまう→これもバーと同じカウンターを使って解決
5.完成したソースコード
なぜかここにうまく貼り付けができないので、PDFをリンクします。