はじめに

備忘録を兼ねてます。

Swiftでゲームを作るとなるとUIView(xib)を駆使するか、ScenekKitを使うか直接OpenGL ESを触るしかない。
UIView(xib)を使ってゲームを作る場合は動きが激しいゲームは作れないので、本格的にゲームを作るとなるとSceneKitもしくはOpenGL ESを使うしかない。

SceneKitはあまり使ったことがないがTiledなどのツールがサポートされていない印象だ。
OpenGL ESを生で触るには、かなりの技術力とコーディング力が必要になる。
ということで今回はSpriteBuilderを使う方法について書く。

※3Dゲームを作るならUnityだと思います。
※本記事は自分が使う限り更新し続ける予定です。(同じ路線でゲームを作りたい方は是非共有お願いいたします♪)

SpriteBuilderとは?

SpriteBuilderはCocos2dのView部分を担当するUIツールだ。
作られたファイルをCocos2dで読み込むことによってプログラムを簡易化する。
xCodeで言うxibの部分だ。

cocos2dOpenGL ESを簡単に操作できるプログラムだ。
このツールを使うことでSwiftでOpenGL ESを簡易的に使うことができる。

デメリット

SpriteBuilderには重大な欠点がある。
なので、先にデメリットを伝えておく。

SpriteBuilderはすでに開発が終わっており、AppStoreなどでも配布されていない。
なのでGitHubなどからプログラムを持ってきてインストールする必要がある。
また、幾つかのバグがそのままになっているのだ。
(あと、基本的にはiOSアプリしか作れない。)

正気の人間ならこのツールを使う選択肢は生まれないと思う。
しかしSwiftで2Dゲームを作ろうとした場合にほとんど方法が残されていないため、このツールを使わざるをえない状況なのだ。

考察

メリットの前にもう一つ考察しておく。
もしこれを読んでいる読者がUnityを良しとしているならこれ以上記事を読む必要はないだろう。
Cocos2d-xのようなC++で書ける強者も同様だ。
Cocos Creatorにチャレンジしている方も不要だろう。

しかしいずれの方法もSwiftではプログラムを書くことができない。
僕のようにSwiftでゲームを作りたいという方は今回紹介する方法を検討してみてはいかがだろうか?

メリット

SpriteBuilderを使った1番のメリットはSwiftOpenGL ESのプログラミングが簡単にできるところだ。
多分現時点で一番使いやすい言語はSwiftだと言っても良い言語を使ってプログラミングができるのは大きな魅力だろう。

UnityCocos Creatorなどを使った場合はネイティブ部分を触れないため、課金処理やスレッド処理などの込み入ったことをする時にハードルが上がる。しかし、ネイティブであるSwiftならその辺でつまづくことはあまりない。
なので、開発効率は圧倒的に高い。

また、幸いにもSpriteBuilderが出力するCocos2d V3Objective-cが大半なのでSwiftのバージョンが上がってもあまり影響を受けない。
そのため、しばらくは使い続けれるだろう。

オープンソースなのでプログラムを変更できるのも大きな特色だ。

やってみよう

何はともあれ、Swiftでゲームを作りたい人は乗っかってみよう。
わからないことなどがあればコメントに書いてくれればわかる範囲で回答するので。

以下、注意点をまとめます。

インストール

昔はAppStoreで配布されていたので、そのままインストールできます。
今はGitHubに書かれている方法でインストールできます。(筆者はAppStoreで入れたので実践はしてませんが・・・)

起動後にPublishするまで

起動時に言語を選べるのでSwiftを選びます。
Objective-cを選んでもMainSceneObjective-cで表示されるだけなので、どちらでもいいです。)

File -> Project setting..でAndroidを外します。
PublishするとxCodeのプロジェクトが作られるので、そこからAndroidMacなどの不要なフォルダやファイルは消しましょう。

Published-Androidなども不要です。ガシガシ消しちゃいましょう。
(Androidのチェックを外しても、なぜか始めの一回目だけは作られてしまいます。)

言語コードに関して

iOS9から言語コードの扱いが変わっているため以下の対応を行う。

Source/libs/cocos2d-iphone/cocos2d-ui/CCBReader/CCBLocalizationManager.m
- (void) loadStringsFile:(NSString*) file
{
    // Load default localization dictionary
    NSString* path = [[CCFileUtils sharedFileUtils] fullPathForFilename:file];

    // Load strings file
    NSDictionary* ser = [NSDictionary dictionaryWithContentsOfFile:path];

    // Check that format of file is correct
    NSAssert([[ser objectForKey:@"fileType"] isEqualToString:@"SpriteBuilderTranslations"], @"Invalid file format for SpriteBuilder localizations");

    // Check that file version is correct
    NSAssert([[ser objectForKey:@"fileVersion"] intValue] == 1, @"Translation file version is incompatible with this reader");

    // Load available languages
    NSArray* languages = [ser objectForKey:@"activeLanguages"];

    // Determine which language to use
    NSString* userLanguage = NULL;

    NSArray* preferredLangs = [NSLocale preferredLanguages];
    for (NSString* preferredLang in preferredLangs)
    {
        if ([languages containsObject:preferredLang])
        {
            userLanguage = preferredLang;
            break;
        }
    }

    // Create dictionary for translations
    _translations = [[NSMutableDictionary alloc] init];

    // Load translations
    if (userLanguage != NULL)
    {
        NSArray* translations = [ser objectForKey:@"translations"];

        for (NSDictionary* translation in translations)
        {
            NSString* key = [translation objectForKey:@"key"];
            NSString* value = [(NSDictionary*)[translation objectForKey:@"translations"] objectForKey:userLanguage];

            if (key != NULL && value != NULL)
            {
                [_translations setObject:value forKey:key];
            }
        }
    }
}

このメソッドを以下のように変更します。

Source/libs/cocos2d-iphone/cocos2d-ui/CCBReader/CCBLocalizationManager.m
- (void) loadStringsFile:(NSString*) file
{
    // Load default localization dictionary
    NSString* path = [[CCFileUtils sharedFileUtils] fullPathForFilename:file];

    // Load strings file
    NSDictionary* ser = [NSDictionary dictionaryWithContentsOfFile:path];

    // Check that format of file is correct
    NSAssert([[ser objectForKey:@"fileType"] isEqualToString:@"SpriteBuilderTranslations"], @"Invalid file format for SpriteBuilder localizations");

    // Check that file version is correct
    NSAssert([[ser objectForKey:@"fileVersion"] intValue] == 1, @"Translation file version is incompatible with this reader");

    // Load available languages
    NSArray* languages = [ser objectForKey:@"activeLanguages"];

    // Determine which language to use
    NSString* userLanguage = NULL;

    NSArray* preferredLangs = [NSLocale preferredLanguages];
    for (NSString* preferredLang in preferredLangs)
    {
        // now loop thru languages from our cocosbuilder
        for (NSString *localizedLanguage in languages)
        {
            // doing range of string as we might have en-GB set in our phone and that will match our en from the activeLanguages
            if ([preferredLang rangeOfString:localizedLanguage].location != NSNotFound)
            {
                userLanguage = localizedLanguage;
                break;
            }
        }

        if (userLanguage != NULL) {
            break;
        }
    }

    // Create dictionary for translations
    _translations = [[NSMutableDictionary alloc] init];

    // Load translations
    if (userLanguage != NULL)
    {
        NSArray* translations = [ser objectForKey:@"translations"];

        for (NSDictionary* translation in translations)
        {
            NSString* key = [translation objectForKey:@"key"];
            NSString* value = [(NSDictionary*)[translation objectForKey:@"translations"] objectForKey:userLanguage];

            if (key != NULL && value != NULL)
            {
                [_translations setObject:value forKey:key];
            }
        }
    }
}

画面遷移

iOS9・・・というか、Capitanから以下のコードが落ちるようになりました。
(xCodeのバージョンのせいかもしれない。とにかく最新だと落ちます。)

Source/libs/cocos2d-iphone/cocos2d/CCTransition.m
-(void)draw:(CCRenderer *)renderer transform:(const GLKMatrix4 *)transform
{
    typedef id (*Func)(id, SEL);
    ((Func)objc_msgSend)(self, _drawSelector);

}

この箇所は以下のように書き換えると動きます。

Source/libs/cocos2d-iphone/cocos2d/CCTransition.m
-(void)draw:(CCRenderer *)renderer transform:(const GLKMatrix4 *)transform
{
    void (*Func)(id, SEL) = (void(*)(id, SEL)) objc_msgSend;
    Func(self, _drawSelector);
}

iOS10から

バージョンの識別が正しくできていないので修正。
以下のコードに置き換える。(ver1と間違えて認識されてしまう。)

Source/libs/cocos2d.xcodeproj/external/ObjectAL.xcodeproj/ObjectAL/Support/IOSVersion.m
- (id) init
{
    if(nil != (self = [super init]))
    {
#if __CC_PLATFORM_IOS
        NSString* versionStr = [[UIDevice currentDevice] systemVersion];
        unichar ch = [versionStr characterAtIndex:0];

        version = (float)(ch - '0');
        if(ch < '0' || ch > '9' || [versionStr characterAtIndex:1] != '.')
        {
//          NSLog(@"Error: %s: Cannot parse iOS version string \"%@\"", __PRETTY_FUNCTION__, versionStr);
            // Version 10以上
            version = (float)(ch - '0') * 10 + (float)([versionStr characterAtIndex:1] - '0');
        }

        float multiplier = 0.1f;
        NSUInteger vLength = [versionStr length];
        for(NSUInteger i = 2; i < vLength; i++)
        {
            ch = [versionStr characterAtIndex:i];
            if(ch >= '0' && ch <= '9')
            {
                version += (ch - '0') * multiplier;
                multiplier /= 10;
            }
            else if('.' != ch)
            {
                break;
            }
        }
#else
        version = 5;
#endif
    }
    return self;
}

既存コードを踏襲して直したが、上記のコードはもう少し良い方法があるきがする・・・。

Images.xcassetsについて

Images.xcassetsCocos2dのプロジェクトでも使われています。
なので、アイコンやスプラッシュなどを設定する際には注意して設定してください。

Cocos2dプロジェクトのImages.xcassetsを誤って選ばないようにしてください。
(邪魔ら消しても良いと思います。)

大量ワーニングについて

ほっといても良いのですが、とにかくワーニングがひたすら出ます。
以下のワーニングが30個くらい出てきます。

ld: warning: object file (/.../Build/Products/Debug-iphonesimulator/libcocos2d.a(IOSVersion.o)) was built for newer iOS version (9.3) than being linked (8.0)

これはこちらのリンクの通りで、以下の対応をすればワーニングが出なくなります。

The culprit is the minimum deployment target for the ObjectAL library - 
In Xcode delve into the cocos2d project -> external -> ObjectAL project: 
Goto build settings and search for 'deployment target' 
and set the value to be <= your main project minimum deployment target.

上記の通りで

「cocos2d project -> external -> ObjectAL」のフォルダをたどってから「build settings」を表示します。
その中の「deployment target」を自分のプロジェクトに合わせた最小ターゲットにすればOKです。

他にも出ますが、上記を削れば自分のプロジェクト内部のワーニングと切り分けれるくらい減ります。

SpriteBuilderのプロジェクトの保存について

プロジェクトはCtrl+Sで保存できるのですが、、、ここにもバグがあって画像のサイズ(1xや2xなど)は保存されません。
なので、通常は2xで作っていてイレギュラーに4xなどを使うと保存されずに次回開く時に2xで表示されてしまいます。

これは致命的なバグですが、回避策があります。
それはclose projectを行うことです。この際に画像サイズが保存されるようなので×で消すのではなく、close projectを行ってから閉じてください。

SpriteBuilderの使い方について

途中までですがこちらに書いてます。
他にも探せばありますので、見てみると良いでしょう。

思っているよりも簡単にゲームを作れるので驚くでしょう。
(こんな名器が世に埋もれてしまうなんて・・・)

実績について

こちらにゲームを作った事について書いてます。
よければ参考にしてください。

ついでに、ゲームで遊んでください。
勇者の冒険
-itunesが開きます-