mooc自学网址:
https://www.icourse163.org/learn/CUC-1450328379?tid=1450742649#/learn/content?type=detail&id=1216695336&sm=1(源自中国传媒大学韩红雷老师的课程和他出版的书籍《《游戏开发程序设计基础》》)
第十章
本次要做的是对前一个坦克大战进行改进,主要采用的是引入外部文件的机制,包括属性修改文件、地图文件、图片文件等等。引入能进行属性修改的INI文件能使参数设置放在外部,而不用在程序中进行修改;地图文件则是用矩阵形式建立地图,1表示障碍物而0表示空地;图片文件能使游戏更加美观,包括背景、障碍物、敌人坦克、玩家坦克以及炮弹发射分别的四个方向。
(1)先在原tank的程序文件夹中创建一个“resources”文件夹,并在其中添加参数修改文件(格式为INI)、地图文件(格式为txt)以及各种位图文件(格式为bmp):
(2)在原来代码的基础上进行修改,主要是加了障碍物和外部文件的使用,下面只对有做修改的部分进行说明:
enum Dir{UP,DOWN,LEFT,RIGHT,NONE}; //增加一个静止的方向变量,因为障碍物实体始终处于静止状态
typedef struct{
int x,y;
int v;
int s;
int b;
int p;
int e;
HBITMAP a[4]; //将原来的颜色属性改成图片属性,每个实体都存有四个视角的图片
};
HBITMAP tank[4],tank2[4],tank3[4],bulletPic[4],grass,background; //用于存放每一个位图
#define MAX_GRASS 64 //表示场景中最多障碍物的数量
int nGrass //先定义障碍物的数量为0,方便后期修改
Entity grasses[MAX_GRASS]; //创建障碍物数组
void ResetPlayer() //重置玩家信息中将原来的颜色设置改成图片
{
memcpy(player.a,tank1,4*sizeof*(Dir)); //外部文件tank1保存给player.a使用,包括四个方向
}
void ReadIni() //将INI属性文件的相关值读入程序变量中,使用的函数是GetPrivateProfileInt,其参数是(所读区域,读取项,输入变量,文件路径)
{
timeStep = GetPrivateProfileInt(L"Global", L"timeStep", timeStep, L"Resources\\Init.ini");
sz = GetPrivateProfileInt(L"Enemy", L"size", sz, L"Resources\\Init.ini");
velf = GetPrivateProfileInt(L"Enemy", L"velf", velf, L"Resources\\Init.ini");
vels = GetPrivateProfileInt(L"Enemy", L"vels", vels, L"Resources\\Init.ini");
enemyFirePer = GetPrivateProfileInt(L"Enemy", L"firePer", enemyFirePer, L"Resources\\Init,ini");
enemyDir = GetPrivateProfileInt(L"Enemy", L"dirPer", enemyDir, L"resources\\Init.ini");
nLife = GetPrivateProfileInt(L"Player", L"nLife", nLife, L"Resources\\Init.ini");
szb = GetPrivateProfileInt(L"Bullet", L"size", szb, L"Resources\\Init.ini");
velb = GetPrivateProfileInt(L"Bullet", L"vel", velb, L"Resources\\Init.ini");
}
void InitMap()
{
FILE*f;
fopen_s(&f,"Resouces\\Map.txt","r"); //打开TXT文件
if(f==NULL)
return;
char line[MAX_GRASS]; //用于临时存放每行读到的信息
int nLine=0; //代表当前读取到第几行
while(!feof(f)){ //如果没有达到文件结尾
fgets(line,MAX_GRASS,f); //读取一整行到line中
for(int i=0;line[i]!='\0';i++){ //对一行中的每个字符进行读取,当出现1的时候,表示此处有障碍物,将此处的坐标进行记录并赋予相关的属性值
if(line[i]=='1'){
if(nGrass>MAX_GRASS)
break;
grass[nGrass].s=sz;
grass[nGrass].b=0;
grass[nGrass].e=0;
grass[nGrass].dir=NONE;
grass[nGrass].v=0;
grass[nGrass].a[0]=grass[nGrass].a[1]=grass[nGrass].a[2]=grass[nGrass].a[3]=grass; //草地的四个方向视图都一致
grass[nGrass].x=sz/2+sz*i; //记录此处的坐标值
grass[nGrass].y=sz/2+sz*nLine;
grass[nGrass].p=0;
nGrass++; //障碍物序号
}
}
nLine++; //进入下一行
}
fclose(f); //关闭文件
}
void Init() //初始化中增加文件读取,LoadImage进行外部文件读取,LoadBitmap进行内部资源文件导入
{
ReadIni(); //先读取外部文件
tank1[0] = (HBITMAP)LoadImage(NULL, L"Resources\\TankBlue.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE); //(实例句柄,路径,图片类型,图片宽和高,以何种方式加载文件)
tank2[0] = (HBITMAP)LoadImage(NULL, L"Resources\\TankYellow.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
tank3[0] = (HBITMAP)LoadImage(NULL, L"Resources\\TankLarge.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
bulletPic[0] = (HBITMAP)LoadImage(NULL, L"Resources\\bullet.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
tank1[1] = (HBITMAP)LoadImage(NULL, L"Resources\\TankBlue1.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
tank2[1] = (HBITMAP)LoadImage(NULL, L"Resources\\TankYellow1.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
tank3[1] = (HBITMAP)LoadImage(NULL, L"Resources\\TankLarge1.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
bulletPic[1] = (HBITMAP)LoadImage(NULL, L"Resources\\bullet1.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
tank1[2] = (HBITMAP)LoadImage(NULL, L"Resources\\TankBlue2.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
tank2[2] = (HBITMAP)LoadImage(NULL, L"Resources\\TankYellow2.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
tank3[2] = (HBITMAP)LoadImage(NULL, L"Resources\\TankLarge2.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
bulletPic[2] = (HBITMAP)LoadImage(NULL, L"Resources\\bullet2.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
tank1[3] = (HBITMAP)LoadImage(NULL, L"Resources\\TankBlue3.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
tank2[3] = (HBITMAP)LoadImage(NULL, L"Resources\\TankYellow3.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
tank3[3] = (HBITMAP)LoadImage(NULL, L"Resources\\TankLarge3.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
bulletPic[3] = (HBITMAP)LoadImage(NULL, L"Resources\\bullet3.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
background = (HBITMAP)LoadImage(NULL, L"Resources\\Back.bmp", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
grass = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_GRASS)); //内部资源文件导入
for (nEnemy = 0;nEnemy < MAX_ENEMY;nEnemy++)
{
enemys[nEnemy].s = sz;
enemys[nEnemy].b = 0;
enemys[nEnemy].e = 1;
enemys[nEnemy].dir = Dir(UP + rand() % 4);
enemys[nEnemy].v = rand() % 2 == 0 ? velf : vels;
//enemys[nEnemy].c = enemys[nEnemy].v == velf ? RGB(0, 122, 122) : RGB(0, 60, 30);
enemys[nEnemy].v == velf ? memcpy(enemys[nEnemy].a, tank3, 4 * sizeof(Dir)) : memcpy(enemys[nEnemy].a, tank2, 4 * sizeof(Dir)); //根据快速或慢速进行图片拷贝
enemys[nEnemy].x = (rand() % 3) * (wndWidth - sz) / 2 + sz / 2;
enemys[nEnemy].y = sz;
enemys[nEnemy].p = 0;
}
ResetPlayer();
InitMap();
}
void Fire(const Entity* ent) //射击中将原来的炮弹颜色改为图片形式
{
memcpy((pBullets+nB)->a,bulletPic,sizeof(Dir)); //将外部文件进行拷贝
}
for (int i = 0;i < nEnemy;i++) { //判断敌人坦克和游戏边界发生碰撞
ent = enemys + i;
if (!WallCollider(ent)) {
int cg = 0;
for (int j = 0;j < nGrass;j++) { //判断敌人和障碍物是否发生碰撞
if (IsCollider(ent, grasses + j)) {
cg = 1;
Move(ent, -ts);
break;
}
}
if (rand() % enemyDir == 0|| cg)
ent->dir = Dir((ent->dir + 1 + rand() % 3) % 4); //碰撞后改变方向
}
}
for (int i = 0;i < nGrass;i++) { //对玩家和障碍物发生碰撞进行检测
ent = grasses + i;
if (IsCollider(ent, &player)) {
switch (player.dir) {
case UP:
player.y = ent->y + ent->s; //进行回撤并处于停止
break;
case DOWN:
player.y = ent->y - ent->s;
break;
case LEFT:
player.x = ent->x + ent->s;
break;
case RIGHT:
player.x = ent->x - ent->s;
break;
}
player.p = 1;
break;
}
}
void DrawEntity(HDC hdc, const Entity* ent) //坦克绘制也采用图片形式而不是颜色
{
HDC hdcMem = CreateCompatibleDC(hdc); //建立一个兼容的后备缓存
HBITMAP bmp = ent->a[int(ent->dir) % 4]; //再建立一个用于保存四个方向的位图
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, bmp); //将对应方向的位图进行选择
BITMAP bm;
GetObject(bmp, sizeof(bm), &bm); //获取bmp位图信息并存放到bm中
SetStretchBltMode(hdc, STRETCH_HALFTONE); //设置和选择位图形式
TransparentBlt(hdc, ent->x - ent->s / 2, ent->y - ent->s / 2, ent->s, ent->s, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, RGB(255, 255, 255)); //支持透明度的位图
SelectObject(hdcMem, hbmOld); //善后处理
DeleteDC(hdcMem);
}
void DrawScene(HDC hdc) //绘制游戏场景中的背景由新加入图片导入的形式,后续的文字效果不变
{
HDC hdcMem = CreateCompatibleDC(hdc); //新建后备缓存
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, background); //把background这个外部文件载入到hdcmem后备缓存中
BITMAP bm;
GetObject(background, sizeof(bm), &bm); //获取缓存信息
SetStretchBltMode(hdc, STRETCH_HALFTONE); //选择模式
BitBlt(hdc, 0, 0, wndWidth, wndHeight, hdcMem, 0, 0, SRCCOPY); //完全拷贝,不含透明信息
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
}
SetWindowLong(hWnd, GWL_STYLE, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU); //最后设置不允许玩家随意更改窗口大小