2D WebGL renderer Pixi.js v4【連載第六回】

前回の話

WebGLでいろんな図形と文字の描き方を一通り復習しました。
これから本番に近づけたらいいなと。

衝突探知

f:id:manmanrai:20170426114646p:plain

理想はこんな感じです。
左のねこはキーボードで操作できる要素、右のブロックは動かぬもの、そして、上のメッセージは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が減ります。減りすぎると死んちゃいます。

f:id:manmanrai:20170426141939p:plain

function 設計

// pixi.jsの初期設定、画像の読み込みなど

function setup() {
  // 初期化。そしてゲームの状態をplayに切り替える。
  // gameLoop();
}

function gameLoop() {
  // 描く。スプライトをrenderに渡す。
}

function play() {
  // ゲームの全てのロジックをここに格納する。
}

function end() {
  // ゲーム終了後。
}

// ほかに必要なhelper functions:
// `keyboard`, `hitTestRectangle`, `contain` and `randomInt`

まず、ゲーム中のシーンと終わったシーンを分けます。(違うContainerで)

f:id:manmanrai:20170426144757p:plain

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

manmanrai.hatenablog.com

参考サイト

pixi.jsの公式サイト(英語)

www.pixijs.com

参考しているチュートリアル(英語)

github.com