LoginSignup
14
10

More than 5 years have passed since last update.

Cocos2d-xとFirebaseでゲームランキングを作る #1

Last updated at Posted at 2017-04-09

Cocos2d-xとFirebaseを使って、ゲームランキングを実装してみます。
今回はFirebaseを設定してユーザー登録まで行います。

プロジェクト作成

Cocos2d-xの導入部分は割愛します。
cocosコマンドを使って、新規プロジェクトを作成します。

$ cocos new RankingTest -p jp.co.ienter.RankingTest -l cpp -d RankingTest

Firebase導入

Firebaseのコンソール画面から、プロジェクトの追加を行います。
プロジェクト名を入力して、プロジェクトを作成をクリック。
s100.png

今回はiOSアプリでランキングを作成しますので、iOSを選択しましょう。
s101.png

iOSのバンドルIDを入力しましょう。
Cocos2d-xのプロジェクト作成時に設定したパッケージ名を入力します。
入力後右下のREGISTER APPボタンをクリックしてください。
s102.png

XCodeにFirebase導入に必要なplistファイルをダウンロードします。
ダウンロードしたplistはXCodeプロジェクトに追加しておきましょう。
s103.png

s103_1.png

Firebase設定

Realtime Databaseを誰でも編集出来るように、ルールを変更します。

s107.png

下記のように修正します。
.read.writeをそれぞれtrueにします。
.indexOnという部分は大量にあるデータをindex化して高速にアクセスするためのものです。

{
  "rules": {
    ".read": true,
    ".write": true,
    "score_ranking":{
      ".indexOn": ["score"]
    }
  }
}

s108.png

XCodeに導入する

ターミナルを起動して、Podfileを作成します。

$ cd RankingTest/proj.ios_mac
$ pod init

するとproj.ios_macフォルダにPodfileが生成されます。
viコマンドでPodfileを編集します。

$ vi Podfile

platform :ios, '8.0'
target 'RankingTest-mobile' do
     pod 'Firebase'
     pod 'Firebase/Database'
end

Podfileを編集後、podをインストールしましょう。
この時、開いているXCodeは閉じておきます。

$ pod install

インストールが完了すると、RankingTest.xcworkspaceファイルが作成されます。
これを開いてみましょう。今後はこちらを起動します。
Build SettingsにあるOther Linker Flagsを編集して$(inherited)を追加します。
s110.png

次にLibrary Search Pathsも同様に$(inherited)を追加します。
s111.png

このままビルドするとエラーが出てしまうので、
足りないFrameworkを追加します。
s112.png

GameController.frameworkを追加します。
s113.png

これで一度ビルドを行います。

Firebase初期化

ここからFirebaseに関するプログラムを記述していきます。
iOSフォルダにあるAppController.mmを開きましょう。

s120.png

AppController.mm
// Firebaseをインポート
#import "Firebase.h"

// 省略 //

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // 省略 //

    // Firebase初期化
    [FIRApp configure];

}

ユーザークラス作成

ユーザークラスを作成します。Firebaseフォルダを作成し、FIBUserBridge.hを新規作成しましょう。
s130.png

FIBUserBridge.h
#include "cocos2d.h"

USING_NS_CC;

class FIBUserBridge;

// コールバック用
using FIBUserBridgeCallback = std::function<void (FIBUserBridge *, const std::string &)>;

class FIBUserBridge
{
public:
    std::string userID;         // ユーザーID
    std::string userName;       // ユーザー名
    long long createTM;         // 作成日時

    static FIBUserBridge* userBridge(const void* user);
    static FIBUserBridge* create(const std::string& userID, const std::string& userName);

};

次に、iOSフォルダにFirebaseフォルダを追加し以下の3ファイルを新規作成しましょう。
拡張子は.mmという事に注意してください。

※ .mmファイルになるとC++言語を書くことが出来ます。

  • FIBUserBridge.mm
  • FIBUser.mm
  • FUBUser.h

s131.png

FIBUserBridge.mm
#import "FIBUserBridge.h"
#import "FIBUser.h"

// FIBUserからFIBUserBridgeに変換します
FIBUserBridge* FIBUserBridge::userBridge(const void *user)
{
    FIBUser *aUser = (FIBUser *)user;
    FIBUserBridge* userBridge = new FIBUserBridge();

    userBridge->userID = aUser.userID ? [aUser.userID UTF8String] : "";
    userBridge->userName = aUser.userName ? [aUser.userName UTF8String] : "";
    userBridge->createTM = [aUser.createTM longLongValue];
    return userBridge;
}

FIBUserBridge* FIBUserBridge::create(const std::string &userID, const std::string &userName)
{
    FIBUserBridge* userBridge = new FIBUserBridge();
    userBridge->userID = userID;
    userBridge->userName = userName;
    userBridge->createTM = 0;

    return userBridge;
}
FIBUser.mm
#import "FIBUser.h"

@implementation FIBUser

-(instancetype) initWithUserID:(NSString *)userID
                      userName:(NSString *)userName
                      createTM:(NSString *)createTM
{
    if ((self = [super init])) {
        _userID = [userID copy];
        _userName = [userName copy];
        _createTM = [createTM copy];
    }

    return self;
}

@end
FIBUser.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@class FIBUser;

typedef void (^FIBUserCompletionHandler)(FIBUser *user, NSError *error);

@interface FIBUser : NSObject

@property (nonatomic, readonly) NSString *userID;     // ユーザーID
@property (nonatomic, readonly) NSString *userName;   // ユーザー名
@property (nonatomic, readonly) NSString *createTM;   // 作成日時

-(instancetype) initWithUserID:(NSString *)userID
                      userName:(NSString *)userName
                      createTM:(NSString *)createTM;

@end 

アカウントクラス作成

次にアカウントクラスを作成します。
ClassesフォルダのFirebaseフォルダにFIBAccountBridge.hを作成しましょう。

s132.png

FIBAccountBridge.h
#include "cocos2d.h"
#include "FIBUserBridge.h"

USING_NS_CC;

class FIBAccountBridge
{
public:
    static void loginUser(const std::string& userID, const FIBUserBridgeCallback callback);
    static void signupUser(FIBUserBridge* user, const FIBUserBridgeCallback callback);
    static void updateUser(FIBUserBridge* user, const FIBUserBridgeCallback callback);
};

次にiOSフォルダのFirebaseフォルダに下記を新規作成しましょう。
こちらも拡張子が.mmなので気をつけましょう。

  • FIBAccountBridge.mm
  • FIBAccount.mm
  • FIBAccount.h

s133.png

FIBAccountBridge.mm
#import "FIBAccountBridge.h"
#import "FIBAccount.h"
#import "FIBUser.h"

// ユーザー取得
void FIBAccountBridge::loginUser(const std::string &userID, const FIBUserBridgeCallback callback)
{
    NSString *aUserID = [NSString stringWithCString:userID.c_str()
                                           encoding:[NSString defaultCStringEncoding]];

    [FIBAccount loginUserWithCompletion:aUserID completion:^(FIBUser *user, NSError *error) {
        if (user) {
            FIBUserBridge* userBridge = FIBUserBridge::userBridge(user);
            callback(userBridge, "");
        } else {
            callback(nullptr, "error");
        }
    }];
}

// ユーザー作成
void FIBAccountBridge::signupUser(FIBUserBridge *user, const FIBUserBridgeCallback callback)
{
    NSString* aUserID = [NSString stringWithCString:user->userID.c_str() encoding:[NSString defaultCStringEncoding]];
    NSString* aUserName = [NSString stringWithCString:user->userName.c_str() encoding:NSUTF8StringEncoding];

    FIBUser* aUser = [[FIBUser alloc] initWithUserID:aUserID
                                            userName:aUserName
                                            createTM:0];
    [FIBAccount singupUserWithCompletion:aUser completion:^(FIBUser *user, NSError *error) {
        if (user) {
            FIBUserBridge* userBridge = FIBUserBridge::userBridge(user);
            callback(userBridge, "");

        } else {
            callback(nullptr, "error");

        }
    }];

}

// ユーザー更新
void FIBAccountBridge::updateUser(FIBUserBridge *user, const FIBUserBridgeCallback callback)
{
    NSString* aUserID = [NSString stringWithCString:user->userID.c_str() encoding:[NSString defaultCStringEncoding]];
    NSString* aUserName = [NSString stringWithCString:user->userName.c_str() encoding:NSUTF8StringEncoding];

    FIBUser* aUser = [[FIBUser alloc] initWithUserID:aUserID
                                            userName:aUserName
                                            createTM:0];
    [FIBAccount singupUserWithCompletion:aUser completion:^(FIBUser *user, NSError *error) {
        if (user) {
            FIBUserBridge* userBridge = FIBUserBridge::userBridge(user);
            callback(userBridge, "");

        } else {
            callback(nullptr, "error");

        }
    }];

}
FIBAccount.mm
#import "FIBAccount.h"
#import "Firebase.h"
#import "FIBUser.h"

@implementation FIBAccount

// ログイン処理
+(void) loginUserWithCompletion:(NSString *)userID completion:(FIBUserCompletionHandler)completion
{
    // 対象ユーザーIDの情報を取得する
    FIRDatabaseReference* ref = [[FIRDatabase database] reference].ref;

    [[ref child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot)
    {

        // snapshotに取得したデータが入っている
        NSDictionary *snapshotData = snapshot.value;

        NSString* aUserID = [snapshot key];
        NSString* aUserName = [snapshotData valueForKey:@"name"];
        NSString* aCreateTM = [snapshotData valueForKey:@"createTM"];

        // FIBUserに格納する
        FIBUser* fibUser = [[FIBUser alloc] initWithUserID:aUserID
                                                  userName:aUserName
                                                  createTM:aCreateTM];
        completion(fibUser, nil);
    }];
}

// ユーザー登録処理
+(void) singupUserWithCompletion:(FIBUser *)fibUser completion:(FIBUserCompletionHandler)completion
{
    FIRDatabaseReference* ref = [[FIRDatabase database] reference].ref;
    [[[ref child:@"users"] child:fibUser.userID] setValue:@{@"name": fibUser.userName,
                                                            @"createTM": [FIRServerValue timestamp]}
                                      withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref)
                                      {
                                          if (error != nil) {
                                              completion(nil, error);
                                          } else {
                                              // 登録完了後、Firebaseからデータを取得する
                                              [[[[[FIRDatabase database] reference].ref child:@"users"] child:fibUser.userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
                                                  NSDictionary * data = snapshot.value;
                                                  FIBUser* wkUser = [[FIBUser alloc] initWithUserID:snapshot.key
                                                                                           userName:[data valueForKey:@"name"]
                                                                                           createTM:[data valueForKey:@"createTM"]];
                                                  completion(wkUser, nil);
                                              }];
                                          }
                                      }];
    }

// ユーザー更新
+(void) updateUserWithCompletion:(FIBUser *)fibUser completion:(FIBUserCompletionHandler)completion
{
    FIRDatabaseReference* ref = [[FIRDatabase database] reference].ref;
    [[[ref child:@"users"] child:fibUser.userID] updateChildValues:@{@"name": fibUser.userName}
                                               withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
                                                   if (error != nil) {
                                                       completion(nil, error);
                                                   } else {
                                                       [[[[[FIRDatabase database] reference].ref child:@"users"] child:fibUser.userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
                                                           NSDictionary * data = snapshot.value;
                                                           FIBUser* wkUser = [[FIBUser alloc] initWithUserID:snapshot.key
                                                                                                    userName:[data valueForKey:@"name"]
                                                                                                    createTM:[data valueForKey:@"createTM"]];
                                                           completion(wkUser, nil);
                                                       }];
                                                   }
                                               }];
}
@end
FIBAccount.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "FIBUser.h"

@interface FIBAccount : NSObject

+(void) loginUserWithCompletion:(NSString * )userID
                     completion:(FIBUserCompletionHandler)completion;
+(void) singupUserWithCompletion:(FIBUser *)fibUser
                      completion:(FIBUserCompletionHandler)completion;
+(void) updateUserWithCompletion:(FIBUser *)fibUser
                      completion:(FIBUserCompletionHandler)completion;
@end

GSUtilComクラスを作成する

共通関数を作成しましょう。
ここでは、端末固有のIDを取得する処理を記述します。
このIDがユーザーIDとなります。

s140.png

GSUtilCom.h
#include <string.h>
#include "cocos2d.h"

using namespace std;
using namespace cocos2d;

class GSUtilCom
{
public:
    // ユーザーID
    static std::string getUserID();
};

次にGSUtilCom.mmファイルを新規作成します。

s141.png

GSUtilCom.mm
#import "GSUtilCom.h"

// ユーザーID取得
std::string GSUtilCom::getUserID()
{
    // 端末固有のIDを取得する
    NSString* userID = [[UIDevice currentDevice].identifierForVendor UUIDString];

    return [userID UTF8String];
}

ユーザー登録処理を記述する

実際の処理をHelloWorldScene.cppに記述しましょう

s150.png

HelloWorldScene.cpp
#include "HelloWorldScene.h"
#include "GSUtilCom.h"
#include "FIBUserBridge.h"
#include "FIBAccountBridge.h"

#define WINSIZE Director::getInstance()->getWinSize()
#define WINCENTER Point(WINSIZE.width*0.5, WINSIZE.height*0.5)

Scene* HelloWorld::createScene()
{
    // 'scene' is an autorelease object
    auto scene = Scene::create();

    // 'layer' is an autorelease object
    auto layer = HelloWorld::create();

    // add layer as a child to scene
    scene->addChild(layer);

    // return the scene
    return scene;
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }

    // セットアップ
    this->setup();

    return true;
}

// セットアップ処理
void HelloWorld::setup()
{
    // ユーザー作成
    Sprite* btnCreateUser_ON = Sprite::create();
    btnCreateUser_ON->setTextureRect(Rect(0, 0, 200, 100));
    btnCreateUser_ON->setColor(Color3B::GRAY);

    Sprite* btnCreateUser_OFF = Sprite::create();
    btnCreateUser_OFF->setTextureRect(Rect(0, 0, 200, 100));
    btnCreateUser_OFF->setColor(Color3B(0,0,200));

    MenuItemSprite* btnCreateUser = MenuItemSprite::create(btnCreateUser_OFF,
                                                           btnCreateUser_ON,
                                                           [this](Ref* sender) {this->createUserTapped((MenuItemSprite*)sender);});
    Label* lblCreateUser = Label::createWithSystemFont("ユーザー作成", "arial", 26);
    lblCreateUser->enableOutline(Color4B::BLACK, 1);
    lblCreateUser->setPosition(Vec2(btnCreateUser->getContentSize().width*0.5, btnCreateUser->getContentSize().height*0.5));
    btnCreateUser->addChild(lblCreateUser);

    Menu* menu = Menu::create(btnCreateUser, NULL);
    menu->setPosition(Vec2(WINCENTER.x, WINSIZE.height*0.9));
    this->addChild(menu);
}

// ユーザー作成
void HelloWorld::createUserTapped(cocos2d::MenuItemSprite *menuSpr)
{
    // ユーザーIDを取得
    std::string aUserID = GSUtilCom::getUserID();
    std::string userName = "テスト";
    FIBUserBridge* fibUser = FIBUserBridge::create(aUserID, userName);

    // ユーザー登録処理
    FIBAccountBridge::signupUser(fibUser, [=](FIBUserBridge* user, std::string error)
    {
        if (error.length()) {
            log("登録失敗=%s", error.c_str());
        } else {
            if (user) {
                log("登録しました!");
                log("id=%s", user->userID.c_str());
                log("name=%s", user->userName.c_str());
                log("createTM=%lld", user->createTM);

                // データ保持
                _userID = user->userID;
            }
        }
    });

}
HelloWorldScene.h
#include "cocos2d.h"

USING_NS_CC;

class HelloWorld : public cocos2d::Layer
{
public:
    static cocos2d::Scene* createScene();

    virtual bool init();

    // implement the "static create()" method manually
    CREATE_FUNC(HelloWorld);

private:
    // ユーザーID保持
    CC_SYNTHESIZE(std::string, _userID, UserID);

    // セットアップ
    void setup();

    // ユーザー作成ボタンタップ処理
    void createUserTapped(MenuItemSprite* menuSpr);
};

これでビルドして実行すると、下記のような画面が表示されます。
ユーザー登録ボタンをタップすると、Firebase上にユーザーが書き込まれます。

s160.png

s161.png

次回はスコア送信処理を実装します。

14
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
10