refactor: 将数学工具函数移至GameMath类 feat(音频): 实现地图音频控制器 feat(调试): 添加游戏调试UI组件 feat(地图): 增加移动区域边界获取方法 fix(角色): 修复角色移动区域抑制逻辑 refactor(世界): 重构游戏世界场景初始化 docs(音频): 完善音频数据库注释
295 lines
7.6 KiB
C++
295 lines
7.6 KiB
C++
#include "world/GameWorld.h"
|
|
#include "character/CharacterObject.h"
|
|
#include "map/GameDataLoader.h"
|
|
#include "scene/GameDebugUIScene.h"
|
|
#include <SDL2/SDL.h>
|
|
#include <frostbite2D/scene/scene_manager.h>
|
|
|
|
namespace frostbite2D {
|
|
|
|
namespace {
|
|
|
|
constexpr int kDefaultTownId = 1;
|
|
|
|
Vec2 ComputeMoveAreaCenter(const game::MoveAreaBounds& bounds) {
|
|
return Vec2((bounds.leftTop.x + bounds.rightBottom.x) * 0.5f,
|
|
(bounds.leftTop.y + bounds.rightBottom.y) * 0.5f);
|
|
}
|
|
|
|
void SetActorGroundPosition(RefPtr<Actor> actor, const Vec2& position) {
|
|
if (!actor) {
|
|
return;
|
|
}
|
|
|
|
if (auto* character = dynamic_cast<CharacterObject*>(actor.Get())) {
|
|
character->SetCharacterPosition(position);
|
|
return;
|
|
}
|
|
|
|
actor->SetPosition(position);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
GameWorld::GameWorld() = default;
|
|
|
|
void GameWorld::onEnter() {
|
|
Scene::onEnter();
|
|
EnsureDebugScene();
|
|
|
|
if (!initialized_) {
|
|
initialized_ = InitWorld();
|
|
}
|
|
|
|
RefreshDebugContext();
|
|
}
|
|
|
|
void GameWorld::onExit() {
|
|
pendingCharacterMove_.reset();
|
|
ClearSuppressedMoveArea();
|
|
if (debugScene_) {
|
|
debugScene_->ClearDebugContext();
|
|
SceneManager::get().RemoveUIScene(debugScene_.Get());
|
|
}
|
|
|
|
Scene::onExit();
|
|
}
|
|
|
|
void GameWorld::Update(float deltaTime) {
|
|
Scene::Update(deltaTime);
|
|
ProcessPendingCharacterMove();
|
|
}
|
|
|
|
bool GameWorld::InitWorld() {
|
|
RemoveAllChildren();
|
|
townPathMap_.clear();
|
|
townMap_.clear();
|
|
mainActor_.Reset();
|
|
pendingCharacterMove_.reset();
|
|
ClearSuppressedMoveArea();
|
|
curTown_ = -1;
|
|
|
|
townPathMap_ = game::loadTownList();
|
|
if (townPathMap_.empty()) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: no town entries found");
|
|
return false;
|
|
}
|
|
|
|
for (const auto& [townId, townPath] : townPathMap_) {
|
|
auto town = MakePtr<GameTown>();
|
|
if (!town->Init(townId, townPath)) {
|
|
continue;
|
|
}
|
|
townMap_[townId] = town;
|
|
}
|
|
|
|
if (townMap_.empty()) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: failed to create any town");
|
|
return false;
|
|
}
|
|
|
|
int defaultTownId = kDefaultTownId;
|
|
if (townMap_.find(defaultTownId) == townMap_.end()) {
|
|
defaultTownId = townMap_.begin()->first;
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
"GameWorld: default town %d missing, fallback to town %d",
|
|
kDefaultTownId, defaultTownId);
|
|
}
|
|
|
|
if (!InitMainCharacter(defaultTownId)) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
"GameWorld: falling back to empty town %d without main character",
|
|
defaultTownId);
|
|
AddCharacter(nullptr, defaultTownId);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GameWorld::InitMainCharacter(int townId) {
|
|
auto mainCharacter = MakePtr<CharacterObject>();
|
|
if (!mainCharacter->Construction(0)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
"GameWorld: failed to construct default main character");
|
|
return false;
|
|
}
|
|
|
|
mainCharacter->EnableEventReceive();
|
|
mainCharacter->SetEventPriority(-100);
|
|
mainCharacter->SetInputEnabled(true);
|
|
AddCharacter(mainCharacter, townId);
|
|
return true;
|
|
}
|
|
|
|
void GameWorld::AddCharacter(RefPtr<Actor> actor, int townId) {
|
|
auto it = townMap_.find(townId);
|
|
if (it == townMap_.end()) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: town %d not found", townId);
|
|
return;
|
|
}
|
|
|
|
mainActor_ = actor;
|
|
curTown_ = townId;
|
|
it->second->AddCharacter(actor);
|
|
AddChild(it->second);
|
|
RefreshDebugContext();
|
|
}
|
|
|
|
void GameWorld::MoveCharacter(RefPtr<Actor> actor, int townId, int area) {
|
|
auto townIt = townMap_.find(townId);
|
|
if (townIt == townMap_.end()) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: target town %d missing", townId);
|
|
return;
|
|
}
|
|
|
|
ClearSuppressedMoveArea();
|
|
|
|
int sourceTownId = curTown_;
|
|
int sourceAreaId = -1;
|
|
if (curTown_ != -1) {
|
|
auto currentTown = townMap_.find(curTown_);
|
|
if (currentTown != townMap_.end()) {
|
|
sourceAreaId = currentTown->second->GetCurAreaIndex();
|
|
}
|
|
}
|
|
|
|
RefPtr<GameMap> targetMap = townIt->second->GetArea(area);
|
|
if (actor && targetMap && sourceTownId != -1 && sourceAreaId != -1) {
|
|
bool foundTransitionEntry = false;
|
|
const auto& moveAreaInfo = targetMap->GetMoveAreaInfo();
|
|
for (size_t i = 0; i < moveAreaInfo.size(); ++i) {
|
|
if (moveAreaInfo[i].town != sourceTownId || moveAreaInfo[i].area != sourceAreaId) {
|
|
continue;
|
|
}
|
|
|
|
game::MoveAreaBounds bounds = targetMap->GetMovablePositionBounds(i);
|
|
SetActorGroundPosition(actor, ComputeMoveAreaCenter(bounds));
|
|
SetSuppressedMoveArea(targetMap.Get(), i);
|
|
foundTransitionEntry = true;
|
|
break;
|
|
}
|
|
|
|
if (!foundTransitionEntry) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
"GameWorld: no matching move area in town %d area %d for source town %d area %d",
|
|
townId, area, sourceTownId, sourceAreaId);
|
|
}
|
|
} else if (actor && !targetMap) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
"GameWorld: target area %d missing in town %d, use town fallback spawn",
|
|
area, townId);
|
|
}
|
|
|
|
if (curTown_ != -1) {
|
|
auto currentTown = townMap_.find(curTown_);
|
|
if (currentTown != townMap_.end()) {
|
|
RemoveChild(currentTown->second);
|
|
}
|
|
}
|
|
|
|
curTown_ = townId;
|
|
mainActor_ = actor;
|
|
townIt->second->AddCharacter(actor, area);
|
|
AddChild(townIt->second);
|
|
RefreshDebugContext();
|
|
}
|
|
|
|
void GameWorld::RequestMoveCharacter(RefPtr<Actor> actor, int townId, int area) {
|
|
if (!actor) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
"GameWorld: ignore move request without actor");
|
|
return;
|
|
}
|
|
|
|
pendingCharacterMove_ = PendingCharacterMove{actor, townId, area};
|
|
}
|
|
|
|
void GameWorld::UpdateMoveAreaSuppression(GameMap* map, size_t moveAreaIndex) {
|
|
if (!suppressedMoveArea_) {
|
|
return;
|
|
}
|
|
|
|
if (suppressedMoveArea_->map != map ||
|
|
suppressedMoveArea_->moveAreaIndex != moveAreaIndex) {
|
|
ClearSuppressedMoveArea();
|
|
}
|
|
}
|
|
|
|
bool GameWorld::ShouldSuppressMoveArea(GameMap* map, size_t moveAreaIndex) const {
|
|
return suppressedMoveArea_ && suppressedMoveArea_->map == map &&
|
|
suppressedMoveArea_->moveAreaIndex == moveAreaIndex;
|
|
}
|
|
|
|
void GameWorld::ProcessPendingCharacterMove() {
|
|
if (!pendingCharacterMove_) {
|
|
return;
|
|
}
|
|
|
|
PendingCharacterMove move = *pendingCharacterMove_;
|
|
pendingCharacterMove_.reset();
|
|
MoveCharacter(move.actor, move.townId, move.area);
|
|
}
|
|
|
|
void GameWorld::EnsureDebugScene() {
|
|
if (!debugScene_) {
|
|
debugScene_ = MakePtr<GameDebugUIScene>();
|
|
}
|
|
|
|
SceneManager::get().RemoveUIScene(debugScene_.Get());
|
|
SceneManager::get().PushUIScene(debugScene_);
|
|
}
|
|
|
|
void GameWorld::RefreshDebugContext() {
|
|
if (!debugScene_) {
|
|
return;
|
|
}
|
|
|
|
debugScene_->SetDebugContext(GetCurrentMap(), GetMainCharacter());
|
|
}
|
|
|
|
void GameWorld::SetSuppressedMoveArea(GameMap* map, size_t moveAreaIndex) {
|
|
if (!map || moveAreaIndex == GameMap::kInvalidMoveAreaIndex) {
|
|
ClearSuppressedMoveArea();
|
|
return;
|
|
}
|
|
|
|
suppressedMoveArea_ = SuppressedMoveArea{map, moveAreaIndex};
|
|
}
|
|
|
|
void GameWorld::ClearSuppressedMoveArea() {
|
|
suppressedMoveArea_.reset();
|
|
}
|
|
|
|
Actor* GameWorld::GetMainActor() const {
|
|
return mainActor_.Get();
|
|
}
|
|
|
|
CharacterObject* GameWorld::GetMainCharacter() const {
|
|
return dynamic_cast<CharacterObject*>(mainActor_.Get());
|
|
}
|
|
|
|
GameTown* GameWorld::GetCurrentTown() const {
|
|
auto it = townMap_.find(curTown_);
|
|
if (it == townMap_.end()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return it->second.Get();
|
|
}
|
|
|
|
GameMap* GameWorld::GetCurrentMap() const {
|
|
GameTown* town = GetCurrentTown();
|
|
if (!town) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<GameMap> area = town->GetCurrentArea();
|
|
return area.Get();
|
|
}
|
|
|
|
GameWorld* GameWorld::GetWorld() {
|
|
return dynamic_cast<GameWorld*>(SceneManager::get().GetCurrentScene());
|
|
}
|
|
|
|
} // namespace frostbite2D
|