Python/tkinter中的帧调整和动画
动画的每个帧的目标是使其长度与前一帧相同,而不管在构建帧时进行了多少处理 这是我编写的一个小动画演示,展示Python中游戏循环的基础知识。带着一双新手的眼睛,我试着保持有限的工具包避免pygame和线程,并坚持使用tkinter(包括大多数安装),以使代码尽可能清晰易懂 最终的结果是有效的,但我对游戏循环(onTimer)的节奏不满意 我错过什么了吗?有没有比躲在特金特某个地方更好的解决办法Python/tkinter中的帧调整和动画,python,tkinter,Python,Tkinter,动画的每个帧的目标是使其长度与前一帧相同,而不管在构建帧时进行了多少处理 这是我编写的一个小动画演示,展示Python中游戏循环的基础知识。带着一双新手的眼睛,我试着保持有限的工具包避免pygame和线程,并坚持使用tkinter(包括大多数安装),以使代码尽可能清晰易懂 最终的结果是有效的,但我对游戏循环(onTimer)的节奏不满意 我错过什么了吗?有没有比躲在特金特某个地方更好的解决办法 #Bounce import tkinter import time import math imp
#Bounce
import tkinter
import time
import math
import random
#--- Initialize Globla Variables ----------------------------------------------
winWid = 640 #window size
winHei = 480
frameRate = 20 #length of a frame (include delay) in millesconds
frameDelay = 0 #the delay at the end of the current frame
frameStart = 0 #when the current frame started
balldiameter = 7
ballShape = []
ballX = []
ballY = []
spdX = []
spdY = []
ballCnt = 0
addTimer = time.perf_counter()
#--- Function/Tookkit list ----------------------------------------------------
def onTimer():
global timerhandle, frameStart, frameDelay, addTimer
#An animation frame is the work being done to draw/update a frame,
# plus the delay between the frames. As the work to draw the
# frame goes up, the delay between the frames goes down
elapsedTime = round((time.perf_counter() - frameStart)* 1000) #time from start of frame until now
frameDelay = frameRate - elapsedTime #delay for this frame is the frame size, minus how long it took to process this frame
if frameDelay < 1: frameDelay = 1 #bottom out with a single millesecond delay
frameStart = time.perf_counter() #start a new frame
#if the frame delay hasn't bottomed out and a half second has passed
if (time.perf_counter() - addTimer) > 0.25 and frameDelay > 1:
addTimer = time.perf_counter() #update the add time
addBall()
window.title("FD:" + str(frameDelay) + " - " + str(ballCnt))
moveTheBalls() #update the position of the balls
timerhandle = window.after(frameDelay,onTimer) #fill update rest of this frame with a delay
def onShutdown():
window.after_cancel(timerhandle)
window.destroy()
def addBall():
global ballCnt
newX = random.randrange(0,winWid)
newY = random.randrange(0,winHei)
color = randomColor()
ballShape.append(canvas.create_oval(newX,newY, newX+balldiameter,newY+balldiameter, outline=color, fill=color))
ballX.append(newX)
ballY.append(newY)
spdX.append((random.random() * 2)-1)
spdY.append((random.random() * 2)-1)
ballCnt = ballCnt + 1
def moveTheBalls():
for i in range(0,ballCnt): #for each ball
ballX[i] = ballX[i] + spdX[i] #update its position
ballY[i] = ballY[i] + spdY[i]
for j in range(i+1,ballCnt): #check for colision between other balls
dist = math.sqrt(( (ballX[i]+(balldiameter/2)) - (ballX[j]+(balldiameter/2)))**2 + ( (ballY[i]+(balldiameter/2)) - (ballY[j]+(balldiameter/2)))**2)
if dist < balldiameter: #if the balls are inside each other
hold = spdX[i] #swap their directions
spdX[i] = spdX[j]
spdX[j] = hold
hold = spdY[i]
spdY[i] = spdY[j]
spdY[j] = hold
if ballX[i] < 0 or ballX[i] > winWid-balldiameter: spdX[i] = spdX[i] * -1 #top or bottom? reverse directions
if ballY[i] < 0 or ballY[i] > winHei-balldiameter: spdY[i] = spdY[i] * -1 #left or right? reverse directions
canvas.coords(ballShape[i], (ballX[i], ballY[i], ballX[i]+balldiameter, ballY[i]+balldiameter))
def randomColor():
hexDigits = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"]
rclr = "#"
for i in range(0,6):
rclr = rclr + hexDigits[random.randrange(0,len(hexDigits))]
return rclr
#--- Main Program - Executes as soon as you hit run --------------------------
window = tkinter.Tk() #Sets the window
canvas = tkinter.Canvas(window, width=winWid, height=winHei, bg="white")
canvas.pack()
addBall()
timerhandle = window.after(20,onTimer) #start the game loop
window.protocol("WM_DELETE_WINDOW",onShutdown) #provide a graceful exit
window.mainloop() #Start the GUI
#反弹
进口tkinter
导入时间
输入数学
随机输入
#---初始化全局变量----------------------------------------------
winWid=640#窗口大小
温黑=480
帧速率=20#帧长度(包括延迟),单位为千分之一
frameDelay=0#当前帧结束时的延迟
frameStart=0#当前帧开始时
球径=7
球形=[]
ballX=[]
贝利=[]
spdX=[]
spdY=[]
ballCnt=0
addTimer=time.perf_计数器()
#---函数/Tookkit列表----------------------------------------------------
def onTimer():
全局timerhandle、frameStart、frameDelay、addTimer
#动画帧是指绘制/更新帧所做的工作,
#加上帧之间的延迟。作为绘制
#帧上升,帧之间的延迟下降
elapsedTime=round((time.perf_counter()-frameStart)*1000)#从帧开始到现在的时间
frameDelay=frameRate-elapsedTime#此帧的延迟是帧大小减去处理此帧所用的时间
如果frameDelay<1:frameDelay=1#以千分之一秒的延迟见底
frameStart=time.perf_counter()#启动新帧
#如果帧延迟没有达到最低点,并且已经过了半秒
如果(time.perf_counter()-addTimer)>0.25且帧延迟>1:
addTimer=time.perf_counter()#更新添加时间
addBall()
窗口标题(“FD:+str(帧延迟)+“-”+str(ballCnt))
移动球()#更新球的位置
timerhandle=window.after(frameDelay,onTimer)#用延迟填充此帧的其余部分
def onShutdown():
取消后的窗口(timerhandle)
window.destroy()
def addBall():
全球球
newX=random.randrange(0,winWid)
newY=random.randrange(0,winHei)
颜色=随机颜色()
ballShape.append(canvas.create_oval(newX,newY,newX+balldiameter,newY+balldiameter,outline=color,fill=color))
ballX.append(newX)
ballY.append(newY)
附加((random.random()*2)-1)
spdY.append((random.random()*2)-1)
ballCnt=ballCnt+1
def moveTheBalls():
对于范围(0,ballCnt)内的i:#对于每个球
ballX[i]=ballX[i]+spdX[i]#更新其位置
ballY[i]=ballY[i]+spdY[i]
对于范围内的j(i+1,ballCnt):#检查其他球之间是否有摩擦
距离=数学sqrt((球[i]+(球直径/2))-(球[j]+(球直径/2))**2+((球[i]+(球直径/2))-(球[j]+(球直径/2))**2)
如果距离<球径:#如果球在彼此内部
保持=spdX[i]#交换方向
spdX[i]=spdX[j]
spdX[j]=保持
hold=spdY[i]
spdY[i]=spdY[j]
spdY[j]=保持
如果ballX[i]<0或ballX[i]>winWid balldiameter:spdX[i]=spdX[i]*-1#顶部还是底部?反向
如果ballY[i]<0或ballY[i]>winHei balldiameter:spdY[i]=spdY[i]*-1#左或右?反向
canvas.coords(ballShape[i],(ballX[i],ballY[i],ballX[i]+球径,ballY[i]+球径))
def randomColor():
六位数=[“0”、“1”、“2”、“3”、“4”、“5”、“6”、“7”、“8”、“9”、“A”、“B”、“C”、“D”、“E”、“F”]
rclr=“#”
对于范围(0,6)内的i:
rclr=rclr+hexDigits[random.randrange(0,len(hexDigits))]
返回rclr
#---主程序-点击run后立即执行--------------------------
window=tkinter.Tk()#设置窗口
canvas=tkinter.canvas(窗口,宽度=winWid,高度=winHei,bg=“白色”)
canvas.pack()
addBall()
timerhandle=window.after(20,onTimer)#开始游戏循环
协议(“WM_DELETE_window”,onShutdown)#提供一个优雅的退出
window.mainloop()#启动GUI
延时调整不包括移动这些球所用的时间。建议在onTimer()
开头移动行moveTheBalls()
(在global…
之后)。我有同样的想法,原始版本在onTimer顶部有第一个时间戳,在移动球之后有第二个时间戳(就在之后)。但它没有起作用。时间戳之间的差异可以忽略不计。我的猜测是,在onTimer中完成的处理不会影响延迟,并且屏幕上的实际更新发生在函数返回/释放之后。我使用的技术是反向的,精度很低,但至少它是有效的。