阶段性合并:动画重定向、碎裂共享库、第三人称玩法、编辑器面板与 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 的拒读)
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
//
|
||||
// Follower.cpp:跟随者行为(NPC 自动跟随主角,UE AIController 的简化位)。
|
||||
//
|
||||
// 挂在每个 NPC 实体上(各自 CharacterController + Animation)。主角由
|
||||
// TargetName 指定(默认 "player",按 NameComponent 名称匹配——引擎无实体
|
||||
// 引用参数类型,名称匹配是标准做法)。逻辑分四层,与 WASDMove 同一套手感:
|
||||
// - 距离闸门(迟滞):拉开到 StopDistance+ResumeGap 才起步,靠近到
|
||||
// StopDistance 即停——"到附近就停止跟随",边界不抖动
|
||||
// - 速度分档(迟滞):距离超过 RunDistance 改跑,回落到 0.85× 才退回
|
||||
// 走——"距离越大走/跑越快"
|
||||
// - 动量整形:真实速度逐帧逼近期望速度(起步渐快、到点刹停),帧率无关
|
||||
// - 动画按真实速度分档(idle/walk/run),迟滞防抖
|
||||
// 移动驱动走 PhysicsWorld 的 CharacterController 门面(水平喂意图速度,
|
||||
// Y 归物理重力);无 CC 时回退直接平移。多个跟随者用 Offset 错开站位。
|
||||
//
|
||||
|
||||
#include <Engine/Scripting/Script.h>
|
||||
#include <Engine/Core/Application.h>
|
||||
#include <Engine/Physics/PhysicsWorld.h>
|
||||
|
||||
#include "ScriptLog.h"
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using namespace Engine;
|
||||
|
||||
namespace {
|
||||
|
||||
class Follower final : public Engine::Scripting::ScriptBehavior {
|
||||
public:
|
||||
const char* Name() const override { return "Follower"; }
|
||||
|
||||
void DeclareParams(Engine::Scripting::ParamBuilder& pb) override {
|
||||
pb.Field("TargetName", &m_TargetName, std::string("player"));
|
||||
// 相对目标的站位偏移(世界系;多个跟随者错开,防叠一起)
|
||||
pb.Field("Offset", &m_Offset, glm::vec3{0.0f});
|
||||
pb.Field("WalkSpeed", &m_WalkSpeed, 3.0f); // 走路 m/s
|
||||
pb.Field("RunSpeed", &m_RunSpeed, 6.0f); // 跑步 m/s
|
||||
pb.Field("StopDistance", &m_StopDistance, 2.5f); // 到点停(米)
|
||||
pb.Field("ResumeGap", &m_ResumeGap, 1.5f); // 再起步的距离余量
|
||||
pb.Field("RunDistance", &m_RunDistance, 10.0f); // 超过即改跑(米)
|
||||
pb.Field("TurnResponse", &m_TurnResponse, 10.0f);// 转向阻尼
|
||||
pb.Field("AccelResponse", &m_AccelResponse, 6.0f); // 加速响应
|
||||
pb.Field("StopResponse", &m_StopResponse, 12.0f); // 制动响应
|
||||
// clip 按名引用(Unity anim.Play("Walk") 语义)
|
||||
pb.Field("IdleClip", &m_IdleClip, std::string("idle"));
|
||||
pb.Field("WalkClip", &m_WalkClip, std::string("walk"));
|
||||
pb.Field("RunClip", &m_RunClip, std::string("run"));
|
||||
}
|
||||
|
||||
void OnStart() override {
|
||||
if (ResolveTarget()) {
|
||||
SLOG_INFO("[Follower] 跟随目标 '{}' 就绪", m_TargetName);
|
||||
} else {
|
||||
SLOG_WARN("[Follower] 未找到目标 '{}'(按 NameComponent 匹配)",
|
||||
m_TargetName);
|
||||
}
|
||||
}
|
||||
|
||||
void OnUpdate(float dt) override {
|
||||
auto& reg = App().GetScene().GetRegistry();
|
||||
|
||||
// 目标失效(被销毁/未就绪)→ 重解析;仍无则静止待命
|
||||
if (m_Target == entt::null || !reg.valid(m_Target)) {
|
||||
m_Target = entt::null;
|
||||
if (!ResolveTarget()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const glm::vec3 tp =
|
||||
reg.get<Engine::ECS::TransformComponent>(m_Target)
|
||||
.GetPosition();
|
||||
const glm::vec3 self = Transform().GetPosition();
|
||||
glm::vec3 delta = (tp + m_Offset) - self;
|
||||
delta.y = 0.0f; // 只取水平面(角色不俯仰)
|
||||
const float dist = glm::length(delta);
|
||||
|
||||
// ---- 距离闸门(迟滞):到点停、拉开距离再起步 ----
|
||||
if (!m_Moving) {
|
||||
if (dist > m_StopDistance + m_ResumeGap) {
|
||||
m_Moving = true;
|
||||
}
|
||||
} else if (dist <= m_StopDistance) {
|
||||
m_Moving = false;
|
||||
}
|
||||
|
||||
// ---- 速度分档(迟滞):远则跑、近则走 ----
|
||||
glm::vec3 desired{0.0f};
|
||||
if (m_Moving && dist > 1e-4f) {
|
||||
if (!m_Sprinting && dist >= m_RunDistance) {
|
||||
m_Sprinting = true;
|
||||
} else if (m_Sprinting && dist <= m_RunDistance * 0.85f) {
|
||||
m_Sprinting = false;
|
||||
}
|
||||
const float speed = m_Sprinting ? m_RunSpeed : m_WalkSpeed;
|
||||
desired = (delta / dist) * speed;
|
||||
}
|
||||
|
||||
// ---- 动量整形:真实速度逐帧逼近期望(起步渐快/到点刹停)----
|
||||
const float response =
|
||||
glm::length(desired) > 0.01f ? m_AccelResponse : m_StopResponse;
|
||||
m_PlanarVel +=
|
||||
(desired - m_PlanarVel) * (1.0f - std::exp(-response * dt));
|
||||
if (glm::length(desired - m_PlanarVel) < 0.02f) {
|
||||
m_PlanarVel = desired; // 贴齐,防低速蠕动
|
||||
}
|
||||
|
||||
// ---- 驱动:角色控制器喂水平速度(Y 归物理);无则直接平移 ----
|
||||
auto& phy = App().GetPhysics();
|
||||
const bool hasCC =
|
||||
TryGetComponent<Engine::ECS::CharacterControllerComponent>() !=
|
||||
nullptr;
|
||||
if (hasCC) {
|
||||
const glm::vec3 cur = phy.GetCharacterVelocity(EntityBits());
|
||||
phy.SetCharacterVelocity(
|
||||
EntityBits(), {m_PlanarVel.x, cur.y, m_PlanarVel.z});
|
||||
} else {
|
||||
Transform().SetPosition(self + m_PlanarVel * dt);
|
||||
}
|
||||
|
||||
// ---- 朝向:面向真实速度方向(模型默认 +Z),低速锁定 ----
|
||||
if (glm::length(m_PlanarVel) > 0.3f) {
|
||||
const float targetYaw =
|
||||
std::atan2(m_PlanarVel.x, m_PlanarVel.z);
|
||||
const glm::vec3 modelFwd =
|
||||
Transform().GetRotationQuat() * glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
const float curYaw = std::atan2(modelFwd.x, modelFwd.z);
|
||||
const float dyaw = std::atan2(std::sin(targetYaw - curYaw),
|
||||
std::cos(targetYaw - curYaw));
|
||||
const float k = 1.0f - std::exp(-m_TurnResponse * dt);
|
||||
Transform().SetRotationQuat(
|
||||
glm::angleAxis(curYaw + dyaw * k, glm::vec3(0, 1, 0)));
|
||||
}
|
||||
|
||||
// ---- 动画:按真实速度分档(迟滞防临界抖)----
|
||||
const float planarSpeed = glm::length(m_PlanarVel);
|
||||
std::string target;
|
||||
if (planarSpeed < 0.15f) {
|
||||
target = m_IdleClip;
|
||||
} else if (m_CurClip == m_RunClip) {
|
||||
target = (planarSpeed >= m_RunSpeed * 0.7f) ? m_RunClip
|
||||
: m_WalkClip;
|
||||
} else {
|
||||
target = (planarSpeed >= m_RunSpeed * 0.9f) ? m_RunClip
|
||||
: m_WalkClip;
|
||||
}
|
||||
if (target != m_CurClip) {
|
||||
if (auto* anim =
|
||||
TryGetComponent<Engine::ECS::AnimationComponent>();
|
||||
anim != nullptr && anim->Play(target, 0.35f)) {
|
||||
m_CurClip = target; // 播放成功才记账(模型未就绪下帧重试)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// 按 NameComponent 名称解析目标(跳过自身);失败返回 false
|
||||
bool ResolveTarget() {
|
||||
auto& reg = App().GetScene().GetRegistry();
|
||||
auto view = reg.view<Engine::ECS::NameComponent>();
|
||||
for (auto e : view) {
|
||||
if (static_cast<u32>(entt::to_integral(e)) == EntityBits()) {
|
||||
continue;
|
||||
}
|
||||
if (view.get<Engine::ECS::NameComponent>(e).name ==
|
||||
m_TargetName) {
|
||||
m_Target = e;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string m_TargetName = "player";
|
||||
glm::vec3 m_Offset{0.0f};
|
||||
float m_WalkSpeed = 3.0f;
|
||||
float m_RunSpeed = 6.0f;
|
||||
float m_StopDistance = 2.5f;
|
||||
float m_ResumeGap = 1.5f;
|
||||
float m_RunDistance = 10.0f;
|
||||
float m_TurnResponse = 10.0f;
|
||||
float m_AccelResponse = 6.0f;
|
||||
float m_StopResponse = 12.0f;
|
||||
std::string m_IdleClip = "idle";
|
||||
std::string m_WalkClip = "walk";
|
||||
std::string m_RunClip = "run";
|
||||
|
||||
entt::entity m_Target = entt::null; // 运行时状态(非参数)
|
||||
bool m_Moving = false; // 距离闸门状态(迟滞)
|
||||
bool m_Sprinting = false; // 速度档状态(迟滞)
|
||||
glm::vec3 m_PlanarVel{0.0f}; // 平滑后的水平真实速度
|
||||
std::string m_CurClip = "idle"; // 当前 clip(边沿检测基准)
|
||||
};
|
||||
|
||||
ENGINE_REGISTER_SCRIPT(Follower)
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user