- 动画: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 的拒读)
189 lines
8.6 KiB
C++
189 lines
8.6 KiB
C++
//
|
||
// 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
|