Files
FlaxEngine/Source/Engine/Engine/Base/GameBase.cpp
2025-03-05 12:09:32 +01:00

281 lines
8.3 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "../Game.h"
#if !USE_EDITOR
#include "Engine/Engine/Time.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Platform/Window.h"
#include "Engine/Profiler/Profiler.h"
#include "Engine/Level/Level.h"
#include "Engine/Core/Config/GameSettings.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Assets/Texture.h"
#include "Engine/Content/JsonAsset.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Render2D/Render2D.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Utilities/Encryption.h"
#include "Engine/Core/Log.h"
#include "FlaxEngine.Gen.h"
namespace GameBaseImpl
{
GameHeaderFlags HeaderFlags = GameHeaderFlags::None;
Guid SplashScreenId;
float SplashScreenTime = 0.0f;
AssetReference<JsonAsset> FirstScene;
AssetReference<Texture> SplashScreen;
void OnMainWindowClosed();
void OnPostRender(GPUContext* context, RenderContext& renderContext);
void OnSplashScreenEnd();
}
bool GameBase::IsShowingSplashScreen()
{
return GameBaseImpl::SplashScreenTime > ZeroTolerance;
}
void GameBase::InitMainWindowSettings(CreateWindowSettings& settings)
{
}
int32 GameBase::LoadProduct()
{
// Startup and project root paths are the same in build game
Globals::ProjectFolder = Globals::StartupFolder;
// Load build game header file
{
int32 tmp;
Array<byte> data;
FileReadStream* stream = nullptr;
#if 1
// Open file
stream = FileReadStream::Open(Globals::ProjectFolder / TEXT("Content/head"));
if (stream == nullptr)
goto LOAD_GAME_HEAD_FAILED;
// Check header
stream->ReadInt32(&tmp);
if (tmp != ('x' + 'D') * 131)
goto LOAD_GAME_HEAD_FAILED;
// Don't allow to load game packaged by the other engine version
stream->ReadInt32(&tmp);
if (tmp != FLAXENGINE_VERSION_BUILD)
goto LOAD_GAME_HEAD_FAILED;
// Load game primary data
stream->Read(data);
if (data.Count() != 808 + sizeof(Guid))
goto LOAD_GAME_HEAD_FAILED;
Encryption::DecryptBytes(data.Get(), data.Count());
Globals::ProductName = (const Char*)data.Get();
Globals::CompanyName = (const Char*)(data.Get() + 400);
GameBaseImpl::HeaderFlags = (GameHeaderFlags)*(int32*)(data.Get() + 800);
Globals::ContentKey = *(int32*)(data.Get() + 804);
GameBaseImpl::SplashScreenId = *(Guid*)(data.Get() + 808);
goto LOAD_GAME_HEAD_DONE;
LOAD_GAME_HEAD_FAILED:
Platform::Fatal(TEXT("Cannot load game."));
return -3;
LOAD_GAME_HEAD_DONE:
Delete(stream);
#else
// Mock the settings to debug executable without content
Globals::ProductName = TEXT("Flax");
Globals::CompanyName = TEXT("Flax");
GameBaseImpl::HeaderFlags = GameHeaderFlags::ShowSplashScreen;
GameBaseImpl::SplashScreenId = Guid::Empty;
Globals::ContentKey = 0;
#endif
}
return 0;
}
bool GameBase::Init()
{
PROFILE_CPU();
// Preload splash screen texture
if (EnumHasAnyFlags(GameBaseImpl::HeaderFlags, GameHeaderFlags::ShowSplashScreen))
{
LOG(Info, "Loading splash screen");
if (GameBaseImpl::SplashScreenId)
GameBaseImpl::SplashScreen = Content::LoadAsync<Texture>(GameBaseImpl::SplashScreenId);
else
GameBaseImpl::SplashScreen = Content::LoadAsyncInternal<Texture>(TEXT("Engine/Textures/Logo"));
if (!GameBaseImpl::SplashScreen)
{
LOG(Error, "Missing splash screen texture!");
}
}
// Preload first scene asset data
const auto gameSettings = GameSettings::Get();
if (!gameSettings)
return true;
GameBaseImpl::FirstScene = gameSettings->FirstScene;
return false;
}
void GameBase::BeforeRun()
{
// Headless more case (no window)
if (Engine::IsHeadless())
{
GameBaseImpl::OnSplashScreenEnd();
return;
}
// Show game window
LOG(Info, "Showing game window");
ASSERT(Engine::MainWindow);
Engine::MainWindow->Show();
// Show splash screen if it should be used
if (GameBaseImpl::SplashScreen && GPUDevice::Instance && GPUDevice::Instance->GetRendererType() != RendererType::Null)
{
LOG(Info, "Showing splash screen");
if (MainRenderTask::Instance == nullptr)
Platform::Fatal(TEXT("Missing main rendering task object."));
GameBaseImpl::SplashScreenTime = 0;
MainRenderTask::Instance->PostRender.Bind(&GameBaseImpl::OnPostRender);
}
else
{
GameBaseImpl::OnSplashScreenEnd();
}
}
void GameBase::BeforeExit()
{
}
Window* GameBase::CreateMainWindow()
{
CreateWindowSettings settings;
settings.Title = *Globals::ProductName;
settings.AllowDragAndDrop = false;
settings.Fullscreen = true;
settings.HasSizingFrame = false;
settings.HasBorder = false;
settings.AllowMaximize = true;
settings.AllowMinimize = true;
settings.Size = Platform::GetDesktopSize();
settings.Position = Float2::Zero;
Game::InitMainWindowSettings(settings);
const auto window = Platform::CreateWindow(settings);
if (window)
{
window->Closed.Bind(&GameBaseImpl::OnMainWindowClosed);
}
return window;
}
void GameBaseImpl::OnMainWindowClosed()
{
const auto window = Engine::MainWindow;
if (!window)
return;
// Clear field (window is deleting itself)
Engine::MainWindow = nullptr;
// Request engine exit
Engine::RequestExit(0);
}
void GameBaseImpl::OnPostRender(GPUContext* context, RenderContext& renderContext)
{
// Handle missing splash screen texture case
if (SplashScreen == nullptr)
{
OnSplashScreenEnd();
return;
}
// Wait for texture loaded before showing splash screen
if (!SplashScreen->IsLoaded() || SplashScreen.Get()->GetTexture()->MipLevels() != SplashScreen.Get()->StreamingTexture()->TotalMipLevels())
{
return;
}
const auto splashScreen = SplashScreen.Get()->GetTexture();
// Configuration
const float fadeTime = 0.5f;
const float showTime = 1.0f;
const float totalTime = fadeTime + showTime + fadeTime;
// Update animation
const auto deltaTime = static_cast<float>(Time::Draw.UnscaledDeltaTime.GetTotalSeconds());
SplashScreenTime += deltaTime;
if (SplashScreenTime >= totalTime)
{
OnSplashScreenEnd();
return;
}
PROFILE_GPU_CPU_NAMED("Splash Screen");
// Calculate visibility
float fade = 1.0f;
if (SplashScreenTime < fadeTime)
fade = SplashScreenTime / fadeTime;
else if (SplashScreenTime > fadeTime + showTime)
fade = 1.0f - (SplashScreenTime - fadeTime - showTime) / fadeTime;
// Calculate image area (fill screen, keep aspect ratio, and snap to pixels)
const auto viewport = renderContext.Task->GetViewport();
const Rectangle screenRect(viewport.X, viewport.Y, viewport.Width, viewport.Height);
Rectangle imageArea = screenRect;
imageArea.Scale(0.6f);
const float aspectRatio = static_cast<float>(splashScreen->Width()) / static_cast<float>(splashScreen->Height());
const float height = imageArea.GetWidth() / aspectRatio;
imageArea.Location.Y += (imageArea.GetHeight() - height) * 0.5f;
imageArea.Size.Y = height;
imageArea.Location = Float2::Ceil(imageArea.Location);
imageArea.Size = Float2::Ceil(imageArea.Size);
// Draw
Render2D::Begin(GPUDevice::Instance->GetMainContext(), renderContext.Task->GetOutputView(), nullptr, viewport);
Render2D::FillRectangle(screenRect, Color::Black);
Render2D::DrawTexture(splashScreen, imageArea, Color(1.0f, 1.0f, 1.0f, fade));
Render2D::End();
}
void GameBaseImpl::OnSplashScreenEnd()
{
// Hide splash screen
SplashScreenTime = 0;
SplashScreen = nullptr;
MainRenderTask::Instance->PostRender.Unbind(&OnPostRender);
// Load the first scene
const auto sceneId = FirstScene ? FirstScene.GetID() : Guid::Empty;
LOG(Info, "Loading the first scene ({})", sceneId);
FirstScene = nullptr;
if (Level::LoadSceneAsync(sceneId))
{
LOG(Fatal, "Cannot load the first scene.");
return;
}
}
#endif