BlogKLabは、多くのスマートフォン向けゲームを開発・提供しています。スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。使っていますか?おそらくスマートフォンゲームを制作している企業の開発者の方々は基本的な機能への対応を済ませていることと思います。端末間でメッセージを往復させた時にかかる時間について調べました。いうものか概要を見ていきましょう。ログインによりプレーヤーを認証することで以下の様な機能が提供されています。Leaderboards全世界でのランキングやソーシャル内でのランキングが見ることができます。Achievements(実績)プレーヤーは条件を達成することで実績を得て、それに応じた経験値を得ることができます。サーバーに保存でき、それらは複数端末で同期されます。Multiplayer最大8人の部屋を作りその中でリアルタイム通信をすることができます。Multiplayer最大8人の部屋を作りその中でターン制ゲームをすることができます。Questsプレーヤーの進行度合いを集計し統計解析することができます。デベロッパーコンソールでゲームを追加することで使うことができます。サンプルコードが用意されています。使うことで、自分でサーバを用意しなくても複数人でのリアルタイム通信を実現できます。ネットワークで構成されます。サーバーが中継をします。使ったゲームを開始するときの手順は下記の通りです。増やしながら例外が吐かれるまで計測しました。おまけ(上限調査)詳細な計測数値は別表に示します。それぞれの通信速度を、送信データ量を変えながら計測しました。メッセージのデータ量による通信時間変化は、規格として定められている範囲では特に大きな差はありませんでした。通信時間はおおよそ下記のとおりとなりました。使われている)では距離の差が通信時間に表れました。距離の影響を受けそうですが、今回の計測では日本国内でも日米間でもほぼ変わらないという結果となりました。サーバーを経由で通信します。サーバーに接続している可能性があります。今回はパケットキャプチャを行わなかったので正確な原因は分かりません。開発者アカウントがあれば手軽にリアルタイム通信を使えることは大きなメリットかと思います。興味のある方はぜひ使ってみてください。通信時間計測レポートでした。Storeに公開しよう〜(予定)はじめに@tenntennです。前回の記事では,spriteパッケージといくつかのイベントについて扱いました。前回の記事を読んでいただいた方は,Android上で画像の描画と移動,回転,拡大/縮小を行うコードをGoだけで書けるようになったのではないかと思います。前回の記事を執筆後にevent/configがevent/sizeに変更されました。確かにサイズに関する情報しかconfig.Eventには含まれていなかったので,納得できる名前変更です。Mobileの情報が徐々に整理されてきました。bindを使ったSDKアプリケーションのサンプルが追加されました。今回は前回扱わなかった,event/touchやevent/lifecycleについて扱おうと思います。Mobileでタッチイベントを使った簡単なゲームなら実装できるようになるでしょう。この記事は,2015年8月16日時点の情報を基に執筆しております。これからもしばらくは,event/configのような破壊的な変更が加わる可能性は十分ありますので,ご注意ください。この記事を執筆当時の最新のコミットはbb4db21edcf86f8e35d2401be92605acd79c8d10です。Mobileのリポジトリのリンクは,このコミットを基準にしております。Mobileのリポジトリから引用したものです。タッチイベント第1回目の記事で動かしてみたexample/basicを覚えていますでしょうか?このサンプルでは,画面をタッチするとタッチした位置に,緑の三角形が描画されていたと思います。Mobileでは,画面をタッチするとtouch.Eventが送られてきます。このサンプルでは,送られてきたtouch.Eventをハンドルしてタッチした位置を取得し,その位置に三角形を描画しています。前回の記事で説明した通り,イベントをハンドルするには,app.AppのEventsメソッドで取得されるチャネルからイベントを受信します。})}タッチイベントを表すtouch.Eventは以下のように定義されています。Type}XやYはご想像の通り、タッチした座標です。これらの値はピクセル単位なので,spriteパッケージで使われるgeom.Pt単位にするには,size.Event.PixelsPerPtを基に計算してやる必要があります。Sequenceは,一連のタッチイベントに付けられるシーケンス番号です。指を同時に画面にタッチすると、タッチイベントが同時に複数発生します。そのため,シーケンス番号は、それらを区別するために利用します。Typeには,タッチイベントの種類を表す値が入っています。タッチイベントの種類は,TypeBegin,TypeMove,TypeEndの3種類が存在し,以下のように定義されています。TypeEnd)1本の指を画面に置いてから,離すまでの間に複数のタッチイベントが発生します。それらの一連のタッチイベントは同じシーケンス番号が振られ,指がどの状態にあるかはTypeで取得します。画面に指を置いた瞬間にTypeがTypeBeginのタッチイベントが発生し,指を動かす度にTypeがTypeMoveのイベントが発生します。画面から指を離すと,TypeがTypeEndのイベントが発生し,一連のシーケンスのタッチイベントが終了します。シーケンス番号は常に一意である訳ではなく,そのアプリの起動中に何度も同じシーケンス番号のイベントが送られてきます。現在の実装では,画面に指を置いた順にシーケンス番号を振られ,指を離すとそのシーケンス番号は開放され、再度別の指を置くと同じシーケンス番号が使いまわされます。新しくシーケンス番号を振る場合は,現在どの指にも振られていない0以上のもっとも小さい数を振るように実装されています。タッチイベントは基礎的な情報しか提供していませんが,これらをうまく使うことでドラッグやフリックなどを実装することができます。Webフロントエンドのタッチイベントのライブラリなどを参考にして、高レベルなタッチイベントを行うライブラリを実装しても面白いでしょう。Mobileにおいて,アプリのライフサイクルはlifecycle.Eventで表わされます。Mobileでは,複数のプラットフォームを一元的に扱うためOnStartやOnStopなどのAndroidのActivityのライフサイクルとは一致していません。lifecycle.Eventは以下のように定義されています。Stage}ライフサイクル中のある状態をStageという型で表現し,lifecycle.Eventは,とあるStageから別のStageに移ることを表しています。Stageには,StageDead,StageAlive,StageVisible,StageFocusedの4つがあり,以下のように定義されています。StageFocused)ステージはStageDeadから始まり,アプリをサスペンドしたり,レジュームしたりすると変わります。各ステージがAndroidのライフサイクルのどの部分にあたるのかは、上記のStageの定義にコメントとして書いてあります。表にまとめると以下のようになります。,「順方向」や「逆方向」は,そのステージ「へ」遷移した(順方向)か,そのステージ「から」遷移した(逆方向)かを表しています。詳細は後述のCrossesメソッドの説明で行います。正方向:onResume,逆方向:onFreeze執筆当時はMacとAndroidでステージの遷移の仕方が違っていました。Androidではステージは以下の表のように変化します。StageAlive->StageFocused一方,Macの場合は以下のように遷移します。Mobileのソースコードを見てもイベントを発生させてる箇所は見つけられませんでした。以下の図のように,ステージは基本的にStageDead->StageAlive->StageVisible->StageFocusedの順(定数の小さい順)に遷移することを前提としています。lifecycle.Event.Crossesメソッドを使うと,引数で渡したステージを順方向(定数の小さい順)に通り過ぎてるか(CrossOn),逆方向(定数の大きい順)に通り過ぎているか(CrossOff)が取得できます。StageFocused}の場合,Crosses(StageVisible)はCrossOnを返します。Crossesメソッドの実装を見る方が分かりやすいかもしれません。Stage型は単なるuint32のエイリアス型であるため,大小比較ができることに注意しましょう。CrossNone}Crossesメソッドを使うことで,プラットフォームによって,とあるステージに遷移しない場合でも,そのステージを「通過」したかどうかが分かります。以下のようにCrossesをメソッドを呼び出した場合,たとえStageVisibleに遷移してなくても,あたかも遷移したかどうかを判断しているように処理ができます。Mobileでは,複数のプラットフォームの実装を用意する必要があり,Crossesメソッドは,その違いを吸収することができる,よく考えられたメソッドです。今回は,タッチイベントとライフサイクルについて説明しました。タッチイベントと前回説明したspriteパッケージを使えば簡単なゲームを作ることができるでしょう。ライフサイクルイベントを使えば,アプリがサスペンドされたことを検出して,ゲームをポーズさせたりすることができ,より完成度のゲームを作ることができるようになると思います。次回は,音の再生と各種センサーを扱う方法について説明する予定です。Storeに公開しよう〜(予定)はじめに@tenntennです。Mobileのインストールを行い,MacとAndroid上でサンプルを動かしてみました。シェーダーを書いたりとOpenGLを直接ガリガリと使っていることが分かります。筆者はOpenGLについてあまり詳しくありません。spriteパッケージを使って,画像の描画や回転,拡大/縮小などを行う方法について説明します。Mobileのイベントモデルを理解する必要があります。Mobileのイベントモデルとspriteパッケージに深く関係しているペイントイベントについても説明したいと思います。この記事は2015年7月26日時点の情報を基に執筆しております。spriteパッケージは執筆当時,expディレクトリ以下にあり実験的な実装であることが分かります。そのため,正式版になるころには破壊的な変更が入るおそれがあります。サンプルを動かしてみようまずはサンプルを動かしてみて何ができるのか実際に見てみましょう。Mobileのリポジトリのexample/sprite以下で提供されています。buildコマンドを使ってapkファイルを作成しましょう。golang.org/x/mobile/example/spriteできたapkファイルをAndroid端末にインストールして動かしてみます。sprite.apkいかがでしょうか?以下のように,Gopherが何かの本を献身的に燃やしているアニメーションが描画されたと思います。Gopherは何かの本の山から何冊か本を積んで焼却炉に向かい,本を燃やしてまた本の山に戻っていきます。Gopherの画像は,手前の焼却炉に近づくたびに大きくなり,奥の何かの本の山に近づくたびに小さくなっています。このことから,このサンプルでは,画像の描画,移動,拡大/縮小が行われていることが分かります。この記事では,このサンプルを例に使って画像の描画および移動,拡大/縮小を行う方法について説明を行います。spriteパッケージspriteパッケージは,2Dグラッフィク向けのシーングラフを提供するパッケージです。シーングラフを構築し,各ノードにテクスチャを貼り付けたり,座標の指定や回転,拡大/縮小を行うことができます。詳しい説明は後述しますが,spriteパッケージを使って画像を描画するには,以下のような流れでシーングラフを構築し,描画します。シーングラフの構築と描画は,描画が必要になった際に発生するペイントイベントをハンドルして行います。画像ファイルの読み込みとシーングラフの構築は,初めてペイントイベントを受信した際にだけ行います。画像ファイルはテクスチャという形でロードされます。シーングラフの構築では,ノードの作成と登録を行い,そのノードにテクスチャの一部分をサブテクスチャとして設定し,そして,ノードのサイズや座標などを決めるアフィン変換行列を設定します。描画を行う前にそのフレームでのノードの配置を決定し,それから描画を行います。spriteパッケージのgodocを見ると,どのような機能が提供されているかが分かります。spriteパッケージ自体は,最低限のインタフェースやシンプルな構造体を提供するだけにとどまっています。具体的な実装を提供しないことで,OpenGL以外でも描画できるように設計されています。sprite.Engineインタフェースを見てみましょう。config.Event)}sprite.Engineインタフェースは,ノードの登録やテクスチャのロード,サブテクスチャの設定,シーングラフの描画などの機能を提供します。このインタフェースのOpenGLを使った実装は,sprite/glspriteパッケージで提供されます。glsprite.Engine関数を呼び出すことで,sprite.Engineインタフェースを実装した値を取得することができます(サンプルでの該当コードはこちら)。engはsprite.Engine型sprite.Engineインタフェースの実装は,sprite/glspriteパッケージ以外にも,sprite/portableパッケージがあります。sprite/portableパッケージは,描画にimageパッケージを使います。そのため,シーングラフを画像として書き出すことができます。著者が作ったサンプルでは,画面(sprite/glsprite)と画像(sprite/portable)に描画し,画像にはHTTPサーバ経由でアクセスできるようにしています。シーングラフの構築シーングラフを構築し,Engine.Renderメソッドを呼び出すことでそのシーングラフを描画することができます。シーングラフのノードはsprite.Node型として提供されています。それはsprite/glspriteパッケージやsprite/portableパッケージなどのエンジンの実装パッケージからアクセスするためで基本的には直接代入は行いません。sprite.Engineインタフェースのメソッドや*sprite.Node型のメソッドを介して設定されます。例外としてArrangerというフィールド(詳細は後述)は直接設定するようになっています。それでははじめに,ルートノードを作成して,エンジンに登録してみましょう(サンプルの該当コードはこちら)。登録を解除する際はUnregisterを呼び出せばよさそうですが,執筆当時はglsprite.Engine関数が返すエンジンは,Unregisterメソッドを呼ぶとpanicが発生しました。Unregisterメソッドはまだ未実装のようです。つぎに,子ノードを追加してみましょう(サンプルの該当コードはこちら)。sceneが親ノードになるこれでシーングラフを構築することができるようになりました。このままEngine.Renderメソッドを呼び出してシーングラフを描画しても何も画像を設定していないため,何も表示されません。そこでつぎに,テクスチャをロードして,ノードに画像を設定する方法を説明します。example/spriteを見ると,assetsというディレクトリの中にwaza-gophers.jpegという画像ファイルが1つ入っています。Gopherや本の山などを切り出して4つのサブテクスチャとしてシーングラフのノードに設定しています。テクスチャをロードしてサブテクスチャに切り出すところまでを説明します。example/spriteでは,以下のloadTexturesという関数でそれを行っています。番号は説明のために筆者が追加したものです。}}(1)では,assetsパッケージのOpen関数を使うことでassetsディレクトリ以下にあるwaza-gophers.jepgという画像ファイルを開いています。os.Openも使えますが,OSごとにファイルパスが異なるため,assets.Openを使うほうがよいでしょう。assetsパッケージにはファイルを開く関数しか無いため,ファイルを作りたい場合はos.Createなどを使うしかないようです。AndroidではAndroidManifest.xmlファイルにWRITE_EXTERNAL_STORAGE権限を追加すれば,os.Createを使ってdataディレクトリ以下にファイルを作成することができるのを確認できました。(2)では,image/pngパッケージやimage/jpegパッケージを使って画像をimage.Image型にデコードしています。透過PNG画像を描画したい場合は,以下のOpenGLの関数をloadTexturesの中で呼んでおくと良いでしょう。gl.ONE_MINUS_SRC_ALPHA)(3)で,エンジンにテクスチャをロードしています。執筆時点ではロードしかできないので,削除はできませんでした。ゲームなどでシーンが変わって,テクスチャをメモリ上から破棄したい場合は,エンジンごと破棄するしかないようです。エンジンとしてglspriteを使用する場合は,内部で画像サイズが2^nに変換されるようです。テクスチャのロードは最初のペイントイベント(paint.Event)が発生する前に呼び出すとpanicが発生します。ペイントイベントについての詳細は後述します。(4)では,sprite.SubTexという構造体の配列を返しています。sprite.SubTex型は,サブテクスチャを表す型で,あるテクスチャの一部分を表しています。ここでは,テクスチャを指定した座標でサブテクスチャとして4分割にしています。執筆時点では,テクスチャ画像の作成およびサブテクスチャの分割は手作業でやる必要がありました。generateを使って渡した画像からテクスチャを作成するツールなどをが出てくることでしょう。一度設定した画像を消したい場合は,空のサブテクスチャ(sprite.SubTex{})を設定すると画像が描画されません。設定するサブテクスチャを変える事もできます。つぎに,切り出したサブテクスチャをノードに設定する部分を見てみます(サンプルの該当コードはこちら)。texs[texBooks])Engine.SetSubTex関数を使うと,引数で渡されたノードに対して,サブテクスチャを設定できます。登録していないノードに対して呼び出すとpanicが発生します。これでノードにサブテクスチャを設定することが出来ました。これだけでは,まだノードの大きさが設定されていないため,画面に表示されません。ノードにアフィン変換行列を設定して,描画する画像サイズを指定しましょう。画像の移動・回転・拡大/縮小Engine.SetTransformメソッドを使うことで,ノードにアフィン変換行列を設定することができます。あるノードの座標や大きさは,親ノードまでの変換結果に,そのノードの変換行列を掛け合わせることで求められます。0),回転角度は0度を基準として,変換行列を掛けます。ノードにサブテクスチャが設定されている場合は,ノードの大きさや座標,回転角度を基に描画が行われます。example/spriteでは,sceneというノードがルートノードにあたります。inchとなり,geom.Pt型で定義されています。1ptが何ピクセルを表すかは端末ごとに違い,geom.Pt.Pxメソッドで取得できます。それでは,Engine.SetTransformメソッドを使った例を見てみましょう(example/spriteには該当箇所はありません)。5)に移動しています。10)だけ移動します。25)の位置に描画されます。20)だけ移動します。アフィン変換行列を表すf32.Affine型のポインタ型には,いくつか便利なメソッドが用意されています。変換行列を生で書くのが辛い場合は,*f32.Affine.Scaleメソッドや*f32.Affine.Rotateメソッド,*f32.Affine.Translateメソッドなどを使うと良いでしょう。メソッドのレシーバがf32.Affine型のポインタとなっているため少々使いづらいですが,直接変換行列を書くよりかは分かりやすいはずです。イベントここまで,いくつかイベントという言葉を使ってきました。Mobileのイベントモデルと各イベントについて簡単に説明します。Mobileでは以下のようなイベントを扱うことができます。かっこ内は対応するイベントの型です。描画イベント(paint.Event)設定イベント(config.Event)ライフサイクルイベント(lifecycle.Event)タッチイベント(touch.Event)マウスイベント(mouse.Event)キーイベント(key.Event)執筆時は,キーイベントやマウスイベントはまだ実装中のようでした。モバイル用というよりはPC用のイベントのようです。キーイベントはAndroidのバックボタンを特定のキーでシミュレートしたりと,デバッグ用途に使えそうです。Mobileでは,app.Main関数を用いて,イベントループを構築します。通常,app.Main関数はmain関数内で呼び出すことが多いでしょう。app.Main関数の引数には,func(app.App)型の関数を渡します。})}この関数で渡されるapp.Appインタフェースは,以下のように定義されています。EndPaint(paint.Event)}各イベントは,app.App.Eventsメソッドで取得できるチャネルを通じて送られてきます。一般的には,以下のようにswitch-case文で各イベントをハンドリングします(サンプルでの該当コードはこちら)。app.Filter関数を通すことにより,各イベントの前処理などをやっているようです。for文のrangeの後にチャネルを書くとチャネルから送られてくるデータを変数に入れループを回してくれます(例)。})}それでは,各イベントについて説明していきたいと思います。この記事では,config.Eventとpaint.Eventについて説明し,他のイベントについては次回以降の記事で説明したいと思います。config.Eventは,画面サイズが変わった場合などに送られてきます。PCだとウィンドウサイズが変わるごとにイベントが発生しますが,端末だと画面が回転した場合などに呼ばれるようです。config.Event構造体からは,画面のサイズが取得できます。float32}paint.Eventは,描画が必要になった場合に送られてきます。上述のとおり,テクスチャをロードはpaint.Eventが送られてきた後に行う必要があります。はじめはひとまず,テクスチャの読み込みおよびシーングラフの構築は初めてpaint.Eventが送られてきたときに行うと良いでしょう(サンプルでの該当コードはこちら)。シーングラフの描画は,sprite.Engine.Renderメソッドを呼び出すことで行なわれます。sprite.Engine.Renderメソッドの引数は,シーングラフのルートノード(*sprite.Node),時刻(clock.Time),config.Eventです。引数に時刻を渡していることから分かるように,sprite.Engine.Renderメソッドはある特定の時刻のシーングラフの状態を描画します。時刻よって座標などが変化するノードのある時刻の状態を決定するには,ノードにsprite.Arrangerインタフェースを設定している必要があります。clock.Time)}sprite.Arrangerインタフェースの定義を見ると,Arrangeというメソッドを持っており,その引数に時刻(clock.Time)を取ることがわかります。sprite.Nodeに設定されたsprite.Arrangerインタフェースは,sprite.Engine.Renderメソッドが呼び出された際に,sprite.Engine.Renderの引数として渡された事項をそのまま渡し,実行されます。example/spriteにおいても,Gopherの座標を決めるためにsprite.Arrangerインタフェースを設定してます(該当箇所はこちら)。})})なお,arrangerFuncは以下のように定義されている,sprite.Arrangerインタフェースを実装する型です(サンプルでの該当コードはこちら)。関数によるインタフェースの実装については,筆者のQiitaの投稿で解説しています。arranging.とコメントあるように,sprite.clockパッケージの中にtween.goというファイルがあり,補間を行う関数が提供されています。paint.Eventが発生し,その後sprite.Engine.Renderメソッドを呼び出しても実際にはまだ描画はされません。そのフレームでのすべての描画に関する処理が終わったらapp.App.EndPaintメソッドを呼ぶ必要があります。EndPaintメソッドが呼び出すと画面など(sprite.portableは画像)に描画結果が出力されます。Mobileにおけるイベントモデルの説明を行いました。次回は今回扱わなかったtouch.Eventやlifecycle.Eventについて説明する予定です。Storeに公開しよう〜(予定)はじめに@tenntennです。1.5は非常に楽しみです。Mobileのアップデートに注目しています。Mobileは,Goを使ってモバイルアプリを書くためのツール類を提供するプロジェクトです。1.5では,iOS向けのアプリがビルドできるようになったり,Androidのサポートが強化されるようです。時点)では,すでにiOS向けのビルドもできるようになっているみたいです。実際に,GoチームによってIvyというAPLライクな言語のインタプリタがiOSとAndroid向けにリリースされています。Storeでリリースしてみました。私が作成したGoFunというアプリは,Javaを使わずすべてGoだけで書いています。JavaとGoを使った記事はよく見かけるのですが,Goだけで書いている記事はあまり見かけません。Storeに公開するところまで扱っている記事も見かけません。そこでこの記事では,複数回に分けてその時に調べたGoだけでAndroidアプリを作ってリリースするまでの手順を説明します。Mobileの簡単な説明とインストール方法を説明したいと思います。この記事で扱う手順やノウハウは,記事執筆時(2015年7月19日)のものです。Mobileはまだまだ開発途中です。現在(執筆当時)も毎日のように破壊的な変更がされています。そのため,ビルド方法やパッケージ名,各ライブラリやツールの使い方が変わる可能性があります。5.1.1)で動作検証をしていますが,他のOSやAndroid端末では動作検証をしてまいません。MobileはLinuxでは動くようですが,Windowsではまだ動かないようです。Mobileのコード上にはWindowsの記述があるので,そのうち対応されるでしょう。1.5のベータ版をダウンロードするかソースコードからビルドしてください。