用DOTS实现爆炸效果

想要爆炸 看了KILL la KILL后突然很想做爆炸效果。 所以来unity试试。
成品是下面这种效果。
无限点击的话真特别有快感,果然爆炸就是爽

先说说DOTS

在讲制作过程前,先提一下DOTS是什么。 官网 的话是这样:
借助Unity的新型高性能、多线程面向数据的技术堆栈(DOTS),您将能够充分利用多核处理器的优势。 DOTS让您能够创建更丰富的用户体验,并使用更易读和能够在其他项目中重用的C#代码进行更快的迭代。
什么叫技术栈呢?就是一系列技术,换句话说,DOTS是由 Entities、Job System、Burst等一系列代码包组成的。 这些玩意一起完成一个目标——提高游戏性能。 DOTS为什么能提高性能我们不做深究。有计算机基础的读者,看到SIMD,内存排列和并行化多少就能猜到大概。 各位感兴趣的话可以看看这个: [基于 Game Object Conversion 和 SubScene 的 DOTS 开发工作流](https://zhuanlan.zhihu.com/p/109943463 )
除了提高性能外,DOTS第二个目标是易用。 这才是关键啊。 易用性。 unity牛逼。
DOTS支持将unity原本的gamobject对象转成DOTS里的对象,也就是实体(Entity)。这个实体(Entity)可以看做一个只有数据的游戏对象。 很多Unity原生对象都可以自动转换。比如Rigidbody和MeshCollider。
自动转换就意味着在使用原生组件的情况下,我们可以很简单的使用DOTS,甚至不需要写代码。

前提准备

在Windows package里下载好这几个包。注意那个HybridRenderer,没有的话转换后的实体不会显示。如果你的实体没有显示,那很有可能是因为没装这个包。 在菜单把FullStackTrace和Entity Debugger打开,方便查看错误。 项目导入问题可以参考 官方的教程

开始吧

1 创建场景

搭建一个下面这样的简单场景 Cube的BoxCllider改成MeshCollider,并选上Convex。不使用BoxCollider因为在使用BoxCollider时会Burst的bug,原因我暂时不知道。

2 创建实体

然后创建一个C#脚本,叫ExplodeManager。 初始化时,创建EntityManager。并在OnDestroy时,释放blobAssetStore。blobAssetStore是一个提供缓存的类,缓存能让你对象创建时更快。
    
    
void Start ( ) { _manager = World . DefaultGameObjectInjectionWorld . EntityManager ; _blobAssetStore = new BlobAssetStore ( ) ; _settings = GameObjectConversionSettings . FromWorld ( World . DefaultGameObjectInjectionWorld , _blobAssetStore ) ; } private void OnDestroy ( ) { _blobAssetStore . Dispose ( ) ; }
第二步就是创建多个实体了。核心为两步:
  1. 转换原本的GameObject为实体
  2. 用NativeArray大量创建实体在上面代码中,省略2中,用SetComponentData方法,给实体配置位置和速度。具体的位置和速度计算可以按你自己的想法任意操作。 如果你不知道有哪些ComponentData的话,可以看EntityDebugger,或者查阅文档。第一个Tip,实体的Scale不好改,所以最好在初始化这就改变原型的Scale。第二个Tip,在数组前做数量校验,否则你的电脑可能会卡死。写完代码,我开心的设了100x100x100个实体,电脑就爆炸了。这样就OK啦,一个产生Entity的方法就制作出来了。
    
    
public void Explode ( GameObject target ) { .. . 省略 1 初始化各种参数 .. . var targetECS = GameObjectConversionUtility . ConvertGameObjectHierarchy ( target , _settings ) ; //创建数组 NativeArray < Entity > many = new NativeArray < Entity > ( amount , Allocator . Temp ) ; _manager . Instantiate ( targetECS , many ) ; .. . 省略 2 给每个实体配置位置和速度 .. . _manager . DestroyEntity ( targetECS ) ; //销毁数组 many . Dispose ( ) ; }
    
    
_manager . SetComponentData ( entity , new Translation { Value = pos } ) ; _manager . SetComponentData ( entity , new PhysicsVelocity { Linear = velocity } ) ;
    
    
public void Explode ( GameObject target ) { .. . 省略 初始化各种参数 .. . //实体的Scale不好改,所以最好在初始化这就改变原型的scale var old = target . transform . localScale ; target . transform . localScale = old * cellSize ; var targetECS = GameObjectConversionUtility . ConvertGameObjectHierarchy ( target , _settings ) ; target . transform . localScale = old ; .. . 其他代码 }
    
    
//简单的校验,但很关键 if ( amount > 5000 ) { Debug . LogError ( "To Many Piece !!! limit is 5000" ) ; return ; }

3 用的Data 和 JobSystem控制实体

但现在有个问题。 这样子创建的实体,是不能用Destroy销毁的。 为此,需要写一个JobSystem,这也是DOTS另一个重要的核心了。
之前提到Entity只是数据,但对游戏来说,仅有数据是不够的。而JobSystem就是用来操作数据的。
首先我们创造数据,创建LifeTimeToDestroyData.cs
    
    
public struct LifeTimeToDestroyData : IComponentData { public float restTime ; }
然后创建LifeTimeToDestroySystem.cs文件
    
    
[ AlwaysSynchronizeSystem ] public class LifeTimeToDestroySystem : JobComponentSystem { protected override JobHandle OnUpdate ( JobHandle inputDeps ) { var ecb = new EntityCommandBuffer ( Allocator . TempJob ) ; Entities . WithoutBurst ( ) . ForEach ( ( Entity entity , ref LifeTimeToDestroyData life ) => { life . restTime -= Time . DeltaTime ; if ( life . restTime < 0 ) { ecb . DestroyEntity ( entity ) ; } } ) . Run ( ) ; ecb . Playback ( EntityManager ) ; ecb . Dispose ( ) ; return default ; } }
注意那个ForEach里面的参数。 系统会根据你的参数,自动找到符合条件的Entity去执行内部的代码。对于我们来说,就是找到包含LifeTimeToDestroyData的实体。 这里销毁实体必须使用EntityCommandBuffer,它将指令缓存并批量执行,提高了性能。
然后,在之前配置实体速度和位置的地方,加上
    
    
_manager . AddComponent < LifeTimeToDestroyData > ( entity ) ; _manager . SetComponentData ( entity , new LifeTimeToDestroyData { restTime = time ) ;
就大功告成啦。

效果

产生1000个实体时的CPU情况 产生10000+个实体时的CPU情况
说实话,不停点击爆炸按钮那感觉确实爽。

DOTS与面向对象

说回DOTS,在开始用时踩了一些坑。包不匹配啊,没有装Hybrid Renderer啊,各种问题。但整体来说解决的都很快,包依赖管理真是造福程序员的伟大发明。 JOBSystem那种编程方法对于程序员来说应该还是蛮有趣的。第一次看到ForEach那块的感觉就像我刚学JAVA时在Spring里看到IOC和AOP的感觉一样。
就看到那代码感觉很爽。
搜索资料时,看到有人说DOTS不够解耦,虽然提高了性能但加重了耦合性。 我觉得这种说法不太对。就像AOP虽然不是面向对象,但在某些场合上有很强的解耦能力。 相反,我觉得DOTS这套模式一定程度上是解耦的,因为它满足了最小接口信息原则。每个JobSystem只需要知道自己关心的Data就可以了。所以我才会说看到那块代码很爽,因为它最小信息,所以代码看着特别地简洁。 但另一方面,似乎没有看到封装性,好像所有的Data对每个System都是可见的。程序变得十分复杂时,可能存在调试困难。谁都不知道这个Data被哪个system改的,如果某个System没有高可用,这个Data的修改可能会带来一系列难以解决的问题。因为System对Data解耦了,但通过Data之间,System又混淆在一起了。 但在游戏开发中,这种不封装性往往又是很有必要的。因为在创造游戏机制时,往往要调用各种组件。比如设计师突然让你实现个无敌的BUFF。一堆设计模式可能不如一个全局变量来的快,虽然你可能之后要付出代价(比如设计师又让你实现个无视无敌的buff),但谁知道还会不会有之后呢。
总之刚学DOTS所知确实有限,目前看来至少上手比较简单。之后我还想继续用DOTS制作试试看,在实际中踩了坑并学到经验后,再来和各位分享啦。

参考

DOTS官网 [使用DOTS制作一款第三人称僵尸射击游戏](https://unity.cn/projects/li-yong-dotszhi-zuo-yi-kuan-di-san-ren-cheng-sang-shi-she-ji-you-xi ) [基于 Game Object Conversion 和 SubScene 的 DOTS 开发工作流](https://zhuanlan.zhihu.com/p/109943463 ) 官方的PONG游戏教程
### 线性区不足转向度的定义与解决方案 #### 定义 线性区不足转向度可以被理解为一种衡量模型从线性假设向更复杂非线性表达能力转变的能力或需求的程度。在线性回归中,模型假定输入特征与输出之间的关系是线性的[^1]。然而,在许多实际应用场景中,这种简单的线性关系往往不足以描述数据的真实分布,尤其是在涉及高维、复杂的非线性模式时。 当面临高度复杂的任务(如图像识别、语音处理或自然语言理解),仅依赖线性模型会导致性能显著下降。因此,“转向度”可视为从简单线性模型过渡到能够捕捉非线性特性的高级模型所需的努力程度或技术调整幅度。 --- #### 解决方案 为了克服线性模型的局限性并提升其适应复杂问题的能力,以下是几种常见的解决方案: ##### 1. **引入多项式特征** 通过增加更高次幂的特征项扩展原始特征空间,从而允许模型拟合更加灵活的关系。例如,如果原特征集为 \([x_1, x_2]\),则可以通过加入平方项和交叉乘积项构建新的特征集合 \([\dots, x_1^2, x_1x_2, x_2^2, \dots]\)[^1]。这种方法虽然能增强模型表现力,但在维度较高时可能导致过拟合风险以及计算成本上升。 ##### 2. **采用核函数方法** 支持向量机(SVM)等算法利用核技巧间接映射低维输入至高维特征空间,而无需显式执行转换操作。这样既保留了高效求解的优势又具备强大的泛化能力。常用的径向基(RBF)核便是典型例子之一,它能够在一定程度上缓解因单纯依靠直线分隔而导致分类效果不佳的情况[^1]。 ##### 3. **深度神经网络的应用** 相较于传统的浅层架构,深层结构提供了逐级抽象提取的功能,使得每一隐藏层都能够专注于特定层次上的表示学习过程。借助激活单元(如ReLU)实现非线性变化累积效应,最终形成对复杂模式的有效表征形式。具体而言,DNNs通过堆叠多个感知器构成多层前馈网路,每经过一层都会经历权重更新加偏置再经由非线性变换的过程,逐步逼近理想决策边界。 ##### 4. **优化注意力机制的设计** 针对序列建模领域存在的挑战,研究者提出了诸如Token Statistics Transformer (ToST)之类的新颖框架来降低时间复杂度的同时保持甚至提高预测精度水平。相比于标准自注意模块需遍历所有位置间相互作用才能获取上下文关联信息的方式,TSSA基于统计特性快速估计整体联系强度,进而大幅削减冗余运算负担[^3]。如下展示了两种不同策略下的伪代码对比情况: ```python # Standard Attention Mechanism O(n²) def standard_attention(Q, K, V): scores = torch.matmul(Q, K.transpose(-2, -1)) attn = torch.softmax(scores, dim=-1) return torch.matmul(attn, V) # Token Statistics Self-Attention O(n) def TSSA(X, heads=8): b, n, d = X.shape proj = nn.Linear(d, heads * d)(X).view(b, n, heads, d // heads).transpose(1, 2) stats = proj.pow(2).mean(dim=1, keepdim=True) gate = 1 / (1 + stats) return (proj * gate).transpose(1, 2).reshape(b, n, d) ``` --- ### 总结 综上所述,解决线性区域不足的问题主要围绕如何突破原有约束条件展开探索实践工作。无论是通过人工构造额外属性还是自动挖掘潜在规律途径都各有千秋;与此同时也要注意到伴随而来可能产生的新难题比如参数爆炸或者梯度消失现象等问题,则需要进一步采取相应措施加以应对。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值