Files
HelloVulkan/App/scripts/WallFracture.cpp
T

277 lines
12 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.
//
// 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 <Engine/Scripting/Script.h>
#include <Engine/Core/Application.h>
#include <Engine/Core/Types.h>
#include <Engine/Assets/AssetManager.h> // CreateCube(门面只前向声明)
#include <Engine/Physics/PhysicsWorld.h>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <random>
using namespace Engine; // u32/f32/Entity 等基础类型 + 门面(同 TestShoot)
namespace {
// bits(PhysicsWorld 键)→ 实体(还原位模式;用前自行 valid 校验)
ECS::Entity EntityFromBits(u32 bits) {
return static_cast<ECS::Entity>(bits);
}
// 实体句柄 → bits(EntityBits() 只覆盖自身绑定实体,碎片簿记
// 需要任意实体的同款换算)
u32 BitsOf(const ECS::EntityHandle& e) {
return static_cast<u32>(
entt::to_integral(static_cast<entt::entity>(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<Engine::ECS::RigidBodyComponent>(
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<size_t>(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<f32>(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<Fragment> m_Fragments; // 活碎片簿记
};
} // namespace
ENGINE_REGISTER_SCRIPT(WallFracture)