• 380 •
C++游戏编程
• 355 •
第9章 地图编辑器
第9章 地图编辑器
在前面一章中,虽然把游戏做出来了。但是,它还存在一个很大的麻烦,即:除了重新编辑并编译代码外,场景是无法改变的。
要改变角色所处的场景,可以这样做:把运行游戏的代码编好并编译好;然后在运行游戏时,角色进入一个新场景则从文件里读取对应场景的数据到游戏;这个场景的描述文件由另外一个程序做成。习惯上,把这个“另外一个程序”称为“地图编辑器”。
很显然,利用“地图编辑器”来做场景是一个很明智的选择。利用它,美工可以做出各种各样的场景,而且场景的画面很美。
本章就来介绍如何做游戏的“地图编辑器”。
9.1 编辑
地图编辑器所要做的是编辑游戏的场景结构数据。根据前一章的说明,场景结构的成员可以分为七种类型:场景属性描述(大小与序号)、角色出现区域、开关区域、地面链表、怪物链表、对象链表和地形描述。
由于游戏的怪物实在太少,没必要在编辑器中设置它们的属性,而直接由游戏程序内定。所以,编辑器所要做的事共有六件,即:设置场景属性描述(大小与序号)、指定角色出现区域、指定开关区域、生成地面链表、生成对象链表和生成地形描述。
9.2 定义编辑类CEdit
后面的介绍都涉及到名为CEdit的类,为了更好地说明,这里先建立CEdit类。其CEdit.h的代码如下:
/*----------------------------------------------------
| CEdit.h
| 编辑类成员声明
| (c) mochsh,2004
------------------------------------------------------*/
#include
#include
#include "CDDraw.h"
#include "CDInput.h"
#include "CText.h"
#include "StructDefine.h"
#define WM_CREATESETSWITCHTRAITDIALOG WM_USER
class CEdit
{
private:
CDDraw
DDraw;//DirectDraw
CDInput
Input;//输入设备对象
CText
Text;//字符串对象
LPDIRECTDRAWSURFACE7 lpDDSPassageway;//通道
LPDIRECTDRAWSURFACE7 lpDDSGroundRC;//地面源表面
LPDIRECTDRAWSURFACE7 lpDDSHighTree;//大树源表面
LPDIRECTDRAWSURFACE7 lpDDSLowTree;//小树源表面
//开关区域指出的角色出现位置的显示标志源表面
LPDIRECTDRAWSURFACE7 lpDDSRoleNewArea;
//编辑的当前场景的角色出现区域源表面
LPDIRECTDRAWSURFACE7 lpDDSRoleAppearArea;
HWND
hWnd;//程序句柄
BOOL
bFullScr;//屏幕模式
LONG
ScreenW;//客户区的宽度
LONG
ScreenH;//客户区的高度
LONG
ColorBits;//图像颜色深度
LONG
LevelHeight;//显示行的高度
LONG
MinLevel;//最小显示等级
LONG
MaxLevel;//最高显示等级
LONG
MaxWObject;//对象矩形图片的最大宽度
LONG
MaxHObject;//对象矩形图片的最大高度
SCENE
Scene;//场景
POINT
ScrPos;//屏幕的位置
LONG
lAppState;//程序状态
LONG
lUpdateScreen; //是否需要更新后台及屏幕内容
LONG
lSaveState;//保存状态
LONG
lEditState;//编辑状态
DWORD
Time;//当前时间
BYTE
GRNKind;//选择的地面类型
BYTE
oldGRNKind;//旧地面类型
LONG
SfPointSceneID; //当前编辑的开关区域指向的场景序号
LONG
StartPosState; //当前编辑的开关区域指出的角色出现位置
LONG
SelectSwitch;//选择了哪个开关区域
OBJECT
* SelectObject;//选择了哪个对象
TCHAR
sceFilePath[MAX_PATH];//场景文件名
PKEY
KeyKB;//键盘状态指针
PKEY
KeyMouse;//鼠标状态指针(屏幕中的)
POINT
MouseScePos;//鼠标转化到场景中的位置
OBJECT
* ShowOb[100];//对象显示数组
public:
CEdit(void);
~CEdit();
//>>>>>>> 设置成员变量 >>>>>>>>>>>
//设置应用程序句柄
void Set_hWnd( HWND hwnd ) {hWnd = hwnd;}
void Set_bFullScr( BOOL bFullScreen )
{//设置屏幕模式
bFullScr = bFullScreen;
}
//设置客户区宽度
void Set_ScreenW( LONG ScrW ) {ScreenW = ScrW;}
//设置客户区高度
void Set_ScreenH( LONG ScrH ) {ScreenH = ScrH;}
void Set_ColorBits( LONG ColorBitCount )
{//设置颜色深度
ColorBits = ColorBitCount;
}
void Set_LevelHeight( LONG LevelH )
{//设置显示行的高度
LevelHeight = LevelH;
MaxLevel =(ScreenH+MaxHObject)/LevelHeight;
MinLevel =(-MaxHObject)/LevelHeight;
}
void Set_MaxWObject( LONG MaxWideObject )
{//设置对象图片的最大宽度
MaxWObject = MaxWideObject;
}
void Set_MaxHObject( LONG MaxHeightObject )
{//设置对象图片的最大高度
MaxHObject = MaxHeightObject;
MaxLevel =(ScreenH+MaxHObject)/LevelHeight;
MinLevel =(-MaxHObject)/LevelHeight;
}
//设置场景序号
void Set_SceneID( LONG SceID )
{
Scene.lSceneID = SceID;
}
//设置场景宽度
void Set_SceneW( LONG SceW )
{
//调整场景宽为80的整数倍
SceW = (SceW/80)*80;
Scene.SceneW = SceW;
}
//设置场景高度
void Set_SceneH( LONG SceH )
{
//调整场景高为80的整数倍
SceH = (SceH/80)*80;
Scene.SceneH = SceH;
}
//设置选择的地面类型
void Set_GRNKind( BYTE grnKind )
{
oldGRNKind = GRNKind;
GRNKind = grnKind;
}
void Restore_oldGRNKind( void )
{
GRNKind = oldGRNKind;
}
//设置选择的地面类型
void Set_lEditState( LONG EditState )
{
lEditState = EditState;
lUpdateScreen = 1;
}
//设置当前开关区域的场景指向
void Set_SfPointSceneID( LONG SceneID )
{
SfPointSceneID = SceneID;
}
//设置当前开关区域的角色出现位置
void Set_StartPosState( LONG PosState )
{
StartPosState = PosState;
}
//>>>>>>> 辅助成员函数 >>>>>>>>>>>
//获取一个矩形
RECT GetRect(LONG left,LONG top, LONG right,LONG bottom);
//>>>> 初始化DirectX成员函数 >>>>>>>
//载入游戏的图像数据
void LoadImageSurf( void );
//初始化DirectX(外部通过调用它来初始化游戏)
void InitDirectX( HWND _hWnd );
void RecordGroundDesc( GROUNDDESC *GrnDesc, SCENE *sce );
void RecordObject( OBJECT *object,SCENE *sce );
void NewScene( void );
void DeleteSceneData( SCENE scene );
//>>>>>>> 文件输入输出相关成员 >>>>>>>
BOOL GetSceneFileName( DWORD dwFlag );
void SaveSwitchField( FILE *file, SCENE *sce );
void LoadSwitchField( FILE *file, SCENE *sce );
void SaveGroundDesc( FILE *file, SCENE *sce );
void LoadGroundDesc( FILE *file, SCENE *sce );
void SaveObject( FILE *file, SCENE *sce );
void LoadObject( FILE *file, SCENE *sce );
void SaveTerrainSquare( FILE *file,SCENE *sce);
void LoadTerrainSquare( FILE *file,SCENE *sce);
void SaveSceneData( BOOL bSaveAs );
void LoadSceneData( void );
//>>>>>> 恢复需要恢复的成员变量>>>>>
void RestoreVariable( void );
//根据鼠标位置改变屏幕的位置
LONG CheckScrPos( void );
//>>>>>> 地面地编辑相关成员 >>>>>>>>
//根据指定地面描述获取对应地面方块的源位置
RECT GetSrcRect( GROUNDDESC *grn,BOOL bGetNew);
//根据指定的地面方块序号获得对应地面方块指针
GROUNDDESC * GetGrnPointer( LONG lN );
//地面地编辑
LONG GroundEdit( void );
//>>>>>> 对象编辑相关成员 >>>>>>>
//删除对象
void DeleteObject( OBJECT * object );
//新加对象
void NewObject( void );
//编辑对象
LONG ObjectEdit( void );
//>>>>>> 开关区域编辑相关成员 >>>>>>>
//删除开关区域
void DeleteSwitch( LONG n );
//新建开关区域
void NewSwitch( void );
//编辑开关区域
LONG SwitchFieldEdit( void );
//>>>>>>> 编辑角色出现区域 >>>>>>>
LONG RoleAppearAreaEdit( void );
//>>>>>> 生成地面 >>>>>>>>>>
void MakeGround( void );
//>>>>>> 对象显示链表的更新 >>>>>>>>>
void UpdateObjectsList( SCENE sce );
//>>>>>>> 地面上的以及文字的显示 >>>>>>>
void MakeEntity( void );
//>>>>>> 编辑器的进程 >>>>>>>>
BOOL EditProcess( void );
//>>>> 获取输入设备状态函数 >>>>>
void GetInputDeviceData( LPRECT lpClientRect );
//>>>> 更新屏幕成员函数 >>>>>>>>
void UpdateScreen( LPRECT lpClientRect );
//>>>> 获得保存状态 >>>>>>>
LONG Get_lSaveState( void ){return lSaveState;}
};
代码
分析
定性数据统计分析pdf销售业绩分析模板建筑结构震害分析销售进度分析表京东商城竞争战略分析
:
该类与CGame类很相似。其可以分为三个部分:初始化环境部分、场景数据的输入输出和场景数据的编辑。其主进程的运行方式和CGame类是相同的。
CEdit.h文件的头部声明了一个WM_CREATESETSWITCHTRAITDIALOG的宏。用于创建设置开关区域属性对话框。
9.3 场景属性描述的设置
当新建一个场景时,应该设置场景的宽、高和序号。CEdit类使用NewScene成员来新建一个场景。
新建一个场景过程中,需要做三件事情。第一件是,根据指定的场景宽、高和序号初始化场景数据结构成员。第二件是,根据场景的宽与高为地面方块链表开辟空间,并将地面方块的ID值初始化为非过度的方块。第三件是,生成与场景对应的地形描述数组。
调用NewScene成员后,将弹出如图9-0所示的对话框,以设置场景属性。
图9-0 设置场景属性的对话框
9.4 地面的编辑及相关
地面的编辑是地图编辑器的一个重点。本节即将介绍的内容有,地面方块的组成方式、选择地面编辑方块的方法和如何编辑地面。
9.4.1 地面的构成方式
游戏的地面由很多方块结合。地面方块的大小为80*80。要注意的是,不要混淆了“地面方块”和“地形方块”的概念。地形方块是用于寻找路径的地形描述,其大小为20*20。
按理说,既然地面由方块组成,则显示场景时应该出现一个一个的方块。但是,前一章中的地面显示却没有出现这样的情况。那是因为,在做这些方块的图片时,在方块的四个边上作了处理,这样的处理称为“过度处理”。
以什么方法实现“过度处理”,这得看美工的功底了。每个人都有自己的一套。但都遵循一个基本原则:方块结合后,看不到矩形框。
每个方块都有四条边,分为:左边(left)、上边(top)、右边(right)和底边(bottom)当方块处于地面类型间的过度区域时,其四条边上的类型是不全相同的。如图9-1所示。
图9-1 过度方块
由图9-1可以看出,该图的过度方块的左边和底边是类型B,上边和右边是类型A。要表示这个方块必须用至少五个值。四个对应于它的四条边的值,另一个是它在场景中的位置。
为了使地面表现出更高的杂乱性。当方块四条边的类型为一定值时,在源图像中可以给出多个这样的方块。如图9-1中的过度方块,如果给出两张那样的过度图片,则可以使出现这样的过度方块的地方表现出不同的效果。
当两个方块的四条边的类型值相同时,称这样的两个方块为“相同过度方式的方块”,以方便说明。
本
书
关于书的成语关于读书的排比句社区图书漂流公约怎么写关于读书的小报汉书pdf
的游戏的地面类型源图像文件是这样的:对于过度方块,给出两个不同的源图片。对于非过度方块,给出十个不同的源图片。
所以,描述一个方块还得添加一个值,这个值用于区分相同过度方式的方块的不同源图片。
最后为地面方块定义一个数据结构。如下:
typedef struct GROUNDDESC {
WORD Index;//在场景中的序号
struct
{
BYTE
left; //矩形左边的地面类型
BYTE
top; //矩形上边的地面类型
BYTE
right; //矩形右边的地面类型
BYTE
bottom; //矩形底边的地面类型
BYTE
Index; //相同过度方式图片序号
} GroundID;
GROUNDDESC * next; //节点
} GROUNDDESC;
地面方块在场景中的序号是这样得到的:从场景的左上角方块开始,从左到右的方向一一为每个方块编号。左上角的方块编号是0,往右是1,2,3…。当到达每一行的最右边的方块时,跳到下一行的最左边的方块编号。所以,从方块的场景编号中可以知道方块在场景中的位置。
本书的游戏只使用两种地面类型,即草地和泥地。当方块的一条边上是草地时,其数据结构的对应成员的值是1,当方块的一条边上是泥地时,其数据结构的对应成员的值是0。
9.4.2 选择编辑方块
在编辑器中,可以通过单击鼠标左键来改变鼠标指针指出的地面方块的四条边类型,以编辑出更美观的地面。此时,鼠标指出的地面方块有四个,这些方块称为“编辑方块”。如图9-2所示。
在图9-2中,鼠标单击的位置处于右下角的方块。可以认为鼠标同时选择了这个方块的左上角、左下角和右上角三个方块。从图中可以看出,只要以需要的地面类型替换这四个方块的公共边的类型,就可以实现地面的编辑。比如说,以黑色类型替换这些公共边后,就变为图9-3所示的地面。
图 9-2 鼠标选择的四个方块 图9-3 替换公共边后的显示
当然,选择编辑方块的方法是多样的。除了图9-2所示的方法外,还有图9-4所示的选择方法。
图 9-4 第二种选择编辑方块方法
图9-2中,共有四个编辑方块。而根据图9-4中的选择方法,需要选出五个编辑方块。图9-4中的编辑方块是这样替换地面类型的:替换鼠标选中方块的四条边的类型。
9.4.3 编辑地面
这里选择图9-2所指出的方法来选择编辑方块。根据这个方法,CEdit类定义了GroundEdit成员函数,其具体代码如下:
/**************************************************
* 函数名:GroundEdit(...)
* 功能:地面编辑
* (c) mochsh, 2004
***************************************************/
LONG CEdit::GroundEdit( void )
{
//还没有建立场景
if( Scene.SceneH==0 || Scene.SceneH==0 )
return 0;
//选择的操作不是地面编辑
if( lEditState!=0 )
return 0;
//没有按下并弹起鼠标左键
if( !(KeyMouse[0].Key==NULL && KeyMouse[1].Key==DIMOFS_BUTTON0) )
return 0;
//修改了上次保存的数据
if( lSaveState!=0 )
lSaveState = 2;
//>>>>>>>> 求鼠标单击的是哪个单元 >>>>>>>>>>
LONG x = (LONG)( (MouseScePos.x+40)/80 );
LONG y = (LONG)( (MouseScePos.y+40)/80 );
LONG wN = Scene.SceneW/80;
LONG hN = Scene.SceneH/80;
// 获得选择中方块的场景序号
LONG grn_Index = y*wN+x;
//>>>> 求鼠标指出的编辑方块(共四个) >>>>>>>
GROUNDDESC * LeftTop = NULL, * LeftBottom = NULL,
* RightTop = NULL, * RightBottom = NULL;
//将所选择的方块作为右下角的方块指针
RightBottom = GetGrnPointer( grn_Index );
//不是每一行的第1个格子
if( x>0 )//获得左下角的方块指针
LeftBottom =GetGrnPointer(grn_Index-1);
//不是第一行
if( y>0 ) {
//获得右上角的方块指针
RightTop = GetGrnPointer(grn_Index-wN);
if( x>0 )//获得左上角的方块指针
LeftTop =GetGrnPointer(grn_Index-wN-1);
}
//>>>>>>>>> 调节四个编辑方块的类型 >>>>>>>>>>
//左上角方块
if( LeftTop != NULL ) {
LeftTop->GroundID.right = GRNKind;
LeftTop->GroundID.bottom = GRNKind;
GetSrcRect( LeftTop,TRUE );
}
//右上角方块
if( RightTop != NULL ) {
RightTop->GroundID.left = GRNKind;
RightTop->GroundID.bottom = GRNKind;
GetSrcRect( RightTop,TRUE );
}
//右下角方块
if( RightBottom != NULL ) {
RightBottom->GroundID.left = GRNKind;
RightBottom->GroundID.top = GRNKind;
GetSrcRect( RightBottom,TRUE );
}
//左下角方块
if( LeftBottom != NULL ) {
LeftBottom->GroundID.top = GRNKind;
LeftBottom->GroundID.right = GRNKind;
GetSrcRect( LeftBottom,TRUE );
}
return 1;
}
代码分析:
该成员代码可以分为四个部分:检测是否是地面编辑操作、获得鼠标选中的方块、获得四个编辑方块和替换编辑方块的类型。
CEdit类采用一个成员变量lEditState来判断选择了什么编辑操作。当lEditState的值是0时,表示编辑地面。当值是1时表示编辑大树。当值是2时,表示编辑小树。当值是3时,表示编辑开关区域。当值是4时,表示编辑角色出现区域。
在获取编辑方块部分中,当选中的方块处于场景的左边界上时,没有左下角和左上角的编辑方块。当选中的方块处于上边界时,没有左上角和右上角的编辑方块。当选中的方块处于场景左上角时,没有其他三个编辑方块。编辑方块是根据序号来定位的。在这部分代码中,调用到了成员GetGrnPointer。该成员的功能是根据给定的序号来取得对应地面方块描述结构的指针。其代码较简单,可以参看光盘中的代码和说明。
在替换编辑方块的类型部分中,每进行一次替换类型操作都调用成员函数GetSrcRect来指定该方块对应于源图像中的位置。
在成员函数的最后,返回1的值。表示需要更新屏幕内容。
9.4.4 获得地面方块的源位置
在地面编辑成员GroundEdit中,调用到了GetSrcRect,以获取方块对应于源图像表面中的位置。其代码如下:
/**************************************************
* 函数名:GetSrcRect(...)
* 功能:获得地面方块对应于源表面的位置
* (c) mochsh, 2004
***************************************************/
RECT CEdit::GetSrcRect( GROUNDDESC * grn, BOOL bGetNew )
{
RECT Rect = {0,0,0,0};
if(grn==NULL)
return Rect;
BYTE Value
= 0;
LONG left
= grn->GroundID.left;
LONG top
= grn->GroundID.top;
LONG right
= grn->GroundID.right;
LONG bottom = grn->GroundID.bottom;
//是泥地方块
if( (left+top+right+bottom)==0 )
Value = 255;
//是草地方块
else if( (left+top+right+bottom)==4 )
Value = 255;
//是泥地与草地的混合方块
else {
if( left )
Value |= 8;//左边界是草地
if( top )
Value |= 4;//上边界是草地
if( right )
Value |= 2;//右边界是草地
if( bottom )Value |= 1;//底边界是草地
}
//算出在源图片中的位置
long lNum;
//非过度方块
if( Value==255 ) {
//随机获得序号
if( bGetNew )
{
lNum = 10*left + random(10);
grn->GroundID.Index = (BYTE)lNum;
}
else//使用已有序号
lNum = grn->GroundID.Index;
Rect.left = 80*lNum;
Rect.right = 80*lNum + 80;
Rect.top = 0;
Rect.bottom = 80;
}
//是过度方块
else {
//随机获得序号
if( bGetNew )
{
lNum = 2*(Value-1)+random(2);
grn->GroundID.Index = (BYTE)lNum;
}
else//使用已有序号
lNum = grn->GroundID.Index;
Rect.left = 80*lNum;
Rect.right = 80*lNum + 80;
Rect.top = 80;
Rect.bottom = Rect.top + 80;
}
return Rect;
}
代码分析:
该成员有两个参数,地面方块的指针和表示是否随机获得相同过度方式的源图片。当编辑地面方块时,需要随机获得相同过度方式的源图片,所以,在GroundEdit成员中以第2个参数为TRUE来调用本成员。当显示地面(即显示地面方块)时,不需要随机获得。
该函数以一个BYTE型变量Value来记录方块四条边上的类型。如果四条边上是相同的类型则把255传给Value。
否则,以位运算记录每以边上的值。此时,Value只有低四位有效。这四位对应于方块的四条边;其对应关系是:从最高位到最低位,分别对应于方块的left,top,right和bottom成员(边)。
因为,Value的初始值是0(即每一位的值是0),且泥地的类型值也是0。所以不检测方块的边是否是泥地,而只检测草地。当某一边的类型是草地时,则Value对应的位的值变为1。
非过度方块有十个相同过度方式的源图片。过度方块有两个相同过度方式的源图片。所以,该成员最后随机地获得某种方块的源图片序号。
9.5 对象的编辑及相关
地面上的实体称为对象。实体所处的位置,角色和怪物是无法到达的。所以,在编辑对象时,应该外加一个操作:生成对应的地形描述(即编辑地形数组)。不同的对象有不同的着地方块,有的着地方块多些,有的少些,甚至有的没有(即悬空的)。本书的游戏的对象只有两种:大树和小树。它们的着地区域所占据的方块只有一个,即树的根部指出的方块。当向场景中的指定位置放置树后,应该把相应位置的方块置为障碍属性。当删除场景中指定位置的树后,应该把相应位置的方块置为非障碍属性。对于其他类型的对象,也采取同样的做法,只是操作的方块数不同而已。
本节将介绍对象的编辑以及相关。
9.5.1 编辑对象
成员函数ObjectEdit的功能是向场景中放置和删除对象。其代码如下:
/**************************************************
* 函数名:ObjectEdit(...)
* 功能:编辑对象
* (c) mochsh, 2004
***************************************************/
LONG CEdit::ObjectEdit( void )
{
//还没有建立场景
if( Scene.SceneH==0 || Scene.SceneH==0 )
return 0;
//选择的操作不是地面编辑
if( lEditState!=1 && lEditState!=2 )
return 0;
//>>>>>>> 编辑对象 >>>>>>>>>>
OBJECT * ob=Scene.Object;
//右键选择对象
if( KeyMouse[0].Key==NULL && KeyMouse[1].Key==DIMOFS_BUTTON1 )
{
while( ob!=NULL )
{
LONG w = ob->wCX/2;
LONG h = ob->wCY/2;
if ( MouseScePos.x>(ob->Pos.x-w) && MouseScePos.x<(ob->Pos.x+w) &&
MouseScePos.y>(ob->Pos.y-h) && MouseScePos.y<(ob->Pos.y+10))
{//选中一对象
SelectObject = ob;
return 0;
}
else
ob = ob->next;
}
if( ob==NULL )
SelectObject = NULL;
}
//左键生成对象
else if( KeyMouse[0].Key==NULL && KeyMouse[1].Key==DIMOFS_BUTTON0 )
{
NewObject();
SelectObject = NULL;
//修改了上次保存的数据
if( lSaveState!=0 )
lSaveState = 2;
return 1;
}
//"Delete"键删除所选对象
else if( KeyKB[0].Key == DIK_DELETE )
{
DeleteObject( SelectObject );
SelectObject = NULL;
return 1;
}
return 0;
}
代码分析:
编辑对象时,是这样操作的:当单击鼠标左键时,表示在鼠标指定位置放置对象。当在场景中的某对象上单击鼠标右键时,表示选择该对象。当选中了对象后,按下键盘的Delete键表示删除该对象。
这个成员函数的返回值类型是LONG型。只要改变了场景中的对象,就返回1,表示需要更新屏幕,以即时显示更改的对象。否则返回0,无需更新屏幕。
9.5.2 添加对象
在ObjectEdit成员中,新加一个对象时,调用了NewObject成员。其代码如下:
/*******************************************
* 函数名:NewObject(...)
* 功能:新加对象
* (c) mochsh, 2004
*******************************************/
void CEdit::NewObject( void )
{
//>>>>>> 获得对象链表的最后一个成员 >>>>>>>
OBJECT * obTemp = Scene.Object;
OBJECT * obLast = NULL;
while( obTemp != NULL )
{
obLast = obTemp;
obTemp = obTemp->next;
}
//>>>>>> 为新对象开辟空间 >>>>>>
//链表为空,则把开辟的空间传给对象链表首地址
if( Scene.Object==NULL ) {
Scene.Object = new OBJECT;
Scene.Object->next = NULL;
obLast = Scene.Object;
}
//非空
else {
obLast->next = new OBJECT;
obLast = obLast->next;
obLast->next = NULL;
}
//>>>>>> 给新对象赋值 >>>>>>>
//是大树的编辑
if( lEditState==1 ) {
obLast->ID = 0;
obLast->lpSurf = lpDDSHighTree;
obLast->wCX = 182;
obLast->wCY = 270;
}
//是小树的编辑
else if( lEditState==2 ) {
obLast->ID = 1;
obLast->lpSurf = lpDDSLowTree;
obLast->wCX = 83;
obLast->wCY = 155;
}
obLast->Pos.x = MouseScePos.x;
obLast->Pos.y = MouseScePos.y;
obLast->rect = GetRect(0,0,9999,9999);
//>>>>> 生成对应的障碍 >>>>>>>
LONG Squ,x,y;
x = MouseScePos.x/20;
y = MouseScePos.y/20;
Squ = y*(Scene.SceneW/20)+x;
Scene.Square[Squ]=1;
++Scene.lObjectN;
}
代码分析:
NewObject成员的代码分为四个部分:获得对象链表的最后一个成员、为新对象开辟空间、给新对象赋值和生成对应的障碍。
编辑的对象只有大树和小树,所以给新对象赋值和生成对应的障碍这两部分的代码较简单。当有很多种对象时,这两部分的代码就复杂得多。因为不同的对象,需要编辑的着地方块数以及取得的方法是不同的。可以这样做:在对象数据结构中,再加一些着地方块的属性记录成员;以后操作对象的着地方块时,就以这些成员的值作为根据;这样能使在操作上具有共性,操作的过程就容易些。
9.5.3 删除对象
在ObjectEdit成员中,调用DeleteObject成员来删除指定的对象。DeleteObject成员只有一个参数,它表示所要删除的对象指针。代码如下:
/***********************************************
* 函数名:DeleteObject(...)
* 功能:删除指定的对象
* (c) mochsh, 2004
************************************************/
void CEdit::DeleteObject( OBJECT * object )
{
if( object==NULL )
return;
OBJECT * ob = Scene.Object;
OBJECT * oldOb = NULL;
while( ob!=NULL )
{
if( ob==object )
{
//删除的不是链表的第1个成员
if( oldOb!=NULL )
oldOb->next = ob->next;
//删除的是链表的第1个成员
else
Scene.Object = ob->next;
//除掉对应的障碍
LONG Squ,x,y;
x = ob->Pos.x/20;
y = ob->Pos.y/20;
Squ = y*(Scene.SceneW/20)+x;
Scene.Square[Squ]=0;
//删除对象
delete ob;
//计数减少
--Scene.lObjectN;
break;
}
else {
oldOb = ob;
ob = ob->next;
}
}
}
代码分析:
删除对象,其实就是释放对应对象的空间。删除了对象,还影响到其他与该对象关联的地形数据和对象链表。所以,在删除指定对象之前,必须把对象链表调整成删除后的形式;和把对应的方块恢复为非障碍方块。
9.6 编辑开关区域
编辑开关区域的方法和编辑对象的方法形式非常相似。编辑开关区域的成员函数是SwitchFieldEdit,其返回值的用途和类型与ObjectEdit成员的是相同的。
CEdit类是这样编辑开关区域的:选择了编辑开关区域命令后,单击左键表示在鼠标指定位置生成开关区域;在已有开关区域上单击鼠标左键,表示选择开关区域;在选择了开关区域的情况下,按下键盘的Delete键,表示删除开关区域。
这些操作与编辑对象的操作是类似的。不同的是单击左键时,SwitchFieldEdit成员将使程序弹出一对话框,用于设置开关区域的属性。如图9-5所示的对话框就是用于设置开关区域的属性的:
图9-5 设置开关区域属性对话框
开关区域的数据结构共有三个成员,这三个成员分别表示开关区域的位置、场景指向和角色的出现位置。开关区域的结构如下:
typedef struct SWITCHFIELD {
RECT rect;//开关区域在当前场景中的位置
LONG SceneID;//开关区域指向的要转换到的场景
POINT startPos; //从要转换到的场景进入当前场景时,角色的出现位置
} SWITCHFIELD, *PSWITCHFIELD;
既然开关区域有三个属性,在编辑开关区域时,就需要设置这三个属性。当单击鼠标左键时,确定了第1个属性,即开关区域的位置。弹出图9-5所示的对话框,是为了设置剩余的两个属性。在对话框中的输入框,可以输入开关区域的场景指向。在对话框的单选按钮组中,可以设置角色的出现位置。角色的出现位置共有四个:开关区域的左上角、右上角、右下角和左下角。
与ObjectEdit成员类似,SwitchFieldEdit成员也调用NewSwitch和DeleteSwitch成员来添加和删除开关区域。这些代码与编辑对象的代码很相似,而且光盘的中的这些代码都有详细地解释,这里就不列出了。
9.7 编辑角色出现区域
本书的游戏的场景是这样的:0号场景作为角色开始进入游戏的场景。玩的过程中,角色死后重新开始,无论角色死前处于几号场景,都将回到0号场景。此时,0号场景的角色出现区域决定了角色的位置。
所以,角色出现区域只适用于0号场景。对于其他场景,没必要编辑角色出现区域。
开关区域中也有一个角色出现位置。这个位置与角色出现区域是不同的。开关区域相当于其指向的场景,从这个场景进入开关区域所处的场景时,必须给角色一个初始位置,这个初始位置就称为开关区域的“角色出现位置”。
角色出现区域的编辑与开关区域中决定开关区域的位置的编辑是相同的。只是开关区域的大小是151*151的矩形,而角色出现区域的大小是100*100的矩形。具体的代码可以参看光盘中的第九章的代码。
9.8 数据的保存与载入
编辑好场景后,就应该把数据保存为文件。当选择了载入场景命令后,则从文件里载入场景数据。这两个操作就是场景数据的输入输出操作。场景的输入输出数据类型与所要编辑的数据类型相同,即场景属性描述(大小与序号)、角色出现区域、开关区域、地面链表、对象链表和地形描述。
9.8.1 保存场景数据
成员函数SaveSceneData的功能就是保存这些数据。其代码如下:
/************************************************
* 函数名:SaveSceneData(...)
* 功能:保存场景数据
* (c) mochsh, 2004
************