#概要
OpenSiv3Dバージョン0.4.1でCamera2Dが追加されたので、それを使ってバトロワゲームを作る
#クラス定義
前置きみたいな感じなので簡潔に。
##1.プレイヤークラス(構造体)
今回、プレイヤーには、
・座標
・HP
・向いている向き
・使用武器(シューター2種類、スナイパー2種類のうち1つ)
・手に持っている武器(メイン武器かサブか)
・操作するもの(人か、NPCか)
・プレイヤー名
・弾発射のクール時間
これらの情報をもたせます
ソースコード
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.バレットクラス(構造体)
プレイヤーと弾は分離して考える(そのほうが管理が楽なので)
もたせる情報は、
・座標
・進む方向
・当たったときのダメージ
・着弾までのフレーム数(=飛距離)
・飛ぶ速さ(一定だけど一応定義)
・弾を発射ししたプレイヤー名(通知用)
ソースコード
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ゲージとか)まで絶対座標になるから、{}で囲ってスコープに入れてあげる(理由はよくわからないけど、多分ローカル変数の関係だと思う)
これを使って、
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);
これもスコープに入れておくと絶対座標と相対座標との区別ができます。
これを使って、
{/*絶対座標で管理するスコープ*/
ScopedRenderStates2D sampler(SamplerState::ClampNearest);
const auto t = camera.createTransformer();
/*プレイヤーUIの描画 (射線、手ブレの度合い)*/
/*プレイヤーの描画*/
/*弾の描画*/
/*オブジェクト(壁)の描画*/
}
/*相対座標で管理*/
/*UI (HPゲージ・ログ)*/
こんな感じで実装
これで大まかなシステムは完成です
最後に
僕にとって初めての「ワールドが1画面に収まらないゲーム」の制作でした。
制作していく上で、Camera2DとかP2Body(物理演算)の機能が便利で使いやすかったです。
ソースコードはGit Hubにあります。ただ、まだ制作中(2020年12月1日時点)なので、この記事と違うかもしれないです。
余談
NPCの数を10000にして実行してみたらFPSが1切ってました(笑)
ハッシュで管理したらもっと軽くなるのかな