2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

phina.jsでモンストっぽいゲームを作る【4】ー敵の配置と当たり判定ー

Posted at

はじめに

モンストもどきを作るという試みの第4回となる今回は、敵の追加と当たり判定を作っていきます。

アセット定義追加

まずは、敵のアセット情報を追加します。

    'dragon': 'https://rawgit.com/alkn203/piko_strike/master/assets/enemy/pipo-enemy021.png',

敵を配置する

Enemyクラス

敵を配置する前に、後に敵を使いまわし出来るようにEnemyクラスを定義します。Spriteクラスを継承したクラスです。

/*
 * 敵クラス
 */
phina.define("Enemy", {
  // 継承
  superClass: 'Sprite',
  // コンストラクタ
  init: function(name) {
    // 親クラス初期化
    this.superInit(name);
  },
});

敵を追加する

プレイヤーを追加する時と基本的には同じで、シーンに追加します。

    // 敵キャラ
    var dragon = Enemy('dragon').addChildTo(this);
    dragon.setPosition(this.gridX.center(), this.gridY.center(-4));

プレイヤーと敵の当たり判定

Colliderアクセサリを使った当たり判定

当たり判定は、phina.jsに元々用意されているhitTestElementで判定をしても良いのですが、今回は自作したColliderアクセサリを使ってみます。Colliderアクセサリについては、[phina.js]Colliderアクセサリを作ってみた話に書いていますので、参考にして下さい。

プレイヤーのCollider

プレイヤーのColliderは単純な矩形にするので、以下のコードをPlayerクラスのコンストラクタに追加します。

  // コライダー
  this.collider.show();

showメソッドを呼び出しておくと、以下のようにプレイヤーの周りにColliderが表示されます。

monst-tut-05-1.gif

敵のCollider

プレイヤーと同じく矩形にしても良いのですが、上下左右の当たり判定を明確に区別するために、以下のように4方向のColliderをそれぞれ設定してみました。

monst-tut-05-2.gif

敵クラスのコンストラクタに以下のコードを追加します。

var s1 = this.width * 0.8;
var s2 = this.width / 10;
var half = this.width / 2;
// コライダー
var top = Collider().attachTo(this).setSize(s1, s2).offset(0, -half).show();
top.id = 'top';
var bottom = Collider().attachTo(this).setSize(s1, s2).offset(0, half).show();
bottom.id = 'bottom';
var left = Collider().attachTo(this).setSize(s2, s1).offset(-half, 0).show();
left.id = 'left';
var right = Collider().attachTo(this).setSize(s2, s1).offset(half, 0).show();
right.id = 'right';
  • Colliderの大きさは、オブジェクトのサイズ変更に対応できるように計算で求めています。
  • Colliderの位置指定は相対座標で行います。
  • 当たり判定でどの位置のColliderに当たったかを知るために、idというプロパティを作っています。

プレイヤーと敵の当たり判定

プレイヤーと敵の当たり判定は、reflectEnemyという処理専用の関数を定義してその中で行います。
reflectEnemyは、MainSceneupdate関数内から呼び出します。

// 敵との反射処理
reflectEnemy: function() {
  var velocity = this.player.physical.velocity;
  var player = this.player;
  var enemy = this.dragon;
  // 敵との当たり判定
  enemy.accessories.each(function(accessory) {
    if (accessory.hitTest(player.collider)) {
      if (velocity.x > 0 && accessory.id === 'left') velocity.x *= -1;
      if (velocity.x < 0 && accessory.id === 'right') velocity.x *= -1;
      if (velocity.y > 0 && accessory.id === 'top') velocity.y *= -1;
      if (velocity.y < 0 && accessory.id === 'bottom') velocity.y *= -1;
    }  
  });
},
  • オブジェクトにアタッチしたアクセサリは、そのオブジェクトのaccessoriesという配列に追加されていますので、その配列をループして判定を行っています。
  • 同時にvelocityの符号をチェックすることで、予期しない反射の動きを防ぐことができます。

動作確認

[runstantプロジェクトへのリンク]

全体コード

// グローバルに展開
phina.globalize();
// アセット
var ASSETS = {
  // 画像
  image: {
    'bg': 'https://rawgit.com/alkn203/piko_strike/master/assets/bg.png',
    'tomapiko': 'https://rawgit.com/phinajs/phina.js/develop/assets/images/tomapiko.png',
    'arrow': 'https://rawgit.com/alkn203/piko_strike/master/assets/arrow.png',
    'dragon': 'https://rawgit.com/alkn203/piko_strike/master/assets/enemy/pipo-enemy021.png',
  },
};
// 定数
var SCREEN_WIDTH = 640;
var SCREEN_HEIGHT = 960;
var PLAYER_SPEED = 50;
var FRICTION = 0.98;
/*
 * メインシーン
 */
phina.define("MainScene", {
  // 継承
  superClass: 'DisplayScene',
  // コンストラクタ
  init: function() {
    // 親クラス初期化
    this.superInit();
    // 背景
    Sprite('bg').addChildTo(this)
                .setPosition(this.gridX.center(), this.gridY.center());
    // 矢印
    var arrow = Sprite('arrow').addChildTo(this).hide();
    arrow.alpha = 0.75;
    // プレイヤー
    this.player = Player().addChildTo(this);
    this.player.setPosition(this.gridX.center(), this.gridY.span(13));
    // 敵キャラ
    var dragon = Enemy('dragon').addChildTo(this);
    dragon.setPosition(this.gridX.center(), this.gridY.center(-4));
    // 矢印
    arrow.setPosition(this.player.x, this.player.y);
    // シーン全体から参照できるようにする
    this.dragon = dragon;
    this.arrow = arrow;
  },
  // 毎フレーム更新処理
  update: function() {
    var velocity = this.player.physical.velocity;
    // 画面端反射処理
    this.reflectScreen();
    // 敵との反射処理
    this.reflectEnemy();
    // 一定速度を下回ったら強引に停止させる
    if (velocity.length() > 0 && velocity.length() < 2) {
      velocity.set(0, 0);
      // 画面タッチを有効にする
      this.setInteractive(true);
    }    
  },
  // 画面端との反射処理
  reflectScreen: function() {
    var velocity = this.player.physical.velocity;
    var player = this.player;
    // 画面端反射処理
    if (velocity.x < 0 && player.left < 0) velocity.x *= -1;
    if (velocity.x > 0 && player.right > SCREEN_WIDTH) velocity.x *= -1;
    if (velocity.y < 0 && player.top < 0) velocity.y *= -1;
    if (velocity.y > 0 && player.bottom > SCREEN_HEIGHT) velocity.y *= -1;
  },
  // 敵との反射処理
  reflectEnemy: function() {
    var velocity = this.player.physical.velocity;
    var player = this.player;
    var enemy = this.dragon;
    // 敵との当たり判定
    enemy.accessories.each(function(accessory) {
      if (accessory.hitTest(player.collider)) {
        if (velocity.x > 0 && accessory.id === 'left') velocity.x *= -1;
        if (velocity.x < 0 && accessory.id === 'right') velocity.x *= -1;
        if (velocity.y > 0 && accessory.id === 'top') velocity.y *= -1;
        if (velocity.y < 0 && accessory.id === 'bottom') velocity.y *= -1;
      }  
    });
  },
  // タッチ開始時処理
  onpointstart: function(e) {
    // タッチ位置を記録
    this.startPos = Vector2(e.pointer.x, e.pointer.y);
    // 矢印表示
    this.arrow.setPosition(this.player.x, this.player.y).show();
    // 縦を0に縮小
    this.arrow.scaleY = 0;
  },
  // タッチ移動時処理
  onpointmove: function(e) {
    // 矢印の方向を求める
    var pos = Vector2(e.pointer.x, e.pointer.y);
    this.arrow.rotation = this.getDegree(this.startPos, pos) + 90;
    // 距離に応じて矢印を拡大縮小
    var distance2 = Vector2.distanceSquared(this.startPos, pos);
    this.arrow.scaleY = distance2 / 10000;
  },
  // タッチ終了時処理
  onpointend: function(e) {
    // 画面タッチを無効にする
    this.setInteractive(false);
    // 矢印非表示
    this.arrow.hide();
    // 矢印の角度から方向を決める
    var deg = this.arrow.rotation - 90;
    // 角度からベクトルを求めてプレイヤーに設定
    this.player.physical.velocity = Vector2().fromDegree(deg, PLAYER_SPEED);
    // 摩擦をかけて徐々に減速させる
    this.player.physical.friction = FRICTION;
  },
  // 2点間の角度を求める
  getDegree: function(from, to) {
    return Math.radToDeg(Math.atan2(from.y - to.y, from.x - to.x));
  },
});
/*
 * プレイヤークラス
 */
phina.define("Player", {
  // 継承
  superClass: 'Sprite',
  // コンストラクタ
  init: function() {
    // 親クラス初期化
    this.superInit('tomapiko');
    // コライダー
    this.collider.show();
  },
});
/*
 * 敵クラス
 */
phina.define("Enemy", {
  // 継承
  superClass: 'Sprite',
  // コンストラクタ
  init: function(name) {
    // 親クラス初期化
    this.superInit(name);

    var s1 = this.width * 0.8;
    var s2 = this.width / 10;
    var half = this.width / 2;
    // コライダー
    var top = Collider().attachTo(this).setSize(s1, s2).offset(0, -half).show();
    top.id = 'top';
    var bottom = Collider().attachTo(this).setSize(s1, s2).offset(0, half).show();
    bottom.id = 'bottom';
    var left = Collider().attachTo(this).setSize(s2, s1).offset(-half, 0).show();
    left.id = 'left';
    var right = Collider().attachTo(this).setSize(s2, s1).offset(half, 0).show();
    right.id = 'right';
  },
});
/*
 * メイン処理
 */
phina.main(function() {
  // アプリケーションを生成
  var app = GameApp({
    title: 'Piko Strike',
    // メインシーンから開始
    startLabel: 'main',
    // アセット読み込み
    assets: ASSETS,
  });
  // 実行
  app.run();
});

次回予定

次回は、敵のライフゲージを表示とダメージ処理を実装したいと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?