KLabGames Tech Blog

KLabは、多くのスマートフォン向けゲームを開発・提供しています。 スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。 このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。

カテゴリ: Unity

KLabでクライアント開発基本ライブラリの開発をしている鬼塚です。

今回はKLabで使用しているクライアント開発基本ライブラリの紹介をしたいと思います。
クライアント開発基本ライブラリとはUnityを用いたスマートフォンゲーム開発において共通して必要になる機能、及び実装ノウハウを集約したライブラリ群です。

kgsdk_img

KLabではUnityを用いたスマートフォンゲームのほとんどでこのクライアント開発基本ライブラリを導入しています。
現在リリース中のアプリでも使用されている為、UnityやiOS/Androidといった各PFのバージョンアップの対応やそれよって生じた不具合等の改修が日々が行われています。
また、Unity4、Unity5どちらにも対応しているので環境を問わず利用することができます。
クライアント開発基本ライブラリの概要についてはコチラの記事をご確認ください。

提供ライブラリ一覧

(1)ストア課金処理ライブラリ

App Storeの In-App Purchase、Google Play の In-App Billing を利用してアプリ内課金を行う際に必要な機能を提供するライブラリです。
iOS/Androidの各PF間の仕様の差異を吸収した作りとなっており商品一覧の取得、 商品の購入、レシート更新(iOS)、レジューム処理の機能を提供しています。

(2)アセット管理ライブラリ

アプリ本体の起動後に必要な画像などの追加データファイル(アセットと呼びます)のダウンロード及びそれらのアセットの読み込みにまつわる様々な問題を解消したライブラリです。
なお、バックグラウンドでのダウンロード機能やDownload機構及び、Object Pooling機構をモニタリングする機能なども提供しています。

(3)チャットシステム

ゲーム内でユーザ同士のチャット機能を提供する際に必要な機能をまとめたライブラリです。
テキストだけのやりとりだけでなく、画像を用いたスタンプや投稿した内容に対するリアクション機能なども提供しています。
チャットシステムの詳細についてはコチラの記事をご確認下さい。

(4)コアユーティリティ郡

ファイル管理、オンメモリキャッシュ、ロケール取得、タイムゾーン取得といった、アプリケーション開発で必要となる基本的な機能をまとめています。
クライアント開発基本ライブラリでは全てのライブラリでこのコアユーティリティ群が含まれています。

(5)通信ライブラリ

HTTPやHTTPS通信にまつわる機能をまとめたライブラリです。
Unity標準のWWWクラスの互換性のない仕様や挙動の吸収やリクエストキューイング、リクエストタイムアウト、自動リトライ等HTTP通信を行う上で必要な機能を提供しています。

(6)WebViewライブラリ

iOS/AndroidのWebView機能をOSによらず透過的に扱えるようにするライブラリです。
WebViewで標準的に利用可能な戻る、進む、リロード、アプリ実装との相互連携などももちろん利用可能で、その他キャッシュの無効化やUserAgentの指定、iOS8から導入されたWKWebViewにも対応しています。

(7)ローカライズテキスト管理ライブラリ

ローカライズテキストを管理するライブラリです。
アプリ内テキストのローカライズを行う為に必要な表示言語、表示するテキスト、表示するテキストを参照する為のIDを簡単に管理することができます。
テキストのローカライズと言語設定管理の2つの機能を提供しています。

(8)PFアチーブメント対応ライブラリ

Google Play Game Servicesと Game Centerのアチーブメント機能を透過的に扱えるようにするライブラリです。
アチーブメントの一覧や達成状況、アンロック等各PFが提供している機能を統一されたインターフェースで利用することができます。

(9)Bluetooth Low Enargyライブラリ

iOS/AndroidのBluetooth Low Energy機能をOSによらず透過的に扱えるようにするライブラリです。
Bluetooth LE通信に必要な機能が含まれおり、Peripheral機器としての利用、Central機器としての利用どちらにも対応しています。

通信ライブラリについてもう少し詳しく

上で紹介したクライアント開発基本ライブラリの中から今回は通信ライブラリにフォーカスしてどのような機能を提供しているかいくつか紹介させて頂きます。

特徴

先ずは通信ライブラリの特徴から。
通信ライブラリではUnity標準の通信クラス(UnityEngine::WWW)と.NET FrameworkのWebClientクラスの拡張クラスのどちらを利用するかを指定できるようになっています。
Unity標準のWWWクラスにはUnityのバージョンによっていくつかの互換性のない仕様や挙動があります。通信ライブラリではそれらの差異を吸収し、また機能拡張することで利用者はUnityのバージョンによる互換性のない仕様や挙動を気にすることなく開発に専念することができます。

RequestTypeによる処理の切り替え

通信ライブラリでは利用者が取得したいデータ形式に合わせて処理を切り替えています。
通信ライブラリで用意しているデータ形式はAPIレスポンス等に使用する為のテキストデータ、Unityで使用するアセットバンドル、テクスチャ用のアセットバンドルではない画像やバイナリファイル、汎用的に使用できるファイルダウンロード等があります。

リクエストキューイング

通信ライブラリではクライアントから投げられたリクエストは一度キューに積まれます。
もし、キューに何も無ければリクエストを開始しますが、前のリクエストが完了していない場合はそのリクエストが完了するまで待機します。こうすることで、複数のスレッドで通信を行った場合でもリクエストした順番に処理することができます。
一度に実行できるリクエストの上限はデフォルトでは5回に設定されていますが、もちろんこの回数等は利用者が自由に変更することができます。

リクエストタイムアウト

リクエストがタイムアウトする迄の時間を設定することができます。
モバイル通信環境で起こりがちな、電波の届かない場所での通信や、正常にレスポンスが返って来なかった場合にタイムアウトを設定することでユーザーを待たせずエラー処理を実施することが可能になります。
また、Unity標準のWWWクラスではこのリクエストタイムアウト処理が提供されていない為自前で実装する必要がありますが、通信ライブラリではリクエストキューイング同様自由にタイムアウト時間を設定することができます。

自動リトライ

通信失敗時にリクエストのリトライ回数を設定することができます。
一時的なネットワーク上のエラーでリクエストが失敗した場合に自動的にリトライされるのでライブラリ利用者のリトライ実装の負担と対アプリユーザへの通信失敗通知によるストレスを軽減することができます。

その他

その他にも通信ライブラリでは便利な機能を提供しています。
例えば、HTTPRequestを同期/非同期に処理するかを切り替えることができ、この機能はUnityEditor拡張実装において複雑になりがちなHTTP通信処理実装の手助けになります。
加えて、ダウンロード時の進捗状況を表示できたり、デバッグ用にダミーのレスポンスを受け取れるように設定できたりなどネットワークを扱う上で必要となる機能を提供しています。

最後に

このようにクライアント開発基本ライブラリではスマートフォンゲームの開発を行う上で必須となる機能を機能単位で利用者が必要な物を取捨選択して導入できるようになっています。
KLabでは、開発パートナー様と共同開発したゲームをKLabにてパブリッシング、プロモーションを行うというモデルを積極的に進めており、開発パートナー様にKG SDKの提供もしています。

パブリッシング事業につきましてはコチラ
KG SDKに関するお問い合わせにつきましてはコチラ
をご覧ください。

このエントリーは、KLab Advent Calendar 2015 の16日目の記事です。

Unityを使ったゲーム開発業務に取り組んでいる、こうさかです。
デバッグ時に便利な、Unityのエディタ拡張を作ったので紹介します。

前提

Unity4.6.9&C#の環境で実装・確認してます。

開発中デバッグめんどくさい問題

もりもりコード書いて、いざ実行すると意図した挙動をしてくれないとき、バグっていそうな箇所にログを仕込む・デバッガを使い内部の状態を見る・気合でコード読むなどの調査をすると思います。しかし、いちいちログ出力コードを書いたり、デバッガを起動したりするのは面倒です。臭い場所が絞れている場合はコードを読めばわかりますが、そもそもあたりがつけられない場合に大量のコードを読むのも時間がかかります。特にゲームなど状態が多く複雑なアプリケーションを開発している場合、バグの調査に多くの時間をとられてしまいがちです。
そこで、常に状態を見ることができれば多少デバッグが楽になるのでは?という発想から、ゲームオブジェクトのフィールド・プロパティをインスペクタに表示するエディタ拡張を作りました。
ちなみにゲームオブジェクトのフィールドだけだったらデフォルトのインスペクタでも表示できます。

サンプル

サンプルプロジェクト

サンプルコンポーネント

using UnityEngine;

[Inspectable]
public class SampleComponent : MonoBehaviour
{
    [Inspectable]
    int sampleInt;

    [Inspectable]
    float sampleFloat;

    [Inspectable]
    public bool SampleBoolean { get; private set; }

    [Inspectable]
    Vector3 SampleVector3 { get { return transform.position; } }

    [Inspectable]
    Color SampleColor { get { return Color.black; } }

    [Inspectable]
    SampleType SampleType { get; set; }

    int ignoreInspection = 1;

    void Start()
    {
        sampleInt = 42;
        sampleFloat = 0f;
        SampleBoolean = true;
        SampleType = new SampleType();
    }

    void Update()
    {
        sampleFloat += Time.deltaTime;
    }
}

こんなコンポーネントを作ってヒエラルキーに設置しておくと

sc1

このように表示されます。

仕組み

カスタムエディタの仕組みを利用して、特定のコンポーネント(DebuggingInspector)をアタッチしたゲームオブジェクトにフォーカスがあたった場合、デフォルトのインスペクタを上書きしデバッグ情報を表示するようになってます。
デバッグ表示対象のコンポーネントはヒエラルキーのルート直下を走査して[Inspectable]属性の有無を確認し抽出しています。[Inspectable]属性がついていた場合は、クラス内に[Inspectable]属性のついたフィールド・プロパティを探してリフレクションを用いて値を取得しインスペクタに表示しています。
デバッグ用ゲームオブジェクトにフォーカスがあたっている場合、基本的には毎フレーム属性の走査が走ってしまいますが、インスペクタ上部にあるAutoRefreshのチェックを外すと更新を行わなくなります。
実装

上記サンプルの

[Inspectable]
SampleType SampleType { get; set; }

では自分で定義した型のプロパティに属性をつけていますが、この場合でもクラスの属性に[Inspectable]をつけることで型定義を辿って入れ子に表示してくれます。

入れ子対象の例

using UnityEngine;

public enum SampleEnum
{
    Yuno,
    Miyako,
    Sae,
    Hiro
}

[Inspectable]    
public class SampleType
{
    [Inspectable]
    Vector2 SampleVector2 { get; set; }

    [Inspectable]
    public SampleEnum SampleEnum { get; private set; }

    public SampleType()
    {
        SampleVector2 = Vector2.one;
        SampleEnum = SampleEnum.Yuno;
    }
}

実際使ってみて

  • 実行中に気軽に中が見れて便利
  • ログ出力コードを書いたり消したりしないでいいのが楽
  • 毎フレームログ出力してしまうよりは遥かに見やすい
  • 雑に[Inspectable]書いておいて放置できるので重要な部分にはとりあえず付けておくと、後々デバッグ時に助けられることが結構あった
  • 副作用のあるプロパティを対象にすると、アクセスのタイミングが変わってしまっておかしくなりそうなので注意が必要
  • 対象が多くなりすぎて見えにくくなってきたのでフォールディングしたい

まとめ

実装簡単で、使ってみるとわりと便利でした。

はじめに

こんにちは
KLab Advent Calendar8日目の記事です。
KLabGames事業部エンジニアの@knsh14です。

みなさんはUnityで開発をする際にどのようにしてアプリのパフォーマンスを計測していますか?
今回はある案件で今までと違うメモリ監視の方法を使ってみたら、デバッグが捗った話を紹介しようと思います。

KLabgamesでは幾つかのゲームでUnityを用いて開発を行っています。
よりよい品質でユーザ様に遊んでいただくために、常にパフォーマンス・チューニングを行っています。
その中でも各シチュエーションでのメモリ使用量を計測し、改善することは低スペック端末などで動作させるために必須です。
ですが、Unity標準のProfilerでは適切にメモリ使用量を計測できない問題がありました。

今までの方法

これまでは以下の様なコードでメモリ使用量を取得して画面に表示していました。

uint totalUsed = Profiler.GetTotalAllocatedMemory();
uint totalSize = Profiler.GetTotalReservedMemory();

System.Text.StringBuilder text = new System.Text.StringBuilder();

text.Append((totalUsed / (1024f * 1024f)).ToString("0.0"));
text.Append("/");
text.Append((totalSize / (1024f * 1024f)).ToString("0.0"));
text.Append(" MB(");
text.Append((100f * totalUsed / totalSize).ToString("0"));
text.Append("%) ");

これをOnGUIで表示するなり、NGUIのラベルに出力するなりしていました。

シンプルですね。
ここで使っているメソッドを調べてみましょう。
Profiler.GetTotalAllocatedMemory()
Profiler.GetTotalReservedMemory()

公式のリファレンスにもなにも書いてない...
実際に動かした結果とプロファイラを見比べてみると
device-screenshot
unity-profiler-screenshot
instruments-screenshot

全ての計測値にかなり開きがありますね。これでは計測の目安とするには不十分です。

ネイティブプラグインを書いてみる

ではどうすればいいのでしょうか?

僕らが見たいメモリ使用量は各OSから見てUnityAppがどれほどのメモリを使っているかというものです。
なのでUnityから利用できるネイティブプラグインを書いてそこから各OSに問い合わせればいいわけです。

こうして書いたネイティブプラグインコードがこちらです。

iOSの場合

#import <Foundation/Foundation.h>
#import <mach/mach.h>

unsigned int getUsedMemorySize() {
    struct task_basic_info basic_info;
    mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
    kern_return_t status;

    status = task_info(current_task(), TASK_BASIC_INFO, (task_info_t)&basic_info, &t_info_count);

    if (status != KERN_SUCCESS)
    {
        NSLog(@"%s(): Error in task_info(): %s", __FUNCTION__, strerror(errno));
        return 0;
    }

    return (unsigned int)basic_info.resident_size;
}

このコードをAssets/Plugins/iOSに適切に配置して読み込むとiOSからUnityAppが使用している物理メモリ量を取得することができます。
ネイティブプラグインを読み込む方法は世の中にたくさんあるのでここでは割愛します。

task_info関数は公式リファレンスによると、現在のtaskに関する情報の配列を返すとあります。
第2引数のflavorにTASK_BASIC_INFOを指定すると第3引数に与えたtask_infoに以下のような構造体が入ります。

struct task_basic_info {
        integer_t       suspend_count;  /* suspend count for task */
        vm_size_t       virtual_size;   /* virtual memory size (bytes) */
        vm_size_t       resident_size;  /* resident memory size (bytes) */
        time_value_t    user_time;      /* total user run time for
                                           terminated threads */
        time_value_t    system_time;    /* total system run time for
                                           terminated threads */
    policy_t    policy;     /* default policy for new threads */
};

この中のresident_sizeがtaskが使っているメモリ量です。
iOSではtask = プロセス = アプリなのでこれでiOSから見たUnityアプリがどれ位メモリを食っているかを確認することができました。

Androidの場合

Androidの方ではtask_infoは使えませんが、代わりにAndroid自体に取得できる機能があったのでそちらを使いました。

import android.os.Debug;
import android.os.Process;
import android.app.ActivityManager;
import android.content.Context;

/**
 * @return 現在使用しているメモリ量(KB)
 */
private static long getUsedMemorySize() {
    final Context context = UnityPlayer.currentActivity.getApplication().getApplicationContext();
    final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    final int[] pids = new int[]{ Process.myPid() };
    final Debug.MemoryInfo[] memoryInfos = activityManager.getProcessMemoryInfo(pids);
    long sumMemories = 0;

    for (Debug.MemoryInfo mi : memoryInfos) {
        sumMemories += mi.getTotalPss();
    }

    return sumMemories;
}

Android公式リファレンスを見るとDebug.MemoryInfoがいろいろな情報を持っているのでこちらを利用します。
プロセス毎にメモリ使用量が取れるのでそれを合算して表示します。

改修後

両プラットフォームともネイティブコードを書いたところで実際にプロファイラと見比べてみましょう

iOS

device-screenshot
instruments-screenshot

Instrumentsから得られた物理メモリ使用量と画面に表示しているメモリ使用量が一致していますね!

Android

device-screenshot

プロファイラからは以下のようになりました

kamata% adb shell dumpsys meminfo | grep gopher
    50409 kB: com.example.gopher (pid XXXXX / activities)
               50409 kB: com.example.gopher (pid XXXXX / activities)

50409 / 1024 = 49.2MB

この通りメモリ使用量がプロファイラから見たものと一致していますね!
こうしてUnityApp上で各OSからみたメモリ使用量を可視化することができました。
これで、実際の端末上で気軽にメモリ使用量を計測することができますね!

最後に

いかがでしたか。

このように簡単なコードを一手間加えるだけでプロファイリングが格段に楽になりましたね。
時には標準Profiler以外の方法も試してみると、よりUnityと上手く付き合っていけるのでは無いでしょうか。
この記事を見てくださった方のメモリ監視が楽になることをサンタさんにお願いして終わりたいと思います。

なお、今回作成したサンプルで使われているGopherのモデルはGitHubにあるこちらのモデルを利用しました。
Gopherの原著作者はRenée Frenchさんです。

次回は@hnwさんの「Autotools三兄弟の末っ子libtoolとは何者なのか」です。
僕の記事より面白そうなので今から楽しみです!

このエントリーは、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さんのラズパイの話です。 お楽しみに。

バーチャルリアリティーは漢のロマン

昔、私が高校生くらいの時にもVRブームがあり、バーチャルリアリティエキスポなるイベントで、 筋斗雲的な何かに乗ったり、高速に上下するLEDアレイディスプレイで三次元を表示するゲームなどに、 ワクワクしたことを思い出します。

そして、ここ数年、再びバーチャルリアリティー(VR)が盛り上がっています。 Oculus Riftや SCEのProject Morpheusといった ヘッドマウントディスプレイも格段に進歩し、 コンピュータグラフィックスやGPUなどの技術の進歩のおかげで、 非常にリアルな仮想現実没入体験が、簡単に手の届く領域にきています。

そんななか、私がもっともワクワクしているのが、 Googleの「Cardboard」です。 なぜなら、非常に安く手軽に手に入れ使うことができ、特にユーザーに身近なものになりうるからです。

本稿では、Cardboardを使ってVRでロボットに搭乗して操縦するデモを作成したことを紹介します。

Cardboardを使ってみる

CardboardはGoogleが公開した段ボールでできたスマフォ用VRアタッチメントです。 設計図も公開されているので、100円ショップで買ったルーペのレンズと、 段ボールと、テープと工具があれば自分で作ることができます。

fig1

簡単!Cardboardを自作する

実際にGoogleが公開しているデータで、 Cardboardを二つほど作ってみました。 レンズは、100円ショップで手に入るルーペを分解すると手に入ります。 私は、ダイソーで写真のルーペを買いました。なんとレンズが二つ入っていて、 一つ買うだけで両目分のレンズが手に入ります。

fig2

レーザーカッターがあれば簡単なのでしょうが、残念ながらないので、 プリントアウトした型紙を段ボールに貼り、 地道に普通のカッターで切り抜いて組み立てます。

純正のCardboardには、頭に固定するバンドがないので、 手芸用のゴム紐とマジックテープで簡単な固定用バンドを追加しました。

以下が実際に自作してみたものです。

fig3

UnityでCardboard(Durovis Dive編)

最初に試したのが、このDurovis Diveです。 リンク先の開発者ページにあるSDKをダウンロードし、 unitypackageをインポートして使います。

インポートしたDive/Prefabs/にあるDive_CameraというPrefabを、 シーンに追加すると、ヘッドトラッキング付両眼カメラになります。 このカメラをメインカメラにすることで、非常に簡単にCardboardなどの スマフォ用VRアタッチメントに対応させることができます。

UnityでCardboard(CardboardSDK編)

次に、Google Cardboard公式のUnitySDKを試してみましょう。 Cardboard Developper Page(Unity)を見ると、Download and Samplesからunitypackageをダウンロードできます。 プロジェクトにインポートすると、Cardboardフォルダができます。

Durovis Diveと同様に、 Cardboard/Prefabs/にある、CardboardMainというPrefabをシーンに追加することで、カメラになります。 これだけで、Cardboardに対応させることができます。

どちらにしたのか

今回は、両方とも実装してみての確認は行いましたが、 詳細な評価と比較はできていません。 実際のアプリではDurovis Diveを使いましたが、 これは作り始めた初期の時点でCardboardのUnity対応SDKがリリースされてなかったという理由によります。

どうやって操作するの?

スマフォVRアタッチメントを使用する場合、大きな問題となるのがユーザーインターフェースです。 Cardboardでは、これを磁石とスマフォの磁気センサーでスイッチを作って解決しています。 磁石二つの位置関係による磁力の変化を、スマフォの磁気センサーで検出することで、一ボタンに対応する入力を実現しています。 非常に面白い仕組みです。 Ver.2のCardboardでは、導電性の布とレバー機構を使うことで、ボタンを押すと画面にタッチされる機構が作られています。これも面白い仕組みですね。 一方で、どちらの入力方式でもアクション性の高いゲームに利用するには入力の少なさと、反応速度と操作しやすさから難しいです。

そうだ、スマフォ二台使おう

ここで、私が考えたのが、スマフォの加速度センサーやタッチパネルをコントローラーとして使えないかということでした。 スマフォの普及率が爆発的に増加した昨今、二つくらい前に使っていたスマフォが机の引き出しの中に眠っていたりしないでしょうか。 そうです、それらをコントローラとして使えばいいのです!!

そうなると、表示用と左手用右手用の都合三台のスマフォが必要です。 我が家には、幸い私と妻が以前使っていたiPhone4とiPhone4sが余っていました。 これをコントローラーとして使えるようにしてみましょう。

無線で本体とコントローラーをつなぐ

では、どのように表示+ゲーム実行用の本体スマフォと、コントローラー用スマフォをつなげばいいのでしょうか。 ここで今回は、WebSocketを使ってみました。 サーバーを介して、本体とコントローラをWebSocketでつないでみました。

fig4

クライアント側のUnityでは、WebSocketSharpのUnity対応のためのKLab改変版を使って、以下のような受信用プログラムが動いています。

using UnityEngine;
using System.Collections;
using WebSocketSharp;

public class WebSocketClient : MonoBehaviour {
    public const int RIGHT = 0;
    public const int LEFT = 1;
    public const int B1 = 0;
    public const int B2 = 1;
    public Vector3[] accel = new Vector3[2];
    public bool[,] button = new bool[2,2]{{false,false},{false,false}};

    private WebSocket ws;

    // Use this for initialization
    void Start () {
        ws = new WebSocket ("ws://URL/chat/test");
        ws.OnMessage += (object sender, MessageEventArgs e) => {
            string [] message = e.Data.Split(new char[]{':'});
            int controllerNo=RIGHT;// Default Right it is not good
            switch(message[1]){
            case "R":
                controllerNo = RIGHT;
                break;
            case "L":
                controllerNo = LEFT;
                break;
            default:
                break;
            }
            switch(message[2]){
            case "AC":
                accel[controllerNo] = new Vector3(float.Parse(message[3]),
                                                  float.Parse(message[4]),
                                                  float.Parse(message[5]));
                break;
            case "B":
                button[controllerNo,int.Parse(message[3])-1] =
                    (message[4].Equals("DOWN")) ? true : false;
                break;
            default:
                break;
            }
        };
        ws.Connect ();
    }

    // Update is called once per frame
    void Update () {
    }
}

Playerのコントロールスクリプトなどで、このクラスから情報を取得して操作します。

一方で、コントローラーは、HTMLファイルで作りました。ブラウザで表示するだけでコントローラーになります。

<!DOCTYPE html>
<html><head>
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no;">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
</head>

<body style='user-select: none; -webkit-user-select: none;'>
<script type="text/javascript">
document.addEventListener('touchmove', function(e) {
  e.preventDefault();
},false);

var ws;
ws = new WebSocket("ws://URL/chat/test");

function send(message){
  ws.send(message);
}

window.addEventListener("devicemotion",function(evt){
  var x = evt.accelerationIncludingGravity.x; //横方向の傾斜
  var y = evt.accelerationIncludingGravity.y; //縦方法の傾斜
  var z = evt.accelerationIncludingGravity.z; //上下方向の傾斜
  send("1:L:AC:"+x+":"+y+":"+z);
},false);
</script>
<div style="width: 100px; height: 100px; background-color: red; margin: 20px; float: right;" ontouchstart="send('1:L:B:1:DOWN')" ontouchend="send('1:L:B:1:UP')" ></div>
<div style="clear: right;"></div>
<br>
<br>
<div style="width: 100px; height: 100px; background-color: blue; margin: 20px; float: right;" ontouchstart="send('1:L:B:2:DOWN')" ontouchend="send('1:L:B:2:UP')" ></div>
</body></html>

このHTMLをコントローラーの識別番号を変えて左手用と右手用の二つ作ります。

中継用サーバーは、単純なWebSocketで届いたメッセージを接続しているクライアントにブロードキャストするだけのサーバーです。 clojure-aleph-chatを参考にしました。

バーチャロンへのオマージュ

二本のスティックでロボットを操作すると言えばあれですね。 加速度センサにより、端末の傾きをとることにより、 左手右手の両方のスティックを前に傾けると前進、 後ろに傾けると後退、前後に互い違いに倒すと回転、 横方向に同じ方向に倒すと横移動、 横方向に開くと上昇、閉じると下降という操作体系です。 皆さん、よくご存じですよね。

コントローラー画面には、攻撃ボタンとブーストボタンを表示してみました。 タップするとそれぞれの動作をします。

fig7

写真にあるように、ブラウザでコントローラー画面を表示させます。 赤が攻撃ボタン、青がブーストボタンです。 AndroidとiOSのどちらでも動きますが、AndroidとiOSで加速度センサーの軸が違っているので気をつけましょう。

プレイ風景・・・

遊んでいるところ(筆者近影)

fig5

実際の画面のスクリーンショット

fig6

まとめ

今回の実験は、三台のスマフォが必要というネックがありますが、 最近のスマフォの普及率を考えると意外と何とかなるのではないでしょうか。 実際我が家には、iOSだけで、3GS,4,4s,5,5s,6,初代iPadがありますし、 AndroidはHTC AriaとKindle Fire HDとKobo Arcがあります。 3台くらいきっとなんとかなりますよね。

また、WebSocketで一回サーバーを介するレイテンシも、プレイしてる分にはあまり気にならず、 作り方次第で十分隠蔽可能なのではないかという感想です。

そして最高ですね。この没入感、飛翔感。楽しいです。文章ではこの楽しさが伝わらないのが残念です。 今回は、搭乗して操縦する体験を目標にしたので、ゲーム性は皆無に等しいのですが、 今後ゲームとして成り立たせる方向も模索したいと思います。


oho-s

↑このページのトップヘ