Giới thiệu về Game Engine

Một phần của tài liệu (Trang 49)

Game Engine có thể hiểu nhƣ là một bộ công cụ xây dựng sẵn gồm nhiều thành phần phối hợp với nhau, mỗi phần đảm nhiệm một công việc và liên kết với các thành phần khác.

Bằng cách sử dụng engine, lập trình viên có thể rút ngắn thời gian xây dựng game nhờ việc sử dụng lại các chức năng có sẵn của engine, hạn chế xây dựng lại từ đầu những thành phần không cần thiết.

Ngày nay có rất nhiều loại game engine cả miễn phí và có thu phí. Có thể kể ra một số engine hỗ trợ cho việc phát triển game di động nhƣ:

Unity3D

Đây là một game engine rất mạnh, hỗ trợ làm game cho PC, Mac, PlayStation, Wii, Web game và cả game di động cho Android và iOS.

Unity hỗ trợ các kĩ thuật đồ họa tiên tiến, với giao diện kéo thả và cơ chế quản lý tài nguyên hợp lý, sử dụng ngôn ngữ C# hoặc Javascript để lập trình game nên rất phù hợp với đại đa số ngƣời dùng, ngay cả những ngƣời chƣa có kinh nghiệm lập trình. Ngày nay, số game phát hành trên thị trƣờng sử dụng Unity3D rất nhiều. Tuy nhiên, số tiền để sở hữu engine này không nhỏ. 1500$ cho phiên bản thƣơng mại đi kèm

50

theo đó là 400$ cho chức năng phát hành game trên iOS, 400$ nữa cho chức năng phát hành game trên Android là một chi phí khá lớn đối với nhiều ngƣời.

Gideros Engine

Đây là một engine 2D miễn phí và sử dụng Lua làm ngôn ngữ chủ đạo.

Việc sử dụng Lua đem đến nhiều lợi ích vì đây là một ngôn ngữ đơn giản, dễ học và dễ tiếp cận. Engine hỗ trợ đầy đủ các chức năng cần thiết để làm game 2D nhƣ: Tối ƣu hóa tốc độ vẽ hình ảnh, hỗ trợ vật lý, hỗ trợ kết nối mạng.

Đi kèm theo đó là một cộng đồng ngƣời dùng rất đông và thân thiện, luôn sẵn sàng trả lời bất cứ câu hỏi nào và giúp đỡ hoàn toàn miễn phí.

Game Engine này có nhiều phiên bản, phiên bản miễn phí dành cho tất cả mọi ngƣời nhƣng với 1 điều kiện là phải kèm theo logo của nhà sản xuất ở đầu game. Nếu bỏ ra 150$ thì chúng ta sẽ gỡ bỏ đƣợc logo này.

Tại Việt nam, có khá nhiều công ty lựa chọn Gideros với nhiều sản phẩm khá hấp dẫn đang phát hành nhƣ Guava7, Ultimate Game Studio, Colrist Entertaiment,…

51

Chƣơng 2: PHÂN TÍCH VÀ XÂY DỰNG ĐỀ TÀI 1. Giới thiệu đề tài và nội dung game

Sau khi đã tìm hiểu kĩ lƣỡng các vấn đề lý thuyết cơ bản trong lập trình đồ họa, bây giờ em sẽ bắt đầu xây dựng một game trên di động có nội dung và lối chơi đơn giản để có thể vận dụng hết các kiến thức đã học. Đồng thời, yêu cầu đặt ra là game phải có lỗi chơi hấp dẫn và có nội dung hợp lý.

Sau một thời gian tìm hiểu thì em quyết định lựa chọn đề tài Biển đảo để làm ý tƣởng cho game mình sẽ xây dựng. Điều này sẽ giúp cho game nhận đƣợc sự đón nhận nhiệt tình của nhiều ngƣời nếu phát hành.

Tên game: Bảo vệ Hoàng Sa Trƣờng Sa

Cốt truyện game là cuộc chiến chống lại quân xâm lƣợc của quân dân Việt Nam nhằm bảo vệ toàn vẹn lãnh thổ các vùng đảo của Tổ quốc. Ngƣời chơi sẽ đƣợc dẫn đến các hòn đảo gắn liền với các trận đánh có thật trong lịch sử, song song với quá trình chơi, ngƣời chơi sẽ nắm thêm thông tin về các sự kiện lịch sử của 2 vùng biển đảo Hoàng Sa và Trƣờng Sa.

Đây sẽ là một game 3D thể loại bắn súng, đặt ngƣời chơi vào một pháo đài trên một hòn đảo giữa biển và liên tục bắn để ngăn ngừa tàu thuyền của quân địch xâm nhập vào đảo.

52

Trò chơi sẽ bao gồm một hòn đảo đặt giữa biển, trên đảo là vị trí của ngƣời chơi với 1 khẩu súng đại bác, có thể xoay chuyển đƣợc theo 2 hƣớng trái phải nhờ vào các phím mũi tên hoặc các nút nhấn hiện trên màn hình.

Xung quanh và phía trƣớc mặt là tàu địch liên tục xông tới tấn công hòn đảo, nhiệm vụ của ngƣời chơi là phải ngắm và bắn thật chính xác để ngăn chặn các tàu địch xâm nhập vào đảo.

Có 2 chế độ chơi là:

- Chiến dịch: Ngƣời chơi sẽ đƣợc dẫn đi theo cốt truyện, là các sự kiện lịch sử có thật và chiến đấu tại các địa điểm có thật, qua phần chơi này ngƣời chơi sẽ đƣợc học rất nhiều kiến thức về lịch sử, về các vùng biển đảo của nƣớc ta. - Tự do: Đây là chế độ chơi liên tục không có điểm dừng, ngƣời chơi liên tục

chống trả các đợt tấn công của quân địch cho đến khi nào bị chết hoặc chán không muốn chơi nữa.

Để tăng tính hấp dẫn và khuyến khích ngƣời chơi, một hệ thống điểm thƣởng và huân chƣơng sẽ đƣợc đƣa vào, khi ngƣời chơi đạt đƣợc một số điểm nhất định thì màn hình sẽ hiện lên thông báo chúc mừng. Tạo ra mục tiêu phấn đấu liên tục cho ngƣời chơi.

53

2. Các thành phần gồm có trong game

Các thành phần chính cần xây dựng trong game là:

- Hệ thống quản lý tài nguyên: Quản lý việc tải và lƣu trữ các tài nguyên hình

ảnh, âm thanh, mô hình 3D trong game.

- Hệ thống quản lý màn hình game: Quản lý các màn hình game nhƣ màn hình

Logo, màn hình Chơi game theo màn, màn hình Chơi game tự do,…

- Hệ thống quản lý các đối tƣợng game: Quản lý các đối tƣợng trong game

nhƣ Tàu, thuyền, súng, đạn, mặt biển, các hộp quà, điểm thƣởng, điểm và máu của ngƣời chơi,…

- Hệ thống quản lý điểm của game

Game sẽ gồm có các màn hình sau:

Màn hình Splash Screne hiện ra logo của nhóm phát triển và hình ảnh mở đầu của game.

Màn hình Main Menu: hiện ra menu lựa chọn, tùy chỉnh game

Màn hình In-Game screen: Đây là 2 màn hình riêng biệt, một màn hình dùng cho phần chơi chiến dịch và một màn hình dành cho phần chơi tự do.

Màn hình Game Over: Khi thua cuộc, màn hình game over hiện ra thông báo thua cuộc và hiện điểm tổng kết.

Màn hình In-Game Menu: Màn hình ingame menu hiện ra khi ngƣời chơi nhấn nút Pause khi đang chơi game, xuất hiện một menu với các tùy chọn: Chơi tiếp, thoát ra menu chính hoặc thoát hẳn game.

54

Trên một màn hình game, sẽ gồm có các đối tƣợng sau:

- Súng đại bác của ngƣời chơi

- Tòa thành nơi ngƣời chơi đang đứng

- Tàu thuyền của quân dịch

- Các thanh máu và thanh hiển thị lực bắn

- Hiển thị số lƣợng quân địch hiện có

- Hiển thị số lƣợng đạn còn lại

55

3. Thiết kế các lớp đối tƣợng

Để bắt tay vào xây dựng game, trƣớc tiên cần tạo ra một Game Engine, đây là một số thành phần cơ bản của game engine đã xây dựng:

Lớp mô hình – Model

Mô hình là lớp chứa dữ liệu các đỉnh vertex, tọa độ các đỉnh, màu sắc, tex coords,…

struct FACE_LINE

{

int v1; int t1; int n1; int v2; int t2; int n2; int v3; int t3; int n3; }; struct frameModel { float max_x; float max_y; float max_z; float min_x; float min_y; float min_z; }; class Model { public: GLuint vboID; int numCount; frameModel frM; Model(void); ~Model(void);

bool init(const char* fileName); };

Lớp Texture

Một lớp texture chứa dữ liệu hình ảnh cần load và vị trí của biến texture sau khi load vào bộ nhớ. Cấu trúc của hàm load dữ liệu cho lớp texture nhƣ sau:

bool Texture::init(const char* fileName, const char* tiling)

{

glGenTextures(1, &texID);

glBindTexture(GL_TEXTURE_2D, texID); int imgWidth, imgHeight, imgBpp; char* fileToRead = (char*)fileName;

#ifdef _ANDROID

LOGI("\nAndroid detected\n");

fileToRead[strlen(fileToRead)-1] = '\0'; #endif

char* imgData = LoadTGA(fileToRead, &imgWidth, &imgHeight, &imgBpp);

56

imgWidth, imgHeight, imgBpp);

glTexImage2D(GL_TEXTURE_2D, 0, (imgBpp==32)?GL_RGBA:GL_RGB, imgWidth, imgHeight, 0, (imgBpp==32)?GL_RGBA:GL_RGB, GL_UNSIGNED_BYTE, imgData);

if (tiling == "REPEAT") {

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); }

else

//if (tiling == "CLAMP")

{

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE); }

// not using mipmap

/*glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);*/ // using mipmap

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,

GL_LINEAR_MIPMAP_LINEAR ); glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); delete[] imgData; return true; } Lớp Shader

Để sử dụng Shader, ta cần tạo ra một lớp quản lý shader, lớp này làm nhiệm vụ load các shader, biên dịch và đƣa vào trong một biến program

int Shaders::Init(const char * fileVertexShader, const char *

fileFragmentShader) {

vertexShader = Graphic::GetInstance()->LoadShader(GL_VERTEX_SHADER,

fileVertexShader);

if ( vertexShader == 0 )

return -1;

LOGI("\nVertex shader %s loaded", fileVertexShader);

fragmentShader = Graphic::GetInstance()-

>LoadShader(GL_FRAGMENT_SHADER, fileFragmentShader);

if ( fragmentShader == 0 ) {

glDeleteShader( vertexShader );

return -2;

}

57

program = Graphic::GetInstance()->LoadProgram(vertexShader, fragmentShader);

LOGI("\nProgram compiled with ID: %d", program);

return 0;

}

Lớp Game Object

Sau khi đã có đầy đủ các thành phần thì chúng ta bắt đầu xây dựng lớp quản lý một đối tƣợng trong game. Một đối tƣợng bao gồm các model, texture, shader,… và các ma trận biến đổi cần thiết

class Objects { public: Vector3 Position; Vector3 Rotation; Vector3 Scale; int type; bool coll; Matrix mvpMatrix; float getDeltaTime;

float speed; // speed of object

GLuint modelID;

int modelVerNum;

GLuint textureID;

GLuint shaderID;

frameModel * frM, frame; // get values max vertex and min vertex of

Model

void UpdateFrame( void ); // change values max vertex and min vertex of Model at present time

Objects(void); ~Objects(void);

virtual void init();

void init(int _model, int _texture, int _shader);

virtual void Draw();

virtual void Update(float deltaTime);

//void Destroy();

};

Resource Manager – Quản lý tài nguyên game

Lớp resource manager có nhiệm vụ load các tài nguyên nhƣ Mô hình, Texture, các Shader và lƣu vào trong bộ nhớ của GPU.

class ResourcesManager: public Singleton<ResourcesManager> {

58 Model* _modelList; int _modelCount; Texture* _textureList; int _textureCount; Shaders* _shaderList; int _shaderCount; public: ResourcesManager(void); ~ResourcesManager(void);

void LoadResouces(const char* fileName);

frameModel * getFrameModel ( int id );

Model* getModel(int id);

Texture* getTexture(int id);

Shaders* getShader(int id);

int loadingCount, loadingTotal; int isPlaySound;

};

Scene Manager - Lớp quản lý màn hình game

Lớp quản lý màn hình game quản lý các màn hình hiện có trong game, cơ chế load một màn hình đƣợc biểu diễn trong đoạn code sau

void SceneManager::LoadScene(const char* fileName)

{

string section_name;

int _num = 0;

int _current = 0, _count = 0;

DATA_LINE* _rline = NULL;

int _curLine = 0;

char* fileToRead = (char*)fileName;

#ifdef _ANDROID

LOGI("\nAndroid detected\n");

fileToRead[strlen(fileToRead)-1] = '\0'; #endif

ifstream f(FileSystem::GetInstance()->GetPath(fileToRead).c_str());

if (f) { while (!f.eof()) { string line; getline(f, line); if (line.substr(0, 1) == "#") {

// Begin read section

int split_pos = line.find(':');

section_name = line.substr(1, split_pos - 1);

LOGI("\n[RM] Parsing \t%s \n", section_name.data());

59 2); LOGI("[RM] Count \t%s \n", section_num_data.data()); _current = 0; _count = 0; _num = atoi(section_num_data.data()); objectList = new Objects[_num];

objectCount = 0;

if (_rline != NULL) delete[] _rline; _rline = new DATA_LINE[8];

_curLine = 0; } else { _count++; if (_current < _num) { if (section_name == "Objects") {

int space_pos = line.find(' ');

DATA_LINE ln;

ln.key = line.substr(0, space_pos); ln.value = line.substr(space_pos + 1); _rline[_curLine].key = line.substr(0, space_pos); _rline[_curLine].value = line.substr(space_pos + 1); _curLine++; if (_count >= 8) {

LOGI("[SM] Count %s \tModelID %s

\tTexID %s \tShaderID %s\n", _rline[1].value.data(), _rline[2].value.data(),

_rline[3].value.data(), _rline[4].value.data()); objectList[objectCount].init(atoi(_rline[2].value.data()), atoi(_rline[3].value.data()), atoi(_rline[4].value.data())); sscanf(_rline[5].value.data(), "%f %f %f", &objectList[objectCount].Position.x, &objectList[objectCount].Position.y, &objectList[objectCount].Position.z); sscanf(_rline[6].value.data(), "%f %f %f", &objectList[objectCount].Rotation.x, &objectList[objectCount].Rotation.y, &objectList[objectCount].Rotation.z); sscanf(_rline[7].value.data(), "%f %f %f", &objectList[objectCount].Scale.x, &objectList[objectCount].Scale.y, &objectList[objectCount].Scale.z); objectCount++;

/*for (int i = 0; i < 8; i++) {

LOGI("%d \tID: %s \tVAL: %s\n", i, _rline[i].key.data(), _rline[i].value.data());

60 _count = 0; _current++; _curLine = 0; } } } } } } else {

LOGE("[SME] Scene file not found !\n"); }

}

Sau khi đã có lớp quản lý màn hình thì chúng ta tạo ra các màn hình game, một màn hình game cơ bản có cấu trúc nhƣ sau:

class GameState

{

public:

virtual void Init() = 0;

virtual void Draw() = 0;

virtual void Update(float deltaTime) = 0;

virtual void Destroy() = 0;

};

Ví dụ màn hình chọn level sẽ có cấu trúc nhƣ sau:

class LevelSelectState: public GameState

{

public:

Rectangle bgRect,survivalRect,storyRect;

string *story;

int currentPage, numPages;

float timer;

LevelSelectState(void); ~LevelSelectState(void); void Init();

void Draw();

void Update(float deltaTime); void Destroy();

};

Sau đó, chúng ta bắt đầu xây dựng các lớp đối tƣợng của game, gồm có: Cannon là khẩu súng đại bác

61

WarShip – các tàu địch Castle – tòa thành

HP – thanh máu của ngƣời chơi. Sea – mặt biển

Khẩu súng đại bác có cấu trúc đối tƣợng nhƣ sau:

class Cannon : public Singleton<Cannon>, public Objects

{

private:

unsigned int numBullet;// số đạn hiện đang có cũng đồng thời là số

đạn giới hạn khi mới bắt đầu chơi.

public:

float angle; //góc quay của súng

float count;

unsigned int reload_time;

bool isUnlimited; Cannon(void); ~Cannon(void); void init(); void Draw();

void setBullet(unsigned int);// set num of bullet

unsigned int getBullet(void);

void Update(float deltaTime); void rotLeft(float deltaTime); void rotRight(float deltaTime); void shock();

void reBegin();

void Fire(float _angle, float _v0); };

Cấu trúc đối tƣợng của một viên đạn:

class Bullet : public Objects, public Singleton<Bullet> {

public:

int MAX_POW; int MIN_POW;

float increase;

float angle;// góc bay của viên đạn so với trục Ox

float v0;//vận tốc ban đầu của viên đạn

float time;//thời điểm của viên đạn tính từ lúc bắn

Vector3 cur_pos;// vị trí ban đầu của viên đạn

bool isFire; //biến lưu lại trạng thái của viên đạn: bắn/chưa bắn

Bullet(void); void init();

void reBegin(void);

void Fire(float _angle, float _v0); void Update(float deltaTime);

62

};

Lớp HP – hiển thị trạng thái máu của ngƣời chơi

class HP :

public Status_Bar, public Singleton<HP> { float cur_hit; public: HP(void); void init(); void Draw();

void Update(float deltaTime); void Change(int value);

bool getHit(); ~HP(void); };

Lớp WarShip – Định nghĩa tàu của quân địch

class WarShip : public Objects

{

int x, y;

int dir; // direction of WarShip

bool addScoreC, addScoreB; // test add score when colliding Castle or Bullet

float leftW, rightW, topW, bottomW, frontW, backW;

int num_Rotate; // test warship rotate?

float dx, dz, agl;

float Angle; // the symbol of angle rotatate around Oy

Matrix mLocation; bool isBombed; bool direct ; bool checkColl; int typeColl; public:

//bool movePearl; // direction when press button PEARL

EnemyBullet* ebullet;

int accurate; int timefire;

Vector3 pos, rot, cmr;

bool isUpdate; int count; WarShip(void); WarShip(int );

63

~WarShip(void);

void Rotate( float, float, float, float );

//void Draw();

void UpdateWS1( float ); void UpdateWS2( float ); bool isCollideBullet(void); bool isCollideCastle(void); bool isCollideWS ( WarShip ws ); void Update(float deltaTime); void Draw();

};

Để di chuyển súng đại bác sang trái và phải theo các sự kiện nhấn phím hoặc bấm vào nút trên màn hình. Ta xử lý nhƣ sau:

//cannon turn right

if (GL2D::IsInRect(holdX, holdY, rectDirectionRight) ||

GL2D::IsInRect(holdX2, holdY2, rectDirectionRight)) {

if (Camera::GetInstance()->getRotation().y >= -0.1)

Camera::GetInstance()->Key(deltaTime, 39);

cannon->rotRight(deltaTime); #ifdef _ANDROID if (ResourcesManager::GetInstance()->isPlaySound == 1) { StopSound(); PlaySound(2, 0); } #endif

LOGI("Turn right\n"); }

//cannon turn left

if (GL2D::IsInRect(holdX, holdY, rectDirectionLeft) ||

GL2D::IsInRect(holdX2, holdY2, rectDirectionLeft)) {

if (Camera::GetInstance()->getRotation().y <= 0.1)

Camera::GetInstance()->Key(deltaTime, 37);

cannon->rotLeft(deltaTime); #ifdef _ANDROID if (ResourcesManager::GetInstance()->isPlaySound == 1) { StopSound(); PlaySound(2, 0); } #endif

LOGI("Turn left\n"); }

Khi ngƣời chơi nhấn vào nút Fire, ta sẽ điều khiển cho thanh lực bắn tăng lên và giảm xuống nhƣ sau:

64 if (GL2D::IsInRect(holdX, holdY, rectFireBT) || GL2D::IsInRect(holdX2, holdY2, rectFireBT)) { if ( power < 0 ) { bullet->increase *= -1; pow_bar->increase *= -1; } if ( power > bullet->MAX_POW ) {

bullet->increase *= -1; //nếu lực bắn đặt tối đa thì lực bắn sẽ giảm xuống nếu tiếp tục nhấn

pow_bar->increase *= -1; }

power += bullet->increase * (deltaTime * 50);

pow_bar->scale += pow_bar->increase * (deltaTime * 50);

LOGI("power: %f\tlengh bar: %f\n",power, pow_bar->scale); }

Khi thả ra thì sẽ bắt đầu sự kiện bắn:

if (GL2D::IsInRect(holdX, holdY, rectFireBT) || GL2D::IsInRect(holdX2, holdY2, rectFireBT))

{

LOGI("Power: %f\n", power); pow_bar->Reset(); cannon->Fire(cannon->angle, power); //cannon->shock(); #ifdef _ANDROID if (ResourcesManager::GetInstance()->isPlaySound == 1) { StopSound(); PlaySound(1, 0); } #endif power = 0; }

65

4. Chuyển game trên máy tính sang điện thoại

Để chuyển game từ máy tính sang điện thoại, chúng ta cần cài đặt các công cụ

- Android SDK

- Android NDK

- Cygwin

Sau khi cài đặt hoàn tất các môi trƣờng trên thì bắt đầu việc chuyển đổi bằng cách tạo ra một Project Android.

Sau đó ta cần biên dịch các file CPP của game bằng cách dùng NDK và Cygwin và Make. Sau đây là file định nghĩa các file CPP cần biên dịch cho MAKE.

LOCAL_MODULE := libgl2jni LOCAL_CFLAGS := -Werror LOCAL_SRC_FILES := ../../InternshipFW/3DMath.cpp \ ../../InternshipFW/Application.cpp \ ../../InternshipFW/BoxObject.cpp \ ../../InternshipFW/BrokenGlass.cpp \ ../../InternshipFW/Bullet.cpp \ ../../InternshipFW/Camera.cpp \ ../../InternshipFW/Cannon.cpp \ ../../InternshipFW/Castle.cpp \

Một phần của tài liệu (Trang 49)

Tải bản đầy đủ (PDF)

(74 trang)