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

Được rồi, Phần 1 lý thuyết chắc hơi khó nút, ở đây mình sẽ lấy ví dụ từ thực tế, và đưa ra cả 2 cách giải quyết theo cả StateMachine và CoroutineReaction

Bài toán newbie

Lấy lại cái đề bài Platformer2D cho nó đơn giản hé.

capture_11132016_164555

Con monster sẽ di chuyển qua lại, nhảy lên xuống trên các platformers. Một số yêu cầu cụ thể như sau :

  • Bấm W để nhảy, nhưng chỉ nhảy khi con Monster đang đứng trên mặt đất, còn ở trên không rồi thì không nhảy đc nữa.
  • Bấm A,D để di chuyển qua lại. Ở trên không hay dưới đất thì đều có thể bấm A,D để qua lại. Chỉ có một điều kiện : vận tốc con monster ko đc quá một số đc config (vd: 5 unit/second)
  • Khi bấm A thì phải xoay mặt con monster về bên trái, và ngược lại khi bấm D.

Giải theo “kiểu” StateMachine

Cái này thì như Phần 1 mình có trình bày sơ bộ rồi, đại khái sẽ có

  • OnGroundState : trong Update() làm vài việc linh tinh như
    – kiểm tra nhấn W thì add lực lên rigidbody
    – kiểm tra nhấn A,D thì thay đổi vận tốc, giới hạn vận tốc di chuyển.
    – kiểm tra nhấn A,D để đổi cái localScale, xoay mặt con monster đúng hướng
    – kiểm tra OnGround = false để chuyển qua InTheAirState
  • InTheAirState : tương tự, trong Update() làm vài cái linh tinh
    – kiểm tra nhấn A,D để thay đổi vận tốc + xoay mặt monster (giống OnGroundState)
    – kiểm tra OnGround = true để quay về lại OnGroundState

Phân tích theo “kiểu” CoReaction

Nhìn tổng thể, các bạn sẽ thấy đc một số hàm cần được viết như :

  • Jump() : sẽ AddForce gì gì đó lên cái Rigidbody, theo trục Y, để làm con monster bay lên
  • Move() : cũng addForce lên Rigidbody, tùy trường hợp, theo hướng trái, phải gì đó (force.x>0 || force.x<0) để di chuyển con monster theo chiều ngang.
  • RevertRender() : tác động lên cái scale của transform, để thực hiện việc “đảo” chiều render của con monster.

Và cái đống if..else các bạn viết trong từng State, là để gọi những hàm trên nhưng phải đảm bảo những condition cụ thể. Từ đây, mình có thể phát biểu lại như thế này :

  • Reaction Jump : sẽ thực hiện việc AddForce lên rigidbody, khi đảm bảo các điều kiện sau là đúng :
    • Con monster đang ở trên mặt đất (OnGround = true)
    • Người chơi NHẤN nút W (Input.GetKeyDown(Keycode.W) = true)
  • Reaction Move : cũng thực hiện AddForce lên rigidbody theo phương ngang, điều kiện để thực thi là :
    • Người chơi GIỮ nút A hoặc D (Input.GetKey(KeyCode.A || D) = true)
    • Vận tốc x của rigidbody không vượt quá một ngưỡng đc config trước (Mathf.Abs(velocity.x) < limitSpeedX) .
  • Reaction RevertRender : thực hiện thay đổi scale của transform, điều kiện để thực thi là :
    • Người chơi NHẤN nút A hoặc D (Input.GetKeyDown(Keycode.A || D) = true)

capture_11132016_172225

Các thành phần trong CoReaction

Parameter

Phân tích cái OnGround mà mình hay đề cập ở trên nhé. Nó là một cái biến bình thường, kiểu boolean, giá trị của nó (true/false) sẽ được thay đổi bởi một hàm kiểm tra nào đó (CheckOnGround() chẳng hạn). Và giá trị của nó sẽ ảnh hưởng đến một số reaction (vd cái Jump reaction sẽ bị deactive khi OnGround = false).

Tương tự, biến velocity.x, kiểu float, giá trị của nó phụ thuộc vào Rigidbody, sẽ ảnh hưởng tới Move reaction, vì velocity.x vượt ngưỡng config thì Move reaction sẽ bị deactive.

Vậy parameter là những tham số ban đầu, kiểu dữ liệu có thể là boolean, int, float. Giá trị của nó sẽ bị thay đổi, và ảnh hưởng tới trạng thái active/deactive của các reaction. Thông qua đó, ta sẽ điều khiển các reaction bằng cách thay đổi giá trị của các parameter này.

Vd linh tinh chút, reaction Shooting sẽ bị deactive khi parameter Ammo bị set về 0, reaction Move, Jump … sẽ được active khi parameter Health > 0 .v.v…

parameter.png

Condition

Như mình có gõ ở trên, Jump reaction được active khi đảm bảo cả 2 điều kiện : OnGround = truePressJumpKey = true., một trong hai thôi cũng không được.  Condition là các câu lệnh kiểm tra giá trị của Parameter có đúng theo yêu cầu được quy định hay không. Mỗi reaction có thể cần nhiều condition, và reaction chỉ active khi TẤT CẢ condition đều thõa mãn.

Action

Jump reaction, khi thõa mãn các condition, nó sẽ thực hiện action : AddForce vào rigidbody. Action là những lệnh sẽ được thực thi khi các condition của một reaction đều thõa mãn (hay reaction được active).

Rồi, giờ có thể tóm tắt lại các reaction theo kiểu “có code” :

Reaction_01.png

Cách phân tách thành Parameter và Condition này ở đâu nghe quen quen…

Hơi giống cách mà Unity design ra cái Animator component của họ phải không nào 😉

Animator.png

Animator dùng các ParameterCondition để định nghĩa cho các Transition giữa các state bên trong nó. Tuy nhiên, Animator là một .. kiểu StateMachine thuần túy, nên cùng lúc chỉ có một state được chạy. Cái hay ở chỗ, khi đã design các transition cẩn thận, công việc còn lại trong code, là set giá trị cho các Parameter (SetBool, SetFloat, SetTrigger …), và để các state tự luân chuyển với nhau.

Và mình hoàn toàn có thể sử dụng hướng tư duy này để định nghĩa các Reaction :

  • Khai báo các Parameter ban đầu (OnGround, VelocityX ..v..v..)
  • Định nghĩa các Reaction, bao gồm 2 yếu tố :
    • Các condition, là các lệnh kiểm tra giá trị các parameter
    • Action : lệnh thực thi khi mọi condition đều đảm bảo.
  • Và cũng tương tự, sau khi đã định nghĩa các reaction, công việc còn lại là set giá trị các parameters (các giá trị này sẽ thay đổi liên tục trong quá trình game), còn các reaction sẽ tự động kiểm tra Conditions của nó và thực thi Action khi TẤT CẢ Condition của nó đều đúng.

Ủa thế nó hay hơn “kiểu” StateMachine ở điểm nào nhỉ?

Code rõ ràng hơn

Sẽ không còn mớ if..else để kiểm tra luân chuyển giữa các state, sau khi đã phân tách thành các reaction, code của bạn sẽ ngắn hơn và sẽ dễ hiểu hơn.

“Đa luồng”

Các reaction gần như được chạy “song song”, mỗi khi thay đổi giá trị parameters, có thể một hoặc vài reaction được active, đây cũng là lý do vì sao mình gọi nó là Co-reaction (na ná Co-routine đúng không :v ).

Dễ mở rộng

Việc thêm một parameter, thêm một reaction, luôn đơn giản hơn nhiều so với việc : thêm một state, viết lại các dòng if..else để luân chuyển giữa các state, kiểm tra các state cũ coi có bị ảnh hưởng bởi state mới không ….

Như mình đã có ví dụ ở phần 1, project thực tế thường phức tạp hơn, vd như :
– Thêm tính năng double jump, nhảy được 2 bước trên không
– Trên mặt đất, nếu bấm S thì ngồi xuống, và không di chuyển qua lại
– Nếu trên tay có cầm thêm tảng đá thì vận tốc giảm xuống, không nhảy double jump được, không leo tường được..
– Đang leo tường thì không di chuyển qua lại được, chỉ có thể nhảy 1 lần
– .v.v…
Mời các bạn xài StateMachine : Ground, OnAir, Crouch, GroundCarrying, OnAirCarrying, ClimbWall ..v.v..

statemachine_mess

Còn nếu tư duy theo Coreaction, sẽ chỉ còn một số reaction đại khái như sau :
– HorizonMoving : Action (add force di chuyển qua lại), Condition (Crouch = false, Climb = false)
– VerticalMoving : Action (addForce đi lên xuống), Condition (Crouch=false, Climb=true)
..v.v…
Đại khái thế, chủ yếu là mở rộng condition, về cơ bản thì action và parameter ít thay đổi.

Vẫn khó hình dung thế nào ấy, ước gì có ít code ….

Được rồi, đến đây nếu bạn nào cảm thấy Coreaction có vẻ lạ và hữu dụng thì có thể qua Phần 3, mình sẽ trình bày một demo ngắn, theo cách đơn giản và dễ hiểu nhất có thể. Mình tách ra để hạn chế post quá dài.

Advertisements

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

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