2D横纵版与斜视角游戏地图开发原理

一个学生问的问题,借机做了个文档。发到博客。

2D横纵版与斜视角游戏地图

开发原理

作者 Honghaier

QQ285421210

日期:2009-12-8

开发前提

1.假设您已经常握了C++语言,并能够熟练使用VC++开发工具。

2.假设您已经能够成功的封装了一个C2DPicture类。它具有以下函数。

//载入图片文件,成功返回一个索引值,失败返回-1

Int LoadImage(const char* szFileName);

//取得纹理

LPDIRECT3DTEXTURE9 GetTexture();

//取得图片信息

Int GetImageWidth();

Int GetImageHeight();

如果您无法做出这样的类,可以参考D3D的可变顶点格式D3DFVF_XYZW,有很多例子。

3. 出于效率的考虑,我认为您应该写一个管理器来管理这些C2DPicture类。假设叫C2DPictureManage.它具有以下函数.

Public:

//载入所有图片,在这里你可以加载图片

BOOL Init();

//取得对应的C2DPicture指针

C2DPicture* GetPicture(int PictureIndex);

//在指定位置显示指定图片

Void ShowPicture(int PictureIndex,int Left,int Right,int Width,int Height);

Private:

//在指定位置渲染图片

Void Render(LPDIRECT3DTEXTURE9 pTexture int Left,int Right,int Width,int Height);

注:为什么不在C2DPicture类中做这个函数呢,因为出于效率的考虑,我们可以在C2DPictureManage定义四个顶点就行了,在ShowPicture函数中通过PictureIndex来获取C2DPicture对象指针。然后取得纹理和图片通过Render进行D3D渲染;

原理简述:

2D的横纵版地图:

整个地图由横,纵,二个方向的TILE块组成。每个TILE块即是一层或几层图片。具体多少层看您的游戏设置了,一般来说,一个TILE里可以放三层,最底层是背景,第二层是远建筑,第三层是近建筑。当然,这只是基于拼合关系的TILE图,还有大量的草,树,星星什么的。是不基于拼合关系的。

所以您需要好好设计数据结构,比如这样,分为两种结构,

1.基于拼合的TILE

Struct SMapTile

{

Int mPosX; //在地图中绝对位置X

Int mPosY; //在地图中绝对位置Y

Int Picture[3]; //三层图片的ID
}

;

2.不固定的地图元素

Struct SMapElement

{

Int mPosX; //在地图中绝对位置X

Int mPosY; //在地图中绝对位置Y

Int mPictureIndex; //图片索引

};

1.1

在这里,我引入了一个典型的2D横纵版场景。每个TILE45 * 32 像素的。我们这里的TILE三层为别是背景墙,斜桥,近墙,放在SMapTile结构中。不固定元素有草,灯光台。放在SMapElement结构中。

这只是一屏。实际上一个游戏场景需要至少几十屏,也可能上百屏。所以这一屏,只是整个场景中的一小区块。

所以一般我们会定义一个地图类CMap,它可能有以下成员

Private:

Int m_MapWidth; //地图的TILE横向数量

Int m_MapHeight; //地图的TILE纵向数量

Int m_TileWidth; //Tile的像素宽

Int m_TileHeight; //Tile的像素高

SMapTile* m_pTileArray; //TILE数组指针

Vector<SMapElement> m_MapElementVec[3]; //介与各层间的元素数组

Public:

BOOL CreateNewMap(int vWidth,int vHeight,); //创建地图,动态为TILE数组申请内存并初始化,如m_pTileArray = new SMapTile[vWidth*vHeight];

VOID SetTilePicture(int vTileX,int vTileY,int vLayerIndex,int vPictureIndex); //为指定的TILE的指定层设置图片索引

VOID AddElement(int vLayerIndex,int vPosX,int vPosY,int vPictureIndex); //放入元素

VOID RenderMap(int vLeft,int vTop,int vWidth,int vHeight); //显示场景中处于vLeft, vTop, vWidth, vHeight区块的地图。 这个理所应当就是屏幕矩形了。

VOID RenderElement(int vLayerIndex, int vLeft,int vTop,int vWidth,int vHeight); //显示处于对应矩形中的元素

那么,怎么能够显示任意区块呢?这个就是2D场景漫游了。

首先,我们先创建一个地图,假设设为200*100TILE横纵向数量。并设置TILE像素高,宽为48*48。这样我们就知道场景有多大了。一个屏幕一般为800*600大小。

200*48 * 100 *48 / (800*600) = 96. 嗯,貌似还可以。

我们在RenderMap函数中应能够正确的处理TILE和元素的渲染。下面是实现过程。

我们假设有全局对象C2DPictureManage G_PictureManage;

VOID CMap::RenderMap(int vLeft,int vTop,int vWidth,int vHeight)

{

Int TileX = vLeft / m_TileWidth ; //计算格子位置

Int TileIY = vTop / m_ TileHeight;

Int OffSetX = vLeft% m_TileWidth; //计算偏移

Int OffSetY = vTop % m_ TileHeight;

//计算从哪开始贴图

Int Left = OffSetX > 0 ? (OffSetX - m_TileWidth) : 0;

Int Top = OffSetY > 0 ? (OffSetY - m_TileHeight) : 0;

//计算总共多少TILE

Int TileNumX = OffSetX > 0 ? (vWidth / m_TileWidth +1) : (vWidth / m_TileWidth;)

Int TileNumY = OffSetY > 0 ? (vHeight / m_ TileHeight +1) : (vHeight / m_ TileHeight;)

//背景

For(int I = 0 ; I < TileNumY; I ++)

For(int J = 0 ; j < TileNumX ; j ++)

{

Int TileIndex = I * m_MapWidth + j;

Int PictureIndex = m_pTileArray[TileIndex]. Picture[0];

If(PictureIndex >= 0)

{

G_PictureManage. ShowPicture (PictureIndex, Left + j* m_TileWidth , Top + i* m_TileHeight, m_TileWidth, m_TileHeight);

}

}

//背间与远建筑层间元素

RenderElement(0, vLeft, vTop, vWidth, vHeight);

//远建筑层

For(int I = 0 ; I < TileNumY; I ++)

For(int J = 0 ; j < TileNumX ; j ++)

{

Int TileIndex = I * m_MapWidth + j;

PictureIndex = m_pTileArray[TileIndex]. Picture[1];

If(PictureIndex >= 0)

{

G_PictureManage. ShowPicture (PictureIndex, Left + j* m_TileWidth , Top + i* m_TileHeight, m_TileWidth, m_TileHeight);

}

}

//远建筑层与近建筑层间元素

RenderElement(1, vLeft, vTop, vWidth, vHeight);

//近建筑层

For(int I = 0 ; I < TileNumY; I ++)

For(int J = 0 ; j < TileNumX ; j ++)

{

Int TileIndex = I * m_MapWidth + j;

PictureIndex = m_pTileArray[TileIndex]. Picture[2];

If(PictureIndex >= 0)

{

G_PictureManage. ShowPicture (PictureIndex, Left + j* m_TileWidth , Top + i* m_TileHeight, m_TileWidth, m_TileHeight);

}

}

//近建筑层上元素

RenderElement(2, vLeft, vTop, vWidth, vHeight);

}

//显示处于对应屏幕矩形中的元素

VOID CMap::RenderElement(int vLayerIndex, int vLeft,int vTop,int vWidth,int vHeight)

{

//取得数量

Int size = m_MapElementVec[vLayerIndex].size();

For(int I = 0 ; I < size ; i++)

{

//图片索引

Int PictureIndex = m_MapElementVec[vLayerIndex][i]. mPictureIndex;

If(PictureIndex > 0)

{

C2DPicture* tpPicture = G_PictureManage.GetPicture(PictureIndex);

If(tpPicture)

{

Int Left = m_MapElementVec[vLayerIndex][i].mPosX;

Int Top = m_MapElementVec[vLayerIndex][i].mPosY;

Int Right = Left + tpPicture-> GetImageWidth();

Int Bottom = Top + tpPicture-> GetImageHeight ();

//判断与格子是否有交集

If(Left > (vLeft + vWidth))continue;

If(Top > (vTop + vHeight))continue;

If(Right < (vLeft))continue;

If(Bottom < (vTop))continue;

//如果有交集,则渲染

G_PictureManage. ShowPicture (PictureIndex, m_MapElementVec[vLayerIndex][i].mPosX - Left , m_MapElementVec[vLayerIndex][i].mPosY - Top);

}
}
}

}

以上是核心显示代码,为了测试,您可以写一个MFC程序并完善SetTilePictureAddElement等函数。通过鼠标消息处理来增加对应的TILE和元素,并通过键盘移动来改变屏幕的矩形。来制做一个可以漫游的简单的场景编辑器。

OK,2D横纵版的场景原理讲述完了。

斜视角的地图:

斜视角地图一般分为两类:45度角和30度角的。理解起来就是45度角的TILE是正方形, 30度角的TILE是宽高比为21. 45度角的斜视角拼合会感觉眼角立体感陡一些,用得较少,一般都是30度的,

1.2

如图所示,所有的TILE中图片都是斜30度的。

其实从绘制上与之前讲的横纵绘制并没有什么不同,算法不需要什么大的改动。但是在场景移动时。加上向左上30度移动。向左下30度移动。向右下30度移动,向右上30度移动。这样就能产生立体感了。

一般视角会随着主角人物的移动来漫游,人物移动,带动屏幕矩形移动。如果要实现斜视角。则一般为人物八个方向的图片。然后在移动时通过人物的位置来取得屏幕在整个地图的矩形位置,然后绘制地图就行了。

好了,基本的原理都讲解完了,看起来不难。但需要您亲自动手来实现它。开始吧,祝好运!