文章目录
- 回顾
- 用户交互
回顾
上一个博客里我们只是简单地显示了一个窗口,这次我们把主要的游戏逻辑给它加进去。这一部分里我们要做的任务有:
- 控制帧率:即每秒渲染多少帧;
- 用户交互:处理用户的鼠标点击事件;
- 完成相关棋子的渲染。
完整代码已经放上github了,在这里
用户交互
先把整段代码放出来
// 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的时候我把棋子缩小了一点。