文章目录
DOTS是Unity在17年左右提出的一个概念,其核心是ECS。
提示:以下是本篇文章正文内容,下面案例可供参考
全称:(Multi-Thread)Data-Oriented-Tech-Stack
(多线程式)数据导向型技术堆栈
Tips:
- jobsystem和ecs是两个不同的东西,但是配合起来使用会有1+1>2的效果
- burst与ecs的高度适配也使得ecs运行效率很高
除非您在寻求短期或中期的性能改进,否则很难判定是否需要过渡到DOTS或何时过渡到DOTS。
DOTS几乎可以为每个应用程序带来一定程度的性能改进。这其中包括性能、电池使用寿命、迭代及项目可扩展性。过渡到DOTS不会造成任何性能的下降,但评估过渡到DOTS所增加的费用却至关重要,尤其是对于那些仅带来较小改进的项目。
对于所有应用程序而言,DOTS适合处理大量数据,例如开放式环境或使用大量相同材料的复杂结构。通过在实例之间共享公共数据以减少内存访问,DOTS也同样适用于重复的元素。
DOTS将来会帮助您开发高质量的内容,而不使用DOTS的Unity却很难做到,这一点务必要考虑清楚。例如,当今的标准游戏和Unity项目已经取代了过去的AAA游戏。放眼未来,您需要采用DOTS来保持竞争力。
针对不同的垂直行业,DOTS可以适用于不同的解决方案:
在改善Unity项目的绩效方面,DOTS有着巨大的潜力。 但是,在使用DOTS时需要做出一些考量,它们会影响到项目的时间表、预算和开发团队。以下是一些需要与项目优先事项进行比较和对比的事项。这些事项可以归类为风险与机遇。
随着时间的推移,晶体管电路逐渐接近性能极限,在摩尔定律逐渐失效的今天,人们面临的数据也呈几何倍数暴增,我们有理由去发明并且学习使用一种效率更高,更能完全发挥硬件性能的软件编程方式,目前看来也许ECS也许能做到。
想要熟悉DOTS以及ECS框架,最好还是要上手做一个小项目,使用部分基础组件,想要熟悉以及精通还需要大量的练习以及使用,开发过程中要配合官方Entities文档使用。
Entities最新版本0.17的官方说明文档

如果是Unity2020.X及以上版本(推荐,作者使用2020.3.26f1c1):

我们准备做一个类似Pac-Man的小游戏,主要熟悉Physics包以及Entities的基本使用,所以不会开发怪物AI之类的,因为使用DOTS开发所以就叫DOTS-MAN好了。
主要功能有:玩家移动,镜头跟随,分数显示,因为如果用ECS来修改UGUI的TEXT可能比较麻烦,这里选择使用HybridECS开发,使用MonoBehaviours开发一些基础功能比如镜头跟随以及物体生产之类。
在开发过程中,因为收集物以及玩家还有地形之类的都要有碰撞,但是ECS无法使用object上面的collider之类的组件,所以就要用Entities包自带的一些脚本。
记得在挂Entities脚本之前删掉不用的Object脚本,避免混淆以及无意义的空间占用
把Object转化成Entity的脚本:

一般配合一起使用的脚本就是PhysicsShape和PhysicsBody,一个控制物理碰撞的类型,一个控制entity的物理性质(例如重力之类的),各个属性的作用都有明确说明:

添加physicsbody之后碰到List越界报错问题解决方案:
go into YOURPROJECTLibrary/PackageCache/
copy com.unity.collections@0.15.0-preview.21 into YOURPROJECT/Packages/
open com.unity.collections@0.15.0-preview.21\Unity.Collections\NativeList.cs
change line 599 from Allocator.None to Allocator.Invalid
组件只有三个,两个存储分别存储移动和旋转的速度,一个负责标记收集物(所以里面没有数据)
要记得把Serializable属性改为GenerateAuthoringComponent,这样把component挂上object之后就会把他变成entity。
创建component和system都可以直接使用右键 -> create -> ECS进行快速选择自带模板

using Unity.Entities;
[GenerateAuthoringComponent]
public struct MoveComponent : IComponentData
{
public float moveSpeed;
}
using Unity.Entities;
[GenerateAuthoringComponent]
public struct RotationComponent : IComponentData
{
public float rotateSpeed;
}
Component配置:
玩家:

墙体和收集物:
要注意在脚本中配置Collision Filter相关以及Collision Response相关,即某个entity属于哪个标签,他能与其他哪些标签的entity发生碰撞

搭建一个使用场景(renderer相关的根据自己喜好来整):
因为mono和ECS是相互穿插的,所以如果mono中有需要的system可以直接先去看看system的代码,配合官方文档理解为何这么做,这样才能把整个流程梳理清楚(至少我学习的时候是这样的)
这里需要一个全局的mono behaviour来控制游戏,例如entity与object的连接,这里我们换一种方式,把之前的玩家小球弄成prefab,然后在这个全局mono控制玩家的生成,起名就叫做GameManager吧(具体说明看注释):
using System.Collections;
using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics;
using UnityEngine.UI;
using Unity.Transforms;
public class GameManager : MonoBehaviour
{
public static GameManager instance;
public bool insaneMode;
//在实体object世界中的prefab
public GameObject ballPrefab;
public GameObject cubePrefab;
public Text scoreText;
public int maxScore;
public int cubesPerFrame;
public float cubeSpeed = 3f;
private int curScore;
private Entity ballEntityPrefab;
private Entity cubeEntityPrefab;
private EntityManager entityManager;
private BlobAssetStore blobAssetStore;
//private bool insaneMode;
private void Awake()
{
if (instance != null && instance != this)
{
Destroy(gameObject);
return;
}
instance = this;
//初始化EntityManager
entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
blobAssetStore = new BlobAssetStore();
//从object世界获得setting
//即inspector中可以获取的prefab
GameObjectConversionSettings settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, blobAssetStore);
//通过GameObjectConversionUtility的ConvertGameObjectHierarchy来把object变成entity
//参数(GameObject root, World dstEntityWorld)
ballEntityPrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(ballPrefab, settings);
cubeEntityPrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(cubePrefab, settings);
}
private void OnDestroy()
{
//重置BlobAssetStore中的blobasset缓存,释放清空blobAssetStore
blobAssetStore.Dispose();
}
private void Start()
{
curScore = 0;
insaneMode = false;
//显示分数,这个函数在每一帧都会调用
DisplayScore();
//创建初始球球
SpawnBall();
}
private void Update()
{
//如果符合条件就开启insanemode疯狂造方块,这里改成手动开启
//if (!insaneMode && curScore >= maxScore)
if (insaneMode)
{
//开启协程造方块
//insaneMode = true;
StartCoroutine(SpawnLotsOfCubes());
}
}
//回调,造方块
IEnumerator SpawnLotsOfCubes()
{
while (insaneMode)
{
//每一帧造cubesPerFrame量的方块
for (int i = 0; i < cubesPerFrame; i++)
{
SpawnNewCube();
}
yield return null;
}
}
void SpawnNewCube()
{
//使用entityManager造方块并且给予属性
Entity newCubeEntity = entityManager.Instantiate(cubeEntityPrefab);
Vector3 direction = Vector3.up;
Vector3 speed = direction * cubeSpeed;
PhysicsVelocity velocity = new PhysicsVelocity()
{
Linear = speed,
Angular = float3.zero
};
//最后记得往entity添加component数据
entityManager.AddComponentData(newCubeEntity, velocity);
}
public void IncreaseScore()
{
curScore++;
DisplayScore();
}
private void DisplayScore()
{
scoreText.text = "Score: " + curScore;
}
//造第一个球
void SpawnBall()
{
Entity newBallEntity = entityManager.Instantiate(ballEntityPrefab);
Translation ballTrans = new Translation
{
//初始位置
Value = new float3(0f, 0.5f, 0f)
};
//还是要记得添加component
entityManager.AddComponentData(newBallEntity, ballTrans);
//设置镜头跟随的对象
CameraFollow.instance.ballEntity = newBallEntity;
}
}
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using UnityEngine;
public class CameraFollow : MonoBehaviour
{
public static CameraFollow instance;
public Entity ballEntity;
//设置一个偏移量用来调整相机位置
public float3 offset;
private EntityManager manager;
private void Awake()
{
if (instance != null && instance != this)
{
Destroy(gameObject);
return;
}
instance = this;
manager = World.DefaultGameObjectInjectionWorld.EntityManager;
}
private void LateUpdate()
{
if (ballEntity == null) { return; }
Translation ballPos = manager.GetComponentData<Translation>(ballEntity);
transform.position = ballPos.Value + offset;
}
}
记得把相机脚本挂到main camera上!
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using UnityEngine;
public class MoveSystem : SystemBase
{
protected override void OnUpdate()
{
float deltaTime = Time.DeltaTime;
float2 curInput = new float2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
Entities.ForEach((ref PhysicsVelocity vel, ref MoveComponent speedData) =>
{
float2 newVel = vel.Linear.xz;
newVel += curInput * speedData.moveSpeed * deltaTime;
vel.Linear.xz = newVel;
}).Run();
}
}
相关要点:
ForEach就是对包含参数相关Component的entity在每一帧都进行一定的操作,其中ref关键字表示对数据进行读取也可以修改,而in关键字表示对数据只读,而且in一定要全部放在ref后面。
后面的的.Run()表示在主线程中运行,如果要在子线程可以使用Schedule。
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
public class RotateSystem : SystemBase
{
protected override void OnUpdate()
{
float deltaTime = Time.DeltaTime;
Entities.ForEach((ref Rotation rotation, in RotationComponent rotationSpeed) =>
{
rotation.Value = math.mul(rotation.Value, quaternion.RotateX(math.radians(rotationSpeed.rotateSpeed * deltaTime)));
rotation.Value = math.mul(rotation.Value, quaternion.RotateY(math.radians(rotationSpeed.rotateSpeed * deltaTime)));
rotation.Value = math.mul(rotation.Value, quaternion.RotateZ(math.radians(rotationSpeed.rotateSpeed * deltaTime)));
}).Run();
}
}
记得这时候往你的object上面挂component!如果想让玩家移动就挂movecomponent,让收集物旋转就挂上rotationcomponent。可以想一想,如果你往收集物上挂了movecomponent会发生什么?为什么会这样?
这时候你的收集物应该是旋转的,玩家小球可以通过wasd或者方向键控制移动:

3. CollectSystem:
然后就是最难的碰撞收集系统了!本来在mono中两三行就可以解决的问题,现在要写几十行才能解决!但是对于后期优化以及性能上的提升,这些困难都不算什么!
相关的解释说明都在注释中了:
using Unity.Entities;
using Unity.Collections;
using Unity.Physics;
using Unity.Physics.Systems;
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
public class CollectSystem : SystemBase
{
//用bufferSystem来处理这些碰撞事件
private EndFixedStepSimulationEntityCommandBufferSystem bufferSystem;
//初始化entity的物理世界
private BuildPhysicsWorld buildPhysicsWorld;
private StepPhysicsWorld stepPhysicsWorld;
protected override void OnCreate()
{
bufferSystem = World.GetOrCreateSystem<EndFixedStepSimulationEntityCommandBufferSystem>();
buildPhysicsWorld = World.GetOrCreateSystem<BuildPhysicsWorld>();
stepPhysicsWorld = World.GetOrCreateSystem<StepPhysicsWorld>();
}
protected override void OnUpdate()
{
//每一帧都添加一个triggerjob来进行碰撞判断,因为需要判断的是有MoveComponent的玩家
//以及有DeleteTag的收集物,所以就要在job中进行选择
Dependency = new TriggerJob
{
speedEntities = GetComponentDataFromEntity<MoveComponent>(),
entitiesToDelete = GetComponentDataFromEntity<DeleteTag>(),
commandBuffer = bufferSystem.CreateCommandBuffer(),
}.Schedule(stepPhysicsWorld.Simulation, ref buildPhysicsWorld.PhysicsWorld, Dependency);
//把job传递到buffer中
bufferSystem.AddJobHandleForProducer(Dependency);
}
//创建一个triggerjob来进行碰撞处理
private struct TriggerJob : ITriggerEventsJob
{
//初始化处理的entity
public ComponentDataFromEntity<MoveComponent> speedEntities;
[ReadOnly] public ComponentDataFromEntity<DeleteTag> entitiesToDelete;
public EntityCommandBuffer commandBuffer;
public void Execute(TriggerEvent triggerEvent)
{
TestEntityTrigger(triggerEvent.EntityA, triggerEvent.EntityB);
TestEntityTrigger(triggerEvent.EntityB, triggerEvent.EntityA);
}
//处理碰撞,如果被碰撞的物品没有DeleteTag,就把DeleteTag挂上去,移除它的物理组件
private void TestEntityTrigger(Entity entity1, Entity entity2)
{
if (speedEntities.HasComponent(entity1))
{
if (entitiesToDelete.HasComponent(entity2)) { return; }
commandBuffer.AddComponent<DeleteTag>(entity2);
commandBuffer.RemoveComponent<PhysicsCollider>(entity2);
}
}
}
}
using Unity.Entities;
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
//这里添加一个属性,就是在collectionsystem发生之后再更新,因为要先碰撞之后再进行处理
[UpdateAfter(typeof(CollectSystem))]
public class DeleteSystem : SystemBase
{
private EndFixedStepSimulationEntityCommandBufferSystem _endSimulationECBSystem;
protected override void OnStartRunning()
{
_endSimulationECBSystem = World.GetOrCreateSystem<EndFixedStepSimulationEntityCommandBufferSystem>();
}
protected override void OnUpdate()
{
var ecb = _endSimulationECBSystem.CreateCommandBuffer();
Entities
.WithAll<DeleteTag>()
.WithoutBurst()
.ForEach((Entity entity) =>
{
//修改分数
GameManager.instance.IncreaseScore();
ecb.DestroyEntity(entity);
}).Run();
_endSimulationECBSystem.AddJobHandleForProducer(Dependency);
}
}
这里ForEach之前有一系列限定条件,比如.WithAll()的意思就是对带有deletetag的entity执行下面的操作,这样能更加方便的进行处理,所以大部分情况下entity都会被打一个标签来区别其他entity


太棒啦!你成功的使用了目前领先的开发模式开发了一个小游戏,虽然这个小游戏的功能在mono中实现的话可以很简单的实现,但是随着工程规模的扩大以及性能需求的提高,ECS只会愈发强大!因为目前DOTS相关教程不完善,所以如果在上述开发中碰到问题主要需要参考官方文档以及一些论坛大牛的解答,想要更深入的理解还需要更多项目的磨练。
Entities最新版本0.17的官方说明文档:
https://docs.unity3d.com/Packages/com.unity.entities@0.17/api/Unity.Entities.html
油管ECS大神Turbo的说明文档:
https://www.tmg.dev/tuts/roll-a-ball-entities-0-17/
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
修改(澄清问题)我已经花了几天时间试图弄清楚如何从Facebook游戏中抓取特定信息;但是,我遇到了一堵又一堵砖墙。据我所知,主要问题如下。我可以使用Chrome的检查元素工具手动查找我需要的html-它似乎位于iframe中。但是,当我尝试抓取该iframe时,它是空的(属性除外):如果我使用浏览器的“查看页面源代码”工具,这与我看到的输出相同。我不明白为什么我看不到iframe中的数据。答案不是它是由AJAX之后添加的。(我知道这既是因为“查看页面源代码”可以读取Ajax添加的数据,也是因为我有b/c我一直等到我可以看到数据页面之后才抓取它,但它仍然不存在)。发生这种情况是因为
我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195
我的Rails站点使用了一个确实不是很好的gem。每次我需要做一些新的事情时,我最终不得不花费与向实际Rails项目添加代码一样多的时间来为gem添加功能。但我不介意,我将我的Gemfile设置为指向我的gem的GitHub分支(我尝试提交PR,但维护者似乎已经下台)。问题是我真的没有找到一种合理的方法来测试我添加到gem的新东西。在railsc中测试它会特别好,但我能想到的唯一方法是a)更改~/.rvm/gems/.../foo。rb,这看起来不对或者b)升级版本,推送到Github,然后运行bundleup,这除了耗时之外显然是一场灾难,因为我不确定我所做的promise是否正
关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion是否有适用于这些的3d游戏引擎?