如何在Tkinter Canvas中实现小球相互碰撞反弹效果
问题原因
- 碰撞检测逻辑错误:当前使用
==判断两个球的边框完全对齐才触发反弹,小球每次移动1像素,很容易出现坐标错位漏检,或是碰撞后两球已经重叠还反复触发方向翻转,直接导致卡顿、来回抖动。 - 动画刷新逻辑不符合Tkinter规范:用无限
while True+time.sleep的写法会阻塞Tkinter原生的事件循环,是Tkinter动画场景的常见错误写法。 - 多余IO操作:循环内每次都打印两个球的坐标,频繁的控制台输出也会拖慢运行速度。
修复方案
- 把碰撞检测改为圆形碰撞判定:计算两个球的圆心距离,当距离小于等于两个球的半径之和时触发反弹,避免边界对齐判定的漏检问题。
- 替换无限循环为Tkinter原生的
after()方法实现定时刷新,避免阻塞事件循环。 - 删除循环内的打印语句。
修复后代码
Ball类代码
class Ball: def __init__ (self,canvas,x,y,diameter,xVelo,yVelo,color): self.canvas = canvas self.image = canvas.create_oval(x,y,diameter,diameter,fill=color) self.xVelo = xVelo self.yVelo = yVelo self.radius = (diameter - x)/2 # 提前存储半径方便后续碰撞计算 def move(self): coordinates = self.canvas.coords(self.image) if (coordinates[2] >= self.canvas.winfo_width() or coordinates[0] < 0): self.xVelo = -self.xVelo if (coordinates[3] >= self.canvas.winfo_height() or coordinates[1] < 0): self.yVelo = -self.yVelo self.canvas.move(self.image,self.xVelo,self.yVelo)
主程序代码
from tkinter import * from Ball import * HEIGHT = 500 WIDTH = 500 window = Tk() canvas = Canvas(window,height=HEIGHT,width = WIDTH) canvas.pack() basket_ball = Ball(canvas,0,0,100,1,1,"orange") soccer_ball = Ball(canvas,399,399,499,1,1,"black") def update_frame(): basket_ball_coords = canvas.coords(basket_ball.image) soccer_ball_coords = canvas.coords(soccer_ball.image) basket_ball.move() soccer_ball.move() # 计算两球圆心坐标 b_center_x = (basket_ball_coords[0] + basket_ball_coords[2])/2 b_center_y = (basket_ball_coords[1] + basket_ball_coords[3])/2 s_center_x = (soccer_ball_coords[0] + soccer_ball_coords[2])/2 s_center_y = (soccer_ball_coords[1] + soccer_ball_coords[3])/2 # 计算圆心距离平方(避免开根号提升性能) distance_sq = (b_center_x - s_center_x)**2 + (b_center_y - s_center_y)**2 min_distance = basket_ball.radius + soccer_ball.radius if distance_sq <= min_distance**2: # 碰撞触发,翻转x、y速度 basket_ball.xVelo = -basket_ball.xVelo basket_ball.yVelo = -basket_ball.yVelo soccer_ball.xVelo = -soccer_ball.xVelo soccer_ball.yVelo = -soccer_ball.yVelo window.after(10, update_frame) # 10ms刷新一次,对应原来的0.01s间隔 update_frame() window.mainloop()
内容的提问来源于stack exchange,提问作者Henry Pham
相关产品推荐
相关产品推荐

