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

Grani Engineering Blog

株式会社グラニはC#を中心として、ASP.NET、Unity、VR開発を行っています。

Prime31 Ver2.17以降のStoreKitプラグインを利用したiOS課金実装(レシートの取扱について)

 はじめまして!アプリケーション開発部の長内です。 突然ですが、UnityでiOSアプリ内課金を実装するとしたら何を利用しますか? Unity5.3以降であればUnity IAPを検討する方もいるでしょう。

 Unity IAPであれば、iOS App Store や GooglePlay だけでなく Windows Store や Amazon Appstore といった様々なストアなどに対応しており、さらにプラットフォーム毎の固有実装を気にせず共通のインターフェイスでゲーム側に組み込めるのも魅力的です。

実際は?

 しかし、現状の殆どの場合はUnityアセットストアで手に入るプラグインを利用する方が多いかと思います。

 そのアセットストアでiOSのアプリ内課金といえば、Prime31 さんからリリースされていますiOS StoreKit In App Purchase Pluginではないでしょうか。

 このアセットは、Unityがまだ3.2、3.3のバージョン頃からあらゆるスタジオなどでも採用され続けている古株のアセットです。

 やはり、昔から利用されているだけあってGoogleでUnityによるiOSアプリ内課金を検索すると候補として上がる程です。

見えないユーザーとの売買

 脱線したお話になりますが、通常レシート(以下領収書データ)は、プラットフォームストア(ここではGooglePlayStoreやiTunesStoreを指します)からユーザーの端末に直接送信されます。

 この時には既に決済は完了しているので、ゲームとしては対応する商品を付与してしまっても、売買のやり取りとしては成立しているのでOKです。

リアル環境なら

 では、簡単なシーケンス図で表してみましょう。

f:id:yosagrapon:20170525153637p:plain

 これが、現実世界でコンビニやスーパーマーケットなどであれば全く問題はありませんね、直接お客様とお店との取引をしていますので、これで成立します。

領収書をもらったことにしよう!

 しかし、このやり取りが電子世界になった途端、そうではいけなくなってしまいます。例えば次のパターンの時はどうでしょうか。

f:id:yosagrapon:20170525153319p:plain

 マズイです、実にマズイです。お客様はなんの売買取引をしていないのに、ゲームサーバーに領収書をもらったとしか言っていないだけでゲームサーバーは言われたアイテムを付与してしまっています。

貰ったことを言うのではなく、その物を渡しましょう

 では、領収書をもらったと言うのではなく、領収書そのものを渡しましょう。

f:id:yosagrapon:20170525153323p:plain

 お客様はちゃんと売買取引を行い、プラットフォームストアから受け取った領収書を渡しているので、ゲームサーバーとしても領収書を受け取っているのなら問題はなさそうです。

はたしてそれは本物?

 しかし、お客様の中でとてつもなくプラットフォームストアの領収書データに精通し有りもしない領収書データを作れた場合はどうなるでしょうか?

f:id:yosagrapon:20170525153327p:plain

 ダメです。実にダメです。結局、お客様は売買取引をせず偽物の領収書データを作って、あたかも売買取引したかのように振る舞った領収書をゲームサーバーに送りつけゲームサーバーは領収書があるから大丈夫と思い、アイテムを付与してしまいました。

本人確認は重要ですね

 では、この問題に対してどう対処するかというと、至極単純です。その領収書を発行した本人に確認を取れば良いのです。あらゆるサイトでも紹介されていてデファクトスタンダードな実装の簡易シーケンスを見てみましょう

f:id:yosagrapon:20170525153925p:plain

 本来はもっと複雑なフローなのですが、簡略的に書いた場合はだいたいこうなります。

また、これが本題では無いのでまたいつかということでおひとつ。

本題

 では、そんな昔からあるアセットの実装方法を今更何を書くのかというと、レシートの取り扱い方が変わったことについてです。

 Prime31さん公式サイトのあらゆるアセットのリリースノートに以下のような事が書かれています。

iOS StoreKit09-23-2016

Version 2.17

  • rebuilt against iOS 10 SDK

  • removed base64 encoded receipt as it has been deprecated by Apple since iOS 7

  • bug fix: avoid restoring the same transactions multiple times even if Apple sends them over and over again

このリリースノートでもっとも重要な内容は

removed base64 encoded receipt as it has been deprecated by Apple since iOS 7

の部分です。

 そうです。この base64 encoded receipt がiTunesStoreから送られてくるレシートそのものなのです。

これが削除されたということは、正しいレシートをゲームサーバーに送ることが出来ませんし Unityマニュアルでもこんな(base64エンコードされたレシートを送信)ことを書いているので削除されたら何も出来ません と見せかけて、そんなことはありませんでした。

 そのかわりのAPIとして

public static string getAppStoreReceiptLocation()

が増えましたので、こちらを使います。

 この関数は、iTunesStoreから受け取ったレシートデータをローカルストレージに保存した"生のレシートデータ"へのファイルパスが返されます。

 なので、このままファイルを読み込んでそのまま送ったとしても結局不正なレシートとして扱われてしまいますのでBase64エンコードから送信までこちらで実装します。

/// <summary>
/// StoreKitManager の purchaseSuccessfulEvent イベントから監視オブジェクトとして使います
/// </summary>
/// <returns>purchaseSuccessfulEvent イベントの監視オブジェクトを返します</returns>
private static IObservable<StoreKitTransaction> PurchaseSuccessfulEventAsObservable()
{
    // event機構からObservableへ
    return Observable.FromEvent<StoreKitTransaction>(x => StoreKitManager.purchaseSuccessfulEvent += x, x => StoreKitManager.purchaseSuccessfulEvent -= x);
}

グラニでは、UniRxを使った実装をしていますので、StoreKitライブラリのpurchaseSuccessfulEventを監視するようにします。

var successObservable = PurchaseSuccessfulEventAsObservable().First().PublishLast().RefCount()
    .SelectMany(transaction =>
    {
        // レシートデータを読み込んでBase64エンコードする
        var receiptUri = new Uri(StoreKitBinding.getAppStoreReceiptLocation());
        var rawReceipt = File.ReadAllBytes(receiptUri.LocalPath);
        var base64Receipt = Convert.ToBase64String(rawReceipt);


        // レシートを生成して検証する
        var receipt = new PlatformStoreReceipt(storeItem, base64Receipt, string.Empty, userOption);
        return ValidateReceiptAsync(receipt);
    })

 そうです。ここで実装している"base64Receipt"こそが、2.17より前のバージョンで存在していた"base64EncodedTransactionReceipt"の写し身です。

 あとは、ゲームサーバーとゲームクライアントの検証結果を元にどうするかという実装を、ゲーム側仕様に則って実装します。

 以上で、まさかの本題より前書きのほうが長い(尺稼ぎ)記事になってしまいましたが、iOS10であるとか新しいバージョンにしなければいけなくなったが突如APIインターフェイスが変わった! と慌てず、まずは今回の実装を参考に実装を修正してみて下さい。殆どのケースでは上手くいきます。(絶対にとは言っていない)

黒騎士と白の魔王におけるMessagePack-CSharpのUnionの活用事例

こんにちは、アプリケーション部の松田(@kimika127)です。

今回はMessagePack-CSharpのUnionを活用した事例をご紹介します。 MessagePack-CSharpそのものについては

neue cc - C#(.NET, .NET Core, Unity, Xamarin)用の新しい高速なMessagePack実装

こちらに詳しくありますのでご覧ください。

Unionについて

MessagePack-CSharpのUnionを噛み砕いていうと「インターフェイスでシリアライズできる機能」になります。 とだけ聞くと便利そうに思えますが、実際にプロジェクトを進めるうえでこの機能を有効的に活用できる場面はそう多くはないでしょう。

今回、黒騎士と白の魔王(以下、黒騎士)では、トーク機能でこのUnionを使うことにしました。

メッセージの種類

トークではたくさんのメッセージを流します。 黒騎士ではメッセージの種類も多岐にわたり、例えば

  • 単純な文字列
  • スタンプ

のほかに、トーク機能を持ったRPGゲーム特有の事例として

  • トークからクエストへみんなで出発する時の募集文

なども要件として出てきます。

メッセージで送る情報

単純にメッセージを送るといっても、その中には

  • 誰が送信したか
  • どこに送信したか(具体的にはトークルームと呼ばれる集団)
  • いつ送信したか
  • どのような内容か

など、これら以外にも多数の情報を持っています。

これらのうち、発言者やルーム、時刻などはメッセージの種類に関係なく、同じものを使うことができます。 しかし内容に関しては、文字列、スタンプ、ロビーへの招待など全く違う情報をクライアントへ送信する必要性が出てきます。 ここでUnionの出番になります。

具体的な実装例

// インターフェイスを用意
// 各種具象型をUnion属性で指定
[Union(0, typeof(TextMessageBody))]
[Union(1, typeof(StampMessageBody))]
[Union(2, typeof(QuestMessageBody))]
public interface IMessageBody { }

// 文字列を送る
[MessagePackObject]
public class TextMessageBody : IMessageBody 
{
    [Key(0)]
    public string Text { get; set; }
}

// スタンプを送る
[MessagePackObject]
public class StampMessageBody : IMessageBody 
{
    [Key(0)]
    public int StampId { get; set; }
}

// クエストへの募集を送る
[MessagePackObject]
public class QuestMessageBody : IMessageBody 
{
    [Key(0)]
    public int QuestId { get; set; }
    [Key(1)]
    public string Text { get; set; } // ユーザが入力できる募集文言とか…
}

具体的な利用例

サーバ側

var textMessage = new TextMessageBody { Text = "グラニのエンジニアブログへようこそ!" };

// シリアライズして送信
var bin = MessagePackSerializer.Serialize<IMessageBody>(textMessage);

クライアント側

// 受信してデシリアライズ
var body = MessagePackSerializer.Deserialize<IMessageBody>(bin);

// C# 7だと型switchが使えるので便利
// が、Unityだとasかインターフェイスに識別子を持たせて分岐
switch (body)
{
    // 文字列が来た
    case TextMessageBody x:
        Debug.Log(x.Text);
        break;
    // スタンプが来た
    case StampMessageBody x:
        Debug.Log(x.StampId);
        break;
    // クエストへの募集が来た
    case QuestMessageBody x:
        Debug.Log(x.QuestId + ":" + x.Text);
        break;
    default:
        break;
}

まとめ

実はMessagePack-CSharpを導入する以前は、本文の全ての要素をCSVにし、string型でクライアントに送ってからパースをしていました。

しかし、iOS/Androidアプリの運用を続けていくにあたって障害の一つとなるのが、「更新されていないかもしれないユーザのクライアント」と「更新しなければいけないサーバ」間の通信の整合性を保つことです。 そこでMessagePackを使えば、要素の追加に強く、既存のクライアントに影響を出しにくく、保守もしやすい設計にすることができます。

今回は黒騎士の中でもUnionという機能を有効に使えた例をご紹介させていただきました。

黒騎士と白の魔王のキャラクターの作りについて

こんにちは、アプリケーション部の松田(@kimika127)です。

Unite 2017 Tokyo講演「「黒騎士と白の魔王」にみるC#で統一したサーバー/クライアント開発と現実的なUniRx使いこなし術」 - Grani Engineering Blog

先日のUnite 2017 Tokyoでも弊社CTOの河合が触れたものになりますが、今回は黒騎士と白の魔王(以下、黒騎士)のキャラクターの作りについてより詳しくお話をしようと思います。

Spine

まず、黒騎士ではキャラクターのアニメーションにSpineを全面的に採用しています。 みなさんのアバター、戦闘中に出てくる敵、マイタウンのスプリやリゼット等、全てSpineで作られています。

Spineにした理由は単純なもので、黒騎士の開発が始まった当初、Unityに対応していることは当然、その上で表現力や開発のしやすさなどを加味した結果によるものです。 現在はUnityからAnima2Dが無償で提供されており、Spineと同じく積極的な更新もされていますので、これから開発をされる方はAnima2Dも選択肢としてあげてよいと思います。

今回はこのSpineで作られた中でもホーム画面等で表示されるキャラクターについて説明しようと思います。

Spectacle

今回ご紹介するホーム画面等で表示されるキャラクターは、Spineの周りに多数のParticleSystemをまとい、タップをするとボイスを喋りながらアピールモーションをする機能です。

先ほどもお話した通り、黒騎士では各所でSpineが使われているうえ、それらの作りが細かく違っています。 例えば、アバターであれはジョブによる着せ替え機能があったり、敵であれば色味の変更や必殺技演出等、必要な要件が異なるからです。 このようなものを全て「Spine」や「キャラ」などで表現するとあいまいになってしまうので、これらを提供する機能群をまとめて「Spectacle」というコードネームで呼び、他と区別するようにしています。

SkeletonAssetの準備

ここは私の専門外ではありますが、キャラクターが動くまでの過程をご紹介します。

まず実際に動かすためにはイラストとアニメーションが必要になります。 最初にアートディレクターが指示書を作ります。 黒騎士ではキャラクターに進化という概念がありますので、イラストレータはその指示書を基に進化前、進化後の2点のイラストを用意します。 アニメータはそこからさらに「待機中」「アピール中」の2つのモーションが必要ですので、計4つのコンテを作成します。

イラストが仕上がると、次に動きを付けるためにイラストを分割します。

f:id:ykimisaki:20170516154324p:plain

分割されたイラストはSpineに取り込まれ、アニメータの手によってボーンとウェイトメッシュによりコンテに従った動きが付けられます。

エフェクトを付ける

Spectacleの特徴はSpineで作られたアニメーションの周りを、パーティクルで飾り付けていることです。 実はアニメータがボーンを組み込む際に、パーティクルを表示する場所に隠れたボーンを組み込んでいます。 例えば以下の写真は杖の先端のボーンに、エフェクタが作成したパーティクルを表示しています。

f:id:ykimisaki:20170516153936p:plain

各パーティクルは指定されたボーンに対して配置されます。

また、ルートボーンに対してCurvyを使用したトレイルが渦を巻くように配置されています。 普段は正面からしか見えませんが、これを上から撮影するとこのように見えます。

f:id:ykimisaki:20170516184819p:plain

これらのパーティクルは動きに合わせた最適なタイミングで出現/消失が制御できるように、Spineのアニメーションタイムライン上でイベントを組み込んでいます。 各イベントはエンジニアとアニメータの間で、どのような名前にするか、どのようなパラメータが渡されるか等の規約があらかじめ決められています。 エフェクト001の出現タイミングだと以下のようなイベントがSpine上で設定されます。

f:id:ykimisaki:20170516154749p:plain

SpectacleはSpineのSkeletonAssetに埋め込まれたボーン情報とイベント情報を読み込み、それに合わせてエフェクトの制御を行っています。

ボーンとエフェクトとイベントを関連付ける

さて、せっかくエフェクトを用意しても、それらがどのボーンに関連付けられているかを知らなければ配置することができません。 SpectacleではDBで定義されたマスタをクライアントに送信し、組み立てます。

重要なことは、各ボーンそのものにエフェクトの情報を直接持っていないことです。 ボーンの番号とエフェクトのPrefabの関連をマスタデータで仲介することにより、Spineで再度アニメーションを弄らなくても、エフェクトの差し替えが容易にできるようにしています。

子Spectacle

Spectacleはそのボーンの先に別のSkeletonAssetを表示することができます。 こちらのSpectacleの周りにいる犬、猫、小鳥は、すべて本体とは切り離された別のSpineです。

f:id:ykimisaki:20170516190218p:plain

これらもまた、エフェクトと同じようにアニメータはSpectacleで表示することができます。

SpectacleEditor

さて、現在リリースされているSpectacleは全部で80体以上に及び、エフェクトはSpectacle1体に付き10個つくこともあります。 そしてこれはおそらく今後も追加がされていくことでしょう。

これだけのSpectacleを円滑に用意するためには、我々エンジニアが関与せず、アートディレクタのもとでイラストレータやアニメータの手ですべて行える環境が必要になります。

上記の工程のうち、

  1. 指示書の作成
  2. コンテの作成
  3. イラストの作成、分割 → Photoshop/sai/CLIP STUDIO/他
  4. アニメーションの作成 → Spine Editor
  5. パーティクルの作成 → Unity

ここまではエンジニアの手がかからないものです。

しかし、ボーンとエフェクトとイベントを関連付ける部分はDBを介しているので、何もしなければアニメータが使いやすいものとは言えず、ミスも起こるでしょう。 そこで、Unity上でボーンとイベント、エフェクトを関連付け、APIを通してDBへ保存するエディタ拡張を用意しています。

f:id:ykimisaki:20170516174437p:plain

SpectacleEditorの役目は、ボーンとエフェクトとイベントの関連付けをアニメータが行えるようにすることです。

ただし実際にアニメーションを組み込み、エフェクトをまとわせて表示できる状態で再生したり、たくさんのSpectacleを並べて再生すると、アートディレクターがバランスの悪さを指摘することがあります。 そのため、SpectacleEditorでは再度SpineやPhotoshopを開かなくても微調整が行える機能を用意しています。

具体的には

  1. Spineのリサイズ
  2. Spineの上下左右の位置の調整
  3. Spineやエフェクトの反転
  4. 子Spectacleの深度調整、反転
  5. アニメーションごとのエフェクトの表示/非表示

です。 これらのデータはすべてDBに保存され、みなさんのお手元では調整されたものが表示されるようになっています。

ボイス

ボイスに関しては、全くの別ラインでの作業になっています。 黒騎士ではCRIを使用しており、Spineのイベントなどを使用せずともアピール開始時に再生処理を呼び出すだけで適切なボイスが呼び出されるように、サウンドエンジニアがミドルウェアを使い用意をしてくれています。

Spectacleの仕様要求と問題点の妥協点

Spectacleはその見た目から、ガチャで排出されるキャラとされるなど黒騎士でも重要な要素になっています。 当然、リッチな見た目への要求は大きく、中には大きな問題になってしまったものもありました。

最後にそれをご紹介しようと思います。

Spectacle for UI

Spectacleはホーム画面やバトル中の召喚演出の他にも、様々な場所で再生が行われます。 例えば

  • マップ上の宝箱、ギフト、ガチャ等でのキャラ取得
  • デッキ編成や強化、図鑑、進化でのキャラ表示
  • ガチャ画面でピックアップ

などです。

f:id:ykimisaki:20170516181326p:plain

詳細は割愛させていただきますが、黒騎士ではuGUIの上に厚いレイヤーを重ねた独自のUIシステムがあります。 このUIシステムとSpectacleを重ねて綺麗に表示させるためには、UI上にうまく組み込ませる必要があります。 そのため、メモリや描画に対する負荷の少ない一部のUI部分では、RenderTextureに一度投影し、RawImageとして取り扱っています。

ただしRawImageをそのまま描画するとエフェクトが正しく描画されないため、専用のシェーダを差し込むことでこの問題を回避しています。

f:id:ykimisaki:20170516182330p:plain

エフェクトの裏への回り込み

Spineはメッシュを変形して表示させるのですが、元の画像の形により、実際の輪郭に比べポリゴンが大きくなることがあります。 以下の青いポリゴンを見ると、周りに大きく余白があることがわかります。

f:id:ykimisaki:20170516184206p:plain

これは3Dでは起こらない問題ですが、黒騎士ではエフェクトがSpineの周りをトレイルが回っているため、奥にあるエフェクトをSpineの余白が透明に塗りつぶしてしまう問題が起こります。 そこでこちらも専用のシェーダを用意し、先に描画されたSpineの透明ピクセルをclipすることで、本来奥にあるはずのエフェクトが見えるようにしています。

f:id:ykimisaki:20170516183905p:plain

ゆがみシェーダ

黒騎士の一部のSpectacleでは、より迫力を出すためにアピール時に波を打つようなゆがみシェーダを取り入れています。 以下の画面の、特に文字部分ではよく目立ちます。

f:id:ykimisaki:20170516190704p:plain

これらのシェーダは基本的にShader Forgeによって作成されたものですが、iOS Metal環境でCameraのRectを弄ると正しくGrabTextureが取得できないという不具合があります。

f:id:ykimisaki:20170516191550p:plain

この不具合は見た目がひどく崩れるので、黒騎士ではシェーダ側でCameraのRectが弄られている際は歪ませない、という苦肉の策を入れています。

アルファマスク化

Spineからエクスポートされる形式はいくつかありますが、黒騎士ではBinary+PNGで吐き出しています。 BinaryはJSONよりも読み込み速度が速いので、特にJSONにこだわらない限りはBinaryをオススメします。

さて問題は画像です。 Spectacleで吐き出されるPNGは主に1024x1024サイズになります。 これを黒騎士の仕様上、プレイヤー4人×召喚2枠=計8体を描画しなければいけません。 つまり、真っ先に削るべきはテクスチャのメモリ使用量という事になります、それもクオリティを下げない方法で。

黒騎士ではこの問題を解決するために、以下の形式をとりました。

  • アルファマスクを使い、透明を含むテクスチャを2枚に分ける
  • iOSはRGB Compressed PVRTC 4bitのBest画質
  • AndroidはRGB Compressed ETC 4bitsのNormal画質

アルファマスクを使用したのは、透明ピクセルを含むテクスチャをPVRTCで圧縮すると画質が大きく下がってしまうためです。 また、Androidの画質がNormalなのは、Best画質にすると途方もない変換時間が掛かってしまい、AssetBundleを生成するCIがタイムアウトしてしまうためです。

アルファマスクを描画するためには、専用のシェーダを用意し、全てのSpine用マテリアルにアルファマスクを差し込み、画像の圧縮設定を行わなければなりません。 これも当然Spectacleの機能で自動化されており、読み込んだSpineから、右クリックのエディタ拡張を使用すると、クリック1回で

  1. アルファマスクの生成
  2. マテリアルに専用シェーダの設定
  3. テクスチャの圧縮設定の変更
  4. アセットバンドルの設定

をすべて一括で行うようになっています。 アルファマスクの生成では、元のPNGデータを壊さずに不透明データを生成するようになっています。

f:id:ykimisaki:20170516193214p:plain

まとめ

以上のように、黒騎士では2D描画もかなりこだわって作っています。

演出のこだわりを達成するために私が大事だと思っていることは、エンジニアの手をできるだけ介さずに、アートディレクタ、イラストレータ、アニメータ、エフェクタの作業の、1から10までの流れを妨げないことだと思っています。 エンジニアは現在Spectacleにはほぼ関わっていない状態ですが、指示書から納品まで、求められるリリースサイクルに耐えうるチームができあがっており、アニメ部との協力体制と的確なフィードバックの賜物であると思っています。

今後も黒騎士と白の魔王を通じて、より良い体験を多くみなさんに届けられるように頑張っていきますので、よろしくお願いします。

iOSで音楽を聴いているときにゲームの音楽を止める

こんにちは、開発部の@mayukiです。

今回はUnityでのゲーム音楽の再生とバックグラウンドアプリケーションによる音楽の再生のお話です。

ゲームの音楽とバックグラウンドの音楽、二つの音楽はなぜぶつかり合うのか

ゲームではゲーム固有の音楽が流れるのが一般的ですが、それとは別にスマートフォンを音楽プレイヤー代わりとして使うシーンが多々あります。iPhoneをお持ちの方は特にそういった使い方が多いでしょう。

ところがそれらの状況は同時に起こり、そのままでは両方の音楽が重なって結果どっちもまともに聞こえないという問題が発生することがしばしばです。特にスマートフォンのゲームは一回の滞在時間が短い場合もあり、そのような状況が発生しやすいでしょう。

対応方針

二つの音楽がぶつかり合ったとき、何とか救う方法はないかそもそもどうすればいいのか…を考えてみます。

  • 上記のとおり、スマートフォンのゲームは一時的に起動される機会が多い
  • 音楽を聴いているままゲームを起動したということは音楽を聴いていたいという可能性が高い
  • ゲームの音量設定を手動でミュートにするよりもバックグラウンドの音楽を止める操作のほうが簡単
    • プレイヤーがゲーム内の音量を上げたり下げたりするのはiOSのコントロールセンターから音楽を操作する簡単さには勝てない

といったことからゲーム側がバックグラウンドのアプリケーションによる音楽再生を尊重したほうがよさそうにおもえます。実際この時の挙動のおすすめはAppleのプログラミングガイドにも記載されています。

ユーザがアプリケーションを起動したとき、既に音声が再生中かも知れません。たとえばその時点で、「ミュージック(Music)」で音楽を再生していた、Safariで音声ストリーミングをしていた、などが考えられます。起動したのがゲームであれば、これは特に重要です。多くのゲームがサウンドトラックや効果音を活用しています。『iOS Human Interface Guidelines』の「Sound」で述べているように、ゲームの場合、その効果音は再生しつつ、起動前に再生していた音声もそのまま再生されるものとユーザは想定しています。 アプリケーションを起動する際、otherAudioPlayingプロパティで、音声が再生中であるかどうか調べてください。他の音声が再生中であれば、ゲーム側のサウンドトラックを無音にし、 AVAudioSessionCategorySoloAmbientカテゴリを指定します。カテゴリについて詳しくは、“カテゴリの取り扱い”(16 ページ)を参照してください。

「バックグラウンドのアプリケーションが音楽を流していたら、ゲーム中のBGMはミュートする(SEはそのまま)」のがよいということですね。ボイスやSEを止めるか止めないかはアプリ次第ですね。

なおこのパターンのデメリットとしてバックグラウンドの音楽とゲームの音楽を重ねられない点がありますが、その挙動で得するパターンはほとんどないので気にしなくてよいと思います。

実装

ではUnityで「バックグラウンドのアプリケーションが音楽を流していたら、ゲーム中のBGMはミュートする(SEはそのまま)」にするための実装をしましょう。

まずゲームBGMのミュートは大抵ゲームのシステムにある何らかの方法(マスターボリューム管理など)でできるようになっているかと思います。つまり「バックグラウンドのアプリケーションが音楽を流しているかどうか」さえ取得できれば、あとはそれに応じてボリュームをコントロールしてあげればよいわけです。

バックグラウンドで音楽が流れているかどうかに関しては、iOSでは先ほど引用したガイドにAVAudioSessionのotherAudioPlayingプロパティを確認してくださいと書いてありますが、iOS 8以降ではAVAudioSessionのsecondaryAudioShouldBeSilencedHintプロパティを利用することが推奨されています。secondaryAudioShouldBeSilencedHintプロパティはセカンダリオーディオ、つまり音楽再生アプリのようなプライマリではないもの(AVAudioSessionCategoryAmbient)はミュートになるべきかどうかを返します。ともあれこれが使えそうです。

Objective-C

チェックすべきものがわかったところで、iOSのAPIをUnityから直接呼び出すことはできないのでObjective-Cのコードを書いてあげます。

#import <AVFoundation/AVFoundation.h>

extern "C" {
    unsigned int avAudioSession_secondaryAudioShouldBeSilencedHint() {
        return [[AVAudioSession sharedInstance] secondaryAudioShouldBeSilencedHint];
    }
}

と言ってもAVAudioSessionsharedInstanceからsecondaryAudioShouldBeSilencedHintを取得したものをそのまま返すだけです。お手軽。AVAudioSession.mmなどの名前でPlugins/iOSなどに保存しておきます。

C# (Unity)

そしてC#での呼び出し側です。これも単純に先ほど書いた関数を呼び出すだけです。

using System;
using System.Runtime.InteropServices;

#if UNITY_IPHONE
public static class AVAudioSession
{
    [DllImport("__Internal", EntryPoint = "avAudioSession_secondaryAudioShouldBeSilencedHint")]
    private extern static int _SecondaryAudioShouldBeSilencedHint();

    /// <summary>
    /// セカンダリオーディオ(ゲームのBGMなど)をオフにすべきかどうかを返します。
    /// </summary>
    public static bool SecondaryAudioShouldBeSilencedHint
    {
        get { return _SecondaryAudioShouldBeSilencedHint() == 1; }
    }
}
#endif

あとはこれを監視して、プロパティの変更に応じて処理してあげればよいでしょう。毎フレーム見るほどでもないし呼び出し負荷ももったいないのでタイマーぐらいでちょうどいいかもしれません。ここはUniRxでちょちょいとやるとベンリですね。

Observable.Interval(TimeSpan.FromSecond(1))
    .Select(_ => AVAudioSession.SecondaryAudioShouldBeSilencedHint)
    .DistinctUntilChanged()
    .Subscribe(x =>
    {
        if (x)
        {
            // ミュートする処理をここに書く
        }
        else
        {
            // ミュート解除する処理をここに書く
        }
    })
    .AddTo(this);

まとめ

こういった普段スマートフォンを利用している中でゲームが邪魔にならない、ということはユーザーの体験に地味によい効果があると思うので実装をおすすめします。

Unite 2017 Tokyo講演「「黒騎士と白の魔王」にみるC#で統一したサーバー/クライアント開発と現実的なUniRx使いこなし術」

CTOの河合です。Unite 2017 Tokyoにて、 「黒騎士と白の魔王」にみるC#で統一したサーバー/クライアント開発と現実的なUniRx使いこなし術 という講演を行いました。

講演に参加いただいたみなさま、ありがとうございます。資料のほか、講演の動画は、また後日配信されるようなので、そちらもご覧いただけたらと思います。

C#大統一理論

「黒騎士と白の魔王」は、世にも珍しくクライアントとサーバーが共にC#で構築されています。クライアントがC#なのは当たり前ですが(Unityのお陰ですね!)、サーバーがC#なのも、そう珍しいわけではないのですが(特にBtoBの場では、Windows Serverで、C#でウェブシステムを組むことはよくあることです)、両者が組み合わさったケースは中々珍しいと思います。しかしグラニでは、一作目の「神獄のヴァルハラゲート」をC#で構築していたこともあり - グラニがC#にこだわる理由 - 神獄のヴァルハラゲートの裏側をCTOが語り尽くす!、両者をC#で作り込んでいくことは自然な選択でした。

しかしそれは同時にチャレンジでもあります。構成に前例が少ないこともですが、単にリクエスト/レスポンスが共通であること以上のメリットを生み出していこうと様々な試みを行いました。成功した試みもあれば、失敗もあります。その経験を、今回のセッションでは是非共有したかったわけです。

クライアントとサーバーがC#で構築されることは、間違いなく大きなメリットがあると実感しています。まだまだ完全な理想点には至っていませんが、着実に向かっていると考えています。今後も、C#大統一理論をより昇華させていきたいですし、ノウハウは積極的に共有していきます。皆さんも、是非チャレンジして欲しいですし、そのための参考になれば何よりも嬉しいです。

ライブラリ群

グラニでは実際に黒騎士と白の魔王で使われている多くのライブラリを、オープンソースソフトウェアとして公開しています。その中にはUnity単独で使うことも可能ですが、サーバーサイドもC#で構築することで、更なるパワーを発揮するのものもあります。

講演の後半で紹介したMessagePack for C#(Unityで、C#で、世界最速の汎用バイナリシリアライザ)もその一つです。単独で使っていただいても最高のパフォーマンスを発揮しますが、サーバーサイドもC#で構築されていれば使い勝手が大幅に向上します。より自然にUnity用のコードジェネレーターを動かせたり、特有の属性でマークすることによる挙動の変更が手間なくできるなど、IDEも含めた言語機能をフルに活かした使い心地を感じていただけるように注力しました。

サーバーサイド実装とインフラ

黒騎士と白の魔王のもう一つのチャレンジとして、gRPCをUnityで使うという世界初の事例があります。Unityで使うというだけではなく、gRPCをStreamingで活用していくというのも、あまり前例のないケースです。

そうした、今回紹介していない、サーバーサイドでのgRPCの使いこなしや、インフラ構築の話は、 2017 年 6 月 1 日(木)にAWS Summit Tokyoのサブトラック Game Tech Session ~AWS Summit Tokyo 2017~にて 「『黒騎士と白の魔王』の gRPC による HTTP/2 API/ストリーミング通信の実践」 として講演する予定です。こちらはUnityだけではなく、他のモバイルアプリケーションやMicroservicesのバックエンドとしてgRPCを使っている方々にも参考になる話ができると思っていますので、是非よろしくお願いいたします。

C# 7.0 が使えるようになったので ValueTuple を活用してみた

アプリケーション部の田口(@t_tetsuzin)です。 社内では数少ないF#erとして潜伏中です。

待ちに待った VisualStudio2017 がリリースされましたね!
Graniではさっそく C# 7.0 を本番環境に投入しています。

そんな待望の C# 7.0 で使えるようになった新機能は

  • タプル(ValueTuple)・タプルの分解
  • Task-like
  • ローカル関数
  • 拡張されたswitch文(パターンマッチ)
  • etc…

と大きいのから小さいのまで多岐にわたりますが、 今回はタプルの便利な使い方について紹介したいと思います。

タプル以外の今回の目玉機能である Task-like はちょうど公開された弊社CTOの記事でどうぞ!

どういう風に便利なのか

※ ValueTuple は .NET Framework 4.7 未満で使用する場合は
Nuget から System.ValueTuple をパッケージをプロジェクトに追加する必要があります。

ソーシャルゲームではユーザーの情報など大量のデータを様々なところから呼び出す必要があるので、 こういう複数の非同期処理を呼ぶ場所がよく出てきます。

public async Task<int> HogeAsync()
{
    await HeavyFunctionAsync(); // 重い処理
    return 10;
}

public async Task<string> FugaAsync()
{
    
    await HeavyFunctionAsync(); // 重い処理
    return "Fuga";
}

public async Task<int[]> MogeAsync()
{
    await HeavyFunctionAsync(); // 重い処理
    return new[] { 10, 20, 30 };
}

非同期処理を並列に実行するため、今まではこのように書いていました。

public async Task Before()
{
    // C#6までの書き方
    var hogeTask = HogeAsync();
    var fugaTask = FugaAsync();
    var mogeTask = MogeAsync();

    await Task.WhenAll(hogeTask, fugaTask, mogeTask);

    var hoge = hogeTask.Result;
    var fuga = fugaTask.Result;
    var moge = mogeTask.Result;

    // hoge, fuga, moge を使った処理へつづく
    await SomeFunctionAsync(hoge, fuga, moge);
}

長いですね。とてもイケてるコードには見えません。
しかし、タプルと分解、そして拡張メソッドを組み合わせることで……

public async Task After()
{
    // C#7以降でタプル記法を使った書き方
    var (hoge, fuga, moge) = await (HogeAsync(), FugaAsync(), MogeAsync()).WhenAll();

    // hoge, fuga, moge を使った処理へつづく
    await SomeFunctionAsync(hoge, fuga, moge);
}

こんな風に書くことができるようになりました。

追加した拡張メソッド

public static class ValueTupleExtensions
{
    public static async Task<(T1, T2)> WhenAll<T1, T2>(this (Task<T1>, Task<T2>) tasks)
    {
        await Task.WhenAll(tasks.Item1, tasks.Item2).ConfigureAwait(false);
        return (tasks.Item1.Result, tasks.Item2.Result);
    }

    public static async Task<(T1, T2, T3)> WhenAll<T1, T2, T3>(this (Task<T1>, Task<T2>, Task<T3>) tasks)
    {
        await Task.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3).ConfigureAwait(false);
        return (tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result);
    }

    public static async Task<(T1, T2, T3, T4)> WhenAll<T1, T2, T3, T4>(this (Task<T1>, Task<T2>, Task<T3>, Task<T4>) tasks)
    {
        await Task.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4).ConfigureAwait(false);
        return (tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result, tasks.Item4.Result);
    }
}

上の例では4組のタプルまでしか挙げていませんが、8組程度なら用意してしまって良いと思います。

今回はタプルしか紹介していませんが、C# 7.0 には他にも有力な新機能が多く含まれているので、積極的に使っていきましょう! もちろん弊社のもうすぐリリースされる新タイトル黒騎士と白の魔王でも C# 7.0 が使われています!