cocos2d-console をカスタマイズ

## 自己紹介

[Smith](https://twitter.com/do_low)です。
ちょうど二年前くらいからスマフォ向けのゲームを作り始め、cocos2d-xもその時から触れ続けています。
現在はビルド周りとか開発環境周りの整備に貧乏暇なしです。

[去年](http://qiita.com/smith/items/62a08feec2ea4465e990)に比べれば cocos2d-xっぽい内容になりました。よかった。


## cocos2d-console とは

[git](https://github.com/cocos2d/cocos2d-console)

例えば、非常に面倒くさいAndroid NDKプロジェクトのapk作成やら実行ですが、

```
$ cocos run -p android
```

と、コマンド一発で実行してくれるイケメンツールです。
中身はpythonです。

とはいえ、プロジェクト独自の定常フローはしばしば生まれるもの。
そんな時には新規のオプションを追加したり、既存のオプションを拡張してしまいましょう。

### カスタマイズ

最短で下記の4ステップで実装可能です。

1. cocos2d.ini の編集
2. プラグイン用ディレクトリの作成
3. \_\_init\_\_.py を作成
4. プラグイン本体の実装

#### 1. cocos2d.ini の編集

cocos コマンドに追加するオプションの実装場所を定義します。
フォーマットは _{PLUGIN_DIR_NAME}.{CLASS\_NAME}_ です。

```bin/cocos2d.ini
[plugins]
my_plugin.MyPluginFoo
```

#### 2. プラグイン用ディレクトリの作成

_cocos2d.ini_ に定義した名前でディレクトリを作成します。

```bash
$ mkdir plugins/my_plugin
```

#### 3. \_\_init\_\_.py を作成

プラグイン用ディレクトリに配置します。
ファイル内の _import_ のフォーマットは _from {PLUGIN_FILE_NAME} import {CLASS\_NAME}_ です。

```plugins/my_plugin/__init__.py
from my_plugin import MyPluginFoo
```

#### 4. プラグイン本体の実装

_cocos.CCPlugin_ を継承します。

```bash
$ vi plugins/my_plugin/my_plugin.py
```

## 実装サンプル

本家を fork してサンプルを追加しています。

https://github.com/dolow/cocos2d-console/tree/custom_plugin_sample

下記プラグインを追加しているのでご参照ください。

_plugins/plugin_sample/plugin_sample.py_
_plugins/plugin_compile/plugin\_my\_compile.py_


## サンプル詳解

### plugin_sample

テキストを出力するだけの最もシンプルなサンプルです。
プラグインとして、必要最低限の実装のみを行っています。

```plugins/plugin_sample/plugin_sample.py
# 新規のプラグインなので、cocos.CCPluginを継承
class PluginSample(cocos.CCPlugin):
    
    @staticmethod
    def plugin_name():
        # 処理上の識別子として使われたり、
        # cocos -h などで表示されるプラグイン名
        return "plugin_sample"
    
    @staticmethod
    def brief_description():
        # cocos -h で表示される説明文
        return "プラグイン作成のサンプルです"
    
    def parse_args(self, argv):
        # cocos.CCPlugin で定義している必須オプションを無視するように
        # 処理を空にして実装
        pass
    
    ...

```

### plugin_ls

引数を取る時のサンプルです。
_cocos.CCPlugin_ に実装されているメソッドを利用して引数を解析しています。

```plugins/plugin_sample/plugin_ls.py
class PluginLs(cocos.CCPlugin):
    
    ...
    
    # cocos.CCPluginで実装されている引数解析メソッドをオーバーライド
    # 必要最低限の引数のみを対象にパース
    def parse_args(self, argv):
        parser = ArgumentParser(prog="cocos %s" % self.__class__.plugin_name(), description=self.__class__.brief_description())
        parser.add_argument("-p", "--platform", dest="platform", help=MultiLanguage.get_string('COCOS_HELP_ARG_PLATFORM'))
        
        (args, unknown) = parser.parse_known_args(argv)
        
        return args
    
    # cocos.CCPluginで実装されている初期化処理をオーバーライド
    # 引数解析の結果をインスタンスのプロパティにアサイン
    def init(self, args):
        self._project   = cocos_project.Project(os.path.abspath(os.getcwd()))
        self._platforms = cocos_project.Platforms(self._project, args.platform)
    
    # cocos.CCPluginで実装されている主処理のメソッドをオーバーライド
    # 引数解析と初期化はここで行った
    def run(self, argv, dependencies):
        args = self.parse_args(argv)
        self.init(args)
        subprocess.call("ls %s" % self._platforms.project_path(), shell=True)
```


### plugin_my_compile

既存のプラグインを拡張するときのサンプルです。

```plugins/plugin_compile/project_my_compile.py
# 元のプラグインを継承する
class CCPluginMyCompile(project_compile.CCPluginCompile):

    ...

    # CCPluginCompile に実装されているカスタム引数の追加メソッドをオーバーライド
    # (値は使ってないけど)
    def _add_custom_options(self, parser):
        super(CCPluginMyCompile, self)._add_custom_options(parser)
        from argparse import ArgumentParser
        parser.add_argument("--sample", dest="sample", help="カスタム引数追加のサンプル")

    # 継承元の処理の前に出力するだけ
    def run(self, argv, dependencies):
        print("this is customized compile")
        super(CCPluginMyCompile, self).run(argv, dependencies)
```


## カスタマイズ例

最近やった事例です。

- _compile_, _run_ の前後にフック処理差し込み
- clean処理の追加
- _new_ 時に社内プロジェクト共通モジュールの追加
- 社内のモジュール群から任意のモジュールをプロジェクトに対して抜き差しする機能

最後の事例では、Xcodeの _*.xcodeproj/project.pbxproj_の編集をする必要があったので、 _plugin_generate/proj_modifier/modify\_pbxproj.py_ を利用させて頂いています。
([ここ](https://github.com/kronenthaler/mod-pbxproj)から引用したソースなのかな？)
これは _project.pbxproj_ をパースしたり再生成を行うモジュールですが、単体ではカバーしている範囲が狭いので拡張して使っています。


以上です、ありがとうございました。