LoginSignup
0

More than 1 year has passed since last update.

posted at

updated at

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

概要

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切ってました(笑)
ハッシュで管理したらもっと軽くなるのかな

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
What you can do with signing up
0