新增调试绘制体系:DebugDraw 服务 + 物理调试 pass + 脚本侧力学线
通用图元(线/箭头四翼头/球/胶囊/盒)瞬时+持久双缓冲聚合; 物理 pass 画刚体速度/重力/接触反力;角色 collider 与力学 线归位(引擎只画资产、脚本画行为);线宽按设备上限钳制
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// ScriptDebug.h:脚本侧力学调试封装(Unity Gizmos/Debug.DrawRay 的
|
||||
// 游戏内同位物)。把调试惯例固化成默认值,脚本不用记参数序:
|
||||
// - width = 像素(≥1),默认 3px
|
||||
// - duration 恒 0(OnUpdate 每帧重画,事件类需求再裸调 Debug::)
|
||||
// - depthTest 恒 false(跟角色跑的线被模型遮挡就失去意义)
|
||||
// - headSize = 世界单位(箭头翼头空间尺寸),默认 0.08
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Engine/Debug/DebugDraw.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace ScriptDbg {
|
||||
|
||||
// 基础箭头:colorHex 语义色见下(重力红/支撑黄/意图蓝/真实绿)
|
||||
inline void Arrow(const glm::vec3& from, const glm::vec3& to,
|
||||
Engine::u32 colorHex, float width = 3.0f) {
|
||||
Engine::Debug::Arrow(from, to, Engine::Debug::Rgb(colorHex),
|
||||
/*headSize*/ 0.08f, /*width*/ width,
|
||||
/*duration*/ 0.0f, /*depthTest*/ false);
|
||||
}
|
||||
|
||||
// 重力:anchor=脚底,len=视觉长度。建议 ∝ gravityScale,且与支撑
|
||||
// 力同长——静置时红(下)黄(上)等长 = 力平衡,一眼可读
|
||||
inline void Gravity(const glm::vec3& anchor, float len) {
|
||||
Arrow(anchor, anchor - glm::vec3(0.0f, len, 0.0f), 0xDC3C3C);
|
||||
}
|
||||
|
||||
// 接触反力(法向支撑):只在有接触时存在——空中传 len=0 内部直接
|
||||
// 不画(力学语义:离地即失支撑)
|
||||
inline void Support(const glm::vec3& anchor, float len) {
|
||||
if (len > 1e-3f) {
|
||||
Arrow(anchor, anchor + glm::vec3(0.0f, len, 0.0f), 0xD9C33C);
|
||||
}
|
||||
}
|
||||
|
||||
// 加速度(深橙):脚本侧差分出的测量加速度(F=ma 的 a)。scale =
|
||||
// m/s² → 世界长度换算;mag 低于 minMag 视为静置/匀速("没有线"=
|
||||
// 没有加速度,本身就是信息),len 钳到 maxLen 防冲击尖峰爆屏
|
||||
inline void Accel(const glm::vec3& anchor, const glm::vec3& accel,
|
||||
float scale = 0.05f, float maxLen = 2.0f) {
|
||||
const float mag = glm::length(accel);
|
||||
if (mag > 0.1f) {
|
||||
const float len = glm::min(mag * scale, maxLen);
|
||||
Arrow(anchor, anchor + (accel / mag) * len, 0xE0640A);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ScriptDbg
|
||||
+174
-15
@@ -1,10 +1,13 @@
|
||||
//
|
||||
// WASDMove.cpp:WASD 控制实体移动(相机水平朝向参考,UE gameplay 语义)。
|
||||
//
|
||||
// 移动语义分派:
|
||||
// 移动语义分派(按实体配置自动选择,优先级从上到下):
|
||||
// - CharacterControllerComponent → 角色控制器路径(扫掠运动学:
|
||||
// 水平喂意图速度 / 重力自动积分 / Space 跳跃,Unity
|
||||
// CharacterController.Move 同构)
|
||||
// - Dynamic 刚体 → 走物理速度门面(每帧喂水平速度,Y 归物理——
|
||||
// 重力/跳跃不受影响;松键喂零速 = 立即停,Unity velocity 语义)
|
||||
// - 无刚体 → 直接平移 Transform
|
||||
// - 都没有 → 直接平移 Transform
|
||||
//
|
||||
// 编辑器形态键位天然不冲突:RMB 按住 = 相机吃 WASD 飞行(脚本也读
|
||||
// 到按键 → 同时在动,测移动时松开 RMB 即可);游戏形态 WASD 常开。
|
||||
@@ -12,14 +15,18 @@
|
||||
|
||||
#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 <Engine/Renderer/Renderer.h>
|
||||
#include <Engine/Renderer/EditorCamera.h>
|
||||
#include <Engine/Physics/PhysicsWorld.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using namespace Engine; // Input 门面(统一输入入口)
|
||||
|
||||
namespace {
|
||||
@@ -28,10 +35,27 @@ namespace {
|
||||
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 OnUpdate(float dt) override {
|
||||
// 位移方向 = 视线水平投影(W 前进 / A 左 / D 右),UE Scene
|
||||
// view 移动惯例;Y 锁 0(升降/跳跃归物理或将来扩展)。
|
||||
// 键鼠查询统一走 Input 门面(脚本层零 GLFW 依赖)
|
||||
// 输入直读按键(Unity Input.GetKey(KeyCode.W) 同形);UI 捕获
|
||||
// 键盘时门面静默。要手柄/运行时改键再换动作层
|
||||
//(Input::ActionValue2("Move")——Unity6 新 Input System /
|
||||
// UE5 Enhanced Input 对应物),本示例保持最简
|
||||
const glm::quat cq = App().GetRenderer()
|
||||
.GetEditorCamera()
|
||||
.GetTransform()
|
||||
@@ -39,32 +63,167 @@ namespace {
|
||||
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);
|
||||
|
||||
glm::vec3 move(0.0f);
|
||||
if (Input::IsKeyDown(Input::Key::W)) move += fwd;
|
||||
if (Input::IsKeyDown(Input::Key::S)) move -= fwd;
|
||||
if (Input::IsKeyDown(Input::Key::D)) move += right;
|
||||
if (Input::IsKeyDown(Input::Key::A)) move -= right;
|
||||
// 约定:X+ = 右(D),Y- = 前(W);方向合成 = fwd*(-Y) + right*(+X)
|
||||
glm::vec2 mv(0.0f);
|
||||
if (Input::IsKeyDown(Input::Key::W)) mv.y -= 1.0f;
|
||||
if (Input::IsKeyDown(Input::Key::S)) mv.y += 1.0f;
|
||||
if (Input::IsKeyDown(Input::Key::A)) mv.x -= 1.0f;
|
||||
if (Input::IsKeyDown(Input::Key::D)) mv.x += 1.0f;
|
||||
if (glm::length(mv) > 1.0f) mv = glm::normalize(mv);
|
||||
|
||||
glm::vec3 move = fwd * (-mv.y) + right * mv.x;
|
||||
move.y = 0.0f;
|
||||
if (glm::length(move) > 1e-4f) {
|
||||
move = glm::normalize(move);
|
||||
}
|
||||
const glm::vec3 vel = move * m_Speed;
|
||||
// 走/跑
|
||||
const bool moving = glm::length(mv) > 0.1f;
|
||||
const bool running =
|
||||
moving && Input::IsKeyDown(Input::Key::LeftShift);
|
||||
const float speed = running ? m_RunSpeed : m_Speed;
|
||||
const glm::vec3 desired = move * speed; // 期望速度(输入意图)
|
||||
|
||||
if (auto* rb = TryGetComponent<Engine::ECS::RigidBodyComponent>();
|
||||
auto& phy = App().GetPhysics();
|
||||
const bool hasCC =
|
||||
TryGetComponent<Engine::ECS::CharacterControllerComponent>() !=
|
||||
nullptr;
|
||||
const bool grounded =
|
||||
hasCC ? phy.IsCharacterGrounded(EntityBits()) : true;
|
||||
|
||||
// ---- 动量整形(惯性的本体,纯脚本):真实速度逐帧逼近期
|
||||
// 望速度——起步渐快、转向沿弧线漂移、折返经零减速再反向、
|
||||
// 松手滑行刹车。加速/转向用 AccelResponse,松手用更硬的
|
||||
// StopResponse(角色要"想停就停");空中操纵性降到 1/4
|
||||
//(起跳后不宜急转弯)。指数逼近,帧率无关
|
||||
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});
|
||||
|
||||
// 跳跃:着地 + 上升沿触发。IsKeyDown 是电平量(按住
|
||||
// 每帧都真),不设边沿会变成贴地火箭;IsGrounded 防
|
||||
// 空中连跳
|
||||
const bool jumpDown = Input::IsKeyDown(Input::Key::Space);
|
||||
if (jumpDown && !m_JumpWasDown &&
|
||||
phy.IsCharacterGrounded(EntityBits())) {
|
||||
phy.Jump(EntityBits(), m_JumpSpeed);
|
||||
}
|
||||
m_JumpWasDown = jumpDown;
|
||||
} else if (auto* rb =
|
||||
TryGetComponent<Engine::ECS::RigidBodyComponent>();
|
||||
rb != nullptr && rb->bodyValid &&
|
||||
rb->type == Engine::ECS::RigidBodyComponent::BodyType::Dynamic) {
|
||||
// 物理驱动:只覆写水平速度,Y 保留物理积分离散结果
|
||||
//(整体喂 0 会抵消重力 → 空中物体悬浮)
|
||||
auto& phy = App().GetPhysics();
|
||||
const glm::vec3 cur = phy.GetLinearVelocity(EntityBits());
|
||||
phy.SetLinearVelocity(EntityBits(), {vel.x, cur.y, vel.z});
|
||||
phy.SetLinearVelocity(
|
||||
EntityBits(), {m_PlanarVel.x, cur.y, m_PlanarVel.z});
|
||||
} else {
|
||||
Transform().SetPosition(Transform().GetPosition() + vel * dt);
|
||||
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)));
|
||||
}
|
||||
|
||||
// ---- 动画:idle/walk/run 三态切换(Direct 模式 crossfade)----
|
||||
// 目标 clip 是"电平量":Shift 中途按下/松开都要能换,所以
|
||||
// 每帧重算目标,只在目标与上帧不同才 Play(边沿)。每帧无
|
||||
// 条件 Play 会不停重置播放头,动画永远播不完
|
||||
const std::string& target = !moving ? m_IdleClip : (running ? 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 = 5.0f; // m/s
|
||||
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(边沿检测基准)
|
||||
bool m_JumpWasDown = false; // 上帧跳跃键(上升沿检测)
|
||||
glm::vec3 m_PrevVel{0.0f}; // 上帧测量速度(加速度差分基准)
|
||||
glm::vec3 m_AccelS{0.0f}; // 平滑测量加速度(EMA 0.35,调试图)
|
||||
};
|
||||
|
||||
ENGINE_REGISTER_SCRIPT(WASDMove)
|
||||
|
||||
Reference in New Issue
Block a user