このエントリーは、KLab Advent Calendar 2015 の12/2の記事です。
KLabとしては久々のAdvent Calendar参戦です。2番手も緊張しますね。KLabGamesの基盤エンジニアのkenseiです。よろしくお願いします。

はじめに

ソーシャルゲームは体力の概念があるゲームが結構あります。
ユーザが体力を全快した時間を知る方法があれば、よりスムーズにゲームを行う事ができます。
体力全快をユーザが知る手段の一つ、「体力全快予定時間にローカルプッシュを用いてユーザに全快を通知する方法」をご紹介したいと思います。

ローカルプッシュとは

Appleのドキュメントによれば、「ローカル通知は、アプリケーション自身がスケジューリングし、送信する」とあります。
指定した時間になると、アプリがフォアグラウンドで動作していない場合は、アラートでの通知 / バッジアイコン、サウンドの形でユーザに伝えます。
アプリがフォアグラウンドで動作している場合は、アラートでの通知を行います。
AndroidはAlarmManagerとNotificationBuilderの組み合わせでローカルプッシュを実装できます。

序章

体力全快通知を送る際に一番重要なのは「キャンセル処理」です。
スマートホンはマルチタスクなので、途中でゲームをサスペンドして別の作業を行った後にゲームを再開するかもしれません。
この時にローカルプッシュをタイマー送信していたらどうなるでしょうか?
ユーザがゲームを再開した時に、キャンセル処理をしない場合、ゲーム中に通知が飛んでしまいます。
また、ユーザが体力を消費したりアイテムなどで全快した場合も、キャンセル処理がないと
体力が全快でなかったり、すでに全快なのに、場違いな回復通知が飛んでしまう事態になってしまいます。

キャンセル処理のタイミング

ではキャンセル処理はどのタイミングで送ればいいでしょうか?
KlabGamesのあるタイトルでは

  • 起動時
  • サーバと通信を行った結果に格納されている、体力全快にかかる時間が0秒だった場合
  • 体力全快にかかる時間が0秒より大きい場合、ローカルプッシュのタイマーを設定する直前に

の3箇所でキャンセル処理を行っています。

  • 起動時は受け取る必要がないので、キャンセルを行います.
  • 2つめのサーバで通信を行う場合、複数のApiに体力全快にかかる時間をサーバ側で計算して返してもらっています.
    体力回復アイテムを使用して全快した場合や、レベルアップで体力が全快した場合などにキャンセルできるようになります.
  • 3つめのローカルプッシュのタイマーを設定する直前にキャンセル処理を行うのは、タイマーを常に最新に保つためです.
    設定されているタイマーを一つにして、常に最新にしておけば誤通知を防ぐ事ができます.

体力全快通知を送る際に一番重要なのは「設定されているタイマーを一つにして、常に最新にしておく事」です。
あれ、一番重要な事が増えた。。

Unity側のプログラム

iOSとAndroidで動くサンプルを作ってみました。
https://github.com/kensei/klab_advent_calendar_2015

体力を消費&回復し続けるだけのゲーム?です。
急ごしらえなので、バグってたら申し訳ありません。

簡単な処理の概要

実際のローカルプッシュの処理

クライアントプラグインを作成して、プラットフォーム別の処理をカプセル化しています。
unityでいうネイティブコードの初期化・ローカルプッシュの設定・ローカルプッシュのキャンセルをブリッジしています。

実はiOSはUnity標準でローカルプッシュの処理が用意されているのですが、

LocalNotification l = new LocalNotification();
l.applicationIconBadgeNumber = 1;
l.fireDate = System.DateTime.Now.AddSeconds(10);
l.alertBody = "test";
NotificationServices.ScheduleLocalNotification(l);

繰り返しを伴うローカルプッシュのような複雑な事はできません。
以上の理由から、今回は最初からネイティブコードを使用してローカルプッシュの実装を行っています。
また、iOS8ではローカルプッシュ通知にもユーザ許可が必要になりました。
ユーザ許可のためにUnityAppControllerを拡張しています。
code

AndroidはAndroidManifestに、ローカルプッシュで使用する権限の設定や、タイマーを受け取るレシーバの設定が必要になります。

初期化

起動時に各プラットフォームのネイティブコードを初期化しています。

ローカルプッシュ設定

  • C#
    • ネイティブコードの呼び出し.
  • iOS
    • UILocalNotificationを作成して、UIApplicationに渡しています.
  • Android
    • レシーバに渡すintentを生成します. IntentはLocalNotificationReceiverが受け取るように設定します.
    • Calendarインスタンスに終了予定時間を設定します. サンプルでは秒で設定しています.
    • AlarmManagerに生成したintentとCalendarを設定します.
  • AndroidReceiver
    • intentから情報を受け取ります.
    • Notificationを生成し、通知を行います.

ローカルプッシュのキャンセル処理

  • C#
    • ネイティブコードの呼び出し.
  • iOS
    • UIApplicationからUILocalNotificationを全て受け取ります.
    • notificationIdが一致する物をキャンセルします.
  • Android
    • Actionが同じPendingIntentを取得します.
    • AlarmManagerにキャンセルを依頼します.

最後に

明日は、僕が入社式に遅刻した時にメッチャ睨んできたpandax381さんのラズパイの話です。 お楽しみに。