书接上文:Python010: Python大作业之移动的小火车动画(三)结果显示
0.注意:
该项目使用的库和资源说明如下:
pygame 2.0.1 (SDL 2.0.14, Python 3.6.6)
另外还添加了一个字体如下图:
1.MyLuckyTrain.py
import time
import pygame
import CCarriage
from CCarriage import CarrigSize
from CCarriage import TrackSize
from CCarriage import Speed
from Common import one_get_three
from Common import Colors
from Common import dis_group_info
# ==================================================================================================
WINDOW_SIZE = (1500, 800) # 定义窗口的宽、高
FONT_SIZE = 30 # 设置字体大小
CARGE_SIZE = CarrigSize.middle # 确定车的大小,关系到点源的选取和车厢的绘制
TRACK_SIZE = TrackSize.large # 指定车轨道的大小
START_POINT = (150, 750) # 指定轨道的起点
TRAIN_SPEED = Speed.High_speed # 指定小火车的速度
# ==================================================================================================
# 初始化
pygame.init()
# 创建窗口--大小
MyWindow = pygame.display.set_mode(WINDOW_SIZE)
# 设置窗口标题
pygame.display.set_caption("懂王唐王的小火车")
# 设置窗口背景为白色
MyWindow.fill(Colors.white)
# 刷新窗口
pygame.display.flip()
# 创建轨道对象
track = CCarriage.Tracks(MyWindow, TRACK_SIZE, START_POINT)
# 绘制轨道
track.draw_tracks()
# 获取起点的源列表
list_source = track.getstartlist()
# 获取最终的点集
fin_list = one_get_three(CARGE_SIZE * 0.6, CARGE_SIZE * 0.6, list_source)
list_len = len(fin_list)
# 刷新窗口--更新轨道
pygame.display.update()
# 将点集变成迭代器
it_train = iter(fin_list)
# 循环保持程序的一直运行
num = 0
g_bFlag = True
while g_bFlag:
num += 1 # 用于防止迭代器穷尽后弹出异常
# ======================动画帧的刷新=========================#
if num < list_len:
templist = next(it_train)
MyWindow.fill(Colors.white) # 重绘背景
pygame.draw.rect(MyWindow, Colors.gray, (10, 10, WINDOW_SIZE[0] - 20, WINDOW_SIZE[1] - 20), 1)
dis_group_info(MyWindow, "Course Info: Python 期中大作业 ", "Group Member: 董照诚 唐佳玄 王凯 王雅婷 ", FONT_SIZE)
track.draw_tracks()
trainback = CCarriage.Carriages(MyWindow, CARGE_SIZE, templist[0], templist[1]) # 绘制小火车的后车厢
hook1 = trainback.draw_carriages()
trainford = CCarriage.Carriages(MyWindow, CARGE_SIZE, templist[2], templist[3]) # 绘制小火车的前车厢
hook2 = trainford.draw_carriages()
pygame.draw.line(MyWindow, Colors.yellow, hook1[1], hook2[0])
pygame.display.update()
time.sleep(TRAIN_SPEED)
# 4.检测事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
g_bFlag = False
2.Common.py
import pygame
import math
from math import sin
from math import cos
from math import atan
from math import pi
from math import sqrt
# 颜色类Colors,画图时可直接选择颜色
class Colors:
white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
sky_blue = (28, 180, 240)
font_blue = (71, 161, 250)
yellow = (255, 190, 0)
gray = (60, 63, 65)
# 功能 :给一个向量的起点和终点的坐标,求解该延长线上距离终点为r的点坐标,也即two_get_third所表达的含义
# 参数1:距离r
# 参数2:终点p1的坐标
# 参数3:起点p2的坐标
# 返回 :一个列表---包含了待求点和p2p1组成的向量的方向角(单位弧度)
def two_get_third(r=0.0, point1=(), point2=()): # 注意:point1()为靠近待求点那一侧的点
ret_list = [(0, 0), 0.0]
if point1[0] - point2[0] == 0: # 如果车轮两点在一条竖线上
if point1[1] - point2[1] > 0:
ret_list = [(point1[0], point2[1] + r), pi / 2] # p1在p2的上面
else:
ret_list = [(point1[0], point2[1] - r), -pi / 2] # p1在p2的下面
else: # 如果车轮两点不在一条竖线上
if point1[0] < point2[0]:
rad = atan((point1[1] - point2[1]) / (point1[0] - point2[0])) + pi
else:
rad = atan((point1[1] - point2[1]) / (point1[0] - point2[0]))
vec_cos = cos(rad) # 先算p2-->p1的向量的方向余弦
vec_sin = sin(rad) # 再算p2-->p1的向量的方向正弦
ret_list = [(r * vec_cos + point1[0], r * vec_sin + point1[1]), rad]
return ret_list
# 功能 :对于两节车厢,有4个车轮,也即是4个点记为P1、P2、P3、P4
# 该函数的功能即使,通过给定的P1,求得P1对应的P2、P3、P4
# 参数1:r1 车轮间距
# 参数2:r2 车厢连接处轮间距
# 参数3:由n多个P1组成的P1的点的列表
# 返回 :由n个形如[P1、P2、P3、P4]组成的列表
#
# 为什么这么做?
# 因为画车厢所有的源头都是来自于P1、P2、P3、P4这4个点
def one_get_three(r1, r2, my_list=[]):
p_sour = my_list[0]
accu = r1 / 12 # 精度整r1的1/12,也即车轮间距的1/12
rev_len = math.floor((3 * r1 + 0.4 * r1) / my_list[1]) + 10 # 取点精度主要是来自于轨道设计处
retril_range = 100
p_start_list = p_sour[:(len(p_sour) - rev_len)]
ret_plist = []
for each in p_start_list:
temp_list = [(), (), (), ()]
# P1
temp_list[0] = each
# P2
e_index = p_sour.index(each)
if e_index > (len(p_sour) - retril_range - 1):
e_index_e = len(p_sour) - 1
else:
e_index_e = e_index + retril_range
it_1 = iter(p_sour[(e_index + 1):e_index_e]) # 创建一个迭代器,包含p_sour中each之后的所有元素
temp_point_1 = next(it_1)
flag_1 = sqrt((temp_point_1[0] - each[0]) ** 2 + (temp_point_1[1] - each[1]) ** 2) - r1 # 求得p1和p2'之间的距离
while flag_1 < -accu or flag_1 > accu:
temp_point_1 = next(it_1)
flag_1 = sqrt((temp_point_1[0] - each[0]) ** 2 + (temp_point_1[1] - each[1]) ** 2) - r1
temp_list[1] = temp_point_1
# P3
f_index = p_sour.index(temp_point_1) # 求出p2的索引
if f_index > (len(p_sour) - retril_range - 1):
f_index_e = len(p_sour) - 1
else:
f_index_e = f_index + retril_range
it_2 = iter(p_sour[(f_index + 1):f_index_e])
temp_point_2 = next(it_2)
flag_2 = sqrt((temp_point_2[0] - temp_point_1[0]) ** 2 + (temp_point_2[1] - temp_point_1[1]) ** 2) - r2
while flag_2 < -accu or flag_2 > accu:
temp_point_2 = next(it_2)
flag_2 = sqrt((temp_point_2[0] - temp_point_1[0]) ** 2 + (temp_point_2[1] - temp_point_1[1]) ** 2) - r2
temp_list[2] = temp_point_2
# P4
g_index = p_sour.index(temp_point_2) # 求出p3的索引
if g_index > (len(p_sour) - retril_range - 1):
g_index_e = len(p_sour) - 1
else:
g_index_e = g_index + retril_range
it_3 = iter(p_sour[(g_index + 1):g_index_e])
temp_point_3 = next(it_3)
flag_3 = sqrt((temp_point_3[0] - temp_point_2[0]) ** 2 + (temp_point_3[1] - temp_point_2[1]) ** 2) - r1
while flag_3 < -accu or flag_3 > accu:
temp_point_3 = next(it_3)
flag_3 = sqrt((temp_point_3[0] - temp_point_2[0]) ** 2 + (temp_point_3[1] - temp_point_2[1]) ** 2) - r1
temp_list[3] = temp_point_3
ret_plist.append(temp_list)
return ret_plist
def dis_group_info(wnd, str1="", str2="", size=30):
myfont = pygame.font.Font('resource/yaheiconsolashybrid.ttf', size)
text_course_info = myfont.render(str1, True, Colors.font_blue)
text_meminfo = myfont.render(str2, True, Colors.font_blue)
wnd.blit(text_course_info, (20, 20)) # 渲染文字
wnd.blit(text_meminfo, (20, 20 + size + 10)) # 渲染文字
3.CCarrage.py
import pygame
from Common import two_get_third
from Common import Colors
from math import pi
from math import cos
from math import sin
import math
class Carriages:
r1 = 0.0 # 同一车厢车钩与车轮的距离
r2 = 0.0 # 两个车轮的间距
# 车厢类的初始化函数
def __init__(self, wnd, carge_size, p1=(), p2=()):
self.myWindow = wnd # 目标窗口
self.carge_P1 = p1 # 后车厢的后车轮
self.carge_P2 = p2 # 后车厢的前车轮
# self.carge_P3 = p3 # 后车厢的挂钩
self.carge_len = carge_size # 车厢长度
self.carge_wid = carge_size / 6 # 车的宽度 已知车轮间距x--->车宽为x/3
self.r1 = carge_size * 0.1 # 同一车厢车钩与车轮的距离 已知车轮间距x--->车轮和车钩的距离为x/6
self.r2 = carge_size * 0.6 # 两个车轮的间距 已知车轮间距x--->车长度为x/0.6
# 绘制单节车厢
def draw_carriages(self):
# 先画车轮
pygame.draw.circle(self.myWindow, Colors.black, self.carge_P1, self.carge_wid / 6, 0) # 后车轮
pygame.draw.circle(self.myWindow, Colors.black, self.carge_P2, self.carge_wid / 6, 0) # 前车轮
# 再画挂钩
l_p3_list = two_get_third(self.r1, self.carge_P1, self.carge_P2) # 返回一个列表,第一个元素为点
pygame.draw.circle(self.myWindow, Colors.yellow, l_p3_list[0], self.carge_wid / 6, 0) # 左挂钩
l_p4_list = two_get_third(self.r1, self.carge_P2, self.carge_P1)
pygame.draw.circle(self.myWindow, Colors.yellow, l_p4_list[0], self.carge_wid / 6, 0) # 右挂钩
# 再画车框---先求4个顶点---再用一条lines语句画折线
# 求出左下方的点lb
l_on_line_list = two_get_third(self.carge_len * 0.2, self.carge_P1, self.carge_P2)
vec_coslb = cos(l_on_line_list[1] - pi / 2)
vec_sinlb = sin(l_on_line_list[1] - pi / 2)
p_on_linel = l_on_line_list[0]
lb_point = (p_on_linel[0] + self.carge_wid / 2 * vec_coslb, p_on_linel[1] + self.carge_wid / 2 * vec_sinlb)
# 求出左上方的点la
vec_cosla = cos(l_on_line_list[1] + pi / 2)
vec_sinla = sin(l_on_line_list[1] + pi / 2)
la_point = (p_on_linel[0] + self.carge_wid / 2 * vec_cosla, p_on_linel[1] + self.carge_wid / 2 * vec_sinla)
# 求出右下方的点rb
r_on_line_list = two_get_third(self.carge_len * 0.2, self.carge_P2, self.carge_P1)
vec_cosrb = cos(r_on_line_list[1] + pi / 2)
vec_sinrb = sin(r_on_line_list[1] + pi / 2)
p_on_liner = r_on_line_list[0]
rb_point = (p_on_liner[0] + self.carge_wid / 2 * vec_cosrb, p_on_liner[1] + self.carge_wid / 2 * vec_sinrb)
# 求出右上方的点ra
vec_cosra = cos(r_on_line_list[1] - pi / 2)
vec_sinra = sin(r_on_line_list[1] - pi / 2)
ra_point = (p_on_liner[0] + self.carge_wid / 2 * vec_cosra, p_on_liner[1] + self.carge_wid / 2 * vec_sinra)
# 绘制闭合折线--矩形
point_list = [la_point, lb_point, rb_point, ra_point]
pygame.draw.lines(self.myWindow, Colors.black, True, point_list, 1)
return [l_p3_list[0], l_p4_list[0]]
class Tracks:
__curv1_rad = 0.0 # 圆弧轨道1跨越的弧度
__curv2_rad = 0.0 # 圆弧轨道2跨越的弧度
startlist = [] # 起点的列表
__l1_end_p = () # 直线轨道1的末端
__cur1_start = 0.0 # 弧线轨道1的起始角
__cur1_end_p = () # 弧线轨道1的末端点
__o1_p = ()
__vec_l2_cos = 0.0 # 直线轨道2的方向余弦
__vec_l2_sin = 0.0 # 直线轨道2的方向正弦
__l2_end_p = () # 直线轨道2的末端点
__cur2_end = 0.0 # 弧线轨道2的终止角度(这是相对于draw.arc函数的终止),相对于弧线轨道,其实是起始角
__o2_p = ()
__l3_start_p = () # 直线轨道3的起点
__l3_end_p = () # 直线轨道3的末端点
# 轨道类的构造函数
def __init__(self, wnd, tracksize=[(0, 0, 0), (0, 0), (0, 0)], start_p=(0.0, 0.0)):
self.myWindow = wnd # 目标窗口
self.line1 = tracksize[0][0] # lines[0] # 直轨道1长度
self.line2 = tracksize[0][1] # lines[1] # 直轨道2长度
self.line3 = tracksize[0][2] # lines[2] # 直轨道3长度
self.curv1_R1 = tracksize[1][0] # curves1[0] # 圆弧轨道1的半径
self.curv1_M1 = tracksize[1][1] # curves1[1] # 圆弧轨道1的弧长
self.curv2_R2 = tracksize[2][0] # 圆弧轨道2的半径
self.curv2_M2 = tracksize[2][1] # curves2[1] # 圆弧轨道2的弧长
self.start_point = start_p # 轨道的起点
self.__to_rad()
# 求出圆弧轨道的弧度
def __to_rad(self):
self.__curv1_rad = self.curv1_M1 / self.curv1_R1
self.__curv2_rad = self.curv2_M2 / self.curv2_R2
# 绘制车道
def draw_tracks(self):
width = 1
# ==================================================================================================================
# 第一段直线
l1_end_p = (self.start_point[0] + self.line1, self.start_point[1])
pygame.draw.line(self.myWindow, Colors.red, self.start_point, l1_end_p, width)
self.__l1_end_p = l1_end_p
# ==================================================================================================================
# 第一段圆弧
# 1.求出pos
# 2.确定起始角度
# 3.确定终止角度
# 4.画弧
# 如果添加一个偏移量(试了一下加一个像素就会好很多),图片显示会更加精确一点
rect_pos_c1 = (l1_end_p[0] - self.curv1_R1, l1_end_p[1] - 2 * self.curv1_R1, 2 * self.curv1_R1, 2 * self.curv1_R1)
# delta = math.radians(2)
cur1_start = -pi / 2
cur1_end = cur1_start + self.__curv1_rad
cur1_end_re = cur1_end + math.radians(3) # 添加一个修正角度,使其连接在一起
pygame.draw.arc(self.myWindow, Colors.sky_blue, rect_pos_c1, cur1_start, cur1_end_re, width)
self.__cur1_start = -cur1_start
# ==================================================================================================================
# 第二段直线
# 1.求出第一段圆弧的圆心
# 2.求出O1P1向量的方向角(方向角与画图时的角度是不一样的,关于x轴镜像)
# 3.求出P1--cur1_end_p点
# 4.偏移90°得到第二段直线的方向角
# 5.有了方向就有了方向余弦和方向正弦
# 6.结合line2的长度求出line2的终点
# 7.划线即可
o1_p = (l1_end_p[0], l1_end_p[1] - self.curv1_R1)
vec_rad_o1p1 = -cur1_end
vec_cos = cos(vec_rad_o1p1) # 方向余弦
vec_sin = sin(vec_rad_o1p1) # 方向正弦
cur1_end_p = (o1_p[0] + self.curv1_R1 * vec_cos, o1_p[1] + self.curv1_R1 * vec_sin)
self.__cur1_end_p = cur1_end_p
self.__o1_p = o1_p
vec_l2_rad = vec_rad_o1p1 - pi / 2
vec_l2_cos = cos(vec_l2_rad)
vec_l2_sin = sin(vec_l2_rad)
l2_end_p = (cur1_end_p[0] + self.line2 * vec_l2_cos, cur1_end_p[1] + self.line2 * vec_l2_sin)
pygame.draw.line(self.myWindow, Colors.red, cur1_end_p, l2_end_p, width)
self.__vec_l2_cos = vec_l2_cos
self.__vec_l2_sin = vec_l2_sin
self.__l2_end_p = l2_end_p
# ==================================================================================================================
# 画第二个圆弧
# 1.求出P2O2向量的方向角(其中圆弧1和圆弧2在第二段直线的两端处的方向是平行)
# 2.根据已知的l2_end_p求得圆心O2的坐标
# 3.根据圆心坐标和圆弧的半径求得pos
# 4.求出圆弧的终止弧度:因为l3直线是水平,所以start弧度已知,减去圆弧角得end弧度
# 5.起始弧度、终止弧度、pos均已知,调用draw画弧即可
vec_rad_p2o2 = vec_rad_o1p1
vec_cos = cos(vec_rad_p2o2) # 方向余弦
vec_sin = sin(vec_rad_p2o2) # 方向正弦
o2_p = (l2_end_p[0] + self.curv2_R2 * vec_cos, l2_end_p[1] + self.curv2_R2 * vec_sin)
rect_pos_c2 = (o2_p[0] - self.curv2_R2, o2_p[1] - self.curv2_R2, self.curv2_R2 * 2, self.curv2_R2 * 2)
cur2_start = pi / 2
cur2_end = cur2_start + self.__curv2_rad
cur2_end_re = cur2_end + math.radians(1) # 修正用的结束角度
pygame.draw.arc(self.myWindow, Colors.sky_blue, rect_pos_c2, cur2_start, cur2_end_re, width)
self.__cur2_end = -cur2_end
self.__o2_p = o2_p
# ==================================================================================================================
# 画第三段直线
l3_start_p = (o2_p[0], o2_p[1] - self.curv2_R2)
l3_end_p = (l3_start_p[0] + self.line3, l3_start_p[1])
pygame.draw.line(self.myWindow, Colors.red, l3_start_p, l3_end_p, width)
self.__l3_start_p = l3_start_p
self.__l3_end_p = l3_end_p
def getstartlist(self):
if 10 < self.line1 <= 200: # 轨道长度不应该太短至少大于10个像素,另外最适宜是200到400之间
step = 2
elif 200 < self.line1 <= 400:
step = 3
elif self.line1 >= 400:
step = 4
# 存储第一段直线上的点源=====================================================================
points_a = self.start_point
while self.__l1_end_p[0] - points_a[0] > 2:
self.startlist.append(points_a)
points_a = (points_a[0] + step, points_a[1])
# 存储第一段圆弧上的点源=========================================================================
points_b = self.__l1_end_p
start_rad = self.__cur1_start
delta = math.radians(1)
while (self.__cur1_end_p[0] - points_b[0]) ** 2 + (self.__cur1_end_p[1] - points_b[1]) ** 2 > 8:
self.startlist.append(points_b)
start_rad = start_rad - delta
cos_temp = cos(start_rad)
sin_temp = sin(start_rad)
points_b = (self.__o1_p[0] + self.curv1_R1 * cos_temp, self.__o1_p[1] + self.curv1_R1 * sin_temp)
# 存储第二段直线的点源==========================================================================
deltax = 4 * self.__vec_l2_cos
deltay = 4 * self.__vec_l2_sin
points_c = self.__cur1_end_p
while (points_c[1] - self.__l2_end_p[1]) > 2:
self.startlist.append(points_c)
points_c = (points_c[0] + deltax, points_c[1] + deltay)
# 存储第二段圆弧上的点源=========================================================================
points_d = self.__l2_end_p
start_rad = self.__cur2_end
delta = math.radians(1)
while (self.__l3_start_p[0] - points_d[0]) ** 2 + (self.__l3_start_p[1] - points_d[1]) ** 2 > 8:
self.startlist.append(points_d)
start_rad = start_rad + delta
cos_temp = cos(start_rad)
sin_temp = sin(start_rad)
points_d = (self.__o2_p[0] + self.curv2_R2 * cos_temp, self.__o2_p[1] + self.curv2_R2 * sin_temp)
# 存储第三段直线上的点源=====================================================================
points_e = self.__l3_start_p
while self.__l3_end_p[0] - points_e[0] > 2:
self.startlist.append(points_e)
points_e = (points_e[0] + step, points_e[1])
return [self.startlist, step]
class CarrigSize:
small = 50 # 长50宽10
middle = 60 # 长60 宽12
large = 75 # 长75 宽15
Xlarge = 90 # 长90 宽18
class TrackSize:
small = [(190, 150, 200), (150, 210), (150, 210)] # [(l1, l2, l3), (r1, m1) (r2, m2)]
middle = [(350, 200, 350), (180, 250), (180, 250)]
large = [(400, 250, 400), (200, 280), (200, 280)]
class Speed:
oxcart = 0.1
normal_speed = 0.04
High_speed = 0.01
plane = 0.002