SDL游戏教程第七课 地图 (Maps)

 

 SDL游戏教程第七课 地图 (Maps)

    本系列教程来自Dev Hub

英文原文地址: http://www.sdltutorials.com/sdl-maps/

 

就像我上一次的课程陈述的那样,我们将要去看一看怎样去做一个基于(tile based)的地图类。除了简单的地图,我们将要用地图去创建许多区域。虽然我们可以创建一个巨型的地图,但管理许多小地图跟容易,并且这样也使得制作块地图(tiling maps)变成可能。顺便说一句,跳到SDL Image tutorial,如果你还没有学习这个课程。我们将要停止使用SDL_LoadBMP,转而使用SDL Image。不说那么多废话了,让我们开始把。

 


已知的Bug: 在本课程里,镜头设定反了,但在SDL 碰撞检测 课程里是正常的。本课程里还没有跟正这个错误,要查看修正后的内容,看看SDL 碰撞检测 的课程。

 

我们将需要准备若干文件, Define.h, CArea.h, CArea.cpp, CMap.h, CMap.cpp, CTile.h, 和 CTile.cpp。因此,创建这些空文件。我要得到一个区域的对象,这个区域对象将会有许多的子地图被存在一个vector容器里(就像许多实体在一个vector容器里一样)。在这些地图对象里,每个地图对象各自有一个小地图快的vector容器。一大片的地图块组成了地图,并且一大片的地图组成了一个区域。

7-area.png

让我们从Define.h开始吧!这个文件将要存一些预定义常量,比如说地图的宽和高,屏幕的宽和高,和块的大小。这些值将会是我们制作地图和其他的东西要用的。把下列面的代码加到define.h里:

 

#ifndef _DEFINE_H_
    
#define  _DEFINE_H_

#define  MAP_WIDTH   40
#define  MAP_HEIGHT  40

#define  TILE_SIZE   16

#define  WWIDTH      640
#define  WHEIGHT     480

#endif

 

我们等一会回来看看这些值的是什么意义,WWIDTH 和 WHEIGHT 是SDL_SetVideoMode用到的值。所以打开CApp_Init,改变这个函数像下面这样:

if ((Surf_Display  =  SDL_SetVideoMode(WWIDTH, WHEIGHT,  32 , SDL_HWSURFACE  |  SDL_DOUBLEBUF))  ==  NULL) {
    
return   false ;
}

很简单吧。接下来,打开CTile.h。这个类将在地图上定一个块。在游戏术语里,一个块就是一个块我们画在屏幕上的图形。回忆一下那个动画课程,每一帧Yoshi都可以被认为是一个块。因此当我们说一个地图有许多块组成,我们就在使用这些块并在一序列的格子里重复。

 

因为我们有整个一个图像包含了我们需要的所有的块,我们只需要加载一个图像,而不是每个块一个图像。每个块因此需要有一些属性。最明显的是那个块要被使用。让我看一下一个地图块的例子:

7-tileset.png

 

你会注意到在最做上角有一个为0的ID,往左边增加了1。这个被叫做Tile ID。通过使用这个ID,和每个块的大小(TILE_SIZE),我们可以知道那个块要画在屏幕上。接下来一个比较重要的东西是TypeID。这个东西决定块是什么类型的。一些例子也许会,不可以见(块不画出来),障碍物(像一堵墙),或者是地面(玩家可以在上面走动)。你可以定义任何你想要的TypeID,但这是最基本的(其他一些例子可以是水,火,冰,等等)这是很基本的,这个块类仅仅有两个成员(对于现在,接下来它将会包含动画块的信息)。那么让我们,看看CTile.h里面吧:

#ifndef _CTILE_H_
    
#define  _CTILE_H_

enum  {
    TILE_TYPE_NONE 
=   0 ,

    TILE_TYPE_NORMAL,
    TILE_TYPE_BLOCK
};

class  CTile {
    
public :
        
int      TileID;
        
int      TypeID;

    
public :
        CTile();
};

#endif

 

 

注意大正在被使用的枚举类型没,这让我们对TypeID同时进行赋值或检查。如果我用TypeID == TILE_TYPE_BLOCK,我可以注意到在我的代码里我在检测一个块的类型。TypeID == 2 不是那么好理解。如果你从来没有用过枚举类型,就把他们当做常量好了。常量值是不能改变。开始的这些值也定义了其他的东西,注意到没有,我把TILE_TYPE_NONE的值定义为0。剩下的这些量会自动被以1为增量的值依次赋值。

现在,打开CTile.cpp,完成下面的代码:

#include  " CTile.h "

CTile::CTile() {
    TileID 
=   0 ;
    TypeID 
=  TILE_TYPE_NONE;
}

 

很好。接下来,让我们看一下做一些地图吧。首先我们需要考虑一下我们的地图,一个文本文件。因此我们需要拿出一个文件格式来,那样women可以很容易的在程序外编辑我们的地图。也许以后你可以创建一个地图编辑器。

每个地图都有宽和高,块的数目。因此一个10X10,将会有100个地图块。 我们将会把我们所有的地图都做成宽和高都是相同的。因此我们不需在地图定义它们(MAP_WIDTH 和 MAP_HEIGHT)。我们需要增加的部分是为每个单独的块增加TileID和TypeID。我们将要使用我开始拿出来的文件格式,并且相对来说比较简单。看看5X5的地图例子:

0:0 0:0 0:0 0:0 0:0
1:0 1:0 1:0 0:0 0:0
1:0 1:0 1:0 0:0 0:0
1:0 1:0 1:0 0:0 0:0
1:0 1:0 1:0 0:0 0:0

每个在文件里的块包括0:0,有效的TitleID:TypeID。一个空格是块与块之间的分割符。 概念性的地图绘制会像这样:

7-map.png

对于本课程我们将会使用40X40的地图, 那是一个不错的大小。为了不让你去拷贝地图例子,打开下面的连接:

Example Map

 

首先,创建一个一个文件夹和你的EXE文件相同文件名:maps。这个文件夹是所有你的地图存放的地方。以1.map的文件名保存地图。再创建另外一个叫做tilesets文件夹在你EXE文件所在的同一个目录下面,以1.png的文件保存。

现在,我们有了地图的文件格式了,让我们开始设计绘制类吧。打开CMap.h:

#ifndef _CMAP_H_
    
#define  _CMAP_H_

#include 
< SDL.h >
#include 
< vector >

#include 
" CTile.h "
#include 
" CSurface.h "

class  CMap {
    
public :
        SDL_Surface
*  Surf_Tileset;

    
private :
        std::vector
< CTile >  TileList;

    
public :
        CMap();

    
public :
        
bool  OnLoad( char *  File);

        
void  OnRender(SDL_Surface *  Surf_Display,  int  MapX,  int  MapY);
};

#endif

 

一些基本的函数再这里,OnLoad和OnRender。OnLoad就像你期望的那样,从一个文件里加载地图, 构成TileList。OnRender也像你期望的那样,把每个块放到屏幕并且用Surf_Tileset绘制它们。让我们定义者秀儿函数怎么做吧,现在打开CMap.cpp:

#include  " CMap.h "

CMap::CMap() {
    Surf_Tileset 
=  NULL;
}

bool  CMap::OnLoad( char *  File) {
    TileList.clear();

    FILE
*  FileHandle  =  fopen(File,  " r " );

    
if (FileHandle  ==  NULL) {
        
return   false ;
    }

    
for ( int  Y  =   0 ;Y  <  MAP_HEIGHT;Y ++ ) {
        
for ( int  X  =   0 ;X  <  MAP_WIDTH;X ++ ) {
            CTile tempTile;

            fscanf(FileHandle, 
" %d:%d  " & tempTile.TileID,  & tempTile.TypeID);

            TileList.push_back(tempTile);
        }
        fscanf(FileHandle, 
"" n " );
    }

    fclose(FileHandle);

    
return   true ;
}

void  CMap::OnRender(SDL_Surface *  Surf_Display,  int  MapX,  int  MapY) {
    
if (Surf_Tileset  ==  NULL)  return ;

    
int  TilesetWidth   =  Surf_Tileset -> /  TILE_SIZE;
    
int  TilesetHeight  =  Surf_Tileset -> /  TILE_SIZE;

    
int  ID  =   0 ;

    
for ( int  Y  =   0 ;Y  <  MAP_HEIGHT;Y ++ ) {
        
for ( int  X  =   0 ;X  <  MAP_WIDTH;X ++ ) {
            
if (TileList[ID].TypeID  ==  TILE_TYPE_NONE) {
                ID
++ ;
                
continue ;
            }

            
int  tX  =  MapX  +  (X  *  TILE_SIZE);
            
int  tY  =  MapY  +  (Y  *  TILE_SIZE);

            
int  TilesetX  =  (TileList[ID].TileID  %  TilesetWidth)  *  TILE_SIZE;
            
int  TilesetY  =  (TileList[ID].TileID  /  TilesetHeight)  *  TILE_SIZE;

            CSurface::OnDraw(Surf_Display, Surf_Tileset, tX, tY, TilesetX, TilesetY, TILE_SIZE, TILE_SIZE);

            ID
++ ;
        }
    }
}
 

 

Bug 修正:这里有一个循环的continue  BUG,我们因该在执行continue语句之前对ID递增1。谢谢Zakalwe帮助找到了这个BUG。

 

让我们从最上面的开始吧。非常明显,构造函数将Tileset设置为NULL(空) 。接下来我们有了OnLoad函数。首先,我们清楚任何老的块,如果不那样的话就会加在两次,但没有两倍的块,不能有效的加载新地图。当我们打开了一个FileHandle(文件句柄),尝试着去打开需要加载的地图。现在,我们遍历地图文件并且取出每个块。最内层的循环是X坐标的循环,从最左边的块到最右边的块。注意一下这个循环是做什么的,因为它也被用在OnRender函数里。整个循环从最左上面的块遍历到最右底下的的块。一次一个快。在这两个循环里我们创建了临时块,并且加载文件信息到里面去。我们然后把这个块存到我们的TileList里。我们关闭这个文件句柄,那么我们完成了。

接下来,我们做一个绘制地图的函数。注意到了MapX和MapY参数。这两个参数告诉绘制地图的函数在屏幕的那个位置绘制。这个是为以后更方便的移动地图。函数一开始,我们检查tileset的有效性。因为我们要直接访问tileset并且不想引起崩溃。我们然后从块结构里读取TilesetWidth和TilesetHeight。这是很重要的,因为我们需要知道一个tileset包含有多少个块,不是它实际的宽和高。那种方式我们可以通过TileID匹配一个tileset。因此,一个包含了宽和高的Tileset,2X2将会有4个块在块集里,但实际上有32X32的像素。因此,一个TileID是0匹配的是第一个块,一个TileID是1将会是接下来的一个,以此类推。TileID在下一行从左到右继续重复。

OK,接下来我们再一次看看这个循环,遍历每个块(请注意在循环里的X和Y坐标也是指块的数量,不是指实际的像素)这一次,不管怎样我们定义了一个ID变量。这个ID每趟循环递增1,并且允许我们都去在地图里的没一个块。因此,我们通过TypeID首先检查这个块是不是要绘制。如果块是TILE_TYPE_NONE,我们跳过这个块并继续。之后我们计算出要在屏幕的哪个位置绘制这个块。这是通过用MapX和MapY来实现(有效的偏移坐标,因为他们从0,0偏移到地图的某一位置,就像是让地图移动一样),并且在循环里处理X和Y坐标。我们不得不转换这些X和Y坐标回像素坐标,通过乘以每个块的大小来实现。

除开那些,我们要对Tileset做一些事情了。我们要做的事情是计算在Tileset里得到合适的块。这是先通过获取块的TileID,然后转换到一个块的坐标来完成的。在这里给一点小小的解释吧。我们有2X2的块集,一个为1的TileID。计算出x坐标,我们会1%2,还是1。当我们把那个数乘以TILE_SIZE,我们得到了16。这个是那个块的正确坐标。同样的对于Y,我们是让1/2,得到0.5。由于这个是一个整数操作,.5被自动舍弃了。因此,我们只得到了0。同样是正确的行坐标。现在,我们有一个TileID是2的块。2%2=0, 和2/2=1。看看X是怎样重复的?0,1,0,1...并且,注意Y是怎样增长当它每一次穿过Tileset的Width的?0,0,0,1,1。我希望我的解释是清晰的,这些内容有时有点不好解释。

接下来,我们要实际的把块绘制到屏幕上通过使用我们刚才计算好的坐标,然后增加ID来访问下一个块。这里有一小点需要注意,我们可以为了追求速度,创建一个OnRender_Cache函数来执行同样的操作,但是会在CMap类定义的Surface里绘制图形。比如SDL_Surface* Surf_Map。然后,只有OnRender函数直接在Surf_Map里渲染操作。但也注意一下,那个方法在以后的工作里是没有呢必要的当我们想要让块动起来。

OK, 太酷了!这仅仅是一小点要一起做的事情,但请不要烦躁! 那真的不是很糟糕当你掌握了这些事情以后。我现在想要说我们可很好的创建地图对象并且绘制地图。这个些东西,非常适合于像Mario和Megaman这样的游戏,因为这样的游戏只有一个大地图。但是对像Zelda和Metroid这样的游戏,这种方式就不太适合了。这就是为什么要引入Areas来了。就像地图文件有格式一样,每个区域有它自己的文件和文件格式。每个区域有一个块集要加载,这个区域的尺寸,和区域里的地图也要被加载。下面是我们要使用的区域:

./tilesets/1.png
3
./maps/1.map ./maps/1.map ./maps/1.map
./maps/1.map ./maps/1.map ./maps/1.map
./maps/1.map ./maps/1.map ./maps/1.map

保存这些内容到一个叫做1.area的文件里, 并且保存到你的maps目录下面。就像地图,这个区域会是块地图。我不会在详细解释了因为我认为你现在应该有了主意了。

打开CArea.h并且:

#ifndef _CAREA_H_
    
#define  _CAREA_H_

#include 
" CMap.h "

class  CArea {
    
public :
        
static  CArea            AreaControl;

    
public :
        std::vector
< CMap >        MapList;

    
private :
        
int                      AreaSize;

        SDL_Surface
*         Surf_Tileset;

    
public :
        CArea();

        
bool     OnLoad( char *  File);

        
void     OnRender(SDL_Surface *  Surf_Display,  int  CameraX,  int  CameraY);

        
void     OnCleanup();
};

#endif

 

这个类将会和Map类在某些方面有些类似,在另一些地方又有不同。类似的,我们让MapList来存储我们的地图,就像Map类有一个块的列表一样。当然,也有Load和Render函数。现在说说不同的地方吧。我们定义了一个静态的AreaControl,很像Entities有一个静态控制变量一样。这样就允许我们用这个对象,在任何一个类里操作一块区域(Area)。接下来,我们定义了AreaSize,代表了地图的数量。我们假定区域总是正方形的,因此AreaSize为3,代表的是3X3的区域。如果你想要,但我认为是不必要的,你也可以定义AreaWidth和AreaHeight。接下来,我们为Tileset定义了一个表面(surface)。你也许注意到了在CMap类里,我们实际上从来不在里面加载地图。那是因为Area为我们做那件事情,然后在传那个加载地图的指针到CMap类。那中方式下,每个地图不自己为自己加载一个块集(tileset),但是事实上所有的地图共享同一个块集(tileset)。另一方面,如果你想要每个地图有它自己的块集,你可以很容易的修改这个类以达到这个目的。

OK, 现在打开CArea.cpp:

#include  " CArea.h "

CArea CArea::AreaControl;

CArea::CArea() {
    AreaSize 
=   0 ;
}

bool  CArea::OnLoad( char *  File) {
    MapList.clear();

    FILE
*  FileHandle  =  fopen(File,  " r " );

    
if (FileHandle  ==  NULL) {
        
return   false ;
    }

    
char  TilesetFile[ 255 ];

    fscanf(FileHandle, 
" %s " n " , TilesetFile);

    
if ((Surf_Tileset  =  CSurface::OnLoad(TilesetFile))  ==   false ) {
        fclose(FileHandle);

        
return   false ;
    }

    fscanf(FileHandle, 
" %d " n " , &AreaSize);

    
for ( int  X  =   0 ;X  <  AreaSize;X ++ ) {
        
for ( int  Y  =   0 ;Y  <  AreaSize;Y ++ ) {
            
char  MapFile[ 255 ];

            fscanf(FileHandle, 
" %s  " , MapFile);

            CMap tempMap;
            
if (tempMap.OnLoad(MapFile)  ==   false ) {
                fclose(FileHandle);

                
return   false ;
            }

            tempMap.Surf_Tileset 
=  Surf_Tileset;

            MapList.push_back(tempMap);
        }
        fscanf(FileHandle, 
"" n " );
    }

    fclose(FileHandle);

    
return   true ;
}

void  CArea::OnRender(SDL_Surface *  Surf_Display,  int  CameraX,  int  CameraY) {
    
int  MapWidth   =  MAP_WIDTH  *  TILE_SIZE;
    
int  MapHeight  =  MAP_HEIGHT  *  TILE_SIZE;

    
int  FirstID  =   - CameraX  /  MapWidth;
        FirstID 
=  FirstID  +  (( - CameraY  /  MapHeight)  *  AreaSize);

    
for ( int  i  =   0 ;i  <   4 ;i ++ ) {
        
int  ID  =  FirstID  +  ((i  /   2 *  AreaSize)  +  (i  %   2 );

        
if (ID  <   0   ||  ID  >=  MapList.size())  continue ;

        
int  X  =  ((ID  %  AreaSize)  *  MapWidth)  +  CameraX;
        
int  Y  =  ((ID  /  AreaSize)  *  MapHeight)  +  CameraY;

        MapList[ID].OnRender(Surf_Display, X, Y);
    }
}

void  CArea::OnCleanup() {
    
if (Surf_Tileset) {
        SDL_FreeSurface(Surf_Tileset);
    }

    MapList.clear();
}
 

很快的,从上面的开始,我们声明了静态对象,然后将AreaSize设成0。然后我们定义了Load函数。它就像是在CMap里的Load函数一样,除了有一小点不同。我们要先加载tileset。我们试着加载块集到表面(surface)里,并且如果失败了返回false。我们然后加载Area的尺寸。OK,我们有了两个循环,就像在刚才在操作地图一样,会遍历并且读取每个地图的文件名。然后它会创建一个地图对象,加载地图,设置好块集准备使用,然后push到列表里。这是非常简单和直接的。

接下来,我们定义了Render函数。我们先计算实际的地图的宽和高以像素为单位。这可以让我们找到第一个要被绘制的地图。一些事情我要首先试着去做一些说明吧。由于一块区域可以是任意尺寸的,就像100X100的地图,我们不想处理所有的麻烦事并且绘制每个单独的地图。我们仅仅想绘制在屏幕区域可见的地图。对于我们的屏幕尺寸,640X480,一次只能有4幅地图是可见的(就像在角上的4幅地图)。因此我们要做的事情是计算第一个要获取的Map ID。接下来是绘制第一个地图。从第一个ID,我们知道接下来要绘制的3幅地图。第一幅地图右边临近的一幅地图,第一幅地图底下的一幅地图,和第一幅地图右下角的地图。那是怎样做到的?让我们看看下面的图像:|

7-area-camera.png

 

我们画了一个家伙,圆圈,在屏幕的正中央,用红方块表示。其他方块是每个地图,并且我们有一个镜头坐标为-700, -700。为什么是负方向?想想,屏幕自己不会正真的移动,所有其他的东西在动。所以,对于一个物体要向上移动,它必须沿着Y的负方向上增加坐标。对于X 坐标也是同样的。因此,为了到达第4幅地图,我们我们必须在负方向上移动。现在,注意标了灰色的地图,这些地图在用户视野里是不可见,所以它们不需要被绘制。找出第一个ID,在这种情况下是4,我们要用它来指定镜头坐标。我们转换这些镜头坐标到地图坐标。-(-700)/640(40*16, 回忆一下MapWidth的计算方法)我们达到了1(舍去小数)。这是在地图上的X坐标,但是我们还没有做完。我们然后计算在地图上的Y坐标,同样的-(-700)/640,但是我们把它乘以AreaSize。这是因为我们在用ID。所以,它变成了1*3, 就是3,并且加到第一次计算得到的1上,我们得到了4. 那么,你应该知道了在地图上的ID是怎么回事了吧。

OK, 这四幅地图我们都遍历了。由于我做了一个i < 4的循环,我们需要指明怎样加到第一个ID上去,真正的比标示出这四幅地图的ID。这首先是通过把第一个ID作为一个偏移。我们然后用i,我们在循环里的位置,处以2然后乘以区域的尺寸。这做了些什么呢?很想地图类,它创建了一个模式,0,0,1,1。同样的是使用了i%2的操作,创建了一个模式0,1,0,1。这给出了我们正确的模式,4,5,7,8。正好是要被绘制的地图。

我们做一点点检查以保证ID是好的,因为ID也许是不存在的。并且现在最后的计算(是的,我知道,一大堆的复杂计算)。它就像是我们计算如何从块集里获取每个块一样去做。它把一个ID转换成实际的像素坐标,然后偏移得到坐标通过使用镜头(使得它看起来像在移动)。最后我们绘制地图,传绘制的坐标过去以便去绘制它。

 

最后,我们定义了我们的清除函数,释放表面和地图。

多么棒的一个加载!我希望所有这些多种多样的计算没有让你迷惑。仅有2件基本的事情我们正要尝试去做,转换像素坐标到基于块的格子坐标。就和块那样,地图也是这样的。其他的事情是转换基于块的格子坐标回ID。

OK, 我们还没完成彻底的完工,仍然还有一些事情要去做(现在你看看到了为什本课程花了这么长的时间才完成!)。你也许注意到了,我把在CArea类里的Render函数的后两个参数叫做CameraX和CameraY。我们将要去做一个镜头类(camera class),它定义了可见的区域。这是我们将要去操纵移动地图的地方。

那么,我们创建2个新文件,CCamera.cpp和CCamera.h。先打开头文件:

#ifndef _CCAMERA_H_
    
#define  _CCAMERA_H_

#include 
< SDL.h >

#include 
" Define.h "

enum  {
    TARGET_MODE_NORMAL 
=   0 ,
    TARGET_MODE_CENTER
};

class  CCamera {
    
public :
        
static  CCamera CameraControl;

    
private :
        
int  X;
        
int  Y;

        
int *  TargetX;
        
int *  TargetY;

    
public :
        
int  TargetMode;

    
public :
        CCamera();

    
public :
        
void  OnMove( int  MoveX,  int  MoveY);

    
public :
        
int  GetX();
        
int  GetY();

    
public :
        
void  SetPos( int  X,  int  Y);

        
void  SetTarget( int *  X,  int *  Y);
};

#endif
 

 

首先,我们定义了一个控制成员对象,就像区域对象一样。然后我们定义了镜头在那里的坐标。我仍了一个额外的能力进去,那是聚焦物体的能力。例如,在Megaman游戏里镜头会聚焦Megaman自己。在那种情况下,当Megaman移动了镜头将会被自动的更新。因此,我们有两个指针,分别对应X坐标和Y坐标。如果这个两个指针是null,镜头会自动的回到镜头的位置。接下来,我们定义一下目标模式,对于现在,只有正常模式(镜头会在目标的做上角)或者是中心模式(将会让镜头指向目标的中心)。我认为这非常的简单。

我们然后定义了一些函数,第一个是OnMove,将会通过使用MoveX和MoveY来增加镜头的X和Y坐标。因此OnMove(0, -1)将会把镜头向上一个像素。然后我们定义了坐标的Get函数,并且能够去设置坐标和设置目标。

OK,现在打开CCamera.cpp:

#include  " CCamera.h "

CCamera CCamera::CameraControl;

CCamera::CCamera() {
    X 
=  Y  =   0 ;

    TargetX 
=  TargetY  =  NULL;

    TargetMode 
=  TARGET_MODE_NORMAL;
}

void  CCamera::OnMove( int  MoveX,  int  MoveY) {
    X 
+=  MoveX;
    Y 
+=  MoveY;
}

int  CCamera::GetX() {
    
if (TargetX  !=  NULL) {
        
if (TargetMode  ==  TARGET_MODE_CENTER) {
            
return   * TargetX  -  (WWIDTH  /   2 );
        }

        
return   * TargetX;
    }

    
return  X;
}

int  CCamera::GetY() {
    
if (TargetY  !=  NULL) {
        
if (TargetMode  ==  TARGET_MODE_CENTER) {
            
return   * TargetY  -  (WHEIGHT  /   2 );
        }

        
return   * TargetY;
    }

    
return  Y;
}

void  CCamera::SetPos( int  X,  int  Y) {
    
this -> =  X;
    
this -> =  Y;
}

void  CCamera::SetTarget( int *  X,  int *  Y) {
    TargetX 
=  X;
    TargetY 
=  Y;
}
 

 

从上到下,像我们以前一样,我们让静态的成员放在前面。我们然后让构造函数对一些成员变量赋初始值。就像我先前说的那样,OnMove函数会增加X和Y。然后我们定义GetX函数。它会检测看看我们是否有一个有效的目标,如果是有效的那么返回目标的坐标作为镜头的坐标,如果不是那么返回镜头的X坐标。对于中心模式,我们把屏幕的坐标处以2来寻找到屏幕的中心,然后从目标的坐标减去它。这将会让镜头对那个目标聚焦。我们然后定义了SetPos和SetTarget函数,它们的代码已经就能解释清楚了它们的功能了。

那么让我们把这些杂乱的东西整理到一起吧。打开CApp.h修改它使之包含新的头文件:

#include  " Define.h "

#include 
" CArea.h "
#include 
" CCamera.h "

 

再增加下面一个新的函数原型:

void  OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode);

我们将要去检查让我们用键盘来移动地图的事件。那么,打开CApp_OnEvent.cpp增加下面的函数:

void  CApp::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode) {
    
switch (sym) {
        
case  SDLK_UP:    CCamera::CameraControl.OnMove(  0 ,   5 );  break ;
        
case  SDLK_DOWN:  CCamera::CameraControl.OnMove(  0 - 5 );  break ;
        
case  SDLK_LEFT:  CCamera::CameraControl.OnMove(  5 ,   0 );  break ;
        
case  SDLK_RIGHT: CCamera::CameraControl.OnMove( - 5 ,   0 );  break ;

        
default : {
        }
    }
}
 

 

我们检查SDL 的按键状态,根据所按的键决定镜头的移动方向。接下,打开CApp_OnCleanup.cpp并且增加区域的cleanup函数:

CArea::AreaControl.OnCleanup();

 

打开CApp_OnRender.cpp并且也增加渲染调用:

CArea::AreaControl.OnRender(Surf_Display, CCamera::CameraControl.GetX(), CCamera::CameraControl.GetY());

 

注意,我们传递镜头的坐标到了OnRender函数。最后,打开CApp_OnInit.cpp:

if (CArea::AreaControl.OnLoad( " ./maps/1.area " ==   false ) {
    
return   false ;
}

SDL_EnableKeyRepeat(
1 , SDL_DEFAULT_REPEAT_INTERVAL  /   3 );

 

SDL_EnableKeyRepeat调用设置键盘的字符重复率。因此当我们按住一个键盘上的按键的时候,按键消息是被持续的发到的。但我们已经完成了。编译这些代码并且运行调试它。我希望那可以以期望的方式工作,这是一个非常长的课程。如果你有一些麻烦的话,请检查一下面的课程文件。并请让我知道如果由于某种原因,我漏掉了一些重要的代码。

SDL Maps - Tutorial Files:
Win32: Zip, Rar
Linux: Tar (Thanks Gaten), Binary (Thanks Thomas)

转载于:https://www.cnblogs.com/Henrya2/archive/2009/04/16/1420083.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值