在实际桌面应用开发中实现一个可交互的“桌宠”应用是一个兼具趣味性和技术挑战性的项目。它要求开发者不仅要掌握图形界面GUI编程还要处理动画、用户交互、系统托盘、多线程以及资源管理等一系列问题。对于许多开发者而言从零开始构建这样一个应用并将其核心流程调通是迈向更复杂桌面应用开发的重要一步。本文将以一个抽象的“桌宠应用”初版调通为背景为希望入门或深化桌面应用开发的读者提供一个从概念到实现、从代码到排错的完整实践指南。我们将使用 Python 语言和 PyQt5 框架作为技术栈因为它能快速构建跨平台的桌面应用并且拥有强大的图形和事件处理能力。通过本文你将理解桌宠应用的核心机制掌握一个可运行的最小化原型并学会如何排查开发过程中的常见问题为后续添加更丰富的功能打下坚实基础。1. 理解桌宠应用的核心概念与工作机制在动手编码之前我们需要先厘清一个“桌宠应用”到底是什么以及它背后涉及哪些关键技术点。这有助于我们在设计架构和编写代码时做出正确的决策。1.1 什么是桌宠应用桌宠应用通常指一个运行在用户桌面上的、具有拟人化或卡通化形象的、可交互的桌面伴侣程序。它不同于传统的窗口应用其核心特征包括始终置顶或浮动应用窗口通常位于其他应用窗口之上或可以设置为半透明不影响用户主要工作。可交互性用户可以通过点击、拖拽、喂食、对话等方式与桌宠互动。自主行为桌宠可以拥有自己的“状态机”例如闲逛、睡觉、吃东西等并在无人交互时自主切换状态。系统托盘集成应用主窗口可以隐藏仅通过系统托盘图标进行控制如显示/隐藏、退出。资源管理需要加载和管理大量的图片精灵图、音频等资源来表现动画和音效。从技术角度看它本质是一个无边框、可拖动、支持透明背景、并持续运行动画的 GUI 应用程序。1.2 关键技术组件与 PyQt5 的对应关系要实现上述特性我们需要利用 GUI 框架提供的相应组件。以 PyQt5 为例关键组件如下表所示功能需求PyQt5 对应组件/技术说明图形界面与窗口QMainWindow,QWidget应用的主容器。无边框与透明setWindowFlags(Qt.FramelessWindowHint),setAttribute(Qt.WA_TranslucentBackground)移除窗口标题栏和边框并设置透明背景使桌宠形状由图片决定。可拖动重写鼠标事件 (mousePressEvent,mouseMoveEvent)在无边框窗口上实现拖拽功能。动画播放QLabelQTimer或QMovie(对于GIF)通过定时器切换QLabel显示的图片序列或直接使用QMovie播放 GIF 来实现帧动画。状态管理与行为自定义状态机或简单的条件逻辑管理桌宠的当前状态如 idle, walk, sleep和状态转换条件。系统托盘QSystemTrayIcon,QMenu创建托盘图标和右键菜单实现后台运行和快捷控制。用户交互鼠标事件 (mousePressEvent,mouseReleaseEvent)响应点击、拖拽等操作。资源路径管理Qt Resource System(.qrc) 或相对路径打包图片、音频等资源或确保在运行时能正确找到它们。理解了这个映射关系我们就知道该从 PyQt5 的工具箱中选取哪些“零件”来组装我们的应用。1.3 初版调通的目标最小可行产品 (MVP)对于“初版调通”我们的目标不是做一个功能完备的复杂应用而是实现一个最小可行产品验证核心链路是否跑通。具体目标包括创建一个无边框、透明背景、可拖动的窗口。在窗口中显示一个静态的桌宠图片。实现点击桌宠时播放一个简单的动画如跳跃一下。将应用集成到系统托盘支持隐藏、显示和退出。整个应用能够稳定启动、运行和退出不报错。达到这个目标就意味着我们成功搭建了桌宠应用的骨架后续所有复杂功能如多状态、物理引擎、对话系统都是在这个骨架上添加“血肉”。2. 环境准备与项目结构搭建在开始编码前我们需要准备好开发环境并规划一个清晰的项目目录结构这对于后续的资源管理和代码维护至关重要。2.1 开发环境配置首先确保你的 Python 环境已就绪推荐 Python 3.7然后安装必要的库。# 使用 pip 安装 PyQt5。注意在有些系统上包名可能是 pyqt5 pip install PyQt5 # 可选但推荐安装用于处理资源文件的工具 pip install pyrcc5安装完成后可以通过一个简单的脚本来验证 PyQt5 是否安装成功import sys from PyQt5.QtWidgets import QApplication, QLabel app QApplication(sys.argv) label QLabel(Hello PyQt5!) label.show() sys.exit(app.exec_())运行这个脚本如果弹出一个显示“Hello PyQt5!”的小窗口则说明环境配置正确。2.2 项目目录结构规划一个良好的目录结构能让资源管理和模块化开发变得清晰。建议为你的桌宠项目创建如下目录desktop_pet_mvp/ ├── main.py # 应用主入口 ├── pet_window.py # 桌宠主窗口类 ├── resources/ # 资源文件夹 │ ├── images/ # 图片资源 │ │ ├── pet_idle.png │ │ └── pet_jump_*.png # 跳跃动画序列帧 │ └── resources.qrc # Qt资源描述文件可选 ├── utils/ # 工具类未来扩展 │ └── __init__.py └── requirements.txt # 项目依赖列表main.py程序的启动脚本负责初始化应用、创建窗口和启动事件循环。pet_window.py核心文件包含桌宠窗口的所有逻辑显示、动画、拖拽、事件处理。resources/存放所有非代码资源。将图片按功能或状态分类存放是很好的实践。resources.qrc如果你使用 Qt 的资源系统来打包资源需要这个文件。它可以避免发布时资源路径丢失的问题但对于初版使用相对路径更简单。requirements.txt记录项目依赖方便在其他环境复现。内容通常就是PyQt55.15。现在请在你的工作区创建上述目录和文件.py文件可以先留空。2.3 准备测试资源为了测试我们需要至少两张图片一张静态的“待机”图片(pet_idle.png): 用于桌宠的默认状态。建议使用 PNG 格式并带有透明背景。一组“跳跃”动画序列帧(例如pet_jump_1.png,pet_jump_2.png,pet_jump_3.png): 用于响应点击时的动画。每张图片代表动画的一帧。你可以自己绘制简单的图形或者从一些免费素材网站获取。将静态图片放入resources/images/将动画序列帧也放入该目录。确保图片名规律便于代码中循环加载。3. 实现桌宠核心窗口类这是整个应用最核心的部分。我们将逐步实现一个具备无边框、透明、可拖动、能显示图片和播放动画的窗口。3.1 创建基础窗口框架首先在pet_window.py中创建主窗口类。import sys import os from PyQt5.QtCore import Qt, QTimer, QPoint from PyQt5.QtGui import QPixmap, QMovie from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel class PetWindow(QMainWindow): 桌宠主窗口类 def __init__(self): super().__init__() self.initUI() self.initPet() self.initAnimation() def initUI(self): 初始化窗口UI属性 # 1. 设置窗口无边框、置顶、透明背景 self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool) self.setAttribute(Qt.WA_TranslucentBackground) # 2. 设置窗口初始大小和位置可根据图片大小调整 self.resize(100, 100) screen_geometry QApplication.desktop().screenGeometry() self.move(screen_geometry.width() - 150, screen_geometry.height() - 150) # 3. 创建一个QLabel作为桌宠的“身体”用于显示图片 self.pet_label QLabel(self) self.pet_label.setGeometry(0, 0, 100, 100) # 与窗口同大 self.pet_label.setScaledContents(True) # 图片缩放以适应label # 4. 初始化鼠标拖拽相关变量 self.dragging False self.drag_position QPoint() def initPet(self): 初始化桌宠的静态状态 # 加载待机图片 idle_image_path os.path.join(os.path.dirname(__file__), resources, images, pet_idle.png) if os.path.exists(idle_image_path): pixmap QPixmap(idle_image_path) self.pet_label.setPixmap(pixmap) self.current_state idle else: print(f警告未找到待机图片 {idle_image_path}) # 可以设置一个默认颜色或文本作为后备 self.pet_label.setText(Pet) self.pet_label.setStyleSheet(background-color: lightblue;) self.current_state error def initAnimation(self): 初始化动画相关组件 # 用于播放序列帧动画的定时器和帧列表 self.animation_timer QTimer() self.animation_timer.timeout.connect(self._update_animation_frame) self.animation_frames [] # 存储QPixmap self.current_frame_index 0 self.is_animating False # 预加载跳跃动画的帧假设有3帧命名为pet_jump_1.png, ... self.jump_frames [] for i in range(1, 4): # 根据你的实际帧数修改 frame_path os.path.join(os.path.dirname(__file__), resources, images, fpet_jump_{i}.png) if os.path.exists(frame_path): self.jump_frames.append(QPixmap(frame_path)) else: print(f警告未找到动画帧 {frame_path}) # 如果一帧都没加载到则清空列表 if not self.jump_frames: self.jump_frames []代码解释initUI: 设置了窗口的核心属性。FramelessWindowHint去边框WindowStaysOnTopHint保持窗口在最前Tool标志可以避免在任务栏显示图标配合系统托盘。WA_TranslucentBackground是实现透明背景的关键。initPet: 加载并显示桌宠的默认静态图片。这里使用了os.path.join来构建资源路径这是一种兼容性较好的方式。initAnimation: 初始化了用于播放序列帧动画的组件。QTimer会定期触发在回调函数中切换pet_label显示的图片。我们预加载了跳跃动画的帧到self.jump_frames列表中。3.2 实现鼠标事件拖拽与点击无边框窗口失去了系统自带的拖拽功能我们必须自己实现。# 在 PetWindow 类中继续添加以下方法 def mousePressEvent(self, event): 重写鼠标按下事件 if event.button() Qt.LeftButton: # 记录拖拽开始的状态和位置 self.dragging True self.drag_position event.globalPos() - self.frameGeometry().topLeft() event.accept() # 可以在这里处理右键点击等其他事件 super().mousePressEvent(event) def mouseMoveEvent(self, event): 重写鼠标移动事件实现窗口拖拽 if self.dragging and event.buttons() Qt.LeftButton: # 根据鼠标全局位置和初始偏移量计算窗口应该移动到的位置 new_pos event.globalPos() - self.drag_position self.move(new_pos) event.accept() super().mouseMoveEvent(event) def mouseReleaseEvent(self, event): 重写鼠标释放事件 if event.button() Qt.LeftButton: self.dragging False event.accept() # 鼠标释放时可以触发一个点击交互例如播放跳跃动画 # 注意简单的点击按下并立即释放也会触发此逻辑 self.on_pet_clicked() super().mouseReleaseEvent(event) def on_pet_clicked(self): 桌宠被点击时的响应函数 print(桌宠被点击了) # 如果当前没有在播放动画则开始播放跳跃动画 if not self.is_animating and self.current_state idle: self.play_animation(self.jump_frames, frame_interval100) # 每100毫秒切换一帧代码解释鼠标事件是 GUI 交互的基础。我们通过重写这三个事件实现了窗口拖拽。mousePressEvent: 左键按下时记录“正在拖拽”状态和鼠标相对于窗口左上角的偏移量 (drag_position)。mouseMoveEvent: 如果处于拖拽状态且左键被按住则根据鼠标的全局位置和之前的偏移量计算并移动窗口到新位置。mouseReleaseEvent: 左键释放时结束拖拽状态。同时我们在这里调用了on_pet_clicked方法作为对点击行为的响应。这是一种简化的处理实际中你可能希望区分“拖拽结束”和“轻微点击”可以通过判断鼠标移动距离来实现。on_pet_clicked: 点击响应函数。这里我们检查当前状态如果不是正在动画中则调用play_animation方法开始播放跳跃动画。3.3 实现序列帧动画播放接下来实现控制动画播放的核心方法。# 在 PetWindow 类中继续添加以下方法 def play_animation(self, frame_list, frame_interval150): 播放一个序列帧动画 Args: frame_list: 包含QPixmap的列表即动画的每一帧。 frame_interval: 每帧之间的时间间隔单位毫秒。 if not frame_list: print(错误动画帧列表为空无法播放。) return self.animation_frames frame_list self.current_frame_index 0 self.is_animating True # 显示第一帧 self.pet_label.setPixmap(self.animation_frames[self.current_frame_index]) # 启动定时器按照指定间隔切换帧 self.animation_timer.start(frame_interval) def _update_animation_frame(self): 定时器回调更新当前动画帧 self.current_frame_index 1 # 如果播放完所有帧停止动画恢复待机状态 if self.current_frame_index len(self.animation_frames): self.animation_timer.stop() self.is_animating False self.current_frame_index 0 # 动画结束后恢复为待机状态 self.initPet() # 重新加载待机图片 return # 显示下一帧 self.pet_label.setPixmap(self.animation_frames[self.current_frame_index])代码解释play_animation: 这是启动动画的公共接口。它接收一个帧列表和帧间隔重置动画状态显示第一帧然后启动定时器。_update_animation_frame: 这是定时器每次超时后调用的函数。它将当前帧索引加一并显示对应的图片。当播放到最后一帧后它会停止定时器重置动画状态并调用initPet()将桌宠恢复为待机状态。至此桌宠窗口的核心功能已基本完成。它现在可以显示、被拖拽、被点击并播放一个简单的动画。4. 集成系统托盘与完善应用入口一个完整的桌面应用通常需要系统托盘支持以便在不关闭应用的情况下隐藏窗口或快速执行退出等操作。4.1 创建系统托盘图标类我们可以在main.py中创建托盘图标但更好的做法是将其集成到主窗口类或创建一个单独的类。这里为了简化我们将其放在pet_window.py的PetWindow类中。from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QAction from PyQt5.QtGui import QIcon class PetWindow(QMainWindow): # ... 之前的 __init__, initUI 等方法保持不变 ... def initTray(self): 初始化系统托盘图标 # 1. 创建托盘图标 self.tray_icon QSystemTrayIcon(self) # 可以设置一个图标这里用窗口图标或自定义图标 # tray_icon_path os.path.join(...) # self.tray_icon.setIcon(QIcon(tray_icon_path)) # 暂时使用默认图标 self.tray_icon.setIcon(self.windowIcon()) # 2. 创建托盘菜单 tray_menu QMenu() # 显示/隐藏动作 self.toggle_action QAction(隐藏, self) self.toggle_action.triggered.connect(self.toggle_window_visibility) tray_menu.addAction(self.toggle_action) # 分隔线 tray_menu.addSeparator() # 退出动作 quit_action QAction(退出, self) quit_action.triggered.connect(self.quit_application) tray_menu.addAction(quit_action) # 3. 将菜单设置给托盘图标 self.tray_icon.setContextMenu(tray_menu) # 4. 显示托盘图标 self.tray_icon.show() # 5. 可选点击托盘图标时切换窗口显示/隐藏 self.tray_icon.activated.connect(self.on_tray_activated) def toggle_window_visibility(self): 切换窗口显示与隐藏 if self.isVisible(): self.hide() self.toggle_action.setText(显示) else: self.show() self.toggle_action.setText(隐藏) def on_tray_activated(self, reason): 托盘图标被激活点击时的处理 # QSystemTrayIcon.ActivationReason 枚举 if reason QSystemTrayIcon.DoubleClick: # 双击托盘图标 self.toggle_window_visibility() # 也可以处理其他 reason如 Trigger(单击), MiddleClick 等 def quit_application(self): 退出应用程序 self.tray_icon.hide() # 隐藏托盘图标 QApplication.quit()然后别忘了在__init__方法中调用self.initTray()。def __init__(self): super().__init__() self.initUI() self.initPet() self.initAnimation() self.initTray() # 新增初始化托盘4.2 创建应用主入口现在在main.py中编写启动脚本。import sys from PyQt5.QtWidgets import QApplication from pet_window import PetWindow def main(): # 每个PyQt5应用都需要一个QApplication实例 app QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) # 重要关闭所有窗口时不退出应用因为窗口可隐藏 # 创建桌宠窗口 pet PetWindow() pet.show() # 初始显示窗口 # 进入应用的主事件循环 sys.exit(app.exec_()) if __name__ __main__: main()关键点解释app.setQuitOnLastWindowClosed(False)这行代码至关重要。它将应用的退出策略设置为“不随最后一个窗口关闭而退出”。因为我们的桌宠窗口可以通过托盘菜单隐藏如果设置为True默认当窗口隐藏时应用就会意外退出。设置为False后应用的生命周期由我们通过QApplication.quit()显式控制。5. 运行验证与效果测试现在让我们运行这个应用验证所有核心功能是否按预期工作。5.1 启动应用在项目根目录下打开终端运行python main.py你应该能看到一个无边框的、显示着你准备的待机图片的窗口出现在屏幕右下角。在系统的托盘区域Windows 在右下角通知区域macOS 在右上角菜单栏Linux 依桌面环境而定出现一个图标。5.2 功能测试清单请按顺序测试以下功能并记录结果测试项操作预期结果实际结果/问题1. 窗口显示运行main.py桌宠窗口正常显示无边框背景透明能看到桌面。2. 拖拽功能鼠标左键按住桌宠窗口任意位置并拖动。窗口应跟随鼠标移动。3. 点击动画在桌宠上快速点击按下并释放鼠标左键。控制台打印“桌宠被点击了”桌宠开始播放跳跃动画图片快速切换动画结束后恢复待机状态。4. 托盘图标查看系统托盘。出现一个图标可能是默认的Qt图标。5. 隐藏/显示右键点击托盘图标 - 选择“隐藏”。桌宠窗口消失。菜单项文字变为“显示”。右键点击托盘图标 - 选择“显示”。桌宠窗口重新出现在原位置。菜单项文字变回“隐藏”。6. 双击托盘双击托盘图标。窗口在显示和隐藏之间切换。7. 退出应用右键点击托盘图标 - 选择“退出”。桌宠窗口和托盘图标都消失应用进程完全结束。如果所有测试项都通过恭喜你你的桌宠应用“初版”已经成功调通5.3 验证动画播放逻辑如果点击后动画没有播放请按以下步骤排查检查控制台输出是否打印了“桌宠被点击了”如果没有说明点击事件没有触发on_pet_clicked方法检查鼠标事件重写是否正确。检查图片路径控制台是否有“警告未找到动画帧 ...”的提示确保resources/images/目录下存在pet_jump_1.png,pet_jump_2.png等文件并且play_animation方法中加载的帧数范围range(1, 4)与实际文件数量匹配。检查动画定时器在_update_animation_frame方法开始处添加print(f”播放第{self.current_frame_index}帧”)观察定时器是否被触发以及帧索引是否正确递增。检查状态锁确保self.is_animating在动画开始和结束时被正确设置为True和False防止动画重叠播放。6. 常见问题排查与调试技巧在开发过程中你可能会遇到一些典型问题。以下是针对此项目的排查指南。6.1 窗口显示问题问题现象可能原因检查与解决窗口有边框或标题栏。setWindowFlags未生效或与其他标志冲突。确保在initUI中最早调用setWindowFlags并检查标志组合。Qt.Tool标志有时会影响无边框可以尝试去掉它测试。窗口背景不透明是黑色或白色。1. 未设置WA_TranslucentBackground。2. 图片本身没有透明通道Alpha。3. 样式表StyleSheet覆盖了背景。1. 确认setAttribute(Qt.WA_TranslucentBackground)被调用。2. 用图片查看工具确认你的PNG图片背景是透明的。3. 检查是否对窗口或pet_label设置了背景色样式。窗口无法置顶被其他窗口覆盖。WindowStaysOnTopHint标志未设置或失效。确认setWindowFlags中包含了Qt.WindowStaysOnTopHint。某些系统设置或桌面环境可能会忽略此提示。6.2 交互与事件问题问题现象可能原因检查与解决窗口无法拖拽。1. 鼠标事件未被正确重写或处理。2.dragging状态逻辑错误。3. 事件被pet_label子控件“吃掉”。1. 在mousePressEvent等函数开始处添加print确认事件被触发。2. 检查dragging在press时设为True在release时设为False。3. 如果pet_label充满了窗口确保它没有设置鼠标跟踪或拦截事件。可以尝试在initUI中为pet_label设置self.pet_label.setAttribute(Qt.WA_TransparentForMouseEvents)让鼠标事件穿透到父窗口。点击触发拖拽但不触发动画。mouseReleaseEvent中的点击判断太宽松。当前的逻辑是释放左键即算点击。可以改进为在mousePressEvent记录按下坐标在mouseReleaseEvent计算与按下坐标的距离如果距离很小如小于5像素则判定为点击否则判定为拖拽结束。动画播放一次后再也无法播放。动画结束后状态未正确重置。检查_update_animation_frame中播放完最后一帧后是否将self.is_animating设为了False以及是否成功调用了self.initPet()来恢复静态图片。6.3 系统托盘问题问题现象可能原因检查与解决托盘图标不显示。1. 系统不支持或需要额外权限。2. 未调用show()方法。3. 图标路径错误使用了无效图标。1. 确认操作系统和桌面环境支持系统托盘。2. 确保self.tray_icon.show()被调用。3. 暂时注释掉setIcon或提供一个绝对路径的有效.ico(Windows) 或.png图片测试。点击“退出”后应用进程残留。quit_application方法未正确终止事件循环。确保quit_application中调用了QApplication.quit()。也可以尝试self.close()然后app.quit()。在任务管理器或htop中检查进程是否消失。关闭窗口后应用直接退出。未设置app.setQuitOnLastWindowClosed(False)。在main.py的QApplication实例化后立即添加这行代码。6.4 通用调试建议多用打印语句在关键函数入口、事件触发处、条件判断分支添加print语句是追踪程序流最直接的方法。检查路径所有文件路径如图片都使用os.path.join和os.path.exists进行构建和检查避免硬编码和路径错误。阅读控制台输出PyQt 和 Python 的运行时错误和警告都会打印到控制台这是最重要的调试信息源。隔离测试如果某个功能如动画不正常可以写一个单独的、最小化的测试脚本来验证该功能本身的逻辑排除其他部分干扰。7. 从初版到可扩展架构最佳实践与下一步成功调通初版只是一个开始。为了让这个桌宠应用更具生命力、更易于维护和扩展你需要考虑以下最佳实践和扩展方向。7.1 代码组织与架构优化状态模式当前使用简单的current_state字符串和if判断。当状态增多如 idle, walk, sleep, eat, happy且每个状态有不同动画和转换逻辑时应使用状态模式。为每个状态定义一个类并实现enter,exit,handle_event等方法。资源管理器将图片加载、缓存管理抽象成一个单独的ResourceManager类。它可以统一处理路径、缓存QPixmap或QMovie对象避免重复加载。配置外部化将窗口大小、初始位置、动画帧间隔、状态转换概率等参数提取到配置文件如config.ini或config.json中方便调整而无需修改代码。事件总线如果未来需要实现复杂交互如桌宠之间通信、响应系统事件可以考虑引入一个简单的事件发布/订阅机制解耦各个模块。7.2 功能扩展方向更多状态与动画为桌宠添加行走、睡觉、吃饭、玩耍等状态并制作相应的动画序列帧。物理与边界让桌宠可以在屏幕边缘“反弹”或实现简单的重力、跳跃物理效果。交互多样化支持右键菜单交互喂食、清洁、切换心情、拖拽食物到桌宠身上等。数据持久化记录桌宠的“心情值”、“饥饿值”、“经验值”等并保存到本地文件实现成长系统。音效使用QSound或QMediaPlayer为不同动作添加音效。网络功能从网络获取天气、时间信息并让桌宠做出相应反应如下雨时打伞。插件系统设计一个插件接口允许通过编写插件来增加新的行为或外观。7.3 生产环境考量虽然这是一个桌面小应用但若想分享给他人使用也需要考虑“生产”问题打包与分发使用PyInstaller,cx_Freeze或Nuitka将 Python 脚本打包成独立的可执行文件.exe,.app, 二进制文件并包含所有资源。注意处理资源文件的路径问题打包后通常需要使用sys._MEIPASS来获取临时解压路径。异常处理与日志在关键位置添加try...except并将异常信息记录到日志文件而不是仅仅打印到控制台方便用户反馈问题。内存管理长时间运行后检查是否有内存泄漏特别是不断创建新的QPixmap或QTimer而未释放。确保动画停止后定时器被正确停止和删除。多平台兼容性PyQt5 本身是跨平台的但系统托盘图标、路径分隔符、快捷键等细节可能不同。需要在不同操作系统Windows, macOS, Linux上进行测试。调通初版是梦想照进现实的第一步。你已经拥有了一个可以运行、可以交互的桌面伙伴骨架。接下来你可以根据自己的创意为它赋予独特的性格、行为和外观让它真正成为你桌面上一道有趣的风景。编程的乐趣往往就始于这样一个能跑起来的“小东西”而后在于不断打磨和丰富它的过程之中。