feat(地图系统): 增强移动区域调试功能

- 添加移动区域边界结构体MoveAreaBounds并集成到地图配置中
- 实现移动区域索引查找和高亮显示功能
- 增加可移动区域检查开关movableAreaCheckEnabled
- 优化调试信息显示,包括坐标和当前所在移动区域
- 重构移动区域相关代码,提高可维护性
This commit is contained in:
2026-04-06 14:36:51 +08:00
parent bcc285eed6
commit f86ce35b68
8 changed files with 165 additions and 44 deletions

View File

@@ -41,6 +41,11 @@ struct MoveAreaTarget {
int area = -2; int area = -2;
}; };
struct MoveAreaBounds {
Vec2 leftTop = Vec2::Zero();
Vec2 rightBottom = Vec2::Zero();
};
struct TileInfo { struct TileInfo {
std::string spritePath; std::string spritePath;
int spriteIndex = 0; int spriteIndex = 0;
@@ -65,6 +70,7 @@ struct MapConfig {
std::vector<std::string> soundIds; std::vector<std::string> soundIds;
std::vector<Vec2> virtualMovablePolygon; std::vector<Vec2> virtualMovablePolygon;
std::vector<Rect> townMovableAreas; std::vector<Rect> townMovableAreas;
std::vector<MoveAreaBounds> townMovableAreaBounds;
std::vector<MoveAreaTarget> townMovableAreaTargets; std::vector<MoveAreaTarget> townMovableAreaTargets;
}; };

View File

@@ -19,6 +19,7 @@ namespace frostbite2D {
*/ */
class GameMap : public Actor { class GameMap : public Actor {
public: public:
static constexpr size_t kInvalidMoveAreaIndex = static_cast<size_t>(-1);
using MapMoveArea = game::MoveAreaTarget; using MapMoveArea = game::MoveAreaTarget;
GameMap(); GameMap();
@@ -45,11 +46,16 @@ public:
/// 检查当前位置是否进入 town move area用于切图/传送判定。 /// 检查当前位置是否进入 town move area用于切图/传送判定。
MapMoveArea CheckIsItMoveArea(const Vec3& curPos) const; MapMoveArea CheckIsItMoveArea(const Vec3& curPos) const;
bool TryGetMoveAreaTarget(const Vec3& curPos, MapMoveArea& outTarget) const; bool TryGetMoveAreaTarget(const Vec3& curPos, MapMoveArea& outTarget) const;
size_t FindMoveAreaIndex(const Vec3& curPos) const;
const std::vector<MapMoveArea>& GetMoveAreaInfo() const; const std::vector<MapMoveArea>& GetMoveAreaInfo() const;
size_t GetMoveAreaCount() const { return moveArea_.size(); }
Rect GetMovablePositionArea(size_t index) const; Rect GetMovablePositionArea(size_t index) const;
const std::string& GetMapPath() const { return mapConfig_.mapPath; }
void SetDebugHighlightedMoveAreaIndex(size_t index);
int GetBackgroundRepeatWidth() const { return backgroundRepeatWidth_; } int GetBackgroundRepeatWidth() const { return backgroundRepeatWidth_; }
bool IsDebugModeEnabled() const { return debugMode_; } bool IsDebugModeEnabled() const { return debugMode_; }
bool IsMovableAreaCheckEnabled() const { return movableAreaCheckEnabled_; }
private: private:
/// 初始化固定图层。图层名字与 DNF 地图层概念保持一致。 /// 初始化固定图层。图层名字与 DNF 地图层概念保持一致。
@@ -84,6 +90,8 @@ private:
/// 地图配置里的整体 Y 偏移;既影响层位置,也影响地板校准。 /// 地图配置里的整体 Y 偏移;既影响层位置,也影响地板校准。
int mapOffsetY_ = 0; int mapOffsetY_ = 0;
bool debugMode_ = true; bool debugMode_ = true;
/// 硬编码调试开关:关闭后忽略可行走区域检测,允许角色自由移动。
bool movableAreaCheckEnabled_ = false;
/// 当前地图正在播放的背景音乐。 /// 当前地图正在播放的背景音乐。
Ptr<Music> currentMusic_; Ptr<Music> currentMusic_;
}; };

View File

@@ -7,17 +7,26 @@ namespace frostbite2D {
class GameMapLayer : public Actor { class GameMapLayer : public Actor {
public: public:
static constexpr size_t kInvalidMoveAreaIndex = static_cast<size_t>(-1);
void Render() override; void Render() override;
void SetDebugFeasibleAreaPolygon(const std::vector<Vec2>& polygon); void SetDebugFeasibleAreaPolygon(const std::vector<Vec2>& polygon);
void AddDebugMoveAreaInfo(const Rect& rect); void AddDebugMoveAreaInfo(const Rect& rect, size_t index);
void SetDebugHighlightedMoveAreaIndex(size_t index);
void ClearDebugAreaInfo(); void ClearDebugAreaInfo();
void AddObject(RefPtr<Actor> obj); void AddObject(RefPtr<Actor> obj);
private: private:
struct DebugMoveAreaInfo {
Rect rect;
size_t index = kInvalidMoveAreaIndex;
};
std::vector<Vec2> feasibleAreaPolygon_; std::vector<Vec2> feasibleAreaPolygon_;
std::vector<Rect> feasibleAreaFillRects_; std::vector<Rect> feasibleAreaFillRects_;
std::vector<Rect> moveAreaInfoList_; std::vector<DebugMoveAreaInfo> moveAreaInfoList_;
size_t highlightedMoveAreaIndex_ = kInvalidMoveAreaIndex;
}; };
} // namespace frostbite2D } // namespace frostbite2D

View File

@@ -17,6 +17,17 @@ constexpr float kDebugHudPaddingX = 8.0f;
constexpr float kDebugHudPaddingY = 6.0f; constexpr float kDebugHudPaddingY = 6.0f;
constexpr char kDebugHudPopupImg[] = "sprite/interface/newstyle/windows/popup/popup.img"; constexpr char kDebugHudPopupImg[] = "sprite/interface/newstyle/windows/popup/popup.img";
void ConfigureTextLine(RefPtr<TextSprite> textSprite, const char* name,
int zOrder) {
if (!textSprite) {
return;
}
textSprite->SetName(name);
textSprite->SetFont("default");
textSprite->SetTextColor(1.0f, 1.0f, 1.0f, 1.0f);
textSprite->SetZOrder(zOrder);
}
} // namespace } // namespace
GameDebugActor::GameDebugActor() { GameDebugActor::GameDebugActor() {
@@ -64,6 +75,9 @@ void GameDebugActor::DetachFromParent() {
} }
void GameDebugActor::SetDebugMap(GameMap* map) { void GameDebugActor::SetDebugMap(GameMap* map) {
if (debugMap_ && debugMap_ != map) {
debugMap_->SetDebugHighlightedMoveAreaIndex(GameMap::kInvalidMoveAreaIndex);
}
debugMap_ = map; debugMap_ = map;
updateOverlay(); updateOverlay();
} }
@@ -74,6 +88,9 @@ void GameDebugActor::SetTrackedCharacter(CharacterObject* character) {
} }
void GameDebugActor::ClearDebugContext() { void GameDebugActor::ClearDebugContext() {
if (debugMap_) {
debugMap_->SetDebugHighlightedMoveAreaIndex(GameMap::kInvalidMoveAreaIndex);
}
debugMap_ = nullptr; debugMap_ = nullptr;
trackedCharacter_ = nullptr; trackedCharacter_ = nullptr;
setOverlayVisible(false); setOverlayVisible(false);
@@ -100,10 +117,7 @@ void GameDebugActor::initOverlay() {
} }
coordText_ = TextSprite::create(); coordText_ = TextSprite::create();
coordText_->SetName("debugCoordText"); ConfigureTextLine(coordText_, "debugCoordText", 1);
coordText_->SetFont("default");
coordText_->SetTextColor(1.0f, 1.0f, 1.0f, 1.0f);
coordText_->SetZOrder(1);
AddChild(coordText_); AddChild(coordText_);
setOverlayVisible(false); setOverlayVisible(false);
@@ -112,19 +126,29 @@ void GameDebugActor::initOverlay() {
void GameDebugActor::updateOverlay() { void GameDebugActor::updateOverlay() {
if (!coordText_ || !debugMap_ || !trackedCharacter_ || if (!coordText_ || !debugMap_ || !trackedCharacter_ ||
!debugMap_->IsDebugModeEnabled()) { !debugMap_->IsDebugModeEnabled()) {
if (debugMap_) {
debugMap_->SetDebugHighlightedMoveAreaIndex(GameMap::kInvalidMoveAreaIndex);
}
setOverlayVisible(false); setOverlayVisible(false);
return; return;
} }
const CharacterWorldPosition& worldPosition = trackedCharacter_->GetWorldPosition(); const CharacterWorldPosition& worldPosition = trackedCharacter_->GetWorldPosition();
char textBuffer[64]; Vec3 currentWorldPos(static_cast<float>(worldPosition.x),
SDL_snprintf(textBuffer, sizeof(textBuffer), "角色坐标: (%d, %d, %d)", static_cast<float>(worldPosition.y),
worldPosition.x, worldPosition.y, worldPosition.z); static_cast<float>(worldPosition.z));
coordText_->SetText(textBuffer); size_t moveAreaIndex = debugMap_->FindMoveAreaIndex(currentWorldPos);
debugMap_->SetDebugHighlightedMoveAreaIndex(moveAreaIndex);
Vec2 textSize = coordText_->GetTextSize(); char coordTextBuffer[96];
Vec2 panelSize(textSize.x + kDebugHudPaddingX * 2.0f, SDL_snprintf(coordTextBuffer, sizeof(coordTextBuffer), "角色坐标: (%d, %d, %d)",
textSize.y + kDebugHudPaddingY * 2.0f); worldPosition.x, worldPosition.y, worldPosition.z);
coordText_->SetText(coordTextBuffer);
Vec2 coordTextSize = coordText_->GetTextSize();
float panelWidth = coordTextSize.x + kDebugHudPaddingX * 2.0f;
float panelHeight = coordTextSize.y + kDebugHudPaddingY * 2.0f;
Vec2 panelSize(panelWidth, panelHeight);
if (background_) { if (background_) {
background_->SetSize(panelSize); background_->SetSize(panelSize);
} }

View File

@@ -101,6 +101,21 @@ int toInt(const std::string& value, int fallback = 0) {
} }
} }
MoveAreaBounds normalizeMoveAreaBounds(int x1, int y1, int x2, int y2) {
MoveAreaBounds bounds;
bounds.leftTop.x = static_cast<float>(std::min(x1, x2));
bounds.leftTop.y = static_cast<float>(std::min(y1, y2));
bounds.rightBottom.x = static_cast<float>(std::max(x1, x2));
bounds.rightBottom.y = static_cast<float>(std::max(y1, y2));
return bounds;
}
Rect makeMoveAreaRect(const MoveAreaBounds& bounds) {
return Rect(bounds.leftTop.x, bounds.leftTop.y,
bounds.rightBottom.x - bounds.leftTop.x,
bounds.rightBottom.y - bounds.leftTop.y);
}
bool readScript(const std::string& path, ScriptTokenStream& outStream) { bool readScript(const std::string& path, ScriptTokenStream& outStream) {
(void)path; (void)path;
return outStream.isValid(); return outStream.isValid();
@@ -257,14 +272,18 @@ bool loadMapConfig(const std::string& mapPath, MapConfig& outConfig) {
if (token == "[/town movable area]") { if (token == "[/town movable area]") {
break; break;
} }
Rect rect(static_cast<float>(toInt(token)), int leftTopX = toInt(token);
static_cast<float>(toInt(stream.get())), int leftTopY = toInt(stream.get());
static_cast<float>(toInt(stream.get())), int rightBottomX = toInt(stream.get());
static_cast<float>(toInt(stream.get()))); int rightBottomY = toInt(stream.get());
MoveAreaBounds bounds = normalizeMoveAreaBounds(
leftTopX, leftTopY, rightBottomX, rightBottomY);
Rect rect = makeMoveAreaRect(bounds);
MoveAreaTarget target; MoveAreaTarget target;
target.town = toInt(stream.get(), -2); target.town = toInt(stream.get(), -2);
target.area = toInt(stream.get(), -2); target.area = toInt(stream.get(), -2);
outConfig.townMovableAreas.push_back(rect); outConfig.townMovableAreas.push_back(rect);
outConfig.townMovableAreaBounds.push_back(bounds);
outConfig.townMovableAreaTargets.push_back(target); outConfig.townMovableAreaTargets.push_back(target);
} }
} else if (segment == "[virtual movable area]") { } else if (segment == "[virtual movable area]") {

View File

@@ -131,6 +131,7 @@ void GameMap::clearLayerChildren() {
moveArea_.clear(); moveArea_.clear();
currentMusic_.Reset(); currentMusic_.Reset();
backgroundRepeatWidth_ = 0; backgroundRepeatWidth_ = 0;
SetDebugHighlightedMoveAreaIndex(kInvalidMoveAreaIndex);
} }
bool GameMap::LoadMap(const std::string &mapName) { bool GameMap::LoadMap(const std::string &mapName) {
@@ -321,19 +322,10 @@ void GameMap::InitVirtualMovableArea() {
if (movablePolygon_.empty()) { if (movablePolygon_.empty()) {
return; return;
} }
if (!movableAreaCheckEnabled_) {
float minX = movablePolygon_.front().x; SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
float minY = movablePolygon_.front().y; "GameMap: movable area check disabled, allowing free movement");
float maxX = movablePolygon_.front().x;
float maxY = movablePolygon_.front().y;
for (const auto& point : movablePolygon_) {
minX = std::min(minX, point.x);
minY = std::min(minY, point.y);
maxX = std::max(maxX, point.x);
maxY = std::max(maxY, point.y);
} }
SDL_Log("GameMap: movable polygon vertices=%zu bounds=(%.0f, %.0f)-(%.0f, %.0f)",
movablePolygon_.size(), minX, minY, maxX, maxY);
if (!debugMode_) { if (!debugMode_) {
return; return;
@@ -357,8 +349,10 @@ void GameMap::InitMoveArea() {
return; return;
} }
for (const auto& rect : moveArea_) { for (size_t i = 0;
layerIt->second->AddDebugMoveAreaInfo(rect); i < moveArea_.size() && i < mapConfig_.townMovableAreaTargets.size();
++i) {
layerIt->second->AddDebugMoveAreaInfo(moveArea_[i], i);
} }
} }
@@ -460,6 +454,9 @@ Vec3 GameMap::CheckIsItMovable(const Vec3& curPos, const Vec3& posOffset) const
int targetY = RoundWorldCoordinate(curPos.y + posOffset.y); int targetY = RoundWorldCoordinate(curPos.y + posOffset.y);
int targetZ = RoundWorldCoordinate(curPos.z + posOffset.z); int targetZ = RoundWorldCoordinate(curPos.z + posOffset.z);
Vec3 result = MakeIntegerWorldPosition(currentX, currentY, targetZ); Vec3 result = MakeIntegerWorldPosition(currentX, currentY, targetZ);
if (!movableAreaCheckEnabled_) {
return MakeIntegerWorldPosition(targetX, targetY, targetZ);
}
if (movablePolygon_.size() < 3) { if (movablePolygon_.size() < 3) {
return MakeIntegerWorldPosition(targetX, targetY, targetZ); return MakeIntegerWorldPosition(targetX, targetY, targetZ);
} }
@@ -502,19 +499,37 @@ GameMap::MapMoveArea GameMap::CheckIsItMoveArea(const Vec3& curPos) const {
return MapMoveArea(); return MapMoveArea();
} }
bool GameMap::TryGetMoveAreaTarget(const Vec3& curPos, MapMoveArea& outTarget) const { size_t GameMap::FindMoveAreaIndex(const Vec3& curPos) const {
// moveArea_ and townMovableAreaTargets share the same index mapping.
int currentX = RoundWorldCoordinate(curPos.x); int currentX = RoundWorldCoordinate(curPos.x);
int currentY = RoundWorldCoordinate(curPos.y); int currentY = RoundWorldCoordinate(curPos.y);
for (size_t i = 0; i < moveArea_.size() && i < mapConfig_.townMovableAreaTargets.size(); ++i) { for (size_t i = 0;
i < moveArea_.size() && i < mapConfig_.townMovableAreaTargets.size();
++i) {
if (moveArea_[i].containsPoint(MakeIntegerWorldPoint(currentX, currentY))) { if (moveArea_[i].containsPoint(MakeIntegerWorldPoint(currentX, currentY))) {
outTarget = mapConfig_.townMovableAreaTargets[i]; return i;
}
}
return kInvalidMoveAreaIndex;
}
bool GameMap::TryGetMoveAreaTarget(const Vec3& curPos, MapMoveArea& outTarget) const {
size_t index = FindMoveAreaIndex(curPos);
if (index != kInvalidMoveAreaIndex &&
index < mapConfig_.townMovableAreaTargets.size()) {
outTarget = mapConfig_.townMovableAreaTargets[index];
return true; return true;
} }
}
return false; return false;
} }
void GameMap::SetDebugHighlightedMoveAreaIndex(size_t index) {
auto layerIt = layerMap_.find("max");
if (layerIt == layerMap_.end() || !layerIt->second) {
return;
}
layerIt->second->SetDebugHighlightedMoveAreaIndex(index);
}
const std::vector<GameMap::MapMoveArea>& GameMap::GetMoveAreaInfo() const { const std::vector<GameMap::MapMoveArea>& GameMap::GetMoveAreaInfo() const {
return mapConfig_.townMovableAreaTargets; return mapConfig_.townMovableAreaTargets;
} }

View File

@@ -13,6 +13,19 @@ constexpr float kDebugEdgePointSize = 5.0f;
constexpr float kDebugVertexSize = 9.0f; constexpr float kDebugVertexSize = 9.0f;
constexpr float kDebugEdgeStep = 4.0f; constexpr float kDebugEdgeStep = 4.0f;
std::vector<Vec2> BuildRectPolygon(const Rect& rect) {
if (rect.empty()) {
return {};
}
return {
Vec2(rect.left(), rect.top()),
Vec2(rect.right(), rect.top()),
Vec2(rect.right(), rect.bottom()),
Vec2(rect.left(), rect.bottom()),
};
}
std::vector<Rect> BuildPolygonFillRects(const std::vector<Vec2>& polygon) { std::vector<Rect> BuildPolygonFillRects(const std::vector<Vec2>& polygon) {
std::vector<Rect> fillRects; std::vector<Rect> fillRects;
if (polygon.size() < 3) { if (polygon.size() < 3) {
@@ -124,10 +137,15 @@ void GameMapLayer::Render() {
Renderer::get().drawQuad(drawRect, Color(0.0f, 1.0f, 0.0f, kDebugAreaAlpha)); Renderer::get().drawQuad(drawRect, Color(0.0f, 1.0f, 0.0f, kDebugAreaAlpha));
} }
for (const auto& rect : moveAreaInfoList_) { for (const auto& moveArea : moveAreaInfoList_) {
Rect drawRect(worldOrigin.x + rect.origin.x, worldOrigin.y + rect.origin.y, bool isHighlighted = moveArea.index == highlightedMoveAreaIndex_;
rect.width(), rect.height()); Color fillColor =
Renderer::get().drawQuad(drawRect, Color(0.0f, 0.0f, 1.0f, kDebugAreaAlpha)); isHighlighted ? Color(0.0f, 1.0f, 1.0f, 0.60f)
: Color(0.0f, 0.0f, 1.0f, kDebugAreaAlpha);
Rect drawRect(worldOrigin.x + moveArea.rect.origin.x,
worldOrigin.y + moveArea.rect.origin.y,
moveArea.rect.width(), moveArea.rect.height());
Renderer::get().drawQuad(drawRect, fillColor);
} }
if (!feasibleAreaPolygon_.empty()) { if (!feasibleAreaPolygon_.empty()) {
@@ -135,6 +153,19 @@ void GameMapLayer::Render() {
DrawPolygonOutline(feasibleAreaPolygon_, worldOrigin, outlineColor); DrawPolygonOutline(feasibleAreaPolygon_, worldOrigin, outlineColor);
DrawPolygonVertices(feasibleAreaPolygon_, worldOrigin, outlineColor); DrawPolygonVertices(feasibleAreaPolygon_, worldOrigin, outlineColor);
} }
for (auto& moveArea : moveAreaInfoList_) {
std::vector<Vec2> polygon = BuildRectPolygon(moveArea.rect);
if (polygon.empty()) {
continue;
}
bool isHighlighted = moveArea.index == highlightedMoveAreaIndex_;
Color moveAreaOutlineColor =
isHighlighted ? Color(0.0f, 1.0f, 1.0f, 1.0f)
: Color(0.0f, 0.0f, 1.0f, kDebugOutlineAlpha);
DrawPolygonOutline(polygon, worldOrigin, moveAreaOutlineColor);
DrawPolygonVertices(polygon, worldOrigin, moveAreaOutlineColor);
}
} }
void GameMapLayer::SetDebugFeasibleAreaPolygon(const std::vector<Vec2>& polygon) { void GameMapLayer::SetDebugFeasibleAreaPolygon(const std::vector<Vec2>& polygon) {
@@ -142,14 +173,23 @@ void GameMapLayer::SetDebugFeasibleAreaPolygon(const std::vector<Vec2>& polygon)
feasibleAreaFillRects_ = BuildPolygonFillRects(feasibleAreaPolygon_); feasibleAreaFillRects_ = BuildPolygonFillRects(feasibleAreaPolygon_);
} }
void GameMapLayer::AddDebugMoveAreaInfo(const Rect& rect) { void GameMapLayer::AddDebugMoveAreaInfo(const Rect& rect, size_t index) {
moveAreaInfoList_.push_back(rect); DebugMoveAreaInfo debugArea;
debugArea.rect = rect;
debugArea.index = index;
moveAreaInfoList_.push_back(std::move(debugArea));
}
void GameMapLayer::SetDebugHighlightedMoveAreaIndex(size_t index) {
highlightedMoveAreaIndex_ = index;
} }
void GameMapLayer::ClearDebugAreaInfo() { void GameMapLayer::ClearDebugAreaInfo() {
feasibleAreaPolygon_.clear(); feasibleAreaPolygon_.clear();
feasibleAreaFillRects_.clear(); feasibleAreaFillRects_.clear();
moveAreaInfoList_.clear(); moveAreaInfoList_.clear();
highlightedMoveAreaIndex_ = kInvalidMoveAreaIndex;
} }
void GameMapLayer::AddObject(RefPtr<Actor> obj) { void GameMapLayer::AddObject(RefPtr<Actor> obj) {

View File

@@ -1,4 +1,4 @@
#include "scene/GameMapTestScene.h" #include "scene/GameMapTestScene.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <frostbite2D/scene/scene_manager.h> #include <frostbite2D/scene/scene_manager.h>
#include <frostbite2D/utils/startup_trace.h> #include <frostbite2D/utils/startup_trace.h>
@@ -7,7 +7,7 @@ namespace frostbite2D {
namespace { namespace {
constexpr char kTestMapPath[] = "map/elvengard/elvengard.map"; constexpr char kTestMapPath[] = "map/elvengard/d_elvengard.map";
} // namespace } // namespace