Python Tkinter乒乓球游戏碰撞无限触发问题排查与修复
Python Tkinter乒乓球游戏碰撞重复触发问题修复
问题说明
使用Python+Tkinter开发基础乒乓球游戏时,实现了球拍控制、球移动逻辑后,出现碰撞无限触发的Bug:球接触球拍后不会正常反弹离开,而是卡在球拍位置反复上下弹跳。
原实现完整代码如下:
from tkinter import * from tkinter.ttk import * from math import sqrt import time, random # 创建Tkinter主窗口 Window = Tk() HEIGHT = 700 WIDTH = 700 c = Canvas(Window, width = WIDTH, height = HEIGHT) c.pack() c.configure(bg='black') # 定义画布中点坐标 MID_X = WIDTH / 2 MID_Y = HEIGHT / 2 # 创建球拍和球 paddle_1 = c.create_rectangle(0, 0, 100, 20, fill='grey', outline = 'white') paddle_2 = c.create_rectangle(0, 0, 100, 20, fill='grey', outline = 'white') ball = c.create_oval(0, 0, 30, 30, fill='white', outline = 'grey') c.move(paddle_1, 300, 0) c.move(paddle_2, 300, 680) c.move(ball, 330, 340) id = [paddle_1, paddle_2, ball] ball_move_x = 0 ball_move_y = 10 cooldown = 0 # 获取物体中心坐标 def get_coords(i): pos = c.coords(i) x = int(pos[0] + pos[2]/2) y = int(pos[1] + pos[3]/2) return x, y # 反弹逻辑(原实现带随机物理效果) def bounce(x, y): x += random.randint(1,20)/10 x *= -1 y += 1 y *= -1 return x, y # 碰撞检测逻辑(原实现:判断中心点是否在指定范围内) def collision(paddle_x, paddle_y, ball_x, ball_y, x = ball_move_x, y = ball_move_y,): if(ball_x in range(paddle_x-50, paddle_x+50, 1) and ball_y in range(paddle_y-30, paddle_y+30, 1)): x, y = bounce(ball_move_x, ball_move_y) print("collision") return x, y else: return x, y # 球拍移动函数 def move_paddle_1_left(e): c.move(paddle_1, -100, 0) def move_paddle_1_right(e): c.move(paddle_1, +100, 0) def move_paddle_2_left(e): c.move(paddle_2, -100, 0) def move_paddle_2_right(e): c.move(paddle_2, +100, 0) # 绑定按键事件 c.bind_all('<KeyPress-a>', move_paddle_1_left) c.bind_all('<KeyPress-d>', move_paddle_1_right) c.bind_all('<Left>', move_paddle_2_left) c.bind_all('<Right>', move_paddle_2_right) c.pack() # 主游戏循环 while True: Window.update() paddle_1_x, paddle_1_y = get_coords(id[0]) paddle_2_x, paddle_2_y = get_coords(id[1]) ball_x, ball_y = get_coords(id[2]) ball_move_x, ball_move_y = collision(paddle_1_x, paddle_1_y, ball_x, ball_y) ball_move_x, ball_move_y = collision(paddle_2_x, paddle_2_y, ball_x, ball_y) c.move(ball, +ball_move_x, +ball_move_y) time.sleep(0.0333333) Window.update()
原碰撞逻辑为:分别获取球和球拍的中心坐标,当球的坐标落在球拍坐标指定范围内时判定为碰撞,触发速度反向。
问题产生原因
- 缺少碰撞方向校验:碰撞触发后球的Y轴速度反向,但单帧移动距离有限,球没有足够时间移出碰撞判定范围,下一帧运行时仍然满足碰撞条件,会再次触发速度反向,最终导致球在碰撞区间内来回弹跳。
- 碰撞判定范围设计不合理:仅以中心点距离作为碰撞依据,没有结合球拍、球的实际边界做检测,判定区间过厚,进一步提高了重复触发的概率。
- 反弹逻辑存在缺陷:
bounce函数对Y轴速度仅做取反加1操作,无法保证反弹后速度方向一定远离球拍,极端情况下会出现速度过小、球无法移出碰撞区的问题。
修复方法
- 给碰撞检测增加方向前置判断:只有球朝向对应球拍移动时才允许触发碰撞
- 检测顶部球拍paddle_1碰撞时,先判断球的Y轴速度是否小于0(即向上移动)
- 检测底部球拍paddle_2碰撞时,先判断球的Y轴速度是否大于0(即向下移动)
- 碰撞触发后,主动将球的位置偏移到球拍碰撞范围外,从根源上避免下一帧重复判定
- 修正反弹速度计算逻辑,保证反弹后Y轴速度方向固定为远离球拍,保留原有X轴随机偏移的趣味物理效果
- 可选优化:替换原中心点距离判定为物体实际边界碰撞判定,进一步提升碰撞检测精度
修复后核心逻辑参考
# 修正反弹逻辑,区分上下球拍保证反弹方向正确 def bounce(y_speed, is_top_paddle): x_offset = random.randint(1,20)/10 # 顶部球拍碰撞后Y轴速度固定为正(向下移动) if is_top_paddle: new_y = abs(y_speed) + 1 # 底部球拍碰撞后Y轴速度固定为负(向上移动) else: new_y = -abs(y_speed) -1 # X轴随机偏移保留原有效果 new_x = ball_move_x + x_offset if random.random() > 0.5 else ball_move_x - x_offset return new_x, new_y # 修正碰撞检测,增加方向判断 def collision(paddle_x, paddle_y, ball_x, ball_y, is_top_paddle): global ball_move_x, ball_move_y # 方向校验:球不朝向当前球拍移动时直接跳过 if is_top_paddle and ball_move_y > 0: return if not is_top_paddle and ball_move_y < 0: return # 范围判定 if(ball_x in range(paddle_x-50, paddle_x+50, 1) and ball_y in range(paddle_y-30, paddle_y+30, 1)): ball_move_x, ball_move_y = bounce(ball_move_y, is_top_paddle) # 碰撞后主动将球移出碰撞范围,避免重复触发 c.move(ball, 0, 15 if is_top_paddle else -15) print("collision") # 主循环中调用时传入球拍位置标识 while True: Window.update() paddle_1_x, paddle_1_y = get_coords(id[0]) paddle_2_x, paddle_2_y = get_coords(id[1]) ball_x, ball_y = get_coords(id[2]) collision(paddle_1_x, paddle_1_y, ball_x, ball_y, is_top_paddle=True) collision(paddle_2_x, paddle_2_y, ball_x, ball_y, is_top_paddle=False) c.move(ball, +ball_move_x, +ball_move_y) # 补充左右边界碰撞逻辑,避免球飞出窗口 if ball_x <=15 or ball_x >= WIDTH-15: ball_move_x *= -1 # 上下边界得分重置逻辑 if ball_y <=0 or ball_y >= HEIGHT: c.coords(ball, MID_X-15, MID_Y-15, MID_X+15, MID_Y+15) ball_move_x = random.choice([-3,3]) ball_move_y = 10 if random.random()>0.5 else -10 time.sleep(0.0333333)
内容的提问来源于stack exchange,提问作者obliviator123
相关产品推荐
相关产品推荐

