[Cocos2d-x]ゲームを起動したまま画像やレベルを編集して素早くプレビューする

ゲームを開発していると、編集した画像を実機ですぐに確認したいという状況がよくあります。グラフィックやレベルなど、直接ソースコードを触らないデザイナの人でも簡単にビルドできる環境が整っていれば良いのですが、OSやSDKなどの準備が大変で、結局誰かにデータを渡してビルドしてもらってその結果を確認というサイクルになってしまいがちです。

「あと2px右にずらしたい」とかでわざわざエンジニアを呼ぶのは、呼ぶ方も呼ばれる方もお互いに大変です。また仮にビルドできる環境があったとしても、clean直後などでビルド自体に時間がかかると編集/確認のサイクルが素早く回せません。

* 問題意識
 * ゲーム画面を見ながら画像やレイアウトの調整ができない
 * ソースコードを落としてきたり署名の設定をしたり、一からビルドするのは大変
 * ビルドできたとしても、ビルドに時間がかかるのは面倒
* やりたいこと
 * 手元で編集したデータを、ユーザが体験するのになるべく近い状況で素早くプレビューしたい
 * ソースからビルドとかはしたくない

↓こんな感じで動いてるとこをすぐ見たい
![ss.png](https://qiita-image-store.s3.amazonaws.com/0/2490/2829a70f-5ac0-890b-e2ac-0a0fd91ab864.png)

cocos2d-xのlua templateにはこれらを解決する仕組みが標準で備わっています。ゲームをdebugビルドするとゲーム内でサーバが立ち上がり、ネットワーク越しに各種コマンドが送られてくるのを待機します。

↓このあたり

```c++:AppDelegate.cpp
bool AppDelegate::applicationDidFinishLaunching()
{
    // ...

#ifdef COCOS2D_DEBUG
    if (startRuntime())
        return true;
#endif

    // ...
}
```
https://github.com/cocos2d/cocos2d-x/blob/cocos2d-x-3.0/templates/lua-template-runtime/frameworks/runtime-src/Classes/AppDelegate.cpp#L40

```cpp:Runtime.cpp
    ConsoleCustomCommand():_fileserver(nullptr)
    {
        cocos2d::Console *_console = Director::getInstance()->getConsole();
        // ...
        _console->listenOnTCP(6010);
        
        _fileserver=new FileServer();
        _fileserver->listenOnTCP(6020);
    }
```
https://github.com/cocos2d/cocos2d-x/blob/cocos2d-x-3.0/templates/lua-template-runtime/frameworks/runtime-src/Classes/Runtime.cpp#L598

Cocos Code IDEでもこの機能が使われており、スクリプトの編集 => プレビューが素早く行えます。
http://www.cocos2d-x.org/wiki/How_to_Debug_Cocos2d-x_Lua_Game_Using_Code_IDE#Code-Hot-Updating

ただし、Cocos Code IDEではスクリプトファイルの保存しかトリガにならないため、画像やレベルデータなどの更新もトリガにするには別途監視が必要です。リソースディレクトリ以下を監視し、更新されたものをゲームに転送してリロードするスクリプトはこんな感じです。

```py:watch_resource.py
import os
import time
from socket import *
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer

CMD_PORT = 6010
FILE_PORT = 6020
BUFFER = 1024
HOST = '0.0.0.0' # change if other device
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
RESOURCE_DIR = BASE_DIR + '/res'

class ResourceHandler(FileSystemEventHandler):
    def on_created(self, event):
        on_resource(event)
    def on_modified(self, event):
        on_resource(event)

def on_resource(event):
    if event.is_directory:
        return
    upload(event.src_path)
    reload()

def upload(src_path):
    filename = os.path.relpath(src_path, BASE_DIR)
    mtime = os.path.getmtime(src_path)
    json = '{"filename":"/%s", "lastmodifytime":"%d"}' % (filename, mtime)
    soc = socket(AF_INET, SOCK_STREAM)
    soc.connect((HOST, FILE_PORT))
    soc.send('%03d %s' % (len(json), json))
    fp = open(src_path, 'rb')
    data = fp.read(BUFFER)
    while (data):
        soc.send(data)
        data = fp.read(BUFFER)
    fp.close()
    soc.close()

def reload():
    soc = socket(AF_INET, SOCK_STREAM)
    soc.connect((HOST, CMD_PORT))
    soc.send('sendrequest {"cmd":"reload","modulefiles":[]}\n')
    soc.send('exit\n')
    soc.close()

if __name__ == "__main__":
    obs = Observer()
    obs.schedule(ResourceHandler(), RESOURCE_DIR, recursive=True)
    obs.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        obs.stop()
    obs.join()
```

同ディレクトリのres以下を監視して、更新があればゲームに送信します。ディレクトリの監視には[watchdog](https://pythonhosted.org/watchdog/index.html)を使っています。

あとはリソースディレクトリとビルド済みのパッケージを共有するようにすれば、ソースコードからビルドする環境が整っていない人でも自由に画像やレベルを編集できるようになります。

cocos2d::Consoleは標準で色々とコマンドを持っており拡張も容易なので、他にも便利なツールが作れるかもしれません。
https://github.com/cocos2d/cocos2d-x/blob/cocos2d-x-3.0/cocos/base/CCConsole.cpp#L266

## リンク

* [[Cocos2d-x]10分でできるCocos Code IDEの導入手順[iOS][Android]](http://dev.classmethod.jp/smartphone/cocos_code_ide/)
* [#20 まさか、Cocos2d-x 使っているのに C++ 書いてるわけないよね？](http://tech.kayac.com/archive/20_snmp.html)