Files
HelloVulkan/App/scripts/WASDMove.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

280 lines
14 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.
//
// 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