Files
HelloVulkan/App/scripts/ThirdPersonCamera.cpp
T
atdunbg beba491bcb 阶段性合并:动画重定向、碎裂共享库、第三人称玩法、编辑器面板与 vendored ufbx
- 动画:HumanoidRig 三层架构(standard 标准槽 / dialects 方言映射 / mapper 配对),
  指/趾统一走世界增量两遍式烘焙;AnimationAttach 拆为纯算法 + facade;修正 HumanoidBasis
  行走基朝向与跨骨架去重、根运动通道
- 物理:CharacterController 角色互碰撞 / shapeOffset / 接触事件;PhysicsWorld 接触合成与
  字符冲量判定
- 渲染与材质:Model 多材质按材质拆子 mesh 与贴图路径解析、DDS 直传、GLB 内嵌纹理落盘、
  不透明通道 alpha 固定 1.0、glTF FlipUVs 对齐;SceneSerializer 支持 meshIndex 与材质覆盖
- 脚本:FractureLib 抽为共享库(HostView 桥接)、WallFracture 参数直绑、新增
  ThirdPersonCamera / Follower / WASDMove 玩法;移除 GameDirector / PlayerController / Target
- 编辑器:新增 GamepadPanel、LogPanel(LogRing),改进 Inspector / 材质 / 统计 / 视口面板
- 依赖:vendored ufbx(FBX 5.x/6.x/7.x 全版本解析,替代 assimp 对 FBX 6.1 的拒读)
2026-10-11 22:22:19 +08:00

189 lines
8.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// ThirdPersonCamera.cpp:第三人称环绕相机(UE SpringArm 同位)。
//
// 挂 primary 相机实体。鼠标增量 → yaw/pitch,相机位置 = 目标实体
// (按名解析,默认 "player")+ 球面偏移,朝向锁定目标。WASDMove 的
// 移动参考 = 本相机朝向——WASD 即"相机水平朝向的前后左右"(UE
// gameplay 语义)。枢轴水平硬跟、竖直带阻尼(FollowResponse):XZ
// 与角色零滞后(跑动不错位),Y 滤掉脚底每物理步的微弹跳(踩碎块/
// 撞墙)避免画面抖动。
//
// OnStart 从编辑器摆的机位反推 yaw/pitch/dist:编辑器摆哪,游戏就
// 从哪个角度开场。Esc 释放光标(调参),左键重捕继续玩。
//
#include <Engine/Scripting/Script.h>
#include <Engine/Core/Application.h>
#include <Engine/Core/Input.h>
#include <Engine/Core/Log.h>
#include <Engine/ECS/Components.h>
#include <Engine/ECS/Scene.h>
#include <entt/entt.hpp>
#include <glm/glm.hpp>
#include <cmath>
using namespace Engine;
namespace {
class ThirdPersonCamera final : public Engine::Scripting::ScriptBehavior {
public:
const char* Name() const override { return "ThirdPersonCamera"; }
void DeclareParams(Engine::Scripting::ParamBuilder& pb) override {
pb.Field("Sensitivity", &m_Sensitivity, 1.0f);
pb.Field("Distance", &m_Distance, 0.0f); // 0 = 吸编辑器机位
pb.Field("HeightOffset", &m_HeightOffset, 1.2f);
pb.Field("PitchMinDeg", &m_PitchMin, -35.0f);
pb.Field("PitchMaxDeg", &m_PitchMax, 75.0f);
pb.Field("GamepadTurnSpeed", &m_PadTurnSpeed, 180.0f);
// 跟随目标按名锁定(默认 "player"):场景有多个角色控制器
// 实体(如跟随 NPC)时,按名解析保证相机锁主角而非随机命中
pb.Field("TargetName", &m_TargetName, std::string("player"));
// 竖直跟随阻尼(1/s,指数逼近;0 = 硬跟=旧行为)。水平 XZ
// 恒硬跟(保证跑动时相机/角色不错位、无拖影——全轴阻尼会
// 让角色在高速移动时被"甩"出画面并抖动);仅 Y 阻尼吸收
// 角色脚底每物理步的微弹跳(踩碎块/撞墙),帧率无关
pb.Field("FollowResponse", &m_FollowResponse, 12.0f);
// 目标 Y 瞬移超过该距离(PIE 传送/回合复位)→ 直接吸附
pb.Field("SnapDistance", &m_SnapDistance, 3.0f);
}
void OnStart() override {
auto& reg = App().GetScene().GetRegistry();
// ---- 找目标:优先按名(TargetName)命中主角 ----
auto byName = reg.view<Engine::ECS::NameComponent>();
for (auto e : byName) {
if (byName.get<Engine::ECS::NameComponent>(e).name ==
m_TargetName) {
m_Target = e;
break;
}
}
// 名称未命中(未命名/改名)→ 回退第一个角色控制器实体
if (m_Target == entt::null) {
auto view = reg.view<Engine::ECS::CharacterControllerComponent>();
if (view.empty()) {
ENGINE_WARN(
"[ThirdPersonCamera] 场景无 CharacterController 实体,相机静止");
return;
}
m_Target = *view.begin();
}
// ---- 从编辑器机位反推 yaw/pitch/dist(摆哪开场在哪)----
const auto& camT = Transform();
const auto& tgtT =
App().GetScene().GetRegistry().get<
Engine::ECS::TransformComponent>(m_Target);
const glm::vec3 offset =
camT.GetPosition() -
(tgtT.GetPosition() +
glm::vec3(0.0f, m_HeightOffset, 0.0f));
const float dist = glm::length(offset);
if (dist > 1e-4f && m_Distance <= 0.0f) {
m_Distance = dist;
m_Pitch =
glm::degrees(std::asin(std::clamp(offset.y / dist,
-1.0f, 1.0f)));
m_Yaw = glm::degrees(std::atan2(offset.x, offset.z));
}
ENGINE_INFO(
"[ThirdPersonCamera] 目标已锁定,dist={:.2f} yaw={:.1f} pitch={:.1f}",
m_Distance, m_Yaw, m_Pitch);
Input::SetCursorCaptured(true);
}
void OnUpdate(float dt) override {
if (m_Target == entt::null) {
return;
}
// 光标管理:Esc 释放(Inspector 调参),左键重捕。
// 手柄玩家不碰鼠标——光标保持捕获态不影响摇杆输入
if (Input::IsKeyDown(Input::Key::Escape)) {
Input::SetCursorCaptured(false);
}
if (Input::IsMouseButtonDown(Input::MouseButton::Left)) {
Input::SetCursorCaptured(true);
}
// ---- 鼠标增量 + 右摇杆 → 球面角 ----
// 鼠标 = 相对增量(GetMouseDelta);右摇杆 = 持续量
//(Look 动作,满推 m_PadTurnSpeed 度/秒),两者叠加
float dx = 0.0f, dy = 0.0f;
Input::GetMouseDelta(dx, dy);
const glm::vec2 padLook = Input::ActionValue2("Look");
const float k = 0.12f * m_Sensitivity;
m_Yaw -= dx * k + padLook.x * m_PadTurnSpeed * dt;
m_Pitch += dy * k + padLook.y * m_PadTurnSpeed * dt;
m_Pitch = std::clamp(m_Pitch, m_PitchMin, m_PitchMax);
m_Yaw = std::fmod(m_Yaw, 360.0f);
// ---- 位置 = 目标枢轴 + 球面偏移 ----
// 水平 XZ 硬跟(与角色零滞后——跑动时相机/角色不错位、无
// 拖影),仅 Y 经阻尼逼近(FollowResponse):吸收目标脚底
// 每物理步的微弹跳(踩碎块/撞墙),相机竖直方向不再 1:1
// 抖动。首帧、瞬移(超 SnapDistance)或阻尼为 0 时直接吸附
// = 旧硬跟语义
const auto& tgtT =
App().GetScene().GetRegistry().get<
Engine::ECS::TransformComponent>(m_Target);
const glm::vec3 rawPivot =
tgtT.GetPosition() +
glm::vec3(0.0f, m_HeightOffset, 0.0f);
if (!m_FollowInit || m_FollowResponse <= 0.0f ||
std::abs(rawPivot.y - m_FollowPivotY) > m_SnapDistance) {
m_FollowPivotY = rawPivot.y;
m_FollowInit = true;
} else {
m_FollowPivotY +=
(rawPivot.y - m_FollowPivotY) *
(1.0f - std::exp(-m_FollowResponse * dt));
}
glm::vec3 pivot = rawPivot; // XZ 硬跟
pivot.y = m_FollowPivotY; // Y 阻尼
const float yawR = glm::radians(m_Yaw);
const float pitchR = glm::radians(m_Pitch);
const glm::vec3 dir( // 枢轴 → 相机(yaw 绕 Y,pitch 抬升)
std::sin(yawR) * std::cos(pitchR),
std::sin(pitchR),
std::cos(yawR) * std::cos(pitchR));
auto& camT = Transform();
camT.SetPosition(pivot + dir * m_Distance);
// ---- 朝向锁定目标 ----
// 引擎 YXZ 序(R = Ry·Rx)视向 = (-cosP sinY, sinP, -cosP cosY)
// 反解:pitch = asin(vd.y),yaw = atan2(-vd.x, -vd.z)
const glm::vec3 vd = glm::normalize(pivot - camT.GetPosition());
const float p = glm::degrees(std::asin(std::clamp(vd.y, -1.0f, 1.0f)));
const float y = glm::degrees(std::atan2(-vd.x, -vd.z));
camT.SetRotation(glm::vec3(p, y, 0.0f));
}
private:
float m_Sensitivity = 1.0f;
float m_Distance = 0.0f; // 0 = OnStart 吸编辑器机位
float m_HeightOffset = 1.2f; // 枢轴高度(瞄胸口)
float m_PitchMin = -35.0f;
float m_PitchMax = 75.0f;
float m_PadTurnSpeed = 180.0f; // 右摇杆满推角速度(度/秒)
std::string m_TargetName = "player"; // 目标实体名(NameComponent)
float m_FollowResponse = 12.0f; // 竖直跟随阻尼 1/s(0 = 硬跟)
float m_SnapDistance = 3.0f; // Y 瞬移吸附阈值 m
float m_FollowPivotY = 0.0f; // 阻尼后的枢轴高度(XZ 硬跟)
bool m_FollowInit = false; // 首帧吸附基准
float m_Yaw = 0.0f;
float m_Pitch = 20.0f;
entt::entity m_Target = entt::null;
};
ENGINE_REGISTER_SCRIPT(ThirdPersonCamera)
} // namespace