Re0:从零开始的C++游戏开发【上】

Re0:从零开始的C++游戏开发

这是蒟蒻观看B站upVoidmatrix的课程从零开始的提瓦特幸存者的个人笔记【自用】

前言:采用适用于小白的easyx图形库。

第一集 追着鼠标的小球

#include <graphics.h>
#include <iostream>
int main(void)
{// 初始化initgraph(1280, 720);int x = 640;int y = 360;// 双缓冲绘图BeginBatchDraw();// 主循环while (true){ExMessage msg;// 读取操作while (peekmessage(&msg)){// 在这里进行消息处理逻辑if (msg.message == WM_MOUSEMOVE){// 数据处理(由于数据处理逻辑简单,所以嵌套在读取操作中)x = msg.x;y = msg.y;}}// 绘制画面cleardevice();solidcircle(x, y, 100);FlushBatchDraw();}EndBatchDraw();return 0;
}

绘图坐标系

easyx中,绘图坐标系相似于二维数组坐标系,即y轴的反转

渲染缓冲区

渲染缓冲区:类似画笔在画布上画画,先绘制的内容就可能被后绘制的内容覆盖掉

cleardevice()就是使当前填充颜色将画布覆盖,默认填充色为黑色

当我们调用solidcircle()这类绘图函数时,一个无边框的填充圆被“逐渐地”绘制到这张画布上
当我们不断清屏,不断画圆,逐渐地过程在“宏观上”体现出来了

而当我们调用BeginBatchDraw();easyx为我们新建一个渲染缓冲区,不同于窗口的渲染缓冲区,它默认是不可见的,随后执行的所有绘制都将在新的画布上进行
而当我们调用FlushBatchDraw();EndBatchDraw();时,easyx会将窗口所显示的缓冲区和新建的缓冲区进行“迅速”交换,这样的交换迅速到我们不会因绘图过程频繁而导致闪烁

游戏框架

*主循环

在上述程序中,我们通过一个while(true)死循环阻塞程序退出,同时不断执行清屏和绘制的操作,这其实就是游戏框架最核心的部分——主循环

在主循环中,我们不断读取玩家的(鼠标、键盘等)操作,将这些操作翻译成我们的数理逻辑,最后再根据现有的数据将画面内容绘制出来
简而言之,就是读取操作、处理数据、绘制画面这三大要素

初始化();
while(true)
{读取操作();处理数据();绘制画面();
}
释放资源();

如上述代码,我们游戏的渲染部分只依赖于当前的数据,依旧是变量x和y的值,而与如何处理得到这些数据的处理逻辑并未有直接关系。这就是软件工程理论中的”解耦耦合“,或者说这就是"数据驱动",或者说“渲染与逻辑分离”中最朴素的思想。

当然,在主循环开始之前,我们需要把主循环过程中所需要的数据初始化,如:将圆的位置坐标初始化、初始化窗口等。

而在主循环结束后,需要对游戏程序使用的资源进行释放。

第二集 进击的井字棋

三大元素

在代码编写之前,

我们首先根据前面所讲述的游戏框架,思考在井字棋的主循环中三大要素如何设计实现。

初始化();
while(true)
{读取操作();处理数据();绘制画面();
}
释放资源();
  1. 读取操作:

    在本程序中,我们只对鼠标输入进行考虑,所以我们只需对鼠标按键按下的消息进行处理:当鼠标点击在空白的棋盘网格时,便执行落子操作。

  2. 数据处理:

    我们只需要对游戏的胜负条件进行检测即可,游戏结束的条件是同类型三颗棋子连成一条直线或棋盘被填满。

    游戏结束时,使用弹窗告诉玩家游戏结果,然后退出主循环。

  3. 绘制画面

    网格棋盘:使用line()函数绘制直线将窗口等分为3X3的网格

    X棋子:使用line()函数绘制连接网格对角线的两条直线

    O棋子:使用circle()函数绘制圆心在网格中心的无填充原型

    除此之外,我们应会在窗口左上角输出一行文字当前妻子类型:X,用以告诉玩家当前被放置的棋子类型。

数据结构

接下来,便是考虑如何组织游戏的数据结构

  1. 棋盘:

    显而易见,我们可以使用二维数组来表示棋盘。我们将二维数组中每个元素类型设置为char类型,再约定'X'字符表示叉号棋子、 'O'字符表示圆形棋子、 '-'字符默认值表示网格中没有棋子

  2. 游戏结束条件

    2.1 某玩家获胜的情况

    我们著需要对'X'字符'O'字符进行穷举,可能出现的情况一共有8种:分别是横向的三行棋子出现同类型符号、竖向的三行棋子出现同类型符号和两条对角线的棋子出现同类型符号。

    2.2 两玩家平局的情况

    即没有玩家获胜的情况,也就是说数组中的每一个元素均不是'-'字符,即可判定玩家平局。

代码编写

到现在,在我们的思路已经十分明晰后,我们着手编写代码。

在代码编写的过程中,我们同样遵循先框架后细化的思路。

我们先把上述思路转变成代码,细节部分先使用注释进行替代,再将每一部分的注释替换为代码。这样可以确保我们在编写代码的过程中不会被突然出现的代码细节打扰。

实现如下:

#include <graphics.h>
#include <iostream>
// 简单粗暴的全局变量并非一个好习惯
char board_data[3][3]
{{'-','-','-'},{'-','-','-'},{'-','-','-'}
};
// 当前落子类型,初始化为'O'
char cur_piece = 'O';
// 检测指定棋子的玩家是否获胜
bool CheckWin(char c)
{if (board_data[0][0] == c && board_data[0][1] == c && board_data[0][2] == c)return true;if (board_data[1][0] == c && board_data[1][1] == c && board_data[1][2] == c)return true;if (board_data[2][0] == c && board_data[2][1] == c && board_data[2][2] == c)return true;if (board_data[0][0] == c && board_data[1][0] == c && board_data[2][0] == c)return true;if (board_data[0][1] == c && board_data[1][1] == c && board_data[2][1] == c)return true;if (board_data[0][2] == c && board_data[1][2] == c && board_data[2][2] == c)return true;if (board_data[0][0] == c && board_data[1][1] == c && board_data[2][2] == c)return true;if (board_data[0][2] == c && board_data[1][1] == c && board_data[2][0] == c)return true;return false;
}
// 检测当前是否出现平局
bool CheckDraw()
{for (size_t i = 0; i < 3; i++)for (size_t j = 0; j < 3; j++)if (board_data[i][j] == '-') return false;return true;
}
// 绘制网格棋盘
void DrawBoard()
{line(0, 200, 600, 200);line(0, 400, 600, 400);line(200, 0, 200, 600);line(400, 0, 400, 600);}
// 绘制棋子
void DrawPiece()
{for (size_t i = 0; i < 3; i++){for (size_t j = 0; j < 3; j++){switch (board_data[i][j]){case 'O':circle(200 * j + 100, 200 * i + 100, 100);break;case 'X':line(200 * j, 200 * i, 200 * (j + 1), 200 * (i + 1));line(200 * (j + 1), 200 * i, 200 * j, 200 * (i + 1));break;case '-':break;default:break;}}}
}
// 绘制左上角文本提示信息
void DrawTipText()
{static TCHAR str[64];_stprintf_s(str, "Current Type of Piece: %c", cur_piece);settextcolor(RGB(225, 175, 45));outtextxy(0, 0, str);
}int main(void)
{// 初始化窗口initgraph(600, 600);// 控制主循环是否进行下去bool running = true;// 消息处理ExMessage msg;// 双缓冲BeginBatchDraw();// 主循环while (running){DWORD start_time = GetTickCount();// 鼠标消息检测while (peekmessage(&msg)){// 检查鼠标左键按下消息if (msg.message == WM_LBUTTONDOWN){// 计算点击位置int x = msg.x;int y = msg.y;// 由于每个网格都是200X200int ind_x = x / 200;int ind_y = y / 200;// 尝试落子if (board_data[ind_y][ind_x] == '-'){board_data[ind_y][ind_x] = cur_piece;// 切换棋子类型if (cur_piece == 'O') cur_piece = 'X';else cur_piece = 'O';}}}// 绘制图像cleardevice();DrawBoard();DrawPiece();DrawTipText();FlushBatchDraw();// X玩家获胜逻辑if (CheckWin('X')){// 弹窗MessageBox(GetHWnd(), "X Player WIN!", "Game End", MB_OK);// 修改主循环控制条件running = false;}// O玩家获胜逻辑else if(CheckWin('O')){// 弹窗MessageBox(GetHWnd(), "O Player WIN!", "Game End.", MB_OK);// 修改主循环控制条件running = false;}// 上述条件都不满足时,对游戏平局检测else if (CheckDraw()){// 弹窗MessageBox(GetHWnd(), "Ops!It's DRAW.", "Game End.", MB_OK);// 修改主循环控制条件running = false;}// 依据间隔时间动态分配休眠时间DWORD end_time = GetTickCount();DWORD delta_time = end_time - start_time;// 按每秒60帧刷新页面if (delta_time < 1000 / 60){Sleep(1000 / 60 - delta_time);}}EndBatchDraw();return 0;
}

值得注意的是,在程序运行时的程序占用率过高,通过任务管理器也可以发现一个小小的井字棋游戏,CPU占用率甚至已经超过了电脑中的绝大部分软件。这是因为计算机在执行while循环时速度较快,我们编写的主循环在顷刻间已经执行完了成千上万次,占用了大量的CPU时间片。对于大部分物理刷新率仅有60Hz的显示设备来说,这无疑是一种性能浪费。所以我们可以使用Sleep();函数来让程序在执行完一次循环后休眠一小段时间,从而减少计算资源的浪费。

在大多教程中,这里或许会简答粗暴的写一句Sleep(15),来让程序在每一次循环结束后强制等待15毫秒。但是,这种设计是不太合适的,随游戏体量的增大,程序每次执行主循环所执行的计算任务可能是不同的,以及涉及到操作系统CPU计算资源的分配,这就导致每次执行主循环所实际消耗的时间可能是不一样的。所以我们需要根据每一帧执行的实际耗时,动态的计算在这之后要休眠多长时间,这是引入一个新的函数GetTickCount()。我们可以使用它来获得程序自运行开始以来到现在的毫秒数DWORD time = GetTickCount();

初始化();
while(true)
{DWORD start_time = GetTickCount(); // 获取此次循环初始时间读取操作();处理数据();绘制画面();DWORD end_time = GetTickCount(); // 获取此次循环结束时间DWORD delta_time = end_time - start_time; // 计算间隔时间// 依据间隔时间动态分配休眠时间// 按每秒60帧刷新页面if (delta_time < 1000 / 60) // 如果间隔时间<每秒60帧,要进行休眠;否则不需要。{Sleep(1000 / 60 - delta_time);}
}
释放资源();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/19967.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序教程DAY3

box标签 第二种方法 绿色第一种 第一种更好 效果一样 完成这个项目 先写循环

Python深度学习基于Tensorflow(13)目标检测实战

文章目录 RPN 整体代码RPN 具体实现过程数据标注读取标注数据固定图片大小调整目标框使用预训练模型获取 feature_shape定义 RPN 网络生成RPN 的 CLS 和 REG 数据集获取所有的锚点计算锚点与目标框的IOU 定义 RPN loss 和 训练过程 参考资料 这里实现的是二阶段目标检测&#x…

十分钟快速搭建检索、排序的大模型RAG系统

以上为实现效果 RAG是目前最火的大模型应用之一&#xff0c;如何能快速实现一个不错的demo呢&#xff1f; 参考 https://github.com/LongxingTan/open-retrievalshttps://colab.research.google.com/drive/1fJC-8er-a4NRkdJkwWr4On7lGt9rAO4P?uspsharing#scrollTo2Hrfp96UY…

第二届“天洑杯”全国高校数据建模大赛圆满收官

近日&#xff0c;第二届“天洑杯”全国高校数据建模大赛在江苏省无锡市第七届智能优化与调度学术会议现场圆满收官。在为期四周的线上赛中&#xff0c;共有来自全国 71 所高校及企业的 117 支队伍参与角逐&#xff0c;共10支队伍进入决赛。 本届大赛评审组由西安电子科技大学教…

鸿蒙开发接口媒体:【@ohos.multimedia.camera (相机管理)】

相机管理 说明&#xff1a; 开发前请熟悉鸿蒙开发指导文档&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 本模块首批接口从API version 9开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块…

Vue3实战笔记(52)—Vue 3封装持仓分析饼图

文章目录 前言一、封装持仓分析饼图总结 前言 接上文&#xff0c;封装持仓分析饼图。 一、封装持仓分析饼图 EChartsPieBorderRadiusType.vue&#xff1a; <template><div><div ref"chartContainer" style"width: 100%; height: 450px"&g…

vue中使用pinia实现状态管理——useXXXStore函数

vue中使用pinia实现状态管理 大家一定在vue中还见过 useXXXStore这样的函数&#xff0c;用来实现状态管理的。可以把它当成一个永远存在的组件&#xff0c;每个组件都可以读取和写入它。它有三个概念&#xff0c;state、getter 和 action&#xff0c;我们可以假设这些概念相当…

低边驱动与高边驱动

一.高边驱动和低边驱动 低边驱动(LSD): 在电路的接地端加了一个可控开关&#xff0c;低边驱动就是通过闭合地线来控制这个开关的开关。容易实现&#xff08;电路也比较简单&#xff0c;一般由MOS管加几个电阻、电容&#xff09;、适用电路简化和成本控制的情况。 高边驱动&am…

Qt 窗口

在Qt Creator 中创建项目的时候&#xff0c;我们能够选择创建QMainWindow 还是 QWidget 两种窗口。 二者有什么区别呢&#xff1f;其中 QMainWindow 是一种主窗口&#xff0c;包含菜单栏&#xff0c;工具栏&#xff0c;状态栏&#xff0c;中心窗口和浮动窗口等多个窗口组合&…

Java基础学习: 代理模式(Proxy Pattern)

文章目录 一、简介1、介绍2、代理模式在Java中的应用 二、实现方式1、静态代理2、动态代理 三、动态代理1、JDK2、Cglib3、总结 参考 一、简介 1、介绍 代理模式 2、代理模式在Java中的应用 统一异常处理。Mybatis使用了代理。Spring aop实现代理。日志框架。 二、实现方式…

位置参数

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 位置参数也称必备参数&#xff0c;是必须按照正确的顺序传到函数中&#xff0c;即调用时的数量和位置必须和定义时是一样的。 &#xff08;1&#x…

使用 Docker 和 Docker Compose 部署 Vue

使用 Docker 和 Docker Compose 部署 Vue 项目有两种方式&#xff1a;直接使用 Docker 和使用 Docker Compose。 创建 Dockerfile 在Vue.js项目根目录下创建一个 Dockerfile 的文件 # 使用最新的官方 Node.js 镜像作为基础镜像&#xff0c;并命名为 builder 阶段 FROM node:…

内测:ew0.0版本

温馨提示&#xff1a;懒得编&#xff0c;可能会存在一点点bug。 合作请私信我&#xff0c;周六日有空。 合作对象&#xff1a;小学生&#xff0c;初中生不等。 #include<bits/stdc.h> #include<windows.h> using namespace std; void sprintf(string s){int len…

stack和queue(1)

一、stack的简单介绍和使用 1.1 stack的介绍 1.stack是一种容器适配器&#xff0c;专门用在具有先进后出&#xff0c;后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入和弹出操作。 2.stack是作为容器适配器被实现的&#xff0c;容器适配器即是…

信号与槽函数的魔法:QT 5编程中的核心机制

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、信号与槽函数的基本概念 二、信号与槽函数的实现原理 三、信号与槽函数的代码实例 四…

搭载算能 BM1684 芯片,面向AI推理计算加速卡

搭载算能 BM1684 芯片&#xff0c;是面向AI推理的算力卡。可集成于服务器、工控机中&#xff0c;高效适配市场上所有AI算法&#xff0c;实现视频结构化、人脸识别、行为分析、状态监测等应用&#xff0c;为智慧城市、智慧交通、智慧能源、智慧金融、智慧电信、智慧工业等领域进…

实用软件分享---- i茅台 在windows上自动预约和自动获取小茅运的软件

专栏介绍:本专栏主要分享一些实用的软件(Po Jie版); 声明1:软件不保证时效性;只能保证在写本文时,该软件是可用的;不保证后续时间该软件能一直正常运行;不保证没有bug;如果软件不可用了,我知道后会第一时间在题目上注明(已失效)。介意者请勿订阅。 声明2:本专栏的…

计算机基础学习路线

计算机基础学习路线 整理自学计算机基础的过程&#xff0c;虽学习内容众多&#xff0c;然始终相信世上无难事&#xff0c;只怕有心人&#xff0c;期间也遇到许多志同道合的同学&#xff0c;现在也分享自己的学习过程来帮助有需要的。 一、数据结构与算法 视频方面我看的是青…

C++_list简单源码剖析:list模拟实现

文章目录 &#x1f680;1. ListNode模板&#x1f680;2. List_iterator模板(重要)&#x1f331;2.1 List_iterator的构造函数&#x1f331;2.2 List_iterator的关于ListNode的行为 &#x1f680;3. Reverse_list_iterator模板(拓展)&#x1f680;4. List模板(核心)&#x1f331…

NVM:Node版本管理切换 vue ‘cnpm‘ 不是内部或外部命令,也不是可运行的程序

nvm使用&#xff1a; Node 多版本管理_node版本管理_骑士梦的博客-CSDN博客 node.js 安装及配置环境变量只看此文_node环境变量设置-CSDN博客 # 安装11.13.0版本。 nvm install 11.13.0# 显示已安装的Node版本列表&#xff08;list可简化为ls&#xff09;。 nvm list# 切换1…