【phina.js】マリオのような敵を踏み潰すエフェクト

4 min read読了の目安(約3800字

鋭意執筆中

https://zenn.dev/alkn203/books/phina-tips-rewrite

はじめに

マリオシリーズが代表するように、敵を上から踏みつけた時に敵が潰れるエフェクトはアクションゲームではもはや定番になっています。
今回は、敵が潰れるエフェクトをphina.jsで表現してみます。

オブジェクトのoriginを理解する

  • 今回の目的を実現するためには、オブジェクトのoriginの変更を行う必要があります。
  • phina.jsのオブジェクトにはoriginというプロパティがあり、位置指定、回転、拡大縮小の時の基準となっています。
  • Vector2クラス形式となっており、デフォルトは(0.5, 0.5)でオブジェクトの中心になっています。

敵が潰れるエフェクト

  • 敵が潰れるアニメーションは、tweener を用います。
  • ScaleYの値を変化させて縦に縮小させます。
// 縦方向に縮小
bugbow.tweener.clear().to({scaleY: 0.1}, 200);
  • 期待する結果としては下方向に潰れて欲しいところですが、今のままだと中心に向かって縮小され、思い通りの結果になりません。
  • これはoriginがオブジェクトの中心になっているのが原因です。

originの変更と位置の調整

  • 下に縮小するようにするためには、origin を(0.5, 1.0)に変更します。
  • 変更したoriginがオブジェクトの位置の基準となるため、変更したoriginの分だけ上にずらして位置調整します。
// origin変更
bugbow.setOrigin(0.5, 1.0);
// 位置調整
bugbow.y += bugbow.height / 2;
// 縦方向に縮小
bugbow.tweener.clear().to({scaleY: 0.1}, 200);

おわりに

実際には、敵が潰れたコマ画像を用意してフレームを切り替えた方が効率的かもしれません。今回の内容は、あくまでも1つのアプローチと考えてもらえればと思います。

サンプルコード

コードを見る
phina.globalize();
// アセット
var ASSETS = {
  // 画像
  image: {
    'tiles': 'https://cdn.jsdelivr.net/gh/alkn203/assets_etc/tiles.png',
    'bugbow': 'https://cdn.jsdelivr.net/gh/alkn203/assets_etc/bugbow.png',
  },
};
// 定数
var GRID_SIZE = 64; // グリッドのサイズ
// ステージデータ
var STAGE = ['4444444444',
             '4000000004',
             '4000000004',
             '4000000004',
             '4000000004',
             '4000000004',
             '4000000004',
             '4000000004',
             '4000000004',
             '4333333334',
             '4000000004',
             '4000000004',
             '4000000004',
             '4000000004',
             '4000000004'];
             
// メインシーン
phina.define('MainScene', {
  superClass: 'DisplayScene',
  // コンストラクタ
  init: function() {
    // 親クラス初期化
    this.superInit();
    // 背景色
    this.backgroundColor = 'skyblue';
    // グリッド
    this.gx = Grid(640, 10);
    this.gy = Grid(960, 15);
    // グループ
    this.objectGroup = DisplayElement().addChildTo(this);
    this.setStage(STAGE);

    var stone = RectangleShape({
      fill: 'gray',
      cornerRadius: 10,
    }).addChildTo(this);
    // 落下
    stone.setPosition(this.gx.center(), this.gy.center(-4)).physical.gravity.y = 0.5;
    
    var bugbow = Sprite('bugbow', GRID_SIZE, GRID_SIZE).addChildTo(this);
    bugbow.setPosition(this.gx.center(), this.gy.center(1));
    // 潰れイベント(1回限り)
    bugbow.one('stump', function() {
      // origin変更
      bugbow.setOrigin(0.5, 1.0);
      // 位置調整
      bugbow.y += bugbow.height / 2;
      // 縦方向に縮小
      bugbow.tweener.clear().to({scaleY: 0.1}, 200);
    });
    
    this.update = function() {
      // 当たり判定
      if (stone.hitTestElement(bugbow)) {
        stone.remove();
        bugbow.flare('stump');
      }
    };
  },
  // マップ作成
  setStage: function(stage) {
    var half = GRID_SIZE / 2;
    var self = this;
    // マップデータをループ
    stage.each(function(arr, j) {
      // 文字列を配列に変換
      arr.toArray().each(function(id, i) {
        var x = self.gx.span(i) + half;
        var y = self.gx.span(j) + half;
        // 空白以外の場合
        if (id > 0) {
          // タイルセットからスプライト作成
          var elem = Sprite('tiles', GRID_SIZE, GRID_SIZE).addChildTo(self.objectGroup);
          elem.setPosition(x, y);
          // フレームインデックス指定
          elem.frameIndex = id - 1;
          return;
        }
      });
    });
  },
});
// メイン
phina.main(function() {
  var app = GameApp({
    startLabel: 'main',
    // アセット読み込み
    assets: ASSETS,
  });
  
  app.run();
});

runstantプロジェクト

https://runstant.com/alkn203/projects/b748b651