Post date: Jul 4, 2011 4:44:30 PM
Ở phần 3, ta đã thiết kế một lớp để quản lý vòng đời của game. Tuy nhiên, khó khăn dễ thấy là game thường rất lớn. Nếu tất cả mọi cập nhật game đều để trong hàm Run(), thì việc quản lý trở nên rất khó khăn. Để giải quyết vấn đề này, ta có thể áp dụng một phương pháp cũ nhưng hiệu quả: chia để trị.
Phương pháp chia để trị này được áp dụng trong tất cả các ứng dụng, thông qua việc áp dụng lượt đồ State diagram:
Source: http://upload.wikimedia.org/wikipedia/commons/c/cf/Finite_state_machine_example_with_comments.svg
Áp dụng mô hình trên, game sẽ được chia thành nhiều state. Vấn đề nãy sinh là state trong game là gì, và chia như thế nào?
State trong game, có thể hiểu là một giai đoạn của game, hay một màn hình/ 1 cảnh / 1 scene (tùy theo cách gọi, cách hiểu).
Khi chơi một game, lấy vị dụ như game Angry Birds trên trình duyệt Chrome:
Mỗi giai đoạn như vậy, ta có thể gọi là một state, hay một scene. ta chọn khái niệm state để gần gũi với mô hình UML.
Với một state quá lớn, ta lại nghĩ đến trường hợp chia nhỏ state thành các sub-state. Tuy nhiên, nên thận trọng trong việc chia sub-state. Việc chia thành các sub-state bên trong state đòi hỏi chi phí quản lý cao hơn. Do đó, không nên nếu không thật sự cần thiết.
Nhìn chung, State cũng giống như một ứng dụng thu nhỏ, do đó cũng có các bước cơ bản sau:
Tuy nhiên, để tách biệt giữ xử lý logic và việc vẽ lên màn hình, Run nên được chia thành 2 bước nhỏ: Update (dành cho xử lý logic) và Render (dành cho việc vẽ). Việc chia tách này mang lại cho ta nhiều lợi ích. Bạn nào đã từng làm qua MVC, chắc hẳn biết được lợi ích của nó. Lúc này, các thao tác cần có của một State bao gồm:
CState.h
#ifndef __CSTATE_H__ #define __CSTATE_H__ namespace GameTutor { class CState { public: CState(){} virtual ~CState(){}
virtual void Init() = 0; virtual void Update() = 0; virtual void Render() = 0; virtual void Exit() = 0; }; } #endif
Sau khi đã chia nhỏ các state, nhiệm vụ tiếp theo là làm sao đảm bảo việc chuyển đổi giữa các state được trơn tru.
Việc quản lý các state nhìn chung là do vòng lặp chính điều khiển. Tuy nhiên để thuận tiên, ta có thể định nghĩa 1 lớp chuyển quản lý các state, tạm gọi là lớp CStateManagement
Để đơn giản, việc quản lý state tuân theo nguyên tắc sau:
CStateManagement.h
#ifndef __CSTATEMANAGEMENT_H__ #define __CSTATEMANAGEMENT_H__ #include "CState.h" namespace GameTutor { class CStateManagement { public: static CStateManagement* GetInstance() { if (!s_pIntance) { s_pIntance = new CStateManagement(); } return s_pIntance; } protected: static CStateManagement* s_pIntance; protected: CStateManagement():m_pCurrentState(0), m_pNextState(0) {} protected: CState* m_pCurrentState; CState* m_pNextState; public: void Update(bool isPause); void SwitchState(CState* nextState); }; } #endif
Trong file trên, CStateManagement được thiết kế theo kiểu Singleton pattern. Điều này đảm bảo lớp CStateManagement tồn tại duy nhất 1 instance trong suốt game.
Tiếp theo, ta xem ví dụ mẫu về cách quản lý state thông qua hàm update và switch state:
CStateManagement.cpp
#include "CStateManagement.h" namespace GameTutor { CStateManagement* CStateManagement::s_pIntance = 0; void CStateManagement::Update(bool isPause) { // check if need switch state if (m_pCurrentState != m_pNextState) { if (m_pCurrentState) { m_pCurrentState->Exit(); delete m_pCurrentState; } if (m_pNextState) { m_pNextState->Init(); } m_pCurrentState = m_pNextState; } //update state if (m_pCurrentState) { if (!isPause) { m_pCurrentState->Update(); } m_pCurrentState->Render(); } } void CStateManagement::SwitchState(CState* nextState) { m_pNextState = nextState; } }
Trong ví dụ trên, hàm Update mới là hàm quản lý chính việc chuyển state. SwitchState chỉ đóng vai trò "đánh dấu". Điều này đảm bảo CStateManagement hoạt động đúng theo 3 tiêu chí đã nêu ở trên.
Do việc quản lý State lúc này được trao cho CStateManagement. Ta chỉ việc kết nối CStateManagement và CGame.
CGame.h
#ifndef __CGAME_H__ #define __CGAME_H__ namespace GameTutor { class CGame { public: static CGame* GetInstance() {return s_pInstance;} virtual ~CGame() {} virtual void Run(); virtual void Exit(); virtual void Pause(); virtual void Resume(); bool IsAlive() {return m_isAlived;} bool IsPause() {return m_isPaused;} protected: CGame(); static CGame* s_pInstance; virtual void Init() = 0; virtual void Destroy() = 0; protected: bool m_isAlived; bool m_isPaused; }; } #endif
CGame.cpp
#include "CGame.h" #include "stdio.h" #include "windows.h" #include "CStateManagement.h" namespace GameTutor { CGame* CGame::s_pInstance = 0; CGame::CGame(): m_isPaused(false), m_isAlived(true) { s_pInstance = this; } void CGame::Pause() { m_isPaused = true; } void CGame::Resume() { m_isPaused = false; } void CGame::Exit() { m_isAlived = false; } void CGame::Run() { this->Init(); while (m_isAlived) { if (m_isPaused) { CStateManagement::GetInstance()->Update(true); } else { CStateManagement::GetInstance()->Update(false); } Sleep(80); } Destroy(); } }
Giả sử ta có 2 State:
CStateLogo.h
#ifndef __CSTATELOGO_H__ #define __CSTATELOGO_H__ #include "CState.h" using namespace GameTutor; class CStateLogo: public CState { public: CStateLogo(); ~CStateLogo() {} void Init(); void Update(); void Render(); void Exit(); private: int m_iCount; }; #endif
CStateLogo.cpp
#include "CStateLogo.h" #include "CStateManagement.h" #include "CStatePoster.h" #include CStateLogo::CStateLogo():m_iCount(0), CState() {} void CStateLogo::Init() { printf("State Logo: Init\n"); m_iCount = 0; } void CStateLogo::Update() { m_iCount++; if (m_iCount >= 10) { CStateManagement::GetInstance()->SwitchState(new CStatePoster()); } } void CStateLogo::Render() { printf("State Logo: %d\n", m_iCount); } void CStateLogo::Exit() { printf("State Logo: Exit\n"); }
Trong đoạn code trên, hàm Update và Render minh họa việc tách biệt giữ Render và Update.
CGameExample được hiệu chỉnh để khởi tạo state CStateLogo
CExample.cpp
#include "CExample.h" #include "CStateLogo.h" #include "CStateManagement.h" #include "stdio.h" void CExample::Init() { CStateManagement::GetInstance()->SwitchState(new CStateLogo()); printf("Init\n"); } void CExample::Destroy() { printf("Destroy\n"); }
Đến thời điểm này, thư viện ta đã xây dựng được 3 lớp cơ bản:
Download