hildsoftのコード置き場

プログラム関連で調べたことやコードの保管場所です

Kotlinでリスナーを書く方法とSAM変換

Kotlinでのリスナーの書き方

Kotlinは少し触っている程度で未だにSAM変換の書き方を覚えられていないため、必要なところだけ自分なりにまとめてみます。

何が何でもSAM変換する必要は無いと思っています。あくまでソースの述量減少に伴う可読性を重視することが大事なので、 コーディング規約に従ってある程度の裁量を持たせても良いかと思います。


SAM変換とSAM変換が使える条件

SAM変換はSAM(Single Abstract Method)を定義したinterfaceを、ラムダ式に置き換えることです。

名前から分かる通り、メソッドを1個だけ定義したインターフェースをクラス化するときに記述量を減らせるだけなので、 複数のメソッドを定義しているインターフェースは従来通りの書き方になります。

あと注意点として、Javaで定義されているinterface限定になります。Kotlinでinterfaceを定義した場合は使えません。

何故そのような仕様になっているのかは分からないですが、今後Kotlinに開発の主軸が移ってきたら対応されるのでしょうか?


冗長な書き方

Kotlinを使っている人はJavaの方が慣れているという人も多いと思うので、Javaのコードから始めていきます。

AndroidでボタンをクリックするとLogを出力する例を書いてみます。

Java

AAAButton.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    Log.v("ログ出力");
  }
});

Kotlin

AAAButton.setOnClickListener( object: View.OnClickListener{
  override fun onClick(view: View?): Unit {
    Log.v("ログ出力")
  }
})

この書き方ではKotlinの省略化のメリットは無いですね。
しかしほぼ1:1で対応しているのでJavaとKotlinを対比する上では見やすいと思います。

SAM変換による省略化

AAAButton.setOnClickListener( object: View.OnClickListener{
  override fun onClick(view: View?): Unit {
    Log.v("ログ出力")
  }
})

このKotlinのコードをベースに進めていきます。

まずsetOnClickListenerに渡すパラメータは、View.OnClickListenerインターフェースを実装したクラスインスタンス ということは文法上自明ですね。

またKotlinでのラムダ式の記述は

{ パラメータ -> メソッドの処理 }

で関数名を省略して書けます。

View.OnClickListenerインターフェースにはonClickメソッドにしかないので、
View.OnClickListenerで実装されているメソッドは一つで、onClickのことだとメソッド名を書かなくても特定出来るわけです。
メソッドの特定ができれば、もちろんパラメータの型も戻り値の型も特定できます。

となると、

  • View.OnClickListener
  • onClick
  • onClickのパラメータの型(View)、戻り値の型(Unit)

は特になくても良い情報となります。削ってしまいましょう。

削る前

AAAButton.setOnClickListener( object: View.OnClickListener{
  override fun onClick(view: View?): Unit {
    Log.v("ログ出力")
  }
})

削った後

AAAButton.setOnClickListener( { view ->
  Log.v("ログ出力")
})

ここからはラムダ式の文法で、

  • 引数が一つであれば省略できて、処理部分で必要ならitとして使用できる
  • メソッド(ここではsetOnClickListener)の最後の引数がラムダ式なら、{}部分を後ろに書ける
  • その状態で()内に引数が無ければ、つまりメソッドの引数がラムダ式だけなら()を省略できる

を適用して省略していくと

AAAButton.setOnClickListener({ Log.v("ログ出力") })
AAAButton.setOnClickListener(){ Log.v("ログ出力") }
AAAButton.setOnClickListener{ Log.v("ログ出力") }

と書くことができます。


中身の少ない同じような記述を並列して定義する場合は、わざわざ指定のinterfaceを実装した内部クラスを作ったりする必要なくシンプルに書くことができます。

個人的には内部の処理が長かったり複雑だったりするとクラス分けたくなりますが、この辺はコーディング規約などでケースバイケースの対応になるかと思います。


サンプル

SAM変換を理解したところで本題。いくつかのパターンを書いておきます。

インターフェース内のメソッド一つ

リスナークラスだけをパラメータに取るメソッド

AAA.setOnXXXListener{ Log.v("ログ出力") }

リスナークラスだけをパラメータに取るメソッドでメソッドの引数を使用したい場合(itを使用)

AAA.setOnXXXListener{ it.BBB() }

リスナー側で実装を要求されるメソッドにパラメータが複数ある場合

AAAButton.setOnClickListener{ p1, p2 ->
  Log.v("ログ出力")
}

インターフェース内のメソッドが複数

  AAA.setOnXXXListener(object : BBBinterface {
    override fun onCCCMethod(p: DDDParameterClass?) {
      Log.v("ログ出力")
    }
    override fun onEEEMethod(p: FFFParameterClass?) {
    }
})

処理が長い場合

  num = 10
  AAA.setOnXXXListener(BBBClass(num))

・・・中略・・・

// 要求されるインターフェースを実装する内部クラス
inner class BBBClass(num: Int) : OnXXXListener {
    override fun onCCCMethod(p: DDDParameterClass?) {
      ・・・中略・・・
    }
}

You need to add a reference to Mono.Android.Export.dll when you use ExportAttribute or ExportFieldAttribute.の対処法

Xamarin.AndroidでMono.Android.Export.dll参照エラーが出た時の対処法

エラー表示

You need to add a reference to Mono.Android.Export.dll when you use ExportAttribute or ExportFieldAttribute.

(ExportAttributeかExportFieldAttributeの属性を使う場合はMono.Android.Export.dllの参照を追加する必要があります。)

対応方法

参照が足りていないので追加するだけです。


f:id:hildsoft:20180527212709j:plain

ソリューションエクスプローラーのAndroidプロジェクトの参照に「Mono.Android.Export」がありません。


f:id:hildsoft:20180527212718j:plain

参照を右クリックしてメニューを表示し、「参照の追加」を選択します。


f:id:hildsoft:20180527211952j:plain

左側から「アセンブリ」を選択し、「Mono.Android.Export」にチェックを入れてOKボタンでダイアログを閉じます。


f:id:hildsoft:20180527212725j:plain

参照に「Mono.Android.Export」が追加されていればビルドが通ると思います。

UnityでuGUIを使ったUIに3Dオブジェクトを表示させたい

UnityでuGUIを使ったUIに3Dオブジェクトを表示させたい

f:id:hildsoft:20180110045224p:plain

検証環境

Unity:2017.2.0f3

Canvasの設定

CanvasにはRender Modeというプロパティがあります。

f:id:hildsoft:20180110031805p:plain

  • Screen Space - Overlay
  • Screen Space - Camera
  • World Space

Screen Space - Overlay

f:id:hildsoft:20180110033127p:plain

Overlayは被せるとか覆うという意味があります。

文字通り、カメラで映し出した映像の上にCanvasの情報を上書きします。

UIは通常画面の一番手前に表示され、他のもので隠されることは無いためこの使い方が一般的です。


Screen Space - Camera

f:id:hildsoft:20180110033356p:plain

この設定は、カメラから一定の位置にCanvasを配置するものです。

OverlayではUIが最前面に表示されるので3Dオブジェクトを配置することができません。

そこで、UI上でも3Dオブジェクトを使う場合は、UIをScene内に入れてしまいます。

今回はこの設定でUI上に3Dオブジェクトを表示させます。


World Space

f:id:hildsoft:20180110034327p:plain

せっかくなので、ついでにもう一つの設定も軽く見ておきましょう。

この設定は文字通り、ワールドスペース内にCanvasをオブジェクトとして配置します。

実際に空間内に存在するので、カメラを移動してもCanvasは動かないため、空間に固定されたものになります。

使ったことは無いですがVRなどで使用されるのかな?

ゲーム内でキャラクターが操作するパネルとして使用することも可能だと思います。


UI用のカメラを作成する

f:id:hildsoft:20180110033356p:plain

この例にもあるように、同じカメラを使用してしまうと位置関係がややこしくなったり、ボタンが隠れてしまったりするのでUI専用のカメラを用意します。

f:id:hildsoft:20180110035341p:plain

Hierarchyで右クリックして、Cameraを追加します。

ここでは「UI Camera」としておきます。

f:id:hildsoft:20180110035637p:plain

Cameraを追加するとAudio Listenerコンポーネントも付いてきますが、Main Cameraにもあるため、2個になってしまいます。

UIでは必要がないのと警告が出るので、これは削除します。

Canvasにカメラを設定

f:id:hildsoft:20180110040012p:plain

これでUI Cameraを移動してもCanvasは自動でUI Cameraに付いてきて、UI Cameraの視界内に入る3Dオブジェクトを描画することができます。


映したいものと映したくないものを分ける

UI用のカメラが3Dオブジェクトを映せるようになると一つ問題が出てきます。

UIだけに映したくてもMainカメラの視界内にUIのオブジェクトが存在すると表示されてしまいます。逆もしかりです。

そこで使用するのがレイヤー機能です。

f:id:hildsoft:20180110041700p:plain

レイヤーはオブジェクトを選択して、Inspectprから変更可能です。

f:id:hildsoft:20180110041854p:plain

0~7までの8個はシステムに予約されていますが、8~31までの24個はユーザーが自由に使用できます。

UI用のカメラに表示したい物を扱うレイヤーを追加します。ここでは「UI 3D Object」とします。


レイヤーを追加しただけでは何も変化はありません。

カメラ側の設定で、何を映すかを指定する必要があります。

f:id:hildsoft:20180110042456p:plain

メインカメラの設定ではUIに関する物を映さない様にCulling Maskから「UI」と「UI 3D Object」を外します。

f:id:hildsoft:20180110042625p:plain

UIカメラの設定ではUIに関する物を映す様にCulling Maskに「UI」と「UI 3D Object」をチェックします。

必要なら、Lightの設定も同様に変更してください。


カメラの描画順

このままではMain Camera描画後に、UI Cameraの映したものを上書きしてしまいます。

なので、あとから描画するUI Cameraの方は必要なところだけを上書きするようにします。

f:id:hildsoft:20180110043215p:plain

Clear FlagsをDepth onlyにすると、必要な個所だけが上書きで更新されます。

また、Depthの値がMain Cameraより大きな値になっていることを確認してください。(初期状態では問題無いと思います)


完成するとこんな感じに

f:id:hildsoft:20180110045224p:plain

作成した3Dオブジェクトは、紐づくUIの子要素にしておくことでカメラの回転やUI要素の移動時にも対応可能になります。

ただし、移動の際はパースの問題が出るため、カメラのProjectionを「Orthographic」にしておいた方がいいかもしれません

Arbor2を使ってみよう その6 ParameterContainer

前回の記事はこちら

code.hildsoft.com

検証バージョン

Unity 2017.1.1p1
Arbor 2.1.7

ParameterContainer

ParameterContainerの役割

ParameterContainerは複数のArborFSMから参照、更新するための変数を保持するためのコンポーネントです。

複数作成することもできますが、グローバル変数のようなものなので、無計画に増やしすぎると管理しづらくなるので注意してください。


f:id:hildsoft:20170929154910j:plain

GameObjectに追加して作成します。


f:id:hildsoft:20170929154919j:plain

空のGameObjectと一緒に作成することもできます。


パラメータの追加方法

f:id:hildsoft:20170929170140j:plain

右上にある+をクリックしてメニューを出し、追加したいクラスを選択するだけです。


パラメータの種類

f:id:hildsoft:20170929170319j:plain

よく使うクラスは一通りそろっているので、自分で拡張する必要は恐らく無いと思います。

Quaternionは内部ではw,x,y,zの4変数で持っている物を変換してx,y,zのオイラー回転で表示しているので直接入力すると若干ズレることがあります。

基本的にQuaternionは手作業で修正するものではないので、Arborやスクリプトから直接Quaternionとして操作した方が良いでしょう。


GlobalParameterContainerの役割

GlobalParameterContainerは、ParameterContainerをシーンを跨いで使用したいときに使います。

ParameterContainerと同じように変数を持つのではなく、ParameterContainerを管理する感じです。


f:id:hildsoft:20170929161747j:plain

まず、ParameterContainerをPrefab化する必要があります。


f:id:hildsoft:20170929161804j:plain

そして、Prefab化したものを登録します。


f:id:hildsoft:20170929162635j:plain

シーン起動時に存在しなければ自動で作成され、存在していれば何も起こりません。

複数のシーンで使用するものは初期化のタイミングや重複などに気を付けないといけないのですが、Prefab化してGlobalParameterContainerを使用するとその煩わしさを回避できます。


ParameterContainerの実際の使い方などは、また別途記事を作成したいと思います。

Arbor2を使ってみよう その5 Calculator

前回の記事はこちら

code.hildsoft.com

検証バージョン

Unity 2017.1.1p1
Arbor 2.1.7

Calculator

Calculatorの役割

f:id:hildsoft:20170925033442j:plain

Calculatorは変数を計算したり変換したりする時に使用します。


Behaviourの設定と接続

f:id:hildsoft:20170925034002j:plain

BehaviourでCalculatorの値を使う場合は、▼をクリックしてメニューを出してから「Calculator」を選択します。


f:id:hildsoft:20170925034321j:plain

変数のタイプをCalculatorにすると、Calculatorの出力からドラッグ&ドロップで紐づけすることができます。


f:id:hildsoft:20170925041250j:plain

接続することのできる入出力は、同じ型(クラス)でなければいけません。

変数のタイプは、Calculatorの欄にカーソルを合わせると、型(クラス)情報が表示されます。


Calculatorの出力値

f:id:hildsoft:20170925040410j:plain

一部の型のCalculatorは実行時にどのような値が出力されているのかを目視確認できます。


Calculatorの実行タイミング

CalculatorはBehaviourが必要としたタイミングで初めて計算されます。

また、キャッシュを持っているため、Calculatorに渡される入力値に変更がない場合は再計算されずキャッシュされた同じ値を返します。


Calculatorの用途

Calculatorは基本的な物しか用意されていません。

RPGでのダメージ計算や自キャラの一番近くの敵を探すなど、作るゲームで必要なものを作成していくことになります。


無いものは作る

Behaviourもそうですが、Arborは基本的な機能を提供し、必要な物を自前で作るという設計思想のようです。

新しくBehaviourやCalculatorを作成するにはこちらを参照してください。

http://arbor.caitsithware.com/manual/customize/

この辺を上手く作るにはプログラマが必要になると思います。


code.hildsoft.com

Arbor2を使ってみよう その4 処理の流れ

前回の記事はこちら

code.hildsoft.com

検証バージョン

Unity 2017.1.1p1
Arbor 2.1.7

処理の流れ

処理実行順

f:id:hildsoft:20170830043703j:plain

Arbor2を使ってみよう その1 概要 - hildsoftのコード置き場

で紹介した簡単なパターンから見ていきます。


f:id:hildsoft:20170919095504j:plain

Arbor2の処理は開始ステートから始まります。

Behaviourはステートごとに上から順に全て実行されます。

ステートは有効になった時に1度だけ実行されます。 そのステート中であれば常に実行されるというわけではないので注意してください。

(Tween系は更新処理があるので、UpdateやFixedUpdate内での時間経過処理が実行されますが、開始処理は有効になった時のみです)


f:id:hildsoft:20170919202253j:plain

ただし、Transition系(ステートを変更するもの)は、Updateメソッドを持っているため毎フレーム実行されています。

これはUnityのUpdateと同じ仕様で実行順は不確定ですので、並び順の通りに処理されることを期待してはいけません

BehaviourのUpdateが実行された時に有効であれば移動先は上書きされてしまいます。 (Behaviourの条件が満たされている中で、最後に呼ばれたものに移動します)

なので、左右両方のキーを押した状態だと、どちらが有効になるかは保証されていません。


ステート更新のタイミング

ステートの変更はUnityのLateUpdateメソッド呼び出し時に更新されます。

ステートが変更されたときに実行されるBehaviourの処理もここで実行されます。

上記のFSMでは入力チェックと移動が分かれているため、キーを押しっぱなしにしても1フレームおきに移動します。

その結果、カクカクとした動きになってしまいます。

Unityのイベント呼び出しの順番はこちらを参照してください。
Unity - Manual: Execution Order of Event Functions


Immediate Transition

f:id:hildsoft:20170919101925j:plain

Transition系には、追加の設定があり、名前(初期値:Next State)の変更、矢印の色変更ができます。

また、処理に関わる重要な設定に、Immediate Transitionがあります。

このチェックがオンの状態だとLateUpdateを待たず、すぐにステートが切り替わって次のステートの処理も引き続き実行します。


常駐ステート

f:id:hildsoft:20170919204347j:plain

常駐ステートは、常に有効になっているステートになります。

ここでも注意しないといけないのが、ステートは1度だけ実行されることです。

Updateを持たないBehaviourは起動時に一度だけ実行されて、毎フレーム呼ばれるわけではありません。


処理の順番

f:id:hildsoft:20170919205414j:plain

画面の左側のTransitionはImmediate Transitionをオンに、右側をオフにしています。


f:id:hildsoft:20170919205348j:plain

現在のステートと、常駐ステートにKey Transitionで同じキーを監視しているBehaviourがあります。

右キーを押したとき、Updateが早く呼ばれた方のBehaviourのステートへ変更されます。


f:id:hildsoft:20170919210924j:plain

現在のステートにあるKey Transitionが先にUpdateを呼び出されたとします。

このとき、Immediate Transitionの設定のため、Updateでステートが更新されます。

通常はLateUpdateで実行されるステートの変更呼び出しもUpdate内で呼ばれることになります。

また、Go To Transitionでステート変更が予約されています。


f:id:hildsoft:20170919210932j:plain

全てのオブジェクトのUpdateが呼び出され、最後にLateUpdateが呼び出されます。

Go To Transitionで予約されていたステートに更新されるため、先ほどのステートまで戻ります。

Key TransitionはUpdateで処理を待つだけなので、実質なにも処理は行われません。

そして次のフレームのUpdateで再びキー入力を待ち受けます。


2017/09/19 21:18
仕様を誤解していましたので、大幅に書きなおしました。申し訳ありません。

code.hildsoft.com

Arbor2を使ってみよう その3 基本操作

前回の記事はこちら

code.hildsoft.com

検証バージョン

Unity 2017.1.0p4
Arbor 2.1.6

ArborFSM Componentの追加

まずはArborFSM Componentを追加することから始まります。

操作はUnity標準のコンポーネントと同じです。

他にも方法はありますが、この2パターンが多いかと思います。

f:id:hildsoft:20170903004136j:plain

Hierarchyから追加したいオブジェクトを右クリックしてArbor>ArborFSM


f:id:hildsoft:20170903004359j:plain

追加したいオブジェクトをInspectorに表示させて、Add ComponentからArbor>ArborFSM


ArborFSM Componentを追加したあと、InspectorにあるOpenEditorでグラフを開きます。

f:id:hildsoft:20170903005927j:plain


Arbor Editor

ステートの追加

f:id:hildsoft:20170903010727j:plain

Arbor Editorを開いた何もない状態です。


f:id:hildsoft:20170903010909j:plain

まずはステートを追加します。

ステートには、ステート、常駐ステートの2種類があります。

ステートのうち一つは開始ステートのフラグが設定されます。

シーンが開始されると、開始ステートから処理が始まります。


f:id:hildsoft:20170903011728j:plain

ステートの操作はギアのアイコンをクリックして出るメニューで行います。

開始ステートに設定をクリックすると、メニューを出したステートが開始ステートに設定されます。
常駐ステートは開始ステートにすることはできません。


Behaviourの追加

ステートにはBehaviourを追加できます。

f:id:hildsoft:20170912023243j:plain

ギアをクリックしてメニューを出し、挙動追加をクリックします。


f:id:hildsoft:20170912024117j:plain

追加メニューが表示されますので、選択します。


f:id:hildsoft:20170912024157j:plain

今回はStateAとStateBにKeyDownTransitionを追加してみました。


Behaviourの接続

ステートを変更するBehaviourにはNext Stateボタンがあります。

f:id:hildsoft:20170912024750j:plain

このボタンをドラッグドロップで別のステートに接続することで、次の移動先が決まっていきます。

どのような条件で移動するかはBehaviourに依りますが、ここでの説明は割愛してもう少し先の回で説明していく予定です。


Calculatorの追加

Calculatorはステートと同様にグラフ上で単独に作成します。

f:id:hildsoft:20170912025727j:plain

右クリックメニューから演算ノード作成を選択します。


f:id:hildsoft:20170912031703j:plain

追加メニューが表示されますので、選択します。


f:id:hildsoft:20170912031137j:plain

今回はTransform.Getを追加してみました。

CalculatorもBehaviourと同じく沢山ありますが、ここでの説明は割愛します。

また接続方法も処理の流れを含めて次回解説します。 (注:先に解説するものがあったので、次々回になります)


code.hildsoft.com