feat(场景管理): 添加UIScene支持并重构场景管理器

refactor(渲染器): 优化相机切换时的渲染批处理

feat(调试工具): 新增游戏调试UI场景和九宫格面板组件

fix(动画系统): 跳过缺失的动画资源加载并记录日志

perf(资源加载): 使用BinaryFileStreamReader优化NPK文件解析

feat(地图系统): 支持多边形可行走区域调试显示

style(代码格式): 清理多余空格和统一文件编码

docs(注释): 补充关键类和方法的文档说明

test(启动跟踪): 添加启动过程性能跟踪工具

chore(依赖): 添加SDL2_ttf库支持
This commit is contained in:
2026-04-06 01:18:21 +08:00
parent 6cd1b42fef
commit bcc285eed6
36 changed files with 2675 additions and 513 deletions

View File

@@ -1,5 +1,6 @@
#include "character/CharacterAnimation.h"
#include "character/CharacterObject.h"
#include <frostbite2D/resource/pvf_archive.h>
#include <SDL2/SDL.h>
#include <algorithm>
#include <array>
@@ -38,6 +39,8 @@ bool CharacterAnimation::Init(CharacterObject* parent,
const CharacterEquipmentManager& equipmentManager) {
parent_ = parent;
actionAnimations_.clear();
missingAnimationPaths_.clear();
skippedMissingAnimationCount_ = 0;
currentActionTag_.clear();
direction_ = 1;
actionFrameFlagCallback_ = nullptr;
@@ -56,6 +59,12 @@ bool CharacterAnimation::Init(CharacterObject* parent,
return false;
}
if (skippedMissingAnimationCount_ > 0) {
SDL_Log("CharacterAnimation: skipped %zu missing animation loads across %zu unique paths for job %d",
skippedMissingAnimationCount_, missingAnimationPaths_.size(),
config.jobId);
}
return true;
}
@@ -77,6 +86,10 @@ void CharacterAnimation::CreateAnimationBySlot(
const character::CharacterConfig& config,
const CharacterEquipmentManager& equipmentManager) {
if (slotName == std::string("skin_avatar")) {
if (shouldSkipMissingAnimation(actionPath)) {
return;
}
Animation::ReplaceData replaceData(0, 0);
if (const auto* equip = equipmentManager.GetEquip(slotName)) {
auto it = equip->jobAnimations.find(config.jobId);
@@ -114,6 +127,10 @@ void CharacterAnimation::CreateAnimationBySlot(
}
std::string aniPath = equipDir + "/" + variation.animationGroup + actionPathTail;
if (shouldSkipMissingAnimation(aniPath)) {
continue;
}
auto animation = MakePtr<Animation>(
aniPath, FormatImgPath,
Animation::ReplaceData(variation.imgFormat[0], variation.imgFormat[1]));
@@ -128,6 +145,23 @@ void CharacterAnimation::CreateAnimationBySlot(
}
}
bool CharacterAnimation::shouldSkipMissingAnimation(const std::string& aniPath) {
PvfArchive& pvf = PvfArchive::get();
std::string normalizedPath = pvf.normalizePath(aniPath);
if (missingAnimationPaths_.find(normalizedPath) != missingAnimationPaths_.end()) {
++skippedMissingAnimationCount_;
return true;
}
if (pvf.hasFile(normalizedPath)) {
return false;
}
missingAnimationPaths_.insert(std::move(normalizedPath));
++skippedMissingAnimationCount_;
return true;
}
std::string CharacterAnimation::DescribeAvailableActions() const {
if (actionAnimations_.empty()) {
return "<none>";

View File

@@ -4,6 +4,7 @@
#include <SDL2/SDL.h>
#include <cmath>
#include <frostbite2D/core/application.h>
#include <frostbite2D/utils/startup_trace.h>
#include <sstream>
#include <utility>
@@ -49,6 +50,7 @@ int32 RoundWorldCoordinate(float value) {
} // namespace
bool CharacterObject::Construction(int jobId) {
ScopedStartupTrace startupTrace("CharacterObject::Construction");
// Reset all runtime state before rebuilding the character from config.
EnableEventReceive();
RemoveAllChildren();
@@ -67,7 +69,10 @@ bool CharacterObject::Construction(int jobId) {
lastDeltaTime_ = 0.0f;
inputEnabled_ = true;
auto config = character::loadCharacterConfig(jobId);
auto config = [&]() {
ScopedStartupTrace stageTrace("character::loadCharacterConfig");
return character::loadCharacterConfig(jobId);
}();
if (!config) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"CharacterObject: failed to load job %d config", jobId);
@@ -79,24 +84,36 @@ bool CharacterObject::Construction(int jobId) {
direction_ = 1;
config_ = *config;
stateMachine_.Configure(*config_);
equipmentManager_.Init(config_->baseJobConfig);
if (auto actionLibrary = loadCharacterActionLibrary(*config_)) {
{
ScopedStartupTrace stageTrace("CharacterEquipmentManager::Init");
equipmentManager_.Init(config_->baseJobConfig);
}
if (auto actionLibrary = [&]() {
ScopedStartupTrace stageTrace("loadCharacterActionLibrary");
return loadCharacterActionLibrary(*config_);
}()) {
actionLibrary_ = *actionLibrary;
}
animationManager_ = MakePtr<CharacterAnimation>();
if (!animationManager_->Init(this, *config_, equipmentManager_)) {
ReportFatalCharacterError("CharacterObject::Construction",
"no usable animation tags were loaded");
return false;
{
ScopedStartupTrace stageTrace("CharacterAnimation::Init");
if (!animationManager_->Init(this, *config_, equipmentManager_)) {
ReportFatalCharacterError("CharacterObject::Construction",
"no usable animation tags were loaded");
return false;
}
}
AddChild(animationManager_);
if (!RequireAction("idle", "CharacterObject::Construction")) {
return false;
}
if (!SetActionStrict("rest", "CharacterObject::Construction", "idle")) {
return false;
{
ScopedStartupTrace stageTrace("CharacterObject initial action setup");
if (!RequireAction("idle", "CharacterObject::Construction")) {
return false;
}
if (!SetActionStrict("rest", "CharacterObject::Construction", "idle")) {
return false;
}
}
SetWorldPosition({});