0%

倒车雷达(三):动画制作

在PC端绘制倒车的动画,达到实时显示车辆的倒车情况的目的。如车辆倒退时,动画中的车辆也倒退,车辆左转时,动画也实时变化。使用的工具为QT(python),做了一个如上形式的可视化界面。

  • 选择的工具为:PyQt5,QGraphicsView,QGraphicsRectItem;
  • 与以往的GUI设计不同,这次的GUI开发为场景和实体的运动,而不是控件的使用;前者更偏向游戏设计,场景移动和物体的碰撞检测,后者更像使用的软件,点击按钮输入密码等;

设置物体

首先创建车辆的实体,这里以方块代表车辆:

1
2
3
class RectItem(QGraphicsRectItem):
def __init__(self, rect=QRectF()):
super(RectItem, self).__init__(rect)

而后创建场景,场景中包括车辆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 负责绘制车辆的类
class GraphicsView(QGraphicsView):
def __init__(self):
super(GraphicsView, self).__init__()

# 创建图形容器
self.setFixedSize(460, 560)

# 将车库实体化为 宽度450 长度600 大小
self.scene = QGraphicsScene()
self.scene.setSceneRect(0, 0, 450, 550)

# 创建车辆实例
self.rect = RectItem()

# 初始化位置为 0 30 车辆宽度 250 长度 400
self.rect.setRect(0, 0, 240, 300)

# 创建颜色刷子
brush1 = QBrush(Qt.SolidPattern)
brush1.setColor(QColor(124, 214, 175))

# 给车身上色
self.rect.setBrush(brush1)

# 将车身添加到容器中 即放到车库里
self.scene.addItem(self.rect)

# 设置当前场景
self.setScene(self.scene)

之后,再将场景添加到窗口中即可,伪代码如下:

1
2
3
4
5
6
7
g = GraphicsView()
# g.setFixedSize(450, 550)
self.car = g.rect
self.scene = g.scene

# print(self.scene.x())
layout.addWidget(g)

因初始化位置为(0, 0),所以车辆在左上角,效果如下所示。完整代码,car_test.py

物体移动

最初的想法是:设定一组位置,通过time.sleep()设置移动速度来达到缓慢移动的效果,但这并不是可行的思路,错误的伪代码如下:

1
2
3
4
5
6
7
x, y = 100, 100
i, j = 0, 0
def move(self):
time.sleep(1)
car.setpos(i, j)
if i < x and j < y:
i += 10, j += 10

但实际是,物体会直接移动到(100, 100),并没有中间的过程。在Qt的开发中,一定不能使用time.sleep这种方法,因为它会阻塞事件的循环,导致窗口冻结,从而阻止了GUI的重新绘制。所以在Qt中,必须使用事件来执行操作,如:分为显示事件和工作事件,显示事件负责GUI的显示,工作事件负责刷新物体的位置。如:每次触发计时器的计时时,计算物体的位置,而后显示事件负责更新。

此时修改车辆实体的类,增加光滑移动的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class RectItem(QGraphicsRectItem):
def __init__(self, rect=QRectF()):
super(RectItem, self).__init__(rect)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self._pos_animation = QVariantAnimation()
self._pos_animation.valueChanged.connect(self.setPos)

def move_smooth(self, end, duration):
if self._pos_animation.state() == QAbstractAnimation.Running:
self._pos_animation.stop()
self._pos_animation.setDuration(duration)
self._pos_animation.setStartValue(self.pos())
self._pos_animation.setEndValue(end)
self._pos_animation.start()

而后设置计时器及时触发即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
btn_start.clicked.connect(self.run)

def move_pos(self, scene):
Left = [40]
Center = [40]
for it in scene.items():
self.item = it
for left, center in zip(Left, Center):
pos = QPointF(left, center)
if hasattr(it, 'move_smooth'):
it.move_smooth(pos, 500)

def run(self, distance):
# 偏函数传入函数和参数
wrapper = partial(self.move_pos, self.scene)
timer = QTimer(interval=5000, timeout=wrapper)
timer.start()
wrapper()

实现效果如下,此时可以完美的时间光滑移动。完整代码

物体旋转

我摸索了两种旋转方案,如下图所示:

  • 第一种旋转方案是以左上角的顶点为中心,逆时针旋转
  • 第二种旋转方案是以矩形的中心为旋转点,逆时针旋转

经仿真模拟,第二种方案在操作难度和展示效果两个方面均取得不错的效果。

那么,以此来设置旋转方案:

  • 设立向左旋转的角度self.left_angle和向右旋转的角度self.right_angle两个参数,均初始化为5度
  • 向左旋转时,self.left_angle += 5, self.right_angle -= 5
  • 同理,向右旋转时,self.left_angle -= 5, self.right_angle += 5
  • PyQt5默认的旋转方式是逆时针,所以向左旋转时,角度为(360 - self.left_angle)
  • scenePos()方法可以捕获当前物体左上角的坐标,因此坐标加上车身宽度的一半就是旋转点的横坐标,加上车身长度的一半就是旋转点的纵坐标
  • 更严谨一点可以加上旋转角度来计算旋转点坐标的位置,因为实际的车库倒车中不会频繁的左右转动,但我选择了忽略这些误差。

因此,不难得到旋转的代码:(旋转点的计算不够严谨)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
self.angle_left = 5
self.angle_right = 5

# 以车辆中心为旋转点
def rorate_right(self):
pos = self.car.scenePos()
x = pos.x() + 120
y = pos.y() + 150
# 设置旋转点
self.car.setTransformOriginPoint(QPointF(x, y))
self.car.setRotation(360 - self.angle_right)
self.angle_right += 5
self.angle_left -= 5

def rorate_left(self):
pos = self.car.scenePos()
x = pos.x() + 120
y = pos.y() + 150
self.car.setTransformOriginPoint(QPointF(x, y))
self.car.setRotation(self.angle_left)
self.angle_left += 5
self.angle_right -= 5

实现的效果如下。完整代码

结语

而接下来的就是读取车辆倒车时与后方障碍物的距离,根据距离信息更新动画中车辆的位置。这涉及了多线程的东西,将在下文给出实现方案。

参考

站在巨人的肩膀上,我们能更好的前行:

  1. https://stackoverflow.com/questions/55277098/move-qgraphicsitem-continuously-in-qgraphicsscene-and-check-collision
  2. https://stackoverflow.com/questions/11147443/rotate-qgraphicspixmapitem-with-mouse
感谢上学期间打赏我的朋友们。赛博乞讨:我,秦始皇,打钱。

欢迎订阅我的文章