読者です 読者をやめる 読者になる 読者になる

いものやま。

雑多な知識の寄せ集め

GameCenterのLeaderboardに対応させる方法について。

SoloXmasでは、GameCenterのLeaderboardにも対応させた。

今日はその方法について。

iTunesConnectでLeaderboardを追加

まずは、iTunesConnectでLeaderboardの項目を追加する。

iTunesConnectでアプリの情報を入力できるようにした後、

「iTunesConnect」-「マイ App」-「(アプリ名)」-「機能」-「GameCenter」で、Leaderboardを追加する。
このとき、「シングル」と「グループ」を選択できるけど、基本的には「シングル」でOK。
(「グループ」は複数のアプリで1つのLeaderboardを共有して使う場合に使うみたい)

新規のLeaderboardを作成したら、設定をしていく。

  • Leaderboardの参照名
    iTunesConnectで表示される名前
  • Leaderboard ID
    プログラムから参照するときに使うID
  • スコアのフォーマットタイプ
  • スコア送信タイプ
    ベストスコアを送信するか、最新スコアを送信するか
  • 並び替えの順序
    昇順(数が小さい方が順位がいい)か降順(数が大きい方が順位がいい)か
  • スコアの範囲(オプション)
    スコアの最小値と最大値を指定することが出来る

そして、Leaderboardをどうやって表示するかのローカリゼーションを追加する。
ここに追加した設定にしたがって、Leaderboardの名前やスコアは表示されることになる。

アプリでGameCenterを有効にする

まだiTunesConnectでの作業。

「iTunesConnect」-「マイ App」-「(アプリ名)」-「(バージョン情報)」で、GameCenterを有効にする。
さらに、Leaderboardで、さっき作成したものを追加する。

ここまで出来たら、iTunesConnectでの作業は完了。

次はXcodeでの作業。

プロジェクトを開いたらTARGETSでアプリを選択して、「Capabilities」でGameCenterを有効に。
こうすると、GameCenterが使えるようになる。
(GameKitフレームワークのリンクなどもやってくれる)

GameKitManager

今回はGameKitの機能を簡単に使えるようにするために、GameKitManagerというクラスを作ってみた。
正直、こういうデザインをするとマネージャだらけになるので、あまりいいデザインではないのだけど(^^;

//==============================
// SoloXmas
//------------------------------
// GameKitManager.swift
//==============================

import GameKit

class GameKitManager: NSObject, GKGameCenterControllerDelegate {
  // 続く

なお、GKGameControllerDelegateプロトコルに準拠するようにしているのは、Leaderboardの表示を行ったときに、コンプリーションハンドラでその表示を消す必要があるから。

クラス定数とクラスメソッド

まずはクラス定数とクラスメソッドから。

  // 続き

  private static let leaderboardID = "com.ouka_do.SoloXmas.BestScore"
  
  private static let manager = GameKitManager()
  
  class func getInstance() -> GameKitManager {
    return GameKitManager.manager
  }

  // 続く

クラス定数として、作成したLeaderboardのIDを参照できるようにしている。
ここではprivateにしてるけど、複数のLeaderboardを作成した場合、参照は可能にした方がいいかも。

それと、シングルトンにしている。

プロパティとイニシャライザ

次にプロパティとイニシャライザ。

  // 続き
  
  private(set) var localPlayer: GKLocalPlayer
  
  private weak var rootViewController: UIViewController!
  
  override private init() {
    self.localPlayer = GKLocalPlayer.localPlayer()
    self.rootViewController = nil
    super.init()
  }

  // 続く

プロパティとして、GameCenterでのローカルプレイヤーの情報を持つGKLocalPlayerのインスタンスと、ルートビューコントローラへの参照を持てるようにしている。

ただ、GKLocalPlayerのインスタンスについては、不要かも。
ここらへん、GameKitフレームワークの設計がよく分かってない・・・

ルートビューコントローラへの参照は、Leaderboardを表示したあとに、その表示したLeaderboardを閉じることが出来るようにするためのもの。
こっちも実装のための実装で、あんまりキレイじゃない・・・

ログイン

そして、ログインの処理。

  // 続き
  
  func login(rootViewController: UIViewController) {
    self.localPlayer = GKLocalPlayer.localPlayer()
    self.localPlayer.authenticateHandler = {(loginViewController, error) in
      if loginViewController != nil {
        rootViewController.presentViewController(loginViewController!, animated: true, completion: nil)
      }
    }
  }

  // 続く

ログインの処理は、GKLocalPlayer#authenticateHandlerを設定することで行われるみたい。
正直、かなりヘンテコな設計だと思うのだけど・・・まぁ、そうしろというのだから、おとなしく従っておく。

なお、これはAppDelegateから呼び出すことになる(後述)。

スコアの送信

次はスコアの送信。

  // 続き
  
  func reportScore(point: Int) {
    let score = GKScore(leaderboardIdentifier: GameKitManager.leaderboardID)
    score.value = Int64(point)
    GKScore.reportScores([score], withCompletionHandler: nil)
  }

  // 続く

LeaderboardのIDを指定してGKScoreのインスタンスを作成し、データをセットしたあと、送信するだけ。

ちょっと気をつけないといけないのが、GKScore.reportScores(_: [GKScore], withCompletionHandler: ((NSError?) -> Void)?)の第一引数は配列だということ。
なので、配列を作って引数として渡している。

Leaderboardの表示

最後にLeaderboardの表示。

  // 続き
  
  func showLeaderboard(rootViewController: UIViewController) {
    let gameCenterViewController = GKGameCenterViewController()
    gameCenterViewController.leaderboardIdentifier = GameKitManager.leaderboardID
    gameCenterViewController.gameCenterDelegate = self
    self.rootViewController = rootViewController  // to dismiss gameCenterViewController
    rootViewController.presentViewController(gameCenterViewController, animated: true, completion: nil)
  }
  
  func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController) {
    if self.rootViewController != nil {
      self.rootViewController.dismissViewControllerAnimated(true, completion: nil)
      self.rootViewController = nil
    }
  }
}

Leaderboardの表示には、GKGameCenterViewControllerを使うといい。
(ちなみに、GKLeaderboardViewControllerというのもあるけど、GKGameCenterViewControllerが使えるなら、GKGameCenterViewControllerを使うべき、とドキュメントに書いてある)

GKGameCenterViewControllerのインスタンスを作成したら、いろいろ設定したあと、ルートビューコントローラに表示させる。

ここでちょっと注意しないといけないのが、GKGameCenterViewControllerはビューの「完了」ボタンを押されても、自分で勝手に消えてはくれないということ。
(SocialフレームワークのSLComposeViewControllerは、投稿すると勝手に消えるのに・・・)
なので、「完了」ボタンが押されたときのデリゲートの中で自前で消してやらないといけない。
そこで、GKGameCenterViewControllerを表示させているビューコントローラ(今回の場合、ルートビューコントローラ)への参照を保持しておいて、GKGameCenterViewControllerを消すようにする。
このあたり、もうちょっとマシな実装が出来るといいんだけど・・・

AppDelegateの修正

あとは、GameKitManagerの各メソッドを適切なところで呼び出すだけ。

スコアの送信やLeaderboardの表示は、適切なタイミングで呼び出せばいいだけなので省略するけど、ちょっとメンドウなのがログイン。
これは、AppDelegateの中でイベントに合わせて呼び出すようにする。

//==============================
// SoloXmas
//------------------------------
// AppDelegate.swift
//==============================

import UIKit

@UIApplicationMain
class AppDelegate: NSObject, UIApplicationDelegate {
  var window: UIWindow?
  
  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
    if let rootViewController = self.window?.rootViewController {
      GameKitManager.getInstance().login(rootViewController)
    }
    return true
  }
  
  // 省略
}

これでOK。
アプリを起動するとログインを行って、もしログインされていないようなら認証の画面が表示されるようになる。

今日はここまで!