CHƯƠNG IX – STATE MACHINE, PLAYER, BOT

Link Source code HD không che: https://goo.gl/nQKxdE 
Các bạn nên mở sources code vừa đọc vừa so sánh với tut này vì mình sẽ giải thích những gì trong sources code.

1. State (trạng thái object)

  • State ám chỉ trạng thái của nhân vật (player) hay của các game object (VD: tảng đá di chuyển, kẻ địch, boss, …) mỗi object đều có trạng thái riêng. Như dưới đây là trạng thái của Mario.
  • Tại một thời điểm bất kì thì object chỉ có duy nhất 1 trạng thái (như trên là 1 trong 4 trạng thái đó) không có trường hợp 2 trạng thái.

  • Không phải trạng thái nào cũng có thể chuyển đổi qua lại, như trên thì từ Falling chỉ chuyển qua được Standing khi có vật chạm dưới chân chứ không thể nào từ Falling sang running hay jumping được.

  • Vấn đề đặt ra là làm sao để có thể quản lý được những trạng thái này cho gọn nhẹ và dễ dàng quản lý sửa đổi. Cách đơn giản nhất là if, else đặt các trigger để chuyển state rồi xử lý trong switch case hoặc if, nếu nhân vật có ít state thì dễ dàng cài đặt nhưng nếu có nhiều state thì sao (vài chục hay cả trăm)? Lúc này Pattern State Machine được nêu ra và áp dụng vào giúp cho việc quản lý state dễ dàng hơn trong việc code và update state.


2. Entity

  • Entity là 1 Abstract class (Chỉ dùng cho kế thừa), class này định nghĩa 1 object có body (như hình chữ nhật để kiểm tra va chạm), Position (Vị trí object) Và vận tốc (vx, vy). Mình tạo class này với những tham số và hàm get set của nó để dùng cho việc áp dụng cho những đối tượng Moveable (Có thể di chuyển đượcc). Bản chất thì sau mỗi lần update tọa độ sẽ được dời theo vector (vx, vy) với vx là vận tốc theo Ox và vy là vận tốc theo Oy. Mình để class Entity này trong folder GameObjects, các bạn có thể vào đó xem thông tin của class này.

  • Entity cũng sẽ sử dụng để xử lý va chạm và mình sẽ đề cập ở phần sau.

  • Class Entity này được mình đặt trong Folder: GameObjects






3. Player State, Player Data

  • Các class của Player mình đặt trong folder: GameObjects/Player

  • Để áp dụng State Machine thì mình sẽ có 2 class là PlayerData và PlayerState. PlayerData sẽ nắm giữ con trỏ thông tin của Player và PlayerState vì việc chuyển State của player phụ thuộc yếu tố bên ngoài và bên trong nên phải có việc chuyển đổi qua lại thuận tiện giữa các state trong class Player và cả trong class PlayerState. PlayerData sẽ giúp từ class State của player có thể gọi các hàm, cũng như chuyển state trong class Player, và cũng giúp từ State có thể lấy thông tin của player như position, vận tốc,….
  • PlayerData là class đơn giản vì chỉ nắm giữ thông tin con trỏ. Mình có sử dụng pre-define class PlayerState và Player vì mình không thể include class PlayerState và Player được vì trong class Player và PlayerState đều nắm con trỏ PlayerData. Nếu trong class PlayerData bạn include PlayerState và trong class PlayerState bạn include PlayerData thì sẽ xuất hiện lỗi ở Visual Studio.




  • PlayerState là 1 abstract class dùng cho kế thừa, mỗi State sẽ là 1 class khi chuyển State ta chỉ việc chuyển con trỏ state trong class Player đến State mới, việc phân class sẽ giúp ta dễ dàng quản lý và sử lý ở hàm update hơn. Việc dùng if và switch case quá nhiều đôi khi sẽ dần đến việc rối code. Như Mario ở trên đầu bài có 4 state thì mình sẽ có sơ đồ class như sau.



  • Class PlayerState
  • Thuộc tính: chỉ có 1 thuộc tính mPlayerData nắm giữ thông tin của State hiện tại và Player.

  • Hàm:
    • GetState(): là hàm thuần ảo (bắt buộc phải kế thừa) trả về tên state hiện tại (tên theo enum đã định nghĩa trước).

    • HandleKeyboard(): hàm lấy thông tin keyboard những phím nào đang bấm xuống hay không bấm xuống để xử lý bàn phím trong state.

    • Update(): hàm update, được gọi mỗi frame trong class Player.

    • Mình có tạo 4 state tương ứng của Mario các bạn có thê vào đó để tham khảo cách xử lý các state này.

4. Class Player

  • Là class kế thừa từ class Entity vì player có thể di chuyển và va chạm (phần va chạm sẽ nói ở sau).

  • Class Player sẽ nắm giữ thông tin của PlayerData và thực hiện update state này và truyền thông tin bàn phím cho state này từ PlayerData.
  • Class Player sẽ nắm giữ các Animation tương ứng với mỗi State, khi chuyển state thì cũng sẽ chuyển ngay các animation. Mỗi Animation sẽ có size khác nhau nên sau khi chuyển animation thì mình sẽ set lại size cho Player.
void Player::changeAnimation(PlayerState::StateName state)
{
    switch (state)
    {
        case PlayerState::Running:
            mCurrentAnimation = mAnimationRunning;
            break;
        case PlayerState::Standing:
            mCurrentAnimation = mAnimationStanding;
            break;
        case PlayerState::Falling:
            mCurrentAnimation = mAnimationJumping;
            break;
        case PlayerState::Jumping:
            mCurrentAnimation = mAnimationJumping;
            break;
    }
    this->width = mCurrentAnimation->GetWidth();
    this->height = mCurrentAnimation->GetHeight();
}


void Player::SetState(PlayerState *newState)
{
    delete this->mPlayerData->state;
    this->mPlayerData->state = newState;
    this->changeAnimation(newState->GetState());
    mCurrentState = newState->GetState();
}


5. Chỉnh sửa DemoScene

  • Để cho Player sử dụng được key board mình sẽ tạo 1 map ở DemoScene, map là kiểu dữ liệu có 2 phần từ là key và data, key là chỉ duy nhất và data là nội dung của key đó.
std::map<intbool> keys
  • Mỗi keycode của bàn phím sẽ là 1 số nguyên và mình sẽ dùng nó là key và data nó là 1 biến bool, nếu true là phím đang được nhấn xuống và false là phím không được nhấn xuống.
void DemoScene::OnKeyDown(int keyCode)
{
    keys[keyCode] = true;
    mPlayer->OnKeyPressed(keyCode);
}
void DemoScene::OnKeyUp(int keyCode)
{
    keys[keyCode] = false;
}


  • Trong mỗi lần update của Scene mình sẽ truyền thông tin này cho Player để Player truyền qua State của nó và xử lý những bàn phím cần thiết trong State.

  • Một số trường hợp như Jump thì chỉ nhảy khi nhấn phím space lần đầu thì mình sẽ gọi hàm nó trong hàm OnKeyDown.
  • Các bạn sử dụng phím mũi tên trái, phải để di chuyển mario và space để nhảy.

Tổng kết

  • State machine là 1 kiểu Design Pattern nó giúp chúng ta chia nhỏ các state ra và quản lý chúng dễ dàng hơn. Tùy theo từng nhân vật, đối tượng mà mỗi State có hàm hay thuộc tính chức năng riêng.

  • Các bạn có thể dùng Pattern này áp dụng tương tự cho các con bot, game object khác.

  • Đến phần va chạm mình sẽ chỉnh sửa tiếp những state này để các bạn có thể xử lý va chạm bên trong state, vì mỗi state sẽ có những kiểu va chạm khác nhau. VD: như khi đang ở State Jumpping mà bạn chạm vật ở trên đầu Player thì nó lập tức chuyển sang State Falling thì việc chạm vật ở trên đầu là 1 trigger để chuyển đổi state.

  • Các bạn tham khảo thêm trong source code để tham khảo thêm về cách hoạt động của Player.

  • Tùy thuộc vào số lượng và độ phức tạp của state mà ta tạo thêm class State hơạc chỉ dùng switch case hay if để chuyển state. Việc tạo thêm class cũng làm cho game nó thêm phức tạp và nhiều cái để quản lý cho nên phải lựa chọn hợp lý.
Share on Google Plus

About Lộc Thọ

This is a short description in the author block about the author. You edit it by entering text in the "Biographical Info" field in the user admin panel.
    Blogger Comment
    Facebook Comment

0 Comment:

Post a Comment