Google Play Game Servicesをとりあえず使ってみる（実装編）


[前記事](http://qiita.com/yamacraft/items/105957b1b8609ac46277)の続きです。

## 下準備

必要なライブラリは１つだけです。

```gradle:app/build.gradle
dependencies {
    compile 'com.google.android.gms:play-services-games:8.4.0'
}

```

## GoogleApiClientの接続設定

Google Play Game Services（以下、ゲームサービス）へのサインインや実績、リーダーボードの送信などは`GoogleApiClient`を使って行われます。

下記のソースコードは、実際にズンドコキヨシゲーム（以下、ズンゲー）での初期化からサインイン周りの実装内容になります。

本来は必要なタイミングで接続等をすべきでしょうが、とりあえず実装だけしてみたかったのでアプリ起動時（正しくはフォアグラウンド移行時）に強制サインインする仕組みにしています。


```java:MainActivity.java

public class MainActivity extends AppCompatActivity
        implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

    private GoogleApiClient mGoogleApiClient;
    private boolean mIntentInProgress;
            
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 初期設定
        // 各種リスナー登録とGoogleAPIで利用するAPIやスコープの設定
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(Games.API).addScope(Games.SCOPE_GAMES)
                .build();
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 今回は画面が表示されるたびに必ず接続させる
        mGoogleApiClient.connect();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 100) {
            mIntentInProgress = false;
            if (resultCode != RESULT_OK) {
                // エラーの場合、resultCodeにGamesActivityResultCodes内の値が入っている
                return;
            }
            if (!mGoogleApiClient.isConnected()) {
                mGoogleApiClient.reconnect();
            }
        }
    }

    @Override
    public void onConnected(Bundle bundle) {
        // プレイヤー情報取得
        Player p = Games.Players.getCurrentPlayer(mGoogleApiClient);
        
        // 試しにプレイヤー名を表示
        String displayName = "???";
        if (p != null) {
            displayName = p.getDisplayName();
        }
        Toast.makeText(this, String.format("%s でログインしています。", displayName), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onConnectionSuspended(int i) {
        mGoogleApiClient.connect();
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        int errorCode = connectionResult.getErrorCode();
        
        // サインインしていない場合、サインイン処理を実行する
        if (errorCode == ConnectionResult.SIGN_IN_REQUIRED
                && !mIntentInProgress && connectionResult.hasResolution()) {
            try {
                mIntentInProgress = true;
                connectionResult.startResolutionForResult(this, 100);
            } catch (IntentSender.SendIntentException e) {
                mIntentInProgress = false;
                mGoogleApiClient.connect();
            }
        }
    }
}
```

サインイン部分は（おそらく）Playゲームアプリを通しているため、`onActivityResult()`にて結果を見る必要があります。

さらにちょっとややこしい部分だと思いますが、非サインイン時は`.connect()`を実行した際に`onConnectionFailed()`が呼ばれるので、その中の`ErrorCode`を見て適宜サインインの処理を呼び出す必要が出てきます。
この辺りは課金関連のやりとりに近いところもあるので、その辺を実装した経験のある人には見慣れた内容かもしれません。

なお、`.isConnected()`を呼び出すことで、Google APIに接続できているか確認することができます。というか、できない状態でGoogleApiClientを利用すると容赦なくクラッシュするので（※）、以降の実績やリーダーボードの送信時にも必ず接続状況を見るようにしましょう。

※ この事は公開後に知ったので、ズンドコキヨシゲームv1.0.1は非ログイン状態で動かすと容赦なくアプリが落ちます！


## 各種実装

実績やリーダーボードへの情報送信には、各アイテムのIDが必要になります。
これらはGoogle Play Developer Consoleで確認することができますし、一括してリソース化したテキストデータも表示してくれます。

![GooglePlayGameService004.png](https://qiita-image-store.s3.amazonaws.com/0/4560/c2464019-1d66-f35d-5e68-140a4925958e.png "GooglePlayGameService004.png")

モザイク化した部分が必要なID、下の「リソースを取得」で登録したIDのstrings.xmlのリソースデータを生成してくれます。


### 実績解除の送信

実績の解除送信は、メソッド一つで実装できます。

```java:MainActivity.java
Games.Achievements.unlock(googleApiClient, /* 取得する実績のID */);
```

ここは「実績」というシステムに慣れている方には説明不要ですが、解除した実績の取り消しは行えません。
送信自体も呼び出せば取り消しは不可能なので、実績解除の判定ロジックには気をつけましょう。

### リーダーボードの送信

リーダーボードにデータを送る場合も、メソッド一つです。

```java:MainActivity.java
Games.Leaderboards.submitScore(googleApiClient,
                    /* リーダーボードのID */, /* 送信する値 */);
```

複数回送信した場合、毎回上書きされるわけではなくゲームサービス側で一番良い値を採用してくれるようです。


こちらも送信した値は取り消せませんし、「送信する値」がダイレクトにリーダーボードに反映されるところに注意してください。

例えばあるタイミングで送信する値がおかしくなってしまった場合、リーダーボードの結果がむちゃくちゃになってしまい、新規のリーダーボードが必要になってしまいます（この辺は例の改ざん対策である程度対応できるかもしれませんが、不明です）。

### 実績画面、リーダーボード画面呼び出し

ユーザーの実績解除の確認画面や、リーダーボード画面は（おそらく）Play ゲームを呼び出して表示する形での実装になります。

```java:MainActivity.java
// リーダーボード画面の呼び出し
startActivityForResult(Games.Leaderboards.getLeaderboardIntent(mGoogleApiClient,
                        /* 見たいリーダーボードのID */ , /*REQUEST_CODE*/);

// 実績画面の呼び出し
startActivityForResult(Games.Achievements.getAchievementsIntent(mGoogleApiClient),
                        /*REQUEST_CODE*/);
            
```

![Achievement_ranking.png](https://qiita-image-store.s3.amazonaws.com/0/4560/890cd08f-a1db-e569-846c-42507df5c4f7.png "Achievement_ranking.png")

左が実績画面、右がリーダーボード画面

リーダーボード画面は2人しかいませんが、３人までは最上段で横並び表示、４人目以降は下に並ぶ感じで表示されるようです。

以上がズンゲーで実装した内容になります。サインイン＋実績やリーダーボードの登録はこれだけでできますし、さらにゲームサービスに登録することで[Playゲームの録画機能](http://pc.watch.impress.co.jp/docs/news/20151111_729984.html)を利用できるようになるので、積極的に導入してみるのも良いかなと思いました。

## 備考

実はゲームサービスから、ユーザーの分析や収益データをチェックすることができるようです。
ズンゲーではそもそも利用者がほぼいない＋課金要素がないということで、ほとんどデータとしては役に立たないですが、ちゃんと利用者がいて課金要素のあるゲームであれば、なかなか興味深いデータを分析できるんじゃないでしょうか。

![スクリーンショット 2016-04-12 19.30.24.png](https://qiita-image-store.s3.amazonaws.com/0/4560/c8dca266-4aad-6288-4da4-3622c6b23844.png "スクリーンショット 2016-04-12 19.30.24.png")

![スクリーンショット 2016-04-12 19.30.32.png](https://qiita-image-store.s3.amazonaws.com/0/4560/6bac603c-c123-0f18-7b04-4ba40419cabc.png "スクリーンショット 2016-04-12 19.30.32.png")

![スクリーンショット 2016-04-12 19.30.42.png](https://qiita-image-store.s3.amazonaws.com/0/4560/b48d6dd0-4c33-f4ab-8932-7d708e05cc51.png "スクリーンショット 2016-04-12 19.30.42.png")

![スクリーンショット 2016-04-12 19.29.12.png](https://qiita-image-store.s3.amazonaws.com/0/4560/fe742337-eb22-0ed5-b97b-fd3f8a7b4ba5.png "スクリーンショット 2016-04-12 19.29.12.png")