Python/tkinter中的帧调整和动画

Python/tkinter中的帧调整和动画,python,tkinter,Python,Tkinter,动画的每个帧的目标是使其长度与前一帧相同,而不管在构建帧时进行了多少处理 这是我编写的一个小动画演示,展示Python中游戏循环的基础知识。带着一双新手的眼睛,我试着保持有限的工具包避免pygame和线程,并坚持使用tkinter(包括大多数安装),以使代码尽可能清晰易懂 最终的结果是有效的,但我对游戏循环(onTimer)的节奏不满意 我错过什么了吗?有没有比躲在特金特某个地方更好的解决办法 #Bounce import tkinter import time import math imp

动画的每个帧的目标是使其长度与前一帧相同,而不管在构建帧时进行了多少处理

这是我编写的一个小动画演示,展示Python中游戏循环的基础知识。带着一双新手的眼睛,我试着保持有限的工具包避免pygame和线程,并坚持使用tkinter(包括大多数安装),以使代码尽可能清晰易懂

最终的结果是有效的,但我对游戏循环(onTimer)的节奏不满意

我错过什么了吗?有没有比躲在特金特某个地方更好的解决办法

#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中完成的处理不会影响延迟,并且屏幕上的实际更新发生在函数返回/释放之后。我使用的技术是反向的,精度很低,但至少它是有效的。