// // WallFracture.cpp:侧线A 验收脚本——可碎墙 + 碎片刚体群 + 寿命清理。 // // WallFracture(挂墙实体:Static Box 刚体 + MeshRenderer 立方体): // OnCollisionEnter(Dynamic 体以足够速度命中)→ 触发一次: // 藏墙(MeshRenderer/RigidBody.active = false,PhysicsWorld 对账 // 自然消化:不渲染 + body 销毁)→ 按网格把墙体积切成 N 个碎片 // 实体(共享一份程序化立方体模型 + Dynamic Box 刚体),初速记入 // 待办表 // OnUpdate:碎片簿记——body 建好(下一帧 Step 对账,同 Bullet // OnCreate 施速的时序契约)即统一施加径向初速(命中点→碎片方向 + // 向上偏置);寿命到期统一 Destroy;总量上限防泄漏 // // 碎片不挂脚本:单一 WallFracture 集中管理全部碎片的施速/寿命/ // 清理(一帧 spawn 几十个实体,每颗一个脚本实例纯属浪费—— // Unity 也用同一个 Explosion 脚本管一片 Fragments)。 // // 覆盖的引擎设施: // - Spawn()/Destroy():帧内即时创建 / 帧尾延迟销毁 // - 组件运行时装配:MeshRenderer(程序化 CreateCube 资产)+ // RigidBody(下一帧对账建 body;active=false 对账销毁) // - OnCollisionEnter:命中点/法线 + 对方实线速度查询 // - SetLinearVelocity:body 建好后的初速注入 // // 验收步骤:编辑器摆一堵墙(MeshRenderer = procedural://cube + // RigidBody Static Box + 挂 WallFracture,缩放即墙尺寸)→ 场景里 // 任意实体挂 TestShoot → Play → RMB 飞行瞄准墙体 → 左键射击: // 墙消失,碎片群从命中点径向飞散、翻滚、落地堆积,若干秒后清空。 // #include #include #include #include // CreateCube(门面只前向声明) #include #include #include #include using namespace Engine; // u32/f32/Entity 等基础类型 + 门面(同 TestShoot) namespace { // bits(PhysicsWorld 键)→ 实体(还原位模式;用前自行 valid 校验) ECS::Entity EntityFromBits(u32 bits) { return static_cast(bits); } // 实体句柄 → bits(EntityBits() 只覆盖自身绑定实体,碎片簿记 // 需要任意实体的同款换算) u32 BitsOf(const ECS::EntityHandle& e) { return static_cast( entt::to_integral(static_cast(e))); } // ---- 可碎墙(挂墙实体;触发后自管一整批碎片)---- class WallFracture final : public Engine::Scripting::ScriptBehavior { public: const char* Name() const override { return "WallFracture"; } void DeclareParams(Engine::Scripting::ParamBuilder& pb) override { // 碎片网格:每轴切几刀(总碎片 = x*y*z;占位档位 = 每碎片 // 一实体,网格 6*4*1=24 起步,中等规模才考虑 instanced draw) pb.Field("GridX", &m_GridX, 6); pb.Field("GridY", &m_GridY, 4); pb.Field("GridZ", &m_GridZ, 1); pb.Field("BurstSpeed", &m_BurstSpeed, 4.5f); // 径向初速 m/s pb.Field("UpBias", &m_UpBias, 2.0f); // 向上偏置 m/s pb.Field("Lifetime", &m_Lifetime, 6.0f); // 碎片寿命(秒) pb.Field("MaxFragments", &m_MaxFragments, 96); // 活碎片上限 pb.Field("MinImpactSpeed", &m_MinImpactSpeed, 2.0f); // 触发阈值 } void OnCollisionEnter(u32 other, const glm::vec3& point, const glm::vec3& normal) override { (void)normal; // 法线留给将来定向喷射 if (m_Fired) return; // 触发条件:对方是活的 Dynamic 刚体且速度够快(过滤 // 静止贴靠/角色轻碰——子弹/抛体必中) auto& scene = App().GetScene(); const ECS::Entity otherE = EntityFromBits(other); if (!scene.GetRegistry().valid(otherE)) return; const auto* otherRb = scene.GetRegistry().try_get( otherE); if (!otherRb || otherRb->type != Engine::ECS::RigidBodyComponent::BodyType::Dynamic) { return; } const f32 speed = glm::length( App().GetPhysics().GetLinearVelocity(other)); if (speed < m_MinImpactSpeed) return; Fire(point); } void OnUpdate(f32 dt) override { ApplyPendingVelocities(); AgeAndCleanup(dt); } private: // ---- 触发碎裂:藏墙 + 切网格 + spawn 碎片群 ---- void Fire(const glm::vec3& hitPoint) { m_Fired = true; // 藏墙:渲染/拾取跳过 + body 对账销毁(组件级 active, // 与 Unity Renderer.enabled + Rigidbody 摘除同语义) if (auto* mr = TryGetComponent< Engine::ECS::MeshRendererComponent>()) { mr->active = false; } if (auto* rb = TryGetComponent< Engine::ECS::RigidBodyComponent>()) { rb->active = false; } // 墙几何:立方体模型半边长 0.5(单位体),墙尺寸 = // Transform 世界缩放;碎片尺寸 = 墙尺寸 / 网格 const auto& wall = Transform(); const glm::vec3 wallPos = wall.GetWorldPosition(); const glm::quat wallRot = wall.GetWorldRotationQuat(); const glm::vec3 wallSize = wall.GetWorldScale(); const glm::ivec3 grid{m_GridX, m_GridY, m_GridZ}; const glm::vec3 fragSize = wallSize / glm::vec3{std::max(1, m_GridX), std::max(1, m_GridY), std::max(1, m_GridZ)}; // 共享一份程序化立方体(缓存去重:同参数全场景一把 mesh) const Engine::ModelRef brick = App().GetAssets().CreateCube(0.5f, 140, 92, 72); for (int i = 0; i < grid.x; ++i) { for (int j = 0; j < grid.y; ++j) { for (int k = 0; k < grid.z; ++k) { // 单元中心(墙局部)→ 世界(墙旋转/平移搬运; // 加单元内抖动避免完美网格的假感) const glm::vec3 cell{ (i + 0.5f) * fragSize.x - wallSize.x * 0.5f, (j + 0.5f) * fragSize.y - wallSize.y * 0.5f, (k + 0.5f) * fragSize.z - wallSize.z * 0.5f}; const glm::vec3 jitter{ Rand(-0.2f, 0.2f) * fragSize.x, Rand(-0.2f, 0.2f) * fragSize.y, Rand(-0.2f, 0.2f) * fragSize.z}; const glm::vec3 pos = wallPos + wallRot * (cell + jitter); auto frag = Spawn("fragment"); auto& tr = frag.GetComponent< Engine::ECS::TransformComponent>(); tr.SetPosition(pos); // 随机小倾角:落地堆积不齐整,视觉更像碎块 tr.SetRotationQuat(glm::quat(glm::vec3{ glm::radians(Rand(-30.0f, 30.0f)), glm::radians(Rand(-180.0f, 180.0f)), glm::radians(Rand(-30.0f, 30.0f))})); tr.SetScale(fragSize); auto& fmr = frag.AddComponent< Engine::ECS::MeshRendererComponent>(); fmr.model = brick; // Dynamic Box:boxHalfExtents 默认 0.5(单位体) // × Transform 缩放 = 恰好碎片尺寸,无需改字段 auto& frb = frag.AddComponent< Engine::ECS::RigidBodyComponent>(); frb.type = Engine::ECS::RigidBodyComponent::BodyType::Dynamic; frb.collider = Engine::ECS::RigidBodyComponent::ColliderType::Box; frb.mass = 0.4f; frb.friction = 0.7f; frb.restitution = 0.05f; // 初速待办:body 下一帧建好,OnUpdate 统一施加 //(同 Bullet OnCreate 施速的时序契约) glm::vec3 dir = pos - hitPoint; const f32 len = glm::length(dir); if (len < 1e-3f) { dir = glm::vec3{Rand(-1.0f, 1.0f), 0.6f, Rand(-1.0f, 1.0f)}; } else { dir /= len; } const glm::vec3 vel = dir * m_BurstSpeed * Rand(0.6f, 1.3f) + glm::vec3{0.0f, m_UpBias * Rand(0.5f, 1.0f), 0.0f}; // 总量上限:超限立即回收最老碎片(防泄漏的 // 兜底闸;寿命清理才是常态回收) const u32 bits = BitsOf(frag); if (m_Fragments.size() >= static_cast(m_MaxFragments)) { Destroy(EntityFromBits(m_Fragments.front().bits)); m_Fragments.erase(m_Fragments.begin()); } m_Fragments.push_back({bits, vel, 0.0f}); } } } } // ---- 施速待办:body 建好(bodyValid)即注入,勿重复 ---- void ApplyPendingVelocities() { auto& registry = App().GetScene().GetRegistry(); for (auto it = m_Fragments.begin(); it != m_Fragments.end();) { if (it->applied) { ++it; continue; } const ECS::Entity e = EntityFromBits(it->bits); if (!registry.valid(e)) { // 实体没了(外部销毁) it = m_Fragments.erase(it); continue; } const auto* rb = registry.try_get< Engine::ECS::RigidBodyComponent>(e); if (rb && rb->bodyValid) { App().GetPhysics().SetLinearVelocity(it->bits, it->velocity); it->applied = true; } ++it; // body 未建:留待下一帧(对账晚一帧必到) } } // ---- 寿命清理:到期 Destroy(延迟队列,遍历安全)---- void AgeAndCleanup(f32 dt) { auto& registry = App().GetScene().GetRegistry(); for (auto it = m_Fragments.begin(); it != m_Fragments.end();) { it->age += dt; if (it->age < m_Lifetime) { ++it; continue; } const ECS::Entity e = EntityFromBits(it->bits); if (registry.valid(e)) { Destroy(e); } it = m_Fragments.erase(it); } } static f32 Rand(f32 lo, f32 hi) { thread_local std::mt19937 rng{std::random_device{}()}; return std::uniform_real_distribution(lo, hi)(rng); } struct Fragment { u32 bits = 0; // PhysicsWorld 键(实体位模式) glm::vec3 velocity{0.0f};// 待施初速(body 建好后注入) f32 age = 0.0f; // 存活秒数(寿命清理用) bool applied = false; // 初速已注入 }; int m_GridX = 6; int m_GridY = 4; int m_GridZ = 1; f32 m_BurstSpeed = 4.5f; f32 m_UpBias = 2.0f; f32 m_Lifetime = 6.0f; int m_MaxFragments = 96; f32 m_MinImpactSpeed = 2.0f; bool m_Fired = false; // 一次性触发保护 std::vector m_Fragments; // 活碎片簿记 }; } // namespace ENGINE_REGISTER_SCRIPT(WallFracture)