You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 10:18:43