一、EasyX中的坐标系
不同于数理中的坐标系,EasyX中的y轴是竖直向下的
二、渲染缓冲区
之前的程序添加了这三个函数改善了绘图时闪烁的情况:
小球在"画布“上移动的过程就是我们在调用绘图函数,这个”画布“就是渲染缓冲区,先绘制的内容可能被后面绘制的内容覆盖掉,所以调用cleardevice()函数,相当于用当前背景颜色覆盖画布实现”清屏“。小球”闪烁“的原因在于,绘制过程并不是一瞬间,而是”逐渐“发生的,先前绘制的小球被不断清除,而人眼同时观察到”绘制“与”清除“,所以有种”闪烁“的感觉。
而在调用BeginBatchDraw函数后,EasyX会为我们新建一个画布(渲染缓冲区):
这个缓冲区是默认不可见的,后面进行的所有绘图操作都会绘制在新的画布上:
而在调用FlushBatchDraw或EndBatchDraw函数时EasyX会将前后两个缓冲区进行快速对调:
所以我们将不会再看见在一张画布上由于绘图频繁而导致的”闪烁“。
三、游戏框架
在之前程序中我们运用了死循环来阻塞程序来防止窗口退出,同时在循环中不断执行清屏和绘制操作,这其实已经完成了游戏框架最核心的部分”主循环“,在主循环中不断读取玩家鼠标操作并将这些操作翻译成对数据的处理逻辑,最后再根据现有的逻辑将画面内容绘制出来。也即读取操作——处理数据——绘制画面,在循环中不断执行这些操作便可以完成几乎所有游戏的逻辑设计:
先前代码如下;
#include<graphics.h>
int main() {
initgraph(1280, 720);
int x, y;
//BeginBatchDraw();
while (1) {
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;
}
peekmessage这部分就是读取操作:
将读取到的鼠标坐标赋值给先前定义的x、y就是处理数据:
然后solidcircle便是绘制画面。
还有,一开始需要初始化,最后需要释放资源。
四、设计本地环境的井字棋
井字棋共九格,X/O双方先后落子,同一颗棋子斜或竖连成一线则一方获胜,如果棋盘被填满则平局。
依据框架确定设计思路:①读取操作:双方只使用鼠标游玩,所以只需要读取鼠标消息。 ②处理数据:根据数据检测输赢平局情况,以弹窗形式告知,然后退出主循环。③绘制画面:使用line函数绘制直线将画布分割为3 x 3棋盘,绘制X棋子时只需要使用line函数绘制对应网格的对角线即可完成;绘制O棋子时只需要使用circle函数绘制以对应网格中心为圆心的圆即可。除此之外,在棋盘上方给出当前落子类型给予玩家提示。
接下来便是组织游戏的数据结构,可以使用二维数组表示棋盘
数组默认值为‘-’,代表没有棋子。
游戏结束的条件:①某玩家获胜,可能情况有8种—竖行三种、横行三种、斜角两种 ②平局,在没有玩家获胜前提下,数组中没有‘-’默认值。
开始写代码:首先是读取操作创建ExMessage存储消息/peekmessage获取消息
#include<graphics.h>
int main() {
initgraph(600, 600);
ExMessage msg;
bool running = true;//加一个控制变量控制循环,想要结束时改成false即可终止循环
while (running) {
while (peekmessage(&msg)) {
}
}
return 0;
}
接下来是数据处理:把数据拿过来进行判断指定棋子玩家是否获胜—创建CheckWin函数,这个函数接受char类型变量并返回布尔值用来检测对应棋子玩家是否满足获胜条件;创建CheckDraw函数,不传入参数返回布尔值来判断是否平局。当检测到玩家获胜,我们调用MessageBox函数弹窗,同时修改主循环控制条件使游戏结束运行。
小插曲:对MessageBox函数的解释
MessageBox函数接收4个参数,分别为父窗口句柄(相当于win系统中用于指向窗口对象的指针,可使用GetHWnd函数获取用来获取当前绘图窗口的句柄)、提示内容、弹窗标题、弹窗样式
接下来就可以编写获胜情况了:
if(CheckWin('X')){
MessageBox(GetHwnd(), _T("X玩家获胜"), _T("游戏结束"), MB_OK);
running = false;
}
else if(CheckWin('O'){
MessageBox(GetHWnd(), _T("O玩家获胜"), _T("游戏结束"), MB_OK);
running = false;
}
else if(CheckDraw()){
MessageBox(GetHWnd(), _T("平局!"), _T("游戏结束"), MB_OK);
running = false;
}
GetHwnd() 获取当前绘图窗口的句柄,_T(" ")决定显示界面输出内容,MB_OK决定显示界面只出现OK按钮。
接下来便是游戏画面的绘制,首先确保开启了批量绘图BeginBatchDraw() + EndBatchDraw(),并且在每次绘图后刷新批量绘图的渲染缓冲区FlushBatchDraw()。随后在每次绘图前清空画面cleardevice()。定义DrawBoard函数绘制棋盘网格。定义DrawPiece函数绘制棋子。定义DrawTipText函数定义当前棋子的文本提示信息。
全局大致框架如下:
#include<graphics.h>
bool CheckWin(char c) {
}
bool CheckDraw() {
}
void DrawBoard() {
}
void DrawPiece() {
}
void DrawTipText() {
}
int main() {
initgraph(600, 600);
ExMessage msg;
BeginBatchDraw();
bool running = true;
while (running) {
while (peekmessage(&msg)) {
}
if (CheckWin('X')) {
MessageBox(GetHWnd(), _T("X玩家获胜"), _T("游戏结束"), MB_OK);
running = false;
}
else if (CheckWin('O')) {
MessageBox(GetHWnd(), _T("O玩家获胜"), _T("游戏结束"), MB_OK);
running = false;
}
else if (CheckDraw()) {
MessageBox(GetHWnd(), _T("平局!"), _T("游戏结束"), MB_OK);
running = false;
}
cleardevice();
DrawBoard();
DrawPiece();
DrawTipText();
FlushBatchDraw();
}
EndBatchDraw();
return 0;
}