CustomTkinter豆子主题点击游戏递归错误排查与修复请求
豆子点击游戏(Bean Tycoon)问题修复方案
问题核心定位
- 递归错误与线程滥用:
BeanDoubler的run方法存在无延迟的while True循环,持续调用increment导致CPU占用过高并触发递归错误;同时未正确初始化Thread父类,线程逻辑完全无效。 - 类继承与实例化混乱:
BeanApp错误继承Bank和BeanCounter,但主函数又单独实例化这两个类,导致数据不共享;多处直接调用类方法(如BeanCounter.increment)而非实例方法,引发参数传递错误。 - UI操作违规:非UI线程直接修改UI组件,违反CustomTkinter的线程安全规则;标签更新函数未绑定实例数据,显示内容始终为
None。 - 数据传递失效:类之间未通过实例引用共享数据,点击、售卖等操作无法正确修改全局的豆子/金钱数值。
关键修复要点
- 重构类关系:
BeanApp不再继承Bank和BeanCounter,而是持有这两个类的实例,确保数据全局统一。 - 修正线程实现:
BeanDoubler正确继承Thread并调用super().__init__(),添加sleep控制循环频率;线程中UI更新通过app.after()委托给UI线程执行。 - 修复方法调用:所有操作均通过实例调用方法(如
self.bean_counter.increment()),而非直接调用类方法。 - 绑定UI与数据:标签更新函数直接读取实例数值,确保UI实时同步数据变化。
- 完善按钮逻辑:补充售卖、升级按钮的实际功能,处理金钱扣除、豆子翻倍等核心逻辑。
完整修复代码
import customtkinter as ctk from customtkinter import CTkImage, CTkButton, CTkLabel from PIL import Image import time as t from threading import Thread class Bank(): def __init__(self): self.money = 0 def deposit(self, amount, handler=None): self.money += amount if handler: handler() def withdraw(self, amount, handler=None): if self.money >= amount: self.money -= amount if handler: handler() return True else: print('余额不足,无法购买') return False class BeanCounter(): def __init__(self, amount=0): self.amount = amount self.click_multiplier = 1 # 点击乘数 def increment(self, handler=None): self.amount += self.click_multiplier if handler: handler() class BeanDoubler(Thread): def __init__(self, bean_counter, update_func): super().__init__(daemon=True) # 守护线程,随主程序退出 self.bean_counter = bean_counter self.update_func = update_func self.running = True def run(self): while self.running: # 每2秒翻倍一次豆子(可调整频率) if self.bean_counter.amount > 0: self.bean_counter.amount *= 2 self.update_func() # 委托UI线程更新 t.sleep(2) def stop(self): self.running = False class BeanApp(ctk.CTk): def __init__(self): super().__init__() self.title("Bean Tycoon") self.geometry("500x400") # 匹配预期界面的窗口大小 # 初始化核心数据实例 self.bank = Bank() self.bean_counter = BeanCounter() self.bean_doubler = None self._setup_ui() def _setup_ui(self): # 点击乘数区域 multiplier_lbl = CTkLabel(self, text="Multipliers") multiplier_lbl.place(x=250, y=1, anchor="n") self.one_click_btn = CTkButton(self, text="x1", width=20, height=10, command=lambda: self.set_multiplier(1), state="disabled") self.one_click_btn.place(x=145, y=25, anchor="nw") self.two_click_btn = CTkButton(self, text="x2", width=20, height=10, command=lambda: self.set_multiplier(2), state="normal") self.two_click_btn.place(x=173, y=25, anchor="nw") self.click_multi_lbl = CTkLabel(self, text=f" Beans/click: x{self.bean_counter.click_multiplier} ") self.click_multi_lbl.place(x=3, y=45, anchor="nw") # 豆子显示与点击按钮 self.bean_amt_lbl = CTkLabel(self, text=f" Beans: {self.bean_counter.amount} ") self.bean_amt_lbl.place(x=3, y=5, anchor="nw") # 替换为实际豆子图片路径,这里做加载失败兼容 try: bean_img = CTkImage(Image.open("bean.png"), size=(200, 200)) except: bean_img = None # 图片不存在时显示文本 self.bean_btn = CTkButton(self, text="Click for Beans" if not bean_img else "", image=bean_img, command=self.on_click_bean, width=180, height=180) self.bean_btn.place(x=250, y=330, anchor="s") # 金钱显示与售卖按钮 self.money_amt_lbl = CTkLabel(self, text=f" Money: ${self.bank.money} ") self.money_amt_lbl.place(x=3, y=25, anchor='nw') self.sell_btn = CTkButton(self, text="Sell Beans", command=self.on_sell_beans) self.sell_btn.place(x=250, y=360, anchor='s') # 商店区域 shop_lbl = CTkLabel(self, text="Shop") shop_lbl.place(x=425, y=5, anchor="nw") self.doubler_btn = CTkButton(self, text="Bean Doubler\n$100", command=self.buy_bean_doubler, width=100, corner_radius=20) self.doubler_btn.place(x=390, y=30, anchor="nw") self.auto_collect_btn = CTkButton(self, text="Auto Collect 1\n$200", command=self.buy_auto_collect, width=100, corner_radius=20) self.auto_collect_btn.place(x=390, y=70, anchor="nw") def update_bean_label(self): self.bean_amt_lbl.configure(text=f" Beans: {self.bean_counter.amount} ") def update_money_label(self): self.money_amt_lbl.configure(text=f" Money: ${self.bank.money} ") def update_multiplier_label(self): self.click_multi_lbl.configure(text=f" Beans/click: x{self.bean_counter.click_multiplier} ") def on_click_bean(self): self.bean_counter.increment(self.update_bean_label) def on_sell_beans(self): if self.bean_counter.amount > 0: self.bank.deposit(self.bean_counter.amount, self.update_money_label) self.bean_counter.amount = 0 self.update_bean_label() def set_multiplier(self, multiplier): self.bean_counter.click_multiplier = multiplier self.one_click_btn.configure(state="disabled" if multiplier ==1 else "normal") self.two_click_btn.configure(state="disabled" if multiplier ==2 else "normal") self.update_multiplier_label() def buy_bean_doubler(self): if self.bank.withdraw(100, self.update_money_label): if not self.bean_doubler or not self.bean_doubler.is_alive(): self.bean_doubler = BeanDoubler(self.bean_counter, self.update_bean_label) self.bean_doubler.start() self.doubler_btn.configure(text="Doubler Active", state="disabled") def buy_auto_collect(self): if self.bank.withdraw(200, self.update_money_label): # 自动收集逻辑:每秒增加1个豆子 def auto_collect(): self.bean_counter.increment(self.update_bean_label) self.after(1000, auto_collect) auto_collect() self.auto_collect_btn.configure(text="Auto Active", state="disabled") if __name__ == "__main__": app = BeanApp() app.mainloop()
修复说明
- 线程安全:
BeanDoubler使用守护线程,避免主程序退出后线程残留;UI更新通过委托函数在UI线程执行,符合CustomTkinter规则。 - 数据统一:所有操作均通过
BeanApp持有的bank和bean_counter实例进行,确保数据全局一致。 - 功能完善:补充了点击乘数切换、豆子售卖、商店购买逻辑,完全匹配预期界面的功能需求。
- 错误处理:添加了金钱不足提示、图片加载失败兼容处理,提升程序稳定性。
内容的提问来源于stack exchange,提问作者T.E.G Studios
相关产品推荐
相关产品推荐

