這次不是直接講解下去,而是談一下如何把我們寫的遊戲做成一個exe檔案,這樣一來,使用者不需要安裝python就可以玩了。掃清了遊戲釋出一大障礙啊!
perl,python,java等程式語言,非常好用,語法優美,功能強大;VB啥的,功能上編寫的時候總有那麼點不舒服的地方(個人見解),可是使用者和受眾極多,一個很大的原因就是:VB是微軟提供的,可以很方便的編譯(偽?)生成exe檔案。有了exe,所有的Windows都能方便的使用了。
我們不能指望使用者在玩我們的遊戲之前都安裝一個python和pygame,甚至還要裝一些其他額外的庫(比如上一章的gameobjects),這會嚇退99%以上的人……所以把我們的遊戲打包(注意是打包而不是編譯,python畢竟是指令碼程式)成一個可執行檔案勢在必行。
今天是父親節啊,祝天下父親快樂平安~perl有perlcc(免費高效但配置極其複雜),perlapp(簡單效果也不錯但是收費)等工具;而對python來說,py2exe是不二之選,首先是免費的,而且壓出來的檔案,雖然不能和編譯軟體相比,還是不錯的了。
到py2exe的官方網站下載安裝包,注意要對應自己的python版本。
py2exe是需要寫一個指令碼進行打包的操作,使用下面這個專為pygame寫就的指令碼(參考py2exe官方),可以極大的方便打包操作,注意在使用前修改BuildExe裡的各個引數。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
#!python # -*- coding: gb2312 -*- # 這個指令碼專為pygame優化,使用py2exe打包程式碼和資源至dist目錄 # # 使用中若有問題,可以留言至: # //eyehere.net/2011/python-pygame-novice-professional-py2exe/ # # 安裝需求: # python, pygame, py2exe 都應該裝上 # 使用方法: # 1: 修改此檔案,指定需要打包的.py和對應資料 # 2: python pygame2exe.py # 3: 在dist資料夾中,enjoy it~ try: from distutils.core import setup import py2exe, pygame from modulefinder import Module import glob, fnmatch import sys, os, shutil except ImportError, message: raise SystemExit, "Sorry, you must install py2exe, pygame. %s" % message # 這個函式是用來判斷DLL是否是系統提供的(是的話就不用打包) origIsSystemDLL = py2exe.build_exe.isSystemDLL def isSystemDLL(pathname): # 需要hack一下,freetype和ogg的dll並不是系統DLL if os.path.basename(pathname).lower() in ("libfreetype-6.dll", "libogg-0.dll", "sdl_ttf.dll"): return 0 return origIsSystemDLL(pathname) # 把Hack過的函式重新寫回去 py2exe.build_exe.isSystemDLL = isSystemDLL # 這個新的類也是一個Hack,使得pygame的預設字型會被拷貝 class pygame2exe(py2exe.build_exe.py2exe): def copy_extensions(self, extensions): # 獲得pygame預設字型 pygamedir = os.path.split(pygame.base.__file__)[0] pygame_default_font = os.path.join(pygamedir, pygame.font.get_default_font()) # 加入拷貝檔案列表 extensions.append(Module("pygame.font", pygame_default_font)) py2exe.build_exe.py2exe.copy_extensions(self, extensions) # 這個類是我們真正做事情的部分 class BuildExe: def __init__(self): #------------------------------------------------------# ##### 對於一個新的遊戲程式,需要修改這裡的各個引數 ##### #------------------------------------------------------# # 起始py檔案 self.script = "MyGames.py" # 遊戲名 self.project_name = "MyGames" # 遊戲site self.project_url = "about:none" # 遊戲版本 self.project_version = "0.0" # 遊戲許可 self.license = "MyGames License" # 遊戲作者 self.author_name = "xishui" # 聯絡電郵 self.author_email = "blog@eyehere.net" # 遊戲版權 self.copyright = "Copyright (c) 3000 xishui." # 遊戲描述 self.project_description = "MyGames Description" # 遊戲圖示(None的話使用pygame的預設圖示) self.icon_file = None # 額外需要拷貝的檔案、資料夾(圖片,音訊等) self.extra_datas = [] # 額外需要的python庫名 self.extra_modules = [] # 需要排除的python庫 self.exclude_modules = [] # 額外需要排除的dll self.exclude_dll = [''] # 需要加入的py檔案 self.extra_scripts = [] # 打包Zip檔名(None的話,打包到exe檔案中) self.zipfile_name = None # 生成資料夾 self.dist_dir ='dist' def opj(self, *args): path = os.path.join(*args) return os.path.normpath(path) def find_data_files(self, srcdir, *wildcards, **kw): # 從原始檔夾內獲取檔案 def walk_helper(arg, dirname, files): # 當然你使用其他的版本控制工具什麼的,也可以加進來 if '.svn' in dirname: return names = [] lst, wildcards = arg for wc in wildcards: wc_name = self.opj(dirname, wc) for f in files: filename = self.opj(dirname, f) if fnmatch.fnmatch(filename, wc_name) and not os.path.isdir(filename): names.append(filename) if names: lst.append( (dirname, names ) ) file_list = [] recursive = kw.get('recursive', True) if recursive: os.path.walk(srcdir, walk_helper, (file_list, wildcards)) else: walk_helper((file_list, wildcards), srcdir, [os.path.basename(f) for f in glob.glob(self.opj(srcdir, '*'))]) return file_list def run(self): if os.path.isdir(self.dist_dir): # 刪除上次的生成結果 shutil.rmtree(self.dist_dir) # 獲得預設圖示 if self.icon_file == None: path = os.path.split(pygame.__file__)[0] self.icon_file = os.path.join(path, 'pygame.ico') # 獲得需要打包的資料檔案 extra_datas = [] for data in self.extra_datas: if os.path.isdir(data): extra_datas.extend(self.find_data_files(data, '*')) else: extra_datas.append(('.', [data])) # 開始打包exe setup( cmdclass = {'py2exe': pygame2exe}, version = self.project_version, description = self.project_description, name = self.project_name, url = self.project_url, author = self.author_name, author_email = self.author_email, license = self.license, # 預設生成視窗程式,如果需要生成終端程式(debug階段),使用: # console = [{ windows = [{ 'script': self.script, 'icon_resources': [(0, self.icon_file)], 'copyright': self.copyright }], options = {'py2exe': {'optimize': 2, 'bundle_files': 1, 'compressed': True, 'excludes': self.exclude_modules, 'packages': self.extra_modules, 'dist_dir': self.dist_dir, 'dll_excludes': self.exclude_dll, 'includes': self.extra_scripts} }, zipfile = self.zipfile_name, data_files = extra_datas, ) if os.path.isdir('build'): # 清除build資料夾 shutil.rmtree('build') if __name__ == '__main__': if len(sys.argv) < 2: sys.argv.append('py2exe') BuildExe().run() raw_input("Finished! Press any key to exit.") |
可以先從簡單的程式開始,有了一點經驗再嘗試打包複雜的遊戲。
一些Tips:
- 如果執行出錯,會生成一個xxx.exe.log,參考這裡的log資訊看是不是少打包了東西。
- 一開始可以使用console來打包,這樣可以在命令列裡看到更多的資訊。
- 對於每一個遊戲,基本都需要拷貝上面的原始程式碼修改為獨一無二的打包執行檔案。
- 即使一個很小的py檔案,最終生成的exe檔案也很大(看安裝的庫而定,我這裡最小4.7M左右),事實上py2exe在打包的時候會把無數的不需要的庫都打進來導致最終檔案臃腫,如果你安裝了很繁雜的庫(wxPython等)更是如此。使用zip打包以後檢視裡面的庫檔案,把不需要的逐一加入到self.exclude_modules中,最後可以把檔案尺寸控制在一個可以接受的範圍內。
2011/08/21 追記:
很多人在打包使用Font模組時出現問題,這裡需要把sdl_ttf.dll宣告為非系統檔案,我已經修改了指令碼預設就加入了。而且建議,如果將來是確定要打包為exe的,那麼就不要使用系統字型,即”pygame.font.SysFont(xxx)”,而是使用字型檔案,然後打包時將檔案當作圖片等一起打包,這樣出問題的概率會大大降低。
2011/09/24 追記:
感謝blues_city網友,“dist_dir”應該是屬於py2exe的特有options而不是setup的。
歡迎大家試用並提出建議,不斷完善這個指令碼。