CHƯƠNG 3 PHÂN TÍCH TRÒ CHƠI
3.2. Xây dựng hệ thống khả chuyển
Để dễ dàng hơn trong việc lập trình cũng như việc chuyển trò chơi từ hệ thống này sang các nền tảng khác, ta cần phải xây dựng hệ thống theo các phương pháp sau.
Windows 32
Cài đặt phần chung. Cài đặt cho windows,
Cài đặt trò chơi
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 47
3.2.1. Sử dụng các tiền xử lý
Các thông số kỹ thuật của các thiết bị tương đối khác nhau, đôi khi cũng có sự khác nhau giữa các thư viện trên các thiết bị. Do đó ta cần phải sử dụng các định nghĩa tiền xử lí để có thể viết mã nguồn chạy tốt trên mọi hệ thống.
Các định nghĩa tiền xử lí có sẵn trên hệ thống:
WIN32: có giá trị đúng khi nền tảng biên dịch hiện tại là windows. ANDROID: có giá trị đúng khi nền tảng biên dịch hiện tại là Android.
Các cách sử dụng tiền xử lí để tăng tính khả chuyển trong chương trình:
Định nghĩa các thông số: các thiết bị có thể có màn hình khác nhau, do đó ta đưa các thông số đó vào các định nghĩa lúc biên dịch.
#define SCREEN_W 960 #define SCREEN_H 640
Viết riêng cho từng cấu hình: sử dụng tiền xử lí #if, #else, #endif để viết một đoạn mã mà chỉ chạy trên một nền tảng mong muốn.
3.2.2. Sử dụng nguyên mẫu hàm, lớp trừu tượng
Khai báo một nguyên mẫu hàm, lớp trừu tượng chung cho tất cả các hệ thống sau đó ta cài đặt khác nhau trên mỗi nền tảng khác nhau.
Ví dụ, do xử lí âm thanh trong trò chơi này tương đối đơn giản, do đó, ta cần hai hàm để thực hiện chơi một âm thanh bất kỳ và dừng tất cả các âm thanh đang chơi. Ta khai báo nguyên mẫu hàm như sau:
// Play Sound
void PlaySound(int id, int loop = 1); // Stop all sound
void StopSound();
Và sau đó ta sẽ viết hàm này trên các hệ thống khác nhau sử dụng tiền xử lý như phương pháp trên.
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 48 3.3. Cấu trúc của một Trò chơi
3.3.1. Vòng lặp trò chơi
Vòng lặp trò chơi chính là trung tâm của trò chơi. Trò chơi khác hẳn với các ứng dụng hướng sự kiện khác, ngay cả khi không có sự kiện xuất nhập, trò chơi vẫn phải hoạt động và đáp ứng liên tục để tạo ra hiệu ứng hoạt động của các nhân vật, đối tượng trong trò chơi.
Có thể ví dụ trò chơi như một cuốn phim hoạt hình mà người chơi có thể tương tác với các nhân vật trong trò chơi. Để tạo được hiệu ứng hoạt hình, trò chơi phải vẽ hơn 12 hình trên một giây để đảm bảo hiệu ứng hoạt hình. Ngoài ra trò chơi cần phải tính toán, xử lí các sự kiện phát sinh, tính toán các vật thể, các nhân vật và hoạt động trong trò chơi.
Đó là hai công việc chính của vòng lặp trò chơi
- Cập nhật, tính toán trạng thái hiện tại.
- Vẽ các nhân vật, đối tượng, giao diện màn hình.
Hình 20. Biễu diễn vòng lặp của trò chơi
Kết thúc
Xử lí sự kiện
Vẽ Sai
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 49
Hai hành động này được lặp đi lặp lại liên tiếp nhau trong suốt trò chơi hình thành nên vòng lặp chính của trò chơi. Để mọi việc rõ ràng và dễ dàng hơn, chúng ta nên tách riêng biệt hai hành động này và sử dụng các biến trạng thái để lập trình.
Ví dụ:
Trong game cần vẽ một quả bóng di động. Ta nhận thấy:
- Để xử lí cập nhật, tính toán trạng thái của quả bóng ta cần các thông tin bao gồm: bán kính bóng, vị trí trái bóng, và vận tốc của trái bóng.
- Để vẽ trái bóng lên màn hình: ta cần biết các thông tin: bán kính, vị trí.
Bán kính, vị trí, vận tốc:chính là các biến trạng thái chung cho chương trình. Sử dụng các biến trạng thái này, ở hành động cập nhật, ta không cần quan tâm trái bóng được vẽ như thế nào vẫn có thể thực hiện được và ngược lại. Việc nhìn một góc nhìn riêng biệt sẽ khiến chúng ta làm mọi việc dễ dàng và đơn giản hơn nhiều.
Số lần vẽ/cập nhật trên một giây gọi là FPS (Frame per second). Thông thường, một trò chơi 3D có số FPS chuẩn là 60 FPS. Số FPS càng lớn thì trò chơi chạy càng mượt mà, tuy nhiên nếu số FPS quá lớn thì sẽ gây hiện tượng trò chơi quá nhanh, người dùng không thể chơi tốt được, khi đó, chúng ta cần phải giới hạn số FPS lại một giá trị mong muốn.
FPS là giá trị phụ thuộc nhiều vào cấu hình của thiết bị, do đó: việc chuyển đổi trò chơi trên nhiều hệ thống khác nhau sẽ dẫn đến số FPS khác nhau. Trên một thiết bị mạnh hơn, số FPS sẽ tăng lên, do đó chúng ta sẽ phải giới hạn lại. Ngược lại, chúng ta cần tối ưu hóa thuật toán, cách vẽ hoặc chỉnh sửa nội dung của trò chơi đó để đạt được số FPS mong muốn tạo ra kết quả tối ưu cho người chơi.
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 50
3.3.2. Cấu trúc trò chơi
Hình 21. Cấu trúc của một trò chơi
Một trò chơi bao gồm nhiều giao diện khác nhau. Ví dụ: bảng chọn chính, mục giới thiệu trò chơi, mục điểm cao nhất và trò chơi. Để tích hợp điều này vào vòng lặp chính, chúng ta sẽ có một biến trạng thái lưu lại giao diện hiện tại của trò chơi, rồi theo đó xử lí tương ứng.
Dựa vào biến trạng thái chúng ta sẽ lưu lại giao diện hiện tại và xử lí trạng thái tương ứng. Tương tự như trong hàm cập nhật hoặc hàm vẽ.
Trong thiết kế trò chơi của mình, tôi đưa ra các giao diện sau
- Bảng chọn chính: Hiển thị các mục để người chơi có thể truy xuất được các mục khác trong trò chơi.
- Điểm cao: Ghi lại các kỷ lục được lập trong trò chơi. - Giới thiệu: Giới thiệu sơ lược về trò chơi.
- Trò chơi: nơi người chơi chơi và ghi điểm.
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 51
Hình 22. Giao diện của trò chơi
Các trạng thái của trò chơi
-
Hình 23. Trạng thái của trò chơi
Bảng chọn chính
Vào chơi
Xem Điểm Giới Thiệu
Hết
Chọn Xem Điểm Chọn Giới thiệu
Chọn Vào chơi
Mất hết mạng
Chọn nút quay lại Vẽ
Xử lí sự kiện Trạng thái hiện tại
Xử lí trò chơi
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 52
3.3.3. Nội dung trò chơi
Trò chơi mô phỏng hành động cắt trái cây của một kiếm sĩ. Trò chơi chỉ có một chế độ chơi duy nhất tính điểm. Người chơi được bắt đầu với năm mạng và một thanh kiếm. Trái cây lần lượt được tung lên từ bên dưới, người chơi có nhiệm vụ sử dụng kiếm để cắt trái cây ra làm đôi trước khi trái cây đó rơi ra khỏi tầm nhìn. Cứ mỗi trái cây cắt thành công, người chơi sẽ dành được một điểm.
Độ khó của trò chơi: tăng dần, cứ 50 điểm đạt được, trò chơi sẽ xuất hiện thêm một trái cây khiến mức độ khó của trò chơi tăng lên. Ngược lại người chơi sẽ được tặng một mạng.
Trò chơi kết thúc: khi người chơi mất tất cả các số mạng của mình. Cứ mỗi khi người chơi làm rơi một trái cây, người chơi sẽ bị trừ một mạng.
Các thiết kế thuật toán: Chuyển động của trái cây:
Ban đầu vị trí P trái cây được đặt bên dưới màn hình. Trái cây được cung cấp một vận tốc ban đầu và . Trong môi trường tồn tại gia tốc trọng trường . Theo đó, sau mỗi khung hình, ta có cập nhật trạng thái của trái cây đã cho như sau:
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 53
Hình 24. Biễu diễn chuyển động của trái cây
Thanh kiếm của người chơi:
Thanh kiếm, vết chém của người chơi được nhập từ màn hình thông qua cảm ứng chạm. Dữ liệu vào tương đối rời rạc do hạn chế của hệ điều hành. Ví dụ khi người dùng lượt tay trên màn hình thì sẽ có khoảng n điểm được ghi lại (không phải tất cả các điểm). Từ những dữ liệu này, chúng ta xây dựng thành thanh kiếm có hình dạng tương đối như hình.
Hình 25. Biễu diễn thanh kiếm của người chơi
Kiểm tra va chạm:
Ta kiểm tra va chạm giữa thanh kiếm và trái cây bằng cách kiểm tra va chạm giữa thanh kiếm và hình chữ nhật viền xung quanh trái cây.
Hình 26. Biễu diễn va chạm giữa thanh kiếm và trái cây Thanh kiếm sau khi được render Thanh kiếm sau khi được render
Quy tắc:
DB = BC = w/2.Trong đó, w là độ rộng của thanh kiếm tại B.
Chạm và các điểm được ghi nhận
A
B D
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 54
Tính toán tọa độ sau va chạm:
Trước khi va chạm: Gia tốc của vật là . Sau khi va chạm: Trái cây được cắt làm 2, và được truyền thêm một gia tốc tương ứng do lực chém truyền vào là . Gia tốc sau:
.
Hình 27. Biễu diễn tính toán tọa độ sau va chạm
3.3.4. Đồ họa
Chúng ta sử dụng bộ thư viện OPENGL ES 2.0. Ta cài đặt một số phương thức để vẽ một đối tượng 3D và vẽ giao diện 2D.
Vẽ đối tượng 3D:
- Một đối tượng 3D bao gồm định nghĩa về tọa độ và một đối tượng texture được lưu riêng rẻ.
- Dữ liệu về tọa độ trong trò chơi được tạo ra bằng phần mềm 3DS Max Studio, và sau đó được xuất ra tập tin văn bản kiểu obj (wave front object), đây là dữ liệu bao gồm các thông tin để vẽ một đối tượng:
Tọa độ. (vertex position). UV. (textcoord).
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 55
- Đối tượng 3D trong trò chơi chủ yếu là trái cây và đã được cắt sẵn ra làm 2 phần bằng nhau đễ dễ dàng xử lí trong trò chơi.
Hình 28. Biễu diễn đối tượng 3D trong trò chơi
Các đối tượng này được tự vẽ để đảm bảo tính đơn giản và tăng tốc độ thực thi trong chương trình.
Vẽ giao diện 2D:
Giao diện 2D là một phần không thể thiếu trong trò chơi, vì nó là phần quan trọng để thể hiện thông tin tới người dùng.
Để vẽ 2D trong OpenGL ES 2.0 chúng ta tạo một texture rồi tiến hành vẽ từng bộ đôi tam giác 1 ra màn hình. Ví dụ, để vẽ hình bên dưới thì chúng ta vẽ hai tam giác như sau:
Hình 29. Biễu diễn giao diện 2D
Để tạo ra toàn bộ giao diện 2D trong chương trình, ta nén tất cả vào một texture có kích thước lớn, sau đó tải vào bộ nhớ và vẽ từng bộ đôi tam giác như mô hình trên.
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 56
Hình 30. Biễu diễn cách bố trí để vẽ giao diện 2D trong trò chơi
3.3.5. Âm Thanh
Trò chơi tương đối đơn giản, do đó trong chương trình ta có hai loại âm thanh sau: Âm thanh nền.
Âm thanh hiệu ứng.
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 57
Tên âm thanh Mô tả Kích hoạt
MUSIC_TITLE Nhạc nền. Kích hoạt tại bảng chọn chính.
CUT Âm hiệu ứng Người dùng chọn một menu.
Người dùng cắt thành công một trái cây.
GAME_OVER Nhạc nền. Khi trò chơi kết thúc.
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 58
CHƯƠNG 4 QUÁ TRÌNH LẬP TRÌNH
4.1. Thiết kế chương trình
Dựa vào các phân tích, ta có thiết kế khung của một chương trình.
Hình 31. Biễu diễn khung thiết kế của chương trình
- Lớp Application là bộ giao tiếp giữa ứng dụng và các nền tảng.
- Lớp FileSystem là lớp trừu tượng được dùng để thực hiện các hành động đọc/ghi tập tin.
-Lớp VideoDriver: là lớp cài đặt các phương thức vẽ hình liên quan đến OpenGLES 2.0.
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 59
- Lớp Sprite2DManager, Sprite3DManager là lớp chịu trách nhiệm tải các đối tượng 3D cũng như giao diện 2D và vẽ lên trong chương trình.
Ở mỗi nền tảng các đối tượng này sẽ được kế thừa và cài đặt khác nhau nếu cần thiết.
Hình 32. Cách kế thừa các lớp trừ tượng trên các nền tảng khác nhau
Đây là bộ khung cần thiết để chúng ta tiến hành xây dựng các thành phần trong chương trình.
4.2. Vòng lặp trò chơi
Chúng ta thực hiện vòng lặp trò chơi qua hai phương thức của lớp Application, đó là phương thức Update() và Render().
- Phương thức Update: cập nhật trạng thái của trò chơi.
- Phương thức Render: Render nội dung của trò chơi ra màn hình thiết bị.
Do trò chơi bao gồm nhiều giao diện (bảng chọn chính, xem kỷ lục, xem giới thiệu, chơi game…). Chúng ta sẽ khai báo một biến trạng thái để lưu trữ trạng thái hiện tại. Sau đó ở hàm Update và Render, chúng ta sẽ dựa vào biến trạng thái này để cập nhật trò chơi.
void UpdateGame(int type) {
switch (s_iCurrentState) {
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 60 case GAME_START: UpdateGameStart(type); break; case MAIN_MENU: UpdateMainMenu(type); …
Ứng với mỗi trạng thái ta sử dụng một chỉ thị để xác định hành động hiện tại là Paint hay Render. Ngoài ra mỗi trạng thái cần có các hành động để tải và hủy bỏ các đối tượng trên.
- UPDATE: Cập nhật trạng thái hiện tại. - PAINT: Render trạng thái hiện tại.
- CTOR: Tải các đối tượng cần thiết để vẽ và tính toán trạng thái hiện tại. - DTOR: Hủy bỏ các biến đã sử dụng trong trạng thái hiện tại.
Hình 33. Biễu diễn vòng lặp của trò chơi
4.3. Nội dung trò chơi
Chúng ta định nghĩa các biến trạng thái rồi lần lượt cài đặt các nội dung như sau: Các biến dùng để vẽ và cập nhật trái cây:
enum {ST_NORMAL, ST_CUT, ST_HIDDEN};
struct Fruit { int status; float x; float y; float ax; float ay; int model; };
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 61
Fruit fruits[MAX];
Các biến dùng để lưu trữ và xử lí nhập từ người chơi: int blades[MAX_BLADE][2];
int bladesLength = 0;
Ta có vòng đời của một trái cây như sau:
Hình 34. Vòng đời của một trái cây
Các chức năng cài đặt:
4.3.1. Khởi tạo vị trí hiện tại của trái cây
// gán gia tốc fruits[i].ay = 0.28f; fruits[i].ax = (rand() % 10 - 5) * 0.01f; // gán vịtrí fruits[i].y = -2.8f; fruits[i].x = (rand() % 90) / 10.0f - 4.0f; // gán trạng thái fruits[i].status = ST_NORMAL;
// gán loại trái cây
fruits[i].model = (rand() % 3) * 3;
4.3.2. Cập nhật trạng thái của trái cây
// Tính toán lại gia tốc fruits[i].ay -= 0.007f; fruits[i].y += fruits[i].ay; fruits[i].x += fruits[i].ax; // Xác định lại trạng thái if (fruits[i].y < -3.0f) { fruits[i].status = ST_HIDDEN; }
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 62
4.3.3. Kiểm tra va chạm
Chúng ta kiểm tra cắt nhau giữa các điểm thuộc thanh kiếm và đường biên va chạm của trái cây như sau:
// ax, ay là tọa độ của trái cây.
float cx = blades[bladesLength - 2][0] + vx * j * stepLength;
float cy = blades[bladesLength - 2][1] + vy * j * stepLength;
if(fruits[i].status == ST_NORMAL && PointerInRect(cx, cy, ax - 56, ay - 58, 112, 116)) { // Đã cắt nhau…
4.3.4. Tính toán lại các trạng thái sau khi va chạm
fruits[i].ax += 0.02f * vx; fruits[i].ay += 0.02f * vy;
fruits[i].model = fruits[i].model + 1; fruits[i].status = ST_CUT;
Tuy nhiên bây giờ, trái cây của chúng ta đã được cắt làm 2: do đó, chúng ta sẽ có: fruits[i + 1].ax = -fruits[i].ax;
fruits[i + 1].x = fruits[i].x; fruits[i + 1].y = fruits[i].y; fruits[i + 1].ay = -fruits[i].ay;
fruits[i + 1].model = fruits[i].model + 1; fruits[i + 1].status = ST_CUT;
4.4. Đồ Họa
Sơ đồ hoạt động của OpenGLES 2.0 trong chương trình như sau:
Hình 35. Sơ đồ hoạt động của OpenGl ES 2.0 trong chương trình
SVTH: Nhữ Thị Trà My- 08CNTT02 Trang 63
4.4.1.Tải shader và biên dịch
OpenGLES 2.0 cung cấp một đường ống để chúng ta có thể lập trình cách thức hoạt động của bộ xử lí đồ họa.
Shader là bộ điều khiển hoạt động của OpenGLES 2.0 và được chạy ngay trên bộ xử lí