Vấn đề đặt ra
Vấn đề trong việc xử lý các trạng thái của đối tượng khi lập trình các hoạt động. Khi cần xử lý đối tượng Object.
void Object::update(float delta) { if (KeyPress(BUTTON_LEFT_ARROW)) { this->velocity = MOVE_LEFT_VELOCITY; this->image = MOVE_LEFT_IMAGE; } }
Vấn đề nảy sinh khi thêm trạng thái "Jump".
void Object::update(float delta) { if (KeyPress(BUTTON_LEFT_ARROW)) { this->velocity = MOVE_LEFT_VELOCITY; this->image = MOVE_LEFT_IMAGE; } if (KeyPress(BUTTON_SPACE)) { this->velocity = JUMP_VELOCITY; this->image = JUMP_IMAGE; } }
Khi nhấn Space
thì Object nhảy liên tục và nhấn Space + Left Arrow
để nhảy qua bên trái thì hình ảnh không chính xác.
Thêm thuộc tính m_Jumping
kiểu bool
để kiểm tra Object khi Jump và cập nhật lại như sau:
if (KeyPress(BUTTON_LEFT_ARROW)) { this->velocity = MOVE_LEFT_VELOCITY; if (!this->m_Jumping) this->image = MOVE_LEFT_IMAGE; } if (KeyPress(BUTTON_SPACE)) { if (!m_Jumping) { this->m_Jumping = true; this->velocity = JUMP_VELOCITY; this->image = JUMP_IMAGE; } }
Vấn đề được giải quyết nhưng khi Object có thêm nhiều trạng thái khác thì phải thêm rất nhiều biến bool
và điều kiện if … else …
.
Giải pháp
Mô hình State Pattern.

Khái niệm cơ bản
State là “trạng thái” của đối tượng.
Ví dụ:
- Object có các state là Stand, Running, Jumping, ...
- Scene có các state là Intro, Play, End, ...
State Pattern là cách tổ chức quản lý các State một cách hợp lý.
Đặc điểm
- Giảm rắc rối trong quá trình xử lý các sự kiện.
- Dễ dàng chỉnh sửa trong quá trình lập trình.
Hiện thực
Trong bài viết này, State Pattern được trình bày dưới 2 dạng:
- Enum kết hợp với Switch-Cases.
- OOP.
Enum kết hợp với Switch – Cases
Xác định được các State của đối tượng và tạo Enum
cho các State đó.
Ví dụ, Object có 3 loại State: Stand
, Move
và Jump
nên sẽ có Enum
cho các State:
enum State { STAND_STATE = 0, MOVE_STATE, JUMP_STATE };
Áp dụng Enum State
của đối tượng vào trong class đối tượng:
Khai báo lớp Object
#ifndef __OBJECT_H__ #define __OBJECT_H__ #include <stdio.h> class Object { public: void update(float delta); private: State m_currentState; }; #endif // !__OBJECT_H__
Định nghĩa lớp Object
#include "Object.h" void Object::update(float delta) { switch (this->m_currentState) { case State::STAND_STATE: // Làm bất kỳ các hoạt động nào như mong muốn // khi đối tượng Object đang ở trong trạng thái Stand. break; case State::MOVE_STATE: // Tương tự Stand State. break; case State::JUMP_STATE: // Tương tự Stand State. break; default: break; } }
OOP
Xác định các State của đối tượng, hiện thực State Pattern theo hướng OOP.
Tạo lớp State thuần ảo.
#ifndef __STATE_H__ #define __STATE_H__ #include <stdio.h> class State { public: State(); ~State(); virtual void enter() = 0; virtual void update(float delta) = 0; virtual void exit() = 0; }; #endif // !__STATE_H__
Trong ví dụ này, cần override lại 3 phương thức:
Enter
: được gọi khi chuyển từ State cũ sang State mới. Phương thức này khởi tạo các điều kiện cần thiết để phương thứcUpdate
hoạt động một cách hiệu quả.Update
: cập nhật của đối tượng khi đang ở trong một State bất kỳ.Exit
: được gọi khi kết thúc State cũ để chuyển sang State mới. Phương thức này kết thúc các điều kiện cần thiết đã khởi tạo từ phương thứcEnter
trước đó.
Tạo lớp StandState kế thừa từ State, overrride các phương thức cần thiết.
#ifndef __STAND_STATE_H__ #define __STAND_STATE_H__ #include "State.h" class StandState : public State { public: StandState(); ~StandState(); void enter(); void update(float delta); void exit(); }; #endif // !__STAND_STATE_H__
Sau khi có đủ các State của đối tượng, áp dụng vào class của đối tượng:
Trong khai báo lớp Object, khai báo:
- Thuộc tính
State* m_currentState
“chạy” State hiện tại của đối tượng - Phương thức
void changeState(State* state)
để thay đổi các State của đối tượng.
#ifndef __OBJECT_H__ #define __OBJECT_H__ #include <stdio.h> #include "State.h" class Object { public: Object(); ~Object(); void update(float delta); void changeState(State* state); private: State* m_currentState; }; #endif // !__OBJECT_H__
Trong định nghĩa lớp Object, sử dụng thuộc tính và phương thức.
#include "Object.h" Object::Object() { m_currentState = new StandState(); } Object::~Object() { if (m_currentState != nullptr) { delete m_currentState; m_currentState = nullptr; } } void Object::update(float delta) { m_currentState->update(delta); } void Object::changeState(State* state) { if (m_currentState) m_currentState->exit(); m_currentState = state; m_currentState->enter(); }
Tương tự đối với các State còn lại của đối tượng.
Phân biệt Game State và Object State:
- Game State là các trạng thái của Game. Ví dụ các trạng thái của Game: IntroState – là State giới thiệu về game; PlayState – là State “chạy” game, ...
- Object State là các trạng thái của Object. Ví dụ các trạng thái của Object: StandState – là State đứng yên của Object; MoveState – là State di chuyển của Object, ...
Khi áp dụng State Pattern, sử dụng Enum cho Object State và OOP cho GameState vì:
- Object State của mỗi đối tượng là khác nhau, khi làm theo hướng OOP cần khởi tạo rất nhiều class khác nhau nên khó quản lý hết.
- Game State thì các State của nó mang tính duy nhất nên có thể kết hợp OOP của State Pattern với Singleton Pattern để quản lý Game State đơn giản.