tmlib.jsでシューティング風なクソゲーを作る03 - 当たり判定を付ける
今回やったこと
前回の続きです。今回は当たり判定を実装します。
今回の仕上がり
以下の3つの判定を実装しました。
- 敵と機雷
- 爆風と機雷
- 爆風と敵
機雷は敵と着弾すると爆発します。爆発には他の機雷を誘爆する効果があります。敵は機雷が着弾するか爆発に巻き込まれると消滅します。これで誘爆による連鎖のような爽快感が出る…かな。
当たり判定の実装
基本的に毎フレーム当たり判定をチェックするだけです。tmlib.jsのtm.app.Elementを継承するクラスにはcollisionというメソッドがあるのですが…使い方がよくわからなかったので、古典的な方法で当たり判定を取りました。例えば機雷と敵との当たり判定はこんな感じです。
checkBulletAndEnemyHit: function () { var self = this; var i; var deadBullet = []; // 削除対象の機雷リスト var deadEnemy = []; // 削除対象の敵リスト // 機雷と敵の当たり判定をチェックしてヒットしている場合は削除リストにオブジェクトを登録する for (i= 0; i<self.bulletList.length; i++) { var bullet = self.bulletList[i]; for (var j=0; j<self.enemyList.length; j++) { var enemy = self.enemyList[j]; if(bullet.isHitElement(enemy)) { console.log("hit!! :%d,%d", bullet.x, bullet.y ); // すでにヒット処理済みの場合は爆風エフェクト発生しないし、削除対象リストに登録しない if (bullet.isDead === false) { bullet.isDead = true; // 爆風エフェクト開始 var blast = Blast(bullet.x, bullet.y).addChildTo(self.blastPool) .startBlast() .on("endBlast", function (e) { console.log("endBlast"); self.blastList.erase(e.target); }); self.blastList.push(blast); deadBullet.push(bullet); } // すでにヒット処理済みの場合は削除対象リストに登録しない if (enemy.isDead === false) { enemy.isDead = true; deadEnemy.push(enemy); } } } } // 削除対象の機雷と敵を削除 for (i=0; i<deadBullet.length; i++) { deadBullet[i].remove(); self.bulletList.erase(deadBullet[i]); } for (i=0; i<deadEnemy.length; i++) { deadEnemy[i].remove(); self.enemyList.erase(deadEnemy[i]); } self.renewEnemy(); }
単純にループで走査して総当りでチェックです。コードの全体はjsdo.itで確認してください。これでようやくゲームの遊び部分の基礎工事ができたかなと。スプライトをほぼ使っていないので見た目がヒドイですが…。
次回やること
敵の出現パターンとかは後々考えるとして次はゲームの終了条件を設定したいと思います。冗長なコードの修正は最後にやろうと思います。
tmlib.jsでシューティング風なクソゲーを作る02 - 敵を実装
今回やったこと
前回の続きです。今回は的(敵)を出現させる部分を作りたいと思います。
まずは動くものを
敵がひたすら湧いてくるだけ。当たり判定はまだ付けていません。
追加したEnemyオブジェクト
全体は以下のとおり。
var ENEMY_DELAY_GROUP = [0, 30, 15, 8, 45]; var GO_LEFT = -1; var GO_RIGHT = 1; var OFFS_Y = ENEMY_HEIGHT + 4;// マージンを持たせる tm.define("Enemy", { superClass: "tm.display.AnimationSprite", init: function () { var enemySS = tm.asset.SpriteSheet({ image: "enemy", frame: { width: 32, height: 32, count: 18 }, animations: { "walkLeft": [13, 15, "walkLeft", 8], "walkRight": [16, 18, "walkRight", 8] } }); this.superInit(enemySS, ENEMY_WIDTH, ENEMY_HEIGHT); //this.blendMode = 'lighter'; this.cnt_delay = 0; // 移動方向を決定 var dir = tm.util.Random.randint(0, 65535); // とりあえず単純に50%で var go_dir = GO_RIGHT; if (dir <= 32767) { go_dir = GO_LEFT; } var speed = tm.util.Random.randint(1, 3) * 2.6 + 4; this.v = tm.geom.Vector2(speed * go_dir, 0); // オフセットを決めて的が重ならないようにする this.position.y = tm.util.Random.randint(0, 8) * OFFS_Y + ENEMY_TOP_LIMIT; if (GO_LEFT == go_dir) { this.gotoAndPlay("walkLeft"); this.position.x = SCREEN_WIDTH + ENEMY_WIDTH; } else { this.gotoAndPlay("walkRight"); this.position.x = -ENEMY_WIDTH; } // 出現のディレイを決定(めんどいのでまんま乱数を使う。テーブルを使うかは後で検討) var idx = tm.util.Random.randint(0, 4); this.cnt_delay = ENEMY_DELAY_GROUP[idx]; }, update: function () { // 出現時間まで待機 if (this.cnt_delay <= 0) { if (0 == this.checkMoveArea()) { this.position.add(this.v); }else { this.cnt_delay = 200000; this.remove(); this.flare("onRemove"); } } else { this.cnt_delay--; } }, checkMoveArea: function () { var is_kill = 0; if (this.v.x > 0) { if (this.position.x > SCREEN_WIDTH + ENEMY_WIDTH) { is_kill = 1; } } else { if (this.position.x < -ENEMY_WIDTH) { is_kill = 1; } } return is_kill; } });
スプライトシートを使って表示させています。 挙動としては左方向、右方向に直進し続けるだけのものです。色々と手抜きしているので全体が完成したら動きの部分は検討する余地がありますね。
これといって複雑なことはしていません。画面外に出たら"onRemove"というイベントを発火させています。
追加したコードの概要
コードで言うと、この箇所です。
this.enemyPool = new tm.display.CanvasElement().addChildTo(this); for(var i=0; i<NUMBER_OF_ENEMY; i++) { this.createEnemy(); }
出現させる敵の数だけループを回してEnemyオブジェクトを生成します。createEnemyの実装は以下のとおりです。
createEnemy: function() { var self = this; var enemy = new Enemy().addChildTo(self.enemyPool) .on("onRemove", function(e){ self.renewEnemy(); }); this.cntEnemy++; },
Enemyオブジェクトを作ってenemyPoolというCanvasEelementに追加します。そのままメソッドチェーンで"onRemove"イベントのハンドラを実装しています。その後は敵カウンタをインクリメントしているだけです。renewEnemyはこんな感じに実装しています。
renewEnemy: function () { var self = this; this.cntEnemy--; if (this.cntEnemy === 0) { for(var i=0; i<NUMBER_OF_ENEMY; i++) { this.createEnemy(); } } },
呼び出されると敵カウンタをデクリメントし、1体も敵がいなくなったら、また敵を一括で生成します。
次回
当たり判定を付けて行きたいと思います。まずは敵と機雷あたりから。
tmlib.jsでシューティング風なクソゲーを作る01 - 自機の実装
概要
ゲームの基本処理が詰まったジャンルといえばシューティング。ゲーム制作のリハビリには最適だろうと思いましたので、やっていきます。絵は書けないので図形を駆使して仕組みをとりあえず実装していきます。とりあえずタイトル画面とかは後回しにして、ゲーム画面から着手します。
今回やったこと
まずは、公式のチュートリアルやtmlib.js作者さん自身のチュートリアル記事を読む&写経。初めて真面目にプロトタイプベースの言語に触れ、混乱しながらも、自機の操作、機雷の発射部分を実装。まだゲームにはなっておりません。
とりあえず諸々のトリガー処理をイベント駆動で組んでみたものの、設計としてイケてるのかまだ実感がありません…。
ぼんやりとした仕様
大変ざっくりとした仕様をまとめます。我ながら思いつき工事です。こんな仕様書を仕事でもらったら殺意が芽生えることでしょう。
システム
プレイヤーを操作し、機雷を的に当てる。機雷は誘爆させることができ、誘爆した回数に応じてスコアにボーナスが加算される。誘爆は的を破壊したときの爆発、機雷自身の爆発で発生する。
画面遷移
タイトル、ゲームシーン、結果表示くらいは実装する。
プレイヤー
画面上部で左右方向にのみ移動可能。攻撃はプレイヤー下方にのみ行える。攻撃方法は移動速度の変化する機雷を投下するイメージ。
機雷
画面下方に向かって移動する。徐々に減速するが、一定時間すると以後等速となる。的に着弾するか、しばらくすると爆発する。
的(エネミー)
とりあえず画面左右から入り乱れて出現する。それ以外はぼちぼち考える。
次回予告
的が出てくる部分を作っていこうと思います。
思い立ったが吉日
何を思ったか
仕事の谷間が続いているので何か作りたくなりました。 やっぱり何か作っていたい。
何を作る?
業務に全く別の何かを作りたい。 仕事で扱う言語はCが99%を占める。 それじゃあ今、隆盛(混沌)を極めるJavaScriptをいじろう。 簡単なミニゲームでも作ってリハビリしよう。
という流れで、安直に決めました。 JavaScriptでミニゲーム作ってみます。
何を使う?
ゲームエンジンを作りたいわけではないので、どこぞのフレームワークに乗っかろうと思い調査しました。なんとなく主観で良さそうだったのは以下の2つ。
Phaser.js
有名ドコロらしい。でも、今回考えている規模には少々リッチすぎる。とぅーまっちってやつです。
tmlib.js
いいサイズ感。そして国産。今回はこちらを使ってみます。
ひとまずまとめ
今回は道具を揃えるところまで。作る内容等は次からまとめていこうと思います。 ゆっくりやっていきます。