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

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

#クラス定義
前置きみたいな感じなので簡潔に。
##1.プレイヤークラス(構造体)
今回、プレイヤーには、
・座標
・HP
・向いている向き
・使用武器(シューター2種類、スナイパー2種類のうち1つ)
・手に持っている武器(メイン武器かサブか)
・操作するもの(人か、NPCか)
・プレイヤー名
・弾発射のクール時間
これらの情報をもたせます

<details>
<summary>ソースコード</summary>
<div>

```C++: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;
};

```

</div>
</details>

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

<details><summary>ソースコード</summary>
<div>

```C++: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; //弾を撃ったプレイヤー名
};

```

</div>
</details>



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

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

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

これを使って、

```C++: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ゲージなど)。
ただ、描画のときは処理時とは違って、もう一つ宣言が必要(カメラでの描画用のレンダー？)。
それがこれ

```C++
ScopedRenderStates2D sampler(SamplerState::ClampNearest);
```

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

これを使って、

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

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

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

こんな感じで実装

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

ゲーム画面はこんな感じ(開発中)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596925/638f5a21-6947-74a0-6b01-2f0c01585811.png)


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

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

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