WSL2+Buildozer+PyInstaller:pygame游戏一键打包APK与exe全攻略
很多用 Python 写小游戏的朋友卡在发布这一步特别可惜游戏在电脑上跑得好好的想发到手机上一个 APK网上教程要么要 Mac、要么让你装虚拟机折腾一圈还没到打包就放弃了。我自己的环境是 Windows WSL2(Ubuntu 22.04)这套组合把安卓打包和 Windows exe 发布的两个问题都解决了。这篇文章完整记录从零开始的操作流程包括 Buildozer 打包 APK 的完整配置、PyInstaller 在 Windows 侧出 exe 的注意事项以及我实际踩过的各种坑。适合已经用 pygame 写完小游戏、想把作品发到安卓手机和 Windows 电脑上运行的朋友也适合刚接触 WSL2、想搞清楚整个链路怎么通的人。1. 为什么把打包流程拆成WSL2 打 APK Windows 打 exe先聊方案设计。pygame 游戏最终要发布成两个目标安卓的 APK 和 Windows 的 exe。这两个目标对环境的要求完全不同硬要在同一个系统里搞定反而会走弯路。1.1 APK 打包为什么绕不开 Linux安卓平台的 Python 打包工具链是 python-for-androidp4aBuildozer 只是它的前端封装。p4a 的设计目标是在 Linux 和 macOS 上运行从来没支持过 Windows 原生环境。原因是打包过程中要交叉编译 Python 解释器和一堆原生库SDL、ffi、OpenSSL 等这需要完整的 POSIX 工具链而 Windows 的原生环境差得太远。所以摆在 Windows 用户面前的选择无非是装虚拟机、装双系统、或者用 WSL2。虚拟机方案最重双系统切换麻烦WSL2 几乎是零成本。WSL2 不是模拟器它是一个轻量级虚拟机内核就是真正的 Linux 内核Ubuntu 22.04 里的 apt、gcc、make 全都能正常工作p4a 在里面跑完全没问题。1.2 exe 打包为什么不放在 WSL2 里有人会想既然 WSL2 这么方便那 exe 是不是也能在 WSL2 里直接打答案是不能或者说不建议。PyInstaller 不支持交叉编译它在 Linux 里打包出来的可执行文件是 Linux ELF 格式Windows 根本不认。想用 Wine 跑 PyInstaller 再打包 Windows 程序属于可玩但极不稳定的路线我不推荐任何人把时间花在这上面。正确思路是WSL2 专门负责 APKWindows 侧单独准备一个 Python 环境负责 exe。两边互相独立靠同一个游戏项目目录和同一份依赖清单requirements.txt保持同步。1.3 整体流程预览这套方案跑通后的完整链路是这样的在 WSL2(Ubuntu 22.04) 里安装 Python 虚拟环境、pygame、Buildozer。游戏项目放在 WSL2 的 Linux 文件系统里不是 /mnt/c编写代码和素材。在 WSL2 里执行buildozer android debug得到 APK。把 APK 从 WSL2 复制到 Windows 侧传到手机安装。在 WSL2 里用pip freeze导出依赖清单。在 Windows 侧创建虚拟环境安装相同依赖。在 Windows 侧用 PyInstaller 打包得到 exe。我第一次完整跑通大概花了半天其中大头是等 p4a 首次编译下载 SDK、NDK、编译 Python真正操作的部分并不多。这也是我想在这篇文章里把步骤写细的原因——操作本身不复杂但坑都在细节里。2. 从零搭环境WSL2、Ubuntu 22.04 与 pygame 开发环境这一节从空白环境开始把 WSL2 和 Ubuntu 22.04 配好确认 pygame 能跑起来同时说清楚几个容易踩雷的地方。2.1 安装 WSL2 并指定 Ubuntu 22.04如果你还没装过 WSL在 Windows 11 或 Windows 102004上最省事的方式是管理员权限 PowerShell 里执行wsl --install这个命令默认装最新版 Ubuntu但我们要的是 22.04建议直接指定发行版wsl --install -d Ubuntu-22.04安装完重启系统首次启动会要求设置 Linux 用户名和密码。如果之前已经装了其他版本可以用下面命令确认当前发行版和 WSL 版本wsl -l -v看到 VERSION 列是 2 就行。如果显示 1执行wsl --set-version Ubuntu-22.04 2顺便说一句WSL2 的内存和 CPU 默认只占用物理机的一半。打包 APK 时 p4a 要同时编译多个东西内存不够容易崩溃。建议在 Windows 用户目录下创建一个.wslconfig文件内容如下[wsl2] memory8GB processors4 swap4GB改完后wsl --shutdown再重新进 WSL2 生效。这个文件对整个打包流畅度帮助很大。2.2 Ubuntu 22.04 基础配置换源和系统依赖进入 WSL2 后第一件事换软件源。国内访问 Ubuntu 官方源很慢我用的清华镜像。编辑/etc/apt/sources.list把archive.ubuntu.com和security.ubuntu.com替换为mirrors.tuna.tsinghua.edu.cn。如果不太熟悉 vim可以直接用 sed 替换sudo sed -i s//.*archive.ubuntu.com//mirrors.tuna.tsinghua.edu.cng /etc/apt/sources.list sudo sed -i s//security.ubuntu.com//mirrors.tuna.tsinghua.edu.cng /etc/apt/sources.list sudo apt update sudo apt upgrade -y接下来装打包 APK 所需的全部宿主依赖。这一步简化版是装 Buildozer 文档推荐的一套但实际跑下来有几样容易漏autoconf、libtool、pkg-config、zlib1g-dev、libncurses5-dev、libncursesw5-dev、libffi-dev、libssl-dev、cmake、openjdk-17-jdk、unzip、zip、git。特别是 openjdkBuildozer 的新版本要求 JDK 17装错版本会报一堆 Java 相关错误。sudo apt install -y git zip unzip openjdk-17-jdk autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libffi-dev libssl-dev cmake这里有个小细节WSL2 里跑 pygame 开发测试还需要 SDL 的运行时库。虽然 pygame 的 pip 包自带 SDL 二进制但在 Linux 下有些模块比如 mixer还是会去找系统库稳妥起见一起装上sudo apt install -y libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev装完后python3 --version确认是 3.10 系列Ubuntu 22.04 默认就是这个版本pygame 和 p4a 对 Python 3.10 的支持都非常稳定。2.3 创建虚拟环境并安装 pygame不建议直接在系统 Python 里装包WSL2 里也一样虚拟环境是必须的。我的习惯是建一个~/dev目录放所有项目游戏项目单独一个 venvmkdir -p ~/dev cd ~/dev python3 -m venv mygame-venv source mygame-venv/bin/activate pip install --upgrade pip pip install pygame虚拟环境最大的好处是打包 exe 时要在 Windows 侧复现同样的依赖有虚拟环境后pip freeze导出的清单干净可靠不会把系统里乱七八糟的包带进去。验证 pygame 能不能跑在虚拟环境里执行python -c import pygame; print(pygame.version.ver)WSL2 从较新版本开始自带 WSLgGUI 程序能直接弹窗显示所以你也可以直接写个简单窗口测试。如果遇到无法显示的画面多数情况是没装 WSLg 的图形依赖或者 Windows 侧没更新 WSLwsl --update一下基本能解决。2.4 项目目录放 Linux 侧还是 Windows 侧这是一个很多人忽略但影响很大的点。WSL2 的/mnt/c/是 Windows 文件系统的挂载点IO 性能比 Linux 原生文件系统差很多尤其是 Buildozer 编译时会产生海量小文件放/mnt/c上整个打包时间可能翻倍。我的建议是游戏项目代码、虚拟环境、Buildozer 缓存全放在 Linux 侧如~/dev/mygame最终产物 APK 复制到 Windows 侧或者直接传给手机只有这一步走/mnt/c。3. 让一份 pygame 代码同时适应 APK 和 exe资源路径、字体与输入处理在开始打包之前一定要先把游戏代码调整到双端兼容状态。这一步不做好打包阶段会反复失败或者运行时报错。3.1 统一资源路径方案pygame 游戏离不开图片、音效、字体这些外部资源。在电脑上开发时你用的是os.path.join(assets, image.png)这在本地没问题。但打包后情况变了PyInstaller 的 onefile 模式会把资源解压到临时目录安卓打包后资源在应用的私有目录里代码里的相对路径很可能就失效了。我的惯例是在游戏入口文件里加一个统一的资源路径函数import os import sys def resource_path(relative): 兼容 PyInstaller 和安卓打包的资源路径 if hasattr(sys, _MEIPASS): # PyInstaller 临时解压目录 base sys._MEIPASS elif ANDROID_ARGUMENT in os.environ: # python-for-android 环境下__file__ 指向用户私有目录 base os.path.dirname(os.path.abspath(__file__)) else: # 本地开发 base os.path.dirname(os.path.abspath(__file__)) return os.path.join(base, relative)然后所有加载资源的地方统一用resource_path(assets/images/player.png)。这样在三点之间无缝切换本地开发、PyInstaller exe、安卓 APK。3.2 把游戏资源目录规划清楚和双端兼容配套的是资源目录规划。项目结构我建议这样mygame/ ├── main.py ├── requirements.txt ├── assets/ │ ├── images/ │ │ ├── player.png │ │ └── background.jpg │ └── sounds/ │ └── jump.wav └── fonts/ └── custom.ttfmain.py是游戏入口assets放图片和音效fonts放字体。Buildozer 打 APK 时会根据source.include_exts配置自动把这些资源打进包里PyInstaller 则需要靠--add-data补充。这个目录规划越规范后面打包越省心。3.3 中文字体问题pygame 在 Windows 上可以用默认字体显示中文因为系统里有中文字体文件。但在安卓上默认字体对中文支持很差甚至直接显示方框。解决方案很简单把一款开源中文字体比如思源宋体、文泉驿微米黑注意版权放到fonts目录启动时用pygame.font.Font指定font_path resource_path(fonts/custom.ttf) font pygame.font.Font(font_path, 24)千万别用pygame.font.SysFont它依赖操作系统的字体注册表安卓和 exe 环境都不保险。自定义字体文件是跨平台最可靠的方案。3.4 屏幕适配与触屏输入电脑上的游戏尺寸一般是固定的比如 800x600。手机屏幕分辨率千差万别直接全屏会导致画面拉伸变形或者只显示左上角一部分。我的处理方式是游戏内部保持一个逻辑分辨率如 800x600绘制到一个 Surface 上最后统一缩放。代码骨架是这样import pygame LOGIC_W, LOGIC_H 800, 600 def main(): pygame.init() info pygame.display.Info() screen_w, screen_h info.current_w, info.current_h screen pygame.display.set_mode((screen_w, screen_h), pygame.FULLSCREEN) canvas pygame.Surface((LOGIC_W, LOGIC_H)) clock pygame.time.Clock() while True: # 在 canvas 上绘制游戏内容 # canvas.fill((255, 255, 255)) # ... # 缩放并绘制到屏幕 scaled pygame.transform.scale(canvas, (screen_w, screen_h)) screen.blit(scaled, (0, 0)) pygame.display.flip() clock.tick(60)安卓端pygame.display.Info()在 WSL2 里跑的时候可能读取不到真实屏幕信息但你在自己电脑上开发时会正常读取。到了手机上FULLSCREEN会填满屏幕配合缩放就统一了。触屏输入方面pygame 在安卓上会把触摸映射成鼠标事件所以MOUSEBUTTONDOWN和MOUSEMOTION在手机上依然能用。不过如果游戏需要同时用多点触控比如双按钮操作最好自己维护一个手指坐标列表用触摸事件模拟多指。我的简单实现是记录所有 active 手指的坐标fingers {} for event in pygame.event.get(): if event.type pygame.FINGERDOWN: fingers[event.finger_id] (event.x * LOGIC_W, event.y * LOGIC_H) elif event.type pygame.FINGERUP: fingers.pop(event.finger_id, None)FINGERDOWN是 pygame 2.x 在移动端新增的底层触控事件event.x和event.y是 0~1 的标准化坐标乘逻辑宽度高度就得到逻辑坐标系里的位置。这样在电脑上开发时用鼠标模拟手机上用真实手指代码逻辑一致。3.5 音效初始化的顺序pygame.mixer 在安卓上偶尔会初始化失败尤其在音频硬件还没准备好的时候。我习惯在游戏启动时加一个重试机制pygame.mixer.pre_init(44100, -16, 2, 512) try: pygame.mixer.init() except pygame.error: # 安卓平台偶尔初始化失败重试一次 pygame.mixer.quit() pygame.mixer.init(44100, -16, 2, 512)即便初始化失败也不应该让游戏崩溃用 try-except 包住加载音效的逻辑加载失败的音效就跳过。游戏主循环能跑起来是第一优先级。4. 打包 APKBuildozer 配置、依赖安装、编译与排错这一节是文章的核心完成 APK 的整个打包过程包含每一步可能遇到的报错和解决办法。4.1 安装 Buildozer 并初始化项目在 WSL2 的虚拟环境里安装 Buildozerpip install buildozer cd ~/dev/mygame buildozer initbuildozer init会在项目目录生成一个buildozer.spec文件这是打包的核心配置。它会把当前目录当作 source.dir默认值就是.所以进对目录再执行很重要。4.2 buildozer.spec 关键配置详解buildozer.spec内容很长但我们需要改动的字段就几个[app] title MyGame package.name mygame package.domain org.example source.dir . source.include_exts py,png,jpg,jpeg,kv,atlas,txt,ttf,otf,wav,mp3,ogg version 0.1 requirements python3,pygame android.archs arm64-v8a android.accept_sdk_license True android.min_sdk_version 21 android.api_level 30 android.ndk_api_level 21逐个解释title显示在手机桌面上的 App 名称。package.name包名后缀会在package.domain后面拼成一个完整包名如org.example.mygame。这个包名后续要改就会比较麻烦最好一开始就想清楚。source.include_exts非常重要的配置。决定哪些扩展名的文件会被打进 APK。如果你有.json关卡文件、.csv数据文件记得加进去漏掉的话游戏运行时会找不到文件。requirements所有 Python 依赖python3必须保留pygame 直接写上。如果用到了其他纯 Python 库比如 requests也写在这里但要注意 p4a 不是所有包都能编译。android.archs目标 CPU 架构。现在主流手机都支持 arm64-v8a只保留它打包时间会短不少。如果老设备也想覆盖可以改成arm64-v8a, armeabi-v7a但编译时间会明显增加。android.accept_sdk_license必须设成 True否则 SDK 下载后停在许可确认环节。android.min_sdk_version和android.api_level决定 APK 支持的安卓最低版本。21 对应安卓 5.0覆盖绝大部分设备。还有一处需要注意默认 spec 里bootstrap sdl2保持这个值不要动。有些教程说 pygame 游戏必须选 pygame bootstrap实际那是老版本的用法现在 p4a 的 pygame 配方基于 SDL2 工作bootstrap 保持 sdl2 才是正确选择。4.3 宿主依赖再检查前面已经装过基础的宿主依赖了但 Buildozer 首次运行还会检查缺失的工具。如果哪个没装会直接报Missing dependencies提示。把下面这组命令当作标准清单再跑一遍确保万无一失sudo apt install -y git zip unzip openjdk-17-jdk autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libffi-dev libssl-dev cmakeopenjdk-17-jdk这个最容易装错版本。可以用java -version确认必须看到 17 字样。4.4 首次编译漫长等待与网络加速核心命令就一行buildozer android debug首次运行做的事非常多下载 Android SDK、下载 NDK这个体积很大、用 python-for-android 交叉编译 Python、编译 pygame、把资源和代码打进 APK。整个过程在网速正常、机器内存 8G 以上的前提下大概需要 40 到 90 分钟中间看起来像卡住了一样其实是在编译。国内网络环境下最大的痛点是 SDK 和 NDK 下载慢。Buildozer 默认从 Google 官方服务器下载速度不理想。我的做法是先让 buildozer 跑起来它会在~/.buildozer/android/platform/创建目录结构并试图下载如果失败了把 SDK 压缩包手动下载后放到对应位置再重新跑。具体来说Buildozer 会把 SDK 放到~/.buildozer/android/platform/android-sdk。你可以提前下载 Android command line tools 解压进去。NDK 也类似放到~/.buildozer/android/platform/android-ndk-rXX。这一部分网上有更详细的教程但我的经验是与其手动安置版本引发版本不匹配不如直接用buildozer自带的下载流程挂一个代理或者半夜网络好的时候跑一次省心。4.5 首次编译的报错对照表我把实际操作中最常见的错误列成表方便对号入座报错信息原因解决办法SDK license not accepted忘记设置许可接受在 spec 中设android.accept_sdk_license Trueopenjdk version mismatchJDK 版本不对卸载重装openjdk-17-jdkNo module named Cythonp4a 内部需要 Cython手动pip install cython/usr/bin/ld: cannot find -lpython3.10宿主编译环境缺少 Python 开发头文件sudo apt install python3-devOut of memoryWSL2 分配内存不足调整.wslconfig增加 memory执行wsl --shutdownFailed to extract libpng.so或类似NDK 与 SDK 版本不匹配删掉~/.buildozer/android/platform重新让 Buildozer 下载匹配版本最坑的是最后一类NDK 版本不匹配。Buildozer 的默认 NDK 版本是固定的如果你手动替换过 NDK版本和 p4a 不兼容会报各种链接错误。这时候最好的办法就是删干净~/.buildozer/android/重新下载。4.6 拿到 APK 并传到手机编译成功后产物在bin/目录下命名格式是mygame-0.1-arm64-v8a-debug.apk。debug 版因为要方便调试体积会偏大签名是 debug 签名但安装到手机完全没问题。把 APK 复制到 Windows 侧桌面方便传输cp bin/mygame-0.1-arm64-v8a-debug.apk /mnt/c/Users/你的用户名/Desktop/然后通过微信文件传输助手、QQ、数据线或者网盘把这个 APK 发到手机上安装。手机需要允许安装未知来源应用这是安卓的常规操作。5. exe 打包实操依赖导出、Windows 侧环境配置与 PyInstaller 出包APK 搞定了接下来处理 Windows exe。前面说过exe 要在 Windows 侧打但依赖信息可以在 WSL2 里导出来。5.1 在 WSL2 里锁定依赖版本进入项目的虚拟环境导出依赖清单pip freeze requirements.txt这个文件里包含 pygame 及其所有依赖的精确版本。Windows 侧安装同样版本能最大程度避免在 Windows 上跑起来行为不一致的问题。如果开发时用了很多只服务于调试的包可以手动编辑 requirements.txt只留游戏运行真正需要的。5.2 Windows 侧创建虚拟环境在 Windows 上打开 PowerShell进入游戏项目目录我通常把项目整个复制到 Windows 侧一份或者在 Windows 侧用 git clone 管理。cd D:\dev\mygame python -m venv win-build-venv .\win-build-venv\Scripts\Activate.ps1 pip install --upgrade pip pip install -r requirements.txt pip install pyinstaller这里有一个常见坑如果 Windows 上没装 Python先到官网装 Python 3.10 或 3.11安装时勾选 Add Python to PATH。微软商店版的 Python 也可以但路径有时候比较怪命令行里跑python时要注意。5.3 PyInstaller 打包命令确认游戏在 Windows 侧能正常运行后执行打包pyinstaller --noconfirm --onefile --windowed --name MyGame --add-data assets;assets --add-data fonts;fonts main.py逐个参数说明--onefile打成一个单独的 exe方便分发。--windowed不显示黑色控制台窗口游戏程序必须加。--name MyGame生成的 exe 名字。--add-data assets;assets把资源目录打包进 exe。Windows 上用分号分隔源路径和目标路径这是和 Linux 最大的区别Linux 是冒号。main.py程序入口。resource_path函数在 PyInstaller onefile 模式下会自动找到sys._MEIPASS解压目录所以资源文件能正确加载。5.4 exe 打包的常见问题资源找不到多半是--add-data路径不对。检查命令里的路径是否真实存在以及资源路径函数是否用resource_path拼接。双击 exe 没反应因为加了--windowed控制台被隐藏报错信息看不见。排查技巧是先用不加--windowed的版本打一次运行时会弹出控制台看到具体的 Python 报错修复后再正式打包。文件体积过大pygame 游戏动辄 50MB 以上很正常。想压缩体积可以检查有没有打进不必要的依赖或者在虚拟环境里只保留游戏运行需要的包再打包。杀毒软件误报PyInstaller 打包的 exe 有时会被 Windows Defender 或其他杀软误报一是因为 onefile 自解压特征二是因为没有数字签名。个人项目一般直接添加信任想彻底解决就得买代码签名证书成本不低量大的商业化发布才需要考虑。5.5 同一份代码两套交付物到这里exe 就出现在dist/MyGame.exe。把 APK 发给安卓手机用户把 exe 发给 Windows 用户两个平台共用一套代码。后续更新游戏时修好代码后重新跑一遍buildozer android debug和pyinstaller即可整个流程已经固定下来。这里再强调一遍资源路径函数的重要性如果代码里到处是裸的相对路径exe 能跑但 APK 找不着资源或者反过来。我在项目上线前会先在两个平台各跑一遍确认资源加载全部正常。6. 真机测试、性能优化与后续扩展建议打包成功只是开始真机上的表现才是真正的考验。这一节聊聊我在双端运行后的一些实测体会和优化思路。6.1 安卓真机上的性能表现pygame 本质上还是用软件渲染为主的框架在手机上跑小游戏贪吃蛇、弹球、飞行射击这类完全没问题即使逻辑分辨率 800x600 全程缩放帧率也能稳定在 60fps。但如果你的游戏里有大量全屏特效、逐像素处理手机上会明显发热掉帧。我的建议是控制单帧的绘制次数和图片尺寸。手机 GPU 处理大尺寸 Surface 缩放开销不小可以先把画布分辨率调低一些比如 480x320反而在手机上更流畅因为缩放计算量小很多。另外clock.tick(60)一定要放在主循环里否则低端手机会跑到满帧然后发热严重游戏体验反而差。6.2 exe 端的适配思考Windows 端主要面对的是不同分辨率的显示器。如果游戏逻辑分辨率固定且缩放绘制在 1080p 和 4K 屏幕上都能正常显示。窗口模式下pygame.display.set_mode((LOGIC_W, LOGIC_H))即可别强制全屏用户自己缩放窗口就行。6.3 打包流程的提速技巧Buildozer 首次打包耗时最长是因为要下载 SDK/NDK 并编译 Python。第二次再打包就快很多因为 SDK 和 NDK 都缓存好了。有几点可以注意~/.buildozer/目录是整个打包流程的缓存把它备份好换机器或者重装 WSL2 后能省大量时间。只改游戏代码时Buildozer 会复用已编译的 Python 和依赖只重新打包 APK速度通常在几分钟内。如果改了requirementsp4a 会重新编译新增的包耗时增加是正常的。6.4 更进一步这个方案还能干什么这套环境天然支持从 pygame 扩展到更复杂的 Python 移动开发。如果你想做 UI 更丰富、交互更接近原生应用的项目可以考虑 KivyBuildozer 对它支持得更好。Kivy 和 pygame 共存也完全可以只要在requirements里加上kivy或者pygamep4a 都能处理。如果游戏规模变大性能要求变高pygame 可能就不够用了。到时候可以把逻辑层用 Cython 优化或者迁移到 Godot、Cocos Creator 这类专业游戏引擎。但那是后话——先用 pygame 把自己的第一款跨平台游戏完整发布出去这个成就感是无可替代的。最后再分享一个实际体会我的第一次打包 APK 经历并不顺利问题出在 NDK 版本不匹配上反复删缓存下载好几次才成功。现在回头看多数坑都来自对 Linux 环境和 Android 工具链的不熟悉而不是流程本身有多难。按这篇文章的步骤走大部分问题应该都能绕开。真遇到报错先看报错信息里的路径和版本号再对照文中的排错表基本都能定位到问题所在。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →