Tải bản đầy đủ (.pdf) (45 trang)

Hướng dẫn lập trình Game C/C++ cho người mới bắt đầu

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (944.36 KB, 45 trang )


1 | P a g e

Game 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:

2 | P a g e



Java: 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)

3 | P a g e

Mộ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:



4 | P a g e





Bước 2, tạo file main.cpp,




Add một item mới



5 | P a g e




Tạo main.cpp từ template



với nội dung:



1
2
3
4
#include "stdio.h"
int main()
{
while(true)

6 | P a g e

5
6
7
8
{
printf("Hello Game\n");
}
}





Frame delay

Đây được xem là bước cải tiến đầu tiên của "game" trên. Như bạn thấy, dòng hello game xuất hiện một
cách liên tục thông qua vòng lập. Mỗi vòng lặp như vậy, được gọi là một "frame".

Trong thực tế, mắt người chỉ cần 24 frame/s là đủ để cảm nhận hiệu ứng "mượt mà". Do đó để tránh
lãng phí CPU, cũng như nhường CPU cho các task khác, ta tạo ra khoảng delay giữa các frame. Khoảng
thời gian này là bao nhiêu, sẽ được giải thích rõ hơn trong các phần sau. Hiện tai, ta thử cho khoảng thời
gian cố định là nghĩ 80 ms.



1
2
3
4
5
6
7
8
9
10
#include "stdio.h"
#include "windows.h"

int main()

{
while(true)
{
printf("Hello Game\n");
Sleep(80);
}

7 | P a g e

11
}
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.

8 | P a g e


Và một số thuộc tính:




[bool] m_isAlive: cho biết game còn "sống" hay không. Được thiết lập "true" khi init, và được thiết lập
false khi hàm exit được gọi.
[bool] m_isPaused: cho biết game có đang tạm dừng hay không. Được thiết lập false thi khi init. Giá trị
được cập nhật thông qua hàm Pause/Resume



1
2
3
4
5
6

7
8
9
10
11
12
13
14
15
16
/// CGame.h

#ifndef __CGAME_H__
#define __CGAME_H__

namespace GameTutor
{
class CGame
{
public:
CGame();
virtual ~CGame() {}
virtual void Run();
virtual void Exit();
virtual void Pause();
virtual void Resume();

9 | P a g e

17

18
19
20
21
22
23
24
25
26
27
28
bool IsAlive() {return m_isAlived;}
bool IsPause() {return m_isPaused;}
protected:
virtual void Init() = 0;
virtual void Destroy() = 0;
protected:
bool m_isAlived;
bool m_isPaused;
};
}

#endif





1
2

3
4
5
6
7
8
9
10
11
/// CGame.cpp

#include "CGame.h"
#include "stdio.h"
#include "windows.h"

namespace GameTutor
{
CGame::CGame(): m_isPaused(false), m_isAlived(true){}

void CGame:: Pause()

10 | P a g e

12
13
14
15
16
17
18

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
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)
{
printf("paused\n");
}
else
{
printf("running\n");
}
Sleep(80);

11 | P a g e

40
41
42
43
}
Destroy();
}
}



Trong ví dụ trên, ta thấy lớp CGame được thiết kế với 2 hàm thuần ảo. Điều này có nghĩa là ta không thể
khởi tạo trực tiếp lớp CGame. Để sử dụng lớp CGame, ta cần tạo một lớp thừa kế từ CGame. Điều này
đảm bảo cho sự tách biệt giữa lớp thư viện (CGame) và ứng dụng. Vì sao cần có sự tách biệt này?



Đả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:




1
2
3
4
5
6
7
8
9
/// CExample.h
#ifndef __CEXAMPLE_H__
#define __CEXAMPLE_H__
#include "CGame.h"


using namespace GameTutor;

class CExample:public CGame
{

12 | P a g e

10
11
12
13
14
15
16
17
18
19
public:
CExample(): CGame() {}
virtual ~CExample() {}

protected:
void Init();
void Destroy();
};

#endif






1
2
3
4
5
6
7
8
9
10
11
12
13
//Example.cpp
#include "CExample.h"
#include "stdio.h"

void CExample::Init()
{
printf("Init\n");
}

void CExample: estroy()
{
printf("Destroy\n");
}



13 | P a g e



Hàm main được thiết kế đơn giản như sau:



1
2
3
4
5
6
7
/// main.cpp
#include "CExample.h"

int main()
{
(new CExample())->Run();
}
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:

14 | P a g e





Source:


Á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 )
Các menu phụ chọn màn chơi
Loading trước khi vào màn chơi
Màn chơi


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.

15 | P a g e




State 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
Run

Exit

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:




Init
Update
Render
Exit

16 | P a g e








1
2
3
4
5
6

7
8
9
10
11
12
13
14
/// 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;

17 | P a g e

15
16
17
18
virtual void Exit() = 0;

};
}
#endif




Quả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
đó
Chỉ chuyển state (chuyển sang state khác) khi State cũ đã kết thúc việc update & render.



1
2
/// CStateManagement.h



18 | P a g e

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

29
30
#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);
};


19 | P a g e

31
32
}
#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:


1
2
3
4
5
6
7
8
9
10
11

12
13
14
15
/// 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)
{

20 | P a g e

16
17
18
19
20
21
22
23

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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;
}
}



21 | P a g e

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.


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
và CGame.


1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// 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();

22 | P a g e

20
21
22
23
24
25
26
27
28
29
static CGame* s_pInstance;
virtual void Init() = 0;
virtual void Destroy() = 0;
protected:
bool m_isAlived;
bool m_isPaused;
};
}

#endif




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// 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;
}

23 | P a g e

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

40
41
42
43

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);

24 | P a g e

44
45
46
47
48
49
}
Sleep(80);
}
Destroy();
}
}



Ví dụ sử dụng State và chuyển state

Giả sử ta có 2 State:



State Logo: xuất ra màn hình từ 1 đến 10. Sau đó chuyển qua state Poster
State Poster: xuất ra màn hình từ 10 tới 1. Sau đó kết thúc game.




1
2
3
4
5
6
7
8
9
/// CStateLogo.h

#ifndef __CSTATELOGO_H__
#define __CSTATELOGO_H__
#include "CState.h"
using namespace GameTutor;

class CStateLogo: public CState
{

25 | P a g e

10
11
12
13
14
15
16
17

18
19
20
21
public:
CStateLogo();
~CStateLogo() {}

void Init();
void Update();
void Render();
void Exit();
private:
int m_iCount;
};
#endif



1
2
3
4
5
6
7
8
9
10
11

12
13
/// 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");

×