Add player camera controller
This commit is contained in:
@@ -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/graphics/camera.h>
|
||||
#include <frostbite2D/graphics/renderer.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ns_game {
|
||||
|
||||
@@ -18,11 +17,12 @@ void WhiteboxScene::onEnter() {
|
||||
player_->SetTopLeftPosition(level_.playerSpawn);
|
||||
player_->SetMovementWorld(&level_.collision);
|
||||
AddChild(player_);
|
||||
configureCamera();
|
||||
}
|
||||
|
||||
void WhiteboxScene::OnUpdate(float deltaTime) {
|
||||
(void)deltaTime;
|
||||
updateCamera();
|
||||
void WhiteboxScene::Update(float deltaTime) {
|
||||
frostbite2D::Scene::Update(deltaTime);
|
||||
updateCamera(deltaTime);
|
||||
}
|
||||
|
||||
bool WhiteboxScene::OnEvent(const frostbite2D::Event& event) {
|
||||
@@ -46,6 +46,31 @@ void WhiteboxScene::Render() {
|
||||
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() {
|
||||
const bool enabled = !(debugFlags_.showStageGrid || debugFlags_.showCollision ||
|
||||
debugFlags_.showActorBounds ||
|
||||
@@ -58,22 +83,17 @@ void WhiteboxScene::toggleDebugOverlay() {
|
||||
debugFlags_.showSpawnPoints = enabled;
|
||||
}
|
||||
|
||||
void WhiteboxScene::updateCamera() {
|
||||
void WhiteboxScene::updateCamera(float deltaTime) {
|
||||
if (!player_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* renderer = frostbite2D::Application::get().getRenderer();
|
||||
if (!renderer || !renderer->getCamera()) {
|
||||
auto* camera = activeCamera();
|
||||
if (!camera) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto playerPos = player_->GetTopLeftPosition();
|
||||
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));
|
||||
cameraController_.Update(*camera, player_->GetWorldBounds(), deltaTime);
|
||||
}
|
||||
|
||||
} // namespace ns_game
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "../core/debug_flags.h"
|
||||
#include "../data/level_definition.h"
|
||||
#include "../debug/debug_overlay.h"
|
||||
#include "camera_controller.h"
|
||||
#include "../stage/stage_renderer.h"
|
||||
|
||||
#include <frostbite2D/scene/scene.h>
|
||||
@@ -14,16 +15,19 @@ namespace ns_game {
|
||||
class WhiteboxScene : public frostbite2D::Scene {
|
||||
public:
|
||||
void onEnter() override;
|
||||
void OnUpdate(float deltaTime) override;
|
||||
void Update(float deltaTime) override;
|
||||
bool OnEvent(const frostbite2D::Event& event) override;
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
frostbite2D::Camera* activeCamera() const;
|
||||
void configureCamera();
|
||||
void toggleDebugOverlay();
|
||||
void updateCamera();
|
||||
void updateCamera(float deltaTime);
|
||||
|
||||
LevelDefinition level_;
|
||||
DebugFlags debugFlags_;
|
||||
CameraController cameraController_;
|
||||
StageRenderer stageRenderer_;
|
||||
DebugOverlay debugOverlay_;
|
||||
frostbite2D::Ptr<PlayerActor> player_;
|
||||
|
||||
Reference in New Issue
Block a user