Colliding Mice碰撞老鼠工程分析
工程效果
具体完整代码可在示例里面找到。工程路径在Qt安装目录下的
Examples\Qt-XX.XX.XX\widgets\graphicsview\collidingmice 目录下,XX.XX.XX为Qt的版本号,如:5.14.1。
可以看到,小老鼠碰撞后耳朵会变红。
工程总体就是多了一个mouse的源文件和头文件,即Mouse类相关文件。在Graphics View框架结构主要包含三个类:场景类(QGraphicsScene)、视图类(QGraphicsView)和图元类(QGraphicsItem),统称为“三要素”。而Mouse类就是继承自QGraphicsItem类的自定义类。
Scene类管理数量众多的Item类,并组织起他们的信息交流。但是它只是起后台管理的功能,并不会让这些Item类展现出现。View类起到将Item类可视化的功能,支持旋转和缩放。有点类似于浏览器的后端与前端,后端负责流程交互,前端负责展示效果。
mouse.h
其实QT本身就提供了Item类,包括矩形、椭圆形、线型等,但是我们一般使用的图案都是几种类型的结合,所以需要自定义类。以下为官方提供的Item类。
class Mouse : public QGraphicsItem
{
public:Mouse();QRectF boundingRect() const override;QPainterPath shape() const override;void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget) override;protected:void advance(int step) override;private:qreal angle = 0;qreal speed = 0;qreal mouseEyeDirection = 0;QColor color;
};
Mouse类是继承自QGraphicsItem类,所以我们在工程里面见到很多并未定义的函数,就是来自QGraphicsItem类。在Graphics View框架当中,QGraphicsItem是所有item的基础类。后三个函数是自定义Item类必须重新定义的纯虚函数。
paint函数是实际的绘制函数,包含老鼠的身体、眼镜等部分都是需要在paint函数里面绘制。
boundingRect 函数返回的是Item重新绘制的区域大小,相当于老鼠的平面面积。因为实际上动画的实现,就是擦除旧的,再画新的,所以好像移动了一样。这个擦除区域的大小就是由boundingRect() 来返回的。
Shape函数主要用于碰撞检测,它会返回老鼠的精确形状,默认情况下它返回的就是boundingRect的值,但有时候我们想要靠近5cm就认为碰撞,就需要重写该函数。
advance函数用来推进Scene,可以很容易实现Item的动画效果。调用Scene的advance函数就会自动调用Scene中所有Item的advance函数,而且Item的advane函数会被分为两个阶段调用两次。第一次phase为0,告诉所有的Items:Scene将要改变;第二次phase为1,在这时才进行具体的操作。
qreal是double数据类型,QColor是Qt的颜色变量,这几个定义的向量在后面会用到。
mouse.cpp
一、paint函数
先来看看它是怎么通过paint函数画出老鼠的。Graphics View框架调用paint函数绘制item的内容,而且是以本地坐标系为基准。
1.身体、眼睛、鼻子
setBrush函数是设置笔刷的函数,主要就是内部填充的颜色,这里的color变量是Mouse类的私有变量,主要是想实现随机颜色的老鼠。
drawEllipse函数是paninter类的画椭圆的函数,共有四个参数,前两个代表对应矩形的左上角的坐标,后两个代表宽度和高度。
setBrush函数是设置笔刷的函数,主要就是内部填充的颜色,这里的color变量是Mouse类的私有变量,主要是想实现随机颜色的老鼠。
drawEllipse函数是paninter类的画椭圆的函数,共有四个参数,前两个代表对应矩形的左上角的坐标,后两个代表宽度和高度。
随机颜色的设定:color是Qt类Qcolor的实例化对象,三个参数代表RGB的值。bounded函数主要用于产生0-256的随机数。帮助手册说明如下:
随机角度的确定:setRotation函数是继承自QGraphicsItem类的一个函数,主要用于设定item出现在sence坐标系时的角度。默认情况下,item一般是以sence的中心(0,0)为自身局部坐标系的中心。通过setRotation函数可以改变老鼠开始移动时的方向。
接下来画白色的眼睛和黑色的鼻子,如果drawEllipse的后两个参数是相同的,画出来就是圆形。至于这个QRectF是强制转换函数,就是将里面四个参数先转为QRectF,然后再作为drawEllipse函数的参数。这个叫函数的重载。不管是直接四个参数,还是传入QRectF参数都是可以的,QT都有提供对应的函数。
painter->setBrush(Qt::black);
painter->drawEllipse(QRectF(-2, -22, 4, 4));
2.瞳孔
这里存在一个变量就是矩形左上角的x,这个mouseEyeDirection变量是在advance函数里面进行更新的,主要实现的效果就是,当老鼠往左,那瞳孔就左移一点;当老鼠往右,那瞳孔就右移一点。
3.耳朵
耳朵是用来表示碰撞的,正常情况下耳朵是灰黄色,当老鼠之间发生碰撞,耳朵会变成红色。这里使用了scene类的collidingItems函数进行判断是否发生碰撞。这个this代表的就是类本身,就是某一个对象(老鼠),函数会返回与该老鼠碰撞的其他item的清单。因为此工程只需要判断碰撞与否,而不需要判断与谁发生碰撞,所以用isEmpty函数判断是否为空就行了。
使用手册相关说明如下:
从By default, all items whose shape intersects item or is contained inside item's shape are returned.可以得知,碰撞的检测是以shape交叉或者包含为准的,这就是shape函数实现的。
4.尾巴
drawPath函数用于绘制给定的路径曲线。
cubicTo函数是功能是画贝塞尔曲线的。使用c1和c2指定的控制点在当前位置和给定端点之间添加三次Bezier曲线。添加曲线后,将当前位置更新为曲线的端点。所以例程中存在6个参数,其实对应的就是c1、c2、endPoint三个点的坐标点。而实例化对象时用的(0,20)就是起点。
因为path.cubicTo(-5, 22, -5, 22, 0, 25);中的c1和c2是一样的,相当于曲线一定要从这一点经过(贝塞尔曲线的一个特性是:控制点之间的连线必定和曲线相交,而这两个点坐标相同,在这两个控制点就是交点,即在曲线上)。而path.cubicTo(5, 27, 5, 32, 0, 30);则表示曲线得从(5,27)和(5,32)这两个点中间经过,然后到达(0,30)这个点。
至此,老鼠已经画完了。关于贝塞尔曲线的简单介绍,请参见《贝塞尔曲线简单介绍》。
二、advance函数
当Scene准备刷新场景的时候,就会调用每一个Item类的advance函数,完成场景的刷新。通过advance函数我们可以完成老鼠的移动。
void Mouse::advance(int step){if (!step)return;QLineF lineToCenter(QPointF(0, 0), mapFromScene(0, 0));if (lineToCenter.length() > 150) {qreal angleToCenter = std::atan2(lineToCenter.dy(), lineToCenter.dx());angleToCenter = normalizeAngle((Pi - angleToCenter) + Pi / 2);if (angleToCenter < Pi && angleToCenter > Pi / 4) {// Rotate leftangle += (angle < -Pi / 2) ? 0.25 : -0.25;} else if (angleToCenter >= Pi && angleToCenter < (Pi + Pi / 2 + Pi / 4)) {// Rotate rightangle += (angle < Pi / 2) ? 0.25 : -0.25;}} else if (::sin(angle) < 0) {angle += 0.25;} else if (::sin(angle) > 0) {angle -= 0.25;}
QLineF lineToCenter(QPointF(0, 0), mapFromScene(0, 0));
《C++中反正切atan2(y,x)与atan(x)的区别》博文。
qreal angleToCenter = std::atan2(lineToCenter.dy(), lineToCenter.dx());angleToCenter = normalizeAngle((Pi - angleToCenter) + Pi / 2);
因为atan2的范围为[-PI, PI], 运用数学不等式知识,则:
0 <= PI - angleToCenter <= 2*PI
PI / 2 <= PI - angleToCenter + PI / 2 <= 2*PI + PI / 2
static qreal normalizeAngle(qreal angle)
{while (angle < 0)angle += TwoPi;while (angle > TwoPi)angle -= TwoPi;return angle;
}
为什么通过atan2角度之后,还需要经过(Pi - angleToCenter) + Pi / 2计算呢?这是我思考很久才弄明白的,因为说明手册也没说。
以下图为例,角度不代表真实角度,就是为了表示他们之间的换算关系。老鼠的头部是朝上的,我们习惯以老鼠头部为基准,然后去判断方向,如下图我们可以认为Scene坐标系原点在老鼠头部的左前方。
所以我们可以推断出这个angleToCenter角度是以Item坐标系的y轴负方向为基准,然后逆时针旋转的角度。
当然在程序中得到的数值和计算的数值都是以弧度制表示的,上面为了讲解用了角度值。原理是一样的。
angle能推测出是老鼠前进的方向,通过说明手册能得知,这个角度是Item沿Z轴旋转的方向,以顺时针为正,默认情况下为0.也就是两个坐标系x、y轴方向一致的时候是0.
根据angleToCenter值不同,angle增减值也不一样。弧度制0.25大约相当于角度制是14度。
if (angleToCenter < Pi && angleToCenter > Pi / 4) {// Rotate leftangle += (angle < -Pi / 2) ? 0.25 : -0.25;
- 假如老鼠在(pi /4, pi)区间内(见上上一副图所示)跑动时,逆时针转动的跨角小于pi / 2, 则补偿即加上0.25弧度的角度,防止老鼠跑动时,向左上角跑出了圈外。
- 假如老鼠在(pi /4, pi)区间内(见上上一副图所示)跑动时,逆时针转动的跨角大于pi / 2, 则补偿即减去0.25弧度的角度,防止老鼠跑动时,向右下角跑出了圈外。
- 假如老鼠在[pi, Pi + Pi / 2 + Pi / 4)区间内(见上上一副图所示)跑动时,顺时针转动的跨角大于pi / 2, 则补偿即减去0.25弧度的角度,防止老鼠跑动时,向右上角跑出了圈外。
- 假如老鼠在[pi, Pi + Pi / 2 + Pi / 4)区间内(见上上一副图所示)跑动时,顺时针转动的跨角小于pi / 2, 则补偿即加上0.25弧度的角度,防止老鼠跑动时,向左下角跑出了圈外。
下面几个判断的原理差不多,都是让不同位置的老鼠掉头往中心移动。
mapToScene函数是把Item坐标系的点映射到Scene坐标系,是mapFromScene函数的反操作。
如下图所示,蓝色老鼠就位于橙色老鼠0-Pi/2的区域内,根据判断条件,增加angle增加0.5,向右旋转28°,避开蓝色老鼠。
rotation函数表示的是当前item绕Z轴顺时针旋转的角度,默认值是0(如下图所示)。
setPos(mapToParent(0, -(3 + sin(speed) * 3)));
通过下图就能看到上面几个数据是如何得到的,基本就是返回一个比item还要大一点的矩形。
Shape函数
Graphice View框架使用shape函数返回了形状来确定两个item是否相撞,所以shape函数返回的形状会更加精确点。代码所示,这个矩形相当于就是老鼠的椭圆大小,不考虑尾巴多出来那部分。
Main函数
首先创建了一个scene,之前讲到的scene坐标系就是这里创建的。scene左上角在(-300,-300),长和宽都是600像素。
QGraphicsScene scene;scene.setSceneRect(-300, -300, 600, 600);
QGraphicsScene类是作为QGraphicsItems类的容器,它可以高效地决定每个item的位置和哪个item是可见的。
如下所示,Scene默认情况是使用索引算法,这种算法可以加速搜索item,比较适用于静态场景。但是如果场景有很多动画,我们可以设置为NoIndex,这样会比较快点。
scene.setItemIndexMethod(QGraphicsScene::NoIndex);
for (int i = 0; i < MouseCount; ++i) {Mouse *mouse = new Mouse;mouse->setPos(::sin((i * 6.28) / MouseCount) * 200,::cos((i * 6.28) / MouseCount) * 200);scene.addItem(mouse);}
QGraphicsView view(&scene);view.setRenderHint(QPainter::Antialiasing);view.setBackgroundBrush(QPixmap(":/images/cheese.jpg"));view.setCacheMode(QGraphicsView::CacheBackground);view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);view.setDragMode(QGraphicsView::ScrollHandDrag);
view.setWindowTitle(QT_TRANSLATE_NOOP(QGraphicsView, "Colliding Mice"));view.resize(400, 300);view.show();
QTimer timer;QObject::connect(&timer, &QTimer::timeout, &scene, &QGraphicsScene::advance);timer.start(1000 / 33);
本文转自:Qt Creator Colliding Mice碰撞老鼠例程解析【1.5W字数长文!详细!】_研究僧-彬彬的博客-CSDN博客
Colliding Mice碰撞老鼠工程分析相关推荐
- Qt Creator Colliding Mice碰撞老鼠例程解析【1.5W字数长文!详细!】
工程效果 可以看到,小老鼠碰撞后耳朵会变红.具体完整代码可在示例里面找到. 工程总体就是多了一个mouse的源文件和头文件,即Mouse类相关文件.在Graphics View框架结构主要包含三个类: ...
- Qt 视图框架示例 Colliding Mice 的翻译
目录名字 Qt 视图框架示例 Colliding Mice 的翻译 简介: Mouse Class 定义 Mouse Class 定义 The Main() 函数 Qt 视图框架示例 Collidin ...
- 机器学习cae_CAE工程分析技术年会记
读书使人充实,讨论使人机智,笔记使人准确,读史使人明智,读诗使人灵秀,数学使人周密,科学使人深刻,伦理使人庄重,逻辑修辞使人善辩.凡有所学,皆成性格.---- (英国)培根 可能是会议热度不够,非常遗 ...
- MATLAB数学计算与工程分析范例教程,MATLAB数学计算与工程分析范例教程
基本信息 书名:MATLAB数学计算与工程分析范例教程 定价:28.00元 作者:石博强,赵金 编著 出版社:中国铁道出版社 出版日期:2005-05-01 ISBN:9787#113057596 字 ...
- ML之FE:利用FE特征工程(分析两两数值型特征之间的相关性)对AllstateClaimsSeverity(Kaggle2016竞赛)数据集实现索赔成本值的回归预测
ML之FE:利用FE特征工程(分析两两数值型特征之间的相关性)对AllstateClaimsSeverity(Kaggle2016竞赛)数据集实现索赔成本值的回归预测 目录 输出结果 设计思路 核心代 ...
- 03-instancing 工程分析详解
opengl编程指南第8版源码怎么下载.编译,请参考<opengl编程指南第8版源码编译详细说明> 1. 程序启动 请参考<03-drawcommands工程分析详解> 2. ...
- python与金融工程的区别_科研进阶 | 纽约大学 | 金融工程、量化金融、商业分析:Python金融工程分析...
科研进阶 | 纽约大学 | 金融工程.量化金融.商业分析:Python金融工程分析(2021.2.6开课)mp.weixin.qq.com 课题名称 = Python金融工程分析 = 项目配景 大数 ...
- MATLAB数学计算与工程分析范例教程,MATLAB 2016数学计算与工程分析从入门到精通...
全书通过近400个实例讲解了利用MATLAB 2016进行数学计算和工程分析的方法和技巧,涵盖了MATLAB的五大功能:1)数值计算功能:2)符号计算功能:3)图形与数据可视化功能:4)可视化建模与仿 ...
- 计算机辅助工程分析课程论文,教学大纲—计算机辅助工程分析.doc
<计算机辅助工程分析>课程教学大纲 英文课程名Computer Aided Engineering总 学 时32学 分2课程编码202725理论教学学时8适用专业机械工程.过程装备与控制工 ...
最新文章
- STM32 UART2程序--端口重映射
- EJS学习(五)之EJS的CommonJs规范版本
- Javascript实现MD5加密
- 数学--数论--HDU6919 Senior PanⅡ【2017多校第九场】
- linux proc进程,linux 下 /proc/进程号/ 重要进程文件的内容解析
- MiniProfiler.EF6监控调试MVC5和EF6的性能
- Python3.7.2版本出现ModuleNotFoundError: No module named 'paramiko'解决办法
- HTTP所承载的货物(图像、文本、软件等)要满足的条件
- Scrapy 导出的 cvs 文件,双击打开乱码问题
- 大项目之网上书城(一)——注册页面
- c语言入门if语句(嵌套)
- 需求分析与原型设计———记账软件
- 摄影基础1 : 135相机
- 图形学进阶——移动端TB(D)R架构基础
- 网友评选最好玩实用的二十大良心网站,You Know?!
- android手机网速,简单一步让你的手机网速至少提升2倍!
- html输入文本颜色,Input输入字体颜色改变js(兼容IE)
- 【Maven】基础概念、仓库、构建与部属
- 唐常杰--一篇 它引 上万的大牛论文 与 数据血统论-- 趣味数据挖掘之三
- 帝国CMS审计-后台模板注入导致getshell
热门文章
- FL Studio电音编曲软件V21中文完整版 安装下载教程
- 明天终于要到公司开工了
- matlab实现密堆立方体,LAMMPS如何定义六角密堆结构HCP
- 前端开发神器VS Code安装教程
- javascript 判断 flash 插件是否安装
- style-component中引入icon-font步骤以及出现方块问题的解决
- 水漆哪个品牌好?十大品牌水漆排行榜
- 清华大学 博士后 原来入的计算机科学与技术 现在能入软件工程吗,清华大学软件学院...
- 服务器上MySQL数据库密码忘了
- 谷歌又闹大乌龙!Jeff Dean参与的新模型竟搞错Hinton生日