本项目通过使用 windows 窗口应用程序 实现一个简化版的 吃豆子游戏,主要涉及的知识点包含有:面向对象编程思想、windows 消息循环的工作原理、windows 窗口应用程序实现、父类与子类的设计和使用、GDI 函数的简单了解、函数模板与动态分配的使用。

简化版 “吃豆子游戏—pacman”

需求分析:
在游戏中,玩家操作的角色是一张大嘴,游戏的目的就是玩家操作大嘴移动躲避敌人,并在移动过程中吃掉地图上所有的豆子,游戏胜利通关。游戏的地图是一张二维平面地图,并且存在墙壁和障碍物。游戏中敌人运动速度比大嘴稍慢,确保大嘴能够躲避敌人,敌人种类数量多种,分为普通敌人、保卫者、扰乱者。

项目主要包含以下文件:
1、GMap.h:地图类的声明文件
2、GMap.cpp:地图类的实现文件
3、GObject.h:物体类的声明文件
4、GObject.cpp:物体类的实现文件
5、pacman.cpp:创建主窗口,实现游戏运行的客户端

头文件:

(一)、设计一个地图类,其基本属性包含有:障碍物的尺寸、豆子的半径、大小为19 x 19的二维矩阵(逻辑地图点阵)、墙体与障碍物的颜色等,实现接口包含有:初始化敌我双方出现位置没有豆子出现、绘制地图与豆子,声明 Gobject 类和 pacman 类与本类为友元关系。最后在该地图类同个头文件下创建了三个继承 GMap 类的关卡地图类。

GMap.h

#pragma once //为避免头文件被包含多次,确保只被编译一次
#include "stdafx.h" //头文件预编译
#include <list> //导入“标准模板库”中的“链表类”头文件#define MAPLENTH 19  //定义逻辑地图大小
#define P_ROW 10 //大嘴初始逻辑位置(从0开始数)
#define P_ARRAY 9
#define E_ROW 8  //敌人初始逻辑位置
#define E_ARRAY 9using std::list; //链表命名空间//抽象类GMap
class GMap
{
protected:static int LD; //障碍物的尺寸static int PD; //豆子的半径bool mapData[MAPLENTH][MAPLENTH]; //障碍物逻辑地图点阵    bool peaMapData[MAPLENTH][MAPLENTH]; //豆子逻辑地图点阵(四个联通口也各有一颗豆子哦)COLORREF color;//墙体与障碍物的颜色void InitOP(); //初始化敌我双方出现位置没有豆子出现public:void  DrawMap(HDC& 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();
};

(二)、采用面向对象的编程思想分析物体类
面向对象的编程思想:
1、大嘴与敌人的共性:
(1)都会移动
(2)移动方向分为上下左右四个方向
(3)碰到墙壁、障碍物后都会停止
(4)都拥有自己的坐标位置
2、不同之处:
(1)大嘴是需要人为控制移动的,敌人是通过程序设定随机或有方向性的移动的
(2)大嘴能够吃豆子,敌人不能
(3)敌人能够抓住大嘴,而大嘴不能抓住敌人

依据大嘴和敌人的共性和不同之处,设计一个父类,取名为GObject,其基本属性包含有:物体在地图中的实际坐标位置、运动的朝向、帧数,实现接口包含有:判断物体是否到达逻辑坐标位置、碰撞检测、将实际坐标转换为逻辑坐标、判断物体到达逻辑坐标后更新物体在矩阵中的行列坐标等。最后在该物体类同个头文件下创建其他类,包含有: 继承 GObject 父类的大嘴类和敌人类、继承敌人类的普通敌人类、继承普通敌人类的保卫者类和扰乱者类。

GObject.h

#include "stdafx.h"
#include "GMap.h" //地图类的头文件
#include <time.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
{
protected: //保护型基本属性int mX; //物体的实际坐标位置int mY;TWARDS twCommand; //方向指令缓存POINT point; //物体在方格的中心坐标int dRow; //物体的逻辑横坐标int dArray; //逻辑纵坐标int speed; //速度TWARDS tw; //朝向int frame; //祯数//子程序bool Achive(); //判断物体是否到达逻辑坐标位置(即是否到达中方格中心)bool Collision(); //逻辑碰撞检测,将物体摆放到合理的位置int PtTransform(int k); //将实际坐标转换为逻辑坐标virtual void AchiveCtrl(); //物体到达逻辑坐标位置后更新物体在矩阵中的行列坐标位置public:void SetPosition(int Row, int Array);  //设置实际坐标位置(传入逻辑坐标位置设置实际坐标位置)void DrawBlank(HDC& hdc); //在物体位置周围绘制大小为24x24的白框void virtual Draw(HDC& hdc) = 0;//绘制物体对象,虚函数static GMap* pStage; //指向地图类的指针,设置为静态,使所有自类对象都能够使用相同的地图GObject(int Row, int Array) //构造函数{frame = 1; //初始化设置帧数为1,用于刷新大嘴的张合,像是在吃东西一样pStage = NULL;this->dRow = Row;this->dArray = Array;this->point.y = dRow * pStage->LD + pStage->LD / 2;this->point.x = dArray * pStage->LD + pStage->LD / 2;this->mX = point.x;this->mY = point.y;}void virtual action() = 0; //数据变更的表现,这是一个纯虚函数,能够阻止物体类的实例化,敌我双方移动的模板int GetRow(); //获取大嘴所在的行、列信息int GetArray();
};//玩家控制的对象:大嘴
class PacMan :public GObject
{
protected:virtual void AchiveCtrl(); //重写虚函数,若没有撞墙则吃掉豆子public:POINT GetPos(); //得到自身的位置TWARDS GetTw(); //得到自身的朝向bool Win(); //赢得胜利void Draw(HDC& hdc); //刷新大嘴,动态的哦void SetTwCommand(TWARDS command); //设置自身的朝向void Over(); //游戏失败结束PacMan(int x, int y) :GObject(x, y) //构造函数,初始化速度与朝向{this->speed = PLAYERSPEED;twCommand = tw = LEFT;}void action(); //碰撞检测
};//追捕大嘴的敌人
class Enermy :public GObject
{
protected:void Catch(); //判断是否抓住大嘴void virtual MakeDecision(bool b) = 0; //实现改变方向的函数COLORREF color; //敌人的颜色public:static PacMan* player; //声明一个pacman类的指针,设置为静态变量,必须初始化,在类的外部将它初始化为空void virtual  Draw(HDC& hdc); //用来画敌人的模板,虚函数Enermy(int x, int y) :GObject(x, y) //构造函数,初始化朝向和速度{this->speed = ENERMYSPEED;tw = LEFT;twCommand = UP;}void virtual action(); //数据变更的表现,虚函数
};class RedOne :public Enermy  //普通敌人,继承Enermy
{
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 //守卫者,继承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 //扰乱者,继承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);}
};

源文件:

(一)、地图类实现文件,设置了障碍物尺寸实际大小为36,豆子半径为3(注意这两个值是实际大小),实现:初始化敌我双方出现位置没有豆子出现,绘制墙壁,绘制豆子,重写析构函数,初始化二维矩阵,初始化自身的成员矩阵。

GMap.cpp

#include "stdafx.h"
#include "GMap.h"int GMap::LD = 36;
int GMap::PD = 3;//敌我双方出现位置没有豆子出现
void GMap::InitOP()
{peaMapData[E_ROW][E_ARRAY] = false;peaMapData[P_ROW][P_ARRAY] = false;
}void GMap::DrawMap(HDC& memDC)
{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, CreateSolidBrush(color));}}}
}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);}}}
}GMap::~GMap()
{}//Stage_1成员定义:
#define A true //定义
#define B 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,//0B, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, B,//1B, A, A, B, A, A, B, B, B, A, B, B, B, A, A, B, A, A, B,//2B, A, B, B, A, A, A, A, A, A, A, A, A, A, A, B, B, A, B,//3B, A, B, A, A, A, B, B, B, A, B, B, B, A, A, A, B, A, B,//4B, A, B, A, A, A, A, A, A, A, A, A, A, A, A, A, B, A, B,//5B, A, A, A, A, A, B, B, A, A, A, B, B, A, A, A, A, A, B,//6B, A, B, A, A, A, A, A, A, A, A, A, A, A, A, A, B, A, B,//7B, A, B, A, A, A, A, A, B, A, B, A, A, A, A, A, B, A, B,//8A, A, A, A, A, A, A, A, B, B, B, A, A, A, A, A, A, A, A,//9B, A, B, A, A, A, A, A, A, A, A, A, A, A, A, A, B, A, B,//10B, A, B, A, A, B, A, A, A, A, A, A, A, B, A, A, B, A, B,//11B, A, B, A, B, B, B, A, A, A, A, A, B, B, B, A, B, A, B,//12B, A, A, A, A, B, A, A, A, A, A, A, A, B, A, A, A, A, B,//13B, A, B, B, A, A, A, A, A, A, A, A, A, A, A, B, B, A, B,//14B, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, B,//15B, A, A, A, A, B, B, B, A, B, A, B, B, B, A, A, A, A, B,//16B, 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 BStage_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();
}//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,//0A, A, A, A, A, A, A, B, A, A, B, A, A, A, B, A, B, A, A,//1B, A, A, A, B, A, A, B, A, A, B, A, B, A, B, A, B, A, B,//2B, B, B, A, B, A, A, B, B, A, B, A, B, A, B, A, B, B, B,//3B, A, A, A, A, A, A, A, A, A, A, A, B, B, B, A, A, A, B,//4B, A, A, B, A, A, A, A, A, A, A, A, A, A, A, A, A, A, B,//5B, A, A, B, A, A, A, B, B, B, B, B, B, A, A, B, A, A, B,//6B, A, A, B, A, B, A, A, A, A, A, A, A, A, A, B, A, A, B,//7B, A, A, B, A, B, A, A, B, A, B, A, A, B, A, B, A, A, B,//8A, A, A, B, A, B, A, A, B, B, B, A, A, B, A, B, A, A, A,//9B, A, A, B, A, B, A, A, A, A, A, A, A, B, A, A, A, A, B,//10B, A, A, B, A, A, A, B, B, B, B, B, A, B, A, A, A, A, B,//11B, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, B,//12B, A, A, A, B, B, B, B, B, B, B, A, A, A, A, A, A, A, B,//13B, A, A, A, A, A, A, A, A, A, A, A, A, B, A, A, A, A, B,//14B, B, B, B, B, A, A, A, A, B, B, B, A, B, A, A, A, A, B,//15B, A, A, A, B, B, B, A, A, A, A, B, A, B, B, B, A, A, B,//16A, 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 BStage_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();
}//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,//0A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A,//1B, A, A, B, A, A, B, B, B, B, B, B, B, A, A, A, B, A, B,//2B, A, B, B, A, A, A, A, A, A, A, A, B, A, A, A, B, A, B,//3B, A, B, A, A, A, B, B, B, B, B, B, B, A, A, A, B, A, B,//4B, A, B, A, B, B, B, A, A, A, A, A, B, B, B, A, B, A, B,//5B, A, A, A, B, A, B, A, A, A, A, A, A, A, A, A, B, A, B,//6B, A, B, A, B, A, A, A, A, A, A, A, A, B, A, A, B, A, B,//7B, A, B, A, B, B, A, A, B, A, B, A, A, B, A, A, B, A, B,//8B, A, A, A, A, B, A, A, B, B, B, A, A, B, A, A, B, A, B,//9B, A, B, A, A, B, A, A, A, A, A, A, A, B, A, A, A, A, B,//10B, A, B, A, A, B, A, A, A, A, A, A, B, B, B, A, B, A, B,//11B, A, B, A, A, B, A, B, B, B, B, B, B, A, B, A, B, A, B,//12B, A, B, A, A, B, A, A, A, A, A, A, A, A, B, A, B, A, B,//13B, A, B, B, A, B, B, B, B, B, B, A, B, A, B, A, B, A, B,//14B, A, A, A, A, B, A, A, A, A, A, A, B, A, B, A, B, A, B,//15B, B, B, B, B, B, A, A, B, B, B, A, B, A, B, A, B, A, B,//16A, 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 BStage_3::Stage_3()
{color = RGB(100, 300, 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();
}

(二)、物体类的实现文件,实现接口包含有:初始化地图类指针变量,获取物体行、列位置,判断物体是否到达逻辑坐标位置、碰撞检测、将实际坐标转换为逻辑坐标、判断物体到达逻辑坐标后更新物体在矩阵中的行列坐标等、绘制大嘴,绘制敌人,初始化在Enermy内部实现的pacman类的指针,抓捕函数实现,敌人的运动实现,即信息变更实现,普通敌人、守卫者、扰乱者的不同移动方式等等,blablabla一大堆,在代码中我认为需要注释的地方全部都注释了,相信所有读者了解面向对象思想的都应该看得懂。

#include "stdafx.h"
#include "GObject.h"//GOject成员定义:
GMap* GObject::pStage = NULL;int GObject::GetRow()
{return dRow;
}int GObject::GetArray()
{return dArray;
}int GObject::PtTransform(int k)
{return (k - (pStage->LD) / 2) / pStage->LD;
}bool GObject::Achive()
{int n = (point.x - pStage->LD / 2) % pStage->LD;int k = (point.y - pStage->LD / 2) % pStage->LD;bool l = (n == 0 && k == 0);return l;
}void GObject::AchiveCtrl()
{if (Achive()){dArray = PtTransform(point.x);//更新列dRow = PtTransform(point.y);//更新行}
}void GObject::DrawBlank(HDC& hdc)
{RECT rect;rect.top = mY - RD;rect.left = mX - RD;rect.right = mX + RD;rect.bottom = mY + RD;FillRect(hdc, &rect, ::CreateSolidBrush(RGB(255, 255, 255)));
}void GObject::SetPosition(int Row, int Array)
{dRow = Row;dArray = Array;this->point.y = dRow*pStage->LD + pStage->LD / 2;this->point.x = dArray*pStage->LD + pStage->LD / 2;
}bool GObject::Collision()
{bool b = false;AchiveCtrl();//更新行、列的数据,如果是大嘴,则会执行PacMan重写的AchiveCtrl函数消除豆子//判断指令的有效性if (dArray < 0 || dRow < 0 || dArray > MAPLENTH - 1 || dRow > MAPLENTH - 1){b = true;}else if (Achive()){switch (twCommand) //判断物体行进的方向{case LEFT:if (dArray > 0 && !pStage->mapData[dRow][dArray - 1]) //判断下一个格子是否能够通行{b = true; //指令无效}break;//以下方向的判断原理相同case RIGHT:if (dArray < MAPLENTH - 1 && !pStage->mapData[dRow][dArray + 1]){b = true;}break;case UP:if (dRow > 0 && !pStage->mapData[dRow - 1][dArray]){b = true;}break;case DOWN:if (dRow < MAPLENTH - 1 && !pStage->mapData[dRow + 1][dArray]){b = true;}break;}if (!b){tw = twCommand; //没撞墙,指令成功}}//依照真实的方向位移mX = point.x; //得到实际位置坐标mY = point.y;int MAX = pStage->LD * MAPLENTH + pStage->LD / 2; //四个通口值int MIN = pStage->LD / 2; 四个通口值switch (tw) //判断行进的方向{case LEFT:if (dArray > 0 && !pStage->mapData[dRow][dArray - 1]) //判断下一个格子是否能够通行{b = true;break;//"撞墙了"}point.x -= speed;if (point.x<MIN){point.x = MAX; //越过屏幕到另一端出现}break;//以下方向的判断原理相同case RIGHT:if (dArray<MAPLENTH - 1 && !pStage->mapData[dRow][dArray + 1]){b = true;break;//"撞墙了"}point.x += speed;if (point.x>MAX){point.x = MIN;}break;case UP:if (dRow>0 && !pStage->mapData[dRow - 1][dArray]){b = true;break;//"撞墙了"}point.y -= speed;if (point.y<MIN){point.y = MAX;}break;case DOWN:if (dRow<MAPLENTH - 1 && !pStage->mapData[dRow + 1][dArray]){b = true;break;//"撞墙了"}point.y += speed;if (point.y>MAX){point.y = MIN;}break;}return b;
}//PacMan成员定义:
void PacMan::AchiveCtrl()
{GObject::AchiveCtrl();if (Achive()){if (dRow >= 0 && dRow<MAPLENTH&&dArray >= 0 && dArray<MAPLENTH)//防止数组越界{if (pStage->peaMapData[dRow][dArray]){pStage->peaMapData[dRow][dArray] = false;}}}
}void PacMan::action()
{Collision();
}void PacMan::SetTwCommand(TWARDS command)
{twCommand = command;
}bool PacMan::Win()
{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 point;
}void PacMan::Over()
{tw = OVER;
}TWARDS PacMan::GetTw()
{return tw;
}void PacMan::Draw(HDC& memDC) //绘制大嘴
{if (tw == OVER) //被抓住,游戏结束{}else if (frame % 2 == 0) //第4祯动画与第2祯动画{int x1 = 0, x2 = 0, y1 = 0, y2 = 0;int offsetX = DISTANCE / 2 + D_OFFSET; //设置弧弦交点:6int offsetY = DISTANCE / 2 + D_OFFSET; switch (tw){case UP:x1 = point.x - offsetX;x2 = point.x + offsetX;y2 = y1 = point.y - offsetY;break;case DOWN:x1 = point.x + offsetX;x2 = point.x - offsetX;y2 = y1 = point.y + offsetY;break;case LEFT:x2 = x1 = point.x - offsetX;y1 = point.y + offsetY;y2 = point.y - offsetY;break;case RIGHT:x2 = x1 = point.x + offsetX;y1 = point.y - offsetY;y2 = point.y + offsetY;break;}Arc(memDC, point.x - DISTANCE, point.y - DISTANCE,point.x + DISTANCE, point.y + DISTANCE,x1, y1,x2, y2); //画圆弧,内切圆,以x1y1,x2y2逆时针画MoveToEx(memDC, x1, y1, NULL); //将绘图位置移动到点x1y1LineTo(memDC, point.x, point.y); //画x1y1到实际位置坐标直线LineTo(memDC, x2, y2); //再到x2y2}else if (frame % 3 == 0) //第3帧动画{Ellipse(memDC, point.x - DISTANCE, point.y - DISTANCE,point.x + DISTANCE, point.y + DISTANCE);}else {int x1 = 0, x2 = 0, y1 = 0, y2 = 0;switch (tw){case UP:x1 = point.x - DISTANCE;x2 = point.x + DISTANCE;y2 = y1 = point.y;break;case DOWN:x1 = point.x + DISTANCE;x2 = point.x - DISTANCE;y2 = y1 = point.y;break;case LEFT:x2 = x1 = point.x;y1 = point.y + DISTANCE;y2 = point.y - DISTANCE;break;case RIGHT:x2 = x1 = point.x;y1 = point.y - DISTANCE;y2 = point.y + DISTANCE;break;}Arc(memDC, point.x - DISTANCE, point.y - DISTANCE,point.x + DISTANCE, point.y + DISTANCE,x1, y1,x2, y2);MoveToEx(memDC, x1, y1, NULL);LineTo(memDC, point.x, point.y);LineTo(memDC, x2, y2);}frame++; //绘制下一祯
}//Enermy成员定义:
PacMan* Enermy::player = NULL; //在Enermy内部实现一个pacman类的指针,由于是静态变量,所以必须初始化void Enermy::Catch()
{int DX = point.x - player->GetPos().x;int DY = point.y - player->GetPos().y;if ((-RD < DX && DX < RD)  &&  (-RD < DY && DY < RD)){player->Over(); //被抓住,游戏失败结束}
}void Enermy::Draw(HDC& hdc) //画敌人
{HPEN pen = ::CreatePen(0, 0, color); //创建一个画笔实例HPEN oldPen = (HPEN)SelectObject(hdc, pen); Arc(hdc, point.x - DISTANCE, point.y - DISTANCE,point.x + DISTANCE, point.y + DISTANCE,point.x + DISTANCE, point.y,point.x - DISTANCE, point.y); //画了半圆型的头int const LEGLENTH = (DISTANCE) / (LEGCOUNTS);//根据祯数来绘制身体和“腿部”if (frame % 2 == 0){MoveToEx(hdc, point.x - DISTANCE, point.y, NULL); //矩形的身子LineTo(hdc, point.x - DISTANCE, point.y + DISTANCE - LEGLENTH);MoveToEx(hdc, point.x + DISTANCE, point.y, NULL);LineTo(hdc, point.x + DISTANCE, point.y + DISTANCE - LEGLENTH);//从左往右绘制“腿部”for (int i = 0; i<LEGCOUNTS; i++) {Arc(hdc, point.x - DISTANCE + i * 2 * LEGLENTH, point.y + DISTANCE - 2 * LEGLENTH,point.x - DISTANCE + (i + 1) * 2 * LEGLENTH, point.y + DISTANCE,point.x - DISTANCE + i * 2 * LEGLENTH, point.y + DISTANCE - LEGLENTH,point.x - DISTANCE + (i + 1) * 2 * LEGLENTH, point.y + DISTANCE - LEGLENTH);}}else{MoveToEx(hdc, point.x - DISTANCE, point.y, NULL); //绘制身体LineTo(hdc, point.x - DISTANCE, point.y + DISTANCE);MoveToEx(hdc, point.x + DISTANCE, point.y, NULL);LineTo(hdc, point.x + DISTANCE, point.y + DISTANCE);//从左往右绘制“腿部”MoveToEx(hdc, point.x - DISTANCE, point.y + DISTANCE, NULL);LineTo(hdc, point.x - DISTANCE + LEGLENTH, point.y + DISTANCE - LEGLENTH);for (int i = 0; i<LEGCOUNTS - 1; i++){Arc(hdc, point.x - DISTANCE + (1 + i * 2)*LEGLENTH, point.y + DISTANCE - 2 * LEGLENTH,point.x - DISTANCE + (3 + i * 2)*LEGLENTH, point.y + DISTANCE,point.x - DISTANCE + (1 + i * 2)*LEGLENTH, point.y + DISTANCE - LEGLENTH,point.x - DISTANCE + (3 + i * 2)*LEGLENTH, point.y + DISTANCE - LEGLENTH);}MoveToEx(hdc, point.x + DISTANCE, point.y + DISTANCE, NULL);LineTo(hdc, point.x + DISTANCE - LEGLENTH, point.y + DISTANCE - LEGLENTH);}//根据方向绘制眼睛int R = DISTANCE / 5; //眼睛的半径switch (tw) //依据行进的朝向画不同朝向的眼睛{case UP: Ellipse(hdc, point.x - 2 * R, point.y - 2 * R,point.x, point.y);Ellipse(hdc, point.x, point.y - 2 * R,point.x + 2 * R, point.y);break;case DOWN:Ellipse(hdc, point.x - 2 * R, point.y, point.x, point.y + 2 * R);Ellipse(hdc, point.x, point.y, point.x + 2 * R, point.y + 2 * R);break;case LEFT:Ellipse(hdc, point.x - 3 * R, point.y - R,point.x - R, point.y + R);Ellipse(hdc, point.x - R, point.y - R,point.x + R, point.y + R);break;case RIGHT:Ellipse(hdc, point.x - R, point.y - R,point.x + R, point.y + R);Ellipse(hdc, point.x + R, point.y - R,point.x + 3 * R, point.y + R);break;}frame++; //准备绘制下一祯SelectObject(hdc, oldPen);DeleteObject(pen); //释放画笔资源return;
}void Enermy::action() //移动
{bool b = Collision(); //碰撞检测MakeDecision(b); //有障碍物则改变移动方向Catch(); //捕抓检测
}//RedOne成员,普通敌人
void RedOne::Draw(HDC& hdc)
{Enermy::Draw(hdc);
}void RedOne::MakeDecision(bool b)
{int i = rand();if (b) //撞到墙壁,改变方向{//逆时针转向if (i % 4 == 0){tw == UP ? twCommand = LEFT : twCommand = UP;}else if (i % 3 == 0){tw == DOWN ? twCommand = RIGHT : twCommand = DOWN;}else if (i % 2 == 0){tw == RIGHT ? twCommand = UP : twCommand = RIGHT;}else{tw == LEFT ? twCommand = DOWN : twCommand = LEFT;}return;}if (i % 4 == 0) //没有碰到墙壁{twCommand != UP ? tw == DOWN : twCommand == UP;}else if (i % 3 == 0){tw != DOWN ? twCommand = UP : twCommand = DOWN;}else if (i % 2 == 0){tw != RIGHT ? twCommand = LEFT : twCommand = RIGHT;}else{tw != LEFT ? twCommand = RIGHT : twCommand = LEFT;}
}//BlueOne成员定义 ,保卫者
void BlueOne::Draw(HDC& hdc)
{Enermy::Draw(hdc);
}void BlueOne::MakeDecision(bool b)
{const int DR = this->dRow - player->GetRow(); //获取与大嘴的逻辑距离const int DA = this->dArray - player->GetArray();if (!b && DR == 0){if (DA <= BLUE_ALERT && DA > 0) //玩家在左侧边警戒范围s{twCommand = LEFT;  //向左移动return;}if (DA < 0 && DA >= -BLUE_ALERT) //右侧警戒范围{twCommand = RIGHT; //向右移动return;}}if (!b && DA == 0){if (DR <= BLUE_ALERT&&DR>0) //下方警戒范围{twCommand = UP;return;}if (DR < 0 && DR >= -BLUE_ALERT) //上方警戒范围{twCommand = DOWN;return;}}RedOne::MakeDecision(b); //不在追踪模式时RED行为相同
}//YellowOne成员定义,扰乱者
void YellowOne::Draw(HDC& hdc)
{Enermy::Draw(hdc);
}void YellowOne::MakeDecision(bool b)
{const int DR = this->dRow - player->GetRow();const int DA = this->dArray - player->GetArray();if (!b){if (DR * DR > DA * DA){if (DA>0) //玩家在左侧边警戒范围{twCommand = LEFT; //向左移动return;}else if (DA<0) //右侧警戒范围{twCommand = RIGHT; //向右移动return;}}else{if (DR > 0) //下方警戒范围{twCommand = UP;return;}if (DR < 0) //上方警戒范围{twCommand = DOWN;return;}}}RedOne::MakeDecision(b); //不在追踪模式时RED行为相同
}

(三)、pacman的主程序实现文件,主要难点是 需要对 windows 的消息循环工作原理有一定的了解,讲起来相对较复杂,读者如果看不懂,可以看看创建窗口应用程序并未添加任何文件时,参考相关windows窗口应用程序的讲解看一看一个空白窗口的工作原理,对后面的编程理解有一定的帮助。

// pacman.cpp : 定义应用程序的入口点。#include "stdafx.h"
#include "pacman.h"
#include "GObject.h"#define WLENTH 700 //窗口应用程序的大小
#define WHIGHT 740
#define STAGE_COUNT 3 //关卡总的数量#define MAX_LOADSTRING 100// 全局变量:
HINSTANCE hInst;  //当前实例
TCHAR szTitle[MAX_LOADSTRING]; //标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING]; //主窗口类名//游戏物体
PacMan* p; //大嘴
GObject* e1; //敌人1234
GObject* e2;
GObject* e3;
GObject* e4;//在程序中使用了动态分配,需要使用堆内存回收,定义一个释放动态内存函数模板,传入指针变量即可实现对它所指向的堆内存回收
template<class T>
void Realese(T t)
{if (t != NULL)delete t;
}// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance); //注册是咧
BOOL InitInstance(HINSTANCE, int, HWND&); //这里改动原本函数,增加了hwnd来获得窗口的句柄
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);void ResetGObjects();//主程序入口
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPTSTR    lpCmdLine,_In_ int       nCmdShow) //HINSTANCE代表的是程序的实例,第一个参数代表本程序的实例,第二个参数代表上一个程序的实例。//...它是系统用来管理程序而使用的标识。第三和第四个参数表示的是控制台参数的设置。
{UNREFERENCED_PARAMETER(hPrevInstance); //因为hprevInstance和lpCmdLine这两个参数几乎不会被用到,所以这里是使用一个UNREFERENCED_PARAMETER(lpCmdLine); //UNREFERENCED_PARA                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    METER的宏,用来忽略来自编译器的“未使用过的参数”警报// TODO:  在此放置代码。MSG msg; //MSG是一个结构体,它是windows消息的记录形式,而下面的HACCEL是窗口中的热键表。HACCEL hAccelTable; //窗口中的热键表int s_n = 0; //进行到的关卡数p = new PacMan(P_ROW, P_ARRAY);e1 = new RedOne(E_ROW, E_ARRAY);e2 = new RedOne(E_ROW, E_ARRAY);e3 = new BlueOne(E_ROW, E_ARRAY);e4 = new YellowOne(E_ROW, E_ARRAY);GMap* MapArray[STAGE_COUNT] = { new Stage_1(), new Stage_2(), new Stage_3() };GObject::pStage = MapArray[s_n]; //初始化为第一关地图Enermy::player = p;// 初始化全局字符串LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); //LoadString这个函数是将程序一开始定义的两个全局字符串变量szTitle、LoadString(hInstance, IDC_PACMAN, szWindowClass, MAX_LOADSTRING); //szWindowsClass,初始化为String Table中对应ID的字符串的值。MyRegisterClass(hInstance);//窗口类// 执行应用程序初始化:HWND hWnd;if (!InitInstance(hInstance, nCmdShow, hWnd)){return FALSE;}hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_PACMAN));DWORD t = 0;//主消息循环:while (p->GetTw() != OVER && s_n < 3){if (p->Win()) //通关提示{HDC hdc = GetDC(hWnd);//获取窗口句柄s_n++; //下一关ResetGObjects(); //重新注册大嘴和敌人1234if (s_n <3){MessageBoxA(hWnd, "恭喜您过关", "吃豆子提示", MB_OK);GObject::pStage = MapArray[s_n];//下一张地图RECT screenRect;//窗口的位置screenRect.top = 0;screenRect.left = 0;screenRect.right = WLENTH;screenRect.bottom = WHIGHT;::FillRect(hdc, &screenRect, CreateSolidBrush(RGB(255, 255, 255)));GObject::pStage->DrawMap(hdc);}continue;}if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) // 获得消息队列{TranslateMessage(&msg);DispatchMessage(&msg);}if (GetAsyncKeyState(VK_DOWN) & 0x8000) //获得键盘状态的API{p->SetTwCommand(DOWN);}if (GetAsyncKeyState(VK_LEFT) & 0x8000){p->SetTwCommand(LEFT);}if (GetAsyncKeyState(VK_RIGHT) & 0x8000){p->SetTwCommand(RIGHT);}if (GetAsyncKeyState(VK_UP) & 0x8000){p->SetTwCommand(UP);}else{//每58毫秒游戏的数据和画面更新一次if (GetTickCount() - t > 58) //获取从开机到当前时刻机器与运行的毫秒数,在消息循环外使用一个无符号长整型变量t存储游戏计时{HDC hdc = GetDC(hWnd);e1->action();e2->action();e3->action();e4->action();p->action();GObject::pStage->DrawPeas(hdc);e1->DrawBlank(hdc);e2->DrawBlank(hdc);e3->DrawBlank(hdc);e4->DrawBlank(hdc);p->DrawBlank(hdc);e1->Draw(hdc);e2->Draw(hdc);e3->Draw(hdc);e4->Draw(hdc);p->Draw(hdc);DeleteDC(hdc);t = GetTickCount();}}}Realese(e1);//释放内存Realese(e2);Realese(e3);Realese(e4);for (int i = 0; i < STAGE_COUNT; i++){Realese(MapArray[i]);}if (p->GetTw() == OVER){MessageBoxA(hWnd, "出师未捷", "吃豆子提示", MB_OK);}else{MessageBoxA(hWnd, "恭喜您赢得了胜利", "吃豆子提示", MB_OK);}Realese(p);return (int)msg.wParam;
}//
//  函数:  MyRegisterClass()
//
//  目的:  注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{WNDCLASSEX wcex; //WNDCLASSEX是一个结构体,它是我们使用的窗口类。wcex.cbSize = sizeof(WNDCLASSEX); //窗口类结构体所占大小。wcex.style = CS_HREDRAW | CS_VREDRAW; //窗口的样式,CS_HREDRAW | CS_VREDRAW代表了窗口在水平和竖直方向运动时,窗口的会面重绘。wcex.lpfnWndProc = WndProc;wcex.cbClsExtra = 0;wcex.cbWndExtra = 0;wcex.hInstance = hInstance; //本应用程序的实例wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PACMAN)); //程序图标wcex.hCursor = LoadCursor(NULL, IDC_ARROW); //鼠标图标wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); //背景色wcex.lpszMenuName = MAKEINTRESOURCE(IDC_PACMAN); //菜单的名称wcex.lpszClassName = szWindowClass; //窗口类的名称wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); //窗口小图标return RegisterClassEx(&wcex); //注册窗口类
}//
//   函数: InitInstance(HINSTANCE, int)
//
//   目的: 保存实例句柄并创建主窗口
//
//   注释:
//
//        在此函数中,我们在全局变量中保存实例句柄并
//        创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow, HWND& hWnd)
{hInst = hInstance; // 将实例句柄存储在全局变量中hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,0, 0, WLENTH, WHIGHT, NULL, NULL, hInstance, NULL);if (!hWnd){return FALSE;}ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);return TRUE;
}//
//  函数:  WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的:    处理主窗口的消息。
//
//  WM_COMMAND  - 处理应用程序菜单
//  WM_PAINT    - 绘制主窗口
//  WM_DESTROY  - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{int wmId, wmEvent;PAINTSTRUCT ps;HDC hdc;switch (message){case WM_COMMAND:wmId = LOWORD(wParam);wmEvent = HIWORD(wParam);// 分析菜单选择: switch (wmId){case IDM_ABOUT:DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);break;case IDM_EXIT:DestroyWindow(hWnd);break;default:return DefWindowProc(hWnd, message, wParam, lParam); //windows API函数,默认的窗口过程函数}break;case WM_PAINT:hdc = BeginPaint(hWnd, &ps);// TODO:  在此添加任意绘图代码...EndPaint(hWnd, &ps);break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;
}// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{UNREFERENCED_PARAMETER(lParam);switch (message){case WM_INITDIALOG:return (INT_PTR)TRUE;case WM_COMMAND:if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL){EndDialog(hDlg, LOWORD(wParam));return (INT_PTR)TRUE;}break;}return (INT_PTR)FALSE;
}void ResetGObjects() //玩家获得上一张图的胜利后,分为两种情况:进入下一关或者游戏结束
{p->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);
}

游戏结果展示:
(一)、gif动态图

(二)、运行结果说明:
简化版 “吃豆子游戏—pacman” 实现一般,游戏运行时经常会出现bug,但有时又能正常运行成功。本项目项目作为C++较难一点的入门项目,难度适中,能够使读者对面向对象编程思想,类的各种性质:封装、多态、继承,虚函数,构造函数,析构函数,内存控制等的知识点更加熟悉。

C++项目实战(二)——简化版 “吃豆子游戏---pacman” 实现相关推荐

  1. Taro多端开发实现原理与项目实战(二)

    Taro多端开发实现原理与项目实战(二) 多端电商平台项目概述及开发准备 学习了前面的基础知识和进阶后是否跃跃欲试?我们准备了一个电商平台的项目来和大家一起实践使用 Taro 开发电商平台. 项目概述 ...

  2. c语言期中项目实战二—简易扫雷,思路分析加代码详细注释

    c语言期中项目实战二-简易扫雷,思路分析+代码详细注释 游戏介绍 项目步骤 模块化编程 设置菜单 设置棋盘 打印棋盘 布置雷 排查雷 总结及总代码和详细注释 游戏介绍 扫雷这个经典游戏,直到现在仍有很 ...

  3. 吃豆豆游戏的C语言程序码,C++实现基于控制台界面的吃豆子游戏

    本文实例讲述了C++实现基于控制台界面的吃豆子游戏.分享给大家供大家参考.具体分析如下: 程序运行界面如下所示: ESC键可退出游戏. main.cpp源文件如下: #include "li ...

  4. 厉害的程序员都有吊炸天的实力,C++实现吃豆子游戏!

    作为"全世界卖得最多的街机游戏","吃豆人"大家再熟悉不过了.但它是"现代游戏AI鼻祖"这件事,恐怕知道的人不多. 这篇文章主要介绍了C++ ...

  5. flutter 项目实战二 网络请求

    本项目借用 逛丢 网站的部分数据,仅作为 flutter 开发学习之用. 逛丢官方网址:https://guangdiu.com/ flutter windows开发环境设置 flutter 项目实战 ...

  6. c#吃豆子游戏,模仿百度在线应用吃豆子

    这是一款模仿百度在线吃豆子的应用,本人对于C#小游戏制作的理解不是很深.CSDN里面用c#做的吃豆子也实在是太少,有的也太过于简单,于是萌生了一个念头,做一款C#吃豆子游戏,希望互相学习. csdn下 ...

  7. C++小项目-吃豆子游戏

    GMap.h #pragma once //保证头文件只被编译一次#include "stdafx.h"#define MAP_LEN 19 //逻辑地图大小 (逻辑地图由行.列各 ...

  8. C语言项目实战:《打字母游戏》零基础项目丨183 行源代码示例

    这篇文章主要为大家详细介绍了C语言实现--<打字练习系统>,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下! 游戏介绍: <字母游戏>是一款敏捷打 ...

  9. 强化学习实战 --- 用DQN让Ai玩吃豆子游戏吧

    前景介绍 上期文章介绍TensorFlow入门基础篇,本意就是给介绍强化学习做一篇前置. 本期我们将尝试利用深度强化学习来让神经网络学习自动地玩一款经典的吃豆人小游戏.让我们愉快地开始吧~ 吃豆人小游 ...

最新文章

  1. 2022-2028年中国硫化橡胶粉行业市场发展调研及竞争战略分析报告
  2. go-kit微服务学习-官方示例stringsvc学习
  3. 实际操作_MFC修改控件的Tab顺序
  4. shiro登陆流程源码详解
  5. c#利用三层架构做一个简单的登录窗体
  6. java 输出数据类型_输入输出和java数据类型
  7. 994. 腐烂的橘子
  8. 【VRP】基于matlab遗传算法求解出租车网约车接送客车辆路径规划问题【含Matlab源码 YC003期】
  9. 统计项目代码行数工具,如何统计代码行数。
  10. 接口压力测试工具JMeter
  11. 谷歌浏览器怎么下载网页视频 网页视频下载方法分享
  12. cisco privilege权限
  13. uniApp实现选择图片裁剪设置用户头像
  14. 【考研数学】琴生不等式
  15. kafka 创建 topic 报错 Error: Exception thrown by the agent : java.rmi.server.ExportException: Port alrea
  16. “百度杯”CTF比赛 十一月场Fuzz
  17. 读论文-OVSeg-基于遮罩自适应CLIP的开放词汇语义分割-Open-vicabulr semantic segmentation with mask-adaptived CLIP
  18. 华为Ascend昇腾计算产业介绍
  19. 用新浪SAE免费搭建自己的应用
  20. Python中的排除sort函数的参数key:a.sort(key=lambda x: x[1])是什么意思

热门文章

  1. mysql中DQL操作
  2. 使用canvas截图跨域导致的报错(Failed to execute ‘toDataURL‘ on ‘HTMLCanvasElement)
  3. Postman系列(七)-查看接口响应
  4. 字节跳动取消大小周,程序员们有何意见?
  5. Stata:交乘项的对称效应与图示
  6. 参议院和众议院的区别
  7. mysql安装mysql-5.7.35-1.el7.x86_64.rpm-bundle.tar,问题及其他ip访问mysql
  8. VBA快速取消EXCEL各工作表的自动筛选
  9. qps,tps,TP50,TP90,TP99,TP999详解
  10. iOS 简单动画效果