Entity System trong Unity3D, các framework ECS cho Unity – P2

Mục lục:

  • Phần 1 : tào lao cơ bản về EntityComponentSystem. (you’re here).
  • Phần 2 : những framework thông dụng, cấu trúc của chúng.

Ở  phần 1, mình đã tào lao về vài thứ cơ bản như :

  • ECS là gì ?
  • Sức mạnh của ECS là gì ?
  • Unity3D có phải là một engine dựa trên ECS không ?

Ở phần 2 này, mình sẽ xoay quanh vài món sau:

  • Cấu trúc của framework như thế nào ?
  • Có những ECS framework nào để tôi dùng chung với Unity3D ?
  • Khi nào nên dùng ECS trong project của bạn ?

1.  Unity đã là một ECS engine rồi, còn mần thêm ECS framework với Unity làm gì nữa ?

Hướng tiếp cận hiện tại vẫn còn “nặng” hơi hướng OOP

Trong hầu hết các tuts về scripting trong Unity, đều ngã về Hướng đối tượng (OOP) nhiều hơn, các giải pháp design codebase vẫn xoay quanh kế thừa (Inheritance), ảo hóa (abstract, interface) rồi đến các pattern quen thuộc như Singleton, Command, MVC … Trong ECS mình chưa thấy người ta áp dụng các tư duy đó.

Nhìn chung sử dụng OOP trên “nền” ECS cũng không có gì là sai cả, vẫn hiệu quả trong đa số các trường hợp, tuy nhiên vẫn khó để đạt được độ flexible, decoupling như ECS. Lượng “fan” của ECS vẫn lớn, nên trong cộng đồng vẫn xuất hiện nhiều framework ECS cho Unity.

Unity chưa cho phép can thiệp vào hệ thống System bên dưới

Điều này thì dễ thấy rõ rồi, bạn chưa thể viết thêm một “custom” System cho Unity để chuyên xử lý cho những “custom” components của bạn đc, đa phần bạn sẽ viết các Monobehaviour ở “bên trên” để xử lý cho các behaviour trong game. Cũng chính vì thế nên các framework sẽ phải xây dựng lại khá nhiều để áp dụng được ECS vào Unity.


2.  Cấu trúc của các ECS framework ?

Framework có thể nhiều, nhưng rồi chúng cũng sẽ xoay quanh việc giải quyết các bài toán cơ bản như sau :

2.1. Xây dựng lại hệ thống quản lý Entity, Component

Đây là điều hiển nhiên, phải có một “nơi” để lưu trữ các “kho” Entity, component, hỗ trợ thao tác tạo, xóa entity/component. Ngoài ra còn để thực hiện việc Group các entity, và đôi khi là group các Component.

2.2. Tạo và quản lý các Group

Đây chính là phần giải quyết vấn đề về performance của thao tác Iterating trong các System mà mình đã có nêu ở Kết luận trong Phần 1. Mỗi System thường sẽ có một Group đi kèm (vd: MovingSystem sẽ làm việc với MovableGroup – là tập hợp các Entity có MovableComponent).
Việc này được thực hiện kết hợp với hệ thống quản lý Entity, Component bên trên. Khi có sự thay đổi của các entiy, component (thêm, xóa) thì các Groups cũng phải được update theo.

2.3. Update cho các System

Các System luôn cần Update() mỗi frame. Hầu hết các framework đều “mượn” Update() từ Monobehaviour của Unity để update cho các Systems.

2.3. Giải quyết vấn đề “liên lạc” giữa các System

Nhìn chung là xoay quanh ObserverPattern, các System sẽ “lắng nghe” các sự kiện trong game. Để dễ hình dung, mình có đề bài đơn giản thế này : khi con monster chết thì phải xuất hiện một explode effect tại vị trí monster. Theo như mình ngâm cứu thì các framework có 2 hướng :

  • Tạo một event, chứa các data cần thiết cho việc tạo hiệu ứng (position, effectID), rồi gửi event này tới những system có đăng kí “nghe”. Sẽ có một EffectSystem gì đó xử lý khâu này
  • Tạo luôn một Entity, gắn EffectComponent (có trường position, effectID), và EffectSystem sẽ làm việc với entity này như những System bình thường khác.

2.5. Tạo “cầu nối” giữa framework và Unity

Đây cũng là khâu quan trọng nhất, vì thật sự là chúng ta đang đi xây dựng một ECS “bên trên” của một ECS có sẵn, nên phải có những thứ cần được “đồng bộ”:

  • Các Entity trong hệ thống, cũng phải tương đương với các GameObject trong Unity
  • Chuyển các Event của Unity vào framework cho các System xử lý :
    + Collision event : OnCollisionEnter/Exit() …
    + Create/Destroy entity, component events: Start(), OnDestroy()..
    + Input events : mouse, keyboard
    + Application events : OnApplicationPause/Quit() …
    + ..v…v…

3.  Có thì down về xài thôi, tại sao phải ngâm cứu cấu trúc framework làm gì ?

Đánh giá performance của framework

Đây là tiêu chí đầu tiên mà các developer luôn muốn biết khi sử dụng bất kì một 3rd package nào. Nói đến ECS nghĩa là nói đến một hệ thống sẽ phải lưu trữ, quản lý số lượng entity, component, group rất lớn. Nếu như những thao tác thêm/bớt/sửa không được chú ý thì performance game sẽ tụt mạnh.

Custom lại framework

  • Không phải ECS framework nào cũng đáp ứng đầy đủ cho project của bạn. Có thể bạn sẽ phải viết thêm nhiều Action, event cho framework thì mới dùng trong project đc.
  • Tương lai khi Unity update, có thể sẽ có những thành phần không hoạt động trong framework nữa, bạn cần update framework theo Unity. Đây chính là khâu “cầu nối” với Unity mà mình đề cập ở trên.
  • Áp dụng ObjectPool pattern. Đây là một pattern cực quan trọng, tuy nhiên trong những framework mà mình dùng, không cái nào đề cập đến việc này cả. Để có thể đưa ObjectPool vào, bạn phải can thiệp vào cái khâu quản lý thêm/xóa entity mà mình để cập ở trên.

Học nhanh các framework hoặc Engine khác

Tương lai có thể có thêm nhiều framework ECS khác mọc lên, nhưng về nền tảng thì chúng đều giống nhau, nếu bạn nắm được cái lõi, thì sẽ học nhanh hơn.
Hơn nữa, hầu hết các game engine xuất hiện gần đây đều đi theo hướng ECS design này, nắm được ECS bạn sẽ học công cụ nhanh hơn, 😉

Tự tạo một ECS framework

Có thể lắm chứ, biết đâu một ngày nào đó bạn có ý tưởng hay hơn, có thể tạo ra một ECS framework cho riêng mình, hoặc publish cho cộng đồng sử dụng. 😉


4. Có những ECS framework nào để tôi dùng chung với Unity3D ?

Nói dông dài cả buổi, cuối cùng cũng đi vào phần chính. :D.
Dưới đây là một số framework mình đã có dùng qua :

4.1. Entitas

Một dự án OpenSource, các bạn dễ dàng tìm thấy trên GitHub, dự án này nổi lên từ sau hội nghị Unite Europe 2015, nhận được khá nhiều Star trên GitHub. Có sẵn một số ví dụ và cả các sample do cộng đồng tự làm và share tại đây, các bạn có thể tham khảo thêm. Về cảm nhận cá nhân của mình thì :

Ưu điểm

  • Performance rất tốt.
    Entitas thực hiện rất tốt khâu quản lý Entity, Component, Group. Sử dụng tối đa Pool để lưu trữ entity, component nên không lo vấn đề memory. Kết hợp việc cache entity, component, nên performance cho việc Iterating trong các System vẫn hiệu quả nhất.
  • Hỗ trợ Code Generating
    Giảm bớt thời gian cho việc code các Component
  • Hỗ trợ thao tác với Entity/Component trong Inspector (ở mức tương đối)

Nhược điểm:

  • Phần “cầu nối” giữa framework và Unity chưa thực sự mạnh
    – Phần vì Entity trong framework không tương đương với GameObject trong Unity cho nên thao tác add/remove component vào Gameobject sẽ hơi tốn công hơn. (lần update gần nhất, Entitas có thêm Component EntityLink để giải quyết việc “link” Gameobject với entity)
    – Component của Entitas là component đúng nghĩa của ECS, tức là chỉ chứa raw data, ko có bất kì method nào trong đó, nó không phải là một Monobehaviour như các ScriptComponent khác của Unity, nên ko thể thực hiện thao tác Add/Remove trong Inspector như các component khác.
    – Việc “transfer” các events của Unity như Physics, Input … đều phải làm manual, framework chưa hỗ trợ sẵn các module này để dùng.
  • Lượng code phải gõ hơi nhiều
    Điều này có lẽ xuất phát từ nguyên nhân “cầu nối” mình nêu ở trên. Cho đến giờ thì hầu hết các khâu tạo/xóa entity, thêm/bớt component … các bạn đều phải làm trong code, chứ chưa hoàn toàn theo kiểu đóng prefab (nghe bộ gần giống hardcode thế này). Có lẽ cần thêm vài Editor extension hỗ trợ nữa.
  • Liên lạc giữa các System trong Entitas theo kiểu “tạo entity, gắn Component” như mình đã đề cập “Cấu trúc framework” ở trên. Việc này khiến bạn đôi khi phải có thêm một số component, system chỉ chuyên cho việc xử lý event (có vẻ hơi thừa).
  • Không build sẵn ObjectPool
    Như mình đã nói từ đầu, đây là điểm yếu chung mà tất cả các framework mình dùng qua đều gặp.

Tổng kết : nói chung thì để áp dụng Entias mình thấy vẫn hơi khó, nhưng cũng nên tìm hiểu nó, để học được việc Optimize Performance, cách quản lý và cache entity, component.

4.2. EgoCS

Xuất hiện sau Entitas, các bạn có thể xem tại UnityForum và project này trên GitHub. Mình cũng từng ngâm cứu và có liên lạc trực tiếp với tác giả để tối ưu performance vài chỗ trong framework. Tuy nhiên dạo gần đây (từ 4/2016) không thấy tác giả Update tiếp. Cá nhân mình thì cảm nhận như sau :

Ưu điểm :

  • “Thân thiện” với Unity:
    Vì Entity/Component trong framework cũng tương đương với Gameobject/Component trong Unity, nên sẽ tiếp cận sẽ dễ dàng hơn.
  • Framework cũng có hỗ trợ transfer một số event cơ bản của Unity (Collision ..). Cho nên nhìn chung thì khâu “cầu nối” có vẻ ổn.
  • Hệ thống quản lý EntityComponent có vẻ ổn, tác giả có viết một số method tối ưu cho khâu GetComponent(), có lẽ là phục vụ cho việc tạo Group

Nhược điểm:

Có lẽ nhược điểm lớn nhất là … chưa có demo projects, nên cũng chưa có gì nhiều để nhận xét, đánh giá 😛

4.3. Unity-Ash

Một ECS framework, phát triển dựa vào Ash framework cho ActionScript, các bạn có thể tìm thấy trên Github. Cái này thì mình chưa có dùng, chỉ mới xem sơ qua demo của họ, thêm vào đây để bạn nào thích thì ngâm cứu 😉

4.4. uFrame ECS

Đây là một package trên UnityAssetStore, xuất hiện cũng khá lâu, giờ đã free và OpenSource trên GitHub. Project này mới open từ khoảng tháng 5/2016. Theo cá nhân mình đánh giá thì đây là framework rất ổn cho việc áp dụng ECS vào project.

Ưu điểm

  • “Thân thiện” với Unity
    uFrame giống với EgoCS ở khoảng này, entity/component hoàn toàn tương ứng với Gameobject/Component trong Unity, nên việc add/remove Component được thực hiện trong Inspector rất bình thường
  • Hỗ trợ Visual Scripting
    Đây có lẽ là tính năng sáng giá nhất, giảm thời gian code để bạn tập trung vào việc design nhiều hơn, uFrame sẽ generate hầu hết các code của bạn.
  • Hỗ trợ Debug ngay trong Graph
    Đây cũng là tính năng rất hữu hiệu cho quá trình debugging
  • Cho phép Custom khá nhiều
    Bạn có thể tạo thêm Event, CustomAction cho project, vì những build-in event,action thường không đủ.

Nhược điểm:

  • Làm việc nhóm với Git, SVN
    Cũng như bao VisualScripting khác, uFrame sẽ generate ra rất nhiều file json để lưu trữ các Diagram trong quá trình design, và thường là nó không xóa các file thừa, cho nên dung lượng folder diagram tăng lên liên tục. Các bạn cần biết điều này để tránh đưa những file thừa đó vào Git (nếu sử dụng Git để quản lý mã nguồn). Giải pháp cụ thế có lẽ mình sẽ trình bày ở một bài khác, viết riêng về uFrame
  • Module Import/Export chưa thật sự tốt
    Việc backup hệ thống khi làm việc với uFrame là điều nên làm thường xuyên. Tuy nhiên thao tác Import lại rất nặng, thời gian import rất lâu (vì thường thì lượng file generate khá lớn)
  • Phần DrawInspector rất nặng
    Cũng vì các Editor script khá nhiều, lại phức tạp, để hỗ trợ cho phần visual trên Inspector, nên nếu GameObject nhiều component, sẽ khiến bạn khó khăn trong việc “cuộn” trên Inspector. Cái này thì ảnh hưởng UX xíu thôi 😀
  • Cộng đồng chưa nhiều, nên việc tìm kiếm demo hoặc nhờ support sẽ khó
  • Không build sẵn ObjectPool : bệnh “toàn dân” của các framework 😛
  • Còn nhiều lỗi vặt khác trong quá trình dùng, tất nhiên có giải pháp hết, mình hẹn trong một bài viết khác.

Kết luận : cơ bản thì uFrame vẫn là thành viên khá sáng giá, nên dùng


5. Khi nào nên dùng ECS trong project của bạn ?

“Ôi, ECS đã quá !!! Dùng ngay, xài ngay !!!”
Trước khi nhào vào dùng ECS thì các bạn nên ngâm qua mấy món sau:

Tiếp cận tư duy ECS không phải đơn giản

Trong trường học cũng như ra đi làm, đa số là chúng ta tiếp cận OOP, rất khó để chuyển từ tư duy của kế thừa, đa hình … sang tư duy hướng dữ liệu (data driven), hướng hành vi (behaviour) như trong ECS.

Để có thể ra được một bản design codebase (bao nhiêu Component, System, những Event nào trong game …) sẽ hơi khó. Không khéo sau một hồi bạn sẽ vô tình quay về với OOP lúc nào ko hay (nào là PlayerComponent và EnemyComponent với một đống data, PlayerSystem và EnemySystem sẽ ôm hết behaviour cho Player và Enemy …) o_O

Mình cũng có tổng hợp một số bài viết về những mindset khi làm việc với ECS, đính link ở cuối bài, các bạn có thể tham khảo thêm.

Lượng code viết ra có thể rất nhiều

Vì ECS là đi theo hướng “tổng quát hóa”, hướng “hành vi” (behaviour). Từ chỗ design xem có bao nhiêu đối tượng, bạn sẽ chuyển qua design xem có bao nhiêu behaviour trong game (di chuyển khi có input, phát nổ khi va chạm, tự trở đầu khi gặp chướng ngại vật ….), từ đó bạn sẽ phải quy đổi ra thành rất nhiều module con, kèm theo nhiều Component, System.

Lấy ví dụ điển hình, đề bài đơn giản, bạn clone cái demo quen thuộc của Unity Platformer2D sang ECS thế nào ?

ECS-demo-platformer2d

Với OOP thì đơn giản, cộng thêm một chút “pha loãng” thành Component, các bạn sẽ hình dung, chỉ cần một số scripts như : Player, Enemy, Bomb, Rocket …

Nhưng với ECS, các bạn sẽ cần design ra một hệ thống đại loại thế này :
+ Components : Movable, MovingOnInput, GiveDamageOnCollision, ExplodeOnCollision, AutoMoving …
+ System : tương ứng MovingSystem, ExplodeSystem, AutoMovingSystem …
Nói chung là cũng kha khá nhiều thứ cần design đấy.

Khi nào nên dùng và dùng như thế nào?

Lượng behaviour trong game nhiều và phức tạp
Khi game của bạn ở mức trung tới lớn, các thuộc tính của object lớn và cần được tổng hợp bởi nhiều thuộc tính con .v..v.. các bạn có thể xem lại phần 1 ở phần “Lợi ích ECS”, từ lợi ích ECS có thể suy ra lúc nào cần dùng.

Giữ OOP + tách Component hợp lý
Cá nhân mình thấy, nếu project vừa phải, thì OOP vẫn ổn, bạn vẫn có thể kết hợp tư duy ECS trong việc phân tách component và bố trí method phù hợp, vẫn mang lại hiệu quả như thường. Mình cũng từng đọc qua một số Best scripting practices cho Unity, đề cập tới việc viết Components sao cho ổn, hy vọng vài bữa có thời gian sẽ tổng hợp lại ở một bài khác.

Kết hợp cả ECS và OOP
Tất nhiên, một project không nhất thiết là ôm khư khư ECS từ đầu tới cuối. Có thể chỉ cần ECS cho các thành phần core. Còn các khâu lặt vặt như UI, load/save data … cứ theo kiểu OOP bình dân, nhanh gọn hiệu quả 😉


6. Tổng kết

Trên đây là một số trải nghiệm linh tinh của mình trong quá trình sử dụng ECS. Nếu có vấn đề gì các bạn cứ comment bên dưới, mình sẽ bổ sung để bài viết tốt hơn.

Nếu rãnh có thể mình sẽ làm bài tut về Entitas hoặc uFrameECS. Mà thiết nghĩ, sau 2 bài ECS này chắc mấy bạn cũng tự học được mấy framework ECS rồi, dễ òm á mà 😉

Link tham khảo

Advertisements

6 thoughts on “Entity System trong Unity3D, các framework ECS cho Unity – P2

  1. Hi! Hiện tại mình đang làm một side project kiểu Tower Defense và lần đầu mình ứng dụng với ECS (mình dùng unity + entitas). Cảm giác biến ý tưởng thành code rất mạch lạc và rõ ràng. Tuy nhiên trong quá trình sự dụng mình cũng vấp phải một số vấn đề. Mình có một số câu hỏi muốn xin ý kiến của bạn.
    1. Biết rằng trên cùng một Entity không thể tồn tại 2 components giống nhau được. Vậy có nên tách thành 2 components không? Ví dụ: ta có slow component add vào enemy entity trong một khoảng thời gian 3s, đến giây thứ 2 (kể từ khi add slow component lần đầu) ta lại muốn add thêm một slow component add vào enemy entity…
    2. Khi muốn thể hiện mối liên kết giữa các Entities với nhau thì ta nên làm thế nào? Ví dụ: một Character Entity có nhiều Skill Entites. Thì mình nên thể hiện mối quan hệ này như thế nào?

    Like

    1. 1. Movement và SlowComponent
      Một entity có 2 component giống nhau, mình thấy cũng bình thường, như Unity chẳng hạn, 1 entity (GameObject) có thể có vài cái collider cũng đc mà (đồng ý là có những component chỉ đc phép add duy nhất một (), nhưng đa số đều có thể add nhiều). Theo như mình nghĩ, bạn đang có một entity với MovementComponent, trong đó có thuộc tính speed, và dụng ý khi thêm SlowComponent là để làm giảm vận tốc MovementComponent này, và có thể là làm giảm 50% speed trong vòng 3s, càng thêm SlowComponent thì speed càng giảm.
      Nếu như bạn dùng Entitas, mình đoán bạn sẽ có một MovementSystem, với Group (Movement).
      Vụ Slow mình nghĩ thế này, logic là, cứ add một SlowComponent thì giảm movement.speed xuống 50%, hết 3s thì gấp đôi movement.speed lên 2 (để trở về như cũ). Và cứ có một logic mới, liên quan tới việc thuộc tính của component này bị ảnh hưởng bởi component khác, thì cần phải có một System. Vậy thì cần một SlowSystem (Reactive + execute), group (Movement, Slow). SlowSystem khi đc trigger (SlowComponent đc added) thì giảm movement.speed xuống 50%, trong execute thì đếm lui 3s, đủ 3s thì nâng movement.speed lên lại.

      2. Liên kết giữa entity.
      Chỗ này chưa thực sự hiểu ý bạn lắm. “Character Entity có nhiều Skill Entites”, mình nghĩ chắc ý bạn là nhiều Skill components. Mình đoán có thể bạn đang muốn cấu tạo thuộc tính cho Character bằng nhiều component lẻ như Movement, FLyable, Shooting … Và có mối liên hệ về mặt logic (vd : nếu vừa có Movement và Shooting thì Movement.speed sẽ giảm chẳng hạn). Nếu đúng như vậy, thì bạn thử theo hướng giống câu 1) : cách tương tác giữa SlowComponent và MovementComponent đc thực hiện thông qua SlowSystem.

      Món ECS này hay, mà cũng khó, quả thực, mình chưa gặp ai đủ tự tin nói “tôi có thể làm 1 bản design Component-System tốt” cả. Mình cũng định viết 1 bài tổng hợp các trick, tips trong việc design theo ECS này (theo chủ quan là chính), mà dạo này bận quá. Hy vọng vài dòng comment trên giúp ích 😉

      Like

  2. Cảm ơn bạn đã chia sẻ ý kiến! Hồi đầu mình mới làm quen với ecs nên đầu óc vẫn chưa được mạch lạc cho lắm. Sau một thời gian thực hành thấy tư tưởng làm game đã thay đổi rất nhiều, không còn bó buộc vào oop như trước nữa.
    Mà chẳng hiểu sao lâu lâu mình mới connect được với blog này được. Có thể do quá tải hay giới hạn connection chăng?
    Một lần nữa cảm ơn về những chia sẻ nhiệt tình của bạn 😀

    Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s