环境选择:Visual Studio 2022 与 EasyX

基本知识

渲染缓冲区

initgraph(x,y);初始化

cleardevice();清空当前画布

双缓冲优化

BeginBatchDraw():新建画布作为新的渲染缓冲区(默认不可见),随后新的绘图操作将默认在新的画布上进行.

FlushBatchDraw()&EndBatchDraw():将窗口当前所显示的缓冲区与新的缓冲区进行交换

游戏基本主框架

1
2
3
4
5
6
7
初始化()
while(true){
读取操作();
处理数据();
绘制画面();
}
释放资源();

井字棋小游戏的实现

数据处理

通过二维数组实现

1
2
3
4
5
6
7
char board_data[3][3] =
{
{'-','-','-'},
{'-','-','-'},
{'-','-','-'},
};
char current_piece = 'O';

读取操作

只考虑鼠标按键按下的消息进行处理,点击时执行落子操作

处理数据

胜负判断(通过函数 枚举进行判断)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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;
}

游戏结束时用弹窗告诉玩家结果并退出主循环

绘制画面

应用EasyX函数实现

提示玩家当前落子类型

1
2
3
4
5
6
7
8
void DrawTipText()
{
static TCHAR str[64];
_stprintf_s(str, _T("当前棋子类型:%c"), current_piece);

settextcolor(RGB(225, 175, 45));
outtextxy(0, 0, str);
}

棋盘与棋子的绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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;
}
}
}
}

游戏主循环

初始化

1
2
3
4
   initgraph(600, 600);
bool running = true;
ExMessage msg;
BeginBatchDraw();

主循环部分

实现轮流落子功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
while(peekmessage(&msg))
{
if (msg.message == WM_LBUTTONDOWN)
{
int x = msg.x;
int y = msg.y;

int index_x = x / 200;
int index_y = y / 200;

if (board_data[index_y][index_x] == '-')
{
board_data[index_y][index_x] = current_piece;
if (current_piece == 'O')
current_piece = 'X';
else
current_piece = 'O';
}
}
}

cleardevice();

DrawBoard();
DrawPiece();
DrawTipText();

FlushBatchDraw();

EndBatchDraw();
return 0;

胜利检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

对游戏的优化(帧时动态延时)

思路:对每秒60fps的实现,相比简单的sleep函数,可在主循环开头调用一次函数获取开始时间;主循环结束后再调用一次函数获取结束时间;从而获取一次主循环花费的时间,在此基础上进行优化

  • DWORD start_time = GetTickCount();//获取开始时间

  • DWORD end_time = GetTickCount();//获取结束时间

  • DWORD delta_time = end_time - start_time;//计算差值

  • if (delta_time < 1000 / 60)
    {
        Sleep(1000 / 60 - delta_time);
    }
    

    通过对delta_time的计算从而执行具体的sleep时间,实现完整的60fps每秒游戏,使得画面更完整

思想:主循环内应该尽量避免阻塞式的行为或者过于繁重且耗时过长的任务