こう書きたくなる
for(i = 0; i < size(enemies); i++)
for(j = 0; j < size(bullets); j++)
if (...) {
// 弾が当たったときの処理
enemies.erase(i);
bullets.erase(j);
}
が、ループの途中で要素を削除するのは危険!
このような書き方が安全 (疑似コード)
for(i = 0; i < size(enemies); i++)
for(j = 0; j < size(bullets); j++)
if (...) {
enemies[i].deleteFlag = true;
bullets[i].deleteFlag = true;
}
enemies.remove_if( enemy -> enemy.deleteFlag );
bullets.remove_if( bullet -> bullets.deleteFlag );
実際のゲームには、「タイトル画面」や「リザルト画面」などがある。
またステージも複数ある。
これらの切り替えはどのように実装すると良いか?
最も単純なやり方: 「今どの画面にいるか」を変数で保持して切り替える
mode = TitleScreen;
switch(mode) {
case TitleScreen:
TitleScreenUpdate();
break;
case MaingameScreen:
MainScreenUpdate();
break;
case ResultScreen:
ResultScreenUpdate();
break;
}
先ほどのようなやり方でも問題ないといえば問題ないが、
画面が増えてくると大変になってくる
ゲーム中の様々な画面をクラスで表し、インタフェースで抽象化する
interface Screen {
update();
}
class TitleScreen implements Screen {
update() { /* ... */ }
}
class MaingameScreen implements Screen {
update() { /* ... */ }
}
現在の画面を表すオブジェクトを作り、それのupdate
を呼ぶ
// 現在の画面
Screen current_screen = TitleScreen();
while(true) {
current_screen.update();
}
これでメインループの中が単純になる
単純に以下のような実装にすると、自分自身を破棄してしまう
class TitleScreen {
update() {
if(...) {
current_screen = MaingameScreen(); // NG!
}
}
}
(RAII機構を積まずにGCで何とかする言語なら問題ないのかもしれないけど…)
処理中に画面オブジェクトの破棄が走ると良くないので、その外でやる
string next_screen = "";
class TitleScreen {
update() {
if(...) {
next_screen = "Maingame";
}
}
}
処理中に画面オブジェクトの破棄が走ると良くないので、その外でやる
string nextScreen;
while(true) {
current_screen.update();
if(nextScreen) {
if(nextScreen == "Title") current_screen = TitleScreen();
if(nextScreen == "Game") current_screen = GameScreen();
if(nextScreen == "Result") current_screen = ResultScreen();
}
}
画面遷移部分も抽象化すると良いかも?
change_screen(next_screen) {
current_screen = screen_factories[next_screen].make();
}
while(true) {
current_screen.update();
if(next_screen)
change_screen(next_screen);
}
管理機能を 1 つのクラスにまとめてしまうと便利
Siv3D や Phaser.js などは元からそういうクラスを用意している
class ScreenManager {
register_screen(string id, ScreenFactory factory);
change_screen(string id);
current_screen_update();
};
「画面Aから画面Bに移る遷移がある」
ならば⇒「画面Aクラスは画面Bクラスに依存する」
となってしまった場合、依存関係の循環が発生する これは良くない!
なので、基本的に遷移先の画面を表すクラスへの依存を持ってはいけない
上の疑似コードで next_screen
がクラス型ではなかったのはこのため!
よくある実装はこういう形になっている
main関数などが画面マネージャに各画面を表すクラスを登録し、
各画面は他の画面をIDなどで参照する
今解説したような「ゲーム上で切り替わる各画面」のことを、
Unityおよびそれに影響を受けた文化圏等では「シーン」と呼ぶ
「シーン作って」と言われたらなんか画面増やしたりステージ増やしたりしたいんだなと思おう
余談:
あくまで「Scene」の語は比較的広く共有された単語に過ぎないことに注意。
UnrealEngineでは「Level」だったり、GameMakerStudioでは「Room」だったりする
タイトル画面~ゲーム本体~リザルト画面~タイトル画面
のような一周できる画面遷移を俗に「ゲームループ」と呼ぶ
「ちゃんとしたゲーム」としての体裁になる、1 つのベースライン
※「メインループ」とは全くの別概念なので混同しないように
※メインループをゲームループと呼ぶ人もいるけど...
飛んだり跳ねたり戦ったりするような
いわゆる「ゲーム本体」の部分をインゲームと呼ぶ
タイトル画面・リザルト画面・ロード画面・設定画面など
副次的な部分全般をアウトゲームと呼ぶ
ゲーム開発者が時々使ってる用語なので覚えとくとビビらなくて済むかも
前半において、FPSの調整方法には2種類あると説明した
これらは必要に応じて併用されることがある
ちらっと触れた「複合型FPS調整」ではなく処理自体を2種類に分ける
この場合だとFPSが2種類存在することになる
という形で処理を分ける設計が広く使われる
著名な例 :
Update()
/ FixedUpdate()
マルチスレッドでメインループ自体を複数に分ける設計のほか、
1 つのメインループ内の update 実行回数で帳尻を合わせるケースもある
今回は単純にforループで1つずつオブジェクトを見ていくことで実装した
しかし...
となってくると、パフォーマンス上の問題が出てくる
基本的にコンピュータは
場合の計算に強いが、種類が多いとそういう訳に行かない
→ループの回し方、オブジェクト管理の仕方の工夫が色々出てくる
やる気ある人は調べてみてください
今日の内容
こうした実装は、本物のゲームプログラマーであれば大体できる
つまり本質的にはUnityなど無くてもゲームは作れる
それでも彼らはUnityなどを使う それはなぜか?
「最低限動く」ようなものは誰でも作れる
一方で、高度に細部が最適化されたものは作るのが大変
大量の複雑なオブジェクトが絡み合う大規模なゲームでは特に問題!
どのゲームでも同じような基盤部分の最適化を
新しいゲームを作るたびにやるのはバカバカしい
→Unityのようなゲームエンジンで労力を節約する
これがゲームエンジンを使う(プログラマ視点の)大きな理由
(プログラマ以外の視点では素材管理とかステージ設計みたいなツールが全部入りというのが大きい)
が、そうした重量級のお膳立てが必要なければ
別に必ずしも使わなくて良いと思う(個人の感想です)
本日はお疲れ様でした!