JetBrains Riderで適用されるCodeStyle
AndroidStudio 3.2-Canary17の所感
Moduleの依存グラフが表示されるようになっていた
- いつからだろうか
- Build Variants >
i
アイコンから見れる - 依存Moduleの依存は見れない
app
>sdk
>ktx
のような依存グラフにはなってない- そのうち改善される?
Android API-28公開
- これでライブラリ公開やGoogle Play Consoleへのアップロードが可能になった
- APIがFIXされたので、互換性チェックも行えるだろう
- Kotlinバージョン 1.2.41 にアップデート
ConstraintLayoutが破壊的変更
- パッケージ名が変更された
- NG
androidx.constraintlayout.widget.ConstraintLayout
- OK
androidx.constraintlayout.ConstraintLayout
- NG
NavigationEditor対応
- 相変わらずビルドに失敗する
- Googleにクラッシュレポートをアップして終了
Unity学習の整理 Animatorの基本
UnityChanをアニメーションさせる
Animator
- 複数Layerが配置できる
- Layerは
Baseが身体本体の歩きモーション
表情モーション
のように組み合わせられる - UnityChan付属のAnimator
UnityChanAnimationCheck
が参考になる
- Layerは
- 基本的にステートマシンとして動作する
- おそらく同じ骨構成であれば別アバターにも適用できる構造のはず
アニメーションループ
- アニメーションが終わったタイミングでイベントを発火というユースケースがある
- イベント処理で使っていた
- 現在のAnimationの位置が正規化されたTime値でわかる
- 開始 => 終了で1.0
- 周回を進められるので、小数点以下が正規化された現在のループ
- 整数部がループ回数として得られる
- アニメーションがループ設定になっていない場合でも時計は進み続ける
- UnityChanの
JUMP00
で確認できる - 時計は進むが、アニメーションは止まる
- 適切な次のアクションに切り替えてあげなければならない
- UnityChanの
- ポーズだけのアニメーションの場合、凄まじい速度で時計が進む
- UnityChanの
POSE
で確認できる
- UnityChanの
UniRxでループタイミングを得る方法
normalizedTime
をint変換することで、整数部の切り替わり = ループのタイミングとして検出できる。- 0は最初の開始時となる。
_animator = GetComponentInChildren<Animator>(); _animator.UpdateAsObservable() .Select(_ => (int) _animator.GetCurrentAnimatorStateInfo(0).normalizedTime) .DistinctUntilChanged() .Subscribe(loop => Debug.Log($"Animation Loop!! [{loop}]"));
RangeBarで始めるCustomViewとDataBindingとMVVM
どういうことがしたいのか
こんなUIをMVVMで簡潔に実現したい。
- ユーザーに心拍数(下限、上限)等の一定範囲データを設定させる
- その際、実装コストを減らしたい
- なるべく現代的に仕上げたい
- UIは RangeBar を利用する
- RangeBarを操作すると、対応する値が表示される
- RangeBarでは最大値1000のようなインデックス値のみ設定でき、min-maxのような範囲は指定できない
- ViewとModel分離の利点として、UIに設定可能な値と実際の値を分離することができる
- Modelを単純値のシンプルなまま、UIをカスタムすることができる
- 実際に設定されるViewの変数と保存される値の取次をViewModelが担当する
設計方針
- Android Architecture ComponentsのMVVMを使用
- Viewはほぼレイアウトファイルのみ
- 入力値は範囲を絞る
- 人間にとって心拍0はありえないので、入力値を限定させる
- データは整数値で保存する
RangeBarをDataBinding対応にする
DataBindingを行うための拡張
@BindingAdapter
でViewModel -> Viewの一方通行@InverseBindingAdapter
で View <-> ViewModel の双方向- ここではViewModelにRangeBarのインデックス値を設定させている
@InverseBindingAdapter(attribute = "range") fun RangeBar.getRange(): Range<Int> { return Range.create(leftIndex, rightIndex) } @BindingAdapter("range") fun RangeBar.setRange(range: Range<Int>?) { @Suppress("NAME_SHADOWING") val range = range ?: return if (range.upper <= range.lower // validation error. || range.lower == leftIndex && range.upper == rightIndex // not modified. ) { return } setThumbIndices(range.lower, range.upper) } @BindingAdapter("tickCount") fun RangeBar.bindTickCount(value: Int) { this.setTickCount(value) }
ViewModel
val heartrateRange = MutableLiveData<Range<Int>>()
にViewが持つIndex値を保存するcommitHeartrateRange()
で保存されるときにIndex値を変更してあげる- このViewModelでは、実際に保存するときとViewに渡される値に
HEARTRATE_RANGE_OFFSET
ぶんの差が発生するので、ViewModelがソレを吸収する
class FitnessViewModel : AppViewModel() { private val fitnessRepository: FitnessRepository by lazy { FitnessRepository.getInstance() } private val serivce: FitnessService by lazy { FitnessService() } val heartrateRange = MutableLiveData<Range<Int>>() override fun attach(owner: ViewModelOwner) { super.attach(owner) heartrateRange.value = serivce.heartrateRange.let { Range.create(it.min - HEARTRATE_RANGE_OFFSET, it.max - HEARTRATE_RANGE_OFFSET) } } fun commitHeartrateRange() { val current = heartrateRange.value ?: return launch { // HeartrateRangeはRaw値のため、ここで値を変換する serivce.setHeartrateRange(HeartrateRange( current.lower + HEARTRATE_RANGE_OFFSET, current.upper + HEARTRATE_RANGE_OFFSET )) } } companion object { @JvmStatic val HEARTRATE_RANGE_OFFSET = 50 @JvmStatic val HEARTRATE_RANGE_MAX = 200 } }
XML設定
app:tickCount
固定値だが、ViewModelの定数を計算で表示しているapp:range
は双方向なので、@={ViewModelの変数名}
を使用する
<com.edmodo.rangebar.RangeBar2 app:tickCount="@{fitness.HEARTRATE_RANGE_MAX - fitness.HEARTRATE_RANGE_OFFSET}" app:range="@={fitness.heartrateRange}" android:id="@+id/HeartrateRange" android:layout_width="match_parent" android:layout_height="wrap_content"/>
RangeBarを継承せずにカスタムイベントを追加する
- RangeBarの仕様上、値が変更されるたびにコールバックが飛んでくる
- RangeBarの値を50から100に移動させると、50,51,52.....100のようにすべての値でコールバックが飛んでくる
- そのたびにストレージに書き込むと、無駄な書き込みが発生する
- ユーザーが値を決定した(指を離した)際のListenerは定義されていない
- カスタムで作り、DataBindingにも反映させる
app:onRangeComplete
というAttributeを作成したOnCompleteListener
には引数付きだが、DataBinding側では引数なしのラムダ式を書いても問題なくコード生成してくれる- これにより、 ViewModelの
commitHeartrateRange()
を確定タイミングでのみ行わせることができる
interface OnCompleteListener { fun onIndexChangeCompleted(view: RangeBar, leftThumbIndex: Int, rightThumbIndex: Int) } @BindingAdapter("onRangeComplete") fun RangeBar.setOnCompleteListener(listener: OnCompleteListener?) { if (listener == null) { setOnTouchListener(null) } else { setOnTouchListener { _, event -> if ((event.action and MotionEvent.ACTION_UP) != 0) { listener.onIndexChangeCompleted(this, leftIndex, rightIndex) } return@setOnTouchListener false } } }
<com.edmodo.rangebar.RangeBar2 app:onRangeComplete="@{() -> fitness.commitHeartrateRange()}" />
RangeBarの変更を別なViewへ反映する
- RangeBarが変更されたら、実際の設定値をユーザーに表示させなければならない
- これもレイアウトXMLに記述できる
- 変更通知はLiveDataが受け持っているため、複雑な設定は不要
app:valueText
で、テキストを設定している- このとき、string.xmlのフォーマッタを適用している
@string/HogeFuga(値)
で参照できるsafeUnbox()
で囲わないとLintに怒られる場合がある
- 計算もできるので、表示するときには
fitness.HEARTRATE_RANGE_OFFSET
を加算してもとの値にしている
- このとき、string.xmlのフォーマッタを適用している
- 計算がダルければ、表示テキスト用のLiveDataをViewModel側に用意しても良いかもしれない
<org.andriders.ace.component.activity.widget.AppKeyValueView app:keyText="Heartrate" app:valueText="@{@string/BodyHeartrateFormat(safeUnbox(fitness.heartrateRange.lower) + fitness.HEARTRATE_RANGE_OFFSET, safeUnbox(fitness.heartrateRange.upper) + fitness.HEARTRATE_RANGE_OFFSET)}" android:layout_width="match_parent" android:layout_height="wrap_content"/>
<string name="BodyHeartrateFormat">%1$d bpm - %2$d bpm</string>
発生した問題
LiveDataの変更をしても値が反映されない場合
- 生成されるLayoutBindingクラスにLifecycleOwnerを渡していないとそうなる
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = ProfileBinding.inflate(inflater, container, false).also { binding -> binding.setLifecycleOwner(activity) binding.fitness = fitnessViewModel } return binding.root }
Unity学習の整理 UnityEditorでC#のUnitTest
C#でコードを書くにあたって、UnitTestができたほうが安心できるので調べた。
UnitTestの方法
- UnityEditorに組み込まれたUnitTest Runnerが使用できる
- NUnit相当の機能が使える
テストプロジェクトに癖がある
Create > Testing
でTestsディレクトリを作成可能- 作成されたテストプロジェクトは、UnityProject(ゲーム)側のdllが参照されない
- ゲームコードのUnitTestができないのでは?
- 公式動画を見ると、アセット内に直接UnitTestコードを追加していた
- 成果物バイナリにUnitTestが含まれてしまうのでは?
- 最適化で外される?
- 試しに
new HogeTest()
をゲームコードから呼び出したが、リリースビルドでも動作した - CIなりなんなりでどうにもできるとはいえ、この構成は正しいのか?と不安になる
- C#の仕様とも比べなければならない
- 成果物バイナリにUnitTestが含まれてしまうのでは?
非同期のテストも記述できる
IEnumerator
を返却することで非同期(Unity Coroutine)のテストが行える- その場合のAttributeは
[UnityTest]
Runtime(Editor外)でもテストが実行できる
- Windowsで確認
- Monoに限定されるだろうが、実ビルドしてEditor外でUnitTestが行える
- ある程度安心はできそう
Rider内でテストが失敗する
- 原因が不明
- Unity Editorでは成功しているが、Riderは実行直前に例外で落ちる
Unity学習の整理 連携する統合開発環境
開発環境は必要に応じて最適なものを使いたい。 Unityでアプリを開発するにあたって、選定を行った。
C#プロジェクトエディタの選定
- Microsoft VisualStudioとJetbrains Riderが候補
- 色々勘案した結果、現在はRiderを使っている
Visual Studioの利点・欠点
- Unityプロジェクト側にプラグインを追加せずに使える
- Riderは専用プラグインが自動的に組み込まれる
- 「どうしてもファイルを増やしたくない」ならこっち
- 日本語環境がある
- Visual Studioである
- エディタ機能は超強力
- Xamarin等、他の環境も使いやすい
- 話がそれるけど、iOSのLayout Editor等の環境はVisual Studioのほうが整備されている
- こっちに慣れておけば、同じC#のXamarinを使う際の抵抗が小さくなるかも
- ReShaperがないとどうにもならない
- 結局Jetbrainsのお世話になるのである
- 有料である
- 会社規模が大きい場合、有料となる
- ライセンス料は結構高い
Riderの利点
- Android StudioやGolandと同様の使いやすさ
- ベースが同じなので、概ね同じように動作する
- 微妙にショートカットや機能が違う
- Visual Studio+ReShaper環境に合わせられる
- サンプルコードだとBreakPointにアタッチできない
- Visual Studioならできる
- Pluginとの兼ね合いかもしれない
- 通常のプロジェクトなら問題ない
- Windows, Ubuntuでは正常動作
Unityの初期設定
Edit > Preferences
- Unity2018から正式に
.Net 4.5 + C#6.0
が使用できるようになった- なのでそれを使う
Edit > Project Settings > Player
から変更
- 必要な参照は NuGet for Unity と Asset Store から取得する
Reactive Extension for Unity -> UniRx
- UnityでもRxを使いたいので導入する
- NuGetで取得すると、古いC#バージョン向けのコードが降ってくる
- MainThreadDispatcher等が無いので、注意する
MainThreadDispatcherの実装
- UnityMainThreadへのアタッチ方法を確認するため、コードを読む
- GlobalなDispatcherを登録していた
- GameObjectが取得できるのがMainThread限定なので、その特性を利用している
- C#世界ではなく、UnityEngine側も利用して実装されている
- MainThreadDispatcherが登録されており、それがMainThreadのPostを担当している
リポジトリ管理
- gitを利用する
- Windowsを使っていると改行コードがズレるので、LFで統一
- .gitattributeで統一しておくといい
- Visual StudioはCRLF+BOMじゃないと正常に動作しなかった気がするけど、直ったかな?
- このあたりはプロジェクトメンバーの構成に合わせて変化するだろう
- Mergerを設定する
.gitconfig
を編集するUnityYAMLMerger
というツールが付属しているので、PATHを通してConfigを設定する
Unity学習の整理 Unityについての所見
なぜゲームエンジンを覚えるのか
- 個人的に、Unityで何か作ってみたくなった
- フルスクラッチのレンダリングエンジン(以下、フルスクラッチ)に比べて大規模開発に向いている
- フルスクラッチに比べて柔軟性は欠ける
- AR/VR分野に興味がある
- はらぺこあおむしARが面白いよ!
2018.06時点のスキル
- ゲームスタジオ系のツールは使ったことがない
- RPGツクールはある
- Unityを使うのは3年ぶりくらい
- まともに使ったことはない
- ゲーム開発会社にいたのは2年半
- 新卒のとき
- DirectX / OpenGL ESでゲーム開発経験はある
- レンダリングエンジンはフルスクラッチ
- Mascot Capsule も使っていた
UnrealEngineとの比較
- UnrealEngineは個人的にすごく好き
- 学生時代(2005年頃)から、たまに雑誌で紹介されていたね
- C#による手軽なコーディングが行える
- アプリを観察してみると、モバイル分野ではUnityのほうが盛んだと思う
- どっかでデータを見た気がするけど、ソースが見つからなかった
学習環境
- 2018.06時点では、基本的に独習である
- 勉強会は、子育てもあるので出づらい
- 情報はかなりの量が出ている
- 調べてみると、痒いところに手が届かない感じの情報量
- UnitTestの件とか
- 調べ尽くしてわからなければ、 コミュニティ にも頼ろう
- リソースは標準のものやAsset Storeから無料・低額な素材を探す
- 学習用としては十分である
Unityのインストール
- マルチプラットフォームで使える
- Windows, Ubuntu, Macでそれぞれインストールした
- Ubuntu版はここからダウンロード
- バージョンは
2018.1.2f1
で統一- Ubuntu版はこれ
Linux Download Assist
からダウンロードして、chmodで実行権限を与えて実行すれば良い
Linux(Ubuntu)版の不具合
- Floating Windowのリサイズができない
- フォントに独特の滲みがある
- 同スペックマシンの場合、Windows版に比べて重い
- 不具合ではないが、Launcherが登録されないので自分で
*.desktop
ファイルを書く必要がある