Частота кадров и анимация в Python / tkinter

Цель каждого кадра анимации — иметь ту же длину, что и предыдущий, независимо от объема обработки, которая происходит при построении кадра.

Это небольшая демонстрация анимации, которую я написал, чтобы продемонстрировать основы игрового цикла на Python. Ориентируясь на новичков, я старался использовать ограниченный набор инструментов; избегайте pygame и threading и придерживайтесь 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))
    
#Random color - This is a helper function
#Returns a random color.
#Usage c4 = randomcolor()
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

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *