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

View File

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

View File

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

View File

@@ -17,6 +17,17 @@ constexpr float kDebugHudPaddingX = 8.0f;
constexpr float kDebugHudPaddingY = 6.0f;
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
GameDebugActor::GameDebugActor() {
@@ -64,6 +75,9 @@ void GameDebugActor::DetachFromParent() {
}
void GameDebugActor::SetDebugMap(GameMap* map) {
if (debugMap_ && debugMap_ != map) {
debugMap_->SetDebugHighlightedMoveAreaIndex(GameMap::kInvalidMoveAreaIndex);
}
debugMap_ = map;
updateOverlay();
}
@@ -74,6 +88,9 @@ void GameDebugActor::SetTrackedCharacter(CharacterObject* character) {
}
void GameDebugActor::ClearDebugContext() {
if (debugMap_) {
debugMap_->SetDebugHighlightedMoveAreaIndex(GameMap::kInvalidMoveAreaIndex);
}
debugMap_ = nullptr;
trackedCharacter_ = nullptr;
setOverlayVisible(false);
@@ -100,10 +117,7 @@ void GameDebugActor::initOverlay() {
}
coordText_ = TextSprite::create();
coordText_->SetName("debugCoordText");
coordText_->SetFont("default");
coordText_->SetTextColor(1.0f, 1.0f, 1.0f, 1.0f);
coordText_->SetZOrder(1);
ConfigureTextLine(coordText_, "debugCoordText", 1);
AddChild(coordText_);
setOverlayVisible(false);
@@ -112,19 +126,29 @@ void GameDebugActor::initOverlay() {
void GameDebugActor::updateOverlay() {
if (!coordText_ || !debugMap_ || !trackedCharacter_ ||
!debugMap_->IsDebugModeEnabled()) {
if (debugMap_) {
debugMap_->SetDebugHighlightedMoveAreaIndex(GameMap::kInvalidMoveAreaIndex);
}
setOverlayVisible(false);
return;
}
const CharacterWorldPosition& worldPosition = trackedCharacter_->GetWorldPosition();
char textBuffer[64];
SDL_snprintf(textBuffer, sizeof(textBuffer), "角色坐标: (%d, %d, %d)",
worldPosition.x, worldPosition.y, worldPosition.z);
coordText_->SetText(textBuffer);
Vec3 currentWorldPos(static_cast<float>(worldPosition.x),
static_cast<float>(worldPosition.y),
static_cast<float>(worldPosition.z));
size_t moveAreaIndex = debugMap_->FindMoveAreaIndex(currentWorldPos);
debugMap_->SetDebugHighlightedMoveAreaIndex(moveAreaIndex);
Vec2 textSize = coordText_->GetTextSize();
Vec2 panelSize(textSize.x + kDebugHudPaddingX * 2.0f,
textSize.y + kDebugHudPaddingY * 2.0f);
char coordTextBuffer[96];
SDL_snprintf(coordTextBuffer, sizeof(coordTextBuffer), "角色坐标: (%d, %d, %d)",
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_) {
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) {
(void)path;
return outStream.isValid();
@@ -257,14 +272,18 @@ bool loadMapConfig(const std::string& mapPath, MapConfig& outConfig) {
if (token == "[/town movable area]") {
break;
}
Rect rect(static_cast<float>(toInt(token)),
static_cast<float>(toInt(stream.get())),
static_cast<float>(toInt(stream.get())),
static_cast<float>(toInt(stream.get())));
int leftTopX = toInt(token);
int leftTopY = toInt(stream.get());
int rightBottomX = toInt(stream.get());
int rightBottomY = toInt(stream.get());
MoveAreaBounds bounds = normalizeMoveAreaBounds(
leftTopX, leftTopY, rightBottomX, rightBottomY);
Rect rect = makeMoveAreaRect(bounds);
MoveAreaTarget target;
target.town = toInt(stream.get(), -2);
target.area = toInt(stream.get(), -2);
outConfig.townMovableAreas.push_back(rect);
outConfig.townMovableAreaBounds.push_back(bounds);
outConfig.townMovableAreaTargets.push_back(target);
}
} else if (segment == "[virtual movable area]") {

View File

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

View File

@@ -13,6 +13,19 @@ constexpr float kDebugEdgePointSize = 5.0f;
constexpr float kDebugVertexSize = 9.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> fillRects;
if (polygon.size() < 3) {
@@ -124,10 +137,15 @@ void GameMapLayer::Render() {
Renderer::get().drawQuad(drawRect, Color(0.0f, 1.0f, 0.0f, kDebugAreaAlpha));
}
for (const auto& rect : moveAreaInfoList_) {
Rect drawRect(worldOrigin.x + rect.origin.x, worldOrigin.y + rect.origin.y,
rect.width(), rect.height());
Renderer::get().drawQuad(drawRect, Color(0.0f, 0.0f, 1.0f, kDebugAreaAlpha));
for (const auto& moveArea : moveAreaInfoList_) {
bool isHighlighted = moveArea.index == highlightedMoveAreaIndex_;
Color fillColor =
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()) {
@@ -135,6 +153,19 @@ void GameMapLayer::Render() {
DrawPolygonOutline(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) {
@@ -142,14 +173,23 @@ void GameMapLayer::SetDebugFeasibleAreaPolygon(const std::vector<Vec2>& polygon)
feasibleAreaFillRects_ = BuildPolygonFillRects(feasibleAreaPolygon_);
}
void GameMapLayer::AddDebugMoveAreaInfo(const Rect& rect) {
moveAreaInfoList_.push_back(rect);
void GameMapLayer::AddDebugMoveAreaInfo(const Rect& rect, size_t index) {
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() {
feasibleAreaPolygon_.clear();
feasibleAreaFillRects_.clear();
moveAreaInfoList_.clear();
highlightedMoveAreaIndex_ = kInvalidMoveAreaIndex;
}
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 <frostbite2D/scene/scene_manager.h>
#include <frostbite2D/utils/startup_trace.h>
@@ -7,7 +7,7 @@ namespace frostbite2D {
namespace {
constexpr char kTestMapPath[] = "map/elvengard/elvengard.map";
constexpr char kTestMapPath[] = "map/elvengard/d_elvengard.map";
} // namespace