KLabGames Tech Blog

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

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

昔、私が高校生くらいの時にも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

連載目次

はじめに

@tenntennです。

もうすぐGoのバージョン1.5がリリースされる予定ですが, みなさまはどの機能に注目しているでしょうか? コンカレントGCだったりshared libraryが作れるようになったりと,Go 1.5は非常に楽しみです。 その中でも私は,Go 1.4で入ったGo Mobileのアップデートに注目しています。

Go Mobileは,Goを使ってモバイルアプリを書くためのツール類を提供するプロジェクトです。 Go 1.5では,iOS向けのアプリがビルドできるようになったり,Androidのサポートが強化されるようです。 masterブランチの最新(2015年7月19日 時点)では,すでにiOS向けのビルドもできるようになっているみたいです。 実際に,GoチームによってIvyというAPLライクな言語のインタプリタがiOSAndroid向けにリリースされています。

私も先日,Go Conference 2015 SummerのLT大会で使用したLTタイマーのGoFunソースコード)を試しに個人でGoogle Play Storeでリリースしてみました。 私が作成したGoFunというアプリは,Javaを使わずすべてGoだけで書いています。 JavaとGoを使った記事はよく見かけるのですが,Goだけで書いている記事はあまり見かけません。 また,Google Play Storeに公開するところまで扱っている記事も見かけません。 そこでこの記事では,複数回に分けてその時に調べたGoだけでAndroidアプリを作ってリリースするまでの手順を説明します。 まず今回は,Go Mobileの簡単な説明とインストール方法を説明したいと思います。

この記事で扱う手順やノウハウは,記事執筆時(2015年7月19日)のものです。 Go Mobileはまだまだ開発途中です。 現在(執筆当時)も毎日のように破壊的な変更がされています。 そのため,ビルド方法やパッケージ名,各ライブラリやツールの使い方が変わる可能性があります。

なお,この記事ではMac OSX Yosemite(10.10.3)とNexus 9(Android 5.1.1)で動作検証をしていますが,他のOSやAndroid端末では動作検証をしてまいません。 Go MobileはLinuxでは動くようですが,Windowsではまだ動かないようです。 Go Mobileのコード上にはWindowsの記述があるので,そのうち対応されるでしょう。

Go Mobileのインストール

最新のGo Momobileを使用するには,Go 1.5が必要です。 Go 1.5のベータ版をダウンロードするかソースコードからビルドしてください。 なお,Go 1.5のビルドには,Go 1.4が必要です。

Go 1.5のインストールが終わったら,次はgomobileコマンドをgo getします。

$ go get golang.org/x/mobile/cmd/gomobile

$GOPATH/bin以下にgomoibleコマンドががインストールされます。 $PATH$GOPATH/binが含まれていない場合は追加しておきましょう。 これでgomobileコマンドが使えるようになったはずです。

$ gomobile -h
Gomobile is a tool for building and running mobile apps written in Go.

To install:
....

つづいて,gomobile initを実行しましょう。 このコマンドは,Go Mobileを使うために必要なものをインストールしてくれます。 どうやら,モバイル端末向けにクロスコンパイルするためのGoツールチェーンやAndroid NDK,OpenAL(libopenal)がインストールされるようです。 執筆当時のバージョンではandroid-ndk-r10d$GOPATH/pkg/gomobile/以下にインストールされるようでした。 なお,実行には結構時間がかかるので,-vをつけて進捗を確認するとよいでしょう。

$ gomobile init -v

Goだけでモバイルアプリを書く

Go Mobileでは,以下の2種類の方法でGoを使ってモバイルアプリを開発することができます。

  • Java(Android)やObjective-C(iOS)からGoで書かれた処理を呼び出す
  • GoでOpenGLやOpenALを使ってアプリを書く

Go Mobileは,AndroidやiOSが提供するAPIのラッパーをすべて用意することを目的としてはいません。 GUIなどは通常のAndroidやiOSのアプリの開発と同じように,JavaやObjective-Cを使い,Goが得意なところはGoに任せるといった具合に使用することを想定しています。 (Swiftから呼び出せるのかは調べてません。すいません。)

一方で,OpenGLやOpenALの関数が呼び出せるようになっています。そのため,ゲームなどOS標準のUIを使わない場合は,これらを使用して開発できるようになっています。 しかしながら,まだ提供されている機能は低レベルな関数が多く,多くのパッケージがexp以下に配置されていることから分かるように,まだまだ実験的に実装されているだけのようです。

この記事では,GoだけでAndroidアプリを作る方法について説明します。 JavaからGoで書かれた処理を呼び出す方法については,いくつか日本語でも記事があるので探してみるとよいでしょう。

なお,Go Mobileのアプリからでも,ほとんどのGoの標準パッケージで提供される機能が使用できるようです。

サンプルを動かしてみる

Go Mobileのリポジトリには,exampleというディレクトリがあります。 この中には,Go Mobileを触れてみるのにちょうど良いサンプルがいくつか入っています。 ここでは最もシンプルなサンプルのexample/basicを使って動かし方を説明していきます。

まずはMac上で動かしてみましょう。 サンプルのあるディレクトリまで行き,go runコマンドで実行します。

$ cd $GOPATH/src/golang.org/x/mobile/example/basic/
$ go run main.go

うまく実行できると下のような赤い背景に緑の三角形がでるはずです。 緑の三角形はドラッグできるので,ぜひ動かしてみてください。

basic

Mac上で動かせることは確認できましたので,次にAndroid上で動かしてみましょう。 gomobile buildコマンドを使うと,モバイルアプリ用にビルドができます。 デフォルトではapkが生成されます。-target iosと指定すると,iOS向けのappファイルがビルドされます。 appからipaを作ればiOSでも動かせるとは思いますが,私の環境では実行できるipaは生成できませんでした。

$ cd ~/Desktop #どこでもよい
$ gomobile build golang.org/x/mobile/example/basic

上記のコマンドを打つと,カレントディレクトリにbasic.apkが生成されているかと思います。 adb installでAndoridにインストールして実行してみましょう。

$ adb install basic.apk

うまくいくと以下のようにAndroid上でアプリが実行できます。

basic_android

上記の例では,gomobile buildを使用しましたが,gomoible installを使えば,ビルド後にadb installを使って自動でAndroidへのインストールするところまでやってくれます。 gomobile installを使用するには,adbコマンドが必要となります。 Android SDKの開発環境を用意して,adbコマンドにパスを通しておきましょう。 なお,gomobile installコマンドはビルドターゲットがandroidでないと動作しませんので,注意してください。

$ gomobile install golang.org/x/mobile/example/basic

いかがだったでしょうか?Goだけで書かれたアプリがAndroid端末上で動くのは感動しますよね。 次回は,サンプルを見ながらGo Mobileで提供されているspriteパッケージについて説明する予定です。

※この記事ではgitのタグ「v2.75」から生成したブランチ上でコードリーディングしています。

こんにちは。前回まではタグ「v2.74」でコードリーディングしていましたが、今回からは「v2.75」を使います。

さて、今回は初心者から上級者までよく使うモディファイアであろうSubdivision Surfaceについて追いかけていこうと思います。

Subdivision Surfaceモディファイアは私がBlenderを学び始めてから最初に触れたモディファイアです。私はアーティストではないので立派な作品が作れる訳ではないのですが、ゲーム開発の際にテスト用のモデルを作るときやスカルプトの元となるメッシュを作るとき等、個人的には最も使用頻度の高いモディファイアです。「最も良く使うモディファイアは?」と聞かれたときにSubdivision Surfaceを挙げる人は多いのではないでしょうか?

Subdivision Surfaceってなに??

What is Subdivision Surface?
です。(ゲーム開発のテスト用として作ったモデルより)

まずはキーワード検索

それではコードリーディングを始めます。

まずはそれっぽいキーワードで検索して読む場所の狙いをつけていきます。

「Subdivision(Any)Surface」で検索すると
 8 results in 4 files これくらいなら1つずつ見ていけますね。こんなenum値が見つかりました。

typedef enum ModifierType {
    eModifierType_None              = 0,
    eModifierType_Subsurf           = 1,
    eModifierType_Lattice           = 2,
    eModifierType_Curve             = 3,
    eModifierType_Build             = 4,
    eModifierType_Mirror            = 5,
    /* 省略 */
    eModifierType_CorrectiveSmooth  = 51,
    NUM_MODIFIER_TYPES
} ModifierType;

Subsurf は1番ですか。やはり基本的なモディファイアとして据えられているようですね。このenum値の定義がある事は覚えておく事にします。

ただ、「Subdivision(Any)Surface」を検索しても頂点の位置を計算しているような関数がヒットしませんでした。そこで少し視点を変えて、Subdivision SurfaceモディファイアのApplyボタンを押したときの挙動から追いかけていく事にします。

Applyボタンにマウスカーソルを当て、Pythonコードを表示します。 Apply
このPythonコードと同じように「modifier_apply(」で検索すればヒントになるかもしれません。

検索結果は
8 results in 5 files
今回もうまく絞り込めたようです。

見つかった関数は

static DerivedMesh *dynamicPaint_Modifier_apply(DynamicPaintModifierData *pmd, Object *ob, DerivedMesh *dm)  // ダイナミックペイントなので関係なさそう
int ED_object_modifier_apply(ReportList *reports, Scene *scene, Object *ob, ModifierData *md, int mode)
void OBJECT_OT_modifier_apply(wmOperatorType *ot) // ウインドウマネージャーのオペレータータイプが関係してるのでモディファイアの中身に関する処理だとは思えない
static void maskmodifier_apply(struct SequenceModifierData *UNUSED(smd), ImBuf *ibuf, ImBuf *mask) //マスクは今回関係ないと思う

こんな感じです。

というわけで

int ED_object_modifier_apply(ReportList *reports, Scene *scene, Object *ob, ModifierData *md, int mode)

を細かく調べていきましょう。

ステップ実行

ED_object_modifier_apply関数の先頭部分にブレイクポイントを張ってApplyボタンを押します。するとちゃんとこの関数を通ることが確認できます。
ここで引数のModifierDataというのが気になるので定義に飛んでみます。すると先ほど登場したenum値「ModifierType」のすぐ近くに定義されていました。

typedef enum ModifierType {
    eModifierType_None              = 0,
    eModifierType_Subsurf           = 1,
    eModifierType_Lattice           = 2,
    eModifierType_Curve             = 3,
    eModifierType_Build             = 4,
    eModifierType_Mirror            = 5,
    /* 省略 */
    NUM_MODIFIER_TYPES
} ModifierType;

typedef enum ModifierMode {
    eModifierMode_Realtime          = (1 << 0),
    eModifierMode_Render            = (1 << 1),
    eModifierMode_Editmode          = (1 << 2),
    eModifierMode_OnCage            = (1 << 3),
    eModifierMode_Expanded          = (1 << 4),
    eModifierMode_Virtual           = (1 << 5),
    eModifierMode_ApplyOnSpline     = (1 << 6),
    eModifierMode_DisableTemporary  = (1 << 31)
} ModifierMode;

typedef struct ModifierData {
    struct ModifierData *next, *prev;

    int type, mode;
    int stackindex, pad;
    char name[64];  /* MAX_NAME */

    /* XXX for timing info set by caller... solve later? (ton) */
    struct Scene *scene;

    char *error;
} ModifierData;

int type, mode;にはenum値「ModifierType」「ModifierMode」がそれぞれ入りそうですね。「ModifierType」の中でSubdivision Surfaceモディファイアを示す「eModifierType_Subsurf」の値は1です。
ModifierData *mdのtypeをデバッカで確認してみると1が代入されているので、このmdはSubdivision Surfaceモディファイアを示すModifierDataであることが確認できます。

さてここからメッシュを形作るための計算をしている場所を目指していきたいと思います。
ここから先は紛らわしい名前の関数はないのであまり迷いなくステップインしていけます。 ED_object_modifier_apply()
 ー>modifier_apply_obdata()
  ー>mesh_create_derived_for_modifier()
   ー>modwrap_applyModifier()
    ー>applyModifier()
     ー>subsurf_make_derived_from_derived()

struct DerivedMesh *subsurf_make_derived_from_derived(
        struct DerivedMesh *dm,
        struct SubsurfModifierData *smd,
        float (*vertCos)[3],
        SubsurfFlags flags)

という関数までたどり着きました。この関数はDerivedMesh型のポインタを引数として受け取り、同じ型のポインタを返す関数です。おそらく引数の方が変形前のメッシュ、戻り値の方が変形後のメッシュなのだと予想できます。関数の内容は、

struct DerivedMesh *subsurf_make_derived_from_derived(
        struct DerivedMesh *dm,
        struct SubsurfModifierData *smd,
        float (*vertCos)[3],
        SubsurfFlags flags)
{
    /* 省略 */
    CCGDerivedMesh *result;

    /* 省略 */

            result = getCCGDerivedMesh(ss, drawInteriorEdges, useSubsurfUv, dm);

    /* 省略 */

    return (DerivedMesh *)result;
}

となっているので、dmとresultsの頂点数を比較してみると確認できるでしょう。頂点数を確認するには、

            printf("before: %d\n",dm->numVertData);
            result = getCCGDerivedMesh(ss, drawInteriorEdges, useSubsurfUv, dm);
            printf("after:  %d\n",result->dm.numVertData);

とすればよく、結果は
 before: 8
 after: 26
となります。

デフォルトのキューブの頂点数は当然8で正解、変形後のメッシュの頂点数はBlenderの画面の表示 num of verts と一致していますね!

CCGSubSurf

というわけで、いろいろ確認しながら

static CCGDerivedMesh *getCCGDerivedMesh(CCGSubSurf *ss,
                                         int drawInteriorEdges,
                                         int useSubsurfUv,
                                         DerivedMesh *dm)

にたどり着きました。この中でSubdivision Surfaceの計算を行っているようです。

この「CCG」ってなんだろう??引数の型「CCGSubSurf」で検索してみましょう。

すると
http://minormatter.com/zr/software/ccgsubsurf
にたどり着きます。 「Catmull-Clark Gridding Subdivision Surface Library」CCGとはこの事だったのですね。「Catmull-Clark法」というアルゴリズムに基づいたSubdivision Surfaceライブラリだそうです。この記事の執筆時点ではv0.01がダウンロードできるようで、.h/.cファイル1つずつのシンプルなものになっています。Blender2.75にはこのライブラリが組み込まれているようですね。

Subdivision Surfaceの今後

2015年中の開発が期待されるBlenderのプロジェクト18 anticipated Blender development projects of 2015の中に「OpenSubdiv」の導入があります。

「OpenSubdiv」はPixarのオープンソースプロジェクトだそうで、GPUでのSubdivision Surfaceの処理が可能になるようですね。CCGにはGPUで計算しているような箇所は見受けられなかったので今後Subdivision Surfaceモディファイアが高速に処理できるようになる事が期待できそうです。コレは楽しみですし、今後様々なモディファイアやその他の処理にGPGPUが活きてくることが予想されますね!


@fmystB

↑このページのトップヘ