From 3a56db3dc75a473c3aae21f32e286c6a6e218edc Mon Sep 17 00:00:00 2001 From: 1165565692-lang <1165565692@qq.com> Date: Tue, 9 Jun 2026 14:28:36 +0800 Subject: [PATCH] Add camera zone lock --- game/docs/planning/game_architecture_board.md | 20 +++--- game/docs/planning/scene_build_plan.md | 3 +- game/src/data/level_definition.h | 11 ++++ game/src/debug/debug_overlay.cpp | 41 ++++++++++-- game/src/debug/debug_overlay.h | 8 ++- game/src/scene/camera_controller.cpp | 4 ++ game/src/scene/camera_controller.h | 1 + game/src/scene/whitebox_scene.cpp | 65 ++++++++++++++++++- game/src/scene/whitebox_scene.h | 12 ++++ 9 files changed, 146 insertions(+), 19 deletions(-) diff --git a/game/docs/planning/game_architecture_board.md b/game/docs/planning/game_architecture_board.md index 6cf3745..c51fec6 100644 --- a/game/docs/planning/game_architecture_board.md +++ b/game/docs/planning/game_architecture_board.md @@ -178,12 +178,13 @@ mindmap - `scene/whitebox_scene.*` - `scene/camera_controller.*`:玩家跟随、dead zone、平滑和世界边界 clamp。 +- `CameraZoneLock`:玩家进入 battle zone 后摄像机锁定到 zone camera bounds,当前用 `F2` debug 清场解锁。 下一步: - 抽象 `GameplayScene` 作为通用玩法场景基类。 - `WhiteboxScene` 只保留第一关测试内容。 -- 引入 battle zone 触发逻辑。 +- 将 battle zone debug 清场替换为敌人清空自动解锁。 - 增加局内 reset 入口,用于卡状态、死亡重开和联机重同步。 ### 3. Stage @@ -579,15 +580,14 @@ mindmap 下一步建议按这个顺序做: -1. `CameraZoneLock`:让 battle zone 可以临时锁定摄像机范围,清场后恢复跟随。 -2. `StageLayerSource`:让 `StageLayer` 同时支持色块占位和 sprite 资源路径。 -3. `StageAssetSlots`:建立 `stage_01` 资源槽位和素材来源记录,做到素材可直接落位。 -4. `StageProps`:把后景/前景 props 统一到 layer 数据。 -5. `BattleZone`:在现有 zone 数据上做触发、锁镜头、清场解锁。 -6. `EnemyActor`:做第一个敌人占位,接入出生点。 -7. `CombatSystem`:把 hitbox/hurtbox 真正跑起来。 -8. `SpriteAnimator`:把玩家动画从硬编码改成组件。 -9. `InputConfig`:把默认键位/手柄映射迁移到可配置数据。 +1. `StageLayerSource`:让 `StageLayer` 同时支持色块占位和 sprite 资源路径。 +2. `StageAssetSlots`:建立 `stage_01` 资源槽位和素材来源记录,做到素材可直接落位。 +3. `StageProps`:把后景/前景 props 统一到 layer 数据。 +4. `BattleZone`:把 `F2` debug 清场替换为敌人清空自动解锁。 +5. `EnemyActor`:做第一个敌人占位,接入出生点。 +6. `CombatSystem`:把 hitbox/hurtbox 真正跑起来。 +7. `SpriteAnimator`:把玩家动画从硬编码改成组件。 +8. `InputConfig`:把默认键位/手柄映射迁移到可配置数据。 ## 素材协作提示 diff --git a/game/docs/planning/scene_build_plan.md b/game/docs/planning/scene_build_plan.md index 3ccc003..660c875 100644 --- a/game/docs/planning/scene_build_plan.md +++ b/game/docs/planning/scene_build_plan.md @@ -22,6 +22,7 @@ - `DebugOverlay` 已能显示碰撞、玩家框、battle zone、camera lock 范围和 spawn point。 - `WhiteboxScene` 已支持 `F1` 切换 debug overlay。 - `CameraController` 已接入:修复场景 `OnUpdate()` 不被调用导致摄像机不动的问题,并提供跟随、dead zone、平滑和世界边界 clamp。 +- `CameraZoneLock` 已接入:玩家进入 battle zone 后摄像机临时锁定到 zone camera bounds,`F2` 临时清场并恢复全关卡跟随。 ## 2. 第一关场景目标 @@ -250,7 +251,7 @@ game/assets/stage/stage_01/ ### 场景骨架实施顺序 1. 已完成:`CameraController` 修复当前摄像机不会动的问题,实现玩家跟随、边界 clamp、dead zone 和平滑参数。 -2. `CameraZoneLock`:让 battle zone 可以锁定摄像机范围,先只做 debug 触发和解锁占位。 +2. 已完成:`CameraZoneLock` 让 battle zone 可以锁定摄像机范围,先用 `F2` 做 debug 清场和解锁占位。 3. `StageLayerSource`:扩展 `StageLayer`,让每个 rect 支持色块占位或 sprite 资源路径。 4. `StageAssetSlots`:创建并记录 `stage_01` 资源槽位,不要求已有最终素材。 5. `StageProps`:把 props 从旧 `WhiteboxProp` 迁移到统一 layer 数据,支持 back/front 两层。 diff --git a/game/src/data/level_definition.h b/game/src/data/level_definition.h index 8c5f037..2f3d681 100644 --- a/game/src/data/level_definition.h +++ b/game/src/data/level_definition.h @@ -34,6 +34,17 @@ struct BattleZoneDefinition { std::vector enemySpawns; }; +enum class BattleZoneDebugState { + Idle, + Active, + Cleared +}; + +struct BattleZoneDebugInfo { + size_t zoneIndex = 0; + BattleZoneDebugState state = BattleZoneDebugState::Idle; +}; + struct StageLayer { std::string id; StageLayerRole role = StageLayerRole::LevelVisual; diff --git a/game/src/debug/debug_overlay.cpp b/game/src/debug/debug_overlay.cpp index f5e518a..198a42e 100644 --- a/game/src/debug/debug_overlay.cpp +++ b/game/src/debug/debug_overlay.cpp @@ -37,15 +37,43 @@ void drawCross(const frostbite2D::Vec2& position, color); } +BattleZoneDebugState resolveZoneState( + size_t zoneIndex, const std::vector* debugInfo) { + if (!debugInfo) { + return BattleZoneDebugState::Idle; + } + + for (const BattleZoneDebugInfo& info : *debugInfo) { + if (info.zoneIndex == zoneIndex) { + return info.state; + } + } + return BattleZoneDebugState::Idle; +} + +frostbite2D::Color zoneColor(BattleZoneDebugState state) { + switch (state) { + case BattleZoneDebugState::Active: + return frostbite2D::Color(1.0f, 0.82f, 0.15f, 0.95f); + case BattleZoneDebugState::Cleared: + return frostbite2D::Color(0.45f, 0.48f, 0.52f, 0.65f); + case BattleZoneDebugState::Idle: + default: + return frostbite2D::Color(0.95f, 0.25f, 0.25f, 0.9f); + } +} + } // namespace void DebugOverlay::Render(const LevelDefinition& level, const DebugFlags& flags, - const frostbite2D::Rect* playerBounds) const { + const frostbite2D::Rect* playerBounds, + const std::vector* + battleZoneDebug) const { if (flags.showCollision) { drawCollision(level); } if (flags.showBattleZones) { - drawBattleZones(level); + drawBattleZones(level, battleZoneDebug); } if (flags.showSpawnPoints) { drawSpawnPoints(level); @@ -65,9 +93,12 @@ void DebugOverlay::drawActorBounds(const frostbite2D::Rect& bounds) const { drawOutline(bounds, frostbite2D::Color(1.0f, 0.78f, 0.1f, 0.9f)); } -void DebugOverlay::drawBattleZones(const LevelDefinition& level) const { - for (const BattleZoneDefinition& zone : level.battleZones) { - drawOutline(zone.trigger, frostbite2D::Color(0.95f, 0.25f, 0.25f, 0.9f), +void DebugOverlay::drawBattleZones( + const LevelDefinition& level, + const std::vector* battleZoneDebug) const { + for (size_t i = 0; i < level.battleZones.size(); ++i) { + const BattleZoneDefinition& zone = level.battleZones[i]; + drawOutline(zone.trigger, zoneColor(resolveZoneState(i, battleZoneDebug)), 3.0f); drawOutline(zone.cameraBounds, frostbite2D::Color(0.25f, 0.55f, 1.0f, 0.75f), 2.0f); diff --git a/game/src/debug/debug_overlay.h b/game/src/debug/debug_overlay.h index c99b4e1..e4c99c1 100644 --- a/game/src/debug/debug_overlay.h +++ b/game/src/debug/debug_overlay.h @@ -4,18 +4,22 @@ #include "../data/level_definition.h" #include +#include namespace ns_game { class DebugOverlay { public: void Render(const LevelDefinition& level, const DebugFlags& flags, - const frostbite2D::Rect* playerBounds) const; + const frostbite2D::Rect* playerBounds, + const std::vector* battleZoneDebug) const; private: void drawCollision(const LevelDefinition& level) const; void drawActorBounds(const frostbite2D::Rect& bounds) const; - void drawBattleZones(const LevelDefinition& level) const; + void drawBattleZones( + const LevelDefinition& level, + const std::vector* battleZoneDebug) const; void drawSpawnPoints(const LevelDefinition& level) const; }; diff --git a/game/src/scene/camera_controller.cpp b/game/src/scene/camera_controller.cpp index f0e0884..fa7d42e 100644 --- a/game/src/scene/camera_controller.cpp +++ b/game/src/scene/camera_controller.cpp @@ -27,6 +27,10 @@ void CameraController::Configure(const CameraControllerConfig& config) { config_ = config; } +void CameraController::SetWorldBounds(const frostbite2D::Rect& worldBounds) { + config_.worldBounds = worldBounds; +} + void CameraController::SnapToTarget(frostbite2D::Camera& camera, const frostbite2D::Rect& targetBounds) { camera.setPosition(clampToWorld( diff --git a/game/src/scene/camera_controller.h b/game/src/scene/camera_controller.h index 5909b80..99e7de5 100644 --- a/game/src/scene/camera_controller.h +++ b/game/src/scene/camera_controller.h @@ -16,6 +16,7 @@ struct CameraControllerConfig { class CameraController { public: void Configure(const CameraControllerConfig& config); + void SetWorldBounds(const frostbite2D::Rect& worldBounds); void SnapToTarget(frostbite2D::Camera& camera, const frostbite2D::Rect& targetBounds); void Update(frostbite2D::Camera& camera, const frostbite2D::Rect& targetBounds, diff --git a/game/src/scene/whitebox_scene.cpp b/game/src/scene/whitebox_scene.cpp index f8ec10e..8d914c3 100644 --- a/game/src/scene/whitebox_scene.cpp +++ b/game/src/scene/whitebox_scene.cpp @@ -12,6 +12,9 @@ namespace ns_game { void WhiteboxScene::onEnter() { frostbite2D::Scene::onEnter(); level_ = CreateWhiteboxLevel(); + battleZoneStates_.clear(); + battleZoneStates_.resize(level_.battleZones.size()); + activeBattleZoneIndex_ = -1; player_ = frostbite2D::MakePtr(); player_->SetTopLeftPosition(level_.playerSpawn); @@ -22,6 +25,7 @@ void WhiteboxScene::onEnter() { void WhiteboxScene::Update(float deltaTime) { frostbite2D::Scene::Update(deltaTime); + updateBattleZones(); updateCamera(deltaTime); } @@ -33,6 +37,11 @@ bool WhiteboxScene::OnEvent(const frostbite2D::Event& event) { toggleDebugOverlay(); return true; } + if (!keyEvent.isRepeat() && + keyEvent.getKeyCode() == frostbite2D::KeyCode::F2) { + clearActiveBattleZone(); + return true; + } } return frostbite2D::Scene::OnEvent(event); @@ -43,7 +52,10 @@ void WhiteboxScene::Render() { frostbite2D::Scene::Render(); const frostbite2D::Rect playerBounds = player_ ? player_->GetWorldBounds() : frostbite2D::Rect::Zero(); - debugOverlay_.Render(level_, debugFlags_, player_ ? &playerBounds : nullptr); + const std::vector battleZoneDebug = + buildBattleZoneDebugInfo(); + debugOverlay_.Render(level_, debugFlags_, player_ ? &playerBounds : nullptr, + &battleZoneDebug); } frostbite2D::Camera* WhiteboxScene::activeCamera() const { @@ -54,6 +66,22 @@ frostbite2D::Camera* WhiteboxScene::activeCamera() const { return renderer->getCamera(); } +std::vector WhiteboxScene::buildBattleZoneDebugInfo() + const { + std::vector debugInfo; + debugInfo.reserve(battleZoneStates_.size()); + for (size_t i = 0; i < battleZoneStates_.size(); ++i) { + BattleZoneDebugState state = BattleZoneDebugState::Idle; + if (battleZoneStates_[i].cleared) { + state = BattleZoneDebugState::Cleared; + } else if (activeBattleZoneIndex_ == static_cast(i)) { + state = BattleZoneDebugState::Active; + } + debugInfo.push_back(BattleZoneDebugInfo{i, state}); + } + return debugInfo; +} + void WhiteboxScene::configureCamera() { auto* camera = activeCamera(); if (!camera) { @@ -83,6 +111,41 @@ void WhiteboxScene::toggleDebugOverlay() { debugFlags_.showSpawnPoints = enabled; } +void WhiteboxScene::clearActiveBattleZone() { + if (activeBattleZoneIndex_ < 0 || + activeBattleZoneIndex_ >= static_cast(battleZoneStates_.size())) { + return; + } + + battleZoneStates_[static_cast(activeBattleZoneIndex_)].cleared = true; + activeBattleZoneIndex_ = -1; + cameraController_.SetWorldBounds(level_.cameraBounds); +} + +void WhiteboxScene::updateBattleZones() { + if (!player_) { + return; + } + + if (activeBattleZoneIndex_ >= 0) { + return; + } + + const frostbite2D::Rect playerBounds = player_->GetWorldBounds(); + for (size_t i = 0; i < level_.battleZones.size(); ++i) { + BattleZoneRuntime& runtime = battleZoneStates_[i]; + if (runtime.cleared || + !level_.battleZones[i].trigger.intersects(playerBounds)) { + continue; + } + + runtime.triggered = true; + activeBattleZoneIndex_ = static_cast(i); + cameraController_.SetWorldBounds(level_.battleZones[i].cameraBounds); + break; + } +} + void WhiteboxScene::updateCamera(float deltaTime) { if (!player_) { return; diff --git a/game/src/scene/whitebox_scene.h b/game/src/scene/whitebox_scene.h index 21985e5..6acc653 100644 --- a/game/src/scene/whitebox_scene.h +++ b/game/src/scene/whitebox_scene.h @@ -9,6 +9,8 @@ #include #include +#include +#include namespace ns_game { @@ -20,13 +22,23 @@ public: void Render() override; private: + struct BattleZoneRuntime { + bool triggered = false; + bool cleared = false; + }; + frostbite2D::Camera* activeCamera() const; + std::vector buildBattleZoneDebugInfo() const; void configureCamera(); void toggleDebugOverlay(); + void clearActiveBattleZone(); + void updateBattleZones(); void updateCamera(float deltaTime); LevelDefinition level_; DebugFlags debugFlags_; + std::vector battleZoneStates_; + int activeBattleZoneIndex_ = -1; CameraController cameraController_; StageRenderer stageRenderer_; DebugOverlay debugOverlay_;