1-2 飞机大战游戏场景
前言:
根据前面的项目框架,搭建游戏的运行场景......
1.0 框架预览
基于该框架首先实现游戏的运行场景
2.0 图片文件
创建图片文件,本次项目使用easyx作为图形库文件,在easyx中想要显示图片,需要有一张图片和图片的掩码文件,创建image.h文件和image.cpp文件用于图片设置,具体创建效果如下所示。创建的头文件中做了函数的声明,包含函数的x,y轴的坐标和图片的地址,掩码图片的地址。
#ifndef __IMAGE_H_
#define __IMAGE_H_
#include <easyx.h>
void put_trans_parent_image
(
int x,
int y,
const IMAGE* mask,
const IMAGE* img
);
#endif
#define _CRT_SECURE_NO_WARNINGS
#include "image.h"
void put_trans_parent_image(int x, int y,
const IMAGE *mask, const IMAGE *img)
{
putimage(x, y, mask, SRCAND);
putimage(x, y, img, SRCPAINT);
}
3.0 程序对象
注:对于这款飞机大战游戏,对应的对象就是精灵,然后将将所有元素的共性抽象成一个对象进行方便后续的调用,具体程序如下所示。
这个精灵对象就是所有游戏中出现的对象的共同特性,我们只需要继承精灵,就能在此基础上,续写其他的对象了。将上面的代码写入文件 sprite.h,并添加上头文件守卫。
#ifndef __SPRITE_H_
#define __SPRITE_H_
// 飞机的属性和方法
typedef struct sprite
{
void (*draw)(sprite*);
void (*update)(sprite*);
int x;
int y;
int width;
int height;
}sprite_t;
#endif
hero.h文件,继承sprite对象的属性和方法
#ifndef __HERO_H_
#define __HERO_H_
#include "sprite.h"
#include <easyx.h>
typedef enum heroStatus
{
hero_normal0 = 0, // 英雄处于正常的状态
hero_normal1 = 1, // 英雄处于正常的状态
hero_down0 = 3, // 英雄处于销毁状态1
hero_down1 = 4, // 英雄处于销毁状态2
hero_down2 = 5, // 英雄处于销毁状态3
hero_down3 = 6, // 英雄处于销毁状态4
hero_destory // 英雄完全被销毁
}heroStatus_e;
typedef struct hero
{
sprite_t super;
IMAGE* imgArrHero[6]; // 对应6种不同状态的图片
IMAGE* imgArrHeroMask[6]; // 对应6种不同状态的掩码
heroStatus_e status; // 英雄的状态更换
int life; // 英雄的生命值
int heroUpdateCnt; // 计数值
}hero_t;
void heroInit(hero_t* h);
void heroDestory(hero_t* h);
#endif
hero.cpp文件,对hero.h文件中的对象和参数进行处理
#define _CRT_SECURE_NO_WARNINGS
#include "hero.h"
#include <stdio.h>
#include "image.h"
#define HERO_IMAGE_STRUCT 6
#define IMAGE_PAST 50
#define MASK_IMAGE_PAST 50
#define IMAGE_TYPE_NORMAL 2
#define IMAGE_TYPE_DOWN 4
enum heroStatus heroStatusSqauence[7] =
{
hero_normal0,
hero_normal1,
hero_down0,
hero_down1,
hero_down2,
hero_down3,
hero_destory
};
void heroDraw(hero_t* h) // 绘制函数
{
put_trans_parent_image
(
h->super.x,
h->super.y,
h->imgArrHero[h->status],
h->imgArrHeroMask[h->status]
);
};
void heroUpdate(hero_t* h) // 飞机状态更新
{
h->heroUpdateCnt++;
if (h->heroUpdateCnt >= 15)
{
h->heroUpdateCnt = 0;
if (h->life != 0)
{
if (h->status == hero_normal0)
{
h->status = hero_normal1;
}
else if (h->status == hero_normal1)
{
h->status = hero_normal0;
}
}
else
{
// 状态向后变化
if (h->status < hero_destory)
{
h->status = heroStatusSqauence[h->status + 1];
}
}
}
}
void heroInit(hero_t *h)
{
h->super.draw = (void (*)(sprite_t*))heroDraw;
h->super.update = (void (*)(sprite_t*))heroUpdate;
h->heroUpdateCnt = 0;
h->status = hero_normal0;
h->life = 1;
h->super.x = 178;
h->super.y = 600;
for (int i = 0; i < HERO_IMAGE_STRUCT; i++)
{
h->imgArrHero[i] = new IMAGE;
h->imgArrHeroMask[i] = new IMAGE;
}
char imgPath[IMAGE_PAST];
char imgMaskPath[MASK_IMAGE_PAST];
for (int i = 0; i < IMAGE_TYPE_NORMAL; i++) // 飞机完整的2种状态
{
sprintf(imgPath, "asset/img/hero/hero%d.png", i);
sprintf(imgMaskPath, "asset/img/hero/hero%d_mask.png", i);
loadimage(h->imgArrHero[i], imgPath);
loadimage(h->imgArrHeroMask[i], imgMaskPath);
}
for (int i = 0; i < IMAGE_TYPE_DOWN; i++) // 飞机销毁的4种状态
{
sprintf(imgPath, "asset/img/hero/hero_down%d.png", i);
sprintf(imgMaskPath, "asset/img/hero/hero_down%d_mask.png", i);
loadimage(h->imgArrHero[i + 2], imgPath);
loadimage(h->imgArrHeroMask[i + 2], imgMaskPath);
}
}
// 销毁飞机函数
void heroDestory(hero_t* h)
{
for (int i = 0; i < HERO_IMAGE_STRUCT; i++)
{
delete h->imgArrHero[i];
delete h->imgArrHeroMask[i];
}
}
4.0 游戏循环
gameLoop.h文件:现在,我们将这个循环封装成一个函数,放置到源文件 gameloop.cpp 当中。函数的参数为 sprite 对象
指针与游戏帧率 fps 。为了保证 sprite 对象的 draw 方法与 update 方法,每一帧都被执行一次。可以在循环中,调用 sprite 的 draw 方法与 update 方法。
gameLoop.cpp文件
#define _CRT_SECURE_NO_WARNINGS
#include <easyx.h>
#include "gameloop.h"
#include "sprite.h"
void gameLoop(sprite_t* s, int fps)
{
timeBeginPeriod(1); // 设置系统计时器的分辨率到 1 毫秒,提高时间测量精度
LARGE_INTEGER startCount, endCount, F; // 声明用于高精度计时的变量
QueryPerformanceFrequency(&F); // 获取高性能计数器每秒的频率(即每秒计数值),存储在 F 中
BeginBatchDraw(); // 开始批处理绘图模式,减少不必要的屏幕刷新,优化绘图效率
while(1) // 无限循环,保持游戏运行
{
QueryPerformanceCounter(&startCount); // 获取当前计数器值作为起始时间点
cleardevice(); // 清除绘图设备,准备新一帧的绘制
s->draw(s); // 绘制
s->update(s); // 状态更新
QueryPerformanceCounter(&endCount); // 获取当前计数器值作为结束时间点
long long elapse = // 算从帧开始到结束所经过的时间,单位是微秒
(endCount.QuadPart - startCount.QuadPart) /
F.QuadPart * 1000000;
while (elapse < 1000000 / fps) // 确保每一帧的时间间隔大致等于 1000000 / fps 微秒(即每秒帧数的倒数)
{
Sleep(1);
QueryPerformanceCounter(&endCount);
elapse = (endCount.QuadPart - startCount.QuadPart)
* 1000000 / F.QuadPart;
}
FlushBatchDraw(); // 将批处理中的绘图操作立即提交并显示在屏幕上
}
EndBatchDraw(); // 结束批处理绘图模式
timeEndPeriod(1); // 恢复系统计时器的默认分辨率
}
5.0 游戏场景
background.h文件,背景文件中包含背景A和背景B,背景还继承了精灵的属性和方法,同时包含存储背景图片地址的参数。
#ifndef __BACKGROUND_H_
#define __BACKGROUND_H_
#include "sprite.h"
#include <easyx.h>
typedef struct background
{
sprite_t super;
int yA;
int yB;
IMAGE* imgBackground;
}background_t;
void backgroundInit(background_t *);
void backgroundDestory(background_t *);
#endif
background.cpp文件:
backgroundpraw 为分别绘制A、B两幅背景的函数。两幅背景图片的左上角坐标分别为:(0, yA),(0,yB) 图片为 imgBackground。
backgroundupdate 更新两幅图片的左上角坐标,每次移动1像素。若 yA 大于等于0,则将 yA 复位
为-750,yB 复位为0。
接着就是初始化函数,将 draw 和 update 两个方法赋值为 backgroundpraw 和backgroundupdate yA初始值设置为-750, y8 初始值设置为0。创建并载入图片 img/bg·png。
最后是 backgroundDestroy 函数,销毁初始化时创建的 IMAGE 对象即可。
把当前主函数中 hero 对象,改为 background 对象,可以看到游戏循环 gameloop ,正常渲染并更新了背景对象。
6.0 渲染更新
#define _CRT_SECURE_NO_WARNINGS
#include "gameloop.h"
#include "hero.h"
#include "background.h"
int main(void)
{
initgraph(422, 750); // 初始化画布
setbkcolor(WHITE); // 设置画布颜色
cleardevice(); // 清除画布
hero_t h;
heroInit(&h);
gameLoop((sprite*)&h, 60);
heroDestory(&h);
background b;
backgroundInit(&b);
gameLoop((sprite_t*)&b, 60);
backgroundDestory(&b);
closegraph();
return 0;
}