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を呼んでいるためです。

次にAppController.swiftを作成します。
(Xcodeでシングルビューアプリケーションを作成した際に生成されるAppDelegate.swiftをコピればいいと思います)
ポイントは@UIApplicationMainです。
これが付くことでAppController.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) {}
}

現状ディレクトリはこんな感じ↓

2. Objective-C++のBridge Fileを用意する

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

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

NativeBridge.h

#import <Foundation/Foundation.h>

@interface NativeBridge : NSObject

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

@end
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のメソッドなどが呼び出せるようになります。

現在のディレクトリ構造↓

3. AppController.swiftで2にて作ったBridge Fileを呼ぶ

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

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が使えるということだけで、嬉しい気がする。

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