一个学生问的问题,借机做了个文档。发到博客。
2D横纵版与斜视角游戏地图
开发原理
作者 Honghaier
QQ:285421210
日期: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横纵版场景。每个TILE是45 * 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*100的TILE横纵向数量。并设置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程序并完善SetTilePicture,AddElement等函数。通过鼠标消息处理来增加对应的TILE和元素,并通过键盘移动来改变屏幕的矩形。来制做一个可以漫游的简单的场景编辑器。
OK,2D横纵版的场景原理讲述完了。
斜视角的地图:
斜视角地图一般分为两类:45度角和30度角的。理解起来就是45度角的TILE是正方形, 30度角的TILE是宽高比为2:1. 45度角的斜视角拼合会感觉眼角立体感陡一些,用得较少,一般都是30度的,
图1.2
如图所示,所有的TILE中图片都是斜30度的。
其实从绘制上与之前讲的横纵绘制并没有什么不同,算法不需要什么大的改动。但是在场景移动时。加上向左上30度移动。向左下30度移动。向右下30度移动,向右上30度移动。这样就能产生立体感了。
一般视角会随着主角人物的移动来漫游,人物移动,带动屏幕矩形移动。如果要实现斜视角。则一般为人物八个方向的图片。然后在移动时通过人物的位置来取得屏幕在整个地图的矩形位置,然后绘制地图就行了。
好了,基本的原理都讲解完了,看起来不难。但需要您亲自动手来实现它。开始吧,祝好运!