KLabGames Tech Blog

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

この記事は KLab Advent Calendar 2017 8日目の記事です。

こんにちは knsh14 です。 Unity で AnimationClip Editor を作った話をします。

KLab では様々な演出などで AnimationClip を使っていますが、Unity 標準の AnimationClip のエディタはどうにも使いづらいものになっています。

  • 各オブジェクトの並べ方が幅優先表示になっていて非常に見づらい
  • ショートカットがほとんど無い
  • 編集にじゃまなパーツを折りたたんで非表示にできない

などなどあります。 既存のアセット等でも対応できなさそうだったので、今回内製でエディタを作成することになりました。

実際の画面

sample

機能

エディタというのは単純だと思っていてもいろいろな機能があります。

エディタとしての機能

  • 新規作成、読み込み
  • 前回保存分を再開する
  • AnimationClip の情報の編集
  • AnimationCurve への KeyFrame の追加削除、値の編集
    • 変更はその場で反映される
  • キーのコピペ
  • キーを範囲選択して左右へ移動させる
  • 操作の Undo/Redo
  • データの保存

AnimationClip を編集するための機能

  • 今の状態を自動でプレビューできるようにする
  • シーン側でオブジェクトを動かしたりしたらエディタのキーフレームに反映される

基本設計

クラスやプロジェクト構成などは Clean Architecture を参考に作成しました。 Unity Editor 拡張で説明するより、 iOS/Android で解説されている資料のほうがより詳細にかかれていてわかりやすいと思うので、そちらを参考にしていただくのが一番いいと思います。

以下のような構成でできている仕組みです。

  1. Presentation 層

    1. View
      • UI を構築します。
      • UnityEditor.EditorWindow を継承し、アプリケーション全体の起動も担当します。
    2. ViewModel
      • Data 層の Entity を View で表示するためのクラスです
    3. Presenter (Controller)
      • View から受け取ったイベントを UseCase に流して処理してもらったり、データを取得したりする。
      • 本来なら Controller がイベントを受け取り UseCase に流す、 Presenter がデータを取得するという役割分担までするのですが、一旦まとめて実装しました。
  2. UseCase 層

    1. UseCase
      • 固有のロジックを実装します
    2. Translator
      • Entity を受け取り、Presentation 層で使う ViewModel に変換します。
      • Presentation から受け取ったものを Entity で扱えるようにしているところもあります。
  3. Data 層

    1. Repository

      • UseCase と DataStore の遣り取りをするための薄いインターフェース
    2. DataStore

      • Entity の集合で CRUD を実装します
      • DataStore をシリアライズして JSON に保存することで前回の状態から作業を再開できるような仕組みも実装できます。
    3. Entity

      • コアのデータ定義をします。
      • 例えば GameObject の ID、アニメーションするプロパティ、AnimationCurve のセットやショートカット名と実行するキーの組み合わせなどです

こうすることで以下のようなメリットがあります。

  1. UI と裏側のロジックを完全に分離できる

    • 特に Unity EditorWindow のイベントハンドリングは中々癖が強いので出来る限り一元管理できる仕組みにできたほうが都合がいいです。
  2. テストしやすい

    • 操作を UI から切り離しているので、操作ごとにテストを走らせるなんてこともできます
    • このエディタは一人で作っていたので簡単にテストできる仕組みは動作を安定させるために必須項目でした
  3. コードの再利用がしやすい

    • ショートカット機能では利用者が好きなようにショートカットの割当をできるのですが、これは別のウィンドウで行います。
    • そのために似た実装が増えるのは大変だし、共通化することでどちらかでバグが出たらすぐに気づけるようにしました。
  4. 実装にブレが出にくい

    • この領域では何をするかが割りときっちり決まっているので、実装にブレが出づらいです。
    • また後でメンテナンスする場合にも読みやすさが高くなります。

デメリットもあって、次のようなものです。

  1. ちゃんと理解してないとどこに何を書くのかわかりづらい

    • これは MVC などでも言えることだと思いますが、設計をしっかり理解してからじゃないとうまくかけないのはちょっとハードルが高いです。
  2. 当然ですがコードが長くなる

    • 強いて言えば程度のデメリットですが、当然たくさんコードを書かないといけません。

エディタを実装する

全ての機能を紹介するのは大変なので一部だけ抜粋してどのように実装したか紹介します。

Undo/Redo

Undo/Redo は自前で実装するのは結構たいへんですが、Unity には割りと簡単に Undo をサポートする仕組みがあります。 Undo したいオブジェクトをシリアライズされる状態にして、UnityEngine.Object から辿れるようにします。 UnityEditor.Undo.RecordObject(UnityEngine.Object obj, string title) で操作を記憶してやると、その操作が Undo スタックに乗って、Unity のショートカットで Undo/Redo することができます。

範囲選択してコピペ

エディタといえばコピペですね! ペーストは clipboard にあるものを取り出してキーを追加する操作をすればいいので簡単なのですが、意外と大変なのがコピーです。

コピー操作はキーフレームを clipboard に入れることで実装できます。 ただコピーと一口に言ってもいろいろなコピー対象があります。

  • パーツについてるプロパティのキー全体をコピー
  • パーツについてるプロパティのキーの一部をコピー
  • パーツについてる一部のプロパティのキー全体をコピー
  • パーツについてる一部のプロパティのキーの一部をコピー
  • フレキシブルに選択した範囲をコピー

この種類を全部対応するのは大変そうだったので、最後の「事前に範囲選択をしてからその領域にあるキーフレームをコピー」だけを実装することにしました。 編集する対象によってこの辺は変わることが多いと思うのでどの仕組みにも対応できるように作っておけると楽になると思います。

ショートカット

ショートカットは利用していただいてるデザイナの方にも非常に好評で作ってよかった機能です。

仕組みは簡単で

  1. View にショートカットに対応しているメソッドを用意
  2. キーイベントからキー入力と突き合わせて一致したメソッド名を取得
  3. リフレクションで実行

というフローになっています。
ただこれには問題点があって、これだと1発のキー入力しかショートカットに登録できません。
Ctrl-X Ctrl-O のような2段階ショートカットに対応するには、一旦キー入力をキューに保存するなどの工夫が必要になります。

苦労したところ

UIレイアウトがなかなか直感的に書きづらい

全部 C# で書けるのは割りと楽なことも多いのですが、HTML で書いてこの要素にイベント仕込むだけならもっとUI作成も楽なのになあと思うことも多少あります。 またレイアウトではどうしても右寄せ左寄せなどの配置が難しいので、キーフレームの描画などは苦労することもありました。

またいろいろなデータを描画しようと思うと描画順で頭を悩ませることもありました。
処理的にはここでひとまとめに書くのがシンプルなんだけど描画を考えるとあとでわざわざ描くといった苦しいコードになることもありました。
depth 欲しいです。

Unity Editor 拡張のイベントハンドリングは辛い

Unity のイベントハンドリングは UnityEditor.Event.current を見てハンドリングするのですが、個別のUIに操作を割り当てるのがかなり苦手な感じを受けました。   ボタンやUIの値が変更された場合は割りと簡単に個別のハンドリングができるのですが、ドラッグやショートカット用のキーイベントなどになると途端にシンプルに書きづらくなるので、注意する必要があります。

終わりに

今回は Unity の Editor 拡張でエディタを作るという、Unity を使っているだけならなかなかやらないような話をしました。   ちゃんとしたエディタを一通り作るのは Unity の力を借りてもかなり大変でした。 普段は何気なくやっている操作も色々試行錯誤がある上にできていると思うと有り難みもましてきます。 世の中のテキストエディタなどを作っているエンジニアの方々の苦労が忍ばれます。
この文章を書いている Vim のコントリビューターの方々には尊敬の念を禁じ得ません。

Unity 2017 では標準の Timeline がかなり便利なので、そちらの UI を手軽に Unity 5 系でも使えたら嬉しいなあと思います。

明日は9日目です。hohean さんの記事です。お楽しみに

このエントリーは、KLab Advent Calendar 2017 の12/4の記事です。

今年のISUCONはKLabが作問するということで、ゲームが題材だと予想していた方も多かったと思います。 その期待(?)に応えるべく、本選ではCookie Clickerを元にしたゲームを出題しました。 これを複数人で協力プレイできるようにして、WebSocketを使っていて、クッキーではなく椅子を増やし、椅子の数が多倍長整数になる、というゲームでした。

この記事では @hasi_t がISUCON7本選の作問でやったことを書きます。

プロトタイプ作成

まずプロトタイプ作成を担当しました。 Cookie Clickerを元にする、という案が出ていたので、自分がやりたい要素を入れたプロトタイプを作りました。

一つ目の要素は未来計算と操作遅延です。 サーバが未来の値を計算できるようなデータを返し、クライアント側で各時点での値を計算するようにする、という設計です。 そして操作を遅延させることで可能な限り同時刻での見え方が同じになるようにしました。 このような設計によって通信頻度を下げる、というのは、 CEDEC 2015で発表したことの実践で(参考)、 それを実現するための技術や開発体制について普段の業務で日頃考えていたりするのですが、 その実態を見せる機会と考え、この要素を入れました。

二つ目の要素は多倍長計算です。 以前、 ICFPC 2016 というプログラミングコンテストに参加したときに多倍長有理数を使って、 こういう多倍長計算を使った問題を作りたいと思っていた、というのがあります。 あと、メモリを消費させて、ただキャッシュするだけでは駄目なようにしたい、というのも考えていました。

ちなみに、作問チームのうち3人 (@mecha_g3, @___Johniel, @hasi_t) はここ4年ほど、この3人だけではないですが、DiamondPrincessというチームでそのICFPCに参加しています。そして今年は2位でした!

プロトタイプの時点ではWebSocketではなく、普通のHTTP通信で、サーバはPHPで雑に書いた状態でした。 ver0とver1を作ったのですが、ver0の時点ではミリ秒単位ではなく秒単位だったりしました。 あと、命名が雑すぎて抽象的な一文字変数名だらけになっていたので、本実装時に名前の調整が結構大変でした。

本実装作成

WebSocketを使うことになり、初期実装は他の人に任せて、 ゲーム画面を作るためにJavaScriptコードをロジックとビューに分割したり、 いい感じのフォントを探したりしていました。

ベンチマーカのバリデーション作成

未来計算の設計時点で、ベンチマーカから見て検証可能にすることを考えていました。 誤差を許容するには、許容範囲やそれに合わせた計算など、考えることが多く、それを避けた結果、厳密な検証になりました。 そのため、値が1でもずれるとfailするという厳しいコンテストになりました。

負荷走行前の検証はaddIsu, buyItemに対するレスポンスが必ず存在するので良かったのですが、 負荷走行後の検証はタイムアウトなどでレスポンスが無い場合を考える必要があり、 上限と下限を考えて検証する必要があってちょっと大変でした。

エラーメッセージを参加者にとってわかりやすくする、という作業をするのがぎりぎりになってしまい、 初期実装作成者には苦労をかけてしまいました。

ベンチマーカのチューニング

当然といえば当然ですが、初期実装そのままで検証していたら、 負荷走行後の検証の実行時間が長すぎる問題が発生し、高速化をしました。

やったこととしては、指数表記変換関数の高速化、item価格と生産速度のキャッシュ、1000回ループの回避、指数表記変換関数内の10のn乗のキャッシュ、addingの累積和を使う、などです。

実装は以下の通りです。指数表記変換関数(big2exp)はutil.goにあります。

とりあえず、Go言語のbig.Intは、つらい、という感想でした。

マスタデータ調整

いい感じのマスタデータにしないと多倍長使う意味が無い、と思っていたので、 1分間のベンチマークで10の10万乗ぐらいまで到達できるようにマスタデータを調整しました。 調子に乗って1個目の購入価格が10の10万乗ぐらいあるアイテムを作ったら、 初期実装の時点でそこが重すぎて、リハーサルやってみたけどまともにチューニングできない、 みたいなことも起きたりして申し訳ない感じだったりしました。

ちなみに、Excelで値を調整して、Pythonスクリプトで雑なシミュレーションをする、ということをしていました。 もちろん、値を直接扱えないので、対数をとって計算していました。

おわりに

参加者の皆様、運営の皆様、お疲れ様でした! いろいろご迷惑おかけしましたが、出題することができて嬉しく思っています。 ありがとうございました!

(本稿はKLab Advent Calendar 2017 の3日目の記事になります)

昨今、電子工作やマイコンプログラミングへのハードルが急激に下がっているという印象があります。皆さんの身の周りでもArduinoやRaspberry Piに秋葉原で買ったセンサーを繋いで遊んでいる人がいるのではないでしょうか?

私の所属しているKLab株式会社にも「Make部」という部活があり、毎年Maker Faire Tokyoで各個人が作品を展示したり社内勉強会を開いたりして、業務と無関係に電子工作を楽しんでいたりします。

ふつうのLinuxマシンでもセンサー類を扱いたい

とはいえ、ArduinoもRaspberry Piも普段使っているLinuxマシンやmacOSマシンとは随分異なる環境です。ふつうのLinuxマシンにセンサーを接続して、気軽に扱えないものでしょうか?

Raspberry Piでセンサー類を簡単に扱えるのは、GPIOインターフェースが存在するためです。GPIOピンと各種センサー類やサーボモータなどを接続することで、Linux+電子工作の組み合わせが簡単に実現できるよ、というのがRaspberry Piの強みだと言えるでしょう。

Raspberry PiのGPIOピン

一方で、多くのLinuxマシンにはGPIOインターフェースが存在しません。たとえばUSB to I2C変換モジュールを使えばLinuxマシンでも電子工作的な遊びはできますが、追加の出費が必要ならRaspberry Piを買った方がマシだよ、となってしまいそうです。

本稿では、DigiTempを使ってLinuxマシンで比較的安価に温度センサーを扱う方法を紹介します。DigiTempはLinuxのシリアル通信インターフェース経由で1-Wire接続のセンサーを扱うOSSで、多くのLinuxディストリビューションで標準パッケージとして採用されています。

ソフトウェアのインストール

まずはソフトウェアのインストールをしましょう。大抵の環境でDigiTempはコマンド一発で入るはずです。

Debian系なら apt でインストールできます。

$ sudo apt install digitemp

RedHat系は yum でインストールできます。

$ sudo yum install digitemp

macOSでも遊べます。Homebrewからインストールしましょう。

$ brew install digitemp

後述するようにシリアル通信ドライバも必要です。こちらは利用するシリアル変換ICに応じて適切なドライバをインストールしてください。

ハードウェアの準備

当然ですが、ソフトウェアだけで外気温の測定はできません。DigiTempの利用には下記のような準備が必要です。

USB to シリアル変換モジュール

USBからシリアル通信(UART)への変換モジュールです。Arduinoで遊んだことがあれば必ず1個は持っているのではないでしょうか。変換ICとしてはFTDI社のFT232RLなどが有名ですが、他にもSilicon Labs社CP2102やProlific社 PL2303を採用した変換モジュールも容易に入手できます。

ちなみに私はCP2102ベースの変換モジュールを利用しています。これはeBayで1.15ドルでした。

CP2102 USB to シリアル変換モジュール

FTDI製ICを使っているモジュールであればドライバは標準でインストールされていることが多いかもしれません。それ以外の場合は手動でドライバをインストールする必要があるはずです。

温度センサ DS18B20

DS18B20はMaxim Integrated Products社製の温度センサです。秋月で買うと1個250円ですが、eBayなどを探せばもっと安いものも見つかります。

DS18B20

このセンサーは1-WireというMaxim独自のプロトコルで動作します。このプロトコルがUART経由で扱う上でのキモです。1-Wireはデータレートが低速かつ通信線1本で動作するので、UARTからでもドライブできるのです。他のプロトコルのセンサーであれば何らかのICが必須になるでしょう。

UARTとDS18B20の接続用ボード

UARTとDS18B20を直結しても動くことは動くらしいのですが、逆流防止などの用心のため、簡単な回路を工作してみました。

UART to DS18B20 接続用ボード

これはdword1511/onewire-over-uartで紹介されている下記の回路図を元に作成したものです。

回路図

材料は下記の通りです。

  • ユニバーサル基板
  • L型ピンソケット(1×6、メス)
  • 5.1kΩ 抵抗
  • スイッチングダイオード 1N4148
  • ポリウレタン銅線(配線用)

動かしてみる

これらを組み合わせると次のような見た目になります。思ったより場所を取る感じの仕上がりになってしまいました。

完成図

これをLinuxマシンのUSBに接続して、先ほどインストールしたDigiTempを起動すると温度が取れます。

コマンドの使い方として、まずは-wオプションを指定して1-Wireデバイスのスキャンを行う必要があります。-sオプションはシリアルポートの指定です。

$ /usr/bin/digitemp_DS9097 -s/dev/ttyUSB0 -w
DigiTemp v3.7.1 Copyright 1996-2015 by Brian C. Lane
GNU General Public License v2.0 - http://www.digitemp.com
Turning off all DS2409 Couplers
.
Devices on the Main LAN
28FF933161150389 : DS18B20 Temperature Sensor

これで接続されているセンサーの情報が$HOME/.digitemprcに記録され、次回以降の起動でこの情報を参照するようになります。

$ /usr/bin/digitemp_DS9097 -a -d 2 -n 5
DigiTemp v3.7.1 Copyright 1996-2015 by Brian C. Lane
GNU General Public License v2.0 - http://www.digitemp.com
Dec 02 21:21:27 Sensor 0 C: 21.50 F: 70.70
Dec 02 21:21:29 Sensor 0 C: 21.56 F: 70.81
Dec 02 21:21:31 Sensor 0 C: 21.50 F: 70.70
Dec 02 21:21:33 Sensor 0 C: 21.50 F: 70.70
Dec 02 21:21:35 Sensor 0 C: 21.44 F: 70.59

上記は2秒間隔でセンサーの値を5回取得する指定です。摂氏と華氏で温度が取れているのがわかります。

ちなみに、筆者はこれを家のLinuxルータに刺した上で取得した値をMackerelに書き出しています。

温度変化グラフ

上記のグラフが作りたいだけならRaspberry Piで温度センサーを扱った方が楽なのでは?と思われるかもしれません。理屈で言えばそうかもしれませんが、個人的にRaspberry Piは長期間電源を入れっぱなしにする気が起きないので、ふつうのLinuxマシンで運用できることに価値があるように感じています。同じ感覚の方が他にいらっしゃるかはわかりませんが…。

まとめ

  • DigiTempというLinux/macOS上で温度センサーDS18B20を扱うOSSを紹介しました
    • シリアル通信(UART)経由で1-Wireセンサーをドライブできます
    • 多くのLinuxディストリビューションで標準パッケージ採用されています
  • Raspberry Piで同じことをするより適用範囲が広かったり長期運用しやすかったりするかもしれません

@hnw

↑このページのトップヘ