- TestShoot 发射源从 EditorCamera(视口 UI 状态,PIE 渲染不用它) 改为绑定实体位姿,挂 primary 相机实体即第一人称枪口; 仅光标捕捉态开火,防编辑器交互误射 - 新增 PlayerController:FPS 控制(鼠标捕捉/Esc 释放/点视口重捕、 yaw+pitch 转向、WASD+Space/Ctrl 移动+Shift 加速),驱动 primary 相机实体 Transform,PIE 所见即玩家所见 - 靶子/碎片玻璃 alpha 0.42/0.45→0.85、自发光 2→3:低不透明度在 亮背景下靶子几乎隐形(用户误认 HUD 横条为靶子)
242 lines
11 KiB
C++
242 lines
11 KiB
C++
//
|
||
// TestShoot.cpp:射击脚本(发射源 = 绑定实体位姿,挂谁从谁发)。
|
||
//
|
||
// 正确用法:挂到场景 primary 相机实体(CameraComponent.primary=true)
|
||
// 上——PIE/游戏形态渲染源就是它(SceneRenderer::BuildFrame 分派),
|
||
// 实体朝向即准星,实体位置 + MuzzleOffset 即枪口。配 PlayerController
|
||
// (FPS 转向/移动/鼠标捕捉)即完整第一人称射击。
|
||
//
|
||
// 三个脚本协作(放一个文件方便对照):
|
||
// TestShoot(挂相机实体):
|
||
// 光标捕捉态下鼠标左键上升沿 → 从绑定实体沿视线 spawn 子弹实体
|
||
// (Dynamic 球刚体 + Bullet 脚本项,枪口初速经参数传入);
|
||
// 未捕捉(编辑器交互中)不射——防点 UI 误发
|
||
// Bullet(子弹实体,由 TestShoot 运行时装配):
|
||
// OnCreate 施加枪口初速(body 已由当帧 Step 对账建好);
|
||
// OnCollisionEnter → 命中点 spawn ImpactFx + 自毁;
|
||
// 超时自毁防飞出地图泄漏;存活期画调试十字 = 弹道可视化
|
||
// ImpactFx(特效实体,由 Bullet 运行时装配,空实体 + 调试点标):
|
||
// 扩张十字 + 白→红渐隐,寿命尽自毁——占位特效
|
||
//
|
||
// 历史注:早期版本从编辑器相机(EditorCamera)发射——那是视口 UI
|
||
// 状态,不是场景实体,PIE 渲染根本不用它(渲染源 = primary 相机
|
||
// 实体),子弹会从视口外飞出。已改为绑定实体位姿(Unity 组件语义:
|
||
// 挂谁从谁发)。
|
||
//
|
||
// 覆盖的引擎设施:
|
||
// - Spawn()/Destroy():ScriptBehavior 实体底座(Scene::CreateEntity/
|
||
// DestroyEntity 延迟队列)
|
||
// - 组件运行时装配:RigidBody(PhysicsWorld 下一帧对账建 body)、
|
||
// ScriptComponent(ScriptRuntime 下一帧对账建实例)
|
||
// - OnCollisionEnter/Exit:PhysicsWorld::GetEvents → ScriptRuntime
|
||
// 帧首派发
|
||
//
|
||
// 验收步骤:primary 相机实体挂 PlayerController + 本脚本 → Play →
|
||
// 鼠标转视角 → 左键射击:弹道点飞出 → 撞击瞬间原地绽放十字特效、
|
||
// 子弹消失。
|
||
//
|
||
|
||
#include <Engine/Scripting/Script.h>
|
||
#include <Engine/Core/Application.h>
|
||
#include <Engine/Core/Input.h>
|
||
#include <Engine/Physics/PhysicsWorld.h>
|
||
#include <Engine/Debug/DebugDraw.h>
|
||
|
||
#include <glm/glm.hpp>
|
||
|
||
using namespace Engine; // Input / Debug 门面
|
||
|
||
namespace {
|
||
|
||
// ---- 子弹脚本(TestShoot 运行时挂载;不手动挂场景)----
|
||
class Bullet final : public Engine::Scripting::ScriptBehavior {
|
||
public:
|
||
const char* Name() const override { return "Bullet"; }
|
||
|
||
void DeclareParams(Engine::Scripting::ParamBuilder& pb) override {
|
||
// 枪口初速(世界系 m/s):TestShoot spawn 时按视线方向写进
|
||
// 脚本项参数——数据经参数层传递,与序列化同一条路
|
||
pb.Field("Velocity", &m_Velocity, glm::vec3{0.0f});
|
||
pb.Field("Lifetime", &m_Lifetime, 5.0f); // 超时自毁(秒)
|
||
}
|
||
|
||
void OnCreate() override {
|
||
// body 已由本帧 Step 的 SyncScene 建好(spawn 发生在上帧
|
||
// OnUpdate):这里施加初速,物理从下步起按弹道积分
|
||
if (glm::length(m_Velocity) > 1e-4f) {
|
||
App().GetPhysics().SetLinearVelocity(EntityBits(), m_Velocity);
|
||
}
|
||
}
|
||
|
||
void OnUpdate(f32 dt) override {
|
||
m_Age += dt;
|
||
if (m_Age >= m_Lifetime) {
|
||
Destroy(static_cast<Engine::ECS::Entity>(EntityBits()));
|
||
return;
|
||
}
|
||
// 弹道可视化:命中点即弹着点(橙十字 = 弹头,速度线 = 尾迹)
|
||
const glm::vec3 pos = Transform().GetPosition();
|
||
const glm::vec3 vel =
|
||
App().GetPhysics().GetLinearVelocity(EntityBits());
|
||
Debug::Cross(pos, 0.09f, Debug::Rgb(0xFF8C1A),
|
||
/*width*/ 2.0f, /*duration*/ 0.0f,
|
||
/*depthTest*/ false);
|
||
if (glm::length(vel) > 0.5f) {
|
||
Debug::Arrow(pos, pos + vel * 0.03f, Debug::Rgb(0xFFB366),
|
||
/*headSize*/ 0.04f, /*width*/ 2.0f,
|
||
/*duration*/ 0.0f, /*depthTest*/ false);
|
||
}
|
||
}
|
||
|
||
void OnCollisionEnter(u32 other, const glm::vec3& point,
|
||
const glm::vec3& normal) override {
|
||
// 防重入:Destroy 是帧尾延迟执行,命中帧内子弹与碎片群
|
||
// 可产生多对接触事件(碎片云一块挨一块),无标志会一帧
|
||
// 爆出 N 个特效(同 WallFracture m_Fired 的一次性触发模式)
|
||
if (m_Hit) return;
|
||
m_Hit = true;
|
||
|
||
(void)other; (void)normal; // 法线留给将来定向溅射
|
||
|
||
// 命中特效:空实体锚在命中点,ImpactFx 脚本项由下一帧对账
|
||
// 实例化(Spawn 即时建实体,脚本实例化天然晚一帧)
|
||
auto fx = Spawn("impact");
|
||
fx.GetComponent<Engine::ECS::TransformComponent>()
|
||
.SetPosition(point);
|
||
auto& sc = fx.AddComponent<Engine::ECS::ScriptComponent>();
|
||
Engine::ECS::ScriptComponent::Item item;
|
||
item.name = "ImpactFx";
|
||
sc.items.push_back(std::move(item));
|
||
|
||
// 自毁:延迟队列,帧尾统一执行(事件派发循环安全)
|
||
Destroy(static_cast<Engine::ECS::Entity>(EntityBits()));
|
||
}
|
||
|
||
private:
|
||
glm::vec3 m_Velocity{0.0f};
|
||
f32 m_Lifetime = 5.0f;
|
||
f32 m_Age = 0.0f;
|
||
bool m_Hit = false; // 首次命中置位(防同帧多次接触重复特效)
|
||
};
|
||
|
||
// ---- 命中特效脚本(Bullet 运行时挂载;空实体 + 调试点标占位)----
|
||
class ImpactFx final : public Engine::Scripting::ScriptBehavior {
|
||
public:
|
||
const char* Name() const override { return "ImpactFx"; }
|
||
|
||
void DeclareParams(Engine::Scripting::ParamBuilder& pb) override {
|
||
pb.Field("Duration", &m_Duration, 0.4f); // 特效寿命(秒)
|
||
}
|
||
|
||
void OnUpdate(f32 dt) override {
|
||
m_Age += dt;
|
||
if (m_Age >= m_Duration) {
|
||
Destroy(static_cast<Engine::ECS::Entity>(EntityBits()));
|
||
return;
|
||
}
|
||
// 扩张十字 + 白→红渐隐(占位:将来换 VFX/粒子系统)
|
||
const f32 t = m_Age / m_Duration;
|
||
const glm::vec3 pos = Transform().GetPosition();
|
||
Debug::Cross(pos, 0.12f + t * 0.55f,
|
||
glm::vec4{1.0f, 1.0f - t, 1.0f - t, 1.0f},
|
||
/*width*/ 3.0f, /*duration*/ 0.0f,
|
||
/*depthTest*/ false);
|
||
}
|
||
|
||
private:
|
||
f32 m_Duration = 0.4f;
|
||
f32 m_Age = 0.0f;
|
||
};
|
||
|
||
// ---- 枪口脚本(挂谁从谁发:位姿取绑定实体,正挂 primary 相机)----
|
||
class TestShoot final : public Engine::Scripting::ScriptBehavior {
|
||
public:
|
||
const char* Name() const override { return "TestShoot"; }
|
||
|
||
void DeclareParams(Engine::Scripting::ParamBuilder& pb) override {
|
||
pb.Field("BulletSpeed", &m_BulletSpeed, 25.0f); // 枪口初速 m/s
|
||
pb.Field("MuzzleOffset", &m_MuzzleOffset, 0.6f); // 出膛距离 m
|
||
pb.Field("FireCooldown", &m_FireCooldown, 0.15f); // 最小射击间隔 s
|
||
pb.Field("BulletRadius", &m_BulletRadius, 0.12f); // 弹头碰撞半径 m
|
||
// 半径 0.12 的反隧穿账:物理离散检测(无 CCD),检测带 =
|
||
// 半径 + 障碍半厚;薄板(如 0.2m 墙)窗口 2*(0.12+0.1)=0.44m
|
||
// > 每步进位移 0.42m(25 m/s @ 1/60),垂直命中保证检出。
|
||
// 半径越小高速弹越易整步跨过薄碰撞体(无接触=无事件)
|
||
}
|
||
|
||
void OnUpdate(f32 dt) override {
|
||
m_Cooldown -= dt;
|
||
|
||
const bool down =
|
||
Input::IsMouseButtonDown(Input::MouseButton::Left);
|
||
|
||
// 捕捉态门控:光标未捕捉 = 用户在跟编辑器 UI 交互,不射
|
||
// (m_FireWasDown 照常对账,重捕按下的那一下不误发)
|
||
if (!Input::IsCursorCaptured()) {
|
||
m_FireWasDown = down;
|
||
return;
|
||
}
|
||
|
||
// 左键上升沿触发(电平查询 + 上帧状态对比,Unity
|
||
// GetMouseButtonDown 边沿语义的手工等价物)
|
||
const bool fire = down && !m_FireWasDown &&
|
||
m_Cooldown <= 0.0f;
|
||
m_FireWasDown = down;
|
||
if (fire) {
|
||
SpawnBullet();
|
||
m_Cooldown = m_FireCooldown;
|
||
}
|
||
}
|
||
|
||
private:
|
||
void SpawnBullet() {
|
||
// 枪口 = 绑定实体视线前方(挂 primary 相机实体 = 第一人称
|
||
// 射击;PIE 渲染源就是这台相机,所见即所射)
|
||
const glm::quat rot = Transform().GetWorldRotationQuat();
|
||
const glm::vec3 fwd = rot * glm::vec3(0.0f, 0.0f, -1.0f);
|
||
const glm::vec3 muzzle =
|
||
Transform().GetWorldPosition() + fwd * m_MuzzleOffset;
|
||
|
||
// ---- 子弹实体装配(spawn 底座 + 组件运行时装配演示)----
|
||
auto bullet = Spawn("bullet");
|
||
auto& tr = bullet.GetComponent<Engine::ECS::TransformComponent>();
|
||
tr.SetPosition(muzzle);
|
||
tr.SetRotationQuat(rot);
|
||
|
||
// Dynamic 球刚体:参数给默认档(轻质量/低弹性,命中即停)
|
||
auto& rb = bullet.AddComponent<Engine::ECS::RigidBodyComponent>();
|
||
rb.type = Engine::ECS::RigidBodyComponent::BodyType::Dynamic;
|
||
rb.collider =
|
||
Engine::ECS::RigidBodyComponent::ColliderType::Sphere;
|
||
rb.sphereRadius = m_BulletRadius;
|
||
rb.mass = 0.2f;
|
||
rb.friction = 0.3f;
|
||
rb.restitution = 0.1f;
|
||
|
||
// Bullet 脚本项:初速/寿命经参数层传入(与序列化同一条路,
|
||
// 下一帧 ScriptRuntime 对账实例化)
|
||
auto& sc = bullet.AddComponent<Engine::ECS::ScriptComponent>();
|
||
Engine::ECS::ScriptComponent::Item item;
|
||
item.name = "Bullet";
|
||
item.params.push_back(
|
||
{"Velocity",
|
||
Engine::Scripting::ParamValue{fwd * m_BulletSpeed}});
|
||
item.params.push_back(
|
||
{"Lifetime", Engine::Scripting::ParamValue{5.0f}});
|
||
sc.items.push_back(std::move(item));
|
||
}
|
||
|
||
f32 m_BulletSpeed = 25.0f;
|
||
f32 m_MuzzleOffset = 0.6f;
|
||
f32 m_FireCooldown = 0.15f;
|
||
f32 m_BulletRadius = 0.08f;
|
||
f32 m_Cooldown = 0.0f;
|
||
bool m_FireWasDown = false; // 左键上升沿检测基准
|
||
};
|
||
|
||
ENGINE_REGISTER_SCRIPT(Bullet)
|
||
ENGINE_REGISTER_SCRIPT(ImpactFx)
|
||
ENGINE_REGISTER_SCRIPT(TestShoot)
|
||
|
||
} // namespace
|