Essential Qt 第十九章 子类化QGraphicsItem
来源:互联网 发布:华诚律师事务所 知乎 编辑:程序博客网 时间:2024/06/07 05:07
前面两章通过元素/视图架构完成了一个贪吃蛇游戏,但这个游戏还有些不足,首先,游戏以吃到10个食物为胜利条件,而在游戏中用户却不知道游戏到底进行到哪一步了,其次,游戏的界面有些单调,比如蛇的身体,如果有些色彩会好看不少,所以对上上一章的游戏做些改进,大致上会是下面这个样子
从图上可以看出,每个食物上多了一个数字,这样可以提示用户游戏的精度,同时蛇的身体有了色彩上的变化,而实现这些功能,需要子类化QGraphicsItem。Qt提供了大量的QGraphicsItem的子类以供我们使用,但有时候出现要求比较特殊,Qt的默认类没办法满足我们的需求时,就必须子类化QGraphicsItem来实现。
首先看食物,上一章的食物使用的是QGraphicsRectItem,这里将会用一个自定义的FoodItem类来代替他,FoodItem继承自QGraphicsItem,QGraphicsItem里有两个纯虚函数需要实现.,首先看下FoodItem的头文件
class FoodItem : public QGraphicsItem{private: int number_int; //该值用于记录当前的数字(即游戏中第几个食物)public: FoodItem(int nu , QGraphicsItem* parent = 0); void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); //paint()和boundingRect()这两个是必须重写的纯虚函数 QRectF boundingRect()const; QPainterPath shape()const; //这个函数用于显示外形,关于外形和外框稍后再述,这里这个函数可以暂时不用实现 void updateNumber(int nu); //这个自定义的函数用于更新食物上的数字};
绘制函数的实现
void FoodItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget){ Q_UNUSED(option) //该函数不需要使用option和widget这两个参数,这里使用Q_UNUSED宏来避免编译器出现各种warning Q_UNUSED(widget) painter->setBrush(QBrush(Qt::yellow)); painter->drawRect(1,1,MAP_SIZE_SNAKE-2,MAP_SIZE_SNAKE-2); //绘制背景 painter->setPen(QPen(QColor(Qt::red))); QFont ft; ft.setPointSize(15); painter->setFont(ft); //设置字体的颜色和大小,然后把数字绘制,写在item上 painter->drawText(QRect(1,1,MAP_SIZE_SNAKE-2,MAP_SIZE_SNAKE-2),Qt::AlignCenter,QString::number(number_int));}绘图函数通过两次draw来绘出完整的“食物”,先绘制一个黄色方块,再在上面绘制一个红色的数字,这里需要注意的是绘制的区域比20X20的区域小了点,这是为了方便后面的碰撞检测,暂时可以忽略坐标上的细微变化
然后是boundingRect()函数的实现
QRectF FoodItem::boundingRect()const{ return QRectF(0,0,MAP_SIZE_SNAKE,MAP_SIZE_SNAKE);}该函数用于返回item的外框,而下面的shape()函数则用于返回item的外形
QPainterPath FoodItem::shape()const{ QPainterPath p; p.addRect(1,1,MAP_SIZE_SNAKE-2,MAP_SIZE_SNAKE-2); return p;}关于一个item的外框和外形,就这个类来说并没太大区别,需要注意的是,外框的返回值是QRectF,也就是说一个item的外框只能是个矩形,而外形的返回值是QPainterPath,这是一个可以记录复制区域的类,这说明外形可以是个复制的形状,而外框只能是个矩形,关于外框和外形的关系,在稍后的SnakeItem类中会进一步说,应为对于FoodItem来说,外形和外框是一直的,外形比外框的区域小一点点也只是为了后面的碰撞函数做准备,另外需要说明的一点是外形(shape)和外框(boundingRect)这两个名字是我起的,单纯为了方便描述为题
最后是updateNumber()函数,主程序每次更新食物位置前需要调用该函数来跟新数字,具体实现很简单
void FoodItem::updateNumber(int nu){ number_int = nu;}
到这里为止可以使用FoodItem来取代,假下来需要使用SnakeItem类来取代QGraphicsPathItem,这里同样直接继承自QGraphicsItem类,先看下头文件
class SnakeItem : public QGraphicsItem{private: QList<GridPoint> snakePaths_List;public: SnakeItem(QGraphicsItem* parent = 0); void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); QRectF boundingRect()const; QPainterPath shape()const; void setPath(const QList<GridPoint>& newPath); //自定义函数};QGraphicsPathItem继承QGraphicsItem的时候添加了一个函数setPath(),而我们实现的SnakeItem类需要一个类似功能的函数,所以这里就直接命名为setPath(),但这个函数的参数和QGraphicsPathItem提供的setPath()完全不同
接下来是绘制函数的实现
void SnakeItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget){ Q_UNUSED(option) Q_UNUSED(widget) int colors = 1; for(auto A :snakePaths_List) { QRect rc(A.x*MAP_SIZE_SNAKE,A.y*MAP_SIZE_SNAKE,MAP_SIZE_SNAKE,MAP_SIZE_SNAKE); painter->setBrush(QBrush(QColor(colors*10,colors*2,colors*15))); //颜色的递变 painter->drawRect(rc); ++colors; }}绘制函数很简单,蛇的身体是由诺干个连续的方块构成,绘制函数就对这些方块区域逐个绘制,这样蛇的颜色就会变成彩色,这里只是为了演示所以演示弄的很简单(淡出的递减),如果你喜欢也可以设置成变的颜色
接下来就是略显混淆的外框(boundingRect())和外形(shape())函数了
QRectF SnakeItem::boundingRect()const{ int maxX = snakePaths_List.first().x; int maxY = snakePaths_List.first().y; int minX = maxX; int minY = maxY; for(auto A : snakePaths_List) { if(maxX < A.x) maxX = A.x; if(maxY < A.y) maxY = A.y; if(minX > A.x) minX = A.x; if(minY > A.y) minY = A.y; } int X = minX*MAP_SIZE_SNAKE; int Y = minY*MAP_SIZE_SNAKE; int W = (maxX-minX+1) * MAP_SIZE_SNAKE; int H = (maxY-minY+1) * MAP_SIZE_SNAKE; return QRectF(X,Y,W,H); //外框返回一个矩形}QPainterPath SnakeItem::shape()const{ QPainterPath p; for(auto A : snakePaths_List) p.addRect(QRectF(A.x*MAP_SIZE_SNAKE,A.y*MAP_SIZE_SNAKE,MAP_SIZE_SNAKE,MAP_SIZE_SNAKE)); //外形返回的是一个复制的区域(即蛇的身体区域) return p;}
关于外框和外形的区别,可以参照下图,以蛇为例,红色区域就是外框,所以在boundingRect()函数中需要计算除所有坐标的最大/最小值,即上下左右四个极值,
通过这四个值(上下左右)形成的区域就是蛇的外框,而外形就比较好理解了,所能看到的拥有各种形状的蛇的身体就是外形,另外需要确定的是外形的区域不能位于外框之外,否则就无法显示出来
对于前面的FoodItem来说,由于外形和外框一致,如果不需要使用稍后使用的碰撞函数的话完全可以不用实现shape()函数,之所以实现shape()函数并且是的食物的外形比外框略小,是为了后面碰撞函数服务。但对于像SnakeItem这样有着复制外形的item来说,他们的外框和外形并不一直,所以需要额外实现shape(0函数来获得item的外形。
最后是setPath()函数的实现
void SnakeItem::setPath(const QList<GridPoint>& newPath){ snakePaths_List.clear(); snakePaths_List = newPath;}到这里我们实现了SnakeItem类,如果直接替换条游戏中原来的QGraphicsPathItem类会在显示上出现状况,出现这种情况的原因是,每当当蛇移动时会调用setPath()函数来重置蛇的路径(身体所在区域),必须刷新item的外形,然后机智的我就在setPath()函数里加上一句
update();希望调用item的paint()函数来实现刷新item外形的作用,实际操作确实完全无效,对于QGraphicsItem来说,他的update()函数无法调用自己的paint()函数,也就是说无法更新item的形状,要更新item,必须调用QGraphicsScene->update();,scene会更新在他上面的所有item,即调用位于scene的所有item的paint()函数。
最后是碰撞函数,最初使用的是判断食物和蛇的坐标是否相等来判断蛇是否吃到(碰撞)到食物,对于这个例子来,这样做并没有什么不妥,甚至非常方便,但一旦item的外形比较复杂时,使用坐标判断就比较麻烦了,QGraphicsItem提供了专门的碰撞函数来判断碰撞
virtual bool collidesWiteItem(const QGraphicsItem *other, Qt::ItemSelectionModemode = Qt::IntersectsItemShape) const virtual bool collidesWitePath(const QPainterPath &path, Qt::ItemSelectionModemode = Qt::IntersectsItemShape) const virtual bool collidingItems(Qt::ItemSelectionModemode = Qt::IntersectsItemShape) const
以上内容复制自Qt文档,函数的作用和他们的名字一样,需要注意的是Qt::ItemSelectionMode这个枚举值,他代表碰撞的类型
Qt::ContainsItemShape 当一个item的外形完全包含了另一个item
Qt::IntersectsItemShape 当两个item的外形有任意的重叠
Qt::ContainsItemBoundingRect 当一个item的外框完全包含了另一个item
Qt::IntersectsItemBoundingRect 当两个item的外框有任意的重叠
这就是前面为什么FoodItem的外形和外框一样,还需要重新实现shape()函数的原因,碰撞在主函数里碰撞检测使用的是检测外形,当没有实现shape()的时候,程序会把boundingRect()函数的返回值作为外形,所以处于谨慎的原则,还是重新实现了shape()函数.
完整代码https://pan.baidu.com/s/1sldsMtv
- Essential Qt 第十九章 子类化QGraphicsItem
- QGraphicsItem的子类化问题
- Essential Qt 第十章 事件
- Essential Qt 第二十章 数据库
- 初识Qt绘图QGraphicsItem
- QT 继承自QGraphicsItem
- qt 继承QGraphicsItem
- 初识Qt绘图QGraphicsItem
- QGraphicsItem子类对象间的通信
- Essential Qt 第二章 界面布局
- Essential Qt 第三章 信号与槽
- Essential Qt 第三章 搜寻对话框
- Essential Qt 第四章 记事本(一)
- Essential Qt 第九章 内存管理
- Essential Qt 第十一章 事件的运用
- Essential Qt 第十二章 文本文件的读写
- Essential Qt 第十三章 文件拖放
- Essential Qt 第十四章 事件过滤器
- 兄弟连学Python(5) -- 块排方法进行排序
- 机械臂的运动规划和运动学都是成熟的
- DB2开发与性能优化
- mysql的安装
- 《spring cloud微服务实战》读书笔记——Spring Cloud Hystrix(三)断路器的原理
- Essential Qt 第十九章 子类化QGraphicsItem
- 转:kaggle案例:员工离职预测 (附视频)
- lucene&solr从入门到精通-----删除,修改,查询
- php变量与变量类型
- 北上广租房记
- Redis初窥:List操作常用命令
- PyQt5笔记(05) -- 绝对位置
- Dubbo comsumer 远程调用流程分析
- 亿图图示中文破解版(附带软件破解补丁及软件破解教程)