源码地址:https://download.csdn.net/download/m0_51152186/86404896
一、GUI简介
1、概念:图形用户界面,是指采用图形方式显示的计算机操作用户界面
2、核心技术:Swing、AWT
3、优点
(1)可以写出我们心中想要的一些小工具
(2)工作时候,也许需要维护到 Swing 界面
(3)了解 MVC 架构,了解监听
使用GDI绘图
(1)画点
在pacman.cpp文件中找到:
// 获取消息
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if(msg.message == WM_QUIT) {
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
在这段代码下方增加如下的代码:
// 画点测试
{
HDC hdc = ::GetDC(g_hwnd); // 获得设备句柄
SetPixel(hdc, rand() % WLENTH , rand() % WHIGHT, // 在随机的位置一个随机颜色的点
RGB(rand() % 256, rand() % 256, rand() % 256));
::ReleaseDC(g_hwnd, hdc); // 释放设备
}
(2)画线段
将刚才 /画点即其后大括号内的代码
改为如下代码:
// 画线测试
{
HDC hdc = ::GetDC(g_hwnd);
// 创建画笔
HPEN pen = CreatePen(PS_SOLID, 2, RGB(rand() % 256, rand() % 256, rand() % 256));
// 选择画笔
HPEN oldPen = (HPEN)SelectObject(hdc, pen);
MoveToEx(hdc, rand() % WLENTH, rand() % WHIGHT, NULL);
LineTo(hdc, rand() % WLENTH, rand() % WHIGHT);
// 恢复画笔
SelectObject(hdc, oldPen);
::ReleaseDC(g_hwnd, hdc);
// 暂停1豪秒,不然画得太快,看清
Sleep(1);
}
运行截图如下:
(3)画矩形
将上面画线段的代码替换为如下代码:
// 画矩型测试
{
HDC hdc = ::GetDC(g_hwnd);
{
// 创建画笔
HPEN pen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
// 选择画笔
HPEN oldPen = (HPEN)SelectObject(hdc, pen);
// 画矩型(空心)
Rectangle(hdc, 100, 200, 300, 500);
// 恢复画笔
SelectObject(hdc, oldPen);
DeleteObject(pen);
}
{
// 创建画笔
HBRUSH bBrush = CreateSolidBrush(RGB(0, 0, 255));
// 填充 矩型
RECT rect;
rect.left = 50;
rect.top = 270;
rect.right = 150;
rect.bottom = 370;
FillRect(hdc, &rect, bBrush);
DeleteObject(bBrush);
}
::ReleaseDC(g_hwnd, hdc);
// 暂停1豪秒,不然画得太快,看清
Sleep(1);
}
(4)画圆
将上面画矩形的代码换为如下代码:
// 画圆测试
{
HDC hdc = ::GetDC(g_hwnd);
//画圆:后面四个数字,构成一个正方形
Ellipse(hdc, 200, 150, 300, 250);
//画椭圆
Ellipse(hdc, 200, 270, 340, 370);
//画椭圆
Ellipse(hdc, 100, 100, 200, 150);
::ReleaseDC(g_hwnd, hdc);
}
图 13
(5)画弧形
将刚才画圆的代码换为如下代码:
// 画弧型测试
{
HDC hdc = ::GetDC(g_hwnd);
Arc(hdc, 100, 100, 200, 300 // 矩型 左上点,右下点
, 150, 200 // 起点
, 100, 200 // 终点 (与起点逆时针连接)
);
Arc(hdc, 0, 0, 100, 100
, 50, 100
, 50, 0
);
::ReleaseDC(g_hwnd, hdc);
}
正在上传…重新上传取消
图 14
(6)画大嘴玩家
将上面代码替换为如下代码:
// 综合应用, 画一个大嘴对象
{
static DWORD dwTime = GetTickCount();
// 当距离上绘图的时间大于50豪秒时,才进行本次绘制
if(GetTickCount() - dwTime >= 50) {
dwTime = GetTickCount();
}
else {
continue;
}
/* 模拟当前的帧
本对象一共5帧,每一帧画不同的图形
*/
static int iFrame = 0;
++iFrame;
if(iFrame >= 5) {
iFrame = 0;
}
// 代表对象的中心位置
int x = 300, y = 300;
// 对象的半径
int r = 100;
// dc 对象句柄
HDC hdc = ::GetDC(g_hwnd);
std::shared_ptr<HDC__> dc(::GetDC(g_hwnd), [](HDC hdc) {
::ReleaseDC(g_hwnd, hdc);
});
// 获取窗口客户区大小
RECT rc;
GetClientRect(g_hwnd, &rc);
// 创建画刷
std::shared_ptr<HBRUSH__> br(
::CreateSolidBrush(RGB(255, 255, 255)),
[](HBRUSH hbr) {
::DeleteObject(hbr);
});
// 画背景(清除上一帧所画内容
FillRect(dc.get(), &rc, br.get());
#define PI (3.1415926f) // 定义 圆周率的值
switch(iFrame) {
case 0: {
Ellipse(dc.get(), x - r, y - r, x + r, y + r); // 画一个圆
MoveToEx(dc.get(), x - r, y, NULL); // 画一个横线
LineTo(dc.get(), x, y);
break;
}
case 1: {
// 画嘴(两条线与纵轴偏离 PI/4
int x0, y0; // 左上角的点
int x1, y1; // 左下角的点
x0 = x - static_cast<int>(r * sin(PI * 0.75f));
y0 = y + static_cast<int>(r * cos(PI * 0.75f));
x1 = x + static_cast<int>(r * sin(PI * 1.25f));
y1 = y - static_cast<int>(r * cos(PI * 1.25f));
SetPixel(dc.get(), x0, y0, RGB(255, 0, 0));
SetPixel(dc.get(), x1, y1, RGB(0, 255, 0));
SetPixel(dc.get(), x, y, RGB(0, 0, 0));
Arc(dc.get(), x - r, y - r, x + r, y + r // 画一个半圆 + 一条坚线
, x1, y1
, x0, y0);
MoveToEx(dc.get(), x0, y0, NULL); // 画坚线
LineTo(dc.get(), x, y);
MoveToEx(dc.get(), x1, y1, NULL);
LineTo(dc.get(), x, y);
break;
}
case 2: {
Arc(dc.get(), x - r, y - r, x + r, y + r // 画一个半圆 + 一条坚线
, x, y + r
, x, y - r
);
// 画坚线
MoveToEx(dc.get(), x, y - r, NULL); // 从圆弧上面的点开始
LineTo(dc.get(), x, y + r); // 到圆弧下面的点结束
break;
}
case 3: {
// 画嘴(两条线与纵轴偏离 PI/4
int x0, y0; // 左上角的点
int x1, y1; // 左下角的点
x0 = x - static_cast<int>(r * sin(PI * 0.75f));
y0 = y + static_cast<int>(r * cos(PI * 0.75f));
x1 = x + static_cast<int>(r * sin(PI * 1.25f));
y1 = y - static_cast<int>(r * cos(PI * 1.25f));
SetPixel(dc.get(), x0, y0, RGB(255, 0, 0));
SetPixel(dc.get(), x1, y1, RGB(0, 255, 0));
SetPixel(dc.get(), x, y, RGB(0, 0, 0));
// 画一个半圆 + 一条坚线
Arc(dc.get(), x - r, y - r, x + r, y + r
, x1, y1
, x0, y0);
// 画坚线
MoveToEx(dc.get(), x0, y0, NULL);
LineTo(dc.get(), x, y);
MoveToEx(dc.get(), x1, y1, NULL);
LineTo(dc.get(), x, y);
break;
}
case 4: {
// 画一个圆
Ellipse(dc.get(), x - r, y - r, x + r, y + r);
// 画一个横线
MoveToEx(dc.get(), x - r, y, NULL);
LineTo(dc.get(), x, y);
break;
}
default:
break;
}
}
运行后即可出现半圆,缺四分之一圆,圆三个形状交替出现而形成的绘画,用来模拟闭嘴、张嘴、完全张开嘴的形状。
正在上传…重新上传取消
图 15
正在上传…重新上传取消
图 16
(7)地图及关卡制作
- 打开GMap.h文件。输入如下代码:
#pragma once
#include <list>
#define MAPLENTH 19 // 逻辑地图大小
#define P_ROW 10 // 我方的位置坐标
#define P_ARRAY 9 // 我方的位置坐标
#define E_ROW 8 // 敌方的位置坐标
#define E_ARRAY 9 // 敌方的位置坐标
using std::list;
//抽象类GMap
class GMap
{
protected:
static int LD; // 障碍物尺寸
static int PD; // 豆子的半径
void InitOP(); // 敌我双方出现位置没有豆子出现
bool mapData[MAPLENTH][MAPLENTH]; // 障碍物逻辑地图点阵
bool peaMapData[MAPLENTH][MAPLENTH]; // 豆子逻辑地图点阵
COLORREF color; // 地图中墙的颜色
public:
void DrawMap(HDC &hdc); // 绘制地图
void DrawPeas(HDC &hdc); // 绘制豆子
virtual ~GMap();
GMap()
{
}
friend class GObject; // 允许物体类使用直线的起点和终点的信息做碰撞检测
friend class PacMan; // 允许"大嘴"访问豆子地图
};
第一关地图设计
//"第一关"
class Stage_1 : public GMap
{
private:
bool static initData[MAPLENTH][MAPLENTH]; // 地图数据
public:
Stage_1();
};
第二关地图设计
//第二关
class Stage_2 : public GMap
{
private:
bool static initData[MAPLENTH][MAPLENTH]; // 地图数据
public:
Stage_2();
};
第三关地图设计
// 第三关
class Stage_3 : public GMap
{
private:
bool static initData[MAPLENTH][MAPLENTH]; // 地图数据
public:
Stage_3();
};
地图类的实现:
打开GMap.cpp文件,输入以下代码
#include "stdafx.h"
#include "GMap.h"
int GMap::LD = 36; // 墙的宽度
int GMap::PD = 3; // 豆子的直径
在GMap.cpp文件最下方输入以下代码:
//敌我双方出现位置没有豆子出现
void GMap::InitOP()
{
peaMapData[E_ROW][E_ARRAY] = false; // 敌方位置没有豆子
peaMapData[P_ROW][P_ARRAY] = false; // 玩家位置没有豆子
}
类的成员函数mapData存储了墙体的数据,遍历这个数组,当发现该处是墙壁时在此处绘制一个矩形模拟墙体,在GMap.cpp文件最下方输入绘制地图函数:
void GMap::DrawMap(HDC &memDC)
{
HBRUSH hBrush = CreateSolidBrush(color);
for(int i = 0; i < MAPLENTH; i++) {
for(int j = 0; j < MAPLENTH; j++) {
//绘制墙壁
if(!mapData[i][j]) {
RECT rect;
rect.left = j * LD;
rect.top = i * LD;
rect.right = (j + 1) * LD;
rect.bottom = (i + 1) * LD;
FillRect(memDC, &rect, hBrush); // 填充矩型区域,模拟墙体
}
}
}
DeleteObject(hBrush); // 删除画刷对象
}
成员变量peaMapData存储的是豆子数据,遍历该数组,如果发现该处元素为真,则调用画圆的函数画豆子,在GMap.cpp文件最下方接着输入绘制豆子的代码:
void GMap::DrawPeas(HDC &hdc) // 画豆子函数
{
for(int i = 0; i < MAPLENTH; i++) { // 遍历整个数组
for(int j = 0; j < MAPLENTH; j++) {
if(peaMapData[i][j]) { // 如果该处有豆子
Ellipse(hdc, (LD / 2 - PD) + j * LD, // 画圆:模拟豆子
(LD / 2 - PD) + i * LD,
(LD / 2 + PD) + j * LD,
(LD / 2 + PD) + i * LD);
}
}
}
4.3通往成功的捷径——游戏隐藏后门的实现
在按下“B”键时,直接通过关卡,在GMap.cpp文件最下方输入:
// 如果按下B,直接过关
if(GetAsyncKeyState('B') & 0x8000) {
MessageBoxA(NULL, "无意中您发现了秘笈", "", MB_OK);
for(int i = 0; i < MAPLENTH; i++) {
for(int j = 0; j < MAPLENTH; j++) {
peaMapData[i][j] = false;
}
}
}
}
接着输入析构函数:
GMap::~GMap()
{
}
隐藏后门运行截图:
正在上传…重新上传取消
图 17
正在上传…重新上传取消
图 18
正在上传…重新上传取消
图 19
在GMap.cpp文件最下方输入第一关地图相关函数及数据,代码中定义了A为真,B为假,其中真的位置代表该处有豆子,假的位置代表该处是墙。
//Stage_1成员定义:
#define A true // true:表示豆子
#define B false // false:表示墙壁
bool Stage_1::initData[MAPLENTH][MAPLENTH] = {
B, B, B, B, B, B, B, B, B, A, B, B, B, B, B, B, B, B, B, //0
B, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, B, //1
B, A, A, B, A, A, B, B, B, A, B, B, B, A, A, B, A, A, B, //2
B, A, B, B, A, A, A, A, A, A, A, A, A, A, A, B, B, A, B, //3
B, A, B, A, A, A, B, B, B, A, B, B, B, A, A, A, B, A, B, //4
B, A, B, A, A, A, A, A, A, A, A, A, A, A, A, A, B, A, B, //5
B, A, A, A, A, A, B, B, A, A, A, B, B, A, A, A, A, A, B, //6
B, A, B, A, A, A, A, A, A, A, A, A, A, A, A, A, B, A, B, //7
B, A, B, A, A, A, A, A, B, A, B, A, A, A, A, A, B, A, B, //8
A, A, A, A, A, A, A, A, B, B, B, A, A, A, A, A, A, A, A, //9
B, A, B, A, A, A, A, A, A, A, A, A, A, A, A, A, B, A, B, //10
B, A, B, A, A, B, A, A, A, A, A, A, A, B, A, A, B, A, B, //11
B, A, B, A, B, B, B, A, A, A, A, A, B, B, B, A, B, A, B, //12
B, A, A, A, A, B, A, A, A, A, A, A, A, B, A, A, A, A, B, //13
B, A, B, B, A, A, A, A, A, A, A, A, A, A, A, B, B, A, B, //14
B, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, B, //15
B, A, A, A, A, B, B, B, A, B, A, B, B, B, A, A, A, A, B, //16
B, A, A, A, A, B, A, A, A, A, A, A, A, B, A, A, A, A, B, //17
B, B, B, B, B, B, B, B, B, A, B, B, B, B, B, B, B, B, B, //18
};
#undef A
#undef B
Stage_1::Stage_1()
{
color = RGB(140, 240, 240); // 墙的颜色
for(int i = 0; i < MAPLENTH; i++) {
for(int j = 0; j < MAPLENTH; j++) {
this->mapData[i][j] = this->initData[i][j];
this->peaMapData[i][j] = this->initData[i][j];
}
}
//敌我双方出现位置没有豆子出现
this->InitOP();
}
在GMap.cpp文件最下方输入第二关地图相关函数及数据,代码中定义了A为真,B为假,其中真的位置代表该处有豆子,假的位置代表该处是墙。
//Stage_2成员定义
#define A true
#define B false
bool Stage_2::initData[MAPLENTH][MAPLENTH] = {
B, B, B, B, B, B, B, B, B, A, B, B, B, A, B, B, B, B, B, //0
A, A, A, A, A, A, A, B, A, A, B, A, A, A, B, A, B, A, A, //1
B, A, A, A, B, A, A, B, A, A, B, A, B, A, B, A, B, A, B, //2
B, B, B, A, B, A, A, B, B, A, B, A, B, A, B, A, B, B, B, //3
B, A, A, A, A, A, A, A, A, A, A, A, B, B, B, A, A, A, B, //4
B, A, A, B, A, A, A, A, A, A, A, A, A, A, A, A, A, A, B, //5
B, A, A, B, A, A, A, B, B, B, B, B, B, A, A, B, A, A, B, //6
B, A, A, B, A, B, A, A, A, A, A, A, A, A, A, B, A, A, B, //7
B, A, A, B, A, B, A, A, B, A, B, A, A, B, A, B, A, A, B, //8
A, A, A, B, A, B, A, A, B, B, B, A, A, B, A, B, A, A, A, //9
B, A, A, B, A, B, A, A, A, A, A, A, A, B, A, A, A, A, B, //10
B, A, A, B, A, A, A, B, B, B, B, B, A, B, A, A, A, A, B, //11
B, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, B, //12
B, A, A, A, B, B, B, B, B, B, B, A, A, A, A, A, A, A, B, //13
B, A, A, A, A, A, A, A, A, A, A, A, A, B, A, A, A, A, B, //14
B, B, B, B, B, A, A, A, A, B, B, B, A, B, A, A, A, A, B, //15
B, A, A, A, B, B, B, A, A, A, A, B, A, B, B, B, A, A, B, //16
A, A, A, A, B, A, A, A, A, A, A, B, A, A, A, B, A, A, A, //17
B, B, B, B, B, B, B, B, B, A, B, B, B, A, B, B, B, B, B, //18
};
#undef A
#undef B
Stage_2::Stage_2()
{
color = RGB(240, 140, 140); // 墙的颜色
for(int i = 0; i < MAPLENTH; i++) {
for(int j = 0; j < MAPLENTH; j++) {
this->mapData[i][j] = this->initData[i][j];
this->peaMapData[i][j] = this->initData[i][j];
}
}
//敌我双方出现位置没有豆子出现
this->InitOP();
}
在GMap.cpp文件最下方输入第三关地图相关函数及数据,代码中定义了A为真,B为假,其中真的位置代表该处有豆子,假的位置代表该处是墙。
//Stage_3成员定义
#define A true
#define B false
bool Stage_3::initData[MAPLENTH][MAPLENTH] = {
B, B, B, B, B, B, B, B, B, A, B, B, B, B, B, B, B, B, B, //0
A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, //1
B, A, A, B, A, A, B, B, B, B, B, B, B, A, A, A, B, A, B, //2
B, A, B, B, A, A, A, A, A, A, A, A, B, A, A, A, B, A, B, //3
B, A, B, A, A, A, B, B, B, B, B, B, B, A, A, A, B, A, B, //4
B, A, B, A, B, B, B, A, A, A, A, A, B, B, B, A, B, A, B, //5
B, A, A, A, B, A, B, A, A, A, A, A, A, A, A, A, B, A, B, //6
B, A, B, A, B, A, A, A, A, A, A, A, A, B, A, A, B, A, B, //7
B, A, B, A, B, B, A, A, B, A, B, A, A, B, A, A, B, A, B, //8
B, A, A, A, A, B, A, A, B, B, B, A, A, B, A, A, B, A, B, //9
B, A, B, A, A, B, A, A, A, A, A, A, A, B, A, A, A, A, B, //10
B, A, B, A, A, B, A, A, A, A, A, A, B, B, B, A, B, A, B, //11
B, A, B, A, A, B, A, B, B, B, B, B, B, A, B, A, B, A, B, //12
B, A, B, A, A, B, A, A, A, A, A, A, A, A, B, A, B, A, B, //13
B, A, B, B, A, B, B, B, B, B, B, A, B, A, B, A, B, A, B, //14
B, A, A, A, A, B, A, A, A, A, A, A, B, A, B, A, B, A, B, //15
B, B, B, B, B, B, A, A, B, B, B, A, B, A, B, A, B, A, B, //16
A, A, A, A, A, A, A, A, B, A, A, A, A, A, B, A, A, A, A, //17
B, B, B, B, B, B, B, B, B, A, B, B, B, B, B, B, B, B, B, //18
};
#undef A
#undef B
Stage_3::Stage_3()
{
color = RGB(100, 44, 100); // 墙的颜色
for(int i = 0; i < MAPLENTH; i++) {
for(int j = 0; j < MAPLENTH; j++) {
this->mapData[i][j] = this->initData[i][j];
this->peaMapData[i][j] = this->initData[i][j];
}
}
//敌我双方出现位置没有豆子出现
this->InitOP();
}
使用地图
打开pacman.cpp文件,增加包含头文件“GMap.h”的代码
找到“HACCEL hAccelTable=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDC_PACMAN));”
一行,把此行一下删掉,改为以下代码:
// 当前的关卡
int s_n = 0; // [0, 1, 2]
// 地图
GMap *MapArray[STAGE_COUNT] = { new Stage_1(), new Stage_2(), new Stage_3() };
MSG msg;
// 主消息循环:
bool bRunning = true;
while(bRunning && s_n < STAGE_COUNT) {
// 获取消息
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if(msg.message == WM_QUIT) { // WM_QUIT消息,退出循环。
break;
}
TranslateMessage(&msg); // 翻译消息
DispatchMessage(&msg); // 分发消息
}
HDC hdc = ::GetDC(g_hwnd);
{
MapArray[s_n]->DrawPeas(hdc); // 画豆子
MapArray[s_n]->DrawMap(hdc); // 画地图
}
::ReleaseDC(g_hwnd, hdc); // 释放设备资源
}
return (int) msg.wParam;
只有地图的运行截图如下
int s_n = 0;(第一张地图)
正在上传…重新上传取消
图 20
int s_n = 1;(第二张地图)
正在上传…重新上传取消
图 21
int s_n = 2;(第三张地图)
正在上传…重新上传取消
图 22
4.4游戏可移动对象设计与实现
游戏中可移动对象包括敌军和玩家,因此可以抽象出一个共同的父类“GObject”来存放两种对象的相同逻辑和数据。在工程中增加GObject类,打开GObject.h文件,删除原来的,输入以下代码:
#include "stdafx.h"
#include <time.h>
#include "GMap.h"
#define PLAYERSPEED 6 //玩家速度
#define ENERMYSPEED 4 //敌人速度
#define LEGCOUNTS 5 //敌人腿的数量
#define DISTANCE 10 //图型范围
#define BLUE_ALERT 8 //蓝色警戒范围
#define D_OFFSET 2 //绘图误差
#define RD (DISTANCE + D_OFFSET) //绘图范围 12
enum TWARDS { //方向枚举
UP, // 上
DOWN, // 下
LEFT, // 左
RIGHT, // 右
OVER, // 游戏结束
};
class GObject // 物体类 : 大嘴和敌人的父类
{
public:
GObject(int Row, int Array)
{
m_nFrame = 1; // 帧数
pStage = NULL; // 当前关卡
this->m_nRow = Row; // 行
this->m_nArray = Array; // 数组
// 中心位置
this->m_ptCenter.y = m_nRow * pStage->LD + pStage->LD / 2;
this->m_ptCenter.x = m_nArray * pStage->LD + pStage->LD / 2;
this->m_nX = m_ptCenter.x;
this->m_nY = m_ptCenter.y;
}
void SetPosition(int Row, int Array); // 设置位置
void DrawBlank(HDC &hdc); // 画空白
void virtual Draw(HDC &hdc) = 0; // 绘制对象
void virtual action() = 0; // 数据变更的表现
int GetRow();
int GetArray();
static GMap *pStage; //指向地图类的指针,设置为静态,使所有自类对象都能够使用相同的地图
protected:
int m_nX;
int m_nY;
TWARDS m_cmd; // 指令缓存
POINT m_ptCenter; // 中心坐标
int m_nRow; // 逻辑横坐标
int m_nArray; // 逻辑纵坐标
int m_nSpeed; // 速度
TWARDS m_dir; // 朝向
int m_nFrame; // 祯数
bool Achive(); // 判断物体是否到达逻辑坐标位置
bool Collision(); // 逻辑碰撞检测,将物体摆放到合理的位置
int PtTransform(int k); // 将实际坐标转换为逻辑坐标
virtual void AchiveCtrl(); // 到达逻辑点后更新数据
};
上面代码即声明GObject类的代码,其中定义了共同属性,如坐标、速度和指令等;还定义了共同函数,如画空白和设置位置等,下面的子类将会根据各自不同的逻辑覆盖含有“virtual”关键字声明的函数。
玩家对象的设计
在GObject.h文件中接着输入玩家对象的声明。玩家对象“PacMan”为”GObject“的子类。该类扩展了父类的功能,实现了Draw()函数和action()函数,其中Draw()函数负责绘制自己,action()函数负责本类的行为,本类的构造函数中,设置了本类的初始速度为”PLAYERSPEED“,设置了朝向为”LEFT“,具体代码如下:
class PacMan : public GObject // 玩家对象
{
protected:
virtual void AchiveCtrl(); // 重写虚函数
public:
POINT GetPos();
bool IsOver(); // 游戏是否结束
bool IsWin(); // 玩家是否赢得游戏
void Draw(HDC &hdc); // 负责绘制自己
void SetTwCommand(TWARDS command); // 设置玩家下一步指令
PacMan(int x, int y) : GObject(x, y) // 构造函数,产生新对象时调用
{
this->m_nSpeed = PLAYERSPEED; // 设置玩家速度
m_cmd = m_dir = LEFT; // 设置
}
void action(); // 玩家的动作函数
void SetOver(); // 设置游戏结束函数
};
敌军对象的设计
下面是敌军对象的声明。声明增加了负责抓住玩家的Catch()函数,增加了敌军的AI函数MakeDecision()的声明。在构造函数Enermy()中,设定本类的初始速度为ENERMYSPEED,设定方向为LEFT,设定命令为UP。在GObject.h文件中输入以下代码;
class Enermy : public GObject // 敌军对象
{
protected:
void Catch(); // 是否抓住玩家
void virtual MakeDecision(bool b) = 0; // AI实现,确定方向
COLORREF color;
public:
static std::shared_ptr<PacMan> player;
void virtual Draw(HDC &hdc); // 负责绘制自己
Enermy(int x, int y) : GObject(x, y) // 构造函数
{
this->m_nSpeed = ENERMYSPEED; // 设置速度
m_dir = LEFT; // 设置朝向
m_cmd = UP; // 设置移动方向
}
void virtual action(); // 负责行为
};
下面是3种不同种类的敌军对象的声明:
- 红色敌军声明:构造函数中设定颜色为RGB(255,0,0);
- 蓝色敌军声明:构造函数中设定颜色为RGB(0,0,255);
- 黄色敌军声明:构造函数中设定颜色为RGB(200,200,100);
三个类同时声明MakeDecision()函数,这个函数指明对象的行为,三个类分别实现这个函数,表明每个对象的行为各不相同,在GObject.h文件中输入以下代码:
// 三种 敌人
class RedOne : public Enermy // 随即移动S
{
protected:
void virtual MakeDecision(bool b);
public:
void Draw(HDC &hdc);
RedOne(int x, int y) : Enermy(x, y)
{
color = RGB(255, 0, 0);
}
};
class BlueOne : public RedOne //守卫者
{
protected:
void virtual MakeDecision(bool b);
public:
void Draw(HDC &hdc);
BlueOne(int x, int y) : RedOne(x, y)
{
color = RGB(0, 0, 255);
}
};
class YellowOne : public RedOne // 扰乱者
{
protected:
void virtual MakeDecision(bool b);
public:
void Draw(HDC &hdc);
YellowOne(int x, int y) : RedOne(x, y)
{
color = RGB(200, 200, 100);
}
};
可移动对象的实现
下面是GObject对象的实现代码,主要功能包括:
- 位置调整函数AchiveCtrl():将物体摆放到合理的位置;
- DrawBlank():绘制空白区域;
- Collision():碰撞检测
在GObject.cpp文件,输入GObject对象实现代码:
#include "stdafx.h"
#include "GObject.h"
// GOject成员定义:
GMap *GObject::pStage = NULL;
int GObject::GetRow() // 返回行
{
return m_nRow;
}
int GObject::GetArray() // 返回数组首地址
{
return m_nArray;
}
int GObject::PtTransform(int k) // 坐标转换函数
{
return (k - (pStage->LD) / 2) / pStage->LD;
}
// 判断物体是否到达逻辑坐标位置
bool GObject::Achive()
{
int n = (m_ptCenter.x - pStage->LD / 2) % pStage->LD; // 计算x坐标的余数
int k = (m_ptCenter.y - pStage->LD / 2) % pStage->LD; // 计算y坐标的余数
bool l = (n == 0 && k == 0); // 如果两个余数都为0,说明到达中心位置
return l;
}
// 到达逻辑点后更新数据
void GObject::AchiveCtrl()
{
if(Achive()) { // 如果达到逻辑坐标
m_nArray = PtTransform(m_ptCenter.x); // 更新列
m_nRow = PtTransform(m_ptCenter.y); // 更新行
}
}
void GObject::DrawBlank(HDC &hdc)
{
// 申请资源,并交给智能指针处理
HBRUSH hbr = ::CreateSolidBrush(RGB(255, 255, 255)); // 创建画刷,绘制矩型函数要求使用
std::shared_ptr<HBRUSH> phbr(&hbr, [](auto hbr) { // 把资源交给智能指针处理,自动释放
DeleteObject(*hbr); // 离开 DrawBlank函数时,会自动调用释放资源
});
RECT rect;
rect.top = m_nY - RD;
rect.left = m_nX - RD;
rect.right = m_nX + RD;
rect.bottom = m_nY + RD;
FillRect(hdc, &rect, *phbr); // 绘制矩型
}
// 设置中心位置
void GObject::SetPosition(int Row, int Array)
{
m_nRow = Row;
m_nArray = Array;
this->m_ptCenter.y = m_nRow * pStage->LD + pStage->LD / 2;
this->m_ptCenter.x = m_nArray * pStage->LD + pStage->LD / 2;
}
// 碰撞检测
bool GObject::Collision()
{
bool b = false;
//更新行、列的数据若是大嘴,则会执行PacMan重写的AchiveCtrl函数消除豆子
AchiveCtrl();
//判断指令的有效性
if(m_nArray < 0 || m_nRow < 0 || m_nArray > MAPLENTH - 1
|| m_nRow > MAPLENTH - 1) {
b = true;
}
else if(Achive()) {
switch(m_cmd) { //判断行进的方向
case LEFT: //如果朝向为左
//判断下一个格子是否能够通行
if(m_nArray > 0 &&
!pStage->mapData[m_nRow][m_nArray - 1]) {
b = true; // "撞墙了"
}
break;
//以下方向的判断原理相同
case RIGHT: //如果朝向为右
if(m_nArray < MAPLENTH - 1 &&
!pStage->mapData[m_nRow][m_nArray + 1]) {
b = true; // "撞墙了"
}
break;
case UP: //如果朝向为上
if(m_nRow > 0 &&
!pStage->mapData[m_nRow - 1][m_nArray]) {
b = true; // "撞墙了"
}
break;
case DOWN: //如果朝向为下
if(m_nRow < MAPLENTH - 1 &&
!pStage->mapData[m_nRow + 1][m_nArray]) {
b = true; // "撞墙了"
}
break;
}
if(!b) {
m_dir = m_cmd; //没撞墙,指令成功
}
}
//依照真实的方向位移
m_nX = m_ptCenter.x;
m_nY = m_ptCenter.y;
int MAX = pStage->LD * MAPLENTH + pStage->LD / 2;
int MIN = pStage->LD / 2;
switch(m_dir) { //判断行进的方向
case LEFT:
//判断下一个格子是否能够通行
if(m_nArray > 0 &&
!pStage->mapData[m_nRow][m_nArray - 1]) {
b = true;
break; // "撞墙了"
}
m_ptCenter.x -= m_nSpeed;
if(m_ptCenter.x < MIN) {
m_ptCenter.x = MAX;
}
break;
//以下方向的判断原理相同
case RIGHT:
if(m_nArray < MAPLENTH - 1 &&
!pStage->mapData[m_nRow][m_nArray + 1]) {
b = true;
break; // "撞墙了"
}
m_ptCenter.x += m_nSpeed;
if(m_ptCenter.x > MAX) {
m_ptCenter.x = MIN;
}
break;
case UP:
if(m_nRow > 0 &&
!pStage->mapData[m_nRow - 1][m_nArray]) {
b = true;
break; // "撞墙了"
}
m_ptCenter.y -= m_nSpeed;
if(m_ptCenter.y < MIN) {
m_ptCenter.y = MAX;
}
break;
case DOWN:
if(m_nRow < MAPLENTH - 1 &&
!pStage->mapData[m_nRow + 1][m_nArray]) {
b = true;
break; // "撞墙了"
}
m_ptCenter.y += m_nSpeed;
if(m_ptCenter.y > MAX) {
m_ptCenter.y = MIN;
}
break;
}
return b;
}
玩家对象的实现
这里增加了判断游戏是否胜利的函数IsWin(),判断逻辑是遍历当前地图的数据,查看豆子的数量,如果发现至少还有一个豆子,则没有胜利。同时也实现Draw()方法,该方法负责绘制玩家对象自己,在GObject.h中继续输入以下代码:
//PacMan成员定义:
void PacMan::AchiveCtrl()
{
GObject::AchiveCtrl();
if(Achive()) {
if(m_nRow >= 0 && m_nRow < MAPLENTH &&
m_nArray >= 0 && m_nArray < MAPLENTH) { // 防止数组越界
if(pStage->peaMapData[m_nRow][m_nArray]) {
pStage->peaMapData[m_nRow][m_nArray] = false;
}
}
}
}
void PacMan::action()
{
Collision(); // 进行碰撞检测
}
void PacMan::SetTwCommand(TWARDS command)
{
m_cmd = command; // 设置移动方向
}
bool PacMan::IsOver()
{
return m_dir == OVER; // 判断游戏是否结束
}
bool PacMan::IsWin()
{
for(int i = 0; i <= MAPLENTH; i++) {
for(int j = 0; j <= MAPLENTH; j++) {
if(pStage->peaMapData[i][j] == true) { // 是豆子
return false; // 存在任意一个豆子,没取得胜利
}
}
}
return true; // 没有豆子,胜利
}
POINT PacMan::GetPos()
{
return m_ptCenter; // 返回对象的中心位置
}
void PacMan::SetOver()
{
m_dir = OVER; // 设置游戏结束
}
void PacMan::Draw(HDC &memDC)
{
if(m_dir == OVER) {
// 游戏结束,什么也不干
}
else if(m_nFrame % 2 == 0) { // 第4祯动画与第2祯动画:张嘴形状
int x1 = 0, x2 = 0, y1 = 0, y2 = 0;
int offsetX = DISTANCE / 2 + D_OFFSET; // 弧弦交点X
int offsetY = DISTANCE / 2 + D_OFFSET; // 弧弦交点Y
switch(m_dir) {
case UP: // 向上移动
x1 = m_ptCenter.x - offsetX;
x2 = m_ptCenter.x + offsetX;
y2 = y1 = m_ptCenter.y - offsetY;
break;
case DOWN: // 向下移动
x1 = m_ptCenter.x + offsetX;
x2 = m_ptCenter.x - offsetX;
y2 = y1 = m_ptCenter.y + offsetY;
break;
case LEFT: // 向左移动
x2 = x1 = m_ptCenter.x - offsetX;
y1 = m_ptCenter.y + offsetY;
y2 = m_ptCenter.y - offsetY;
break;
case RIGHT: // 向右移动
x2 = x1 = m_ptCenter.x + offsetX;
y1 = m_ptCenter.y - offsetY;
y2 = m_ptCenter.y + offsetY;
break;
}
// 画出 弧型部分
Arc(memDC, m_ptCenter.x - DISTANCE, m_ptCenter.y - DISTANCE,
m_ptCenter.x + DISTANCE, m_ptCenter.y + DISTANCE,
x1, y1,
x2, y2);
// 画直线部分,最后组合成玩家对象:一个大嘴的形象
MoveToEx(memDC, x1, y1, NULL);
LineTo(memDC, m_ptCenter.x, m_ptCenter.y);
LineTo(memDC, x2, y2);
}
else if(m_nFrame % 3 == 0) { // 第三帧动画:画出整个圆形
Ellipse(memDC, m_ptCenter.x - DISTANCE, m_ptCenter.y - DISTANCE,
m_ptCenter.x + DISTANCE, m_ptCenter.y + DISTANCE);
}
else { // 嘴完全张开的形状
int x1 = 0, x2 = 0, y1 = 0, y2 = 0;
switch(m_dir) {
case UP: // 向上移动
x1 = m_ptCenter.x - DISTANCE;
x2 = m_ptCenter.x + DISTANCE;
y2 = y1 = m_ptCenter.y;
break;
case DOWN: // 向下移动
x1 = m_ptCenter.x + DISTANCE;
x2 = m_ptCenter.x - DISTANCE;
y2 = y1 = m_ptCenter.y;
break;
case LEFT: // 向左移动
x2 = x1 = m_ptCenter.x;
y1 = m_ptCenter.y + DISTANCE;
y2 = m_ptCenter.y - DISTANCE;
break;
case RIGHT: // 向右移动
x2 = x1 = m_ptCenter.x;
y1 = m_ptCenter.y - DISTANCE;
y2 = m_ptCenter.y + DISTANCE;
break;
}
// 画出 弧型部分
Arc(memDC, m_ptCenter.x - DISTANCE, m_ptCenter.y - DISTANCE,
m_ptCenter.x + DISTANCE, m_ptCenter.y + DISTANCE,
x1, y1,
x2, y2);
// 画直线部分,最后组合成玩家对象:一个大嘴的形象
MoveToEx(memDC, x1, y1, NULL);
LineTo(memDC, m_ptCenter.x, m_ptCenter.y);
LineTo(memDC, x2, y2);
}
m_nFrame++;// 绘制下一祯
}
敌军对象的实现
1.敌军对象成员定义代码:
定义Catch()函数,判断是否抓住了玩家,抓住玩家则游戏结束。定义Draw()函数,该函数负责绘制自己,定义action()函数,负责对象的行为,该函数直接调用父类碰撞检测函数Collision()实现功能,在GObject.h文件中继续输入以下代码:
//Enermy成员定义:
std::shared_ptr<PacMan> Enermy::player = nullptr;
// 抓住,游戏结束
void Enermy::Catch()
{
int DX = m_ptCenter.x - player->GetPos().x;
int DY = m_ptCenter.y - player->GetPos().y;
if((-RD < DX && DX < RD) && (-RD < DY && DY < RD)) {
player->SetOver();
}
}
void Enermy::Draw(HDC &hdc)
{
HPEN pen = ::CreatePen(0, 0, color);
HPEN oldPen = (HPEN)SelectObject(hdc, pen);
Arc(hdc, m_ptCenter.x - DISTANCE, m_ptCenter.y - DISTANCE,
m_ptCenter.x + DISTANCE, m_ptCenter.y + DISTANCE,
m_ptCenter.x + DISTANCE, m_ptCenter.y,
m_ptCenter.x - DISTANCE, m_ptCenter.y); // 半圆型的头
int const LEGLENTH = (DISTANCE) / (LEGCOUNTS);
// 根据祯数来绘制身体和“腿部”
if(m_nFrame % 2 == 0) {
// 矩形的身子
MoveToEx(hdc, m_ptCenter.x - DISTANCE, m_ptCenter.y, NULL);
LineTo(hdc, m_ptCenter.x - DISTANCE,
m_ptCenter.y + DISTANCE - LEGLENTH);
MoveToEx(hdc, m_ptCenter.x + DISTANCE, m_ptCenter.y, NULL);
LineTo(hdc, m_ptCenter.x + DISTANCE,
m_ptCenter.y + DISTANCE - LEGLENTH);
for(int i = 0; i < LEGCOUNTS; i++) { // 从左往右绘制“腿部”
Arc(hdc,
m_ptCenter.x - DISTANCE + i * 2 * LEGLENTH,
m_ptCenter.y + DISTANCE - 2 * LEGLENTH,
m_ptCenter.x - DISTANCE + (i + 1) * 2 * LEGLENTH,
m_ptCenter.y + DISTANCE,
m_ptCenter.x - DISTANCE + i * 2 * LEGLENTH,
m_ptCenter.y + DISTANCE - LEGLENTH,
m_ptCenter.x - DISTANCE + (i + 1) * 2 * LEGLENTH,
m_ptCenter.y + DISTANCE - LEGLENTH
);
}
}
else {
MoveToEx(hdc, m_ptCenter.x - DISTANCE, m_ptCenter.y, NULL); // 绘制身体
LineTo(hdc, m_ptCenter.x - DISTANCE, m_ptCenter.y + DISTANCE);
MoveToEx(hdc, m_ptCenter.x + DISTANCE, m_ptCenter.y, NULL);
LineTo(hdc, m_ptCenter.x + DISTANCE, m_ptCenter.y + DISTANCE);
MoveToEx(hdc, m_ptCenter.x - DISTANCE,
m_ptCenter.y + DISTANCE, NULL);
LineTo(hdc, m_ptCenter.x - DISTANCE + LEGLENTH,
m_ptCenter.y + DISTANCE - LEGLENTH);
for(int i = 0; i < LEGCOUNTS - 1; i++) { // 从左往右绘制“腿部”
Arc(hdc,
m_ptCenter.x - DISTANCE + (1 + i * 2)*LEGLENTH,
m_ptCenter.y + DISTANCE - 2 * LEGLENTH,
m_ptCenter.x - DISTANCE + (3 + i * 2)*LEGLENTH,
m_ptCenter.y + DISTANCE,
m_ptCenter.x - DISTANCE + (1 + i * 2)*LEGLENTH,
m_ptCenter.y + DISTANCE - LEGLENTH,
m_ptCenter.x - DISTANCE + (3 + i * 2)*LEGLENTH,
m_ptCenter.y + DISTANCE - LEGLENTH
);
}
MoveToEx(hdc, m_ptCenter.x + DISTANCE, m_ptCenter.y + DISTANCE, NULL);
LineTo(hdc, m_ptCenter.x + DISTANCE - LEGLENTH,
m_ptCenter.y + DISTANCE - LEGLENTH);
}
//根据方向绘制眼睛
int R = DISTANCE / 5; // 眼睛的半径
switch(m_dir) {
case UP:
Ellipse(hdc, m_ptCenter.x - 2 * R, m_ptCenter.y - 2 * R,// 画左眼
m_ptCenter.x, m_ptCenter.y);
Ellipse(hdc, m_ptCenter.x, m_ptCenter.y - 2 * R, // 画右眼
m_ptCenter.x + 2 * R, m_ptCenter.y);
break;
case DOWN:
Ellipse(hdc, m_ptCenter.x - 2 * R, m_ptCenter.y, // 画左眼
m_ptCenter.x, m_ptCenter.y + 2 * R);
Ellipse(hdc, m_ptCenter.x, m_ptCenter.y, // 画右眼
m_ptCenter.x + 2 * R, m_ptCenter.y + 2 * R);
break;
case LEFT:
Ellipse(hdc, m_ptCenter.x - 3 * R, m_ptCenter.y - R, // 画左眼
m_ptCenter.x - R, m_ptCenter.y + R);
Ellipse(hdc, m_ptCenter.x - R, m_ptCenter.y - R, // 画右眼
m_ptCenter.x + R, m_ptCenter.y + R);
break;
case RIGHT:
Ellipse(hdc, m_ptCenter.x - R, m_ptCenter.y - R, // 画左眼
m_ptCenter.x + R, m_ptCenter.y + R);
Ellipse(hdc, m_ptCenter.x + R, m_ptCenter.y - R, // 画右眼
m_ptCenter.x + 3 * R, m_ptCenter.y + R);
break;
}
m_nFrame++; //准备绘制下一祯
SelectObject(hdc, oldPen); // 还原画笔
DeleteObject(pen); // 删除画笔对象
return;
}
void Enermy::action()
{
bool b = Collision(); // 判断是否发生碰撞
MakeDecision(b); // 设定方向
Catch(); // 开始抓捕
}
2.“红色敌军对象“成员定义代码
主要定义了MakeDecision()函数,随机产生该对象的方向。该函数先随机产生一个数字,根据该数字、是否撞墙、当前的朝向以及移动的方向,来产生新的朝向和移动方向。在GObject.h文件中输入以下代码:
//RedOne成员
void RedOne::Draw(HDC &hdc)
{
Enermy::Draw(hdc);
}
void RedOne::MakeDecision(bool b)
{
//srand(time(0));
int i = rand();
if(b) { // 撞到墙壁,改变方向
if(i % 4 == 0) { // 逆时针转向
m_dir == UP ? m_cmd = LEFT : m_cmd = UP; // 面向上,向左拐
}
else if(i % 3 == 0) {
m_dir == DOWN ? m_cmd = RIGHT : m_cmd = DOWN; // 面向下,向右拐
}
else if(i % 2 == 0) {
m_dir == RIGHT ? m_cmd = UP : m_cmd = RIGHT; // 面向右,向上拐
}
else {
m_dir == LEFT ? m_cmd = DOWN : m_cmd = LEFT; // 面向左,向下拐
}
return; // 提前结束函数,返回
}
// 程序运行到这里,说明没有撞墙,继续处理
if(i % 4 == 0) {
m_cmd != UP ? m_dir == DOWN : m_cmd == UP; // 非向上移动则使之面向下,否则面向上
}
else if(i % 3 == 0) {
m_dir != DOWN ? m_cmd = UP : m_cmd = DOWN; // 非向下移动则使之面向上,否则面向下
}
else if(i % 2 == 0) {
m_dir != RIGHT ? m_cmd = LEFT : m_cmd = RIGHT; // 非向右移动则使之面向左,否则面向右
}
else {
m_dir != LEFT ? m_cmd = RIGHT : m_cmd = LEFT; // 非向左移动则使之面向右,否则面向左
}
}
3.“蓝色敌军对象“成员定义代码
主要定义了MakeDecision()函数,该函数会判断玩家是否进入其警戒范围,如果在其范围内,则设定移动方向并进行追击,其他处理同红色敌军对象相同。在“GObject.h“文件输入以下代码://BlueOne成员定义
void BlueOne::Draw(HDC &hdc)
{
Enermy::Draw(hdc);
}
void BlueOne::MakeDecision(bool b)
{
const int DR = this->m_nRow - player->GetRow();
const int DA = this->m_nArray - player->GetArray();
if(!b && DR == 0) {
if(DA <= BLUE_ALERT && DA > 0) { // 玩家在左侧边警戒范围s
m_cmd = LEFT; // 向左移动
return;
}
if(DA < 0 && DA >= -BLUE_ALERT) { // 右侧警戒范围
m_cmd = RIGHT; // 向右移动
return;
}
}
if(!b && DA == 0) {
if(DR <= BLUE_ALERT && DR > 0) { // 下方警戒范围
m_cmd = UP; // 向上移动
return;
}
if(DR < 0 && DR >= -BLUE_ALERT) { // 上方警戒范围
m_cmd = DOWN; // 向下移动
return;
}
}
RedOne::MakeDecision(b); //不在追踪模式时RED行为相同
}
4.“黄色敌军对象“成员定义代码
主要定义了MakeDecision()函数,该函数会判断玩家是否进入其警戒范围,如果在其范围内,则设定移动方向并进行追击,其他处理同红色敌军对象相同。在“GObject.h“文件输入以下代码://YellowOne成员定义
void YellowOne::MakeDecision(bool b)
{
const int DR = this->m_nRow - player->GetRow();
const int DA = this->m_nArray - player->GetArray();
if(!b) {
if(DR * DR > DA * DA) {
if(DA > 0) { // 玩家在左侧边警戒范围
m_cmd = LEFT; // 向左移动
return;
}
else if(DA < 0) { // 右侧警戒范围
m_cmd = RIGHT; // 向右移动
return;
}
}
else {
if(DR > 0) { // 下方警戒范围
m_cmd = UP; // 向上移动
return;
}
if(DR < 0) { // 上方警戒范围
m_cmd = DOWN; // 向下移动
return;
}
}
}
RedOne::MakeDecision(b); // 调用红色对象的函数,实现随机移动功能
}
void YellowOne::Draw(HDC &hdc)
{
Enermy::Draw(hdc); // 绘制自身
4.5实现
使用玩家对象、敌军对象及地图对象来完成整个游戏。
- 生成游戏窗口
打开pacman.cpp文件,找到wWinMain函数,将函数内容内容删除,输入以下代码:
// 参数不再使用了
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// 初始化全局字符串
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_PACMAN, szWindowClass, MAX_LOADSTRING);
// 注册窗口类
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if(!InitInstance(hInstance, nCmdShow)) {
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_PACMAN));
- 定义相关变量
定义当前关卡、3关地图数组、玩家对象及4个敌军对象,并设定当前关卡为第一关,在pacman.cpp中接着输入:
// 当前的关卡
int s_n = 0; // [0, 1, 2]
// 地图
GMap *MapArray[STAGE_COUNT] = { new Stage_1(), new Stage_2(), new Stage_3() };
// 玩家对象
// 自己
auto g_me = std::make_shared<PacMan>(P_ROW, P_ARRAY);
// 设定四个敌人对象
auto e1 = std::make_shared<RedOne>(E_ROW, E_ARRAY); // 红色敌军对象
auto e2 = std::make_shared<RedOne>(E_ROW, E_ARRAY); // 红色敌军对象
auto e3 = std::make_shared<BlueOne>(E_ROW, E_ARRAY); // 蓝色敌军对象
auto e4 = std::make_shared<YellowOne>(E_ROW, E_ARRAY); // 黄色敌军对象
// 关卡
GObject::pStage = MapArray[s_n]; // 初始化为第一关地图
// 设定玩家
Enermy::player = g_me; // 用一个指针指向玩家对象
MSG msg;
DWORD dwLastTime = 0;
3,游戏循环
在循环中首先判断是否赢得比赛,赢则提示玩家赢得游戏;在玩家点击确认后,重设玩家和4个敌军状态并进入下一关,如果没有下一关则跳出循环,接着输入如下代码:
// 主消息循环:
// 玩家没有被抓,并且关卡<3
while(!g_me->IsOver() && s_n < STAGE_COUNT) {
// 判断是否赢得比赛
if(g_me->IsWin()) {
s_n++; // 移动到下一关
// 重设自己和敌人位置
g_me->SetPosition(P_ROW, P_ARRAY);
e1->SetPosition(E_ROW, E_ARRAY); // 设置敌军一的位置
e2->SetPosition(E_ROW, E_ARRAY); // 设置敌军二的位置
e3->SetPosition(E_ROW, E_ARRAY); // 设置敌军三的位置
e4->SetPosition(E_ROW, E_ARRAY); // 设置敌军四的位置
// 判断是否完成了3关,如果完成,退出游戏,否则进入下一关
if(s_n < 3) {
MessageBox(g_hwnd, _T("恭喜过关"), _T("吃豆子提示"), MB_OK);
GObject::pStage = MapArray[s_n]; //
RECT screenRect;
screenRect.top = 0;
screenRect.left = 0;
screenRect.right = WLENTH;
screenRect.bottom = WHIGHT;
HDC hdc = GetDC(g_hwnd); // 获取设备
std::shared_ptr<HDC__> dc(hdc, [](HDC hdc) { // 智能指针,自动管理资源
::ReleaseDC(g_hwnd, hdc);
});
::FillRect(dc.get(), &screenRect, CreateSolidBrush(RGB(255, 255, 255)));
GObject::pStage->DrawMap(hdc); // 画地图
continue; // 继续进行循环
}
else {
// 跳出循环
break;
}
}
上述代码判断玩家是否通关,如果通关则进入下一关,但当通过第三关时,跳出循环并提示玩家胜利,如果玩家失败,则跳出循环并提示玩家失败。
4.消息处理
接着输入消息处理部分,此处获取消息的函数为“PeekMessage”,该函数不同于“GetMessage”函数,前者无论是否有消息都立即返回,如果有消息,则从队列中移取该消息,而后者无消息不返回,一直停在该函数的调用处。因此如果使用“GetMessage”函数,无法形成消息循环,导致游戏停止不动,因此这里改用“PeekMessage”函数。
// 获取消息
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg); // 翻译消息
DispatchMessage(&msg); // 分发消息
}
5.游戏速度调节。
若不调节游戏速度,不同计算机的游戏速度会导致游戏运行速度差异较大。为防止这种情况,需要通过时间判断来调节速度。当两次循环的时间没有超过40毫秒时,不进行后续的操作,这样就控制了游戏的帧数为25帧/秒。
代码如下;
// 判断时间,否则画得太快
if(GetTickCount() - dwLastTime >= 40) {
dwLastTime = GetTickCount(); // 记住本次的时间
}
else {
continue; // 时间不到,本次不进行绘画
}
6.游戏画面和状态更新
先获得窗口的设备句柄,后面的对象都要画到这个设备上,接着调用地图的方法绘制豆子和地图,再调用对象的成员方法更新状态,绘制玩家对象和敌军对象。最后调用GetAsyncKeyState()函数,获取按键状态,并设定玩家状态的指令,代码如下:
{
HDC hdc = ::GetDC(g_hwnd); // 获得设备
std::shared_ptr<HDC__> dc(hdc, [](auto hdc) { // 不使用时自动释放
::ReleaseDC(g_hwnd, hdc); // 释放设备
});
MapArray[s_n]->DrawPeas(hdc); // 画豆子
MapArray[s_n]->DrawMap(hdc); // 画地图
// 画敌人及自动运动
{
e1->action(); // 敌军一的行为函数
e1->DrawBlank(hdc); // 画敌军一的空白
e1->Draw(hdc); // 画敌军一的主体部分
e2->action(); // 敌军一的行为函数
e2->DrawBlank(hdc); // 画敌军一的空白
e2->Draw(hdc); // 画敌军一的主体部分
e3->action(); // 敌军一的行为函数
e3->DrawBlank(hdc); // 画敌军一的空白
e3->Draw(hdc); // 画敌军一的主体部分
e4->action(); // 敌军一的行为函数
e4->DrawBlank(hdc); // 画敌军一的空白
e4->Draw(hdc); // 画敌军一的主体部分
}
{
// 画自己
g_me->DrawBlank(hdc);
g_me->Draw(hdc);
// 自己向前移动
g_me->action();
// 获取按键 : 控制自己的方向
if(GetAsyncKeyState(VK_DOWN) & 0x8000) { // 检测到下方向键按下
g_me->SetTwCommand(DOWN); // 设置下一步的移动方向为向下
}
if(GetAsyncKeyState(VK_LEFT) & 0x8000) { // 检测到左方向键按下
g_me->SetTwCommand(LEFT); // 设置下一步的移动方向为向左
}
if(GetAsyncKeyState(VK_RIGHT) & 0x8000) { // 检测到右方向键按下
g_me->SetTwCommand(RIGHT); // 设置下一步的移动方向为向右
}
if(GetAsyncKeyState(VK_UP) & 0x8000) { // 检测到上方向键按下
g_me->SetTwCommand(UP); // 设置下一步的移动方向为向上
}
}
}
}
至此,整个消息循环结束,游戏也进入到结束阶段。下文是结束游戏的提醒代码,根据玩家的状态使用消息框“MessageBoxA”进行不同的提示。
// 如果游戏结束
if(g_me->IsOver()) {
MessageBoxA(NULL, "出师未捷", "吃豆子提示", MB_OK);
}
// 否则,提示赢得游戏
else {
MessageBoxA(NULL, "恭喜您赢得了胜利\r\n确定后游戏退出", "吃豆子提示", MB_OK);
}
return (int) msg.wParam;
5. 结论
通过开发一个完整的游戏程序,帮助用户逐步了解事件驱动程序的编程机制,熟悉了可视化设计工具及常用控件的使用方法,掌握了能开发应用程序的基本思路和技巧,这是一个全方位的学习体验。
- 掌握严谨的工程命名规范和代码书写规范。
- 学会开发项目程序必须掌握的基础语法和相关函数。
- 掌握直接使用GDI绘图技巧。
- 获得解决编程中出现的常见错误的能力。
下面用一个思维导图对本项目中的主要知识点进行总结:
正在上传…重新上传取消
图 23
【1】郑莉,董渊,张瑞丰.C++语言设计.北京:清华大学出版社,2003
【2】陈卫卫.C++/C++程序设计教程.北京:希望电子出版社,2002
【3】余苏宁、王明福,C++程序设计 北京:高等教育出版社,2003
【4】郑振杰.C++程序设计 北京:人民邮电出版社,2005
【5】柴欣,C/C++程序设计 河北大学出版社,2002