· unity unity ecs · 5 min read
[Unity ECS - Phần 2] Hiểu sâu về ECS và DOTS: Tối ưu hiệu năng trong Unity
ECS và DOTS không chỉ là cách tiếp cận mới trong Unity — mà còn là chìa khóa để xây dựng các trò chơi có hiệu năng cao, mở rộng dễ dàng. Bài viết này sẽ giúp bạn hiểu sâu cách hoạt động của ECS và lý do vì sao nó vượt trội so với Game Object truyền thống.

Giới thiệu về Seri
Sau khi mọi người đã nắm được các khái niệm cơ bản và cách thiết lập một dự án ECS, giờ là lúc chúng ta đi sâu hơn vào nội tại của hệ thống này: cách mà ECS (Entity Component System) hoạt động kết hợp cùng DOTS (Data-Oriented Tech Stack) để khai thác tối đa hiệu năng.
Trong phần này, chúng ta sẽ hiểu:
- Vì sao kiến trúc ECS lại tối ưu.
- Đưa ra ví dụ so sánh giữa 1 dự án dùng MonoBehaviour truyền thống và dùng ECS.
Ví dụ so sánh giữa Game Object truyền thống với ECS
Để hiểu rõ hơn về sự khác biệt giữa MonoBehaviour và ECS, chúng ta sẽ xây dựng một ví dụ đơn giản: tạo từ 100 đến 30000 thực thể và di chuyển chúng.
Cách tiếp cận với MonoBehaviour truyền thống
// File: CubeMover.cs
using UnityEngine;
public class CubeMover : MonoBehaviour
{
[SerializeField] private float speed = 5f;
void Update()
{
transform.position += new Vector3(speed * Time.deltaTime, 0, 0);
if (transform.position.x > 10f)
{
transform.position = new Vector3(-10f, transform.position.y, transform.position.z);
}
}
}
// File: CubeSpawner.cs
using UnityEngine;
public class CubeSpawner : MonoBehaviour
{
public GameObject cubePrefab;
// Có thể thay đổi số lượng cube tạo ra
[SerializeField]
public int numberOfCubes = 30000;
void Start()
{
for (int i = 0; i < numberOfCubes; i++)
{
Vector3 randomPosition = new Vector3(
Random.Range(-10f, 10f),
Random.Range(-5f, 5f),
Random.Range(-5f, 5f)
);
Instantiate(cubePrefab, randomPosition, Quaternion.identity);
}
}
}
Click play và xem tab Stats, đối với cấu hình máy tôi giao động khoản 140, 37, 16 FPS tương đương với 1000, 10000, 30000 cube được tạo:
![]() | ![]() | ![]() |
---|
Cách tiếp cận với ECS
Như đã học ở bài trước, chúng ta sẽ tạo các Component và System tương ứng:
// File: CubeMoverAuthoring.cs
using Unity.Entities;
using UnityEngine;
public class CubeAuthoring : MonoBehaviour
{
public float speed = 5f;
class Baker : Baker<CubeAuthoring>
{
public override void Bake(CubeAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.Renderable);
AddComponent(entity, new CubeMoverComponent { Speed = authoring.speed });
}
}
}
// File: CubeMoverComponent.cs
using Unity.Entities;
public struct CubeMoverComponent : IComponentData
{
public float Speed;
}
// File: CubeSpawnerSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
[BurstCompile]
[UpdateInGroup(typeof(InitializationSystemGroup))]
public partial struct CubeSpawnerSystem : ISystem
{
public void OnCreate(ref SystemState state)
{
// Chỉ chạy nếu EntitiesReferences tồn tại
state.RequireForUpdate<EntitiesReferences>();
}
public void OnUpdate(ref SystemState state)
{
state.Enabled = false;
var entityManager = state.EntityManager;
// Get the prefab entity from EntitiesReferences singleton
var prefabEntity = SystemAPI.GetSingleton<EntitiesReferences>().cubePrefab;
var ecb = new EntityCommandBuffer(Allocator.Temp);
for (int i = 0; i < 100; i++)
{
var entity = ecb.Instantiate(prefabEntity);
float3 pos = new float3(
UnityEngine.Random.Range(-10f, 10f),
UnityEngine.Random.Range(-5f, 5f),
UnityEngine.Random.Range(-5f, 5f)
);
ecb.SetComponent(entity, LocalTransform.FromPosition(pos));
}
ecb.Playback(entityManager);
ecb.Dispose();
}
}
// File: CubeMoverSystem.cs
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
[WorldSystemFilter(WorldSystemFilterFlags.Default)]
[BurstCompile]
[UpdateInGroup(typeof(SimulationSystemGroup))]
public partial struct CubeMoverSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
float deltaTime = SystemAPI.Time.DeltaTime;
foreach (var (transform, mover) in
SystemAPI.Query<RefRW<LocalTransform>, RefRO<CubeMoverComponent>>())
{
transform.ValueRW.Position.x += mover.ValueRO.Speed * deltaTime;
if (transform.ValueRO.Position.x > 10f)
transform.ValueRW.Position.x = -10f;
}
}
}
Click Play và xem tab Stats, đối với cấu hình máy tôi giao động khoản 143, 108, 57 FPS tương đương với 1000, 10000, 30000 cube được tạo:
![]() | ![]() | ![]() |
---|
Ở file CubeMoverSystem.cs, nếu chúng ta áp dụng Job trong Unity ECS, kết quả có thể cải thiện thêm một chút, tùy thuộc vào số lượng cpu đang có.
// File CubeMoverSystem.cs
using Unity.Burst;
using Unity.Entities;
using Unity.Transforms;
using Unity.Jobs;
[BurstCompile]
[UpdateInGroup(typeof(SimulationSystemGroup))]
public partial struct CubeMoverSystem : ISystem
{
[BurstCompile]
public partial struct CubeMoverJob : IJobEntity
{
public float DeltaTime;
public void Execute(ref LocalTransform transform, in CubeMoverComponent mover)
{
transform.Position.x += mover.Speed * DeltaTime;
if (transform.Position.x > 10f)
transform.Position.x = -10f;
}
}
public void OnUpdate(ref SystemState state)
{
var job = new CubeMoverJob
{
DeltaTime = SystemAPI.Time.DeltaTime
};
state.Dependency = job.Schedule(state.Dependency);
}
}
Phân tích sự khác biệt
Cấu trúc code:
- MonoBehaviour: Logic và dữ liệu được gộp chung trong một class.
- ECS: Tách biệt rõ ràng giữa dữ liệu (CubeMoverComponent) và logic xử lý (CubeMoverSystem).
Hiệu năng:
- MonoBehaviour: Mỗi cube là một GameObject riêng biệt, mỗi lần Update() được gọi riêng lẻ.
- ECS: System xử lý hàng loạt các entity cùng lúc, tận dụng [Burst Compiler] và tối ưu bộ nhớ (data oriented).
Khả năng mở rộng:
- MonoBehaviour: Khi số lượng cube tăng lên (ví dụ 30,000+), hiệu năng sẽ giảm đáng kể.
- ECS: Có thể dễ dàng mở rộng lên hàng chục nghìn entity mà vẫn duy trì hiệu năng tốt.
Kết luận
Qua ví dụ này, ta thấy ECS + DOTS không chỉ giúp tái cấu trúc lại cách viết game logic một cách rõ ràng và có tổ chức, mà còn tăng hiệu năng. Đây là bước đệm quan trọng để phát triển các hệ thống game phức tạp, quy mô lớn.
Bạn có thể tải mã nguồn của bài viết tại đây
Phần tiếp theo
Trong phần tiếp theo, chúng ta sẽ setup được Unity project với DOTS packages hoàn chỉnh