【python】从零实现一个AI桌面宠物:有记忆系统 自主学习 离线运行、会学习你的习惯的桌面宠物游戏
从零实现一个AI桌面宠物纯Python、离线运行、会学习你的习惯你是否想过拥有一只生活在电脑桌面上的虚拟宠物它会主动找你玩会饿会困会开心能记住你喂过它什么甚至拥有独立的性格——而且这一切完全离线运行不需要联网不调用任何云API全部用纯Python实现。公众号文章链接一、项目背景市面上的桌面宠物要么是静态壁纸要么需要联网调用AI接口。我想做一个不一样的完全离线—— 0 API 调用所有AI逻辑在本地运行有大脑—— 能自主学习主人的互动习惯拥有独立的性格和记忆轻量—— 一个普通笔记本就能跑RNN模型仅28K参数零图片资源—— 宠物完全用代码绘制不需要任何图片素材最终成品是一个200×280像素的透明窗口一只矢量风格的卡通猫会住在你的桌面上。二、最终效果功能操作方式左键单击宠物随机互动宠物切换对应表情左键拖拽移动宠物位置右键单击宠物弹出功能菜单宠物即时反馈表情系统托盘图标置顶切换 / 菜单操作右键菜单功能每个操作宠物都会呈现不同的动画表情打招呼— 开心/好奇/活泼地回应喂食— 开心/好奇地闻食物摸摸头— 开心眯眼/好奇张望/甚至犯困玩耍— 活泼蹦跳/开心追逐治疗— 恢复健康安逸舒适改名字— 自定义宠物名称查看状态— 在对话气泡中显示四维属性宠物会随时间自动衰减属性并自主学习在什么时候应该做什么。每个互动都会触发即时视觉反馈让你立刻知道它的心情。三、技术架构整体设计用户互动 → 性格系统 → 行为引擎 → 动作输出 ↘ 记忆系统 ↗ 对话 ← 混合引擎模板 Char-RNN技术选型组件技术选型原因桌面窗口PyQt5原生透明窗口、系统托盘、事件处理游戏渲染pygame-ce高效的2D图形API支持SRCALPHA合成神经网络numpy纯手工实现Char-RNN零ML框架依赖行为决策Q-Learning轻量级强化学习可解释性强四、核心实现详解4.1 矢量风格宠物绘制非像素这是项目的最大亮点之一。宠物不是像素画而是用代码绘制的矢量风格卡通猫。实现文件renderer/sprites.py核心思路用一个_draw_cat_base函数绘制猫的全部身体通过modifiers字典驱动所有动画状态def_draw_cat_base(surf,frame,modifiers):# modifiers 控制所有变形参数:# bounce_y — 垂直弹跳开心时# head_tilt — 头部倾斜好奇时# tail_up — 尾巴抬起玩耍时# eye_open — 眼睛睁开程度0~1.4# smile — 嘴角弧度正笑负哭# blush — 腮红透明度# body_color — 身体颜色生病时变灰breathemath.sin(frame*0.3)*1.5# 呼吸动画ear_twitchmath.sin(frame*0.5)*2# 耳朵抖动# 用 pygame 图元组合成一只猫# 椭圆 → 身体/腿# 圆 → 头/尾巴尖# 多边形 → 耳朵/鼻子# 弧线 → 眼睛/嘴巴# 抗锯齿线 → 胡须/尾巴# 半透明面 → 腮红8种情绪状态各自定义一组 modifiers 参数状态关键动画实现方式idle呼吸起伏sin波驱动body垂直微动happy上下弹跳bounce_y abs(sin(frame*0.5))*6sad垂头丧气head_tilt 负值 半闭眼playful活泼好动高弹跳 尾巴竖起hungry左右张望head_tilt 正弦摆动sleepy打瞌睡eye_open0.1 缓慢点头sick生病萎靡body_color换为灰白色curious好奇歪头head_tilt 摆动 睁大眼技术要点腮红效果通过单独创建带逐像素Alpha的Surface实现blush_surfpygame.Surface((12,8),pygame.SRCALPHA)pygame.draw.ellipse(blush_surf,(255,184,184,120),(0,0,12,8))4.2 右键交互菜单实现文件desktop/pet_window.py右键菜单的完整流程PyQt5检测到Qt.RightButton鼠标事件调用_show_context_menu(globalPos)动态创建一个QMenu添加7个功能项 1个退出项菜单采用深色主题样式背景#2D2D36选中色#FF9F43橙色圆角边框完美融入桌面点击菜单项触发对应操作greet/feed/pet/play/heal→ 调用brain.interact()rename→ 弹出QInputDialog改名字status→ 弹窗显示四维属性数值关键代码片段def_show_context_menu(self,global_pos):menuQMenu(self)menu.setStyleSheet( QMenu { background-color: #2D2D36; color: white; border: 1px solid #555; border-radius: 6px; padding: 4px; } QMenu::item { padding: 6px 24px; border-radius: 4px; } QMenu::item:selected { background-color: #FF9F43; } )actions[(打招呼,greet),(喂食,feed),(摸摸头,pet),(玩耍,play),(治疗,heal),(改名字,rename),(查看状态,status),]forlabel,action_typeinactions:actionQAction(label,self)action.triggered.connect(lambdachecked,taction_type:self.on_menu_action(t))menu.addAction(action)menu.addSeparator()exit_actionQAction(退出,self)exit_action.triggered.connect(self.quit_application)menu.addAction(exit_action)menu.exec_(global_pos)4.3 PyGame嵌入Qt窗口这是一个关键的架构决策。最终方案是PyGame只作渲染引擎Qt负责窗口管理classPetWindow(QWidget):def_setup_pygame(self):pygame.font.init()self.pygame_surfpygame.Surface((WINDOW_WIDTH,WINDOW_HEIGHT),pygame.SRCALPHA)defpaintEvent(self,event):painterQPainter(self)pixelspygame.image.tobytes(self.pygame_surf,RGBA)imageQImage(pixels,w,h,QImage.Format_RGBA8888)painter.drawImage(0,0,image)def_update(self):self.brain.tick()self.renderer.render(self.pygame_surf,self.brain.current_emotion,dialogue)self.update()# 触发 paintEvent每次tickbrain更新状态 → renderer绘制到pygame surface → paintEvent将surface转为QImage显示。这样既享受了pygame的绘图API又获得了Qt的窗口管理能力。4.4 Q-Learning 行为引擎宠物拥有一个轻量级的强化学习大脑状态空间饥饿 × 精力 × 心情 × 健康 × 时间段5维各3级离散 243种状态动作空间7种行为sleep / play / explore / cuddle / eat / wander / groom奖励机制每个动作根据当前状态获得reward性格会影响reward权重探索率初始20%每次更新衰减0.9995最低5%defget_reward(self,action,hunger,energy,mood,health):reward0.0ifactioneat:reward1.0ifhunger50else-0.5rewardpersonality.get(affectionate)*0.2elifactionplay:reward1.0ifenergy40else-1.0rewardpersonality.get(energetic)*0.5rewardpersonality.get(curious)*0.3# ... 其他动作returnreward性格系统是一个5维向量[affectionate, energetic, curious, stubborn, clingy]初始值[0.6, 0.5, 0.7, 0.3, 0.4]。每次互动会微调性格参数且每日变化有上限±0.1保证性格变化平滑自然。4.5 纯NumPy实现的Char-RNN这是项目中最硬核的部分。一个字符级别的循环神经网络完全用NumPy手动实现前向传播和反向传播。classCharRNN:def__init__(self):self.W_embednp.random.randn(vocab_size,embed_dim)*0.01self.W_xhnp.random.randn(embed_dim,hidden_dim)*0.01self.W_hhnp.random.randn(hidden_dim,hidden_dim)*0.01self.W_hynp.random.randn(hidden_dim,vocab_size)*0.01deftrain_step(self,inputs,targets,lr):# 前向传播fortinrange(T):xs[t]self.W_embed[inputs[t]]hs[t1]np.tanh(xs[t] self.W_xhhs[t] self.W_hhself.b_h)# 反向传播BPTTfortinreversed(range(T)):dyprobs[t:t1].copy()dy[0,targets[t]]-1# 计算各参数梯度dW_hyhs[t1].T dy dhdy self.W_hy.Tdh_next# ...# SGD更新forparamin[W_embed,W_xh,W_hh,b_h,W_hy,b_y]:setattr(self,param,getattr(self,param)-lr*grad)模型规格词表70字符英文字母 数字 标点Embedding维度16隐藏层维度64参数量~28K权重文件~112KB训练数据是322条带情绪标签的英文短句happy,sad,hungry等8种标签。首次启动自动训练60个epochloss从3.1降至0.59。对话时80%概率使用中文模板保证通顺20%概率用RNN自由生成增加惊喜感。4.6 记忆系统记忆采用键值对存储每条记忆有7天半衰期def_weight(self,timestamp):days(time.time()-timestamp)/86400return2.0**(-days/7)# 7天半衰期权重低于0.05且访问次数3的记忆自动清理频繁访问的记忆即使时间久远也会保留记忆影响对话内容“你上次喂我鸡肉好好吃~”4.7 互动响应情绪系统宠物在接收到用户操作时立即切换画面表情给用户即时的视觉反馈。实现原理pet/brain.pydefinteract(self,action_type:str)-str:self.emotion_timerFPS*3# 情绪保持3秒# 设置当前情绪和动作驱动渲染层切换动画self.current_emotion,self.current_actionself._pick_emotion([(happy,happy,5),# 50% 概率开心(curious,curious,3),# 30% 概率好奇(playful,playful,2),# 20% 概率活泼])每个操作对应多种可能的情绪反馈使用加权随机选择操作可能情绪权重摸摸头happy(5) / curious(2) / sleepy(1)喂食happy(5) / curious(3) / playful(1)玩耍playful(5) / happy(3) / curious(2)治疗happy(5) / idle(2) / sleepy(1)打招呼happy(5) / curious(3) / playful(2)责骂sad(5) / sleepy(2) / idle(1)情绪切换后持续约3秒emotion_timer然后自动恢复为由属性数值决定的基础情绪。这样既保证了操作的即时反馈又不影响宠物自主行为的连贯性。tick()中的关键逻辑deftick(self):# ... 属性衰减 ...ifself.emotion_timer0:self.emotion_timer-1# 持续显示互动情绪else:self.current_emotionself._determine_emotion()# 恢复自动判断此外查看状态不再弹出独立窗口而是显示在宠物的对话气泡中停留6秒elifaction_typestatus:text饥饿30/100 精力70/100 心情60/100 健康80/100self._show_dialogue(text,durationFPS*6)# 6秒停留五、遇到的坑与解决方案1. Pygame-ce字体初始化崩溃问题Python 3.14 pygame-ce 2.5.7 下调用pygame.font.SysFont()会触发Windows字体枚举但是枚举返回的数据类型与Python 3.14不兼容导致splitext报错TypeError: expected str, not int。解决放弃使用SysFont改为直接加载Windows字体目录下的具体字体文件def_find_chinese_font():paths[C:\\Windows\\Fonts\\simsun.ttc,C:\\Windows\\Fonts\\msyh.ttc,C:\\Windows\\Fonts\\simhei.ttf,]forpinpaths:ifos.path.exists(p):returnpygame.font.Font(p,14)returnpygame.font.Font(None,16)# 最后兜底2. RNN训练速度过慢问题原始实现中get_loss方法先调用forward进行前向传播然后在反向传播中又重复计算了部分前向值导致每个训练step做了2次前向传播。解决重写train_step方法将前向传播和反向传播合并为一个函数复用前向计算的结果训练速度提升了约10倍200 epoch从2分钟缩短至约30秒。3. 透明窗口的事件穿透问题Qt透明窗口默认会让鼠标事件穿透到下层窗口但我们需要在宠物身上捕获点击。解决不设置WA_TransparentForMouseEvents在mousePressEvent中通过_is_on_pet()判断点击位置是否在宠物的渲染矩形内仅在矩形内处理事件。六、如何使用环境要求Python 3.14依赖pygame-ce,PyQt5,numpy安装运行pipinstallpygame-ce PyQt5 numpycdai_pet python main.py或双击run.bat。首次启动会自动训练Char-RNN约30秒之后秒开。所有数据保存在brain_models/data/目录退出时自动保存。七、项目结构ai_pet/ ├── main.py # 程序入口 ├── config.py # 全局配置参数 │ ├── pet/ # AI 大脑模块 │ ├── brain.py # 大脑主控 │ ├── behavior.py # Q-Learning 行为引擎 │ ├── personality.py # 5维性格系统 │ ├── memory.py # 记忆系统 │ └── dialogue.py # 混合对话引擎 │ ├── brain_models/ # 神经网络模块 │ ├── char_rnn.py # 微型 Char-RNN │ ├── train.py # 训练脚本 │ └── data/ # 数据持久化 │ ├── corpus.txt # 训练语料 │ ├── weights.npz # 训练好的权重 │ ├── q_table.json # Q-Learning 表 │ ├── memory.json # 记忆数据 │ └── personality.json # 性格数据 │ ├── renderer/ # 渲染模块 │ ├── sprites.py # 矢量精灵绘制 │ └── renderer.py # 帧动画 对话气泡 │ ├── desktop/ # 桌面集成模块 │ ├── pet_window.py # 透明穿透窗口 右键菜单 │ └── tray.py # 系统托盘 │ └── assets/ # 资源目录八、技术栈总结组件技术用途游戏循环/渲染pygame-ce矢量绘图、帧动画桌面窗口PyQt5透明窗口、系统托盘、右键菜单神经网络numpyChar-RNN 训练与推理行为决策Q-Learning状态-动作价值学习数据格式JSON NPZ配置/状态持久化九、总结与展望这个项目用纯Python实现了一个完整的AI桌面宠物系统核心亮点在于零依赖AI—— RNN完全用NumPy手写不依赖PyTorch/TensorFlow零图片资源—— 矢量风格绘图全部用代码生成零联网—— 所有功能离线运行可成长—— Q-Learning让宠物越养越懂你未来可以扩展的方向更多互动小游戏接球、拼图多宠物同屏互动声音反馈系统自定义外观编辑器项目完全开源代码放在 GitHub。如果你也对桌面宠物或轻量AI感兴趣欢迎Star和PR如果你觉得这篇文章对你有帮助欢迎点赞、收藏、转发让更多人看到用纯Python也能做出这么有趣的AI应用