2D WebGL renderer Pixi.js v4【連載第六回】
前回の話
WebGLでいろんな図形と文字の描き方を一通り復習しました。
これから本番に近づけたらいいなと。
衝突探知
理想はこんな感じです。
左のねこはキーボードで操作できる要素、右のブロックは動かぬもの、そして、上のメッセージはcanvasで書いた状態を示す文字です。
連載第三回の時キーボードコントロールという所に書いたように、
2D WebGL renderer Pixi.js v4【連載第三回】 - manmanrai’s diary
特に追加した、変わった部分の構造はこうなります。
var ねこ, ブロック, メッセージ, 状態; function setup(){ ねこ入れる; ブロック作る; メッセージの初期値書く; キーボード事件の移動数値設定する; 状態をplay設定する; gameLoop(); } function play(){ キーボードの移動数値受け入れる; if(衝突){ ブロックの色とメッセージを変える; }else{ ブロックの色とメッセージを戻す; } } function 衝突(r1, r2){ 個別の中心点を探す; 個別の半分の高さと半分の横幅を計算する; r1の半分の高さとr2の半分の高さ足す; r1の半分の横幅とr2の半分の横幅足す; if(r1,r2の中心点の距離 < r1,r2 足した距離){ 衝突; } }
コードに戻すと、こうなります。
var cat, box, message, state; function setup() { box = new PIXI.Graphics(); box.beginFill(0xCCFF99); box.drawRect(0, 0, 64, 64); box.endFill(); box.x = 120; box.y = 96; stage.addChild(box); cat = new Sprite(resources["images/cat.png"].texture); cat.x = 16; cat.y = 96; cat.vx = 0; cat.vy = 0; stage.addChild(cat); var left = keyboard(37), up = keyboard(38), right = keyboard(39), down = keyboard(40); left.press = function() { cat.vx = -5; cat.vy = 0; }; left.release = function() { if (!right.isDown && cat.vy === 0) { cat.vx = 0; } }; up.press = function() { cat.vy = -5; cat.vx = 0; }; up.release = function() { if (!down.isDown && cat.vx === 0) { cat.vy = 0; } }; right.press = function() { cat.vx = 5; cat.vy = 0; }; right.release = function() { if (!left.isDown && cat.vy === 0) { cat.vx = 0; } }; down.press = function() { cat.vy = 5; cat.vx = 0; }; down.release = function() { if (!up.isDown && cat.vx === 0) { cat.vy = 0; } }; message = new PIXI.Text( "No collision...", {font: "18px sans-serif", fill: "white"} ); message.position.set(8, 8); stage.addChild(message); state = play; gameLoop(); } function play() { cat.x += cat.vx; cat.y += cat.vy; if (hitTestRectangle(cat, box)) { message.text = "hit!"; box.tint = 0xff3300; } else { message.text = "No collision..."; box.tint = 0xccff99; } } function hitTestRectangle(r1, r2) { var hit, combinedHalfWidths, combinedHalfHeights, vx, vy; hit = false; r1.centerX = r1.x + r1.width / 2; r1.centerY = r1.y + r1.height / 2; r2.centerX = r2.x + r2.width / 2; r2.centerY = r2.y + r2.height / 2; r1.halfWidth = r1.width / 2; r1.halfHeight = r1.height / 2; r2.halfWidth = r2.width / 2; r2.halfHeight = r2.height / 2; vx = r1.centerX - r2.centerX; vy = r1.centerY - r2.centerY; combinedHalfWidths = r1.halfWidth + r2.halfWidth; combinedHalfHeights = r1.halfHeight + r2.halfHeight; if (Math.abs(vx) < combinedHalfWidths) { if (Math.abs(vy) < combinedHalfHeights) { hit = true; } else { hit = false; } } else { hit = false; } return hit; };
分析してみたら、結構シンプルに見えてきました。
これ大丈夫だったら本番いけましょう。
宝探しゲーム
ゲームの原型はこんな感じ:モンスターから避けて、宝ゲットして、ドアから出られたら成功です。モンスターとぶつかったらHPが減ります。減りすぎると死んちゃいます。
function 設計
// pixi.jsの初期設定、画像の読み込みなど function setup() { // 初期化。そしてゲームの状態をplayに切り替える。 // gameLoop(); } function gameLoop() { // 描く。スプライトをrenderに渡す。 } function play() { // ゲームの全てのロジックをここに格納する。 } function end() { // ゲーム終了後。 } // ほかに必要なhelper functions: // `keyboard`, `hitTestRectangle`, `contain` and `randomInt`
まず、ゲーム中のシーンと終わったシーンを分けます。(違うContainerで)
gameScene = new Container(); stage.addChild(gameScene); gameOverScene = new Container(); stage.addChild(gameOverScene);
そして、一旦gameOverSceneを隠します。
gameOverScene.visible = false;
ゲームシーン
まず、簡単な洞窟の背景、ドア、勇者、宝箱を作ります。
id = resources["images/treasureHunter.json"].textures; //洞窟 dungeon = new Sprite(id["dungeon.png"]); gameScene.addChild(dungeon); //ドア door = new Sprite(id["door.png"]); door.position.set(32, 0); gameScene.addChild(door); //勇者 explorer = new Sprite(id["explorer.png"]); explorer.x = 68; explorer.y = gameScene.height / 2 - explorer.height / 2; explorer.vx = 0; explorer.vy = 0; gameScene.addChild(explorer); //宝箱 treasure = new Sprite(id["treasure.png"]); treasure.x = gameScene.width - treasure.width - 48; treasure.y = gameScene.height / 2 - treasure.height / 2; gameScene.addChild(treasure);
次はランダム動くモンスターを作ります。
// モンスターの数を設定 var numberOfBlobs = 6, // モンスターとモンスターの間の距離 spacing = 48, // 左から最初にあけたい距離 xOffset = 150, // スピード speed = 2, // 方向 direction = 1; // モンスターを格納する配列作る blobs = []; // 設定通りの数のモンスター作る for (var i = 0; i < numberOfBlobs; i++) { // モンスター産む(?) var blob = new Sprite(id["blob.png"]); // x軸の一定の距離をあく var x = spacing * i + xOffset; // y座標(ランダム) var y = randomInt(0, stage.height - blob.height); // 座標を与える blob.x = x; blob.y = y; // 方向(direction)が1だったら下向き、-1だったら上向き // 方向 かける スピード blob.vy = speed * direction; // 次のモンスターは逆方向にする direction *= -1; // 配列内に格納する blobs.push(blob); gameScene.addChild(blob); }
そして次がHPバーです。
healthBar = new PIXI.DisplayObjectContainer(); healthBar.position.set(stage.width - 170, 6) gameScene.addChild(healthBar); // HPバーのベース var innerBar = new PIXI.Graphics(); innerBar.beginFill(0x000000); innerBar.drawRect(0, 0, 128, 8); innerBar.endFill(); healthBar.addChild(innerBar); // HPバーのHP値 var outerBar = new PIXI.Graphics(); outerBar.beginFill(0xFF3300); outerBar.drawRect(0, 0, 128, 8); outerBar.endFill(); healthBar.addChild(outerBar); healthBar.outer = outerBar;
ここに出たDisplayObjectContainerとContainerとParticleContainerの違いははっきりしたいですね。
あと、outerの意味もイマイチよく分かりません。わかったらまた補足します。
ゲームオーバーシーン
ゲーム終わったら勝利か失敗かを表示するメッセージを準備します。
// 先ほどgameOverSceneを非表示するコードの後に書く message = new Text( "The End!", {font: "64px Futura", fill: "white"} ); message.x = 120; message.y = stage.height / 2 - 32; gameOverScene.addChild(message);
これで事前準備終わりました。たいぶ長くなりました。play functionの中身を次回に。
次回は最終回です!
シリーズ:2D WebGL renderer Pixi.js v4
参考サイト
pixi.jsの公式サイト(英語)
参考しているチュートリアル(英語)