激情六月丁香婷婷|亚洲色图AV二区|丝袜AV日韩AV|久草视频在线分类|伊人九九精品视频|国产精品一级电影|久草视频在线99|在线看的av网址|伊人99精品无码|午夜无码视频在线

高校合作1:010-59833514 ?咨詢電話:400-810-1418 服務(wù)與監(jiān)督電話:400-810-1418轉(zhuǎn)接2

《游戲設(shè)計(jì)之路》——給箱子注入靈魂

發(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;
}

二.數(shù)組模板類

在之前我們定義了一個(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ù)就完全理解啦?

四.游戲狀態(tài)類

還記得之前的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ù)

在主函數(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ì)量文章。

熱門課程推薦

熱門資訊

請(qǐng)綁定手機(jī)號(hào)

x

同學(xué)您好!

您已成功報(bào)名0元試學(xué)活動(dòng),老師會(huì)在第一時(shí)間與您取得聯(lián)系,請(qǐng)保持電話暢通!
確定