發(fā)布時(shí)間:2024-03-08 10:45:50 瀏覽量:229次
哈嘍,又有好幾天沒見啦。前兩天因?yàn)槌霾罨丶?,所以文章更新慢了好多,在這里說一聲抱歉啦。
回顧一下上次我們學(xué)習(xí)的內(nèi)容,在上一篇文章中,我們主要了解到了一種圖片的格式:dds圖片,并且知道了這種格式的圖片應(yīng)該如何去讀取并且最終成功地展示了出來。
那么我們今天就需要將之前的一些元素,如箱子,玩家,墻壁等都修改為圖片來進(jìn)行展示,這樣我們的游戲就會(huì)有真正的畫面啦。
首先還是說明一下,本系列的所有文章均屬于學(xué)習(xí)《游戲開發(fā):世嘉新人培訓(xùn)教材》一書的讀書筆記,因此會(huì)重復(fù)使用到書中已經(jīng)授權(quán)免費(fèi)的代碼。如果有感興趣的讀者,也可以自己去買一本來學(xué)習(xí)哦。當(dāng)然,也可以看看我寫的系列,我會(huì)簡(jiǎn)化書中的內(nèi)容,通俗易懂的記錄下知識(shí)點(diǎn)。
那么我們回顧一下之前的代碼,所有代碼都放在了一個(gè)cpp文件中,所以其實(shí)有很多地方都還可以改進(jìn),要想讓我們的程序具有健壯性,可擴(kuò)展性等優(yōu)秀特質(zhì),我們這次就按照面向?qū)ο蟮乃枷雭韮?yōu)化當(dāng)前的代碼。
之前我們將文件讀取單獨(dú)封裝成為了一個(gè)函數(shù)
readFile( char** buffer, int* size, const char* filename );
函數(shù)里面的代碼我就不展示了
這個(gè)函數(shù)的作用主要是讀取項(xiàng)目同級(jí)目錄下,以filename命名的文件,
并將文件數(shù)據(jù)存儲(chǔ)在buffer中,文件大小存儲(chǔ)在size中
首先,我們需要生成一個(gè)file.h的頭文件,并聲明一個(gè)File類
class File{
public:
File( const char* filename );
~File();
int size() const;
const char* data() const;
unsigned getUnsigned( int position ) const;
private:
int mSize;
char* mData;
};
這樣的作用是,不需要再手動(dòng)去釋放讀取的File內(nèi)存數(shù)據(jù),增加了程序安全性。并且將getUnsigned()函數(shù)的聲明包含了進(jìn)來,方便直接對(duì)外提供讀取dds圖片指定位置的數(shù)據(jù)。
因此我們就需要有一個(gè)file.cpp來定義這個(gè)類
File::File( const char* filename ) : mSize( 0 ), mData( 0 ){
// 與之前readFile函數(shù)一樣的作用,所以只需要按照 File file("data.txt")的方式就可以直接讀取data.txt中的數(shù)據(jù)了
ifstream in( filename, ifstream::binary );
if ( in ){
in.seekg( 0, ifstream::end );
mSize = static_cast< int >( in.tellg() );
in.seekg( 0, ifstream::beg );
mData = new char[ mSize ];
in.read( mData, mSize );
}
}
File::~File(){
// 釋放文件內(nèi)存數(shù)據(jù)
delete[] mData;
mData = 0;
}
int File::size() const {
return mSize;
}
const char* File::data() const {
return mData;
}
//取出unsigned
unsigned File::getUnsigned( int p ) const {
// 返回dds文件中指定位置的數(shù)據(jù)
const unsigned char* up;
up = reinterpret_cast< const unsigned char* >( mData );
unsigned r = up[ p ];
r |= up[ p + 1 ] << 8;
r |= up[ p + 2 ] << 16;
r |= up[ p + 3 ] << 24;
return r;
}
在之前我們定義了一個(gè)Array2D類,主要是使用一維數(shù)組來存儲(chǔ)2D游戲中的二維數(shù)據(jù),因此我們也需要生成一個(gè)Array2D.h的頭文件
template< class T > class Array2D{
public:
Array2D() : mArray( 0 ){}
~Array2D(){
delete[] mArray;
mArray = 0; //將指針賦值為0是一種習(xí)慣。
}
void setSize( int size0, int size1 ){
if ( mArray ){
delete[] mArray;
mArray = 0;
}
mSize0 = size0;
mSize1 = size1;
mArray = new T[ size0 * size1 ];
}
T& operator()( int index0, int index1 ){
return mArray[ index1 * mSize0 + index0 ];
}
const T& operator()( int index0, int index1 ) const {
return mArray[ index1 * mSize0 + index0 ];
}
private:
T* mArray;
int mSize0;
int mSize1;
};
這里面的聲明與之前沒有區(qū)別。
在這里請(qǐng)大家思考一下:知道為什么我們不用生成Array2D.cpp文件么?
這個(gè)類的主要作用就如同它的名字一樣,和File類類似,提供了圖片數(shù)據(jù)的加載與使用
class Image{
public:
Image( const char* filename );
~Image();
int width() const;
int height() const;
void draw(
int dstX,
int dstY,
int srcX,
int srcY,
int width,
int height ) const;
private:
int mWidth;
int mHeight;
unsigned* mData;
};
可以看到里面的構(gòu)造函數(shù)與File類類似,直接給它xxx.dds名稱,它就能將這個(gè)dds的數(shù)據(jù)加載進(jìn)來并保存在mData中,并且使用mWidth,mHeight來記錄了圖片的寬度和高度。
而我們需要重點(diǎn)學(xué)習(xí)的便是這個(gè)draw()函數(shù)了,可以看到里面有6個(gè)參數(shù)
void Image::draw(
int destinationX,
int destinationY,
int sourceX,
int sourceY,
int width,
int height ) const {
unsigned* vram = Framework::instance().videoMemory();
unsigned windowWidth = Framework::instance().width();
for ( int y = 0; y < height; ++y ){
for ( int x = 0; x < width; ++x ){
unsigned* dst = &vram[ ( y + dstY ) * windowWidth + ( x + dstX ) ];
*dst = mData[ ( y + srcY ) * mWidth + ( x + srcX ) ];
}
}
}
我們首先可以先花幾分鐘思考下這幾行代碼的含義,然后我會(huì)為大家講解下具體的作用。
。。。
思考好了么?有沒有理解這幾行代碼的含義呢?
我們來看函數(shù)的參數(shù),其實(shí)從命名來看就可以大概理解它的意思:我們首先明確這個(gè)draw函數(shù)是在Image類中聲明的,顧名思義,就知道它的作用是將圖片繪制在屏幕上。因此我們需要兩個(gè)坐標(biāo)點(diǎn):需要繪制的圖片的起始坐標(biāo)點(diǎn)(sourceX,sourceY),具體要我們繪制在哪個(gè)位置的目標(biāo)坐標(biāo)點(diǎn)(destinationX,destinationY),并且我們需要繪制多大的圖片在目標(biāo)位置(width,height)。
那么我們從參數(shù)就知道了這個(gè)函數(shù)的具體作用:
我用圖形化方式來解讀下這個(gè)函數(shù):假如我們需要將一張圖片繪制在窗口(3,1)的位置,并且只需要展示圖片的上半部分,那么我們就可以這樣來調(diào)用這個(gè)draw函數(shù)
draw(3,1,0,0,100,30);
然后我們來看下具體的代碼,里面只有兩個(gè)for循環(huán)
for ( int y = 0; y < height; ++y ){
for ( int x = 0; x < width; ++x ){
unsigned* dst = &vram[ ( y + dstY ) * windowWidth + ( x + dstX ) ];
*dst = mData[ ( y + srcY ) * mWidth + ( x + srcX ) ];
}
}
解讀:
兩個(gè)for循環(huán)控制了我們繪制的區(qū)域大小
首先,vram表示了當(dāng)前我們的窗口的內(nèi)存數(shù)據(jù),windowWidth表示了當(dāng)前窗口的寬度
意思就是我們需要從mData圖片數(shù)據(jù)中將圖片內(nèi)容繪制在vram中
vram[ ( y + dstY ) * windowWidth + ( x + dstX ) ]表示當(dāng)前需要繪制的像素在窗口的位置,
主要到這里假如是下一行的像素,是需要乘上窗口的寬度
mData[ ( y + srcY ) * mWidth + ( x + srcX ) ]表示取圖片中哪個(gè)位置的像素?cái)?shù)據(jù),
這里的mWidth表示的是圖片的寬度,同理,若是去圖片下一行像素的數(shù)據(jù),就需要乘上圖片的寬度。
然后就是將圖片中的像素?cái)?shù)據(jù)復(fù)制給窗口指定位置的像素點(diǎn)數(shù)據(jù)了。
這樣解讀下來,是不是對(duì)這個(gè)函數(shù)就完全理解啦?
還記得之前的State類么?里面記錄的是實(shí)時(shí)的游戲數(shù)據(jù),根據(jù)這些數(shù)據(jù),我們?cè)诿看屋斎雡,a,s,d之后,才能將畫面更新并繪制出來。
同樣先給出State.h的內(nèi)容
class State{
public:
State( const char* stageData, int size );
~State();
void update( char input );
void draw() const;
bool hasCleared() const;
private:
enum Object{
OBJ_SPACE,
OBJ_WALL,
OBJ_BOX,
OBJ_PLAYER,
OBJ_UNKNOWN,
};
enum ImageID{
IMAGE_ID_PLAYER,
IMAGE_ID_WALL,
IMAGE_ID_BOX,
IMAGE_ID_BOX_ON_GOAL,
IMAGE_ID_GOAL,
IMAGE_ID_SPACE,
};
void setSize( const char* stageData, int size );
void drawCell( int x, int y, ImageID ) const;
int mWidth;
int mHeight;
Array2D< Object > mObjects;
Array2D< bool > mGoalFlags;
Image* mImage; //圖片
};
可以看到里面大部分代碼和之前的一樣,我們只需要關(guān)注新增的代碼部分。
enum ImageID{
IMAGE_ID_PLAYER,
IMAGE_ID_WALL,
IMAGE_ID_BOX,
IMAGE_ID_BOX_ON_GOAL,
IMAGE_ID_GOAL,
IMAGE_ID_SPACE,
};
Image* mImage; //圖片
解讀:
首先可以看到聲明了一個(gè)圖片類,里面包含了圖片的數(shù)據(jù)以及繪制圖片的函數(shù)
然后我們看到這里定義了一個(gè)圖片ID枚舉類,這個(gè)枚舉類的作用便是該書采用了網(wǎng)格繪制圖片的方式,
它將箱子,玩家,墻壁等圖片全部放在了一個(gè)dds圖片文件中,并且保證了他們的高度與寬度相同,
然后按照枚舉類的定義,依此把他們橫排。
mImage存儲(chǔ)的表示上面image.dds的數(shù)據(jù),并且其中按照枚舉類的順序,從左往右將每個(gè)元素的小圖片排列在一起。所以可以看到玩家這個(gè)圖片的序號(hào)便是0,墻壁的序號(hào)是1,以此類推。
下面看下狀態(tài)類的其他函數(shù),也有一些變化,這里我列出需要修改的函數(shù)部分:
void State::draw() const {
for ( int y = 0; y < mHeight; ++y ){
for ( int x = 0; x < mWidth; ++x ){
Object o = mObjects( x, y );
bool goalFlag = mGoalFlags( x, y );
ImageID id = IMAGE_ID_SPACE;
if ( goalFlag ){
switch ( o ){
case OBJ_SPACE: id = IMAGE_ID_GOAL; break;
case OBJ_WALL: id = IMAGE_ID_WALL; break;
case OBJ_BLOCK: id = IMAGE_ID_BLOCK_ON_GOAL; break;
case OBJ_MAN: id = IMAGE_ID_PLAYER; break;
}
}else{
switch ( o ){
case OBJ_SPACE: id = IMAGE_ID_SPACE; break;
case OBJ_WALL: id = IMAGE_ID_WALL; break;
case OBJ_BLOCK: id = IMAGE_ID_BLOCK; break;
case OBJ_MAN: id = IMAGE_ID_PLAYER; break;
}
}
drawCell( x, y, id );
}
}
}
解讀:
draw函數(shù)的變化不算太大,其中只是根據(jù)具體的位置信息,來給id賦予圖片的順序號(hào)
void State::drawCell( int x, int y, ImageID id ) const {
mImage->draw( x*32, y*32, id*32, 0, 32, 32 );
}
解讀
drawCell函數(shù)只保留一行代碼了,這里可以看到繪制在draw函數(shù)中傳入了每個(gè)位置的坐標(biāo)信息以及
需要繪制的圖片ID,然后進(jìn)行了draw( x*32, y*32, id*32, 0, 32, 32 )這樣的調(diào)用。
這個(gè)32是什么意思呢?其實(shí)很好理解,書中將每個(gè)元素的圖片大小固定位了32*32,即玩家,墻,箱子等
這些元素在窗口中占據(jù)的像素大小就是32*32.
這里我在附上Image類中的draw函數(shù)的定義: draw(int dstX, int dstY, int srcX, int srcY, int width, int height )
其中dstX=x*32,dstY=y*32,根據(jù)之前的解讀,dstX,dstY便是繪制目標(biāo)起始坐標(biāo),
這里將x,y乘上32后再賦值其實(shí)是因?yàn)槊總€(gè)元素占據(jù)32*32的大小,
比如第一個(gè)元素從(0,0)開始繪制,那么下一個(gè)元素就要在其元素占據(jù)的位置外進(jìn)行繪制了。
然后看到srcX=id*32,srcY=0,上面我們解讀到圖片數(shù)據(jù)是橫排在一個(gè)dds文件中的,并且為他們編上了
序號(hào),那么每個(gè)圖片的起始繪制坐標(biāo),首先在Y軸上是不會(huì)變化的,都是0;在X軸上,由于是橫著排列,
因此需要序號(hào)*32找到它的X起始繪制坐標(biāo)。
最后兩個(gè)參數(shù)就不用多說了,便是元素圖片的寬度與高度,這里均為32
有沒有理解上面這個(gè)drawCell函數(shù)呢?沒有理解的沒有關(guān)系,可以反復(fù)閱讀幾遍,并且結(jié)合之前Image類中的draw函數(shù)進(jìn)行理解。
同樣的,假如我們的元素圖片大小是45*45,那么可以知道這里的32都需要修改為45.
那假如我們圖片的大小是寬*高=100*60呢?那這個(gè)drawCell函數(shù)又會(huì)是怎樣的呢?大家可以思考一下。
State狀態(tài)類中的其他函數(shù)均沒有變化,主要就是需要理解draw函數(shù)的變化以及drawCell函數(shù)的調(diào)用邏輯。
在主函數(shù)main.cpp中也并沒有代碼的新增,主要就是需要注意到文件讀取的方式不再是調(diào)用readFile函數(shù)了,而是定義File類了。
好啦,今天的內(nèi)容就到這里啦。可以看到今天文章的內(nèi)容還是很豐富的,光閱讀一次可能無法完全理解,尤其是State類對(duì)Image類的調(diào)用。
但學(xué)習(xí)就是這樣,不理解的地方我們就需要反復(fù)的去閱讀,去思考。這樣收獲到的知識(shí)才會(huì)更加深刻。
如果覺得我的學(xué)習(xí)筆記系列還不錯(cuò)的話,可以關(guān)注我并點(diǎn)贊收藏轉(zhuǎn)發(fā)文章三連喲(*?▽?*),我會(huì)繼續(xù)努力為大家?guī)砀嗟母哔|(zhì)量文章。
熱門資訊
探討游戲引擎的文章,介紹了10款游戲引擎及其代表作品,涵蓋了RAGE Engine、Naughty Dog Game Engine、The Dead Engine、Cry Engine、Avalanche Engine、Anvil Engine、IW Engine、Frostbite Engine、Creation引擎、Unreal Engine等引擎。借此分析引出了游戲設(shè)計(jì)領(lǐng)域和數(shù)字藝術(shù)教育的重要性,歡迎點(diǎn)擊咨詢報(bào)名。
2. 手機(jī)游戲如何開發(fā)(如何制作傳奇手游,都需要準(zhǔn)備些什么?)
?如何制作傳奇手游,都需要準(zhǔn)備些什么?提到傳奇手游相信大家都不陌生,他是許多80、90后的回憶;從起初的端游到現(xiàn)在的手游,說明時(shí)代在進(jìn)步游戲在更新,更趨于方便化移動(dòng)化。而如果我們想要制作一款傳奇手游的
3. B站視頻剪輯軟件「必剪」:免費(fèi)、炫酷特效,小白必備工具
B站視頻剪輯軟件「必剪」,完全免費(fèi)、一鍵制作炫酷特效,適合新手小白??靵碓囋嚕?/span>
4. Steam值得入手的武俠游戲盤點(diǎn),各具特色的快意江湖
游戲中玩家將面臨武俠人生的掙扎抉擇,戰(zhàn)或降?殺或放?每個(gè)抉定都將觸發(fā)更多愛恨糾葛的精彩奇遇?!短烀嬗肪哂卸嗑€劇情多結(jié)局,不限主線發(fā)展,高自由...
5. Bigtime加密游戲經(jīng)濟(jì)體系揭秘,不同玩家角色的經(jīng)濟(jì)活動(dòng)
Bigtime加密游戲經(jīng)濟(jì)模型分析,探討游戲經(jīng)濟(jì)特點(diǎn),幫助玩家更全面了解這款GameFi產(chǎn)品。
6. 3D動(dòng)漫建模全過程,不是一般人能學(xué)的會(huì)的,會(huì)的多不是人?
步驟01:面部,頸部,身體在一起這次我不準(zhǔn)備設(shè)計(jì)圖片,我從雕刻進(jìn)入。這一次,它將是一種純粹關(guān)注建模而非整體繪畫的形式。像往常一樣,我從Sphere創(chuàng)建它...
7. 3D動(dòng)畫軟件你知道幾個(gè)?3ds Max、Blender、Maya、Houdini大比拼
當(dāng)提到3D動(dòng)畫軟件或動(dòng)畫工具時(shí),指的是數(shù)字內(nèi)容創(chuàng)建工具。它是用于造型、建模以及繪制3D美術(shù)動(dòng)畫的軟件程序。但是,在3D動(dòng)畫軟件中還包含了其他類型的...
8. 3D打印技巧揭秘!Cura設(shè)置讓你的模型更堅(jiān)固
想讓你的3D打印模型更堅(jiān)固?不妨嘗試一下Cura參數(shù)設(shè)置和設(shè)計(jì)技巧,讓你輕松掌握!
?三昧動(dòng)漫對(duì)于著名ARPG游戲《巫師》系列,最近CD Projekt 的高層回應(yīng)并不會(huì)推出《巫師4》。因?yàn)椤段讕煛废盗性诓邉澋臅r(shí)候一直定位在“三部曲”的故事框架,所以在游戲的出品上不可能出現(xiàn)《巫師4》
10. 如何自己開發(fā)一款游戲(游戲開發(fā)入門必看:五大獨(dú)立游戲開發(fā)技巧)
?游戲開發(fā)入門必看:五大獨(dú)立游戲開發(fā)技巧無論您是剛剛起步開發(fā)自己的第一款游戲,還是已經(jīng)制作了幾款游戲,本篇文章中的5大獨(dú)立游戲開發(fā)技巧都可以幫助您更好地設(shè)計(jì)下一款游戲。無論你對(duì)游戲有著什么樣的概念,都
最新文章
同學(xué)您好!