やらなイカ?

たぶん、iOS/Androidアプリの開発・テスト関係。

#yokohamaunity in #cluster 会場ができるまで

昨晩開催された『yokohama.unity 実質4回目』会場として cluster のワールドを構築・提供しました。

meetup.unity3d.jp

ワールドは横浜の「大さん橋」屋上を 再現 模したもので、遠景に全天球画像、アーチ状の構造物などはフォトグラメトリで作ったハイポリモデルをリトポロジーしてローポリ化したものを配置。

f:id:nowsprinting:20200801042833p:plain:w400

少しでも横浜っぽさを感じ、楽しんでいただけたのであれば幸いです。

以下、製作の流れなどを紹介します。

会場の選定

前回(4/末)のオンライン開催の後、「横浜っぽい会場を作ろうか*1」ということで心当たりを何箇所かロケハンした結果、大さん橋に決まりました。

大さん橋は、みなとみらい*2ベイブリッジ山下公園といったスポットに囲まれており、まさに全天球写真向き。 ちなみに、大さん橋ホールは数多のビアフェスの会場になっていたりします。

遠景

全天球写真はRICOH THETA Sで撮影。ただし、撮影の主目的はフォトグラメトリ(後述)だったので、撮影に行くのはフォトグラメトリ日和つまり曇天。 7月に入って、そろそろ本番撮影をしなければと思うものの、ずっと天候悪く、曇天画像のまま当日を迎えました。

撮影した全天球画像は、@warapuriさんが公開されているライセンスフリーICO球 (Sphere100.fbx) の内側に貼り付けて使用しています。

UnityのSkyboxにそのまま貼って使うこともできましたが、撮影が雑で画像が大さん橋の中央でなく北に寄っていたため、うまく足元の構造物を隠せる上記方式をとりました。

構造物のモデリング

大さん橋屋上の構造物、大さん橋ホール入り口のアーチ(上のスクショでスクリーンの後ろにあるもの)と、南北にある階段状の客席をそれぞれフォトグラメトリ、リトポロジーして使用することを予定していました。

しかし残念ながら客席はリトポロジーが間に合わず、今回は単なる斜面に。

撮影

曇天*3の早朝、 iPhoneで動くマニュアルカメラアプリで、 北側客席は下側を東西に平行移動しながらヨーとピッチ変えつつ撮影。 エントランスも下側から南北に並行移動しながらヨーとピッチ変えつつ撮影。

南側に人がいたので北側を撮影したのですが、背景にランドマークタワーなどのビルが写ったためフォトグラメトリの像を結ぶのにプラスに働いたはず。

フォトグラメトリ処理に使う3DF Zephyr Liteにソース写真500枚制限があるため、客席とエントランスはあえて別々に撮影しています。

フォトグラメトリ

使用ソフトは3DF Zephyr Lite。2年前にSteamのセールで買ったものですが先日バージョン5にアップデートされました。最近は買い切り珍しくてお得感。

store.steampowered.com

北側客席は231枚中231枚が有効。設定はプリセットのカテゴリーを「市街地」、ほかはデフォルト。 奥のほうはエントランスの影があったこともあって暗く、そのせいか湾曲してしまっています。WBくらいオートにしてもよかったのかもしれません。 メッシュ生成の後、周囲のバリ取りを行なってテクスチャ付きメッシュを生成。

f:id:nowsprinting:20200801052744j:plain:w400

エントランスは54枚中53枚が有効。こちらもカテゴリー「市街地」、ほかはデフォルト。 こちらもバリ取りをしますが、出入り口正面の床は残し気味にし(後工程で水平を取りやすくするため)、客席部分は邪魔なので削ってしまいます。

f:id:nowsprinting:20200801052510j:plain:w400

加工したら、テクスチャ付きメッシュをobj形式で出力。fbxでも出力できますが、(恐らくLite版の制限で)テクスチャが出力できません。 テクスチャは後工程で捨ててしまうのですが、リトポロジーの際にテクスチャがあるほうが見やすいです。

なお、方向合わせは3DF Zephyrで無理に行わず、後からBlenderで行います。

スケールは、フォトグラメトリで作られたモデルの寸法を本当に信じていいのか未だ半信半疑。 今回は大きすぎると思って縮小して合わせたものの、終盤になって、どうも合っていたのかも? と…。

トポロジー

トポロジー (retopology) とは、フォトグラメトリやスカルプトで作られたハイポリモデルのメッシュの流れ(トポロジー)を組み直すこと。

自動でリトポロジーを行なってくれるツールもありますが、今回はcluster会場用にローポリモデルにしたいのでハイポリメッシュに沿って手作業で面を貼っていく作業をします。

今回はこちらの記事のワークフローに従い*4、販売されているBlenderアドオンを使用して貼っていきました。

bookyakuno.com

gumroad.com

エントランスのアーチ下側の部分。このようにハイポリメッシュに頂点を吸着させつつ面を貼っていきます。 ちなみにアーチ下の階段に横たわっている白と黒の物体は、寝ていたおじさんです。

f:id:nowsprinting:20200801060603p:plain:w400

貼り終えたら、原点をホール入口中央の床面に合わせておきます。Unityでの位置合わせが楽なように、Blenderでやるのが一番良さそう。 その後、ローポリメッシュをUV展開してfbxエクスポート。Unityに取り込みます。

大さん橋屋上の床面

床面は、Blender大さん橋の平面図を下絵として取り込み、モデリングしていきます。 ここでも原点をホール入口中央の床面に、スケールはフォトグラメトリモデルから測定したものに合わせます。

f:id:nowsprinting:20200801060210p:plain:w400

実際はもっとカーブが多く、さらに南北でアシンメトリーなのですが、かなり単純化して面を貼っています。画像が南半分なのはミラーモディファイア使用のため。 平面図にあらわれないアップダウンは現地の記憶と写真で補いつつ進めます。

また、全天球画像の足元を見えなくするために、実際の床面より広く外に張り出しています(Unity上でコライダによって端まで行けないようにします)。

こちらもUV展開してfbxエクスポートします。本来は芝生部分の塗り分けがあるのですが、今回は一律、木目調のテクスチャをタイリング。

なお、当初、床面はUnity上でPro Builderで作りかけたのですが、大きなオブジェクトの位置合わせが大変で断念しました。 最終的にスタッフルーム(大さん橋ホール)の箱だけPro Builder製に。

小道具

小道具として、横浜のアイデンティティ(箱)をモデル化。 Cluster Creator KitのGrabbable Itemコンポーネントを入れて、持ったり投げたりできるように。みんな遊んでくれてよかった。

f:id:nowsprinting:20200801031042p:plain:w400

また、登壇者に cluster ゲームワールドコンテスト 2020 大賞受賞の@yunoda_3DCGさんがいらっしゃったので、急遽、大賞作『1min. Hockey』に飛べるWorld Gateを設置。

cluster.mu

所感

既知のノウハウのつなぎ合わせで作ったものなのですが、屋外の構造物をまともにフォトグラメトリ&リトポロジーするのははじめてで、色々勝手がわからず試行錯誤しました。

ここに挙げただけでも様々な妥協の産物なのですが、なんとか形にはなったかと。 今回妥協・見送りしたものは、次回yokohama.unity開催までに仕上げたいところ。

なお、大さん橋には撮影に関する規約があり、今回は個人撮影の範囲で利用しています。従って、イベントは限定公開、ワールド非公開、リポジトリも非公開となっています。ご了承願います。

参考

www.nowsprinting.com

[asin:B07N4G4LH7:detail]

*1:オフライン開催の頃から地域.unityのアイデンティティ確立は主催者が意識されていて、オンラインだとそれ無くなってしまうねという話が発端

*2:港町は海側が顔なので海から見るべき、という話があります

*3:多くの写真を撮っていくので、強い影が出ると時間経過で影が変わってしまうことを避けるため

*4:記事中でMatcap表示を勧められていますが、今回の作例ではフォトグラメトリで得られたテクスチャのほうがやりやすい

Unityプロジェクトのディレクトリ構成と .gitignore

Twitterで話題に上がっていたので、自分なりのやり方を。

ディレクトリ構成

Assets/下

自分で作るものはAssets/下にフォルダを作り、そこにまとめています。

Assets
└── MY_PROJECT_NAME
    ├── Materials
    ├── Prefabs
    ├── Scenes
    ├── Scripts
    │   ├── Editor
    │   └── Runtime
    ├── Tests
    │   ├── Editor
    │   └── Runtime
    └── etc...

"Runtime" のところは、規模が大きければ機能やレイヤごとに分けたりします*1

スクリプトを置くフォルダおよび直下のAssembly Definition File (.asmdef)を作ってくれるエディタ拡張を公開しました。 これを使うと簡単にセットアップできます。[2021.5.7追記]

github.com

Assets外

主にビルドスクリプト*2で以下のように指定しています。いずれも.gitignore(後述)で除外指定されているところ。

  • ビルド成果物は Builds/
  • ログは Logs/
  • テスト結果も Logs/
  • コードカバレッジ*3も Logs/

.gitignore

giboでベースを生成

.gitignoreファイルはgiboで生成したベースに、プロジェクトごとの設定を追加しています。

github.com

私はIDEにJetBrains Riderを使っているので、次のように。

$ gibo dump Unity JetBrains >> .gitignore

これで、以下2つの定義が連結された.gitignoreができます。

Rider以外のIDEを使用する場合は JetBrainsVisualStudioVisualStudioCode に置き換えます。

なお、OSごとの定義はあらかじめグローバルの.gitignoreに設定されている前提ですが、gitに不慣れなメンバーもいる状況では macOSWindows を加えてもいいでしょう。

プロジェクト固有の定義を追加

必要に応じて、.gitignoreに追加していきます。

3rd partyのアセットをすべて除外したいケース

まず極端な例。公開 or 納品するプロジェクトなど、3rd partyのアセットをすべて除外したい場合は、以下のようにMY_PROJECT_NAME以外をすべてトラックしないようにします*4

/Assets/*
!/Assets/MY_PROJECT_NAME*

なお、スラッシュを含めず*を書いているのは、ディレクトリの.metaファイルも含めるためです。

特定のアセットだけ個別に除外したいケース

すべてではない場合、シートライセンスのエディタ拡張など、リポジトリに含めたくないものを個別に指定していきます。例えば、

/Assets/ConsolePro*
/Assets/StompyRobot*

同様に、Assets/下に生成されるファイルでトラックしたくないものも追加していきます。代表的なものは以下。

Play modeテスト実行時に作られて、まれによく残ってしまうScene

/Assets/InitTestScene*.unity*

Automated QA パッケージのRecorded Testing機能実行時に自動生成されるもの [2021.5.7追記]

/Assets/AutomatedQA/Temp*

Build Report Inspector パッケージが自動生成するもの

/Assets/BuildReports*

Unity Recorderパッケージのデフォルト出力先 [2021.7.22追記]

/Recordings/

ML-Agents パッケージがトレーニング時に自動生成するもの [2021.5.7追記]

/Assets/ML-Agents*
/results/

NuGetForUnity がDLLを展開するところ *5 *6 [2021.5.22修正]

/Assets/Packages/
/Assets/Packages.meta

VRCSDK がアップロード用にプロジェクトルートに作るファイル

/*.unitypackage

UniVRM が生成する中間ファイル

/Assets/*.vrm
/Assets/*.vrm.meta

Code Coverage パッケージの設定ファイル *7 *8 /ProjectSettings/Packages/com.unity.testtools.codecoverage/

こちらの設定を入れた.gitignoreは、GitHub - nowsprinting/UnityTestExamples: 『Unity Test Framework完全攻略ガイド 第2版』サンプルコードリポジトリからダウンロードしてお使いいただけます。

$ curl -o .gitignore https://raw.githubusercontent.com/nowsprinting/UnityTestExamples/master/.gitignore

アセット提供側が気をつけたいこと

アセット提供側としても、自身のアセットはAssets/直下に置くのでなくディレクトリを切るべきです(以下MY_ASSET_NAMEとします)。

また、再配布可能な他者のアセットを同梱する場合は、MY_ASSET_NAMEの下ではなく、オリジナルのディレクトリに置くと、他のアセットとの競合を避けられます(利用者側がインポート時に判断できます)。

アセットの設定ファイルをAssets/MY_ASSET_NAME/下に置いてパス指定で読み書きしてしまうと、ユーザがディレクトリを変更できなくなり不便です。 設定は、 Settings Manager パッケージを使用してProjectSettings/下に保存するようにすると使いやすくなります(Unity 2018.3 以降)。

また、UPM (Unity Package Manager) パッケージ形式で作ったアセットをunitypackage形式で配布することもできます。詳しくはこちらの記事を参照してください。[2021.5.7追記]

www.nowsprinting.com

*1:直下にAssembly Definition File (.asmdef) を置いてアセンブリを分ける前提

*2:Makefile老人会なのでMakefile

*3:Code Coverage packageの場合、設定で出力先を変更できます

*4:ただし、エディタ拡張だけでなくランタイムのスクリプトその他も除外するのは、納品には良いのですがCIで不便です。Gitはこの設定で、CIはignore定義が別のUnity Collaborateにするという手もあります

*5:Nuget | Restore Packages で復元可能なため

*6:末尾*を使用していないのは、インストール済みパッケージ情報を保持している/Assets/packages.configはトラッキングしたいため、ケースセンシティブでないOS対策です

*7:0.3.0-previewからここに保存されるようになったが、出力先をフルパスで保存してしまうので除外

*8:0.4.0-preview相対パスで保存されるように修正された

リモート時代のモブプログラミング(モブワーク)勉強会でmob IntelliJ pluginをお披露目 #モブLOVE #モブプロ #MobProgramming #RemoteMobProgramming

リモートモブプログラミングにフォーカスした勉強会(もちろんオンライン開催)のLT募集があったので、作っているリモートモブプロ支援IntelliJ pluginの紹介をしてきました。

moblove.connpass.com

勉強会冒頭のアンケートでは、リモートモブプロをやたことはある人は多いものの、単発であったり、うまくできていないという人が2/3くらい。

終盤の 銀の弾丸ラジオ 公開収録では寄せられたお悩み相談に、及部さんたちだけでなくZoomのチャットにも色々知見が寄せられて*1、たいへん有意義な会でした。

そんな中、オンラインあみだくじによってLTのトリで発表させていただきました。Visual Studio CodeのLive Share機能を使っている事例がいくつかあったので、JetBrains派の方々に向けてバランス取れたのではないでしょうか。

プラグインは JetBrains Plugins Repository で公開されています。

plugins.jetbrains.com

ただしまだEAP Channelのみなので、IntelliJ の Preferences | Plugins にチャネルを設定しないと出てきません。 もしくはplugin pageからファイルをダウンロードしてインストールすることも可能です。

EAP Channelの設定その他はGitHub repositoryのREADMEを参照してください。

github.com

チームの事情等でIntelliJが使えないという方は、移植元である mob コマンドをお試しください。こちらの記事でも紹介しています。 www.nowsprinting.com

はじめてのIntelliJ plugin開発でしたが、得られた知見は今後も当ブログで記事にしていく予定です。下のリンクから記事一覧が見られます。 www.nowsprinting.com

*1:Zoomのチャットは残らないのがつらいところですね

#yokohamaunity (cluster開催)で『Rider plugin の作りかた』をLTしてきました

yokohama.unity初のオンライン開催である「yokohama.unity ~オンラインDEハジメテユルクヤッテミル#0~」で、IntelliJ Rider plugin 開発についてのLTをしてきました。

f:id:nowsprinting:20200424225918p:plain

meetup.unity3d.jp

今回は初のオンライン、clusterでの開催でした。clusterはオーディエンスのリアクションが見られるのがとても良いですね*1

ただ、ものすごく贅沢を言うと、

  • 音声がやや聞き取りづらかった(うちの回線の問題だとは思いますが*2
  • スライドの登壇者ビューが小さめ(解像度低め)なので、スライドにキーワードを散りばめておいて話すスタイルだと厳しい

みたいなところは気になりました。

あと個人的に、うっかり他のアカウントにTwitterアカウントを紐付けてしまったため、cluster内で打ったコメントをTwitterに流せないのです*3。連携解除機能とか実装されてほしい。

登壇資料

以下、今回のスライドと、その中で紹介している過去記事です。

www.slideshare.net

www.nowsprinting.com

www.nowsprinting.com

www.nowsprinting.com

説明をだいぶ端折ってしまったSettings/Preferencesなど、また改めて単発のブログ記事を書き起こすつもりです。 今後の追加も含め、下のリンクから記事一覧が見られます。

www.nowsprinting.com

また、製作途中なものを晒したIntelliJ プラグインは、次のイベントでお披露目できる見込み…?

moblove.connpass.com

[5/18追記] 無事リリースされました!

plugins.jetbrains.com

懇親会

懇親会はRemoでという話だったのですが、重すぎてつながらず、急遽Zoomへ。 人数少なくなってしまった寂しさと、少人数だからZoomで会話が成立したので結果オーライ感と。

トータルで楽しかったので、ぜひまたやりましょう!

関連

仮面ライダーゼロワン RKF 仮面ライダーランペイジバルカン

仮面ライダーゼロワン RKF 仮面ライダーランペイジバルカン

  • 発売日: 2020/04/25
  • メディア: おもちゃ&ホビー

*1:なぜ壇上からのスクショを撮っていないのか…

*2:自分の音声どうだったのだろう、というのは気になるので教えてほしいです

*3:ZoomやDiscordのテキストチャットで盛り上げることもできますが、ログがそのサービスに閉じてしまいます。その点、clusterはコメントをTwitterにも流せるのが良いところなのですが

IntelliJ plugin から他のプラグインを使用する

IntelliJ (JetBrains IDE) ファミリー向けのプラグイン開発において、他のプラグイン(例えばGitを操作するための Git4Idea *1 など)や、Riderなど言語別IDEの機能を使う設定について。

基本的なことは公式ドキュメントの下記ページに載っており、それに沿って補足していきます。

プラグイン依存関係 / IntelliJ プラットフォーム SDK プラグイン開発ガイド

build.gradle

build.gradleの intellij ブロック内に plugins の設定を追加します。

Git4Ideaを使用する例 (Kotlin) :

intellij {
  version = "2020.1"
  setPlugins("git4idea")
}

Gradleでビルドおよび runIde タスクを実行するとき、この設定が使われます。 このとき、ここで記述したプラグインの依存関係については自動的に解決されます。

しかし、 test タスクでユニットテストを実行するときは、プラグインの依存関係が解決されないという制限があります。 上例の git4idea には依存先がないのですが、例えば android を使用する場合は次のように android が依存するすべてのプラグインを書く必要があります。

intellij {
  version = "2020.1"
  setPlugins("android", "junit", "Groovy", "gradle")
}

詳しくは下記 Issue を参照してください。この問題が修正される気配は無さそうに見えます。

Install plugin dependencies transitive dependencies automatically for tests. · Issue #38 · JetBrains/gradle-intellij-plugin · GitHub

plugin.xml

plugin.xml には <depends> で依存するプラグインを宣言する必要があります。 com.intellij.modules.platform の下に追加していきましょう。

<depends>com.intellij.modules.platform</depends>
<depends>Git4Idea</depends>

これはランタイムで使用される設定です。ここに書く名称についてはコード補完が効きます。

なお、プラグインの依存を必須にしない場合、 <depends>optional 属性を指定できます。 これは試していないので、公式ドキュメントを参照してください。

classpath

以上でプラグインのビルド・配布はできるのですが、IntelliJ IDEA上で開発を進めるにはclasspathも設定する必要があります。

File > Project Structure... を開き、SDKs > 11(使用しているJDKバージョンによりますが、ここではAmazon Corretto 11)を選択、classpathタブの"+"クリックで必要なjarファイルを選択します。

f:id:nowsprinting:20200419195750p:plain

jarファイルは、IntelliJ IDEAインストールディレクトリ/plugins/下にあります。 Git4Ideaの場合、plugins/git4idea/lib/下に4つのjarファイルがありますが、ディレクトリごと選択すれば大丈夫です*2

参考

プラグイン開発に関しては、公式ドキュメントにある程度の情報が載っています。日本語版もちゃんと更新されています*3

IntelliJ プラットフォーム SDK / IntelliJ プラットフォーム SDK プラグイン開発ガイド

IntelliJ Platform SDK / IntelliJ Platform SDK DevGuide

少し込み入った話になると、ヘルプセンターがヒットしたりはします。

IDEs Support (IntelliJ Platform) | JetBrains

本稿のclasspathの件はここにありました。

How to build and use git4idea as a library in plugin – IDEs Support (IntelliJ Platform) | JetBrains

*1:Git4Ideaはビルトインされているのでインストール不要なプラグインですが、プラグインから使用するための設定は必要です

*2:はじめgit4idea.jarだけを選択していたところ、中途半端な解決しかされず悩みました…

*3:が、翻訳には限界があるので検索で探し当てるのは難しいです

IntelliJ plugin からのログ出力

IntelliJ (JetBrains IDE) ファミリー向けのプラグイン開発をしていて、ログ出力まわりが少々わかりにくかった*1のでメモ。

プラグインからのログ出力

プラグインからログを出力するには、 com.intellij.openapi.diagnostic.Logger を使います。

Javaの場合

Logger log = Logger.getInstance(getClass());
log.trace("trace!!!");
log.debug("debug!!!");
log.info("info!!!");
log.warn("warn!!!");
log.error("error!!!");
log.error("例外を渡すことも可能", new Throwable());

Kotlinの場合*2

val log = Logger.getInstance(javaClass)
log.trace("trace!!!")
log.debug("debug!!!")
log.info("info!!!")
log.warn("warn!!!")
log.error("error!!!")
log.error("例外を渡すことも可能", Throwable())

サンドボックス実行環境のログ出力先

Gradle > runIde で実行すると起動するサンドボックスでは、ログはプロジェクトのルートディレクトリ下 /build/idea-sandbox/system/log/idea.log に出力されます。

idea.logは、Runウィンドウで見られるようにしておくと便利です。 Run > Edit Configurations... を開き、Gradle > runIde の"Logs"タブで"+"アイコンをクリックしてidea.logのパスを追加します。

f:id:nowsprinting:20200418183403p:plain

すると、Runウィンドウにタブが追加されるようになり、そこで idea.log を確認できます。

f:id:nowsprinting:20200419094611p:plain

なお、タブの右方にあるテキストフィールドでログの絞り込み、その左隣のドロップダウン(上図では"all")で表示するログレベルの変更ができます。

実際のログ出力先

プラグインをビルドした後、正規のルートでIntelliJ IDEAなどにインストールして使用するとき、ログはmacOSでは ~/Library/Logs/JetBrains/IntelliJIdea2020.1/idea.log に出力されます。

IDEをJetBrains TOOLBOXからインストールしていれば、TOOLBOXを開いて目的のIDEの右の六角形 > Settings にある "Show logs directory" ボタンで idea.log の場所がFinderで開きます。

なお、2020.1から、ログだけでなく、設定、キャッシュ、プラグインの格納場所が変更になっています。 2019.3以前の情報も含めてまとめられたページを教えていただきました。

ログレベルの変更

Diagnostic loggerのログ出力レベルのデフォルトは INFO です。 DEBUG まで出力するには、 Help > Diagnostic Tools > Debug Log Settings… を開き、改行区切りで # + パッケージ名もしくはクラスのFQCNを設定します*3

f:id:nowsprinting:20200418190423p:plain

ダイアログに書いてあるとおり、TRACE まで出力するにはパッケージ名もしくはFQCNの後ろに :trace を付けます。

なお、いずれも、開発中のサンドボックス実行環境の場合、runIdeで起動された側(サンドボックス側)のIDEで設定する必要があります。

参考

プラグイン開発に関しては、公式ドキュメントにある程度の情報が載っています。日本語版もちゃんと更新されています*4

IntelliJ プラットフォーム SDK / IntelliJ プラットフォーム SDK プラグイン開発ガイド

IntelliJ Platform SDK / IntelliJ Platform SDK DevGuide

少し込み入った話になると、ヘルプセンターがヒットしたりはします。

IDEs Support (IntelliJ Platform) | JetBrains

*1:バージョンの変わり目にはじめてのプラグイン開発をしたからというのが最大の理由ですが

*2:KotlinのパッケージレベルでjavaClassの代替手段がほしい。どなたか知っていたら教えて下さい

*3:ワイルドカードではないので、特定のパッケージ以下をすべて有効にしたければ画像のように書けばok。後ろに*をつけると無効になります

*4:が、翻訳には限界があるので検索で探し当てるのは難しいです

リモートワークでコミュニケーションが足りないなら、モブプロすればいいじゃない

リモートワークが急激に広まる中、その利点とともに、チームのコミュニケーションが不足しがちという不安も聞こえてきます。 その対策としての、リモートモブプログラミングのススメです。

普段の職場へのモブプログラミングの導入には色々と障壁もありますが*1、急なリモートシフトの混乱に乗じてを円滑に進める施策のひとつとして試してみる価値はあるのではないでしょうか。

モブプログラミングとは

まず、モブプログラミング(以下モブプロ)について。知ってる方は飛ばしてください。

モブプロとは、プログラミングを

  • 同じ仕事を
  • 同じ時間に
  • 同じ場所で
  • 同じコンピューターで

行なうことです。

コンピューターを操作するタイピスト*2は交代制で、その場の全員で目の前の問題解決に取り組みます。 ナビゲーターは決してオブザーバーや傍観者ではなく、それぞれ考え、検索し、話し合い、決定します。むしろタイピストは入力するだけなので、言語やドメイン知識があまりないメンバーでもこなせます。

なお、モブ「プログラミング」と銘打たれてはいますが、プログラミングに限らず様々な作業をモブで行なう事例も聞いています*3
また、普段プログラミングを主としていないテストエンジニアやプランナーがプログラマのモブに混ざって知識を共有しつつ作業を進める事例も聞きます。

より詳しく、また、うまく始めるためのコツなど、次のスライドをぜひ参照してください。

speakerdeck.com

実際にやってみて実感しているのは以下のような点です。

  • 暗黙知暗黙知のまま共有できる情報量
  • 知識は、必要なときにタイムリーにインプットするほうが吸収できる
  • プログラミングでは調査の時間がそれなりにあるが、それを手分けできる(もしくは誰かが知っている)ので意外と効率は悪くない
  • 探索的なコードリーディングでも、迷子になりにくい
  • 従来、PRレビューで指摘したりしなかったりすることを、ストレスなくコードに反映できる

リモートモププログラミング

モブプロの定義のうち「同じ場所で」をオンラインで実現するのがリモートモブプロです。

リモートで行なうメリットとして、オンサイトにおける以下の問題が解消します。

  • オフィスに場所がない(会議室が空いていない、自席でやると近所迷惑、大きいモニタが無い)
  • メンバー間でキーボードやエディタの差異があり「同じコンピューターで」コードを書くストレス

以下、自分の体験と、Remote Mob Programming | How we do Remote Mob Programming. および How Remote Mob Programming Is Working for Me を加味したおすすめのセットアップを紹介します。

画面共有

開発環境は参加者個々のものを使い、ビデオ会議システムの画面共有 (screen sharing) 機能でタイピストの画面を全員にシェアします。

いくつか試した範囲では、Zoom Proアカウントがあれば*4Zoomが最もおすすめできますが、Google HangoutsやDiscordでも代用可能です。

ポイントは、

  • 上に挙げたサービスでは、画面共有はビデオ会議セッションのオーナーに限らず任意の参加者が開始できます。ビデオ会議セッションは1つを維持したままで大丈夫です
  • ウィンドウ単位での共有でなく、ディスプレイ1つを指定して共有すべきです。ビルド・実行などでエディタとは別ウィンドウを使用する際に、その内容もモブに対してシェアするためです
  • VSCode Live Shareなど、IDEのコラボレーション機能は(現時点では)モブプロには不向きです。表示するエディタタブおよびスクロール位置を共有できないため、みんなバラバラの箇所を見てしまい、また各自好きに編集できるため、モブとしてディスカッションするのを放棄して個々のアイデアでコードを書いてしまいがち
  • Zoomのリモートコントロール (remote control) 機能を使って1台のコンピューターの操作権をタイピストに渡す方法もありますが、ラグがコーディングの妨げになるため推奨しません。ラグが気にならない作業でなら検討の余地はありそうです

また、モブプロでは参加者すべてが仕事に集中し、貢献することを求めます。特にリモートでは内職しないように、 Remote Mob Programming | How we do Remote Mob Programming. では常に全員のカメラをonにすることを推奨されています。
しかし、慣れているチームや少人数であれば、帯域を節約するためにもカメラoffでも問題ないでしょう。その代わり、マイクは全員常にミュートにせず、相づちは声に出すよう心がけます。

タイピストの交代契機

Remote Mob Programming | How we do Remote Mob Programming. では10分インターバルを提案されています。

慣れてくれば時間制でなく、キリの良いところやタスクボードを用意して短時間で終わるチケットに区切っていく方法でも良いのですが、はじめは時間制が安心です。 休憩タイミングの設定も「n巡したら休憩」と決めやすくなります*5

オンサイトと違いタイピスト交代のコストがかかりますので、いずれにせよ時間は長めに設定するのが良さそうです。

Gitリポジトリによる受け渡し

Remote Mob Programming | How we do Remote Mob Programming. では、タイピストの交代ごとにWIPコミットをGitのリモートリポジトリ(モブセッションのブランチ)にpushし、次のタイピストがpullして続きの作業を行なう方法が紹介されています。

また、その操作をすばやく行なうためのコマンドラインツール mob が公開されています。これはGo言語製のコマンドラインツールで、次のように使用します。

$ mob start 10

これでブランチmob-sessionが作られ、その中でコードを書いていきます。"10"は交代までの時間指定(分)で、通知とデフォルトではsayコマンドの音声で交代を教えてくれます(macOSの場合)。

時間が来たら、

$ mob next

で作業状態をコミット(コミットメッセージはmob next [ci-skip])、pushしてくれます。

次のタイピストは自分のコンソールで

$ mob start 10

を実行すると、ブランチmob-sessionをpullしてくれるので続きの作業ができます。これを繰り返して進めていきます。

一連の作業が終わったら、

$ mob done

でブランチの作業内容がスカッシュされます*6。 適切なメッセージを付けてコミット、pushすることで完了です。

なお、mobコマンドの利用にあたっては、いくつか注意点があります。

  • リモートブランチ名は環境変数で設定します。1つのリポジトリで複数モブが作業する場合、モブごとに設定しましょう
  • リモートブランチをupstreamに設定しておく必要があります
  • mob done 実行時点でリモートブランチが消えてしまうのでオペレーション注意
  • mob start コマンドに share オプションを追加することでZoomの画面共有を開始(切り替え)できますが*7、あらかじめ以下の設定が必要です
    • Zoom > 設定 > キーボードショートカットを開き、「画面共有の開始/停止」の「グローバルショートカットを有効化」チェックボックスをonにします
    • macOS Catalinaの場合、スクリプトからの操作に制限がかかっています。システム環境設定 > セキュリティとプライバシー > "プライバシー"タブ > "アクセシビリティ" を開き、「下のアプリケーションにコンピュータの制御を許可」に /usr/bin/osascript を追加します*8。ただし、悪意のあるスクリプトによる操作も受け入れてしまう設定のため注意が必要です (4/6追記)

mobコマンドを使わない場合でも、交代をスムーズに行なうため「交代時のコミットメッセージにはこだわらない」「後でスカッシュする」等、ルール化しておくとよいでしょう。

[5/18追記] mobコマンドをIntelliJ pluginに移植したものをリリースしました。IntelliJ IDEsユーザの方はぜひ使ってみてください! plugins.jetbrains.com

マイク

環境音やエコーを防ぐため、外付けのマイクの使用を推奨します。指向性のあるコンデンサマイク(数千円のもので十分)で自分の声のみ拾うようにするだけで、ハウリングやタイプ音がなくなります。

私が使っているのはこれ

もしくは、スピーカーでなくヘッドホンを使用するだけでもハウリングは防げます。 ノイズフィルタリングしてくれるソフトウェアもありますが*9、音声品質はストレスの無いモブプロのために重要ですので、ハードウェアで何らかの対策をしておくことをおすすめします。

ホワイトボード

設計についてディスカッションする際は、オンラインで共有できるホワイトボードがあると便利です。 我々もまだ選定中なのですが、Miroは評判もよいですし、タスクボードやテキスト入力もできてこれひとつあれば大丈夫そうな感触です。

miro.com

描くだけであれば、ZoomやMicrosoft Teamsのホワイトボード機能や、Google JamboardやMagicalDrawといった選択肢もあります。 プログラマーは、ペンタブレットや液晶タブレットよりiPadAndroidタブレット端末の所有率が高いと思いますので、iOS/Android対応しているサービスがよいでしょう。

gsuite.google.co.jp

draw.kuku.lu

タスクボードは、GitHub ProjectsやGitKraken Glo Boardsなどの選択肢もあります。

GitKrakenの利用はぜひこちらから! www.gitkraken.com

まとめ

もちろんモブプロは万能ではなく、分担作業が向いているものもたくさんあります。しかし、全くやらないのはもったいないことだと思うので、今この状況を好機だと思って、試してみてはいかがでしょうか。

モブプロ自体、チームによって様々なやり方がありますが、最初は少々やりにくさを感じても今回紹介したようなテンプレートに沿ってやってみることをおすすめします。

参考資料

speakerdeck.com

speakerdeck.com

takaking22.com

(4/4追記) piyolog.hatenadiary.jp

*1:上司の説得(生産性が下がるのではないか等)、会議室が空いていない、自席でやると近所迷惑、大きいモニタが無い、キーボードやエディタの差異など

*2:ペアプロにおける「ドライバー」ですが、モブでは「タイピスト」のほうがふさわしいと思うのでこの表現で

*3:「モブワーク」「モブデザイン」「モブテスティング」などで検索してみてください。ただし万能ではなく、作業によって向き不向きはあります

*4:Proアカウント以上でないと40分制限があります

*5:意識的に休憩を入れないと、つい連続でやってしまって疲労困憊します(しました)

*6:この時点でリモートブランチも削除されますので注意!

*7:macOSLinuxのみ

*8:あらかじめFinderの 移動 > フォルダへ移動 で /usr/bin に移動し、サイドバーにドラッグ&ドロップして追加しておくことで指定可能になります

*9:https://krisp.ai/ など