[pygame] exe化[cx_Freeze]

#pygame → exe化

　ちょくちょく追記と書き直しをしているため､更新日が最近になっていますが｡本記事は**2019/1月10日**時点の記事です｡

###はじめに
　windows上でpygameを用いたプログラムをexeにしていくまで辿った道のりを記録しました｡（2019/1月10日）
　exe化に関しては.pyで使用するライブラリやexe化の方法などの兼ね合いでうまく行ったり行かなかったりします｡今回たまたまかも知れませんが誰かのお役に立てれば幸いです｡


###背景
　pythonファイルの実行ファイル化は主に、pyinstaller, py2exem cx_Freezeの3つが有名だと思います。執筆現在では <b>pyinstaller </b>の使用が最も手軽とされており、初心者プログラマー向けの記事もpyinstallerを用いた実行ファイル化の手順を説明しているものが多めです。
> Pythonスクリプトを単一実行ファイルにする方法
https://qiita.com/hirohiro77/items/466e411fa41f144c8b2a

　pygameを用いた場合でも､上記の記事に従えば実行ファイルが出来るのかなと､試してみたところうまく行きませんでした:confounded:。(テトリスを作ったのに友達に自慢できないなんて！)
　実行ファイルの生成まではうまく行きましたが、実行時に止まってしまいました。

####解決に丸一日
　pygameのexe化なんてニッチな記事なんてあるわけありません｡それでもなんとかgoogleで手探りに調べた結果、最終的に１つの実行ファイルにすることが出来ました。しかし、途中何ヶ所も嵌り､１日潰れてしまいました｡
　同じミスをしないよう、記録としてこの記事を作成しました｡(他の環境での再現性については自信なしです:pear:)


環境
---
windows7 : 32bit
conda : 4.5.12-py37_0
pip : 18.1
python : 3.6.6
cx_Freeze : 5.1.1
pygame : 1.9.4


手順
---
1. cx_Freezeのインストール
1. pygameファイル内のpathの設定
2. setup.pyの作成
3. exe化
4. 単独exeファイル化
5. batファイルによる自動化
</b>

###pygameを使用しない場合のパッケージング
pyinstallerを使ったものが一般的です｡上記に掲載したサイトを参照してください。



#1. cx_Freezeのインストール

cx_Freezeはcondaだと見つかリませんでした｡pipでインストールします。

```
> pip install cx_Freeze --upgrade
```

#### 実行結果

```
...(省略)...
Successfully installed cx_Freeze-X.X.X
>
```
この表示が出ていればおそらく正常にインストールできているでしょう｡

#2 pygameファイル内のpass設定

皆さんがpygameで作ったゲームは、よほど簡単なゲームでない限り、画像ファイルを外部から読み込んでいるはずです。読み込むときのパスの扱いは少し注意が必要です｡

#### うまく行かなかった例

```python:main.py
# ...
image = pygame.image.load('game1/image_file/image1.png')
# ...
```
当初は､上の例のようにpngのパスを直接入れていましたが、これだと実行ファイルを起動したときに、pngファイルの読み込みに失敗する恐れがあります。

osモジュールをインポートしてos.getcwd()を使う形で書き直しましょう。

#### うまくいった例

```python:main.py

# インポート文
import os


# メイン関数
def main():
    #...
    image1_path = os.path.join(os.getcwd(), "python_images\\"+"image1.png")
    #...
    image1 = pygame.image.load(image1_path)
    #...
```
追記(2020/7/8)
pygameのファイル位置に対して動的な絶対パスを作成するというところでしょうか｡

#3. setup.pyの作成

pygameファイルと同階層に、**setup.py**を作成します。このファイルでexeの作成方法を設定していきます。setup.pyの中身は以下のように書いていきます。

```python:setup.py

import sys
from cx_Freeze import setup, Executable

base = None

if sys.platform == "win32" : base = "Win32GUI"
# CUIの場合はこのif文をコメントアウトしてください。

exe = Executable(script = "main.py", base= base)
# "main.py"にはpygameを用いて作成したファイルの名前を入れてください。

setup(
    name = 'your_filename',
    version = '0.1', 
    description = 'converter',
    executables = [exe]
)
# 'your_filename'は好みの名前でどうぞ。
```

これでexe化の準備が整いました。

#4. exe化

pygeme,cx_freezeの環境に入り､対象ファイル直下のディレクトリに移動し､以下を実行します｡

```
> python setup.py bdist_msi
```
pygameファイルの構成によっては時間がかかるかもしれません。
pygameファイルの同階層にdistとbuildというフォルダが出来ているはずです。exeファイルは<b>buildフォルダ</b>内の<b>exe.win32-3.6</b>というフォルダ（環境によって名前は多少変わっているかも知れません。）の中にありますが､まだ他のファイルと依存関係があります。中身を崩さないように注意してください｡
※ここで実行ファイルを起動しても、pngを使うpygameファイルの場合、pngが読み込めません。手順1.5中の <b>"python_images\\"+"image1.png"</b>の部分（例の場合python_imagesフォルダ）を、<b>exe.win32-3.6</b>内にコピーしてください。

# 5. １ファイル化

NSISというソフトを使うとcx_Freezeでも（py2exeでも）1ファイル化が可能です。NSISをインストールし、pygameファイルの真横に、setup.nsiを作り、中身に1ファイル化のための設定などを書いていきます。

詳細な説明は以下のサイトへ

>（引用）http://fanblogs.jp/mountain4101/archive/59/0


途中でmakensis.exeのpathを通す必要があります｡以下のサイトを各自参照してください｡
>(引用)　http://realize.jounin.jp/path.html

#### 例

```:setup.nsi
!define cx_fleezeOutputDir 'build\exe.win32-3.6'
!define exe 'game.exe'
;!define comressor 'lzma' ;one of'zlib', 'bzip2', 'lzma'
!define onlyOneInstance
!include FileFunc.nsh
!insertmacro GetParameters

; - - - - do not edit below this line, normaly - - - -
!ifdef compressor
    SetCompressor ${compressor}
!else
    SetCompress Off
!endif
Name ${exe}
OutFile ${exe}
SilentInstall silent
!ifdef icon
    Icon ${icon}
!endif

; - - - - Allow only one installer instance - - - - 
!ifdef onlyOneInstance
Function .onInit
 System::Call "kernel32::CreateMutexA(i 0, i 0, t '$(^Name)') i .r0 ?e"
 Pop $0
 StrCmp $0 0 launch
  Abort
 launch:
FunctionEnd
!endif
; - - - - Allow only one installer instance - - - - 

Section
    
    ; Get directory from which the exe was called
    System::Call "kernel32::GetCurrentDirectory(i ${NSIS_MAX_STRLEN}, t .r0)"
    
    ; Unzip into pluginsdir
    InitPluginsDir
    SetOutPath '$PLUGINSDIR'
    File /r '${cx_fleezeOutputDir}\*.*'
    
    ; Set working dir and execute, passing through commandline params
    SetOutPath '$0'
    ${GetParameters} $R0
    ExecWait '"$PLUGINSDIR\${exe}" $R0' $R2
    SetErrorLevel $R2
 
SectionEnd
```
一行目
'build\exe.win32-3.6'
の部分は、cx_Freezeが吐き出したファイルの中の実行ファイルが入っているフォルダのパスを入れてあげます。各々の環境によって変えてください。

# 6. 自動化

batとcmdの知識は無いに等しいので、参考程度にお願いします。

いちいちコマンドプロンプトやアナコンダプロンプトを立ち上げてディレクトリを動かしてexe化するのは面倒です。クリックするだけでpygameファイルをexe化してくれるbatファイルを作りました。

このbatファイルをpygameファイルの同階層に入れて実行することで、cx_Freezeを用いたexe化が行えます。（setup.pyは書かないといけません。）
<b>例</b>

```setup_cx_Freeze.bat
@rem エコーを出力しない
@echo off

rem pygameファイルから実行ファイルを生成するBAT
rem Minicondaの仮想環境'test'の中のcx_freezeを使用
rem batファイルと同じディレクトリのsetup.pyファイルを使用。
cd /d %~dp0


if not exist "_TMP" (
type nul > _TMP
rem Anaconda promptを起動(call以下は各自任意のパスを指定してください)
call C:\Users\user\Miniconda3\Scripts\activate.bat

rem exe化用の仮想環境へ移動（任意の仮想環境名をactibateさせてください）
activate test
python setup.py bdist_msi
pause
del "_TMP"

```


また、NSIS自動化のbatファイルは手順4で引用したサイトに掲載されています。


```:setup_nsis.bat
@rem エコーを出力しない
@echo off

rem Batのあるフォルダに移動し.pyファイルを実行するBAT
rem 現在のフォルダに移動
cd /d %~dp0

makensis.exe setup.nsi

pause

exit
```

setup_cx_Freeze.bat→setup_nsis.batの順にバッチファイルを起動することでexeが産まれます。産まれましたか?　私の環境では大量のexeの赤ちゃんが生まれています。


#最後に
　時間が経っているので役に立たないクソ記事と化している可能性があります｡出来た・出来なかったのご報告をお待ちしています｡それによっては書き直しが発生したりしなかったり､この記事が消えたりするかも知れません｡