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:
@@ -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
|
||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user