LoginSignup
2
3

More than 3 years have passed since last update.

phina.js が面白そうなのでお勉強中 その7

Last updated at Posted at 2020-02-24

phina.js が面白そうなのでお勉強中 その6

敵の弾を飛ばしてみる

シューティングゲームでは、敵が弾を撃ってきます。
しかも、自機の方向に斜めに飛んできます。
これを作ってみたいです。

敵の弾が自分の方向に飛んでくるってのはどういうこと?

ってのを考えると、まず物が動くのだから、1フレームごとのX座標とY座標の移動量が解ればいいってことですな。
1フレームごとのX座標とY座標の移動量ってのは、
 X座標の移動量 = 1あたりのX座標の移動量 × 速さ
 Y座標の移動量 = 1あたりのY座標の移動量 × 速さ
ってことだから、
まずは 1あたりのX座標、Y座標の位置が出せれば良いことになります。

「1あたりの」とは何ぞや?

何に対する1あたりなのかがさっぱり謎なんだけど、とりあえず上の計算式から判ることは、速さが1の時の移動量ってことですよね。
速さが2だったら2倍になって、速さが0.5だったら半分になる、ぐらいの感じでいいと思う。

1あたりの出し方(簡単編)

「1あたりの」っていうのを、X座標、またはY座標の長さの大きい方が長さ1の時のY座標、またはX座標の位置って考えると、むっちゃ簡単です。
X座標、またはY座標の長さが大きい方を1にするので、Y座標、またはX座標の長さが小さいほうは0.なんぼになって、1より小さくなるから斜めに動きますよね。
言葉で書くと解りにくいので絵にしました。
X座標が大きい.PNG
X座標が5動いた時に、Y座標が3動くような斜め方向に移動させたい場合、X座標が1動いたらY座標は 3 ÷ 5 動けばいいことになります。
逆に、X座標が3動いた時に、Y座標が5動くような斜め方向に移動させたい場合、
Y座標が大きい.PNG
Y座標が1動いたらX座標は 3 ÷ 5 動けばいいことになります。

算数だけで理解できます。
小学生のころにこれを教えてもらってむっちゃ感動した記憶が。

ただし、これは斜め45°の場合に、X座標もY座標も1ずつ進むので、一番早くなり、弾の速さが一定でない、という欠点があります。
直角二等辺三角形的にはルート2になるので、1.41421356(ひとよひとよにひとみごろ)倍の速さで動くことになります。

1あたりの出し方(かっこいい編)

速さを一定にして動かしたい場合、どうすればいいのかですが、「1あたりの」が同じ長さだったら同じ速さになるはずです。
そこで、「1あたりの」を、X座標が1でY座標が0の時の長さ、または、Y座標が1でX座標が0の時の長さ って考えます。つまり長さ1です。
これがどの方向に向いても長さ1になればいいので、長さ1の円上のどこかの座標ってことになります。
「どこか」っていうと、飛ばしたい方向ですよね。

長さ1の円の座標、って言ったら、三角関数ですよ!
三角関数って生活の何の役にも立たない、とか言ってる人いますけど、役立つのですよ!
三角関数はシューティングゲームで斜めに弾を飛ばすためにあると言っても過言ではない!(と思ってる。)

で、三角関数ってなんだっけ?

自分の中では、半径1の円の、とある角度の座標を求める場合、
 X座標 = コサイン(角度)
 Y座標 = サイン(角度)
で求められる、という理解です。

この角度というのがJavascriptの三角関数では45°とかの度ではなくて、ラジアンを使っているので、一瞬???となるのですが、
 180° = 1πラジアン
 360° = 2πラジアン
  90° = 1/2πラジアン
って覚えておけば、なんとなくイメージできるかと思います。

角度はどうやって求めるのか?

X座標が大きい.PNG
こういう、X座標が5でY座標が3方向の角度を求めるのは、javascriptでは atan2 という関数があって、
 角度(ラジアン) = atan2(Y座標, X座標)
で求められます。
ただし、javascriptのY座標と数学のY座標は向きが逆なので、上の図だと、
 角度(ラジアン) = atan2(-3, 5)
で求められます。
そこで、Aオブジェクトから見たBオブジェクトの角度を求める関数を作りました。

/**
 * 2つのスプライト間の角度(ラジアン)を生成する関数
 * from 開始スプライトオブジェクト
 * to   方向スプライトオブジェクト
 */
function createAngle(from, to) {
    var x = to.x - from.x;
    var y = to.y - from.y;

    return Math.atan2(-y, x);
}

敵の弾を作ってみる

斜めに弾を飛ばす方法が解ったので、敵の弾のクラスを作ってみます。
その前に、弾の画像を作りました。8×8の弾です。
bullet.gif
これを、bullet って名前で ASSETS に設定してます。

敵の弾のクラスのパラメータは、
 弾を生成する場所のX座標、Y座標
 飛ばしたい角度(ラジアン)
 弾のスピード
があれば良いですね。

移動量の計算のY座標に-1を掛けてるのは、javascriptのY座標と数学のY座標は向きが逆だからです。

/**
 * 敵の弾を作成するクラス
 */
phina.define('EnemyBullet', {
    // Spriteを継承
    superClass: 'Sprite',
    /** コンストラクタ
    *   x       X座標
    *   y       Y座標
    *   angle   角度(ラジアン)
    *   speed   弾のスピード
    */
    init: function(x, y, angle, speed) {
        // 親クラスの初期化
        this.superInit('bullet', 8, 8);

        // プロパティの初期化
        this.x = x;
        this.y = y;
        this.angle = angle;
        this.speed = speed;

        // 移動量を計算
        this.moveX = this.speed * Math.cos(this.angle);
        this.moveY = this.speed * Math.sin(this.angle) * -1;
    },
    update: function(app) {
        // 弾の移動処理
        this.x += this.moveX;
        this.y += this.moveY;

        // 画面の外に出たら消す
        if (this.right < 0 || this.left > mainScene.width || this.bottom < 0 || this.top > mainScene.height) {
            this.remove();
            delete this;
        }
    },
});

前回作った敵クラスに、弾を飛ばす処理を入れてみます。
敵から自機の座標が判らないといけないので、自機のshipっていう変数は、グローバルちっくな変数にしました。
この辺、phina.js ではどうすればいいのかよく解らないです。下手すると、グローバルちっくな変数が大量生産されそうな気がする・・・。

自機方向に弾を飛ばすには、自機までの角度を計算して、弾を生成すればいいだけ。

// 自機までの角度を算出
var angle = createAngle(this, ship);
// 弾を発射
EnemyBullet(this.x, this.y, angle, 4).addChildTo(mainScene.group.enemyBulletGroup);

こんな感じかな。
敵1クラス全体だとこんな感じ。

/**
 * 敵1クラス
 * ジグザグ移動
 */
phina.define('Enemy01', {
    // Enemyを継承
    superClass: 'Enemy',
    // 初期化
    init: function() {
        // 親クラスの初期化
        this.superInit('enemy', 32, 32);

        // プロパティの設定
        this.frameIndex = 2;
        this.score = 100;
        this.hardness = 1;

        // 移動速度を決定
        this.speed = 4;

        // 初期値を設定
        this.x = Random.randint(0, mainScene.width);
        this.y = 0;
        // 横方向の速度
        this.speedX = this.speed;
        // 縦方向の速度
        this.speedY = 2 * Random.randint(1, 3);

        // モード 1:左方向、-1:右方向
        this.mode = 1;  
        // 出現位置により移動方向を決める
        if (this.x > mainScene.width / 2) {
            this.speedX = -this.speed;
            this.mode = -1;
        }

    },
    // 敵を動かすメソッド  
    move: function () {
        this.x += this.speedX;
        this.y += this.speedY;
        // スピードを加算する。
        this.speedX += this.mode * 2;

        // スピードが元の4倍を超えた場合
        if (Math.abs(this.speedX) >= this.speed * 4) {
            // 方向を逆転する
            this.mode *= -1;
            // 一定の確率で弾を発射する
            if (Random.randint(0, 3) == 0) {
                // 自機までの角度を算出
                var angle = createAngle(this, ship);
                // 弾を発射
                EnemyBullet(this.x, this.y, angle, 4).addChildTo(mainScene.group.enemyBulletGroup);
            }
        }

        // 画像の決定
        if (Math.abs(this.speedX) < this.speed * 2) {
            // 正面
            this.frameIndex = 2;
        } else if (this.mode == 1) {
            // 右向き
            this.frameIndex = 3;
        } else {
            // 左向き
            this.frameIndex = 1;
        }
    },
 });

今日の成果

今日の成果をここに上げました。
敵が自機方向に弾を撃ってきます。
http://hirotyan.my.coocan.jp/phinajs/Shooting/005/index.html

phina.js が面白そうなのでお勉強中 その8

2
3
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
3