CoReaction – một hướng tư duy khác, thay thế cho StateMachine – P3

Qua phần 1phần 2 đặc sệt lý thuyết, thì phần này mình sẽ trình bày một demo nhỏ, cho bớt khô khan, thêm phần thú vị, hé hé :v

Demo

Mình để demo ở GitHub, các pro clone về tham khảo. Đề bài là một Platformer2D game đơn giản như mình đã dùng làm ví dụ cho Phần 1 và 2.

capture_11132016_164555

Các class quan trọng trong Coreaction

ParameterClasses và ParameterType

Hiện tại mình sử dụng 4 loại parameter (mà có lẽ là cũng đủ rồi) : Boolean, Trigger, Float, Integer. Tương tự 4 loại của Unity animator thế. Interface đơn giản gồm các hàm để đặt giá trị và hàm kiểm tra để dùng cho các Condition.

ReactionController

“Kho chứa” các Parameter và các Reaction. Class chính và quan trọng nhất, đảm nhận việc “chứa” các parameter, khởi chạy các reaction, kiểm tra active state cho các reactions.

ConditionForm

Định nghĩa các Condition để lưu trữ trong các Reaction. Xíu nữa đọc code các bạn sẽ dễ hình dung hơn. Mỗi reaction sẽ có một List<ConditionForm> này.

BaseReaction

Lớp Reaction cơ bản nhất, cho đến giờ thì chưa có trường hợp nào mình cần làm lớp kế thừa cho BaseReaction, nhưng kệ, đề phòng xa, cứ để nó Base class đi kèm các virtual method cơ bản ;).

 

Các step chính trong việc implement Coreaction trong project

ĐƯợc rồi, đến đây mình sẽ bắt đầu “dán” ít code vào cho dễ hiểu.

Định nghĩa các Parameter

Như đã nói từ trên, parameter và reaction sẽ được lưu hết trong ReactionController. Phần khởi tạo và add các parameter được gói gọn trong đoạn code này :

Cú pháp chung để thêm một parameter vào ReactionController là :
reactionController.Add(<ParameterID>, <ParameterType>, <DefaultValue>);

Định nghĩa các Reaction với Action và các Condition

Như mình có ví dụ, 3 action cơ bản được sử dụng trong các Reaction là:

Và đây là đoạn code mình định nghĩa và Add các reaction vào ReactionController :

Cú pháp chung để định nghĩa và thêm một Reaction :
var reaction = new BaseReaction();
reaction.AddCondition(<ParameterID>, <CompareType>, <Value>);
reaction.SetAction(<Action>);
reactionController.AddReaction(reaction);

Update giá trị cho các Parameter trong quá trình chạy game

Sau khi định nghĩa đầy đủ các Reaction, trong quá trình chạy game, ta sẽ setValue cho các Parameter, còn các reaction sẽ được tự động Run() khi các Condition của chúng đều pass. Thông thường thì thao tác SetValue() được thực hiện trong Update(), có một vài parameter sẽ được setValue từ một số event bên ngoài. Đây là đoạn code về phần SetValue cho các parameter

Như ở trên, hầu hết các parameter liên quan tới Input của người dùng, được SetValue trong Update, chỉ có OnGroundState parameter thì được SetValue từ hàm OnGroundStateChange(), hàm này đc gọi từ bên ngoài, tự một component hỗ trợ khác.

Một số câu hỏi xoay quanh cách hoạt động của các thành phần trong Coreaction

Tại sao ParameterID lại là các Integer? Và sao không đặt luôn trong namespace Coreaction như các thành phần còn lại ?

Mình lưu trữ Parameter trong ReactionController bằng một Dictionary<int, BaseParameter>, key cho dictionary là integer. ParameterID được dùng để addCondition cho reaction và để SetValue cho parameter, và để tối ưu khi truy xuất trong Dictionary, mình dùng kiểu integer (không nên dùng string).

Và vì các parameter phải định nghĩa lại ở từng project, đúng hơn là ở từng object có dùng coreaction, vì thế không nên đưa ParameterID vào trong namespace Coreaction như các class kia.

Tại sao ReactionController lại là một MonoBehavior ?

Mình sử dụng LateUpdate() của MonoBehaviour để CheckingActive/Deactive và Run các Reaction. Mình hoàn toàn có thể viết ReactionController là một class bình thường, và gọi Update cho nó cũng đc. Tuy nhiên, để cho tiện dùng thì mình vẫn để MonoBehaivour.

Phần cũng vì, phiên bản Coreaction mình giới thiệu đây là đơn giản nhất, mình đang cải tiến nó, để mọi thao tác AddParameter, AddCondition, AddReaction .v.v. đc thực hiện ngay trong Editor chứ không phải code như thế này, muốn thế thì ReactionController phải là Monobehaviour cho tiện. (Mai mốt version cải tiến mà ổn thì mình share sau :v )

Tại sao lại kiểm tra để chạy các Reaction trong LateUpdate mà không phải Update ?

Có 3 phase chính trong Coreaction :
– Các parameter bị thay đổi value. Nếu có bất kì parameter nào bị thay đổi, một DirtyFlag sẽ đc bật lên, đánh dấu rằng đã có parameter bị thay đổi.
– Kiểm tra dirtyFlag kể trên, nếu đc bật, các reaction đc kiểm tra active/deactive dựa trên value của các parameter.
– Reaction nào đc active (mọi condition đều đúng), sẽ được Run()

Phase Parameter bị thay đổi value, luôn đc thực hiện trong Update(), và có thể đc thực hiện nhờ các event từ bên ngoài. Phải đảm bảo phase này hoàn thiện xong, rồi mới thực hiện CheckingPhase cho reaction. Vì thế mình để CheckingPhase trong LateUpdate().

Làm thế nào mà các Reaction được active/deactive khi parameter thay đổi value?

Ở CheckingPhase cho các reaction, ở mỗi reaction, đơn giản là thực hiện một vòng For qua các condition, nếu có bất kì condition nào failed, thì reaction đó bị deactive, và hàm Run() sẽ không làm gì cả, và khi tất cả condition đều passed, reaction đc active.

Nếu parameter bị SetValue trong Update như thế thì các Reaction cũng sẽ phải check active/deactive liên tục sao ? Performance giảm ?

Khi setValue cho các Parameter, luôn có khâu kiểm tra xem value mới có khác so với value cũ của Parameter ko? Nếu giống hệt thì không làm gì cả, và coi như Parameter chưa bị thay đổi value, và nếu tất cả parameter không bị thay đổi value thì CheckingPhase sẽ được bỏ qua. (Bỏ qua thế nào thì mấy bạn đọc lại chỗ DirtyFlag mình có đề cập ở trên, mục LateUpdate)

Tình huống xấu nhất là khi SetValue cho các FloatParameter, vì số float chỉ cần chênh lệch rất bé đã đc “tính” là thay đổi rồi. Vì thế hạn chế dùng các FloatParameter

Gom góp

Ở trên mình chỉ trích đoạn những phần code quan trọng, và thể hiện rõ cách sử dụng bộ Coreaction. Các bạn nên đọc thêm trong demo, thích thì soi các script trong namespace Coreaction cho dễ hiểu hơn.

Giải pháp trên mình nghĩ ra khi chạy một số dự án trước, đụng tới Statemachine, và thấy rất hiệu quả, dễ bảo trì, dễ mở rộng, code dễ hiểu, nên viết bài share. Tất nhiên, idea mới bao giờ cũng có thiếu sót, hoặc cần phải cải tiến thêm, mình cũng cần thêm ý kiến nhận xét, đóng góp của các pro khác. Các bạn cứ comment ý kiến ở cuối bài, có gì mình thảo luận rồi update bài viết.

Advertisements

11 thoughts on “CoReaction – một hướng tư duy khác, thay thế cho StateMachine – P3

  1. Trong demo chưa có việc chuyển đổi giữa các animation. em chưa biết áp dụng vào việc chuyển đổi ấy. như là animation nhảy, animation di chuyển… a gợi ý cho em với.

    Like

    1. À, cái vụ Animation anh định làm bài riêng, vì trình bày luôn vào trong này, dễ gây lẫn lộn :v. Cũng không quá phức tạp gì, vẫn giữ tư duy chung khi làm việc với Animator : design state và các transition giữa các state thật kĩ, rồi từ code chỉ có SetBool, SetFloat .v..v…, hạn chế tối đa việc chơi animation một cách trực tiếp bằng animator.Play().
      Rồi, còn trong project anh từng làm qua (có dùng Coreaction), thì thế này :
      1. Animator : hạn chế tạo quá nhiều parameter, chỉ một số ít cần thiết (onGround, speed, attack …).
      2. Thêm một số reaction chỉ chuyên biệt cho việc SetBool, SetTrigger .v.v. vào Animator. Vd: SetOnGroundParameterForAnimatorReaction, action là animator.SetBool(true/false), condition là onGround=true/false … Đại khái thế, độ vài cái reaction nữa cho các parameter còn lại.

      Like

  2. Em chào anh . Em thấy anh có nhiều bài viết về Unity khá hay mà trước đó em không hề biết. Em có khá nhiều thắc mắc khi làm Unity. Anh có thể cho em xin địa chỉ mail hay skype để em trao đổi với anh được không ạ? Em mới làm unity nhưng mà tự học nên vẫn còn ngu ngơ lắm @@ Em cảm ơn

    Like

  3. chào bạn. mình thấy nó gần giống với BehaviourTree AI. Lúc đầu đọc bài viết, mình có tìm trên mạng về từ khóa CoReaction thì ko ra :V
    Bạn làm 1 bài về BehaviourTree nữa thì tốt

    Like

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s