Цель каждого кадра анимации — иметь ту же длину, что и предыдущий, независимо от объема обработки, которая происходит при построении кадра.
Это небольшая демонстрация анимации, которую я написал, чтобы продемонстрировать основы игрового цикла на 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
