WallFracture 碎裂手感修正:爆炸飞散 → 玻璃式碎裂(初速距离衰减 + 法线剥落外推 + 网格切细至 8×6)

This commit is contained in:
2026-10-08 22:43:36 +08:00
parent d2701a3d9b
commit bca50976f3
+43 -24
View File
@@ -1,6 +1,12 @@
// //
// WallFracture.cpp:侧线A 验收脚本——可碎墙 + 碎片刚体群 + 寿命清理。 // WallFracture.cpp:侧线A 验收脚本——可碎墙 + 碎片刚体群 + 寿命清理。
// //
// 碎裂语义 = 玻璃/陶瓷式("碎掉"),非爆炸飞散:
// 碎片沿裂纹网格分离,初速 = 径向 × 距离指数衰减(只有命中点
// 周边的碎片被推动,远处几乎原地悬置)+ 沿命中法线轻微外推
// (朝弹侧剥落)+ 微弱上抛——重力是塌落的主导,整体呈
// "裂纹分离 → 孔洞塌落 → 碎块堆在墙脚"的观感。
//
// WallFracture(挂墙实体:Static Box 刚体 + MeshRenderer 立方体): // WallFracture(挂墙实体:Static Box 刚体 + MeshRenderer 立方体):
// OnCollisionEnter(Dynamic 体以足够速度命中)→ 触发一次: // OnCollisionEnter(Dynamic 体以足够速度命中)→ 触发一次:
// 藏墙(MeshRenderer/RigidBody.active = false,PhysicsWorld 对账 // 藏墙(MeshRenderer/RigidBody.active = false,PhysicsWorld 对账
@@ -8,8 +14,8 @@
// 实体(共享一份程序化立方体模型 + Dynamic Box 刚体),初速记入 // 实体(共享一份程序化立方体模型 + Dynamic Box 刚体),初速记入
// 待办表 // 待办表
// OnUpdate:碎片簿记——body 建好(下一帧 Step 对账,同 Bullet // OnUpdate:碎片簿记——body 建好(下一帧 Step 对账,同 Bullet
// OnCreate 施速的时序契约)即统一施加径向初速(命中点→碎片方向 + // OnCreate 施速的时序契约)即统一施加初速;寿命到期统一 Destroy;
// 向上偏置);寿命到期统一 Destroy;总量上限防泄漏 // 总量上限防泄漏
// //
// 碎片不挂脚本:单一 WallFracture 集中管理全部碎片的施速/寿命/ // 碎片不挂脚本:单一 WallFracture 集中管理全部碎片的施速/寿命/
// 清理(一帧 spawn 几十个实体,每颗一个脚本实例纯属浪费—— // 清理(一帧 spawn 几十个实体,每颗一个脚本实例纯属浪费——
@@ -37,6 +43,7 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
#include <cmath>
#include <random> #include <random>
using namespace Engine; // u32/f32/Entity 等基础类型 + 门面(同 TestShoot) using namespace Engine; // u32/f32/Entity 等基础类型 + 门面(同 TestShoot)
@@ -62,21 +69,24 @@ namespace {
void DeclareParams(Engine::Scripting::ParamBuilder& pb) override { void DeclareParams(Engine::Scripting::ParamBuilder& pb) override {
// 碎片网格:每轴切几刀(总碎片 = x*y*z;占位档位 = 每碎片 // 碎片网格:每轴切几刀(总碎片 = x*y*z;占位档位 = 每碎片
// 一实体,网格 6*4*1=24 起步,中等规模才考虑 instanced draw) // 一实体,网格 8*6*1=48 起步,中等规模才考虑 instanced draw)
pb.Field("GridX", &m_GridX, 6); pb.Field("GridX", &m_GridX, 8);
pb.Field("GridY", &m_GridY, 4); pb.Field("GridY", &m_GridY, 6);
pb.Field("GridZ", &m_GridZ, 1); 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("BurstSpeed", &m_BurstSpeed, 1.2f); // 近点径向初速 m/s
pb.Field("Falloff", &m_Falloff, 1.0f); // 距离衰减率 1/m
pb.Field("NormalPop", &m_NormalPop, 0.4f); // 法线外推 m/s
pb.Field("UpBias", &m_UpBias, 0.3f); // 上抛偏置 m/s
pb.Field("Lifetime", &m_Lifetime, 6.0f); // 碎片寿命(秒) pb.Field("Lifetime", &m_Lifetime, 6.0f); // 碎片寿命(秒)
pb.Field("MaxFragments", &m_MaxFragments, 96); // 活碎片上限 pb.Field("MaxFragments", &m_MaxFragments, 128); // 活碎片上限
pb.Field("MinImpactSpeed", &m_MinImpactSpeed, 2.0f); // 触发阈值 pb.Field("MinImpactSpeed", &m_MinImpactSpeed, 2.0f); // 触发阈值
} }
void OnCollisionEnter(u32 other, const glm::vec3& point, void OnCollisionEnter(u32 other, const glm::vec3& point,
const glm::vec3& normal) override { const glm::vec3& normal) override {
(void)normal; // 法线留给将来定向喷射
if (m_Fired) return; if (m_Fired) return;
// 触发条件:对方是活的 Dynamic 刚体且速度够快(过滤 // 触发条件:对方是活的 Dynamic 刚体且速度够快(过滤
@@ -96,7 +106,7 @@ namespace {
App().GetPhysics().GetLinearVelocity(other)); App().GetPhysics().GetLinearVelocity(other));
if (speed < m_MinImpactSpeed) return; if (speed < m_MinImpactSpeed) return;
Fire(point); Fire(point, normal);
} }
void OnUpdate(f32 dt) override { void OnUpdate(f32 dt) override {
@@ -106,8 +116,9 @@ namespace {
private: private:
// ---- 触发碎裂:藏墙 + 切网格 + spawn 碎片群 ---- // ---- 触发碎裂:藏墙 + 切网格 + spawn 碎片群 ----
void Fire(const glm::vec3& hitPoint) { void Fire(const glm::vec3& hitPoint, const glm::vec3& hitNormal) {
m_Fired = true; m_Fired = true;
m_HitNormal = hitNormal;
// 藏墙:渲染/拾取跳过 + body 对账销毁(组件级 active, // 藏墙:渲染/拾取跳过 + body 对账销毁(组件级 active,
// 与 Unity Renderer.enabled + Rigidbody 摘除同语义) // 与 Unity Renderer.enabled + Rigidbody 摘除同语义)
@@ -145,9 +156,9 @@ namespace {
(j + 0.5f) * fragSize.y - wallSize.y * 0.5f, (j + 0.5f) * fragSize.y - wallSize.y * 0.5f,
(k + 0.5f) * fragSize.z - wallSize.z * 0.5f}; (k + 0.5f) * fragSize.z - wallSize.z * 0.5f};
const glm::vec3 jitter{ const glm::vec3 jitter{
Rand(-0.2f, 0.2f) * fragSize.x, Rand(-0.3f, 0.3f) * fragSize.x,
Rand(-0.2f, 0.2f) * fragSize.y, Rand(-0.3f, 0.3f) * fragSize.y,
Rand(-0.2f, 0.2f) * fragSize.z}; Rand(-0.3f, 0.3f) * fragSize.z};
const glm::vec3 pos = const glm::vec3 pos =
wallPos + wallRot * (cell + jitter); wallPos + wallRot * (cell + jitter);
@@ -180,8 +191,10 @@ namespace {
frb.friction = 0.7f; frb.friction = 0.7f;
frb.restitution = 0.05f; frb.restitution = 0.05f;
// 初速待办:body 下一帧建好,OnUpdate 统一施加 // 碎裂初速(玻璃式,非爆炸):离命中点越远越
//(同 Bullet OnCreate 施速的时序契约) // 接近静止(指数衰减)——只有孔洞周边的碎片被
// 冲击波推动,整体呈"裂纹分离→重力塌落";
// 法线外推让碎片朝弹侧剥出,而非向墙内挤压
glm::vec3 dir = pos - hitPoint; glm::vec3 dir = pos - hitPoint;
const f32 len = glm::length(dir); const f32 len = glm::length(dir);
if (len < 1e-3f) { if (len < 1e-3f) {
@@ -190,9 +203,12 @@ namespace {
} else { } else {
dir /= len; dir /= len;
} }
const f32 falloff = std::exp(-len * m_Falloff);
const glm::vec3 vel = const glm::vec3 vel =
dir * m_BurstSpeed * Rand(0.6f, 1.3f) + dir * m_BurstSpeed * falloff *
glm::vec3{0.0f, m_UpBias * Rand(0.5f, 1.0f), Rand(0.4f, 1.1f) +
hitNormal * m_NormalPop * Rand(0.3f, 0.8f) +
glm::vec3{0.0f, m_UpBias * Rand(0.0f, 0.5f),
0.0f}; 0.0f};
// 总量上限:超限立即回收最老碎片(防泄漏的 // 总量上限:超限立即回收最老碎片(防泄漏的
@@ -258,15 +274,18 @@ namespace {
bool applied = false; // 初速已注入 bool applied = false; // 初速已注入
}; };
int m_GridX = 6; int m_GridX = 8;
int m_GridY = 4; int m_GridY = 6;
int m_GridZ = 1; int m_GridZ = 1;
f32 m_BurstSpeed = 4.5f; f32 m_BurstSpeed = 1.2f;
f32 m_UpBias = 2.0f; f32 m_Falloff = 1.0f;
f32 m_NormalPop = 0.4f;
f32 m_UpBias = 0.3f;
f32 m_Lifetime = 6.0f; f32 m_Lifetime = 6.0f;
int m_MaxFragments = 96; int m_MaxFragments = 128;
f32 m_MinImpactSpeed = 2.0f; f32 m_MinImpactSpeed = 2.0f;
glm::vec3 m_HitNormal{0.0f, 0.0f, 1.0f}; // 命中法线(剥落方向)
bool m_Fired = false; // 一次性触发保护 bool m_Fired = false; // 一次性触发保护
std::vector<Fragment> m_Fragments; // 活碎片簿记 std::vector<Fragment> m_Fragments; // 活碎片簿记
}; };