Eaxyx图形库:C++入门级小游戏开发

目录

1. 项目简介

涉及知识

2. 环境配置

开发工具

3. 核心功能

函数总结

库函数

画布大小

数据存储

绘制格子

绘制棋子

获胜检测

平局检测

绘制提示信息

交互设计

帧速率控制

主循环

5. 项目总结

学习收获

未来改进方向

6.参考文档


1. 项目简介


涉及知识

    主要涉及:

  1. C++ 基础 - 使用标准库
  2. 图形编程 - 使用 `graphics.h` 进行图形绘制
  3. 数据结构 - 使用二维数组表示棋盘
  4. 事件处理 - 处理鼠标点击事件
  5. 游戏逻辑 - 检测胜利和平局条件
  6. 绘制棋子 - 根据棋子类型绘制图形
  7. 文本绘制 - 显示当前棋子类型
  8. 时间控制 - 控制帧率
  9. 批量绘制 - 优化绘图性能
  10. 消息框 - 显示游戏结果
  11. 常量定义 - 定义窗口尺寸常量
  12. 字符集兼容性 - 处理字符串以支持 ANSI 和 Unicode

2. 环境配置


开发工具

  1. 开发环境:Microsoft Visual Studio Community 2022 版本 17.10.3
  2. EasyX库:超简单EasyX 全过程不超过一分钟

3. 核心功能

  • 函数总结

  1. initgraph();  初始化窗口

    HWND initgraph(
    	int width,        //宽度
    	int height,       //高度
    	int flag = NULL   //窗口样式 默认NULL
    );
  2. 绘图函数仅作了解使用,深入理解请期待后续内容
    BeginBatchDraw();  //开始批量绘制
    cleardevice();  //清空画布
    FlushBatchDraw();  //执行未完成的绘制任务
    EndBatchDraw();  //结束批量绘制

  3. Exmessage结构体(仅作简单使用,无需深入理解)

    struct ExMessage
    {
    	USHORT message;					// 消息标识
    	union
    	{
    		// 鼠标消息的数据
    		struct
    		{
    			bool ctrl		:1;		// Ctrl 键是否按下
    			bool shift		:1;		// Shift 键是否按下
    			bool lbutton	:1;		// 鼠标左键是否按下
    			bool mbutton	:1;		// 鼠标中键是否按下
    			bool rbutton	:1;		// 鼠标右键
    			short x;				// 鼠标的 x 坐标
    			short y;				// 鼠标的 y 坐标
    			short wheel;			// 鼠标滚轮滚动值,为 120 的倍数
    		};
    
    		// 按键消息的数据
    		struct
    		{
    			BYTE vkcode;			// 按键的虚拟键码
    			BYTE scancode;			// 按键的扫描码(依赖于 OEM)
    			bool extended	:1;		// 按键是否是扩展键
    			bool prevdown	:1;		// 按键的前一个状态是否按下
    		};
    
    		// 字符消息的数据
    		TCHAR ch;
    
    		// 窗口消息的数据
    		struct
    		{
    			WPARAM wParam;
    			LPARAM lParam;
    		};
    	};
    };
  4. circle();  绘制无填充圆

    void circle(      //传入参数
    	int x,        //圆心x坐标
    	int y,        //圆心y坐标
    	int radius    //圆半径
    );
    
  5. line();  绘制直线
    void line(
    	int x1,    //起点x值
    	int y1,    //起点y值
    	int x2,    //终点x值
    	int y2     //终点y值
    );
    
  6. settextcolor(); 设置文字输出颜色
    使用RGB宏来定义颜色
    void settextcolor(RGB(255,0,0));    //红色
  7. _T(); 格式化字符串提供Unicode编码支持,提高可移植性
  8. _stprintf_s();  将格式化的字符串存入指定区域
  9. outtextxy();  指定位置输出字符串
    void outtextxy(
    	int x,     //x坐标
    	int y,     //y坐标
    	TCHAR c    //输出字符串
    );
    
  10. GetTickCount();  检索自系统启动以来已用过的毫秒数
    DWORD GetTickCount();    //返回DWORD (无符号双字(32位)整数类型)
  11. Sleep();  睡眠函数(暂停当前线程的执行,直到超时间隔结束)
    void Sleep(
      [in] DWORD dwMilliseconds    //执行等待,单位毫秒(ms)
    );
  • 库函数

#include <iostream>    //标准输入输出库
#include <vector>        //STL-Vector容器(动态数组)
#include <graphics.h>    //Eaxyx库
  • 画布大小

        使用#define宏命令
        程序编译时将代码中所有内容替换为定义的内容,利于统一管理

#define H 600
#define L 600
//初始化为600*600
  • 数据存储

        通过二维数组(3*3)存储当前格子内容,并全部初始化为'-'记为空

        当前落子类型CurType初始化为O (O先手),以便后续修改

std::vector<std::vector<char>> board_data(3, std::vector<char>(3, '-'));
char CurType = 'O';
  • 绘制格子

        使用line函数对画布长宽三等分(九宫格)

void DrawBorder() {
    line(0, L / 3, H, L / 3);              //x轴1/3处
    line(0, (L / 3) * 2, H, (L / 3) * 2);  //x轴2/3处
    line(H / 3, 0, H / 3, L);              //y轴1/3处
    line((H / 3) * 2, 0, (H / 3) * 2, L);  //y轴2/3处
}
//原点位于窗口左上为(0,0)
//向右为x轴正方向
//向下为y轴正方向
  • 绘制棋子

    遍历矩阵,通过switch判断当前格子的类型,执行特定语句
        使用circle();绘制无填充圆        "O"类型

                因为矩阵为3*3,但画布为H*L,所以需要计算之间的倍数关系

                并且需要计算圆心位置,圆心位置需要加上当前所在格子的一半

                因为H==L 所以半径:H/6

                (因为代数相乘仅能计算到左上角,所以x,y需要加上H/6,也就是100,才是圆心的位置)

        使用line();绘制叉                      "X"类型

                同上:因为代数相乘仅能计算到左上角,所以画直线就很简单,

                仅需要找到坐标:

                左上角:(x, y)        连接        右下角:(x + (H / 3), y + (L / 3))

                左下角:(x, y + (L / 3))    连接    右上角:(x + (H / 3), y)

void DrawPieces() {
    for (int i = 0; i < 3; ++i) {       //遍历
        for (int j = 0; j < 3; ++j) {
            int x = j * (L / 3);        //计算偏移量
            int y = i * (H / 3);        //计算偏移量
            switch (board_data[i][j]) {
            case 'O':
                circle(x + (H / 6), y + (L / 6), H/6);
                break;
            case 'X':
                line(x, y, x + (H / 3), y + (L / 3));
                line(x, y + (L / 3), x + (H / 3), y);
                break;
            default:
                break;
            }
        }
    }
}
  • 获胜检测

        通过遍历来判断是否出现相同类型连成直线,并且返回一个bool

// 获胜检测
bool DetectionWin(char c) {
    for (int i = 0; i < 3; ++i) {
        if ((board_data[i][0] == c && board_data[i][1] == c && board_data[i][2] == c) ||    //判断行是否出现三子连珠
            (board_data[0][i] == c && board_data[1][i] == c && board_data[2][i] == c)) {  //判断列是否出现三子连珠
            return true;
        }
    }
    //判断对角线
    if ((board_data[0][0] == c && board_data[1][1] == c && board_data[2][2] == c) ||
        (board_data[0][2] == c && board_data[1][1] == c && board_data[2][0] == c)) {
        return true;
    }
    return false;
}
  • 平局检测

        通过判断是否还存在空位,来判断是否平局

// 平局检测
bool DetectionDraw() {
    for (size_t i = 0; i < 3; ++i) {
        for (size_t j = 0; j < 3; ++j) {
            if (board_data[i][j] == '-') {    //没有获胜,且没有空位的情况下即为平局
                return false;                 //出现空位'-'返回false,未平局
            }
        }
    }
    return true;
}
  • 绘制提示信息

        提示当前落子类型

// 绘制提示信息
void DrawMessage() {
    static TCHAR str[64];    //定义静态字符串
    _stprintf_s(str, _T("当前棋子类型: %c"), CurType);    //格式化并存入str
    settextcolor(RGB(255, 175, 45));    //设置文字输出色彩,传入RGB格式
    outtextxy(0, 0, str);    //输出
}
  • 交互设计

        通过ExMessage创建存储鼠标信息的实例,并且在主循环中不断地去更新鼠标的位置
        通过if判断是否按下,并且将窗口600*600的坐标转换为3*3的坐标
        然后进行边界检测,并且判断当前点击格子是否能落子,落子成功后交换棋子类型

//主循环内
        while (peekmessage(&msg)) {    //更新交互信息循环 
            if (msg.message == WM_LBUTTONDOWN) {    //判断左键是否按下
                int x = msg.x;    //鼠标按下的x坐标
                int y = msg.y;    //鼠标按下的y坐标

                int index_x = x / (H / 3);    //将窗口x坐标转换为3*3矩阵坐标
                int index_y = y / (L / 3);    //将窗口y坐标转换为3*3矩阵坐标

                if (index_x >= 0 && index_x < 3 && index_y >= 0 && index_y < 3) {    //边界检测
                    if (board_data[index_y][index_x] == '-') {    //判断格子是否为空
                        board_data[index_y][index_x] = CurType;
                        CurType = (CurType == 'O') ? 'X' : 'O';    //落子后交换'O'与'X'
                    }
                }
            }
  • 帧速率控制

        通过在游戏开始获取一次时间(ms),并在画面渲染结束时再次获取(ms)
        计算差值,通过判断,限制在60帧以内,避免循环导致高占用

//主循环
    while (running) {
        
        DWORD start_time = GetTickCount();        //循环开始获取(ms)

//---------------------循环主体---------------------
        //主循环内

        DWORD end_time = GetTickCount();          //循环结束获取(ms)
        DWORD delta_time = end_time - start_time; //计算循环所耗时间   

        if (delta_time < 1000 / 60) {    //通过判断一次循环的时间与60帧的速率

            Sleep(1000 / 60 - delta_time);    //小于60帧的时间,则说明比60帧所耗时间短,则计算差值,并等待
        }
    }
  • 主循环

        设置一个主循环是否继续进行的boolrunning初始化为true
        并且设置为主循环判断条件
        判断获胜与平局的状态
        帧速率的控制

while (running) {
		
		DWORD start_time = GetTickCount();

        while (peekmessage(&msg)) {
            if (msg.message == WM_LBUTTONDOWN) {
                int x = msg.x;
                int y = msg.y;

                int index_x = x / (H / 3);
                int index_y = y / (L / 3);

                if (index_x >= 0 && index_x < 3 && index_y >= 0 && index_y < 3) {
                    if (board_data[index_y][index_x] == '-') {
                        board_data[index_y][index_x] = CurType;
                        CurType = (CurType == 'O') ? 'X' : 'O';
                    }
                }
            }
        }

        cleardevice();
        DrawBorder();     //绘制格子
        DrawPieces();     //绘制棋子
        DrawMessage();    //绘制提示信息
        FlushBatchDraw();

        //获胜以及平局检测
        if (DetectionWin('X')) {
            MessageBox(GetHWnd(), _T("X 玩家获胜"), _T("游戏结束"), MB_OK);
            running = false;    //修改循环条件
        }
        else if (DetectionWin('O')) {
            MessageBox(GetHWnd(), _T("O 玩家获胜"), _T("游戏结束"), MB_OK);
            running = false;    //修改循环条件
        }
        else if (DetectionDraw()) {
            MessageBox(GetHWnd(), _T("平局"), _T("游戏结束"), MB_OK);
            running = false;    //修改循环条件
        }
        
        //帧速率控制
        DWORD end_time = GetTickCount();
        DWORD delta_time = end_time - start_time;

        if (delta_time < 1000 / 60) {

            Sleep(1000 / 60 - delta_time);
        }
    }

5. 项目总结


学习收获

        通过简单的项目了解Eaxyx库的简单使用
        简单了解游戏与现实输入设备的交互
        窗口与矩阵方向不同

未来改进方向

        可以通过后续学习构建简单的本地二人联机
        如有错误之处,还请指出,不要留情,感谢支持!

源代码

关注公众号:ComPute发现

6.参考文档

Voidmatrix:从零开始的游戏开发基础

Eaxyx在线文档

Microsoft C++、C 和汇编程序文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值