最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Unity 實(shí)體組件系統(tǒng)(ECS)——性能測(cè)試

2018-11-20 19:35 作者:皮皮關(guān)做游戲  | 我要投稿

作者:ProcessCA


Hi,大家好。

趁著Unity前幾天更新了Unity ECS,正好把之前的坑填上。

在上一片文章我們動(dòng)手體驗(yàn)了一下Unity ECS(Entities),嘗試了一些基礎(chǔ)操作。

這次我們嘗試用Job System優(yōu)化我們的ECS實(shí)現(xiàn),我們會(huì)把精力放在Job System之上,然后測(cè)試它在游戲幀數(shù)這種比較直觀的參數(shù)上能拉開傳統(tǒng)的Monobehaviour多大的差距。

如果不熟悉Unity ECS,強(qiáng)烈推薦瞄一眼上一篇文章,熟悉ECS的核心概念與基礎(chǔ)用法

Unity 實(shí)體組件系統(tǒng)(ECS)——預(yù)覽與體驗(yàn)

我們打算做一個(gè)測(cè)試小游戲——瘋狂吃豆人,游戲效果如下圖所示:

我們先用Monobehavior(后文簡(jiǎn)稱Mono)快速實(shí)現(xiàn)該游戲,然后再用Unity ECS嘗試優(yōu)化,對(duì)比兩者后再看看會(huì)發(fā)生什么。相信大家都熟悉Mono的使用,我們需要實(shí)現(xiàn)以下幾個(gè)功能:

1.玩家,敵人移動(dòng)(不使用Unity的物理系統(tǒng)而是直接修改Transform)

2.玩家碰撞檢測(cè)(可以用一個(gè)簡(jiǎn)單的碰撞算法實(shí)現(xiàn))

3.敵人隨機(jī)生成與銷毀(需要一個(gè)單獨(dú)的關(guān)卡系統(tǒng)控制敵人生成的位置與生成速度)

首先我們創(chuàng)建兩個(gè)Sphere放在場(chǎng)景中,然后創(chuàng)建兩個(gè)新的Material,修改一下Shader改為Unlit/Color實(shí)現(xiàn)球體的扁平化風(fēng)格,然后再設(shè)置一下顏色(玩家設(shè)置為橘色,敵人設(shè)置為藍(lán)色)。

為了區(qū)分玩家敵人,我們把橘色的材質(zhì)掛載到玩家上,藍(lán)色的材質(zhì)掛載到敵人上。

由于不使用Unity物理系統(tǒng),所以我們移除掉兩個(gè)球體上的Sphere Collider。

記得設(shè)置一下他們的Tag,分別為PlayerEnemy,別忘了把玩家跟敵人做成預(yù)制體。

接著修改一下相機(jī)的Position為0, 0, -10。

設(shè)置Size調(diào)整屏幕的顯示大小,在Projection中把透視改為正交。

創(chuàng)建Canvsa并且添加上敵人數(shù)量(EnemyCountText)這個(gè)Text:

字體設(shè)置就是這樣

準(zhǔn)備工作完成后畫面看上去是這樣的:

程序員審美,簡(jiǎn)單顏色跟純黑背景

搭建好場(chǎng)景后我們先從創(chuàng)建一個(gè)Player類開始,并掛載到玩家身上來實(shí)現(xiàn)玩家的移動(dòng)。

using UnityEngine;

public class Player : MonoBehaviour

{

??? public bool Dead;

??? private float speed;

?

??? void Start() => speed = 5;

?

??? void Update()

??? {

??????? float x = Input.GetAxisRaw("Horizontal");

??????? float y = Input.GetAxisRaw("Vertical");

??????? Vector3 vector = new Vector3(x, y, 0).normalized * speed * Time.deltaTime;

??????? transform.position += vector;

??? }

}

?

代碼是不是異常簡(jiǎn)單,估計(jì)初學(xué)三天的同學(xué)也可以輕松實(shí)現(xiàn)角色的移動(dòng),但看到后面,嘿嘿嘿嘿。

還有一個(gè)更簡(jiǎn)單的攝像機(jī)跟隨腳本:

using UnityEngine;

public class CameraFollow : MonoBehaviour

{

??? private GameObject player;

?

??? void Start() => player = GameObject.FindWithTag("Player");

?

??? void Update() => transform.position = new Vector3(player.transform.position.x,

??????? player.transform.position.y, gameObject.transform.position.z);

}

?

接著創(chuàng)建一個(gè)Enemy腳本掛載在敵人預(yù)制體上,提供一個(gè)供敵人生成器調(diào)用的接口,并且利用一個(gè)求兩個(gè)圓相交或相切的算法實(shí)現(xiàn)碰撞。

using UnityEngine;

public class Enemy : MonoBehaviour

{

??? private EnemySpawn spawn;

??? private float speed;

??? private Player player;

??? private float radius;

??? private float playerRadius;

?

??? //預(yù)留接口

??? public void Init(EnemySpawn spawn, float speed, Player player)

??? {

??????? this.spawn = spawn;

??????? this.speed = speed;

??????? this.player = player;

??? }

?

??? void Start()

??? {

??????? Renderer renderer = GetComponent<Renderer>();

??????? Renderer playerRenderer = player.GetComponent<Renderer>();

??????? radius = renderer.bounds.size.x / 2;

??????? playerRadius = playerRenderer.bounds.size.x / 2;

??? }

?

??? void Update()

??? {

??????? //敵人尋路

??????? Vector3 vector = (player.transform.position - transform.position).normalized *

??????????? Time.deltaTime * speed;

??????? transform.position += vector;

??????? //碰撞檢測(cè)

??????? float distance = (player.transform.position - transform.position).magnitude;

??????? if (distance < radius + playerRadius && !player.Dead)

??????? {

??????????? Destroy(gameObject);

??????????? spawn.EnemyCount--;

??????? }

??? }

}

?

Player跟Enemy腳本都已經(jīng)實(shí)現(xiàn),還需要一個(gè)創(chuàng)建Enemy的腳本EnemySpawn放在場(chǎng)景中當(dāng)作一個(gè)計(jì)時(shí)器,每隔一定時(shí)間就在玩家身邊創(chuàng)建一堆敵人。

using UnityEngine;

public class EnemySpawn : MonoBehaviour

{

??? [HideInInspector]

??? public int EnemyCount;

??? [SerializeField]

??? private GameObject enemyPrefab;

??? private Player player;

??? private float cooldown;

?

??? void Start() => player = GameObject.FindWithTag("Player").GetComponent<Player>();

?

??? void Update()

??? {

??????? if (player.Dead)

??????????? return;

??????? cooldown += Time.deltaTime;

??????? if (cooldown >= 0.1f)

??????? {

??????????? cooldown = 0f;

??????????? Spawn();

??????? }

??? }

?

??? void Spawn()

??? {

??????? Vector3 playerPos = player.transform.position;

??????? for (int i = 0; i < 50; i++)

??????? {

??????????? GameObject enemy = Instantiate(enemyPrefab);

??????????? EnemyCount++;

?

??????????? int angle = Random.Range(1, 360);??????? //在玩家什么角度刷出來(1-359)

??????????? float distance = Random.Range(15f, 25f); //距離玩家多遠(yuǎn)刷出來

??????????? //角度與距離確定好之后算一下Enemy的初始坐標(biāo)

??????????? float y = Mathf.Sin(angle) * distance;

??????????? float x = y / Mathf.Tan(angle);

?

??????????? enemy.transform.position = new Vector3(playerPos.x + x, playerPos.y + y, 0);

??????????? Enemy enemyScript = enemy.AddComponent<Enemy>();

??????????? enemyScript.Init(this, 2.5f, player);

??????? }

??? }

}

?

設(shè)置場(chǎng)景中的EnemySpawn:

把敵人的預(yù)制體放上去

最后把控制的UI腳本加上掛載到場(chǎng)景中就大功告成了。

using UnityEngine;

using UnityEngine.UI;

public class UI : MonoBehaviour

{

??? private Text enemyCountText;

??? private EnemySpawn enemySpawn;

?

??? void Start()

??? {

??????? enemyCountText = GameObject.Find("EnemyCountText").GetComponent<Text>();

??????? enemySpawn = GameObject.Find("EnemySpawn").GetComponent<EnemySpawn>();

??? }

?

??? void Update() => enemyCountText.text = "敵人數(shù)量:" + enemySpawn.EnemyCount;

}

?

我們利用Monobehavior輕車熟路地實(shí)現(xiàn)了我們的游戲。

運(yùn)行游戲:


以為這就結(jié)束了嗎?

下面的才是重點(diǎn)加難點(diǎn)。


我們使用Entities實(shí)現(xiàn)同樣的功能,最后進(jìn)行性能上的比較。

在開始前確保安裝上了Entities,在菜單欄Window->Package Manager->All可以找到,如果網(wǎng)絡(luò)出現(xiàn)問題可以反復(fù)嘗試幾次。

首先我們需要想清楚ComponentSystem的關(guān)系再開始編寫代碼。

我們的游戲有三個(gè)關(guān)鍵的實(shí)體:玩家,敵人,攝像機(jī)

這里畫一張圖方便大家理解,從上往下依次是:實(shí)體,系統(tǒng),組件,系統(tǒng)。他們的關(guān)系通過連線一目了然。

看上去有點(diǎn)復(fù)雜,總的思想就是不同系統(tǒng)需要關(guān)注不同的組件并進(jìn)行相應(yīng)的操作。

提高性能的關(guān)鍵在于腳本的并行,我們看看那些系統(tǒng)應(yīng)該實(shí)現(xiàn)并行:EnemyCollisionSystem,EnemyMoveSystem,這兩個(gè)系統(tǒng)因?yàn)槭顷P(guān)鍵系統(tǒng)并且不存在邏輯與引用的依賴所以可以實(shí)現(xiàn)并行。



首先我們創(chuàng)建一個(gè)新的場(chǎng)景,Camera的設(shè)置需要從第一個(gè)場(chǎng)景中Copy過來使用。

然后我們?cè)賵?chǎng)景中創(chuàng)建一個(gè)空物體代表Player(Tag選擇Player),并且掛上一個(gè)組件:

我們?cè)贛esh一欄中選擇Sphere圓球,然后選擇之前創(chuàng)建的對(duì)應(yīng)的材質(zhì)。

Enemy也是一樣,只需要在Material一欄中選擇不同的材質(zhì)就好了。

UI也可以從之前的場(chǎng)景中復(fù)制過來:

第一步,我們照著圖來編寫好我們的組件,首先創(chuàng)建一個(gè)名為Bootstrap的腳本,為了方便起見我們就把所有的類都放在一個(gè)文件中進(jìn)行管理:

namespace MultiThread

{

??? using UnityEngine;

??? using UnityEngine.UI;

??? using Unity.Entities;

??? using Unity.Jobs;

??? using Unity.Burst;

??? using Unity.Rendering;

??? using Unity.Transforms;

??? using Unity.Mathematics;

??? using Unity.Collections;

??? using Random = UnityEngine.Random;

?

??? public struct PlayerInput : IComponentData

??? {

??????? public float3 Vector;

??? }

?

??? public struct EnemyComponent : IComponentData

??? {

??? }

?

??? public struct CameraComponent : IComponentData

??? {

??? }

?

??? public struct Health : IComponentData

??? {

??????? public int Value;

??? }

?

??? public struct Velocity : IComponentData

??? {

??????? public float Value;

??? }

}

?

值得注意的是Unity ECS里面有一個(gè)bug導(dǎo)致不能在結(jié)構(gòu)中聲明bool類型。

以上的組件屬于自定義組件,除此之外還有三個(gè)Unity提供的組件:

Position,MeshInstanceRenderer,Transform

然后我們創(chuàng)建一個(gè)Bootstrap類,在其中創(chuàng)建一個(gè)能用被Unity自動(dòng)調(diào)用的方法:


[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]

public static void Start()

{

}

?

再Start方法中創(chuàng)建EntityManager開始,并進(jìn)行初始化操作。


EntityManager manager = World.Active.GetOrCreateManager<EntityManager>();

?

GameObject player = GameObject.FindWithTag("Player");

GameObject enemy = GameObject.FindWithTag("Enemy");

GameObject camera = GameObject.FindWithTag("MainCamera");

Text enemyCount = GameObject.Find("EnemyCountText").GetComponent<Text>();

?

//獲取Player MeshInstanceRenderer

MeshInstanceRenderer playerRenderer = player.GetComponent<MeshInstanceRendererComponent>().Value;

Object.Destroy(player);

//獲取Enemy MeshInstanceRenderer

MeshInstanceRenderer enemyRenderer = enemy.GetComponent<MeshInstanceRendererComponent>().Value;

Object.Destroy(enemy);

//初始化玩家實(shí)體

Entity entity = manager.CreateEntity();

manager.AddComponentData(entity, new PlayerInput { });

manager.AddComponentData(entity, new Position { Value = new float3(0, 0, 0) });

manager.AddComponentData(entity, new Velocity { Value = 7 });

manager.AddSharedComponentData(entity, playerRenderer);

//初始化攝像機(jī)實(shí)體

GameObjectEntity gameObjectEntity = camera.AddComponent<GameObjectEntity>();

manager.AddComponentData(gameObjectEntity.Entity, new CameraComponent());

?

上面代碼比較簡(jiǎn)單不做過多講解。

創(chuàng)建第一個(gè)系統(tǒng)PlayerInputSystem,照著上面的設(shè)計(jì)圖紙,關(guān)注相應(yīng)的組件并進(jìn)行操作就好了。



public class PlayerInputSystem : ComponentSystem

{

??? struct Player

??? {

??????? public readonly int Length;

??????? public ComponentDataArray<PlayerInput> playerInput;

??? }

?

??? [Inject] Player player; //加上這個(gè)標(biāo)簽,Unity會(huì)自動(dòng)注入我們聲明的結(jié)構(gòu)中的屬性

?

??? protected override void OnUpdate()

??? {

??????? for (int i = 0; i < player.Length; i++)

??????? {

??????????? float3 normalized = new float3();

??????????? float x = Input.GetAxisRaw("Horizontal");

??????????? float y = Input.GetAxisRaw("Vertical");

??????????? if (x != 0 || y != 0) //注意:直接歸一化0向量會(huì)導(dǎo)致bug

??????????????? normalized = math.normalize(new float3(x, y, 0));

??????????? player.playerInput[i] = new PlayerInput { Vector = normalized };

??????? }

??? }

}

?

以上的操作在上一篇ECS文章中有提及。

PlayerMoveSystem應(yīng)該關(guān)注相應(yīng)的組件并且進(jìn)行相應(yīng)的操作:


public class PlayerMoveSystem : ComponentSystem

{

??? struct Player

??? {

??????? public readonly int Length;

??????? public ComponentDataArray<Position> positions;

??????? public ComponentDataArray<PlayerInput> playerInput;

??????? public ComponentDataArray<Velocity> velocities;

??? }

?

??? [Inject] Player player;

?

??? protected override void OnUpdate()

??? {

??????? float deltaTime = Time.deltaTime;

??????? for (int i = 0; i < player.Length; i++)

??????? {

??????????? //Read

??????????? Position position = player.positions[i];

??????????? PlayerInput input = player.playerInput[i];

??????????? Velocity velocity = player.velocities[i];

?

??????????? position.Value += new float3(input.Vector * velocity.Value * deltaTime);

??????????? //Write

??????????? player.positions[i] = position;

??????? }

??? }

}

?

現(xiàn)在我們就已經(jīng)可以控制我們的玩家小球移動(dòng)了,趁熱打鐵繼續(xù)深入。

CameraMoveSystem與上面的系統(tǒng)在實(shí)現(xiàn)上不會(huì)有太大差別。

[UpdateAfter(typeof(PlayerMoveSystem))] //存在依賴關(guān)系, 我們控制該系統(tǒng)的更新在PlayerMoveSystem之后

public class CameraMoveSystem : ComponentSystem

{

??? struct Player

??? {

??????? public readonly int Length;

??????? public ComponentDataArray<PlayerInput> playerInputs;

??????? public ComponentDataArray<Position> positions;

??? }

??? struct Cam

??? {

??????? public ComponentDataArray<CameraComponent> cameras;

??????? public ComponentArray<Transform> transforms;

??? }

??? [Inject] Player player;

??? [Inject] Cam cam;

?

??? protected override void OnUpdate()

??? {

??????? if (player.Length == 0) //玩家死亡

??????????? return;

??????? float3 pos = player.positions[0].Value;

??????? //相機(jī)跟隨

??????? cam.transforms[0].position = new Vector3(pos.x, pos.y, cam.transforms[0].position.z);

??? }

}

?

UI系統(tǒng)還算比較好理解的,不做闡述細(xì)節(jié)了:

[AlwaysUpdateSystem] //持續(xù)更新系統(tǒng)

public class UISystem : ComponentSystem

{

??? Text enemyCount;

?

??? public void Init(Text enemyCount) => this.enemyCount = enemyCount;

?

??? struct Player

??? {

??????? public readonly int Length;

??????? public ComponentDataArray<PlayerInput> playerInputs;

??? }

??? struct Enemy

??? {

??????? public readonly int Length;

??????? public ComponentDataArray<EnemyComponent> enemies;

??? }

??? [Inject] Player player;

??? [Inject] Enemy enemy;

?

??? protected override void OnUpdate()

??? {

??????? if (player.Length == 0) //玩家死亡

??????????? return;

?

??????? enemyCount.text = "敵人數(shù)量:" + enemy.Length;

??? }

}

?

敵人生成系統(tǒng)中使用了一個(gè)生成的小算法,以玩家為原點(diǎn)在圓球的周長(zhǎng)上隨機(jī)一個(gè)點(diǎn)生成敵人

public class EnemySpawnSystem : ComponentSystem

{

??? EntityManager manager;

??? MeshInstanceRenderer enemyLook;

??? float timer;

?

??? public void Init(EntityManager manager, MeshInstanceRenderer enemyLook)

??? {

??????? this.manager = manager;

??????? this.enemyLook = enemyLook;

??????? timer = 0;

??? }

?

? ??struct Player

??? {

??????? public readonly int Length;

??????? public ComponentDataArray<PlayerInput> playerInputs;

??????? public ComponentDataArray<Position> positions;

??? }

?

??? [Inject] Player player;

?

??? protected override void OnUpdate()

??? {

??????? timer += Time.deltaTime;

??????? if (timer >= 0.1f)

??????? {

??????????? timer = 0;

??????????? CreatEnemy();

??????? }

??? }

?

??? void CreatEnemy()

??? {

??????? if (player.Length == 0) //玩家死亡

??????????? return;

??????? float3 playerPos = player.positions[0].Value;

?

??????? for (int i = 0; i < 50; i++)

??????? {

??????????? Entity entity = manager.CreateEntity();

?

??????????? int angle = Random.Range(1, 360);??????? //在玩家什么角度刷出來

??????????? float distance = Random.Range(15f, 25f); //距離玩家多遠(yuǎn)刷出來

??????????? //計(jì)算該點(diǎn)的x, y分量

??????????? float y = Mathf.Sin(angle) * distance;

??????????? float x = y / Mathf.Tan(angle);

??????????? float3 positon = new float3(playerPos.x + x, playerPos.y + y, 0);

??????????? //初始化敵人及屬性

??????????? manager.AddComponentData(entity, new EnemyComponent { });

??????????? manager.AddComponentData(entity, new Health { Value = 1 });

??????????? manager.AddComponentData(entity, new Position { Value = positon });

??????????? manager.AddComponentData(entity, new Velocity { Value = 1 });

??????????? manager.AddSharedComponentData(entity, enemyLook);

??????? }

??? }

}

?

到這里我們已經(jīng)實(shí)現(xiàn)了玩家的輸入,移動(dòng),攝像機(jī)跟隨,UI,與敵人生成。還沒完,最關(guān)鍵的并行系統(tǒng):EnemyMove跟EnemyCollision還沒有實(shí)現(xiàn)。

難點(diǎn)中的難點(diǎn)來了,EnemyMoveSystem需要繼承JobComponent系統(tǒng)來實(shí)現(xiàn)并行。


public class EnemyMoveSystem : JobComponentSystem

{

??? ComponentGroup enemyGroup;?? //由一系列組件組成

??? ComponentGroup playerGroup;

?

??? protected override void OnCreateManager() //系統(tǒng)創(chuàng)建時(shí)調(diào)用

??? {

??????? //聲明該組所需的組件,包括讀寫依賴

??????? enemyGroup = GetComponentGroup

??????? (

??????????? ComponentType.ReadOnly(typeof(Velocity)),

??????????? ComponentType.ReadOnly(typeof(EnemyComponent)),

??????????? typeof(Position)

??????? );

??????? playerGroup = GetComponentGroup

??????? (

??????????? ComponentType.ReadOnly(typeof(PlayerInput)),

??????????? ComponentType.ReadOnly(typeof(Position))

??????? );

??? }

?

??? [BurstCompile] //使用Burst編譯

??? struct EnemyMoveJob : IJobParallelFor //繼承該接口實(shí)現(xiàn)并行

??? {

??????? public float deltaTime;

??????? public float3 playerPos;

??????? //記得聲明讀寫關(guān)系

??????? public ComponentDataArray<Position> positions;

??????? [ReadOnly] public ComponentDataArray<Velocity> velocities;

?

??????? public void Execute(int i) //會(huì)被不同的線程調(diào)用,所以方法中不能存在引用類型。

??????? {

??????????? //Read

??????????? float3 position = positions[i].Value;

??????????? float speed = velocities[i].Value;

??????????? //算出朝向玩家的向量

??????????? float3 vector = playerPos - position;

??????????? vector = math.normalize(vector);

?

??????????? float3 newPos = position + vector * speed * deltaTime;

??????????? //Wirte

??????????? positions[i] = new Position { Value = newPos };

??????? }

??? }

?

??? protected override JobHandle OnUpdate(JobHandle inputDeps) //每幀調(diào)用

??? {

??????? if (playerGroup.CalculateLength() == 0) //玩家死亡

??????????? return base.OnUpdate(inputDeps);

?

??????? float3 playerPos = playerGroup.GetComponentDataArray<Position>()[0].Value;

?

??????? EnemyMoveJob job = new EnemyMoveJob

??????? {

??????????? deltaTime = Time.deltaTime,

??????????? playerPos = playerPos,

??????????? positions = enemyGroup.GetComponentDataArray<Position>(), //聲明了組件后,Get時(shí)會(huì)進(jìn)行組件的獲取

??????????? velocities = enemyGroup.GetComponentDataArray<Velocity>()

??????? };

??????? return job.Schedule(enemyGroup.CalculateLength(), 64, inputDeps); //第一個(gè)參數(shù)意味著每個(gè)job.Execute的執(zhí)行次數(shù)

??? }

}

?

上面這個(gè)系統(tǒng)比較復(fù)雜但卻是Unity ECS的核心,特別是OnUpdate中進(jìn)行的操作,返回的JobHandle會(huì)被不同線程執(zhí)行,理解這一點(diǎn)是關(guān)鍵。

EnemyCollisionSystem在實(shí)現(xiàn)上幾乎與上述系統(tǒng)一致:


[UpdateAfter(typeof(PlayerMoveSystem))] //邏輯上依賴于玩家移動(dòng)系統(tǒng),所以聲明更新時(shí)序

public class EnemyCollisionSystem : JobComponentSystem

{

??? float playerRadius;

??? float enemyRadius;

??? public void Init(float playerRadius, float enemyRadius)

??? {

??????? this.playerRadius = playerRadius;

??????? this.enemyRadius = enemyRadius;

??? }

?

??? ComponentGroup enemyGroup;

??? ComponentGroup playerGroup;

?

??? protected override void OnCreateManager()

??? {

??????? enemyGroup = GetComponentGroup

??????? (

??????????? ComponentType.ReadOnly(typeof(EnemyComponent)),

??????????? typeof(Health),

??????????? ComponentType.ReadOnly(typeof(Position))

??????? );

??????? playerGroup = GetComponentGroup

??????? (

??????????? ComponentType.ReadOnly(typeof(PlayerInput)),

??????? ????ComponentType.ReadOnly(typeof(Position))

??????? );

??? }

?

??? [BurstCompile]

??? struct EnemyCollisionJob : IJobParallelFor

??? {

??????? public int collisionDamage; //碰撞對(duì)雙方造成的傷害

??????? public float playerRadius;

??????? public float enemyRadius;

??????? public float3 playerPos;

??????? [ReadOnly] public ComponentDataArray<Position> positions;

??????? public ComponentDataArray<Health> enemies;

?

??????? public void Execute(int i)

??????? {

??????????? float3 position = positions[i].Value;

??????????? float x = math.abs(position.x - playerPos.x);

??????????? float y = math.abs(position.y - playerPos.y);

??????????? //距離

??????????? float magnitude = math.sqrt(x * x + y * y);

?

??????????? //圓形碰撞檢測(cè)

??????????? if (magnitude < playerRadius + enemyRadius)

??????????? {

??????????????? //Read

??????????????? int health = enemies[i].Value;

??????????????? //Write

??????????????? enemies[i] = new Health { Value = health - collisionDamage };

??????????? }

??????? }

??? }

?

??? protected override JobHandle OnUpdate(JobHandle inputDeps)

??? {

??????? if (playerGroup.CalculateLength() == 0) //玩家死亡

??????????? return base.OnUpdate(inputDeps);

?

??????? float3 playerPos = playerGroup.GetComponentDataArray<Position>()[0].Value;

?

??????? EnemyCollisionJob job = new EnemyCollisionJob

??????? {

??????????? collisionDamage = 1,

??????????? playerRadius = this.playerRadius,

??????????? enemyRadius = this.enemyRadius,

??????????? playerPos = playerPos,

??????????? positions = enemyGroup.GetComponentDataArray<Position>(),

??????????? enemies = enemyGroup.GetComponentDataArray<Health>()

??????? };

??????? return job.Schedule(enemyGroup.CalculateLength(), 64, inputDeps);

??? }

}

?

最后別忘了加上移除死亡的敵人的系統(tǒng),按照Unity官方的說法我們需要使用如下的格式進(jìn)行實(shí)體的移除,要注意的是IJobProcessComponentData接口,繼承這個(gè)接口可以獲得所有的帶有指定組件的實(shí)體。

public class RemoveDeadBarrier : BarrierSystem

{

}

public class RemoveDeadSystem : JobComponentSystem

{

??? struct Player

??? {

??????? public readonly int Length;

??????? [ReadOnly] public ComponentDataArray<PlayerInput> PlayerInputs;

??? }

??? [Inject] Player player;

??? [Inject] RemoveDeadBarrier barrier;

?

??? [BurstCompile]

??? struct RemoveDeadJob : IJobProcessComponentDataWithEntity<Health>

??? {

??????? public bool PlayerDead;

??????? public EntityCommandBuffer Command;

?

??????? //該方法會(huì)獲取所有帶有Health組件的實(shí)體。

??????? public void Execute(Entity entity, int index, [ReadOnly] ref Health health)

??????? {

??????????? if (health.Value <= 0 || PlayerDead)

??????????????? Command.DestroyEntity(entity);

??????? }

??? }

?

??? protected override JobHandle OnUpdate(JobHandle inputDeps)

??? {

??????? bool playerDead = player.Length == 0;

?

??????? RemoveDeadJob job = new RemoveDeadJob

??????? {

??????????? PlayerDead = playerDead,

??????????? Command = barrier.CreateCommandBuffer(),

??????? };

??????? return job.ScheduleSingle(this, inputDeps); //這里使用ScheduleSingle可以不需要指定Execute的指定順序。

??? }

}

?

最后,當(dāng)然別忘了在Bootstrap.Start中初始化這三個(gè)系統(tǒng):

//初始化UI系統(tǒng)

UISystem uISystem = World.Active.GetOrCreateManager<UISystem>();

uISystem.Init(enemyCount);

//初始化敵人生成系統(tǒng)

EnemySpawnSystem enemySpawnSystem = World.Active.GetOrCreateManager<EnemySpawnSystem>();

enemySpawnSystem.Init(manager, enemyRenderer);

//初始化敵人碰撞系統(tǒng)

EnemyCollisionSystem collisionSystem = World.Active.GetOrCreateManager<EnemyCollisionSystem>();

collisionSystem.Init(playerRenderer.mesh.bounds.size.x / 2, enemyRenderer.mesh.bounds.size.x / 2);

?

當(dāng)這些系統(tǒng)都完成之后我們運(yùn)行游戲看一下效果:

終于,我們用兩種方式都已經(jīng)實(shí)現(xiàn)了該游戲,用Unity Profiler簡(jiǎn)單測(cè)試一下這兩種方式的性能:

測(cè)試機(jī)器的CPU(四核)與內(nèi)存(8GB):

測(cè)試環(huán)境:關(guān)閉了絕大部分進(jìn)程,CPU空閑的情況下使用Unity2019.1 Editor運(yùn)行游戲。

測(cè)試方法:使用玩家小球朝著一個(gè)特定的方向移動(dòng)。


首先來看一下基于Monobehavior的實(shí)現(xiàn):

在敵人數(shù)量在17000左右時(shí),游戲幀數(shù)掉到了30幀。

由于沒有使用Unity的物理系統(tǒng),所以基本上是腳本跟渲染兩大塊占用CPU的性能。

在主線程中一幀的時(shí)間已經(jīng)超過30毫秒了,其中腳本執(zhí)行就占用了幾乎27毫秒。

我們可以看到Job System上的線程也幫我們分擔(dān)了不少渲染上的負(fù)擔(dān):

值得一提的是在unity2017之后加入了Job System,所以Unity的渲染也會(huì)被分配到不同的線程中去執(zhí)行,在一定程度上提高了整體運(yùn)行效率。

但在該游戲最吃性能的還是腳本,而我們希望在Job Sytem的不同工作線程中也能分擔(dān)主線程中的腳本運(yùn)行。

所以我們測(cè)試一下加上了Job Sytem的Unity ECS實(shí)現(xiàn):

同樣在17000左右,ECS實(shí)現(xiàn)依然能維持85幀

Job System的工作線程的確為主線程分擔(dān)了相當(dāng)一部分負(fù)擔(dān),并行化的腳本分配到了不同的工作線程上,利用了多核的性能。

在不同Worker Thread中有藍(lán)色的代碼執(zhí)行片段

我們測(cè)試一下ECS的極限,看看實(shí)例化多少個(gè)單位會(huì)下降到30幀:

在敵人數(shù)量超過65000個(gè)時(shí)幀數(shù)下降到30幀

差不多是驚人的65000個(gè),幾乎是Mono的4倍(因?yàn)槌浞掷昧怂膫€(gè)處理器核心)。

從中我們可以看到,如果使用Unity開發(fā)某個(gè)擁有非常多相似的單位或是模型的游戲的時(shí)候使用Unity ECS會(huì)是不二之選。

摩爾定律就快失效的今天,不考慮用新的數(shù)據(jù)組織方式跟多線程模型來優(yōu)化你的代碼嗎騷年?

附上項(xiàng)目下載地址:https://github.com/ProcessCA/UnityECSTest


最后想系統(tǒng)學(xué)習(xí)游戲開發(fā)的童鞋,歡迎訪問?http://levelpp.com/? ??? ??

游戲開發(fā)攪基QQ群:869551769? ? ??

微信公眾號(hào):皮皮關(guān)

Unity 實(shí)體組件系統(tǒng)(ECS)——性能測(cè)試的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
龙江县| 桂阳县| 泌阳县| 固安县| 高要市| 灵璧县| 闻喜县| 泗水县| 日土县| 万荣县| 繁峙县| 城步| 德钦县| 醴陵市| 江口县| 大田县| 长海县| 化州市| 历史| 余江县| 包头市| 谷城县| 汉川市| 北安市| 荔波县| 江安县| 宣汉县| 夏津县| 雅江县| 资源县| 新安县| 双鸭山市| 五常市| 巴马| 蛟河市| 和顺县| 荣昌县| 天等县| 云南省| 新竹县| 大兴区|