Hướng dẫn lập trình Game C/C++ cho người mới bắt đầu
Trang 1Game C/C++ cho newbie - 01 Lập trình game bắt đầu từ đâu?
bởi Nguyễn Khánh Duy
Game là gì?
Nhìn chung, khi chưa từng biết qua lập trình game, chúng ta thường hay đặt những câu hỏi: lập trình game là như thế nào? Có giống lập trình ứng dụng không? Có khó không? Có đòi hỏi những kỹ thuật gì đặt biệt không ?
Câu trả lời của tôi là: game cũng chỉ là một ứng dụng, và nó cũng như bao ứng dụng khác Khi viết một ứng dụng, bạn có thể bắt đầu từ hàm main, hoặc dùng IDE sinh code tự động để hổ trợ Game cũng vậy Bạn cũng có thể bắt đầu viết game từ hàm main, hoặc dùng các engine hổ trợ
Đứng về khía cạnh lập trình, hay từ chủ quan của tôi, game đơn giản là một vòng lặp vô tận Trong vòng lập đó, bạn vẽ, bạn xử lý các diễn biến của game, xử lý sự kiện tương tác từ người chơi Vậy là đủ cho một game Tuy nhiên, để làm game cho mục đích thương mại, đòi hỏi bạn nhiều hơn thế
Một cách nhìn khác, game là một cuốn phim có tương tác Nếu như một bộ phim cần nhiều thứ như kịch bản tốt, dàn dựng hay, hậu kỳ, kiểm duyệt, quãng bá, thì game cũng vậy Game cũng cần một nội dung hay (kịch bản), coding tốt (dàn dựng, hậu kỳ), kiểm soát chất lượng (kiểm duyệt), quãng bá Nếu một phim thành công được đánh giá qua doanh thu, thì game cũng vậy Tuy nhiên, khi mới bắt đầu, thì doanh thu, lợi nhuận, cần được gạt ra khỏi tư tưởng của mình, để có đủ tỉnh táo tập trung vào chuyên môn
Lập trình game bằng ngôn ngữ nào?
Như mọi ứng dụng khác, bạn có thể lập trình game bằng mọi ngôn ngữ Tùy theo nhu cầu, sở trường mà bạn có thể chọn một ngôn ngữ nào đó để làm game Tuy nhiên, hiện nay có một vài xu thế như sau:
Trang 2Java: thường dùng để viết game cho Mobile - các dòng phone hổ trợ J2ME, hoặc viết game cho Android
Ít khi dùng để viết game cho PC
C#: khi nhắc tới C#, ta có thể nghĩ ngay đến XNA, và gắn liền với thương hiệu Microsoft Dùng viết game cho windows mobiles hoặc PC
Javascript: dùng cho môi trường web
Objective C: Dùng cho iOS như máy MAC, iPhone, iPad
C/C++: với sự lâu đời cũng như được sự hưởng ứng rộng rãi từ hầu hết các chương trình đào tạo đại học, C/C++ được xem là ngôn ngữ cơ bản của mọi ngôn ngữ lập trình, và có lẽ ít nhất một lần trong đời thì mỗi programmer đều từng đụng đến nó Do đó, C/C++ cũng là một ngôn ngữ khá được ưa chuộng trong lập trình game ngày nay, với khả năng thực thi trên khác nhiều platform: Windows, Linux, MacOS, Android, iphone/iPad, Symbian, Brew, Meegoo,
Xin lưu ý là ta không nên đánh đồng C/C++ với Visual C, hay Turbo C, hay Visual studio VC, TC, VS là những IDE, còn C/C++ là ngôn ngữ lập trình Ta có thể code C/C++ hoàn toàn bằng Nodepad, và dùng các trình biên dịch khác nhau để build
Trong nội dung bài này, tôi cũng dùng C/C++ trên môi trường Visual studio 2010 để minh họa Các bạn
có thể download bản express tại đây Không cần thiết phải crack
Lập trình game cần những kiến thức gì?
Đã là lập trình hiển nhiên cần phải biết lập trình, tư duy lập trình Không nhất thiết bạn phải xuất sắc; biết ít, làm ít, biết nhiều, làm nhiều
Một chút kiến thức kỹ năng về game Hay nói cách khác, biết chơi game, và từng chơi game
Biết một ngôn ngữ lập trình nào đó
Biết một ít kiến thức về toán, vật lý (google it, nếu cần)
Trang 3Một số kiến thức về đồ họa 2D, 3D
Trong phần này, ta sẽ bắt đầu code game
Chuẩn bị
Bạn cần cài đặt một số tool cần thiết sau:
Visual studio express 2010
Notepad ++
Python 2.7
Java SDK 1.6
Cài đặt "Hello game"
Như đã trình bài trong bài trước, game là một vòng lập vô tận Do đó, với chương trình như nhau, ta cũng có thể tạm gọi là một game (nhưng chưa có tương tác)
Bước một, dùng visual studio, tạo một Empty project, tên gametutor:
Trang 4Bước 2, tạo file main.cpp,
Add một item mới
Trang 5Tạo main.cpp từ template
với nội dung:
Trang 711 }
Game C/C++ cho newbie - 03 Quản lý vòng đời game
bởi Nguyễn Khánh Duy
Giới thiệu
Với tư tưởng hướng đối tượng, trong bước này, ta sẽ thiết kế những lớp cần thiết cho việc quản lý game
Đối tượng đầu tiên ta thấy chính là game: lớp CGame
Class quản lý game (CGame)
Về cơ bản, ta lớp CGame cần có các phương thức:
Init: thiết lập các tham số cho game
Destroy: hủy game Gọi trước khi kết thúc game, dùng để thu hồi vùng nhớ, giải phóng thiết bị chiếm giữ,
Exit: yêu cầu kết thúc game Làm này được người dùng gọi khi cần kết thúc game
Run: Quản lý vòng lặp chính của game
Pause: tạm dừng game
Resume: khôi phục game sau khi tạm dừng
Trang 8virtual void Run();
virtual void Exit();
virtual void Pause();
virtual void Resume();
Trang 9bool IsAlive() {return m_isAlived;}
bool IsPause() {return m_isPaused;}
protected:
virtual void Init() = 0;
virtual void Destroy() = 0;
Trang 10
void CGame::Resume() {
m_isPaused = false; }
void CGame::Exit()
{
m_isAlived = false; }
void CGame::Run() {
this->Init();
while (m_isAlived) {
if (m_isPaused) {
printf("paused\n"); }
else
{
printf("running\n"); }
Sleep(80);
Trang 11Đảm bảo khả năng sử dụng lại của CGame ở nhiều game khác nhau
Tách biệt giữ lớp thư viện và lớp ứng dụng, thuận tiện trong việc phát triển và bảo trì
Khi này, ta thiết kế 1 lớp mới có tên là CExample, thừa kế từ CGame:
Trang 13Hàm main được thiết kế đơn giản như sau:
Game C/C++ cho newbie - 04.Chia để trị
bởi Nguyễn Khánh Duy
Phương pháp chung
Ở 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:
Trang 14Source: http://upload.wikimedia.org/wikipedia/commons/c/cf
Á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 và cách chia state trong game
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:
Logo nhà sản xuất
Màn hình giới thiệu game (poster)
Menu chính (gồm các nút play, option )
Trang 15State vs sub-State
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
Thiết kế State như thế nào ?
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:
Init
Update
Render
Exit
Trang 16virtual void Init() = 0;
virtual void Update() = 0;
virtual void Render() = 0;
Trang 17Quản lý các State như thế nào?
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:
Tại một thời điểm, chỉ có 1 state được phép "hoạt động" (Update/Render)
Khi chuyển từ một state (A) sang một state khác (B), A phải được hủy (Exit) và B phải được tạo (Init) sau
Trang 18CState* m_pCurrentState;
CState* m_pNextState;
public:
void Update(bool isPause);
void SwitchState(CState* nextState);
};
Trang 21Trong 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
Kết nối State và Game
Do việc quản lý State lúc này được trao cho CStateManagement Ta chỉ việc kết nối CStateManagement
virtual void Run();
virtual void Exit();
virtual void Pause();
virtual void Resume();
bool IsAlive() {return m_isAlived;}
bool IsPause() {return m_isPaused;}
protected:
CGame();
Trang 22static CGame* s_pInstance;
virtual void Init() = 0;
virtual void Destroy() = 0;
Trang 23else
{
CStateManagement::GetInstance()->Update(false);
Trang 27Để việc quản lý được đơn giản, ta cần chia chương trình (game) thành các state nhỏ
Để thuận tiện, trước khi bắt đầu code game, ta nên vẽ trước state diagram, phác họa các state cần thiết, cũng như "đường đi" giữa các state
Đến thời điểm này, thư viện ta đã xây dựng được 3 lớp cơ bản:
Trang 28CGame
CState
CStateManagement
Game C/C++ cho newbie - 05 Game đa nền tảng
bởi Nguyễn Khánh Duy
Một ví dụ khác, hàmprintfdùng cho debug có thể dùng cho console win32, tuy nhiên với Android thì không thể, mà phải được thay bằng android_log_print
Cách thức cấu hình
Để làm được như đã nêu, trước tiên, ta cần cấu hình cho từng platform khác nhau khi compile Ta định nghĩa 1 số file header:
Trang 29Header.h: chứa những thông tin header cần cho chương trình
Config.h: chứa các thông tin cấu hình
Macros.h: chứa các macro thông dụng
Trang 32Trong ví dụ trên, ta dùng 1 hàmwrapper là Log, thay cho printf của win32 console và
android_log_print của Android log Đồng thời thay thế tất cả các hàmprintf bằng Log, và khai báo
#include "header.h" ở tất cả các lớp
Xây dựng lớp đại diện / cầu nối
Tuy nhiên, không phải trong trường hợp nào ta cũng có thể sử dụng macro Với những trường hợp phức tạp, ta thay thế bằng hàm Ta định nghĩa một lớp gọi là CDevice chứa các tập hàm này (Trong ví dụ này,
ta giả sử Sleep là một trường hợp ví dụ)
Trong trường hợp cần can thiệp sang một môi trường khác C/C++ (Obj-C hoặc Android Java), lớp device làm nhiệm vụ như một wrapper, đóng vai trò cầu nối, giúp thư viện game tương đối độc lập với platform
Trang 35Trong ví dụ trên, hàm SleepEx được thay đổi tùy theo platform
Lúc này, lớp CGame được hiệu chỉnh:
Trang 36else
{
CStateManagement::GetInstance()->Update(false); }
Trang 3728
Game C/C++ cho newbie - 06 Frame-rate
bởi Nguyễn Khánh Duy
FrameDt: khoảng thời gian dùng để thực hiện xong một frame
Limit framerate: là kỹ thuật giới hạn số lượng frame trong 1 giây Kỹ thuật này giúp:
+ Ổn định framerate chung cho cả game
+ Giảm tình trạng game lúc nhanh lúc chậm
+ Giúp đồng bộ hóa framerate của game trên các device khác nhau
+ Giảm năng lượng tiêu tốn không cần thiết Fps càng cao, đồng nghĩa với việc CPU/GPU làm việc trong khoảng thời gian dài với công suất cao, gây hao phí không cần thiết Limit framerate giữ fps ở mức độ vừa phải, tạo ra khoảng thời gian "nghĩ ngơi" cho CPU/GPU
Kỹ thuật Limit framerate
Như đã trình bày ở trên, Limit framerate đảm bảo fps được duy trì ổn định quanh một giá trị qui ước Giả sử mong muốn tốc độ game ổn định ở khoảng 25 fps
25 fps40ms/frame
Như vậy, mỗi lần update & render trung bình khoảng 80 ms Trong trường hợp tổng thời gian update + render nhỏ hơn 80ms, game được phép "ngủ" trong khoảng thời gian còn lại
Trang 38Trong hình minh họa trên:
Frame 1: tổng thời gian update + render là 20ms Do đó, game được sleep trong khoảng 20ms còn lại Frame 2: tổng thời gian update + render là 50ms > 40ms Không cần sleep (hoặc sleep 1)
Frame 3: tổng thời gian update + render là 25ms Do đó, game được sleep trong khoảng 15ms còn lại
Kỹ thuật limit fps không phải là kỹ thuật làm tăng fps Để nâng cao fps cho game, cần kỹ thuật
optimization
Nên limit fps bao nhiêu?
Không có một giá trị cụ thể nào được đưa ra cho câu trả lời này Tùy vào từng game, từng loại game mà người lập trình/nhà sản xuất đưa ra con số qui định cho mình Thông thường, fps = 25 là ổn
Tuy nhiên, việc limit fps sẽ không có tác dụng nếu fps thật sự nhỏ hơn fps cần limit Xem ví dụ trên, frame 2 Trong trường hợp này, limit fps không có vai trò gì khi đặt ở ngưỡng 25fps Tuy nhiên, với ngưỡng 12.5 fps (80 ms/frame), limit fps lại có tác dụng
Implementation
<="" a="" style="margin: 0px; padding: 0px; border: 0px; family: inherit; size: inherit; style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; vertical-align: baseline; color: rgb(3, 119, 186); text-decoration: none; outline: 0px;">Bước 1: Lấy ngày giờ hệ thống
<="" a="" style="margin: 0px; padding: 0px; border: 0px; family: inherit; size: inherit; style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; vertical-align: baseline; color: rgb(3, 119, 186); text-decoration: none; outline: 0px;">Ta cần một hàm lấy giờ hệ thống để tính toán khoảng thời gian dùng cho update + render Để làm được điều này:
Trang 39<="" a="" style="margin: 0px; padding: 0px; border: 0px; family: inherit; size: inherit; style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; vertical-align: baseline; color: rgb(3, 119, 186); text-decoration: none; outline: 0px;">
<="" a="" style="margin: 0px; padding: 0px; border: 0px; family: inherit; size: inherit; style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; vertical-align: baseline; color: rgb(3, 119, 186); text-decoration: none; outline: 0px;">
<="" a="" style="margin: 0px; padding: 0px; border: 0px; family: inherit; size: inherit; style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; vertical-align: baseline; color: rgb(3, 119, 186); text-decoration: none; outline: 0px;">
<="" a="" style="margin: 0px; padding: 0px; border: 0px; family: inherit; size: inherit; style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; vertical-align: baseline; color: rgb(3, 119, 186); text-decoration: none; outline: 0px;">Khai báo thư viện time.h trong header.h để sử dụng hàm clock() Việc khai báo này được thực hiện với config PLATFORM_WIN32_VS Các platform khác có một chút khác biệt ta sẽ đề cập sau
Trang 40void SleepEx(unsigned long milisec);
unsigned long GetTimer();
Bước 2: Tạo lớp CFpsManager quản lý fps
Lớp CFpsManager cũng được thiết kế dạng singleton, gồm các phương thức:
SetLimitFps: thiết lập thông số limit fps
BeginCounter: Được gọi khi bắt đầu tình toán fps
EndCounter: Được gọi tại ví trí kết thúc tính đoán fps và thực hiện limit frame rate
GetFrameDt: Lấy FrameDT hiện tại
Trang 41GetRuntimeFps: Lấy Fps hiện tại (giá trị do đạc thực tế) Kết quả trả về có thể không trùng khớp với giá trị thiết lập bởi SetLimitFps
Trang 43long Endtime = CDevice::GetInstance()->GetTimer();
int Dt = int(Endtime - m_iStartTime);
Trang 44Bước 3: Cài đặt chức năng tính toán fps vào vòng lập chính của game
Ta tiến hành hiệu chỉnh CGame
else
{