Wire engine feature services into game skeleton
This commit is contained in:
@@ -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_;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user