LoginSignup
4
0

More than 3 years have passed since last update.

Camera2Dを使ってバトロワゲームを作る

Last updated at Posted at 2020-12-09

概要

OpenSiv3Dバージョン0.4.1でCamera2Dが追加されたので、それを使ってバトロワゲームを作る

クラス定義

前置きみたいな感じなので簡潔に。

1.プレイヤークラス(構造体)

今回、プレイヤーには、
・座標
・HP
・向いている向き
・使用武器(シューター2種類、スナイパー2種類のうち1つ)
・手に持っている武器(メイン武器かサブか)
・操作するもの(人か、NPCか)
・プレイヤー名
・弾発射のクール時間
これらの情報をもたせます


ソースコード
GameClass.h

struct Player {
    Player(String MainWeapon_, Vec2 Pos_, String Type_, String Name_) {
        if (MainWeapon_ != U"M1" && MainWeapon_ != U"M2" && MainWeapon_ != U"S1" && MainWeapon_ != U"S2") {
            if (MainWeapon_ == U"R") {
                Array<String>a = { U"M1",U"M2" }; //S1,S2はCPUには乗せない
                MainWeapon = a[int32(Random() * a.size())];
            }
            else {
                throw Error(U"Visable Weapon Type '" + MainWeapon_ + U"'");
            }
        }
        else {
            MainWeapon = MainWeapon_;
        }

        Cool = 0;
        if (MainWeapon == U"M1") {
            CoolMax = 10;
            Err = 0;
            MoveSpeed = 8;
        }
        elif(MainWeapon == U"M2") {
            CoolMax = 5;
            Err = 0;
            MoveSpeed = 8;
        }
        elif(MainWeapon == U"S1") {
            CoolMax = 50;
            Err = 100;
            MoveSpeed = 5;
        }
        elif(MainWeapon == U"S2") {
            CoolMax = 100;
            Err = 100;
            MoveSpeed = 5;
        }

        Pos = Pos_;
        HP = 100;
        Type = Type_;
        WeaponNow = U"Main";
        Name = Name_;
        Arc = 0_deg;
    }
    void SwitchWeapon() {
        if (WeaponNow == U"Main") {
            WeaponNow = U"Sub";
        }
        else {
            WeaponNow = U"Main";
        }
    }

    void SetBody(P2World &world) {
        Body = world.createDynamicCircle(Pos, 50);
        Body.setPos(Pos);
        //throw Error(Format(Body.getPos()));
    }

    String MainWeapon; //メイン武器(マシンガン1,2(M1,M2):スナイパー1,2(S1,S2))
    /*
    M1:精度高、ダメージ低
    M2:精度低、ダメージ高
    S1:連射高、ダメージ低
    S2:連射低、ダメージ高
    */

    Vec2 Pos;
    int32 HP;
    String Type; //誰が操作するのか
    String WeaponNow;
    int32 Cool; //現在のクール時間
    int32 CoolMax; //発射直後のクール時間
    int32 Err; //手振れ
    String Name; //プレイヤー名
    int32 MoveSpeed;
    double Arc;
    double SwitchArc; //CPU用
    P2Body Body;
};


2.バレットクラス(構造体)

プレイヤーと弾は分離して考える(そのほうが管理が楽なので)
もたせる情報は、
・座標
・進む方向
・当たったときのダメージ
・着弾までのフレーム数(=飛距離)
・飛ぶ速さ(一定だけど一応定義)
・弾を発射ししたプレイヤー名(通知用)

ソースコード
GameClass.h
struct Bullet {
    Bullet(Player Player_,double Arc_) {
        Pos = Player_.Pos;
        Arc = Arc_;
        String Weapon = Player_.WeaponNow;
        if (Weapon == U"Main" && Player_.MainWeapon == U"M1") {
            Damage = 10;
            Life = 30;
        }
        elif(Weapon == U"Main" && Player_.MainWeapon == U"M2") {
            Damage = 8;
            Life = 30;
        }
        elif(Weapon == U"Main" && Player_.MainWeapon == U"S1") {
            Damage = 35;
            Life = 50;
        }
        elif(Weapon == U"Main" && Player_.MainWeapon == U"S2") {
            Damage = 50;
            Life = 50;
        }
        else {
            Damage = 15;
            Life = 15;
        }
        Speed = 50;
        Master = Player_.Name;
    }
    Vec2 Pos;
    double Arc;
    int32 Damage;//当たった時に与えられるダメージ
    int32 Life; //飛び終わるまでのフレーム数
    int32 Speed; //飛ぶ速さ
    String Master; //弾を撃ったプレイヤー名
};


システム

このゲームの大まかな構想は、大きいフィールドを作って、そこに全プレイヤーを配置、描画をCamera2Dを使って自分の周りだけにするという感じ

処理

処理するとき、座標は基本的にSceneの左上を基準にした座標軸(相対座標)になってるから、まずは座標をフィールド基準(絶対座標)にする必要がある。でもそんなに難しくなくて、

const auto t = camera.createTransformer();

こうしてあげるだけで座標が絶対座標になってくれる。
ただ、これをそのままMain関数とかに置いちゃうと、相対座標で管理するべきもの(HPゲージとか)まで絶対座標になるから、{}で囲ってスコープに入れてあげる(理由はよくわからないけど、多分ローカル変数の関係だと思う)

これを使って、

Game.cpp
if(alive){ /*生存中なら*/
/*プレイヤー視点にしたいからカメラの座標は固定*/
    camera.setScale(.5);
    camera.setCenter(Players[0].Pos);
}
for(int32 i = 0; i < Players.size(); ++i){ /*プレイヤー数だけ回す*/
    Players[i].Pos = bodies[i].getPos(); /*物理演算のワールドから座標を取得(あとで説明)*/
    if(/*プレイヤー操作*/){
        const auto t = camera.createTransformer(); /*絶対座標に切り替え*/
        /*プレイヤー処理 (移動、弾の発射など)*/
    }
    else{ /*NPC操作*/{
        /*NPC処理 (移動、弾の発射など)*/
    }

    /*物理演算ワールドに座標をコピー (あとで説明)*/

}

for(int32 i = 0; i < Bullets.size(); ++i){ /*フィールド上の弾の数だけ回す*/
    /*各プレイヤーとの当たり判定処理*/

    /*弾の移動*/
}

/*物理演算 (あとで説明)*/

こんな感じに実装。

「あとで説明」の説明

 バトロワゲームには欠かせない壁などのオブジェクト。ただ、これを1から実装するとなると結構大変(弾が当たってなくなるのはいいけど、プレイヤーが当たったときの処理がわからない)。
なので、重力を0にして物理演算で楽しちゃおう、ということです。
 その時、物理演算のワールドのオブジェクトと実際のワールドのオブジェクトを連携させるために、プレーヤー処理の前に物理演算のワールドから座標を持ってきて、プレイヤー処理が終わってから物理演算のワールドに座標をコピーしてます。

座標を取得→処理→処理後の座標をコピー→次のプレイヤーの処理へ

こんな感じです

描画

もちろん描画でも絶対座標と相対座標の区別は必要(絶対座標:プレイヤーなどの描画 相対座標:HPゲージなど)。
ただ、描画のときは処理時とは違って、もう一つ宣言が必要(カメラでの描画用のレンダー?)。
それがこれ

ScopedRenderStates2D sampler(SamplerState::ClampNearest);

これもスコープに入れておくと絶対座標と相対座標との区別ができます。

これを使って、

Game.cpp
{/*絶対座標で管理するスコープ*/
    ScopedRenderStates2D sampler(SamplerState::ClampNearest);
    const auto t = camera.createTransformer();

    /*プレイヤーUIの描画 (射線、手ブレの度合い)*/
    /*プレイヤーの描画*/
    /*弾の描画*/
    /*オブジェクト(壁)の描画*/
}

/*相対座標で管理*/
/*UI (HPゲージ・ログ)*/

こんな感じで実装

これで大まかなシステムは完成です

ゲーム画面はこんな感じ(開発中)
image.png

最後に

僕にとって初めての「ワールドが1画面に収まらないゲーム」の制作でした。
制作していく上で、Camera2DとかP2Body(物理演算)の機能が便利で使いやすかったです。

ソースコードはGit Hubにあります。ただ、まだ制作中(2020年12月1日時点)なので、この記事と違うかもしれないです。

余談

NPCの数を10000にして実行してみたらFPSが1切ってました(笑)
ハッシュで管理したらもっと軽くなるのかな

4
0
2

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
4
0