Add player camera controller
This commit is contained in:
@@ -148,6 +148,7 @@ mindmap
|
|||||||
已有:
|
已有:
|
||||||
|
|
||||||
- `scene/whitebox_scene.*`
|
- `scene/whitebox_scene.*`
|
||||||
|
- `scene/camera_controller.*`:玩家跟随、dead zone、平滑和世界边界 clamp。
|
||||||
|
|
||||||
下一步:
|
下一步:
|
||||||
|
|
||||||
@@ -408,16 +409,15 @@ mindmap
|
|||||||
|
|
||||||
下一步建议按这个顺序做:
|
下一步建议按这个顺序做:
|
||||||
|
|
||||||
1. `CameraController`:修复摄像机不会跟随的问题,实现跟随、边界 clamp、dead zone 和平滑参数。
|
1. `CameraZoneLock`:让 battle zone 可以临时锁定摄像机范围,清场后恢复跟随。
|
||||||
2. `CameraZoneLock`:让 battle zone 可以临时锁定摄像机范围,清场后恢复跟随。
|
2. `StageLayerSource`:让 `StageLayer` 同时支持色块占位和 sprite 资源路径。
|
||||||
3. `StageLayerSource`:让 `StageLayer` 同时支持色块占位和 sprite 资源路径。
|
3. `StageAssetSlots`:建立 `stage_01` 资源槽位和素材来源记录,做到素材可直接落位。
|
||||||
4. `StageAssetSlots`:建立 `stage_01` 资源槽位和素材来源记录,做到素材可直接落位。
|
4. `StageProps`:把后景/前景 props 统一到 layer 数据。
|
||||||
5. `StageProps`:把后景/前景 props 统一到 layer 数据。
|
5. `BattleZone`:在现有 zone 数据上做触发、锁镜头、清场解锁。
|
||||||
6. `BattleZone`:在现有 zone 数据上做触发、锁镜头、清场解锁。
|
6. `EnemyActor`:做第一个敌人占位,接入出生点。
|
||||||
7. `EnemyActor`:做第一个敌人占位,接入出生点。
|
7. `CombatSystem`:把 hitbox/hurtbox 真正跑起来。
|
||||||
8. `CombatSystem`:把 hitbox/hurtbox 真正跑起来。
|
8. `SpriteAnimator`:把玩家动画从硬编码改成组件。
|
||||||
9. `SpriteAnimator`:把玩家动画从硬编码改成组件。
|
9. `InputConfig`:把默认键位/手柄映射迁移到可配置数据。
|
||||||
10. `InputConfig`:把默认键位/手柄映射迁移到可配置数据。
|
|
||||||
|
|
||||||
## 素材协作提示
|
## 素材协作提示
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
- `StageRenderer` 已支持远景、中景、地面/平台、后景 props 分层绘制,当前先用色块占位。
|
- `StageRenderer` 已支持远景、中景、地面/平台、后景 props 分层绘制,当前先用色块占位。
|
||||||
- `DebugOverlay` 已能显示碰撞、玩家框、battle zone、camera lock 范围和 spawn point。
|
- `DebugOverlay` 已能显示碰撞、玩家框、battle zone、camera lock 范围和 spawn point。
|
||||||
- `WhiteboxScene` 已支持 `F1` 切换 debug overlay。
|
- `WhiteboxScene` 已支持 `F1` 切换 debug overlay。
|
||||||
|
- `CameraController` 已接入:修复场景 `OnUpdate()` 不被调用导致摄像机不动的问题,并提供跟随、dead zone、平滑和世界边界 clamp。
|
||||||
|
|
||||||
## 2. 第一关场景目标
|
## 2. 第一关场景目标
|
||||||
|
|
||||||
@@ -248,7 +249,7 @@ game/assets/stage/stage_01/
|
|||||||
|
|
||||||
### 场景骨架实施顺序
|
### 场景骨架实施顺序
|
||||||
|
|
||||||
1. `CameraController`:修复当前摄像机不会动的问题,实现玩家跟随、边界 clamp、dead zone 和平滑参数。
|
1. 已完成:`CameraController` 修复当前摄像机不会动的问题,实现玩家跟随、边界 clamp、dead zone 和平滑参数。
|
||||||
2. `CameraZoneLock`:让 battle zone 可以锁定摄像机范围,先只做 debug 触发和解锁占位。
|
2. `CameraZoneLock`:让 battle zone 可以锁定摄像机范围,先只做 debug 触发和解锁占位。
|
||||||
3. `StageLayerSource`:扩展 `StageLayer`,让每个 rect 支持色块占位或 sprite 资源路径。
|
3. `StageLayerSource`:扩展 `StageLayer`,让每个 rect 支持色块占位或 sprite 资源路径。
|
||||||
4. `StageAssetSlots`:创建并记录 `stage_01` 资源槽位,不要求已有最终素材。
|
4. `StageAssetSlots`:创建并记录 `stage_01` 资源槽位,不要求已有最终素材。
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#include "camera_controller.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace ns_game {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
float clampValue(float value, float minValue, float maxValue) {
|
||||||
|
if (maxValue < minValue) {
|
||||||
|
return minValue;
|
||||||
|
}
|
||||||
|
return std::clamp(value, minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
float smoothFactor(float smoothing, float deltaTime) {
|
||||||
|
if (smoothing <= 0.0f) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
return 1.0f - std::exp(-smoothing * std::max(deltaTime, 0.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void CameraController::Configure(const CameraControllerConfig& config) {
|
||||||
|
config_ = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraController::SnapToTarget(frostbite2D::Camera& camera,
|
||||||
|
const frostbite2D::Rect& targetBounds) {
|
||||||
|
camera.setPosition(clampToWorld(
|
||||||
|
resolveDesiredPosition(camera.getPosition(), targetBounds)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraController::Update(frostbite2D::Camera& camera,
|
||||||
|
const frostbite2D::Rect& targetBounds,
|
||||||
|
float deltaTime) {
|
||||||
|
const frostbite2D::Vec2 current = camera.getPosition();
|
||||||
|
const frostbite2D::Vec2 desired =
|
||||||
|
clampToWorld(resolveDesiredPosition(current, targetBounds));
|
||||||
|
const float t = smoothFactor(config_.smoothing, deltaTime);
|
||||||
|
camera.setPosition(frostbite2D::Vec2(
|
||||||
|
current.x + (desired.x - current.x) * t,
|
||||||
|
current.y + (desired.y - current.y) * t));
|
||||||
|
}
|
||||||
|
|
||||||
|
frostbite2D::Vec2 CameraController::resolveDesiredPosition(
|
||||||
|
const frostbite2D::Vec2& currentPosition,
|
||||||
|
const frostbite2D::Rect& targetBounds) const {
|
||||||
|
frostbite2D::Vec2 desired = currentPosition;
|
||||||
|
const float targetCenterX = targetBounds.left() + targetBounds.width() * 0.5f;
|
||||||
|
const float screenX = targetCenterX - currentPosition.x;
|
||||||
|
if (screenX < config_.deadZone.left()) {
|
||||||
|
desired.x = targetCenterX - config_.deadZone.left();
|
||||||
|
} else if (screenX > config_.deadZone.right()) {
|
||||||
|
desired.x = targetCenterX - config_.deadZone.right();
|
||||||
|
}
|
||||||
|
desired.y = config_.worldBounds.top();
|
||||||
|
return desired;
|
||||||
|
}
|
||||||
|
|
||||||
|
frostbite2D::Vec2 CameraController::clampToWorld(
|
||||||
|
const frostbite2D::Vec2& position) const {
|
||||||
|
return frostbite2D::Vec2(
|
||||||
|
clampValue(position.x, config_.worldBounds.left(),
|
||||||
|
config_.worldBounds.right() - config_.viewportWidth),
|
||||||
|
clampValue(position.y, config_.worldBounds.top(),
|
||||||
|
config_.worldBounds.bottom() - config_.viewportHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ns_game
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <frostbite2D/graphics/camera.h>
|
||||||
|
#include <frostbite2D/types/type_math.h>
|
||||||
|
|
||||||
|
namespace ns_game {
|
||||||
|
|
||||||
|
struct CameraControllerConfig {
|
||||||
|
frostbite2D::Rect worldBounds;
|
||||||
|
float viewportWidth = 1280.0f;
|
||||||
|
float viewportHeight = 720.0f;
|
||||||
|
frostbite2D::Rect deadZone = frostbite2D::Rect(420.0f, 0.0f, 340.0f, 720.0f);
|
||||||
|
float smoothing = 14.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CameraController {
|
||||||
|
public:
|
||||||
|
void Configure(const CameraControllerConfig& config);
|
||||||
|
void SnapToTarget(frostbite2D::Camera& camera,
|
||||||
|
const frostbite2D::Rect& targetBounds);
|
||||||
|
void Update(frostbite2D::Camera& camera, const frostbite2D::Rect& targetBounds,
|
||||||
|
float deltaTime);
|
||||||
|
|
||||||
|
private:
|
||||||
|
frostbite2D::Vec2 resolveDesiredPosition(
|
||||||
|
const frostbite2D::Vec2& currentPosition,
|
||||||
|
const frostbite2D::Rect& targetBounds) const;
|
||||||
|
frostbite2D::Vec2 clampToWorld(const frostbite2D::Vec2& position) const;
|
||||||
|
|
||||||
|
CameraControllerConfig config_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ns_game
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
#include <frostbite2D/event/key_event.h>
|
#include <frostbite2D/event/key_event.h>
|
||||||
#include <frostbite2D/graphics/camera.h>
|
#include <frostbite2D/graphics/camera.h>
|
||||||
#include <frostbite2D/graphics/renderer.h>
|
#include <frostbite2D/graphics/renderer.h>
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace ns_game {
|
namespace ns_game {
|
||||||
|
|
||||||
@@ -18,11 +17,12 @@ void WhiteboxScene::onEnter() {
|
|||||||
player_->SetTopLeftPosition(level_.playerSpawn);
|
player_->SetTopLeftPosition(level_.playerSpawn);
|
||||||
player_->SetMovementWorld(&level_.collision);
|
player_->SetMovementWorld(&level_.collision);
|
||||||
AddChild(player_);
|
AddChild(player_);
|
||||||
|
configureCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WhiteboxScene::OnUpdate(float deltaTime) {
|
void WhiteboxScene::Update(float deltaTime) {
|
||||||
(void)deltaTime;
|
frostbite2D::Scene::Update(deltaTime);
|
||||||
updateCamera();
|
updateCamera(deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WhiteboxScene::OnEvent(const frostbite2D::Event& event) {
|
bool WhiteboxScene::OnEvent(const frostbite2D::Event& event) {
|
||||||
@@ -46,6 +46,31 @@ void WhiteboxScene::Render() {
|
|||||||
debugOverlay_.Render(level_, debugFlags_, player_ ? &playerBounds : nullptr);
|
debugOverlay_.Render(level_, debugFlags_, player_ ? &playerBounds : nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frostbite2D::Camera* WhiteboxScene::activeCamera() const {
|
||||||
|
auto* renderer = frostbite2D::Application::get().getRenderer();
|
||||||
|
if (!renderer) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return renderer->getCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WhiteboxScene::configureCamera() {
|
||||||
|
auto* camera = activeCamera();
|
||||||
|
if (!camera) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraControllerConfig config;
|
||||||
|
config.worldBounds = level_.cameraBounds;
|
||||||
|
config.viewportWidth = static_cast<float>(config::kVirtualWidth);
|
||||||
|
config.viewportHeight = static_cast<float>(config::kVirtualHeight);
|
||||||
|
cameraController_.Configure(config);
|
||||||
|
|
||||||
|
if (player_) {
|
||||||
|
cameraController_.SnapToTarget(*camera, player_->GetWorldBounds());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void WhiteboxScene::toggleDebugOverlay() {
|
void WhiteboxScene::toggleDebugOverlay() {
|
||||||
const bool enabled = !(debugFlags_.showStageGrid || debugFlags_.showCollision ||
|
const bool enabled = !(debugFlags_.showStageGrid || debugFlags_.showCollision ||
|
||||||
debugFlags_.showActorBounds ||
|
debugFlags_.showActorBounds ||
|
||||||
@@ -58,22 +83,17 @@ void WhiteboxScene::toggleDebugOverlay() {
|
|||||||
debugFlags_.showSpawnPoints = enabled;
|
debugFlags_.showSpawnPoints = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WhiteboxScene::updateCamera() {
|
void WhiteboxScene::updateCamera(float deltaTime) {
|
||||||
if (!player_) {
|
if (!player_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* renderer = frostbite2D::Application::get().getRenderer();
|
auto* camera = activeCamera();
|
||||||
if (!renderer || !renderer->getCamera()) {
|
if (!camera) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto playerPos = player_->GetTopLeftPosition();
|
cameraController_.Update(*camera, player_->GetWorldBounds(), deltaTime);
|
||||||
const float targetX = std::clamp(playerPos.x - 420.0f,
|
|
||||||
level_.cameraBounds.left(),
|
|
||||||
level_.cameraBounds.right() -
|
|
||||||
static_cast<float>(config::kVirtualWidth));
|
|
||||||
renderer->getCamera()->setPosition(frostbite2D::Vec2(targetX, 0.0f));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ns_game
|
} // namespace ns_game
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "../core/debug_flags.h"
|
#include "../core/debug_flags.h"
|
||||||
#include "../data/level_definition.h"
|
#include "../data/level_definition.h"
|
||||||
#include "../debug/debug_overlay.h"
|
#include "../debug/debug_overlay.h"
|
||||||
|
#include "camera_controller.h"
|
||||||
#include "../stage/stage_renderer.h"
|
#include "../stage/stage_renderer.h"
|
||||||
|
|
||||||
#include <frostbite2D/scene/scene.h>
|
#include <frostbite2D/scene/scene.h>
|
||||||
@@ -14,16 +15,19 @@ namespace ns_game {
|
|||||||
class WhiteboxScene : public frostbite2D::Scene {
|
class WhiteboxScene : public frostbite2D::Scene {
|
||||||
public:
|
public:
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void OnUpdate(float deltaTime) override;
|
void Update(float deltaTime) override;
|
||||||
bool OnEvent(const frostbite2D::Event& event) override;
|
bool OnEvent(const frostbite2D::Event& event) override;
|
||||||
void Render() override;
|
void Render() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
frostbite2D::Camera* activeCamera() const;
|
||||||
|
void configureCamera();
|
||||||
void toggleDebugOverlay();
|
void toggleDebugOverlay();
|
||||||
void updateCamera();
|
void updateCamera(float deltaTime);
|
||||||
|
|
||||||
LevelDefinition level_;
|
LevelDefinition level_;
|
||||||
DebugFlags debugFlags_;
|
DebugFlags debugFlags_;
|
||||||
|
CameraController cameraController_;
|
||||||
StageRenderer stageRenderer_;
|
StageRenderer stageRenderer_;
|
||||||
DebugOverlay debugOverlay_;
|
DebugOverlay debugOverlay_;
|
||||||
frostbite2D::Ptr<PlayerActor> player_;
|
frostbite2D::Ptr<PlayerActor> player_;
|
||||||
|
|||||||
Reference in New Issue
Block a user