文章目录

* 回顾 <https://blog.csdn.net/Irlyue/article/details/91348019#_1>
* 用户交互 <https://blog.csdn.net/Irlyue/article/details/91348019#_9>


<>回顾

上一个博客里我们只是简单地显示了一个窗口,这次我们把主要的游戏逻辑给它加进去。这一部分里我们要做的任务有:

* 控制帧率:即每秒渲染多少帧;
* 用户交互:处理用户的鼠标点击事件;
* 完成相关棋子的渲染。
完整代码已经放上github了,在这里
<https://github.com/Irlyue/SmallGames/blob/master/connect_four.h>


<>用户交互

先把整段代码放出来
// connect_four_1.h #ifndef CONNECT_FOUR_1_H #define CONNECT_FOUR_1_H #include
<SDL.h> #include <SDL_ttf.h> #include <SDL_image.h> #include <cstdio> #include
<vector> #include <string> using namespace std; SDL_Window *gWindow = nullptr;
SDL_Renderer*gRenderer = nullptr; constexpr int GRID_SIZE = 50; constexpr int
SPACE= 0; constexpr int RED_PLAYER = 1; constexpr int BLACK_PLAYER = 2; class
ConnectFour { public: ConnectFour() = default; ConnectFour(int nbWGrids, int
nbHGrids) : _nbWGrids(nbWGrids), _nbHGrids(nbHGrids), _nbGrids(nbWGrids *
nbHGrids), _contents(nbWGrids * nbHGrids, SPACE) { _winHeight = GRID_SIZE *
nbHGrids; _winWidth = GRID_SIZE * nbWGrids; } void start() { pre_run(); run(); }
private: int _nbHGrids = 0; int _nbWGrids = 0; int _nbGrids = 0; int _winHeight
= 0; int _winWidth = 0; bool _running = false; int _player = RED_PLAYER; int
_mousePos= -1; int _lastPos = -1; vector<int> _contents; SDL_Texture *
_grayCircle= nullptr; SDL_Texture *_redCircle = nullptr; SDL_Texture *
_blackCircle= nullptr; SDL_Surface *_icon = nullptr; void pre_run() { gWindow =
SDL_CreateWindow("Connect Four", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, _winWidth, _winHeight, SDL_WINDOW_SHOWN); gRenderer =
SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED); _grayCircle =
loadFromFile("images\\gray.png"); _redCircle = loadFromFile("images\\red.png");
_blackCircle= loadFromFile("images\\blue.png"); _icon = IMG_Load(
"images\\icon.png"); SDL_SetWindowIcon(gWindow, _icon); } SDL_Texture *
loadFromFile(string path) { SDL_Texture *texture = nullptr; SDL_Surface *surface
= IMG_Load(path.c_str()); if (surface) { texture = SDL_CreateTextureFromSurface(
gRenderer, surface); SDL_FreeSurface(surface); } else { printf("Failed to load
`%s`! IMG Error: %s\n", path.c_str(), IMG_GetError()); } return texture; } void
run() { _running = true; Uint32 tic, elapsed; while (_running) { tic =
SDL_GetTicks(); handleEvent(); clearScreen(); renderScreen(); SDL_RenderPresent(
gRenderer); elapsed = SDL_GetTicks() - tic; if (elapsed < 30) SDL_Delay(30 -
elapsed); } } void handleEvent() { SDL_Event e; while (SDL_PollEvent(&e) != 0) {
if (e.type == SDL_QUIT) _running = false; handleMouseEvent(e); } } void
handleMouseEvent(SDL_Event &e) { _mousePos = -1; if (e.type == SDL_MOUSEMOTION
|| e.type == SDL_MOUSEBUTTONUP) { int x, y; SDL_GetMouseState(&x, &y); if (x < 0
|| y < 0 || x >= _winWidth || y >= _winHeight) return; int gx = x / GRID_SIZE,
gy= y / GRID_SIZE; _mousePos = gx + gy * _nbWGrids; if (e.type ==
SDL_MOUSEBUTTONUP) { if (_contents[_mousePos] == SPACE) { _contents[_mousePos] =
_player; _lastPos = _mousePos; switchPlayer(); } } } } void switchPlayer() {
_player= _player == BLACK_PLAYER ? RED_PLAYER : BLACK_PLAYER; } void clearScreen
() { SDL_SetRenderDrawColor(gRenderer, 0xff, 0xff, 0xff, 0xff); SDL_RenderClear(
gRenderer); } void renderScreen() { renderPieces(); renderPiecePreview(); } void
renderPieces() { for (int i = 0; i < _nbGrids; i++) { int gx = i % _nbWGrids, gy
= i / _nbWGrids; SDL_Rect rect = { gx * GRID_SIZE, gy * GRID_SIZE, GRID_SIZE,
GRID_SIZE}; if (_contents[i] == SPACE) { SDL_RenderCopy(gRenderer, _grayCircle,
nullptr, &rect); } else { SDL_Texture *target = _contents[i] == BLACK_PLAYER ?
_blackCircle: _redCircle; SDL_RenderCopy(gRenderer, target, nullptr, &rect); } }
} void renderPiecePreview() { if (_mousePos != -1 && _contents[_mousePos] ==
SPACE) { SDL_Texture *target = _player == BLACK_PLAYER ? _blackCircle :
_redCircle; int gx = _mousePos % _nbWGrids, gy = _mousePos / _nbWGrids;
SDL_Rect rect= { gx * GRID_SIZE, gy * GRID_SIZE, GRID_SIZE, GRID_SIZE };
SDL_SetRenderDrawColor(gRenderer, 0xff, 0xff, 0xff, 0xff); SDL_RenderFillRect(
gRenderer, &rect); int s = 5; rect = { gx * GRID_SIZE + s, gy * GRID_SIZE + s,
GRID_SIZE- 2 * s, GRID_SIZE - 2 * s }; SDL_RenderCopy(gRenderer, target, nullptr
, &rect); } } };
我们首先需要一个SDL_Renderer
,就是用来把图片画到窗口的东西。同样我们用一个全局变量来保存它。另外,四子棋每个棋子位置都有三种状态:空的(SPACE)、红棋(RED_PLAYER)和黑棋(BLACK_PLAYER),这里分别对应3个常量。


我们还需要一个变量来保存整个棋局的状态。虽然棋局是一个二维的矩阵,但我们也可以把它表示成为一维的数组,到时候再把相应的x和y计算出来就好了,这里我们采用一维数组的方式,用成员
_contents来保存。

分别介绍一下新引入的成员变量:
bool _running = false; // 是否继续执行游戏的主循环 int _player = RED_PLAYER; //
记录当前下棋的玩家是哪一方,红方或者黑方 int _mousePos = -1; // 记录当前鼠标在哪一个格子里 int _lastPos = -1; //
记录上一次玩家落子的格子位置 vector<int> _contents; // 记录整个棋局的状况,哪些是黑的,哪些是红的,哪些是空白的
SDL_Texture*_grayCircle = nullptr; // 存储空白棋子的图片 SDL_Texture *_redCircle =
nullptr; // 存储红旗的图片 SDL_Texture *_blackCircle = nullptr; // 存储黑骑的图片 SDL_Surface
*_icon = nullptr; // 存储这个游戏程序的图标图片,一般显示在窗口左上角
在pre_run()函数里,我们先把需要的renderer以及图片都加载进来(加载图片的辅助函数loadFromFile自己看上面的代码了):
void pre_run() { gWindow = SDL_CreateWindow("Connect Four",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, _winWidth, _winHeight,
SDL_WINDOW_SHOWN); gRenderer = SDL_CreateRenderer(gWindow, -1,
SDL_RENDERER_ACCELERATED); _grayCircle = loadFromFile("images\\gray.png");
_redCircle= loadFromFile("images\\red.png"); _blackCircle = loadFromFile(
"images\\blue.png"); _icon = IMG_Load("images\\icon.png"); SDL_SetWindowIcon(
gWindow, _icon); }
即如下4张图片

接着看游戏的主循环:
void run() { _running = true; Uint32 tic, elapsed; while (_running) { tic =
SDL_GetTicks(); handleEvent(); // 处理用户交互事件,如鼠标点击 clearScreen(); // 先清屏
renderScreen(); // 然后把棋子之类的画上去 SDL_RenderPresent(gRenderer); // 刷新屏幕 elapsed =
SDL_GetTicks() - tic; // 看一下一次循环用时多少 if (elapsed < 30) SDL_Delay(30 - elapsed);
// 如果太快了,可以让CPU休息一下 } }
游戏主循环里分别做了如下几件事:

* 处理用户交互事件,如鼠标点击;
* 清屏,清除上一帧的东西;
* 把棋子之类的画上去;
* 刷新屏幕;
* 决定是否要等待一些时间,一般帧率fps = 30就可以了,这里我们把每次循环都控制在30ms,帧率也就大概在1000 / 30 = 33左右。
处理交互事件
void handleEvent() { SDL_Event e; while (SDL_PollEvent(&e) != 0) { if (e.type
== SDL_QUIT) _running = false; handleMouseEvent(e); } }
主要三种事件:

* 用户点击了右上角的退出按钮;
* 用户移动了鼠标;
* 用户点击了空白棋子的未知。
第一个事件我们独立处理,后面两种统一在鼠标事件中处理handleMouseEvent()。鼠标事件的处理简单说一下:

* 获取鼠标事件的位置x和y,只处理棋局范围内的鼠标事件
* 计算出鼠标所在的格子;
* 如果是点击事件,我们在BUTTONUP的时候还要把棋子放上去,并且切换玩家switchPlayer();
紧接着,我们要把游戏画面渲染出来renderScreen(),也要做两件事情:

* renderPieces(): 把棋子画上去,有三种棋子:SPACE、RED_PLAYER和BLACK_PLAYER;
* renderPiecePreview:
当鼠标在空白棋子位置时,我们预先显示该玩家的棋子。这个也很简单,因为前面鼠标事件的时候我们记录了当前鼠标所在的格子_mousePos
,如果这个格子是空的,我们就把当前玩家的棋子预先显示在这个格子上。为了有一种动画的效果,代码理preview的时候我把棋子缩小了一点。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信