c++ GUI应用---吃豆人游戏(Visual Studio 2019)

源码地址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)地图及关卡制作

  1. 打开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种不同种类的敌军对象的声明:

  1. 红色敌军声明:构造函数中设定颜色为RGB(255,0,0);
  2. 蓝色敌军声明:构造函数中设定颜色为RGB(0,0,255);
  3. 黄色敌军声明:构造函数中设定颜色为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对象的实现代码,主要功能包括:

  1. 位置调整函数AchiveCtrl():将物体摆放到合理的位置;
  2. DrawBlank():绘制空白区域;
  3. 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实现

使用玩家对象、敌军对象及地图对象来完成整个游戏。

  1. 生成游戏窗口

打开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));

  1. 定义相关变量

定义当前关卡、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. 结论

通过开发一个完整的游戏程序,帮助用户逐步了解事件驱动程序的编程机制,熟悉了可视化设计工具及常用控件的使用方法,掌握了能开发应用程序的基本思路和技巧,这是一个全方位的学习体验。

  1. 掌握严谨的工程命名规范和代码书写规范。
  2. 学会开发项目程序必须掌握的基础语法和相关函数。
  3. 掌握直接使用GDI绘图技巧。
  4. 获得解决编程中出现的常见错误的能力。

下面用一个思维导图对本项目中的主要知识点进行总结:

正在上传…重新上传取消

图 23

参考文献

【1】郑莉,董渊,张瑞丰.C++语言设计.北京:清华大学出版社,2003

【2】陈卫卫.C++/C++程序设计教程.北京:希望电子出版社,2002

【3】余苏宁、王明福,C++程序设计 北京:高等教育出版社,2003

【4】郑振杰.C++程序设计 北京:人民邮电出版社,2005

【5】柴欣,C/C++程序设计 河北大学出版社,2002

  • 4
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Visual Studio 2022支持使用MASM(Microsoft Macro Assembler)进行x64汇编开发。下面是一些关于在Visual Studio 2022中使用MASM x64汇编的基本步骤: 1. 安装Visual Studio 2022:首先,确保你已经安装了最新版本的Visual Studio 2022。你可以从官方网站下载并安装Visual Studio 2022。 2. 创建一个新的空白项目:打开Visual Studio 2022,创建一个新的空白项目。选择合适的语言和平台(如C++和Windows),然后点击“下一步”。 3. 添加汇编源文件:在解决方案资源管理器中,右键单击项目,选择“添加” -> “新建项”。在弹出的对话框中,选择“汇编源文件”并命名它。这将为你创建一个以.asm为扩展名的汇编源文件。 4. 配置项目属性:右键单击项目,选择“属性”。在属性页中,选择“配置属性” -> “常规”。确保“字符集”设置为“使用多字节字符集”。 5. 编写汇编代码:打开汇编源文件,并开始编写你的x64汇编代码。使用MASM语法编写代码,并确保按照正确的指令格式和语义进行编写。 6. 构建和运行:保存汇编源文件后,按下F7键或选择“生成” -> “生成解决方案”来构建项目。如果没有编译错误,你可以运行生成的可执行文件来执行你的汇编代码。 这只是一个简单的指南,帮助你入门使用MASM x64汇编语言在Visual Studio 2022中进行开发。你可以查阅更多的文档和教程来深入了解和扩展你的汇编开发经验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无处安放的小曾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值