Wire engine feature services into game skeleton

This commit is contained in:
2026-06-09 22:58:42 +08:00
parent d353e91f5e
commit d41f9d1096
30 changed files with 680 additions and 77 deletions
+54 -44
View File
@@ -1,6 +1,7 @@
#include "player_actor.h"
#include "../core/asset_paths.h"
#include "../core/game_audio.h"
#include "../core/game_config.h"
#include <algorithm>
@@ -8,13 +9,6 @@
namespace ns_game {
namespace {
constexpr float kSpriteOffsetX = -32.0f;
constexpr float kSpriteOffsetY = -24.0f;
} // namespace
PlayerActor::PlayerActor() {
SetSize(64.0f, 104.0f);
SetTopLeftPosition(160.0f, 380.0f);
@@ -26,29 +20,29 @@ void PlayerActor::SetMovementWorld(const PlatformWorld* world) {
}
void PlayerActor::loadSprites() {
idleSprite_ = frostbite2D::Sprite::createFromFile(assets::kMaleIdle);
runSprite_ = frostbite2D::Sprite::createFromFile(assets::kMaleRun);
punchSprite_ = frostbite2D::Sprite::createFromFile(assets::kMalePunch);
idleAsset_ = LoadPlayerAnimationAsset(assets::kMaleIdleAni,
assets::kMaleIdleImg,
assets::kMaleIdle, 0);
runAsset_ = LoadPlayerAnimationAsset(assets::kMaleRunAni, assets::kMaleRunImg,
assets::kMaleRun, 0);
punchAsset_ = LoadPlayerAnimationAsset(assets::kMalePunchAni,
assets::kMalePunchImg,
assets::kMalePunch, 0);
auto configure = [this](frostbite2D::Ptr<frostbite2D::Sprite> sprite) {
if (!sprite) {
return;
}
sprite->SetSize(config::kPlayerFrameSize, config::kPlayerFrameSize);
sprite->SetSourceRect(frostbite2D::Rect(0.0f, 0.0f,
config::kPlayerFrameSize,
config::kPlayerFrameSize));
sprite->SetTopLeftPosition(kSpriteOffsetX, kSpriteOffsetY);
sprite->SetVisible(false);
AddChild(sprite);
};
configure(idleSprite_);
configure(runSprite_);
configure(punchSprite_);
addAnimationAsset(idleAsset_);
addAnimationAsset(runAsset_);
addAnimationAsset(punchAsset_);
setAnimState(AnimState::Idle);
}
void PlayerActor::addAnimationAsset(PlayerAnimationAsset& asset) {
if (asset.animation) {
AddChild(asset.animation);
} else if (asset.sprite) {
AddChild(asset.sprite);
}
}
void PlayerActor::OnUpdate(float deltaTime) {
deltaTime = std::min(deltaTime, 1.0f / 30.0f);
updateInput();
@@ -68,11 +62,13 @@ void PlayerActor::updateInput() {
if (input.jumpPressed && grounded_) {
velocity_.y = config::kPlayerJumpSpeed;
grounded_ = false;
GameAudio::Get().PlaySoundCue("player_jump");
}
if (input.attackPressed) {
attackTimer_ = config::kAttackDuration;
setAnimState(AnimState::Punch);
GameAudio::Get().PlaySoundCue("player_attack");
}
}
@@ -117,43 +113,57 @@ void PlayerActor::updateAnimation(float deltaTime) {
frameIndex_ = (frameIndex_ + 1) % frameCount;
}
auto sprite = activeSprite();
if (sprite) {
sprite->SetSourceRect(
PlayerAnimationAsset* asset = activeAsset();
if (!asset) {
return;
}
if (asset->animation) {
asset->animation->SetDirection(facingLeft_ ? -1 : 1);
}
if (asset->sprite) {
asset->sprite->SetSourceRect(
frostbite2D::Rect(frameIndex_ * config::kPlayerFrameSize, 0.0f,
config::kPlayerFrameSize,
config::kPlayerFrameSize));
sprite->SetFlippedX(facingLeft_);
asset->sprite->SetFlippedX(facingLeft_);
}
}
void PlayerActor::setAnimState(AnimState state) {
if (animState_ != state) {
const bool stateChanged = animState_ != state;
if (stateChanged) {
animState_ = state;
animTimer_ = 0.0f;
frameIndex_ = 0;
}
if (idleSprite_) {
idleSprite_->SetVisible(state == AnimState::Idle);
}
if (runSprite_) {
runSprite_->SetVisible(state == AnimState::Run);
}
if (punchSprite_) {
punchSprite_->SetVisible(state == AnimState::Punch);
}
auto setVisible = [stateChanged](PlayerAnimationAsset& asset, bool visible) {
if (asset.animation) {
asset.animation->SetVisible(visible);
if (visible && stateChanged) {
asset.animation->Reset();
}
}
if (asset.sprite) {
asset.sprite->SetVisible(visible);
}
};
setVisible(idleAsset_, state == AnimState::Idle);
setVisible(runAsset_, state == AnimState::Run);
setVisible(punchAsset_, state == AnimState::Punch);
}
frostbite2D::Ptr<frostbite2D::Sprite> PlayerActor::activeSprite() const {
PlayerAnimationAsset* PlayerActor::activeAsset() {
switch (animState_) {
case AnimState::Run:
return runSprite_;
return &runAsset_;
case AnimState::Punch:
return punchSprite_;
return &punchAsset_;
case AnimState::Idle:
default:
return idleSprite_;
return &idleAsset_;
}
}
+6 -5
View File
@@ -2,9 +2,9 @@
#include "../core/input_state.h"
#include "../movement/platform_world.h"
#include "player_animation_assets.h"
#include <frostbite2D/2d/actor.h>
#include <frostbite2D/2d/sprite.h>
#include <frostbite2D/types/type_math.h>
namespace ns_game {
@@ -24,18 +24,19 @@ private:
};
void loadSprites();
void addAnimationAsset(PlayerAnimationAsset& asset);
void updateInput();
void updatePhysics(float deltaTime);
void updateAnimation(float deltaTime);
void setAnimState(AnimState state);
frostbite2D::Ptr<frostbite2D::Sprite> activeSprite() const;
PlayerAnimationAsset* activeAsset();
const PlatformWorld* movementWorld_ = nullptr;
InputStateProvider inputProvider_;
PlatformMover platformMover_;
frostbite2D::Ptr<frostbite2D::Sprite> idleSprite_;
frostbite2D::Ptr<frostbite2D::Sprite> runSprite_;
frostbite2D::Ptr<frostbite2D::Sprite> punchSprite_;
PlayerAnimationAsset idleAsset_;
PlayerAnimationAsset runAsset_;
PlayerAnimationAsset punchAsset_;
frostbite2D::Vec2 velocity_;
AnimState animState_ = AnimState::Idle;
@@ -0,0 +1,64 @@
#include "player_animation_assets.h"
#include <frostbite2D/resource/asset.h>
#include <frostbite2D/resource/npk_archive.h>
#include <frostbite2D/resource/pvf_archive.h>
#include <SDL2/SDL.h>
namespace ns_game {
namespace {
constexpr float kSpriteOffsetX = -32.0f;
constexpr float kSpriteOffsetY = -24.0f;
} // namespace
PlayerAnimationAsset LoadPlayerAnimationAsset(const std::string& aniPath,
const std::string& npkImgPath,
const std::string& pngPath,
int zOrder) {
PlayerAnimationAsset asset;
frostbite2D::PvfArchive& pvf = frostbite2D::PvfArchive::get();
if (pvf.isOpen() && pvf.hasFile(aniPath)) {
asset.animation = frostbite2D::MakePtr<frostbite2D::Animation>(aniPath);
if (asset.animation && asset.animation->IsUsable()) {
asset.animation->SetTopLeftPosition(kSpriteOffsetX, kSpriteOffsetY);
asset.animation->SetVisible(false);
asset.animation->SetZOrder(zOrder);
return asset;
}
asset.animation = nullptr;
}
frostbite2D::NpkArchive& npk = frostbite2D::NpkArchive::get();
if (npk.isOpen() && npk.hasImg(npkImgPath)) {
asset.sprite = frostbite2D::Sprite::createFromNpk(npkImgPath, 0);
}
if (!asset.sprite && frostbite2D::Asset::get().exists(pngPath)) {
asset.sprite = frostbite2D::Sprite::createFromFile(pngPath);
}
ConfigurePlayerSpriteFallback(asset.sprite);
if (asset.sprite) {
asset.sprite->SetVisible(false);
asset.sprite->SetZOrder(zOrder);
}
return asset;
}
void ConfigurePlayerSpriteFallback(frostbite2D::Ptr<frostbite2D::Sprite> sprite) {
if (!sprite) {
return;
}
sprite->SetSize(config::kPlayerFrameSize, config::kPlayerFrameSize);
sprite->SetSourceRect(frostbite2D::Rect(0.0f, 0.0f,
config::kPlayerFrameSize,
config::kPlayerFrameSize));
sprite->SetTopLeftPosition(kSpriteOffsetX, kSpriteOffsetY);
}
} // namespace ns_game
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "../core/game_config.h"
#include <frostbite2D/2d/sprite.h>
#include <frostbite2D/animation/animation.h>
#include <string>
namespace ns_game {
struct PlayerAnimationAsset {
frostbite2D::Ptr<frostbite2D::Animation> animation;
frostbite2D::Ptr<frostbite2D::Sprite> sprite;
bool usesEngineAnimation() const { return animation != nullptr; }
};
PlayerAnimationAsset LoadPlayerAnimationAsset(const std::string& aniPath,
const std::string& npkImgPath,
const std::string& pngPath,
int zOrder);
void ConfigurePlayerSpriteFallback(frostbite2D::Ptr<frostbite2D::Sprite> sprite);
} // namespace ns_game
+8
View File
@@ -9,4 +9,12 @@ constexpr const char* kMaleRun =
constexpr const char* kMalePunch =
"assets/character/male/Male_spritesheet_punch_1.png";
constexpr const char* kMaleIdleAni = "character/male/idle.ani";
constexpr const char* kMaleRunAni = "character/male/run.ani";
constexpr const char* kMalePunchAni = "character/male/punch_1.ani";
constexpr const char* kMaleIdleImg = "sprite/character/male/idle.img";
constexpr const char* kMaleRunImg = "sprite/character/male/run.img";
constexpr const char* kMalePunchImg = "sprite/character/male/punch_1.img";
} // namespace ns_game::assets
+99
View File
@@ -0,0 +1,99 @@
#include "game_audio.h"
#include "game_services.h"
#include <frostbite2D/audio/audio_system.h>
#include <frostbite2D/resource/asset.h>
#include <frostbite2D/resource/audio_database.h>
#include <SDL2/SDL.h>
namespace ns_game {
GameAudio& GameAudio::Get() {
static GameAudio audio;
return audio;
}
void GameAudio::PlaySoundCue(const std::string& cueId) {
if (!frostbite2D::AudioSystem::get().isInitialized()) {
return;
}
const std::string path = ResolveAudioPath(cueId);
if (path.empty()) {
return;
}
auto cached = soundCache_.find(path);
if (cached == soundCache_.end()) {
frostbite2D::Ptr<frostbite2D::Sound> sound = LoadSound(path);
if (!sound) {
return;
}
cached = soundCache_.emplace(path, sound).first;
}
cached->second->play();
}
void GameAudio::PlayMusicCue(const std::string& cueId) {
if (!frostbite2D::AudioSystem::get().isInitialized()) {
return;
}
const std::string path = ResolveAudioPath(cueId);
if (path.empty() || frostbite2D::Music::isPathPlaying(path)) {
return;
}
frostbite2D::Ptr<frostbite2D::Music> music = LoadMusic(path);
if (!music) {
return;
}
currentMusic_ = music;
currentMusic_->fadeIn(250);
}
frostbite2D::Ptr<frostbite2D::Sound>
GameAudio::LoadSound(const std::string& path) const {
frostbite2D::Asset& asset = frostbite2D::Asset::get();
if (path.rfind("assets/", 0) == 0 && asset.exists(path)) {
return frostbite2D::Sound::loadFromFile(path);
}
return frostbite2D::Sound::loadFromPath(path);
}
frostbite2D::Ptr<frostbite2D::Music>
GameAudio::LoadMusic(const std::string& path) const {
frostbite2D::Asset& asset = frostbite2D::Asset::get();
if (path.rfind("assets/", 0) == 0 && asset.exists(path)) {
return frostbite2D::Music::loadFromFile(path);
}
return frostbite2D::Music::loadFromPath(path);
}
std::string GameAudio::ResolveAudioPath(const std::string& cueId) const {
frostbite2D::AudioDatabase& database = frostbite2D::AudioDatabase::get();
if (database.isLoaded()) {
if (auto path = database.getFilePath(cueId); path && !path->empty()) {
return *path;
}
}
frostbite2D::Asset& asset = frostbite2D::Asset::get();
const std::string soundPath = "assets/sounds/" + cueId + ".wav";
if (asset.exists(soundPath)) {
return soundPath;
}
const std::string musicPath = "assets/music/" + cueId + ".ogg";
if (asset.exists(musicPath)) {
return musicPath;
}
return {};
}
} // namespace ns_game
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <frostbite2D/audio/music.h>
#include <frostbite2D/audio/sound.h>
#include <string>
#include <unordered_map>
namespace ns_game {
class GameAudio {
public:
static GameAudio& Get();
void PlaySoundCue(const std::string& cueId);
void PlayMusicCue(const std::string& cueId);
private:
GameAudio() = default;
frostbite2D::Ptr<frostbite2D::Sound> LoadSound(const std::string& path) const;
frostbite2D::Ptr<frostbite2D::Music> LoadMusic(const std::string& path) const;
std::string ResolveAudioPath(const std::string& cueId) const;
std::unordered_map<std::string, frostbite2D::Ptr<frostbite2D::Sound>>
soundCache_;
frostbite2D::Ptr<frostbite2D::Music> currentMusic_;
};
} // namespace ns_game
+70
View File
@@ -0,0 +1,70 @@
#include "game_save.h"
#include <frostbite2D/resource/save_system.h>
#include <SDL2/SDL.h>
#include <json/json.hpp>
namespace ns_game {
namespace {
constexpr int kDebugSlot = 0;
constexpr int kSchemaVersion = 1;
nlohmann::json toJson(const DebugFlags& flags) {
return {
{"schema", kSchemaVersion},
{"debug", {
{"enabled", flags.enabled},
{"showCollision", flags.showCollision},
{"showStageGrid", flags.showStageGrid},
{"showActorBounds", flags.showActorBounds},
{"showBattleZones", flags.showBattleZones},
{"showSpawnPoints", flags.showSpawnPoints},
}},
};
}
DebugFlags fromJson(const nlohmann::json& json) {
DebugFlags flags;
const nlohmann::json debug = json.value("debug", nlohmann::json::object());
flags.enabled = debug.value("enabled", flags.enabled);
flags.showCollision = debug.value("showCollision", flags.showCollision);
flags.showStageGrid = debug.value("showStageGrid", flags.showStageGrid);
flags.showActorBounds = debug.value("showActorBounds", flags.showActorBounds);
flags.showBattleZones =
debug.value("showBattleZones", flags.showBattleZones);
flags.showSpawnPoints =
debug.value("showSpawnPoints", flags.showSpawnPoints);
return flags;
}
} // namespace
GameSave& GameSave::Get() {
static GameSave save;
return save;
}
void GameSave::SaveDebugFlags(const DebugFlags& flags) {
frostbite2D::SaveMeta meta;
meta.displayName = "Debug Flags";
meta.userTag = "debug";
if (!frostbite2D::SaveSystem::get().saveSlot(kDebugSlot, toJson(flags),
meta)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"GameSave: failed to save debug flags");
}
}
std::optional<DebugFlags> GameSave::LoadDebugFlags() const {
const std::optional<nlohmann::json> payload =
frostbite2D::SaveSystem::get().loadSlotPayload(kDebugSlot);
if (!payload || !payload->is_object()) {
return std::nullopt;
}
return fromJson(*payload);
}
} // namespace ns_game
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "../core/debug_flags.h"
#include <optional>
namespace ns_game {
class GameSave {
public:
static GameSave& Get();
void SaveDebugFlags(const DebugFlags& flags);
std::optional<DebugFlags> LoadDebugFlags() const;
private:
GameSave() = default;
};
} // namespace ns_game
+23 -1
View File
@@ -1,6 +1,7 @@
#include "game_services.h"
#include <frostbite2D/audio/audio_system.h>
#include <frostbite2D/graphics/font_manager.h>
#include <frostbite2D/resource/asset.h>
#include <frostbite2D/resource/audio_database.h>
#include <frostbite2D/resource/npk_archive.h>
@@ -42,14 +43,17 @@ void GameServices::Initialize() {
status_.saveInitialized = frostbite2D::SaveSystem::get().listSlots().size() > 0;
initializeAudio();
initializeFonts();
initializePvfArchive();
initializeNpkArchive();
initializeSoundPackArchive();
initializeAudioDatabase();
SDL_Log("GameServices: audio=%d save=%d pvf=%d npk=%d soundpack=%d audiodb=%d",
SDL_Log("GameServices: audio=%d save=%d font=%d debugfont=%d pvf=%d npk=%d soundpack=%d audiodb=%d",
status_.audioInitialized ? 1 : 0,
status_.saveInitialized ? 1 : 0,
status_.fontInitialized ? 1 : 0,
status_.debugFontLoaded ? 1 : 0,
status_.pvfArchiveOpen ? 1 : 0,
status_.npkArchiveOpen ? 1 : 0,
status_.soundPackOpen ? 1 : 0,
@@ -63,6 +67,24 @@ void GameServices::initializeAudio() {
status_.audioInitialized = frostbite2D::AudioSystem::get().init(config);
}
void GameServices::initializeFonts() {
frostbite2D::FontManager& fontManager = frostbite2D::FontManager::get();
status_.fontInitialized = fontManager.init();
if (!status_.fontInitialized) {
return;
}
const char* debugFontPath =
firstExistingPath("assets/fonts/debug.ttf", "assets/fonts/ui.ttf");
if (!debugFontPath) {
SDL_Log("GameServices: optional debug font not found");
return;
}
status_.debugFontLoaded =
fontManager.registerFont("debug", debugFontPath, 18);
}
void GameServices::initializePvfArchive() {
const char* pvfPath = firstExistingPath("Script.pvf", "assets/Script.pvf");
if (!pvfPath) {
+3
View File
@@ -5,6 +5,8 @@ namespace ns_game {
struct GameServiceStatus {
bool audioInitialized = false;
bool saveInitialized = false;
bool fontInitialized = false;
bool debugFontLoaded = false;
bool pvfArchiveOpen = false;
bool npkArchiveOpen = false;
bool soundPackOpen = false;
@@ -25,6 +27,7 @@ private:
GameServices() = default;
void initializeAudio();
void initializeFonts();
void initializePvfArchive();
void initializeNpkArchive();
void initializeSoundPackArchive();
+35
View File
@@ -1,11 +1,15 @@
#include "whitebox_scene.h"
#include "../core/game_audio.h"
#include "../core/game_config.h"
#include "../core/game_save.h"
#include "../core/game_services.h"
#include <frostbite2D/core/application.h>
#include <frostbite2D/event/key_event.h>
#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/renderer.h>
#include <frostbite2D/scene/scene_manager.h>
#include <algorithm>
namespace ns_game {
@@ -13,6 +17,9 @@ namespace ns_game {
void WhiteboxScene::onEnter() {
frostbite2D::Scene::onEnter();
level_ = CreateWhiteboxLevel();
if (auto savedDebugFlags = GameSave::Get().LoadDebugFlags()) {
debugFlags_ = *savedDebugFlags;
}
battleZoneStates_.clear();
battleZoneStates_.resize(level_.battleZones.size());
activeBattleZoneIndex_ = -1;
@@ -22,6 +29,11 @@ void WhiteboxScene::onEnter() {
player_->SetMovementWorld(&level_.collision);
AddChild(player_);
configureCamera();
uiOverlay_ = frostbite2D::MakePtr<GameUiOverlay>();
frostbite2D::SceneManager::get().ReplaceUIScene(uiOverlay_);
updateUiOverlay();
GameAudio::Get().PlayMusicCue("stage_01");
}
void WhiteboxScene::Update(float deltaTime) {
@@ -29,6 +41,7 @@ void WhiteboxScene::Update(float deltaTime) {
updateBattleZones();
updateCameraBoundsBlend(deltaTime);
updateCamera(deltaTime);
updateUiOverlay();
}
bool WhiteboxScene::OnEvent(const frostbite2D::Event& event) {
@@ -107,15 +120,19 @@ bool WhiteboxScene::handleDebugKey(frostbite2D::KeyCode keyCode) {
case frostbite2D::KeyCode::F3:
debugFlags_.showCollision = !debugFlags_.showCollision;
debugFlags_.showStageGrid = debugFlags_.showCollision;
persistDebugFlags();
return true;
case frostbite2D::KeyCode::F4:
debugFlags_.showActorBounds = !debugFlags_.showActorBounds;
persistDebugFlags();
return true;
case frostbite2D::KeyCode::F5:
debugFlags_.showBattleZones = !debugFlags_.showBattleZones;
persistDebugFlags();
return true;
case frostbite2D::KeyCode::F6:
debugFlags_.showSpawnPoints = !debugFlags_.showSpawnPoints;
persistDebugFlags();
return true;
default:
return false;
@@ -124,6 +141,7 @@ bool WhiteboxScene::handleDebugKey(frostbite2D::KeyCode keyCode) {
void WhiteboxScene::toggleDebugOverlay() {
debugFlags_.enabled = !debugFlags_.enabled;
persistDebugFlags();
}
void WhiteboxScene::beginCameraBoundsBlend(
@@ -197,6 +215,7 @@ void WhiteboxScene::updateBattleZones() {
runtime.triggered = true;
activeBattleZoneIndex_ = static_cast<int>(i);
beginCameraBoundsBlend(level_.battleZones[i].cameraBounds);
GameAudio::Get().PlaySoundCue("battle_zone_enter");
break;
}
}
@@ -214,4 +233,20 @@ void WhiteboxScene::updateCamera(float deltaTime) {
cameraController_.Update(*camera, player_->GetWorldBounds(), deltaTime);
}
void WhiteboxScene::updateUiOverlay() {
if (!uiOverlay_) {
return;
}
GameUiOverlayState state;
state.debugFlags = debugFlags_;
state.services = GameServices::Get().Status();
state.activeBattleZoneIndex = activeBattleZoneIndex_;
uiOverlay_->SetState(state);
}
void WhiteboxScene::persistDebugFlags() {
GameSave::Get().SaveDebugFlags(debugFlags_);
}
} // namespace ns_game
+4
View File
@@ -4,6 +4,7 @@
#include "../core/debug_flags.h"
#include "../data/level_definition.h"
#include "../debug/debug_overlay.h"
#include "../ui/game_ui_overlay.h"
#include "camera_controller.h"
#include "../stage/stage_renderer.h"
@@ -37,6 +38,8 @@ private:
void updateCameraBoundsBlend(float deltaTime);
void updateBattleZones();
void updateCamera(float deltaTime);
void updateUiOverlay();
void persistDebugFlags();
LevelDefinition level_;
DebugFlags debugFlags_;
@@ -51,6 +54,7 @@ private:
CameraController cameraController_;
StageRenderer stageRenderer_;
DebugOverlay debugOverlay_;
frostbite2D::Ptr<GameUiOverlay> uiOverlay_;
frostbite2D::Ptr<PlayerActor> player_;
};
+77
View File
@@ -0,0 +1,77 @@
#include "game_ui_overlay.h"
#include "../core/game_config.h"
#include <frostbite2D/graphics/font_manager.h>
#include <frostbite2D/graphics/renderer.h>
#include <sstream>
namespace ns_game {
void GameUiOverlay::onEnter() {
frostbite2D::UIScene::onEnter();
if (frostbite2D::FontManager::get().hasFont("debug")) {
statusText_ = frostbite2D::TextSprite::create();
statusText_->SetFont("debug");
statusText_->SetTextColor(0.88f, 0.94f, 1.0f, 1.0f);
statusText_->SetTopLeftPosition(16.0f, 14.0f);
statusText_->SetZOrder(10);
AddChild(statusText_);
textReady_ = true;
}
}
void GameUiOverlay::SetState(const GameUiOverlayState& state) {
state_ = state;
if (textReady_ && statusText_) {
statusText_->SetText(BuildStatusText());
}
}
void GameUiOverlay::Render() {
if (!textReady_) {
RenderFallbackPanel();
}
frostbite2D::UIScene::Render();
}
std::string GameUiOverlay::BuildStatusText() const {
std::ostringstream text;
text << "NS Unknown Game\n";
text << "Debug: " << (state_.debugFlags.enabled ? "on" : "off")
<< " Zone: " << state_.activeBattleZoneIndex << "\n";
text << "Audio " << (state_.services.audioInitialized ? "on" : "off")
<< " Save " << (state_.services.saveInitialized ? "on" : "off")
<< " PVF " << (state_.services.pvfArchiveOpen ? "on" : "off")
<< " NPK " << (state_.services.npkArchiveOpen ? "on" : "off")
<< " AudioDB "
<< (state_.services.audioDatabaseLoaded ? "on" : "off");
return text.str();
}
void GameUiOverlay::RenderFallbackPanel() const {
frostbite2D::Renderer& renderer = frostbite2D::Renderer::get();
renderer.drawQuad(frostbite2D::Rect(12.0f, 12.0f, 250.0f, 54.0f),
frostbite2D::Color(0.04f, 0.05f, 0.06f, 0.75f));
const float serviceCount =
(state_.services.audioInitialized ? 1.0f : 0.0f) +
(state_.services.saveInitialized ? 1.0f : 0.0f) +
(state_.services.pvfArchiveOpen ? 1.0f : 0.0f) +
(state_.services.npkArchiveOpen ? 1.0f : 0.0f) +
(state_.services.audioDatabaseLoaded ? 1.0f : 0.0f);
renderer.drawQuad(frostbite2D::Rect(20.0f, 24.0f, serviceCount * 38.0f,
10.0f),
frostbite2D::Color(0.25f, 0.72f, 0.94f, 0.95f));
renderer.drawQuad(frostbite2D::Rect(20.0f, 44.0f,
state_.debugFlags.enabled ? 180.0f
: 52.0f,
10.0f),
state_.debugFlags.enabled
? frostbite2D::Color(0.95f, 0.78f, 0.25f, 0.95f)
: frostbite2D::Color(0.45f, 0.48f, 0.52f, 0.8f));
}
} // namespace ns_game
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include "../core/debug_flags.h"
#include "../core/game_services.h"
#include <frostbite2D/2d/text_sprite.h>
#include <frostbite2D/scene/ui_scene.h>
#include <string>
namespace ns_game {
struct GameUiOverlayState {
DebugFlags debugFlags;
GameServiceStatus services;
int activeBattleZoneIndex = -1;
};
class GameUiOverlay : public frostbite2D::UIScene {
public:
void onEnter() override;
void SetState(const GameUiOverlayState& state);
void Render() override;
private:
std::string BuildStatusText() const;
void RenderFallbackPanel() const;
GameUiOverlayState state_;
frostbite2D::Ptr<frostbite2D::TextSprite> statusText_;
bool textReady_ = false;
};
} // namespace ns_game