KLabGames Tech Blog

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

連載目次

はじめに

@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

皆さん Docker 使ってますか?

なんだかよくわかんないからいいや・・・ そんなふうに考えていた時期が俺にもありました。

ということで今回は身近な例として、ローカルマシン上で Docker を使って開発環境を構築した事例を紹介します。 どちらかというと、ぶっちぎってサーバサイド寄りの話でしかも管理寄りの話だったりもしますが、その他の方々にも何かお役に立てれば幸いです。

問題

KLab にはたくさんの案件が有りますが、その中には WEBサーバやDBサーバなどが全部詰まった VM イメージを配布する、という方法でローカルの開発環境を提供している案件も有ります。 それらの案件も息が長くなり、下記のような状況が散見されるようになってきました。

  • 配布した VM を個人好みに育ててしまって、アップデートの心理的障壁となっている
  • 追加のミドルウェアを導入したが、環境のアップデートの伝播が行いにくい等
  • 個人が検証目的などで言語のバージョンを変えたり追加のエクステンションなどを入れたが、それを元に戻していない

その結果、チーム内メンバー間の環境の違いによるトラブル(テストが通らない等)が発生するというけしからん事態になってきました。

方向性

そこで、次の様な戦略でローカル開発環境をもっと簡単に管理していきましょう、というのが本論となります。

コードで管理する。環境の再現をコードで表しておくことで、管理が非常に楽になります。また、配布も git などのリポジトリ経由で気軽に行えるので、配布行為自体もラクになります。

個人の好みを配布物に含めない。コードを書く際のエディタやシェルその他(個人にとっての)便利ツールなどは完全に個人の好みの問題であるので、実行環境とは完全に切り離すべきです。 実行環境を不可侵にしておけば、心理的障壁はなくなるはずです。

構築物イメージ

上記のようにしておけば、気軽に作り直せるし、配布も簡単だし、良いことずくめです。

構成の再現

さて、KLab の場合 http を介したモバイルゲームサービスが主ですから、サーバサイドといってもユニットテストだけでなく、なるべく end-to-end なテストも存在します。 従って、開発環境としても WEB サーバや DB サーバなど、もろもろのサービスが必要となってきます。

これらのサービスをそれぞれ、別のコンテナとして立ち上げていくのが Docker 流のやり方です。 とはいえ、各サービスに対してちまちま docker コマンドを叩いていたのでは埒が明きませんから、docker-composeを利用して簡潔に管理できるようにしています。 例えば下記のような感じです。

docker-compose.yml.dist

web:
  build: ./web
  links: [db, memcached, redis]
  volumes:
    - %%SERVER_REPO_DIR%%:/app/src
    - %%LOG_DIR%%:/var/log/www
  ports: ["8080:80"]
ci:
  build: ./ci
  links: [web, db, memcached, redis]
  volumes:
    - %%SERVER_REPO_DIR%%:/app/src
  command: sleep infinity
db:
  build: ./db
memcached: { image: "memcached:1" }
redis: { image: "redis:2.6" }

このようにして、細かくイメージを作りこみたい場合は Dockerfile を作成、それ以外はオフィシャルイメージを流用するなどして、サービス群の関連などと共に記述していきます。

docker ファミリーのインストールが済んでいれば、環境の構築やアップデートは、

  1. これらのファイルが格納されたリポジトリを clone/pull して最新の構成情報を取得
  2. ビルドする (docker-compose build をたたくだけ)
  3. サービスを開始する (docker-compse up をたたくだけ)

を実行してもらうだけなので、実行環境の再現は非常に簡単です。

docker-compose.yml「.dist」 となっているのは、テンプレートとして使用しているためです。 マウントするパス(%%で囲まれている)は人によって異なるのが理由で、 本環境の導入時に、インストーラースクリプトを実行してもらう事によって実体を作成しています。

腐敗防止層

先述の「腐敗防止層」と名付けられた部分は、利用者が docker そのものを意識しなくてすむような、アダプタ的コマンド集の形をしています。 例えば、下記のようなインターフェイスを提供しています。

myenv build                     # docker-compose build などを集約
myenv up                        # docker-compose up などを集約
myenv server test --group Hoge  # テストを実行する
myenv server init-db            # DB の初期化 & test ready な状態にする
myenv server db-cli             # DBのインタラクティブシェル
myenv server shell              # バッチ実行用サーバを模したインタラクティブシェル

dockerdocker-compose などのコマンドは便利ですが、細かいオプションや docker の概念などを短時間でマスターして下さい、というのも難しい話ですし、何より myenv を実行環境への唯一のエントリーポイントとしておくことで、環境が育ってしまうことが避けられます。

依存を少なく

コマンドの dockerize

開発に必須なツール達が色々なソフトウェアに依存していることもあると思います。 例えば、PHP がないと動かない composer など。 これらも全て docker 上で実行するようにスクリプト化しておけば、面倒な依存のインストールの負担をかけなくてすみます。

# 最小構成の例です。実運用では他にも git などが必要だったり、
# composer が作るキャッシュを volume して永続化するなどの工夫が必要になると思います。
echo 'FROM php:5.6-cli
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin
ENTRYPOINT ["composer.phar"]
' | docker build -t php-composer - > /dev/null
docker run -it --rm -v `pwd`:`pwd` -w `pwd` php-composer "$@"

このようにコマンド単位であっても dockerize して依存を少なくし、環境構築がすぱっといくようにしています。

docker-compose を Windows で動かす

Docker client は Windows でも動くようになりましたが、docker-compose は実は Windows をサポートしていません。 色々なやり方があるかもしれませんが、例えば boot2docker を使用している場合は、docker-compose というイメージ名で docker/compose:Dockerfile をビルドしておけば、下記のように利用できます。

boot2docker ssh -t "docker run -it --rm -v `pwd`:`pwd` -v /var/run/docker.sock:/var/run/docker.sock -w `pwd` docker-compose $@"

イメージのビルドも含め、先ほどの myenv build に組み込んでおけば、Windows でも大丈夫ですね!

まとめ

Docker をどっかどっか使って、管理のしやすさを念頭に置いたローカル開発環境の事例を紹介しました。

今回紹介した事例では web サーバが動いていますので、サーバサイドの開発・テスト用途だけではなく、クライアント開発時の http API サーバとしても利用できます。 ここからさらに発展させて、クライアントのテスト実行環境、その他運用上必要なツールの開発に至る、案件の包括的な開発基盤として発展しているケースもありますが、これについてはまた別途紹介したいと思います。


@gilbite a.k.a. イケ☆メン

↑このページのトップヘ