1. 从静态到动态:理解Qt绘制的核心机制
上一篇文章我们画出了一个漂亮的静态棋盘,线条清晰,颜色古朴,看着挺像那么回事。但一个真正的象棋游戏,棋盘必须是活的。你得能点击格子,棋子要能跟着鼠标走,选中了还得高亮显示,这才是我们玩游戏的感觉。今天,我们就来聊聊怎么把这个“死”棋盘,变成一个能跟你互动的“活”棋盘。这其中的关键,就是深入理解并驾驭Qt的绘制与事件系统。
很多新手朋友可能会觉得,画图就是一次性画完就完事了。其实在Qt里,尤其是做游戏界面,绘制是一个持续不断的过程。你可以把QPainter想象成一位画家,而paintEvent事件就是这位画家的“作画指令”。每当窗口需要被重新绘制时——比如窗口刚显示、被其他窗口挡住后又露出来、或者我们手动调用了update()函数——Qt就会发出这个指令,画家就会拿起画笔,根据我们写好的代码,在画布(也就是我们的ChessArea部件)上重新画一遍。
所以,实现动态交互的核心思路就出来了:通过改变数据状态,然后触发重绘,让画家根据新的状态画出新的画面。 比如,当鼠标点击一个格子,我们就把这个格子的坐标记录下来,标记为“被选中”状态,然后立刻调用this->update()。update()一调用,paintEvent就会被触发,我们在paintEvent函数里检查到有格子被选中了,就在绘制棋盘线的同时,在那个格子上额外画一个半透明的黄色矩形作为高亮效果。你看,交互的逻辑(鼠标点击改变状态)和显示的逻辑(画家根据状态绘画)是分离的,但又通过update()紧密联动。这种“状态驱动绘制”的思想,是Qt图形编程,乃至很多UI框架的基石。
理解了这一点,我们就能规划出动态棋盘需要做的几件大事:第一,我们要能精准地捕获鼠标在棋盘上的动作,是点击、是移动、还是释放。第二,我们要建立一个逻辑坐标系统,把鼠标的像素位置转换成“第几行第几列”的棋盘坐标。第三,我们要管理好棋盘的各种状态(哪个格子被选中、哪个棋子在拖拽)。第四,也是最体现功力的一点,就是优化绘制逻辑,让这些动态效果流畅不卡顿。接下来,我们就一步步拆解,把这些功能都给实现出来。
2. 建立棋盘坐标与鼠标事件的桥梁
要让棋盘感知到你的鼠标,首先得教它“认位置”。我们的棋盘在屏幕上是由一个个像素组成的,但下棋时我们思考的是“炮二平五”、“马8进7”这样的逻辑位置。所以,我们需要建立一个从物理像素到逻辑棋格的映射关系。
还记得我们在ChessArea类里定义的chessArea成员变量吗?它表示每个格子的宽度(比如60像素)。假设棋盘左上角第一个交叉点(也就是“车”的起始位)的坐标是(chessArea, chessArea),那么任何一个交叉点的逻辑坐标(col, row)(col和row从0开始)对应的屏幕像素坐标(x, y)就可以通过一个简单的公式计算:
QPoint logicPosToPixel(int col, int row) {
int x = chessArea + col * chessArea;
int y = chessArea + row * chessArea;
return QPoint(x, y);
}
反过来,当我们拿到一个鼠标点击的像素坐标(mouseX, mouseY),怎么反推它是哪个格子呢?这里有个小技巧,因为棋子是放在交叉点上的,所以我们判断的逻辑是:找出离鼠标位置最近的那个交叉点。计算过程可以这样:
// 在mousePressEvent事件处理函数中
void ChessArea::mousePressEvent(QMouseEvent *event) {
QPoint clickPos = event->pos(); // 获取鼠标相对于ChessArea的坐标
// 计算近似的逻辑列和行
int approxCol = (clickPos.x() - chessArea/2) / chessArea;
int approxRow = (clickPos.y() - chessArea/2) / chessArea;
// 将近似坐标约束在棋盘范围内 (0-8列, 0-9行)
approxCol = qBound(0, approxCol, 8);
approxRow = qBound(0, approxRow, 9);


被折叠的 条评论
为什么被折叠?



