BlogKLabは、多くのスマートフォン向けゲームを開発・提供しています。スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。ハンズオン活動報告こんにちは、@trigottです。ハンズオンの紹介をします。依存型の使える関数型プログラミング言語、あるいは定理証明支援系です。上で対話的に証明を進めるためのインタフェースが用意されているため、効率的に証明を進めることができます。等しいことを証明する動画を撮影してみました。雰囲気がつかめれば幸いです。Meeting)という社内勉強会を開催しています。関する発表をしたところ、先輩からハンズオンを開いてほしいということを言われました。人に教えられるほどではないし・・・と最初は不安だったのですが、まあ全3回くらいでやってみよう、という軽い気持ちで開催することにしました。社内から参加者を募り、集まるか不安でしたが5人の方が参加してくれることになりました。この勉強会は昨年の10月から始まり、現在も継続中です。ハンズオンということで、一人で進められるような資料を作成しました。演習の問題は以下のように作成しました。結合法則を証明してください。問題の難易度を示しています。基準を設けず難易度付をしてしまったせいで、あてになりません。コメントに問題の解説を書いています。未完成なものになっています。書くことができ、プログラムとしては不完全ですがコンパイルを通すことができます。解けていない問題がある状態でプログラムをコンパイルしてもエラーにはなりません。一覧が表示されるので、現在の進捗が分かります。最初は全3回くらいで終わる予定でしたが、資料を作成しているうちに内容が膨らみ、気づいたらDay1からDay6までの全6部構成になっていました。以下、各ファイルについて解説と作成時の苦労について書きます。Day1一番最初に作ったので、一番気合が入っていて、一番長いです。基本的な定理証明の仕方を解説しています。最後は、自然数の大小関係を定める2つのデータ型が互いに等価であることを証明する問題になっています。言いつつ、1日ではまったく終わりませんでした。ついて)解説しています。等しいことを証明する問題です。使ってこの問題が証明できれば、初等的なものに限れば等しいことの証明は大体できるのではないでしょうか。リストという順番でデータ構造を紹介するのは、構造が徐々に複雑になっていくという点で自然に難易度を上げることができているのではないかと思います。ものを真似しています。パターンというものも説明しようと思ったのですが、例が思い浮かばず断念しました。Day4レコード型及び依存型を伴うレコード型について解説しています。レコード型の例として二次元平面上の点を定義し、それを使ったマンハッタン距離に関する性質の証明を演習としています。マンハッタン距離が三角不等式を満たすことの証明はやってみると意外と面倒なのですが、面白かったのでそのまま演習に残しています。レコード型の理解に貢献する問題ではないので、余力がある人向けとなっています。この辺から例を考えるのがかなりしんどくなっています。パターンの解説をしています。偽についての説明は、自分の力量不足で怪しいかもしれません。演習には古典論理にまつわる命題をいくつか与えており、そのために必要なので「かつ」と「または」についても解説しています。Day6最後は新しいことはなしで、これまでの知識を使って正しいソートアルゴリズムを定義するという演習です。どこまでヒントを出すかは悩みましたが、ソート済みであることなどの定義はすべて与えて、ソートするプログラムだけを書いてもらう形にしました。ソートするだけでなく、命題を見ると結果のリストがソート済みであることも要求しています。想定解法では、ソートをしつつ証明も同時に行うようにしています。型もそれを推奨するように作ってみました(ただ説明がないので、ちょっと中途半端...)。実際にハンズオンをしていく中で出てきた感想です。ある2つの項が等しいとはどういうことか、またそれはどのようにして証明できるか、について説明したのですが、これが分からないという意見が非常に多かったです。以下のように定義できます。理解するのは非常に難しかったようです。値を比較する際には正規形に計算してから比較するため、プログラムの字面とは違う値が比較されていること等しいというのはとても馴染み深い関係のように思えるにもかかわらず、初心者にとっては超えるべきハードルがいくつもあります。自分でも説明に窮する場面が何度もあり、まだまだ十分に理解できていないなと感じました。簡単な関係から始めるべきだったかもしれません。足し算を以下のように定義しています。対応する命題の左辺と右辺が同一の項であることを要求します。使うことができるようになります。自動で計算を行うということを知っていないと、なぜ証明が通るのかが理解できません。足し算の定義を覚えていないと、どこまで計算が進むかも分かりません。自分もこの辺りの理解が甘く、テキストには解説を書けていませんでした。いう名前をつけるなど、空白だけ詰めた名前を採用していることが多いです。ような名前でもひと続きの識別子として認識されます。ひと続きの名前としては認識しづらいとのことでした。文脈中のある項を別の項で置き換えるパターンです。以下の例のように使います。書いただけでは反映されません。書いてからロードして初めて文脈に反映されます。テキストのなかでも注意がなかったため、ここで戸惑う参加者の方が多かったです。それぞれを使った証明法を解説しています。使って証明すると以下のようになります。ついていることを明示してあげる必要があります。対象となる項を書く必要があり、また完全な証明を与えなければならないため、冗長な記述になります。冗長になっている分、説明が多いためこちらのほうが分かりやすくなっています。使ったほうがわかりやすいと評判でした。標準ライブラリに何があるか把握するのが難しそう演習を進めていく上で補題が必要になったとき、それが標準ライブラリにあるかどうかが分からない、という声がありました(このハンズオンでは標準ライブラリの解説は対象外にしていたのでそのあたりの話は特にテキストには書きませんでした)。これは実際もっともで、自分もいまだにどのように探せばいいのか分かりません。補題を探すときはファイルを標準ライブラリから直接目で見ることが多いし、モジュールの構成も完全には頭に入っていません。すればいいんでしょうか。1週間空くと何をやっていたか忘れる普通のプログラミングではないし、週に1時間しかやっていないため、どうしても前回やっていたことを忘れてしまうという声が多かったです(1時間の最初は前回やっていたことを思い出すところから始まる)。しょうがないところもあるとは思いますが、この問題に対しては対策を思いつきませんでした...。おわりに初めにも書いたとおり、当初は3回完結の想定(本気で3回でソートまでできると思っていた)だったし、やる内容からしても(そして最初の依頼に沿う形としても)ハンズオン形式が相応しいと考えて、ハンズオン用の資料を作成しました。ところが、蓋を空けてみるともう半年以上も続いています。ハンズオンという自分で進める形式の勉強会が長期に渡ると、参加者の間で進度に差が出てしまうという問題が発生します。途中から参加者が少ない場合は開催を中止するようにして、なるべく差が出ないよう配慮をしました。非常に入門難易度の高い言語であり苦労した経験から、自分が教えることでその障壁を少しでも和らげることができたらいいな、ということがありました。比較すると、決定版と言えるような教科書はなく(教科書は最近出版された1冊だけあり、入門にはよさそう)、日本語の情報もそこまで多くはありません。標準ライブラリを使いこなしている人間がどれだけいるのか、気になります。12/24の記事です。KLabのとある新規アプリ開発プロジェクトでは、アプリ内で使用する制作物の納品フローを工夫して、エンジニアの手がどんどん不要になってきています。現在稼働しているワークフローを紹介します。納品フローに関わるプロジェクトチームは大きく5つの班に別れています。以上の各班の作業内容がプロジェクトの最終成果物としてのipaやapkファイルに取り込まれるまでの流れです。以下では単に「バイナリ」と書いた場合ipa、apkファイルのことを指します。サウンド用と分割されています(実際には細かいものがもっとあります)。各レポジトリのメインブランチは、origin/masterを使用しており、バイナリ作成時に最終的に使われるブランチです。そのため、いつでもバイナリ作成ができるようにメインブランチは常に正しく動作することが求められます。エンジニアがいなくてもバイナリをビルドし動作確認ができる1番目のポイントは、誰でもSlack上でBotに話しかけることで簡単にバイナリ作成が出来るので最新のメインブランチに自分の作業内容を取り込んでデバッグ用端末上ですぐに動作確認ができることです。話しかける内容はこんな感じです。自分が動作確認したいブランチとOSを指定してタイトル、説明文を書くだけです。人でも3分教えれば操作を覚えられます。これが導入される前は、ビルドは週末のプロジェクトの正式ビルド時のみで、開発班以外にとって自分の作業内容がデバッグ用端末上で確認できるまで数日待つ必要がありました。それが今では10分ほどに短縮されました。メインブランチの状態が動作確認されるのも数日に1回程度だったので、メインブランチが正常に動作しない時にどこでバグが入りこんだのか調査するのに時間がかかっていました。今では、30分に1回は誰かがビルドしているので、メインブランチに何かあればすぐに開発班以外からバグ報告が来るのでバグの発見速度があがっています。Jenkins上のジョブは触っていけない項目も多く、開発以外には解放していないプロジェクトも多いかと思います。そんな場合でも、SlackのBotでラップしてしまえば必要な項目だけをパラメータ化でき、自由度の高いバリデーションもかけられるので、安全に実行してもらうことができます。仕組みの構築は1時間程度で出来るものなので、メリットが大きいです。使用してBotを作成し社内のサーバーでデーモンとして起動しています。このデーモンが、Slackからのメッセージを受取り、ビルドパラメータにパースして、社内のJenkinsビルドサーバーに対してパラメータとともにPOSTリクエストを投げます。ビルドが完了するとEMランチャにアップロードされ、Slackチャンネルに通知が来る、という流れです。エンジニアを介さずにメインブランチが更新できる2つ目のポイントは、開発班以外でもエンジニアの手を介さずにメインブランチの更新ができることです。※もちろん一部必要な箇所は手動で取り込みます。先に記した通りメインブランチは常に正常に動作することが求められているので、不正な変更は取り除く必要があります。それにもかかわらずなぜエンジニアの手を介さずに更新ができるのかを説明していきます。開発班以外がメインブランチを更新する流れには大きく2つ存在しています。UI・サウンド班UI班が納品するのは主にテクスチャで、サウンド班はオーサリングツールで書き出したサウンドデータを納品します。この2班はもともとSVNに慣れていてGitに移行するメリットがなかったのでそのままSVNを使ってもらっています。cronジョブが必要に応じた頻度で走っていて、SVN側で更新があると納品物に対する自動テストが走り、テストにパスするとGitレポジトリの方にコミットされて取り込まれます。そのため、Gitレポジトリの方で開発班がデータに触る段階ではすでに命名規則などの自動テストの項目をすべて満たした状態が保証されています。UI・サウンド班がSVNにデータをコミットするだけで、開発が手をかけずともメインブランチが更新できてしまいます。UIは図3のフローです。サウンドに関しては少し特殊で、目で見て分からない分動作確認の必要性が高いので、後述の企画・演出班と同様にUnityとGitの環境を構築しています。サウンドは図1のフローです。以下の流れになっています。サウンドデータをSVNにコミットsoundレポジトリのorigin/stgにcronジョブで取り込まれるローカル開発環境にorigin/stgをチェックアウトしUnityEditorで再生し確認Slackからorigin/stgを指定してバイナリビルドデバッグ用端末上で動作確認origin/stgをメインブランチにマージバイナリファイルや、大量のファイルは人間の目で確認するよりも機械的にチェックしたほうが不正なデータが取り込まれる可能性が減らせます。.fbx)を納品します。企画班、演出班ともに開発班と同じフローで作業しています。全員UnityとGitをインストールしており、各自の開発環境を構築しています。サーバーは開発と同様に個人用サーバーにそれぞれ接続しています。加えて、開発班以外は共通でGithubDesktopをインストールしています。GithubDesktopを選んだ理由は主に3つです。MacとWindowsに対応している。Gitの複雑な概念を知らなくても使えるように複雑さを上手く隠蔽している。必要であればGitシェルを立ち上げて任意のGitコマンドが実行できる。GithubDesktop独自の概念は例えば以下のようなものです。ローカルとリモートのブランチの内容をより新しいコミットの方に同期すること。ローカルのmasterブランチの内容を作業ブランチにマージすること。自分のローカルの作業ブランチをリモートにPushすること。ブランチ切り替えや、PullReqもGithubDesktop上から出せます。コンフリクトを発生させないために次のことに注意してもらっています。EnterpriseのTeam機能を使って班ごとのTeamを作成し、アクセス権をコントロールしています。演出Teamにはui-effectsレポジトリのみ読み書き権限を与え、その他のレポジトリは読み取りのみとしています。これにより大雑把にレポジトリを守ることができます。まとめ納品フローに関する説明は以上になります。この仕組みを作り上げるまでは当然開発の工数がそれなりに必要でした。導入した当初は特にGit周りに関する質問が多く、開発班によるサポートが必要でした。今ではごくたまに発生するコンフリクト解消とシステム側のトラブル以外ではほとんどサポートが必要なくなってきています。開発サイクルを速くまわす必要がある新規開発において早期の納品の仕組み化はおおきな価値があること同じ作業を誰でもできるようにすることは想像を超える効果を生み出すことを感じています。UI班から開発班へなどのファイルの受け渡しに関して当初は、プロジェクトの発足からしばらくは手渡しやファイル共有サーバーを使用していました。そのため、単なる画像の差し替えやサウンドの差し替えにもいちいち開発班の手を介す必要がありました。これは今振り返れば、開発サイクルを速くまわす必要がある新規開発においては時間的に大きなロスだったと感じます。次の新規案件では最初の納品の前に仕組みを構築しておきたいものです。Slackからのビルドシステムを作った当初の目的は、ビルドサーバーのJenkinsにアクセスしてパラメータを入力するのが面倒だったのでSlack上での会話の流れでビルドできたら素敵だよねというちょっとした遊び心でした。それが今となっては各班が自分たちで作業と確認のサイクルをまわすためのコアとなっていることは驚きです。繰り返し作業を単に自動化することと、それをさらに普段使い慣れたインターフェイスからすぐにアクセスできるようにすること、の間には大きな隔たりがあります。前者は自分が作業を繰り返す回数を量的に変えるのに対し、後者は作業自体を質的に変える可能性をもっています。今後もプロジェクトのみんなが幸せになる納品フローを常に模索していきたいです。12/22の記事です。22番手も緊張しますね。KLabGamesの基盤エンジニアのkenseiです。プロローグある日突然事は起こります。それまで順調にビルドされていたjenkinsのandroidビルドが突然のエラー。option.の文字が。Androidはファイル内でコードによって呼び出すことができる参照の総数で65535を超える事ができないのです。超えるメソッドを使用するアプリの設定いつかは戦わないといけないのですが、いざその日が来ると憂鬱ですね。計測dexのメソッド数を計測してみます。ありがたいことにツールが公開されているので、それを使用します。gpgs..androidはしょうがないとして、うむ、、GPGSでかいな。ことで、ここを重点的に潰す事にします。Archive)で提供されています。ただのzipですが、jarは中に入ってしまってるので解凍しないといけません。いう事でみんな大好き、shellの出番です。本当はgradleでスマートにやりたかったのですが、aarをzipTreeするとpermissionエラーを吐いてgradleが死にました。googleさん、中をゴニョゴニョしたい人の事考えてassembleして下さい。準備unzipするので、Assetsと同じフォルダにディレクトリを作ります。そこにshellを作っていきます。$GRADLE_WORK_DIRだいたいコメントの通りです。途中でJAVA_HOMEとANDROID_HOMEを設定せずにビルドをしようとする不届き者にemacsを使えなくするお仕置きをしています。vimを使ってればお仕置きされずに済んだのに。準備その2shellでやっていますが、一部の処理はgradleを使用するのでhomebrew等でインストールを済ませておいて下さい。あ、言うの遅くなりましたが、動作はmacでしか確認していません。上記で用意したフォルダにbuild.gradleファイルを作成し、下記の内容を設定します。wrapperを実行します。aarを解凍Assets/Plugins/Androidの下にあるaarを解凍していきます。${PLUGIN_DIR}/{}ちなみにmaxdepthオプション付けてディレクトリをfindすると、rootが入って来てしまうのですが、mindepthを重ねがけするとrootを除外できるのを今回発見しました。他のスマートなやり方あるかもしれないので、もっと綺麗なshellを書けるようになりたい。aarを削除もう使用しないので、aarを消していきます。\;この時にappcompatとsupportはいったん除きます。理由は後で面倒くさいからです!classes.jarを取り出す解凍したaarの中からjarを取り出していきます。\;fiファイルが同名なので、元のフォルダ名+"jar"に名前を変更しています。AARファイルになっていないAndroidLibraryのjarも持ってきています。今回はfirebaseを指定しています。classes.jarを一つのjarにまとめるrenameしたlibフォルダのjarファイル達をunzipして一つのjarファイルにまとめてしまいます。build.gradleを編集します。'2.14.1'}shellにgradleのjarタスクを実行させます。設定ファイルを指定しています。一個のjarファイルにまとまりました。使用していないメソッドをstripするためにproguardをかけていきます。一からproguardのruleファイルを書いていくのは面倒なので、aarの中に入ってるproguard.txtを再利用します。アプリ独自の設定を書いていくようにproguard-rules.proファイルを用意します。ruleファイルを生成させます。proguard-rules.proちなみにデフォルトのproguard-android.txtもまとめて1ファイルにしてしまってます。gradleのproguardタスクでgetDefaultProguardFile関数が使えなかったので。jarファイルダイエットproguardを使用して、未使用のメソッドをjarから抹殺します。androidのpluginとjavaのpuluginでタスクがかち合うので、別名のgradle設定ファイルにします。今回はproguard.gradleとします。androidプラグインを使ってビルドを通すためにダミーのAndroidManifest.xmlを作成します。/></manifest>次にgradle設定ファイルを作成します。'../Assets/Plugins/Android/firebase-common-9.8.0/libs/classes.jar'}proguard.dependsOn(clearJar)injarsにclasses.jarをまとめたgoogle.jar、libraryjarsにjarが依存している物を指定しています。※適当に必要そうなのを足していったので、結局どれが本当に必要なのか分かってないappcompatとsupportは最初に作業フォルダに持ってきたけど、excludeしていたjarを使用します。versionの中で一番新しいのを適当に設定しています。android.jarはproguard.gradleで指定したcompileSdkVersionを指定しています。shellにgradleのjarタスクを実行させます。proguardここはビルドに必要なlibraryjars不足によるエラーとの戦いになるので、何回もgradlewコマンドを叩いて通るまで頑張ります。できあがったミニマムなjarファイルです。aarの中のresourceを戻す解凍したaarの中のリソースを戻していきます。Assets/Plugins/Androidフォルダ直下に置いて、packagingをUnityに任せます。"${PLUGIN_DIR}/{}fiproject.propertiesを追加しないとUnityにAndroidResourceとして認識されないので、追加しています。Levelを設定すれば大丈夫だと思います。generated=trueとしてるのは、aarのバージョンアップ時にフォルダを消すためのマーキングです。AndroidResourceとして認識されているかはUnityAppPath/Temp/StagingArea/android-librariesを見ると確認できます。上記で生成したjarファイルはミニマム過ぎて動きません。試しにfirebaseを全部stripから覗いてみます。com.google.firebase.**ついでにwarningがうるさいので切っています。aarはresourceが未コンパイルなので、R.classがないエラーが出るので。R.$のエラーだけ出るようになるまではdontwarnは設定しない実行してみるとfirebaseが復活しているのが分かります。作業の繰り返しになります。Blogなのに、泣かせるので評判な@hhattoです。