feat(app): WASDMove script (WASD camera-relative movement)

ScriptBehavior gains App() host access (Application injected via
ScriptRuntime), TryGet<T> and EntityBits for physics facade addressing.
WASDMove maps WASD to the editor camera's horizontal heading (UE
gameplay convention): dynamic bodies go through the physics velocity
facade preserving the vertical component (zeroing Y would cancel
gravity mid-fall), bodiless entities translate their transform directly.
Editor-mode key conflict is naturally avoided: RMB-held flight consumes
WASD, release hands keys to the script.
This commit is contained in:
2026-10-03 18:27:12 +08:00
parent 3403822a52
commit cad0481bc0
5 changed files with 106 additions and 5 deletions
+71
View File
@@ -0,0 +1,71 @@
//
// WASDMove.cpp:WASD 控制实体移动(相机水平朝向参考,UE gameplay 语义)。
//
// 移动语义分派:
// - Dynamic 刚体 → 走物理速度门面(每帧喂水平速度,Y 归物理——
// 重力/跳跃不受影响;松键喂零速 = 立即停,Unity velocity 语义)
// - 无刚体 → 直接平移 Transform
//
// 编辑器形态键位天然不冲突:RMB 按住 = 相机吃 WASD 飞行(脚本也读
// 到按键 → 同时在动,测移动时松开 RMB 即可);游戏形态 WASD 常开。
//
#include <Engine/Scripting/Script.h>
#include <Engine/Core/Application.h>
#include <Engine/Core/Window.h>
#include <Engine/Renderer/Renderer.h>
#include <Engine/Renderer/EditorCamera.h>
#include <Engine/Physics/PhysicsWorld.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
namespace {
class WASDMove final : public Engine::Scripting::ScriptBehavior {
public:
const char* Name() const override { return "WASDMove"; }
void OnUpdate(float dt) override {
// 位移方向 = 视线水平投影(W 前进 / A 左 / D 右),UE Scene
// view 移动惯例;Y 锁 0(升降/跳跃归物理或将来扩展)
const glm::quat 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);
auto& win = App().GetWindow();
glm::vec3 move(0.0f);
if (win.IsKeyPressed(GLFW_KEY_W)) move += fwd;
if (win.IsKeyPressed(GLFW_KEY_S)) move -= fwd;
if (win.IsKeyPressed(GLFW_KEY_D)) move += right;
if (win.IsKeyPressed(GLFW_KEY_A)) move -= right;
move.y = 0.0f;
if (glm::length(move) > 1e-4f) {
move = glm::normalize(move);
}
const glm::vec3 vel = move * m_Speed;
if (auto* rb = TryGet<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});
} else {
Transform().SetPosition(Transform().GetPosition() + vel * dt);
}
}
private:
float m_Speed = 5.0f; // m/s
};
ENGINE_REGISTER_SCRIPT(WASDMove)
} // namespace
+19 -1
View File
@@ -27,6 +27,9 @@
#include <vector> #include <vector>
namespace Engine { namespace Engine {
class Application; // 宿主门面(Window/Renderer/Physics 入口),实现见 ScriptRuntime.cpp
namespace Scripting { namespace Scripting {
class ScriptBehavior { class ScriptBehavior {
@@ -52,17 +55,32 @@ namespace Engine {
return m_Scene->GetRegistry().get<T>(m_Entity); return m_Scene->GetRegistry().get<T>(m_Entity);
} }
template<typename T>
T* TryGet() {
return m_Scene->GetRegistry().try_get<T>(m_Entity);
}
// 绑定实体的 entityBits(物理门面按键值寻址)
u32 EntityBits() const {
return static_cast<u32>(entt::to_integral(m_Entity));
}
// 宿主应用(GetWindow/GetRenderer/GetPhysics/GetScene……)
Application& App() const;
bool Valid() const { return m_Scene != nullptr && m_Entity != entt::null; } bool Valid() const { return m_Scene != nullptr && m_Entity != entt::null; }
private: private:
friend class ScriptRuntime; friend class ScriptRuntime;
void Bind(ECS::Scene& scene, entt::entity e) { void Bind(ECS::Scene& scene, entt::entity e, Application* app) {
m_Scene = &scene; m_Scene = &scene;
m_Entity = e; m_Entity = e;
m_App = app;
} }
ECS::Scene* m_Scene = nullptr; ECS::Scene* m_Scene = nullptr;
entt::entity m_Entity = entt::null; entt::entity m_Entity = entt::null;
Application* m_App = nullptr;
}; };
// ---- 按名注册表:脚本类文件级自注册,运行时按名实例化 ---- // ---- 按名注册表:脚本类文件级自注册,运行时按名实例化 ----
@@ -14,6 +14,9 @@
#include <unordered_map> #include <unordered_map>
namespace Engine { namespace Engine {
class Application;
namespace Scripting { namespace Scripting {
// 实例簿记:行为对象 + OnCreate 是否已调 // 实例簿记:行为对象 + OnCreate 是否已调
@@ -24,7 +27,8 @@ namespace Engine {
class ScriptRuntime { class ScriptRuntime {
public: public:
explicit ScriptRuntime(ECS::Scene& scene) : m_Scene(scene) {} explicit ScriptRuntime(ECS::Scene& scene, Application* app = nullptr)
: m_Scene(scene), m_App(app) {}
// 每帧调用(吃 scaled dt):对账 → OnCreate(新实例)→ OnUpdate // 每帧调用(吃 scaled dt):对账 → OnCreate(新实例)→ OnUpdate
void Update(f32 dt); void Update(f32 dt);
@@ -33,6 +37,7 @@ namespace Engine {
void Sync(); void Sync();
ECS::Scene& m_Scene; ECS::Scene& m_Scene;
Application* m_App; // 注入脚本上下文
std::unordered_map<u32, ScriptInstance> m_Instances; // entityBits std::unordered_map<u32, ScriptInstance> m_Instances; // entityBits
}; };
+3 -2
View File
@@ -103,8 +103,9 @@ namespace Engine {
// 物理调试图元 → 渲染侧(PhysicsDebugPass 每帧上传绘制) // 物理调试图元 → 渲染侧(PhysicsDebugPass 每帧上传绘制)
m_Renderer->SetPhysicsDebugSource(&m_Physics->GetDebugFrame()); m_Renderer->SetPhysicsDebugSource(&m_Physics->GetDebugFrame());
// 脚本生命周期(对账式,组件增删/改名即时生效) // 脚本生命周期(对账式,组件增删/改名即时生效);this 注入脚本
m_Scripts = CreateScope<Scripting::ScriptRuntime>(*m_Scene); // 上下文(Window/Renderer/Physics 门面)
m_Scripts = CreateScope<Scripting::ScriptRuntime>(*m_Scene, this);
while (m_Running) { while (m_Running) {
const auto frameStart = std::chrono::steady_clock::now(); const auto frameStart = std::chrono::steady_clock::now();
+7 -1
View File
@@ -10,6 +10,12 @@
namespace Engine { namespace Engine {
namespace Scripting { namespace Scripting {
// 宿主门面访问器:Application 完整类型只在脚本 cpp 里需要,
// 这里仅回引成员(避免引擎头循环依赖)
Application& ScriptBehavior::App() const {
return *m_App;
}
void ScriptRuntime::Update(f32 dt) { void ScriptRuntime::Update(f32 dt) {
Sync(); Sync();
@@ -61,7 +67,7 @@ namespace Engine {
sc.scriptName, bits); sc.scriptName, bits);
continue; continue;
} }
behavior->Bind(m_Scene, e); behavior->Bind(m_Scene, e, m_App);
ScriptInstance inst; ScriptInstance inst;
inst.behavior = std::move(behavior); inst.behavior = std::move(behavior);