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

227 lines
9.9 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.
//
// TestShoot.cpp:阶段1 验收脚本——脚本侧 spawn/destroy 底座 + 碰撞事件
// 转发的端到端链路。
//
// 三个脚本协作(放一个文件方便对照):
// TestShoot(挂任意实体,如相机实体):
// 鼠标左键上升沿 → 从编辑器相机沿视线 spawn 子弹实体
// (Dynamic 球刚体 + Bullet 脚本项,枪口初速经参数传入)
// Bullet(子弹实体,由 TestShoot 运行时装配):
// OnCreate 施加枪口初速(body 已由当帧 Step 对账建好);
// OnCollisionEnter → 命中点 spawn ImpactFx + 自毁;
// 超时自毁防飞出地图泄漏;存活期画调试十字 = 弹道可视化
// ImpactFx(特效实体,由 Bullet 运行时装配,空实体 + 调试点标):
// 扩张十字 + 白→红渐隐,寿命尽自毁——占位特效
//
// 覆盖的引擎设施:
// - Spawn()/Destroy():ScriptBehavior 实体底座(Scene::CreateEntity/
// DestroyEntity 延迟队列)
// - 组件运行时装配:RigidBody(PhysicsWorld 下一帧对账建 body)、
// ScriptComponent(ScriptRuntime 下一帧对账建实例)
// - OnCollisionEnter/Exit:PhysicsWorld::GetEvents → ScriptRuntime
// 帧首派发
//
// 验收步骤:编辑器给任意实体挂 TestShoot → Play → RMB 飞行视角瞄准
// 墙体 → 左键射击:弹道点飞出 → 撞墙瞬间原地绽放十字特效、子弹消失。
//
#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 <Engine/Renderer/Renderer.h>
#include <Engine/Renderer/EditorCamera.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;
};
// ---- 枪口脚本(验收主体:挂到任意实体,位置/朝向取编辑器相机)----
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;
// 左键上升沿触发(电平查询 + 上帧状态对比,Unity
// GetMouseButtonDown 边沿语义的手工等价物)
const bool down =
Input::IsMouseButtonDown(Input::MouseButton::Left);
const bool fire = down && !m_FireWasDown &&
m_Cooldown <= 0.0f;
m_FireWasDown = down;
if (fire) {
SpawnBullet();
m_Cooldown = m_FireCooldown;
}
}
private:
void SpawnBullet() {
// 枪口 = 编辑器相机视线前方(编辑形态 RMB 飞行瞄准;
// 与 WASDMove 的朝向来源一致)
const auto& cam =
App().GetRenderer().GetEditorCamera().GetTransform();
const glm::vec3 fwd =
cam.GetRotationQuat() * glm::vec3(0.0f, 0.0f, -1.0f);
const glm::vec3 muzzle =
cam.GetWorldPosition() + fwd * m_MuzzleOffset;
// ---- 子弹实体装配(spawn 底座 + 组件运行时装配演示)----
auto bullet = Spawn("bullet");
auto& tr = bullet.GetComponent<Engine::ECS::TransformComponent>();
tr.SetPosition(muzzle);
tr.SetRotationQuat(cam.GetRotationQuat());
// 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