如何在Tkinter台球模拟中按Spinbox选定角度击球?
Tkinter台球模拟程序:让白球沿选定角度运动的修复方案
问题描述
我编写了一个基于Tkinter的台球模拟程序,期望点击“Kresli”按钮后,白球能够沿Spinbox选定的角度(箭头已对应瞄准该角度)运动。尝试使用Spinbox的get()方法但无效,附上代码寻求解决。
核心问题分析
原代码存在几个关键问题导致功能失效:
__init__方法中self.mainloop()之后的self.posun_lopty()和self.rotate()永远不会执行,因为mainloop是阻塞的,程序会停在这里直到窗口关闭。- 白球的移动增量
dx和dy被固定为(5, 0),从未根据Spinbox选择的角度更新,所以无论选什么角度,白球只会水平向右移动。 - 多次点击“Kresli”按钮会重复启动
posun_lopty的循环,导致白球加速甚至异常。 - 碰撞检测使用
==判断位置,存在精度问题,可能导致碰撞逻辑失效。
修改后的完整代码
import tkinter import math from tkinter import ttk class Simulacia(tkinter.Tk): def __init__(self): '''Nastavenie počiatočných vlastnosti a spúšťanie ďalších metód triedy''' super().__init__() self.title('Rovnomerný priamočiary pohyb') self.is_moving = False # 控制运动状态,防止重复启动 self.vytvorGUI() self.rotate() # 初始化箭头角度 self.mainloop() def vytvorGUI(self): '''Vytvorenie grafického rozhrania programu (GUI)''' self.uhol = tkinter.IntVar() tkinter.Button(self, text='Kresli', command=self.start_movement).grid(row=1, column=0) tkinter.Button(self, text='Ukonči', command=self.destroy).grid(row=1, column=3) tkinter.Button(self, text='Vymaž pozadie', command=self.reset_canvas).grid(row=2, column=3) tkinter.Label(self, text="Uhol=", width=10).grid(row=2, column=0) self.platno = tkinter.Canvas(self, width=800, height=600, background='green') self.x = 150 self.y = 300 self.lopta = self.platno.create_oval(self.x, self.y, self.x+20, self.y+20, fill="white") self.sipka = self.platno.create_line(160, 310, 280, 310, tags=("minute",), arrow="last") self.radius = 30 # 绘制台球桌边框和球洞 self.platno.create_rectangle(3, 3, 800, 600, outline="brown", width=10) self.platno.create_oval(-self.radius, -self.radius, +self.radius, +self.radius, fill="black") # VLAVO HORe self.platno.create_oval(-self.radius, 600 - self.radius, +self.radius, 600 + self.radius, fill="black") # VLAVO DOLE self.platno.create_oval(800 - self.radius, 0 - self.radius, 800 + self.radius, 0 + self.radius, fill="black") # Vpravo HORE self.platno.create_oval(800 - self.radius, 600 - self.radius, 800 + self.radius, 600 + self.radius, fill="black") # Vpravo Dole self.platno.create_oval(400 - self.radius, -self.radius, 400 + self.radius, self.radius, fill="black") # Hore self.platno.create_oval(400 - self.radius, 600 - self.radius, 400 + self.radius, 600 + self.radius, fill="black") # Dole self.sb_minute = ttk.Spinbox(self, from_=0, to=60, increment=1, width=4, command=self.rotate) self.sb_minute.grid(row=2, column=1) self.sb_minute.set(15) self.platno.grid(row=3, column=0, columnspan=4) # 初始化速度参数 self.speed = 5 # 白球移动速度 self.dx = self.speed self.dy = 0 def rotate(self): self.size = 300 self.m = int(self.sb_minute.get()) self.degrees = self.m * 6 # 每分钟对应6度(360/60) self.angle = math.radians(self.degrees) # 转换为弧度 self.cx = 160 self.cy = 310 # 计算箭头终点坐标 self.x0 = self.cx + self.size * math.sin(self.angle) * 0.40 self.y1 = self.cy - self.size * math.cos(self.angle) * 0.40 self.platno.coords('minute', (self.cx, self.cy, self.x0, self.y1)) def start_movement(self): # 如果已经在运动则不重复启动 if self.is_moving: return # 根据当前角度更新dx和dy current_angle = math.radians(int(self.sb_minute.get()) * 6) self.dx = self.speed * math.sin(current_angle) self.dy = -self.speed * math.cos(current_angle) # Tkinter y轴向下为正,取负对应数学坐标系向上 self.is_moving = True self.posun_lopty() def posun_lopty(self): if not self.is_moving: return self.x += self.dx self.y += self.dy self.platno.move(self.lopta, self.dx, self.dy) # 边界碰撞检测(优化为范围判断,避免==的精度问题) # 上下边界(白球直径20,所以y范围限制在0到580) if self.y <= 0 or self.y >= 580: self.dy = -self.dy # 左右边界(x范围限制在0到780) if self.x <= 0 or self.x >= 780: self.dx = -self.dx # 球洞检测 text_lopticka = "Loptička spadla do diery. Hra skončila" hole_detected = False # 左上洞 if self.x <= self.radius and self.y <= self.radius: hole_detected = True # 左下洞 elif self.x <= self.radius and self.y >= 600 - self.radius: hole_detected = True # 右上洞 elif self.x >= 800 - self.radius and self.y <= self.radius: hole_detected = True # 右下洞 elif self.x >= 800 - self.radius and self.y >= 600 - self.radius: hole_detected = True # 上中洞 elif abs(self.x - 400) <= self.radius and self.y <= self.radius: hole_detected = True # 下中洞 elif abs(self.x - 400) <= self.radius and self.y >= 600 - self.radius: hole_detected = True if hole_detected: self.is_moving = False self.platno.create_text(400, 300, text=text_lopticka, fill="black", font=('Helvetica 15 bold')) self.platno.delete(self.lopta) self.platno.create_oval(400 - self.radius + 10, 600 - self.radius + 10, 400 + self.radius - 10, 600 + self.radius - 10, fill="white") return self.platno.after(10, self.posun_lopty) def reset_canvas(self): # 重置画布和白球位置 self.is_moving = False self.platno.delete("all") self.x = 150 self.y = 300 self.lopta = self.platno.create_oval(self.x, self.y, self.x+20, self.y+20, fill="white") self.sipka = self.platno.create_line(160, 310, 280, 310, tags=("minute",), arrow="last") # 重新绘制边框和球洞 self.platno.create_rectangle(3, 3, 800, 600, outline="brown", width=10) self.platno.create_oval(-self.radius, -self.radius, +self.radius, +self.radius, fill="black") self.platno.create_oval(-self.radius, 600 - self.radius, +self.radius, 600 + self.radius, fill="black") self.platno.create_oval(800 - self.radius, 0 - self.radius, 800 + self.radius, 0 + self.radius, fill="black") self.platno.create_oval(800 - self.radius, 600 - self.radius, 800 + self.radius, 600 + self.radius, fill="black") self.platno.create_oval(400 - self.radius, -self.radius, 400 + self.radius, self.radius, fill="black") self.platno.create_oval(400 - self.radius, 600 - self.radius, 400 + self.radius, 600 + self.radius, fill="black") self.rotate() # 重置箭头角度 Simulacia()
关键修改说明
- 修复代码执行顺序:将
__init__中self.mainloop()之后的无效代码移到mainloop之前,确保初始化逻辑正常执行。 - 新增运动状态控制:添加
self.is_moving标志,防止多次点击“Kresli”导致重复启动移动循环,避免白球加速异常。 - 角度驱动速度计算:新增
start_movement方法,点击按钮时根据Spinbox的角度实时计算dx和dy,确保白球沿箭头方向运动。 - 优化碰撞与检测逻辑:将边界碰撞的
==判断改为范围判断,解决精度问题;合并球洞检测逻辑,避免重复创建提示文本。 - 修复画布重置功能:替换“Vymaž pozadie”按钮的绑定方法,正确重置画布元素,而不是重复创建GUI控件。
内容的提问来源于stack exchange,提问作者Dominik Spišák
相关产品推荐
相关产品推荐

