AuthAndFriends サンプルをざっくり読んできましたが、他のプロジェクトも読むにあたり共有コード部分を個別のプロジェクトのコードに関して読み方を自分なりに整理しておきます。
各プロジェクトのファイル共有状況
以下は VS のソリューションエクスプローラーでのツリーなので、実態のファイルの場所とは異なります。
ここでは、 AuthAndFriends
と P2PNAT
の 2 つのサンプルを例にとって、Shared とそうでない部分の比較をしていきます。
Samples/ + AuthAndFriends/ + EOSSDK/ + SharedAssets/ + SharedSource/ + Graphics/GUI/Dialogs/ + AuthDialogs.h(cpp) + ConsoleDialog.h(cpp) + FriendsDialog.h(cpp) + ExitDialog.h(cpp) + PopupDialog.h(cpp) + Main/ + SDL/SDLMain.h(cpp) + Main.h(cpp) + BaseGame.h(cpp) + BaseMenu.h(cpp) + Source/ + Game.h(cpp) + Menu.h(cpp) + CustomInvitesDialog.h(cpp) + CustomInvites.h(cpp) + P2PNAT/ + EOSSDK/ + SharedAssets/ + SharedSource/ + Graphics/GUI/Dialogs/ + AuthDialogs.h(cpp) + ConsoleDialog.h(cpp) + FriendsDialog.h(cpp) + ExitDialog.h(cpp) + PopupDialog.h(cpp) + Main/ + SDL/SDLMain.h(cpp) + Main.h(cpp) + BaseGame.h(cpp) + BaseMenu.h(cpp) + Source/ + Game.h(cpp) + Menu.h(cpp) + P2PNATDialog.h(cpp) + P2PNAT.h(cpp)
上記は (SDL2 を利用した場合の) 主要なファイルのみを記載したものです。
EOSSDK
, SharedAssets
, SharedSource
はいずれも共有コードです。(ただ、AuthAndFriends には Steam に関するコードがあったりと全く同じファイルがある訳ではありません。ただ、同名のファイルがあれば内容は同じです)
共有部分には、ゲームの起動・終了・イベントループを司る main
部分に加えて FBaseGame
, FBaseMenu
などの基底クラスが存在し、各プロジェクトにそれぞれの FGame
, FMenu
が存在しています。
また、UI 要素としても全プロジェクトに存在しているものは SharedSource/Graphics/GUI/Dialogs
に配置されています。
個別のプロジェクトでのみ利用する CustomInvitesDialog
, P2PNATDialog
は各プロジェクトの Source
フォルダに配置されています。
共有ファイルと個別ファイルの読み進め方
初期化部分
SDLMain.cpp
の Init
関数が初期化の起点になり、そこから各プロジェクトに関する呼び出しの重要な部分は FMain::Initialize
になります。(コードは注目したい部分以外は省略したりカットしています)
void FMain::Initialize(SDL_Window* Window, SDL_GLContext InGLContext, int Width, int Height) { SDLWindow = Window; GLContext = InGLContext; Game = std::make_unique<FGame>(); CreateDeviceDependentResources(); CreateWindowSizeDependentResources(); Game->UpdateLayout(Width, Height); Game->Init(); } void FMain::CreateDeviceDependentResources() { if (Game) { Game->Create(); } }
void FBaseGame::Create() { Menu->CreateFonts(); Menu->Create(); Level->Create(); } void FBaseGame::UpdateLayout(int Width, int Height) { if (Menu) { Menu->UpdateLayout(Width, Height); } } void FBaseGame::Init() { Metrics->Init(); Menu->Init(); EosUI->Init(); FGameEvent Event(EGameEventType::CheckAutoLogin); OnGameEvent(Event); } void FBaseGame::OnGameEvent(const FGameEvent& Event) { PlayerManager->OnGameEvent(Event); Friends->OnGameEvent(Event); Menu->OnGameEvent(Event); Authentication->OnGameEvent(Event); Metrics->OnGameEvent(Event); Level->OnGameEvent(Event); EosUI->OnGameEvent(Event); }
void FBaseMenu::Create() { BackgroundImage->Create(); TitleLabel->Create(); TitleLabel->SetFont(BoldLargeFont->GetFont()); CreateConsoleDialog(); CreateAuthDialogs(); CreateExitDialog(); CreatePopupDialog(); } void FBaseMenu::UpdateLayout(int Width, int Height) { Vector2 WindowSize = Vector2((float)Width, (float)Height); BackgroundImage // 略 if (ConsoleDialog) { // 略 if (FriendsDialog) { // 略 } } if (PopupDialog) { // 略 } if (ExitDialog) { // 略 } if (AuthDialogs) AuthDialogs->UpdateLayout(); } void FBaseMenu::Init() { AuthDialogs->Init(); } void FBaseMenu::OnGameEvent(const FGameEvent& Event) { if (Event.GetType() == EGameEventType::ShowPrevUser) { // 略 } else if (Event.GetType() == EGameEventType::ShowNextUser) { // 略 } else if (Event.GetType() == EGameEventType::CancelLogin) { // 略 } else if (Event.GetType() == EGameEventType::ToggleFPS) { // 略 } else if (Event.GetType() == EGameEventType::ExitGame) { // 略 } else if (Event.GetType() == EGameEventType::ShowPopupDialog) { // 略 } if (FriendsDialog) // 略 if (AuthDialogs) // 略 }
よって、 FGame
, FMenu
に関しては以下を確認すればいいです。プロジェクト固有の Dialog がある場合は、おそらく FMenu
で確認できます。
- コンストラクタ
Create
UpdateLayout
Init
OnGameEvent
(EGameEventType::CheckAutoLogin
イベントのみ)- その他
FBaseGame
のメソッドを override しているもの
また呼び出し順序も、override メソッドを除けば上記の並びになっているようです。
イベントループ部分
イベントハンドル
SDLMain.cpp
の ProcessSDLEvents
関数が起点になり、そこから各プロジェクトに関する呼び出しの重要な部分は FBaseMenu::OnUIEvent
になります。(コードは注目したい部分以外は省略したりカットしています)
void FBaseMenu::OnUIEvent(const FUIEvent& Event) { if (Event.GetType() == EUIEventType::MousePressed) { for (DialogPtr NextDialog : Dialogs) { if (NextDialog->CheckCollision(Event.GetVector())) { NextDialog->OnUIEvent(Event); } else { NextDialog->SetFocused(false); } } } else { for (DialogPtr NextDialog : Dialogs) { NextDialog->OnUIEvent(Event); } } if (AuthDialogs) AuthDialogs->OnUIEvent(Event); }
イベントハンドル部分に関しては以下を確認することになります。
FMenu::OnUIEvent
- 必ずあるわけではないですが、override されている場合があります
- プロジェクトで参照されている各
Dialog
のOnUIEvent
- 上記コードの
Dialogs
に入っているものすべてですが、各プロジェクトのFMenu
内でAddDialog
されているものを探せばだいたい揃います
- 上記コードの
Tick
SDLMain.cpp
の main
関数内で FMain::Tick
が呼び出されます。(Render
は省略します)
void FMain::Tick() { Timer.Tick([&]() { Update(); }); Render(); } void FMain::Update() { if (Game) { Game->Update(); } }
void FBaseGame::Update() { Input->Update(); if (Input->IsKeyPressed(FInput::InputCommands::Exit) || Input->IsGamePadButtonPressed(FInput::InputCommands::Exit)) { FGameEvent Event(EGameEventType::ExitGame); OnGameEvent(Event); } Users->Update(); Menu->Update(); Level->Update(); Friends->Update(); FPlatform::Update(); }
void FBaseMenu::Update() { BackgroundImage->Update(); TitleLabel->Update(); FPSLabel->Update(); std::unique_ptr<FInput> const& Input = FBaseGame::GetBase().GetInput(); if (Input) { // check if characters were typed if (Input->IsAnyKeyPressed()) { //Check if we need to generate repeated key press events if (KeyCurrentlyHeld != FInput::None) { if (!Input->IsKeyReleased(static_cast<FInput::Keys>(toupper(int(KeyCurrentlyHeld))))) { KeyCurrentlyHeldSeconds += static_cast<float>(Main->GetTimer().GetElapsedSeconds()); if (KeyCurrentlyHeldSeconds >= SecondsTilKeyRepeat || (KeyCurrentlyHeld == FInput::Back && KeyCurrentlyHeldSeconds >= (SecondsTilKeyRepeat / 2.0f)) || (KeyCurrentlyHeld == FInput::Delete && KeyCurrentlyHeldSeconds >= (SecondsTilKeyRepeat / 2.0f))) { KeyCurrentlyHeldSeconds = 0.0f; FUIEvent event(EUIEventType::KeyPressed, KeyCurrentlyHeld); OnUIEvent(event); } } else { KeyCurrentlyHeld = FInput::None; KeyCurrentlyHeldSeconds = 0.0f; } } } } for (DialogPtr NextDialog : Dialogs) { NextDialog->Update(); } AuthDialogs->Update(); UpdateFPS(); }
void FPlatform::Update() { if (PlatformHandle) { EOS_Platform_Tick(PlatformHandle); } if (!bIsInit && !bIsShuttingDown) { if (!bHasShownCreateFailedError) { // 略 } } if (bHasInvalidParamProductId || bHasInvalidParamSandboxId || bHasInvalidParamDeploymentId || bHasInvalidParamClientCreds) { if (!bHasShownInvalidParamsErrors) { // 略 } } }
Tick 部分に関しては以下を確認することになります。
FGame::Update
- プロジェクトで参照されている各
Dialog
のUpdate
OnGameEvent
OnGameEvent
は明示的なユーザー操作でトリガされる OnUIEvent
とは異なり、ユーザー操作後であったり、通信の前後であったり、コンソールコマンドであったりとあらゆる状況で呼び出される可能性があります。
基本的には FGame
及び FBaseGame
の OnGameEvent
が呼び出され、そこから dispatch されます。
void FBaseGame::OnGameEvent(const FGameEvent& Event) { PlayerManager->OnGameEvent(Event); Friends->OnGameEvent(Event); Menu->OnGameEvent(Event); Authentication->OnGameEvent(Event); Metrics->OnGameEvent(Event); Level->OnGameEvent(Event); EosUI->OnGameEvent(Event); }
上記の Menu->OnGameEvent
があるため、FMenu
, FBaseMenu
が管理する各 Dialog の OnGameEvent
にさらに伝搬するケースもあります。
よって、OnGameEvent
に関しては以下を確認することになります。
FGame::OnGameEvent
FMenu::OnGameEvent
- プロジェクトで参照されている各
Dialog
のOnGameEvent
終了部分
SDLMain.cpp
の main
関数内で FMain::OnShutdown
が呼び出されます。
void FMain::OnShutdown() { if (Game) { Game->OnShutdown(); } }
void FBaseGame::OnShutdown() { // Must explicitly call FEosUI::OnShutdown() before the end of destruction to allow FBaseGame::GetBase() to not throw. if (EosUI) { EosUI->OnShutdown(); } // Must explicitly call FAuthentication::Shutdown() before the end of destruction to allow FBaseGame::GetBase() to not throw. if (Authentication) { Authentication->Shutdown(); } Release(); }
よって、FGame::OnShutdown
を確認しておけばよいと思います。
まとめ
AuthAndFriends
は共有分のコードを読むのに時間がかかりましたが、他のプロジェクトを読む際はそこを省略できます。
今回は、省略して読むもののより流れを追いつつ簡単に読んでいけるようにサンプルのフレームワーク部分と、各プロジェクトの呼び出し関係を整理しました。
引き続き、他のプロジェクトも読んでいければと思います。