- 动画: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 的拒读)
280 lines
14 KiB
C++
280 lines
14 KiB
C++
//
|
||
// WASDMove.cpp:第三人称移动(WASD/手柄摇杆,相机朝向参考,UE gameplay 语义)。
|
||
//
|
||
// 输入走引擎具名动作层(Move/Jump/Run 默认绑定):WASD + 方向键 +
|
||
// 手柄左摇杆移动(引擎径向死区),Space/手柄 A 跳;跑 = Run 动作
|
||
//(Shift / 手柄 L3 按下左摇杆)。摇杆模拟量直接驱动动量整形(轻推慢走)。
|
||
//
|
||
// 移动语义分派(按实体配置自动选择,优先级从上到下):
|
||
// - CharacterControllerComponent → 角色控制器路径(扫掠运动学:
|
||
// 水平喂意图速度 / 重力自动积分 / Space 跳跃,Unity
|
||
// CharacterController.Move 同构)
|
||
// - Dynamic 刚体 → 走物理速度门面(每帧喂水平速度,Y 归物理——
|
||
// 重力/跳跃不受影响;松键喂零速 = 立即停,Unity velocity 语义)
|
||
// - 都没有 → 直接平移 Transform
|
||
//
|
||
// 编辑器形态键位天然不冲突:RMB 按住 = 相机吃 WASD 飞行(脚本也读
|
||
// 到按键 → 同时在动,测移动时松开 RMB 即可);游戏形态 WASD 常开。
|
||
//
|
||
|
||
#include <Engine/Scripting/Script.h>
|
||
#include <Engine/Core/Application.h>
|
||
#include <Engine/Physics/PhysicsWorld.h>
|
||
#include <Engine/Core/Input.h>
|
||
#include <Engine/Debug/DebugDraw.h>
|
||
#include "ScriptDebug.h"
|
||
#include "ScriptLog.h"
|
||
#include <Engine/Renderer/Renderer.h>
|
||
#include <Engine/Renderer/EditorCamera.h>
|
||
|
||
#include <glm/glm.hpp>
|
||
#include <glm/gtc/quaternion.hpp>
|
||
|
||
#include <cmath>
|
||
|
||
using namespace Engine; // Input 门面(统一输入入口)
|
||
|
||
namespace {
|
||
|
||
class WASDMove final : public Engine::Scripting::ScriptBehavior {
|
||
public:
|
||
const char* Name() const override { return "WASDMove"; }
|
||
|
||
void DeclareParams(Engine::Scripting::ParamBuilder& pb) override {
|
||
pb.Field("Speed", &m_Speed, 3.0f); // 走路 m/s
|
||
pb.Field("RunSpeed", &m_RunSpeed, 6.0f); // 跑步 m/s
|
||
pb.Field("TurnResponse", &m_TurnResponse, 12.0f); // 转向阻尼
|
||
pb.Field("AccelResponse", &m_AccelResponse, 8.0f); // 加速/转向响应
|
||
pb.Field("StopResponse", &m_StopResponse, 14.0f); // 松手制动响应
|
||
pb.Field("JumpSpeed", &m_JumpSpeed, 6.0f); // 起跳初速 m/s
|
||
// clip 按名引用(Unity anim.Play("Walk") 语义)——名字在
|
||
// Inspector 里改,attach 顺序变了脚本零改动
|
||
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 {
|
||
SLOG_TRACE("hello");
|
||
SLOG_INFO("hello");
|
||
SLOG_DEBUG("hello");
|
||
SLOG_WARN("hello");
|
||
SLOG_ERROR("hello");
|
||
SLOG_INFO("[WASDMove] 就绪:走 {:.1f} m/s / 跑 {:.1f} m/s / 跳 {:.1f} m/s",
|
||
m_Speed, m_RunSpeed, m_JumpSpeed);
|
||
}
|
||
|
||
void OnUpdate(float dt) override {
|
||
// 位移方向 = 视线水平投影(W 前进 / A 左 / D 右),UE Scene
|
||
// view 移动惯例;Y 锁 0(升降/跳跃归物理或将来扩展)。
|
||
// 输入 = 具名动作层(Unity6 Input System / UE5 Enhanced Input
|
||
// 同位):Move 默认混绑 WASD/方向键/左摇杆(引擎做径向死区 +
|
||
// 单位圆钳制,斜向不超速),Jump 混绑 Space/手柄 A——键盘
|
||
// 手柄一套代码,将来改键/加绑定不进脚本
|
||
// 移动参考 = 场景 primary 相机(第三人称相机脚本每帧写它
|
||
// 的朝向——玩家看到的视角就是移动参考);无相机实体时
|
||
//(纯独立形态)回退编辑器相机
|
||
glm::quat cq;
|
||
bool refCam = false;
|
||
auto camView = App().GetScene()
|
||
.GetRegistry()
|
||
.view<Engine::ECS::CameraComponent>();
|
||
for (auto e : camView) {
|
||
const auto& cam =
|
||
camView.get<Engine::ECS::CameraComponent>(e);
|
||
if (cam.primary && cam.active) {
|
||
cq = App().GetScene()
|
||
.GetRegistry()
|
||
.get<Engine::ECS::TransformComponent>(e)
|
||
.GetRotationQuat();
|
||
refCam = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!refCam) {
|
||
cq = App().GetRenderer()
|
||
.GetEditorCamera()
|
||
.GetTransform()
|
||
.GetRotationQuat();
|
||
}
|
||
const glm::vec3 fwd = cq * glm::vec3(0.0f, 0.0f, -1.0f);
|
||
const glm::vec3 right = cq * glm::vec3(1.0f, 0.0f, 0.0f);
|
||
|
||
// 约定:X+ = 右(D),Y- = 前(W);方向合成 = fwd*(-Y) + right*(+X)
|
||
const glm::vec2 mvRaw = Input::ActionValue2("Move");
|
||
glm::vec2 mv = mvRaw;
|
||
if (glm::length(mv) > 1.0f) mv = glm::normalize(mv);
|
||
|
||
glm::vec3 move = fwd * (-mv.y) + right * mv.x;
|
||
move.y = 0.0f;
|
||
// 只钳超速,不放大——摇杆模拟量(幅度 <1)保留进速度:
|
||
// 轻推慢走、推满全速(归一化会把幅度吃掉,一动就满速)
|
||
if (glm::length(move) > 1.0f) {
|
||
move = glm::normalize(move);
|
||
}
|
||
// 走/跑:Run 动作(默认绑 Shift + 手柄 L3,按下左摇杆冲刺
|
||
// 的 Xbox 惯例)。不判"摇杆推满"——键盘 WASD 幅度恒 1.0,
|
||
// 那样判会把走路误判成跑
|
||
const bool running = Input::ActionDown("Run");
|
||
const float speed = running ? m_RunSpeed : m_Speed;
|
||
const glm::vec3 desired = move * speed; // 期望速度(输入意图)
|
||
|
||
auto& phy = App().GetPhysics();
|
||
const bool hasCC =
|
||
TryGetComponent<Engine::ECS::CharacterControllerComponent>() !=
|
||
nullptr;
|
||
const bool grounded =
|
||
hasCC ? phy.IsCharacterGrounded(EntityBits()) : true;
|
||
|
||
// ---- 动量整形(惯性的本体,纯脚本):真实速度逐帧逼近期
|
||
// 望速度——起步渐快、转向沿弧线漂移、折返经零减速再反向、
|
||
// 松手滑行刹车。加速/转向用 AccelResponse,松手用更硬的
|
||
// StopResponse(角色要"想停就停");空中操纵性降到 1/4
|
||
//(起跳后不宜急转弯)。指数逼近,帧率无关
|
||
const bool moving = glm::length(mvRaw) > 0.1f;
|
||
float response = moving ? m_AccelResponse : m_StopResponse;
|
||
if (!grounded) {
|
||
response *= 0.25f;
|
||
}
|
||
m_PlanarVel +=
|
||
(desired - m_PlanarVel) * (1.0f - std::exp(-response * dt));
|
||
if (glm::length(desired - m_PlanarVel) < 0.02f) {
|
||
m_PlanarVel = desired; // 贴齐,防低速蠕动
|
||
}
|
||
|
||
// ---- 路径 1:角色控制器(挂了 CharacterControllerComponent)----
|
||
// 水平喂动量整形后的真实速度;Y 保留现值——重力由控制器每
|
||
// 步自动积分,每帧覆写 Y 会把下落速度清零(空中变悬浮)
|
||
if (hasCC) {
|
||
const glm::vec3 cur = phy.GetCharacterVelocity(EntityBits());
|
||
phy.SetCharacterVelocity(
|
||
EntityBits(), {m_PlanarVel.x, cur.y, m_PlanarVel.z});
|
||
|
||
// 跳跃:动作层边沿(Pressed = prev 0 → cur 非 0,键盘
|
||
// Space / 手柄 A 都产生一次上升沿)。IsGrounded 防空中连跳
|
||
if (Input::ActionPressed("Jump") &&
|
||
phy.IsCharacterGrounded(EntityBits())) {
|
||
phy.Jump(EntityBits(), m_JumpSpeed);
|
||
SLOG_INFO("[WASDMove] jump(着地起跳 {:.1f} m/s)",
|
||
m_JumpSpeed);
|
||
}
|
||
} else if (auto* rb =
|
||
TryGetComponent<Engine::ECS::RigidBodyComponent>();
|
||
rb != nullptr && rb->bodyValid &&
|
||
rb->type == Engine::ECS::RigidBodyComponent::BodyType::Dynamic) {
|
||
// 物理驱动:只覆写水平速度,Y 保留物理积分离散结果
|
||
//(整体喂 0 会抵消重力 → 空中物体悬浮)
|
||
const glm::vec3 cur = phy.GetLinearVelocity(EntityBits());
|
||
phy.SetLinearVelocity(
|
||
EntityBits(), {m_PlanarVel.x, cur.y, m_PlanarVel.z});
|
||
} else {
|
||
Transform().SetPosition(Transform().GetPosition() +
|
||
m_PlanarVel * dt);
|
||
}
|
||
|
||
// ---- 朝向:面向"真实速度"方向(不是输入方向,模型默认 +Z)----
|
||
// 这是消"平移感"的关键:动量整形后速度滞后于输入,若面朝
|
||
// 输入方向,身体朝前脚却在侧滑——正是割裂感的来源。面朝
|
||
// 真实速度 = 脚底朝向与位移一致,转向呈现弧线漂移。速度
|
||
// 低于阈值(起步瞬间/折返过零)锁定上帧朝向:折返表现成
|
||
// U 弯回转而不是原地瞬转。只取水平面(角色不俯仰翻滚)
|
||
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)));
|
||
}
|
||
|
||
// ---- 动画:按真实速度分档(迟滞防临界抖)----
|
||
// 判据 = 动量整形后的真实水平速度,不是输入原始值:起步
|
||
// 滑行期已是 walk,松手刹车期保持 walk 直到真停——动画与
|
||
// 位移观感一致,按键瞬通瞬断不再瞬间换挡。run 双阈值迟滞
|
||
//(进 0.9×RunSpeed / 退 0.7×RunSpeed):摇杆在阈值附近晃
|
||
// 不抖档。引擎 Play(名) 自带相位对齐 + crossfade,切档
|
||
// 突兀感只剩判据本身的问题
|
||
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)) {
|
||
// fade 0.35s:locomotion 混合的经验值(Unity 默认
|
||
// 0.25 太短,0.5+ 显拖沓);相位同步在引擎侧做
|
||
m_CurClip = target; // 播放成功才记账(模型未就绪下帧重试)
|
||
}
|
||
}
|
||
|
||
const glm::vec3 anchor =
|
||
(hasCC ? phy.GetCharacterPosition(EntityBits())
|
||
: Transform().GetPosition()) +
|
||
glm::vec3(0.0f, 0.05f, 0.0f);
|
||
|
||
// ---- 运动线(ScriptDbg 封装:像素线宽 3px / 当帧瞬时 /
|
||
// 透视直显已固化为默认值,不用记参数序):蓝 = 输入期
|
||
// 望速度,绿 = 动量整形后的真实速度——同一起点,夹角
|
||
// 即惯性滞量(转向弧线/U 弯/刹车可视化)
|
||
ScriptDbg::Arrow(anchor, anchor + desired * 0.2f, 0x2B5FD0);
|
||
ScriptDbg::Arrow(anchor, anchor + m_PlanarVel * 0.2f, 0x1E9E42);
|
||
|
||
// ---- 力学线:重力(红,下)+ 接触反力(黄,上,仅着地)。
|
||
// 长度 ∝ gravityScale:静置时红黄等长 = 力平衡;起跳
|
||
// 离地黄箭头消失 = 离地即失支撑;gravityScale 调大两
|
||
// 箭同步变长(重量感可视化)
|
||
float gs = 1.0f;
|
||
if (auto* cc = TryGetComponent<
|
||
Engine::ECS::CharacterControllerComponent>()) {
|
||
gs = cc->gravityScale;
|
||
}
|
||
const float gLen = 0.5f * gs;
|
||
ScriptDbg::Gravity(anchor, gLen);
|
||
ScriptDbg::Support(anchor, grounded ? gLen : 0.0f);
|
||
|
||
const glm::vec3 vel = hasCC
|
||
? phy.GetCharacterVelocity(EntityBits())
|
||
: m_PlanarVel;
|
||
if (dt > 1e-5f) {
|
||
m_AccelS = glm::mix(m_AccelS, (vel - m_PrevVel) / dt, 0.35f);
|
||
}
|
||
m_PrevVel = vel;
|
||
ScriptDbg::Accel(anchor, m_AccelS);
|
||
|
||
}
|
||
|
||
void DebugShow() {
|
||
|
||
}
|
||
|
||
private:
|
||
float m_Speed = 3.0f; // 走路 m/s(声明默认;存档值覆盖)
|
||
float m_RunSpeed = 6.0f; // 跑步 m/s(Shift 按住)
|
||
float m_TurnResponse = 12.0f; // 转向阻尼强度(越大越跟手)
|
||
float m_AccelResponse = 8.0f; // 加速/转向响应(动量整形,越小越"重")
|
||
float m_StopResponse = 14.0f; // 松手制动响应(越大刹车越硬)
|
||
glm::vec3 m_PlanarVel{0.0f}; // 平滑后的水平真实速度(动量状态)
|
||
float m_JumpSpeed = 6.0f; // 起跳初速(配合组件 gravityScale 调手感)
|
||
std::string m_IdleClip = "idle"; // clip 名(Inspector 可改)
|
||
std::string m_WalkClip = "walk";
|
||
std::string m_RunClip = "run";
|
||
std::string m_CurClip = "idle"; // 当前目标 clip(边沿检测基准)
|
||
glm::vec3 m_PrevVel{0.0f}; // 上帧测量速度(加速度差分基准)
|
||
glm::vec3 m_AccelS{0.0f}; // 平滑测量加速度(EMA 0.35,调试图)
|
||
};
|
||
|
||
ENGINE_REGISTER_SCRIPT(WASDMove)
|
||
|
||
} // namespace
|