Post date: Jul 21, 2011 4:00:38 PM
Trong phần này, ta sẽ xây dựng một cơ chế quản lý tương tác người dùng cơ bản. Hai cơ chế tương tác chính:
Để có thể linh hoạt trong cả xử lý mouse và touch, ta gọi chung là xử lý dạng pointer. Ngày nay, khá nhiều thiết bị hổ trợ touch đa điểm. Do đó, ta chọn xử lý touch đa điểm là nền tảng chung cho touch và mouse
Mô hình quản lý được đề xuất như sau:
Trong đó:
Nhìn chung, thông tin tương tác thường được xử lý theo 2 dạng:
Ở đây, tất yếu ta sẽ phải cung cấp cả 2 cơ chế cho người dùng. Tuy nhiên, về phía người dùng thì phải chọn cách thức xử lý nào cho phù hợp. Trở lại với 4 ví dụ trên.
Như vậy, ta có một kết luận về đáp ứng các tương tác:
Từ cách sử dụng trên, ta thiết kế lại mô hình xử lý:
Khi tiếp nhận thông tin, CGame một mặt chuyển tiếp thông tin đến Controller-Manager để sử dụng sử dụng cho xử lý gián tiếp; thì một mặt khác sẽ xử lý trực tiếp nếu cần thiết. Trong trường hợp thông tin cần chuyển về từng state xử lý, CGame sẽ chuyển thông tin đến state hiện tại.
enum EControllerEvent
{
ECE_POINTER,
ECE_KEY,
};
enum EPointerEvent{ EPE_NONE, EPE_PRESSED, EPE_RELEASED, EPE_DRAGGED,};enum EKeyEvent
{
EKE_NONE,
EKE_PRESSED,
EKE_HOLD,
EKE_RELEASED,
};
Ứng với tương tác pointer bao gồm các thông tin:
struct SControllerEventPointerData
{
__INT32 ID;
EPointerEvent Event;
__INT32 X;
__INT32 Y;
};
Với tương tác phím, các thông tin bao gồm:
struct SControllerEventKeyData
{
__INT32 KeyCode;
EKeyEvent Event;
};
Một event tổng quát có dạng:
struct SControllerEvent
{
EControllerEvent Type;
union
{
SControllerEventPointerData PointerData;
SControllerEventKeyData KeyData;
};
};
Như đã đề cập ở trên, luồng thông tin bắt đầu từ View, do đó, trước tiên ta thiết kế lại CVSView cho việc nhận các sự kiện từ người dùng:
Tiến hành sửa đổi hàm LRESULT CALLBACK CVSView::WndProc
LRESULT CALLBACK CVSView::WndProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam )
{
SControllerEvent Event;
switch( message )
{
....
case WM_LBUTTONDOWN:
if (!CVSView::GetInstance()) return 0;
CVSView::GetInstance()->m_isLeftMouseDown = true;
Event.Type = ECE_POINTER;
Event.PointerData.Event = EPE_PRESSED;
Event.PointerData.ID = 0;
Event.PointerData.X = LOWORD(lparam);
Event.PointerData.Y = HIWORD(lparam);
CGame::GetInstance()->SetEvent(Event);
return 0;
....
}
return DefWindowProc(hwnd, message, wparam, lparam);
}
Trong bước trên, ta thấy xuất hiện hàm SetEvent của CGame. Implement hàm này chính là công việc tiếp theo cần thực hiện.
Từ mô hình xử lý, ta thấy việc xử lý trực tiếp thông qua 2 công đoạn:
Ở CGame, ta thiết kế hàm virtual bool OnControllerEvent(SControllerEvent Event) {return false;} hàm này trả về true nếu việc xử lý được thực hiện bởi CGame, hoặc false nếu cần chuyển cho State. Mặc định hàm này trả về false, và sẽ được over-write ở từng game cụ thể.
Ví dụ:
virtual bool OnControllerEvent(SControllerEvent Event)
{
if (Event.Type == ECE_KEY && Event.KeyData.Event == EKE_PRESSED && Event.KeyData.KeyCode == EKEY_WIN_Q)
{
this->Exit();
return true;
}
return false;
}
Ở CState, ta thiết kế hàm virtual void OnControllerEvent(SControllerEvent Event) {}. Hàm này là hàm rỗng, và sẽ được overwrite ở từng state cụ thể.
Khi này, ta implement hàm SetEvent:
void CGame::SetEvent(SControllerEvent Event)
{
if (!this->OnControllerEvent(Event))
{
CState* state = CStateManagement::GetInstance()->GetCurrentState();
if (state)
{
state->OnControllerEvent(Event);
}
}
}
Ngoài việc xử lý trực tiếp, SetEvent cung cấp cơ chế xử lý gián tiếp bằng cách chuyển dữ liệu đến CControllerEventManager
void CGame::SetEvent(SControllerEvent Event)
{
CControllerEventManager::GetInstance()->OnEvent(Event);
if (!this->OnControllerEvent(Event))
{
CState* state = CStateManagement::GetInstance()->GetCurrentState();
if (state)
{
state->OnControllerEvent(Event);
}
}
}
Nói về CControllerEventManager, đây là lớp đóng vai trò điều phối việc quản lý các thông tin tương tác. Lớp này vận hành hai lớp chuyên biệt hơn là:
Để thiết kế CControllerPointerManager, CControllerKeyManager, source có bổ sung thêm lớp CLutI. Về cơ bản CLutI giống CLut (xem), tuy nhiên có điểm khác là key là một số nguyên. Các thông tin event được lưu trữ trong một bảng tra dùng cho mục đích kiểm tra các sử kiện đã xãy ra trong frame
Ví dụ sử dụng:
void CStatePoster::Update()
{
spr->UpdateAnimation(CFpsController::GetInstance()->GetFrameDt());
int w = VIEWCLASS::GetInstance()->GetWidth();
int h = VIEWCLASS::GetInstance()->GetHeight();
if (CControllerPointerManager::GetInstance()->WasReleaseInside(0, 0, w, h))
{
spr->ResetAnimation();
}
if (CControllerKeyManager::GetInstance()->GetKeyHoldDuration(EKEY_SPACE) > 1000)
{
CGame::GetInstance()->Exit();
}
}
Xem source để có thêm chi tiết.
Ở version này, Utils được tách ra thành các lớp chức năng nhỏ để tiện sử dụng
Download
FIX BUG:
Trong phần source code download, file CVSView.cpp, thay hàm update với nội dung sau:
void CVSView::Update()
{
while (true)
{
// handle win32 message
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
if (msg.hwnd == m_hwndWindow)
{
//WndProc(m_hwndWindow, msg.message, msg.wParam, msg.lParam);
DispatchMessage(&msg);
}
}
CGame::GetInstance()->Update();
if (!CGame::GetInstance()->IsAlive())
{
DestroyWindow(m_hwndWindow);
return;
}
RefreshGL();
}
}
Lưu ý: nếu run thiếu dll, chép tất cả các dll trong libs ra cùng chỗ với .exe