Cocos2d-xのiOS部分でSwiftを使いたい


# はじめに
cocosコマンドで生成されるデフォルトのCocos2d-xのプロジェクトでは、
現状Objective-Cが使われていますが、Swiftは使えないのか？と思い、試してみました。

まぁ最初にまとめておくと、SwiftからObjective-C++を呼ぶようにすれば、
それらしいことは可能でした。

概要としては
1. アプリ起動時にAppController.swiftが呼ばれるようにする
2. Objective-C++のBridge Fileを用意する
3. AppController.swiftで(2)にて作ったBridge Fileを呼ぶ
です。

※Cocos2d-x ver3.8.1にて行いました。

# 1. アプリ起動時にAppController.swiftが呼ばれるようにする
デフォルトのプロジェクトだと、アプリ起動時にAppController.mmが呼ばれますが、
それを新規に作成するAppController.swiftが呼ばれるようにします。

まず、main.mファイルを削除してしまいます。
こいつが（ざっくり言うと）内部でAppController.mmを呼んでいるためです。
![スクリーンショット 2015-10-20 22.57.29.png](https://qiita-image-store.s3.amazonaws.com/0/995/9d45ce49-c108-43f8-de83-cdb773668e8b.png)

次にAppController.swiftを作成します。
（Xcodeでシングルビューアプリケーションを作成した際に生成されるAppDelegate.swiftをコピればいいと思います）
ポイントは`@UIApplicationMain`です。
これが付くことでAppController.swiftがアプリ起動時に読み込まれるようになります。

```swift:AppController.swift

import UIKit

@UIApplicationMain
class AppController: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        return true
    }

    func applicationWillResignActive(application: UIApplication) {}

    func applicationDidEnterBackground(application: UIApplication) {}

    func applicationWillEnterForeground(application: UIApplication) {}

    func applicationDidBecomeActive(application: UIApplication) {}

    func applicationWillTerminate(application: UIApplication) {}
}
```
現状ディレクトリはこんな感じ↓
![スクリーンショット 2015-10-20 23.19.34.png](https://qiita-image-store.s3.amazonaws.com/0/995/88af066c-eaeb-5836-f9e7-89b78a68a821.png)

# 2. Objective-C++のBridge Fileを用意する
肝となる部分。
今までAppController.mmでcocosの初期化などを行っていたが、それをAppController.swiftから呼ぶために、
Bridge Fileを作成し、Swift → Objective-C++ と連携させる。

NativeBridge.hとNativeBridge.mmを作成します。（この名前は良くないかな…）
NativeBridge.mmに元々AppController.mmで呼んでいたcocosの処理を書きます。（ちょっとだけリファクタリングしてます）

```objc++:NativeBridge.h

#import <Foundation/Foundation.h>

@interface NativeBridge : NSObject

+ (void) applicationDidFinishLaunchingWithOptions;
+ (void) applicationWillEnterForeground;
+ (void) applicationDidEnterBackground;

@end
```

```objc++:NativeBridge.mm

#import "NativeBridge.h"
#import "cocos2d.h"
#import "platform/ios/CCEAGLView-ios.h"
#import "RootViewController.h"

@implementation NativeBridge


+ (void) applicationDidFinishLaunchingWithOptions {
    cocos2d::Application *app = cocos2d::Application::getInstance();
    app->initGLContextAttrs();
    cocos2d::GLViewImpl::convertAttrs();
    
    // Override point for customization after application launch.
    
    // Add the view controller's view to the window and display.
    UIWindow *window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
    
    // Init the CCEAGLView
    CCEAGLView *eaglView = [CCEAGLView viewWithFrame: [window bounds]
                                         pixelFormat: (NSString*)cocos2d::GLViewImpl::_pixelFormat
                                         depthFormat: cocos2d::GLViewImpl::_depthFormat
                                  preserveBackbuffer: NO
                                          sharegroup: nil
                                       multiSampling: NO
                                     numberOfSamples: 0 ];
    
    // Enable or disable multiple touches
    [eaglView setMultipleTouchEnabled:NO];
    
    // Use RootViewController manage CCEAGLView
    RootViewController *viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];
    viewController.view = eaglView;
    
    [window setRootViewController:viewController];
    
    [window makeKeyAndVisible];
    
    [[UIApplication sharedApplication] setStatusBarHidden:true];
    
    // IMPORTANT: Setting the GLView should be done after creating the RootViewController
    cocos2d::GLView *glview = cocos2d::GLViewImpl::createWithEAGLView(eaglView);
    cocos2d::Director::getInstance()->setOpenGLView(glview);
    
    app->run();
}

+ (void) applicationWillEnterForeground {
    cocos2d::Application::getInstance()->applicationWillEnterForeground();
}

+ (void) applicationDidEnterBackground {
    cocos2d::Application::getInstance()->applicationDidEnterBackground();
}

@end

```

次に`Build Settings > Objective-C Bridging Header`に
`$(SRCROOT)/ios/NativeBridge.h`みたいな感じで、上記で作成したNativeBridge.hが読み込まれるようにします。
これをすることでswiftファイルからNativeBridgeのメソッドなどが呼び出せるようになります。

現在のディレクトリ構造↓
![スクリーンショット 2015-10-20 23.38.24.png](https://qiita-image-store.s3.amazonaws.com/0/995/ddec149b-cc15-b069-1a14-c85595391aed.png)

# 3. AppController.swiftで2にて作ったBridge Fileを呼ぶ
最後に、AppController.swiftを下記のようにします。
(2)で作ったNativeBridgeのクラスメソッドを呼んでいます。
これで作業は終了です。
実行すればいつもの見慣れたcocosの~~変な~~マークのスクリーンが出てくるはず。

```swift:AppController.swift

import UIKit

@UIApplicationMain
class AppController: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        NativeBridge.applicationDidFinishLaunchingWithOptions()
        return true
    }

    func applicationWillResignActive(application: UIApplication) {}

    func applicationDidEnterBackground(application: UIApplication) {
        NativeBridge.applicationDidEnterBackground()
    }

    func applicationWillEnterForeground(application: UIApplication) {
        NativeBridge.applicationWillEnterForeground()
    }

    func applicationDidBecomeActive(application: UIApplication) {}

    func applicationWillTerminate(application: UIApplication) {}
}
```

以上…！

# おわりに
これでcocosの開発でもswift使える！とか思ったのですが、一旦Bridge Fileを作らなきゃいけないのはちょっと面倒…
まぁでもAndroidの場合もjni経由じゃなきゃいけないし、ある意味同じ形になるのかな。
とりあえずswiftが使えるということだけで、嬉しい気がする。

（何か間違いがある場合、指摘して頂ければ幸いです。）