hildsoftのコード置き場

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

Kotlinメモ

Kotlin独自の文法やイディオムを忘れやすいので自分用のメモです

f:id:hildsoft:20180731034211j:plain


公式サイト Kotlin Programming Language

nullかどうか判断して実行

// Java: extrasがnullの場合、intValueに初期値0を設定する
int intValue = 0;
if(intent.extras != null) {
  intValue = intent.extras?.getInt("key_intAAA");
}
// Kotlin: extrasがnullの場合、intValueに初期値0を設定する
val intValue = intent.extras?.getInt("key_intAAA") ?: 0

extrasがnullの場合だとgetIntでNullPointerExceptionになるので、安全呼び出し演算子(?.)でnullかどうかチェックしつつ実行し、 nullの場合はエルビス演算子(?:)でチェックして初期値を使用します。

三項演算子

// Java: 小さい方の値を取得
int a = 5;
int b = 10;
int minValue = a < b ? a : b;
// Kotlin: 小さい方の値を取得
val a = 5
val b = 10
val minValue = if (a < b) a else b

Kotlinには三項演算子が無いので、素直にif文を書いた方が良さそうです。

if文で値を返すことができるので、代入程度なら1行に書いても大差ないですし、 代入以外で三項演算子を使っても可読性が落ちる場合が多いので、特に問題は無いでしょう。

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で対象となるオブジェクトを向くように回転させたい(Quaternion.LookRotation)

f:id:hildsoft:20170706031756p:plain

Quaternionはさっぱりわからないという人は一度こちらを見てください。

code.hildsoft.com


Unityで使うQuaternionのサンプルコード(LookRotation)

対象となるオブジェクトを向くには、LookRotationを使ってどのくらい回転させればいいのかを求めて、 得られたクォータニオンを使用して回転させると楽です。

f:id:hildsoft:20170706133217p:plain

このように黒いキューブの方を向くなど、カメラの向き変更などでよく使います。

Quaternion LookRotation(Vector3 forward)

+Z軸を、指定の方向に向けたい時に必要な回転操作が欲しい時に使用します。

+Z方向はVector3.forwardでも定義されていて、カメラの撮影方向もZ軸プラスの方向になります。

今回は黒のキューブに向けてみましょう。

public GameObject targetBlack;

を定義してインスペクタから黒いキューブを設定します。

f:id:hildsoft:20170706030509p:plain

var aim = this.targetBlack.transform.position - this.transform.position;
var look = Quaternion.LookRotation(aim);
this.transform.localRotation = look;

向きたい方向を指定する必要があるので、ターゲットの座標から自身の座標を引き、相対ベクトルを計算します。

よくあるミスとして、引数に対象の座標を指定することがあるので注意してください。

Quaternion LookRotation(Vector3 forward, Vector3 upwards = Vector3.up)

LookRotationメソッドは第二引数を取るものもあります。

これは、指定方向を向いたあとに上方向をどこに指定してするか、対象を向きながら回転して決定します。

大抵の場合は、Vector3.upかtransform.upを指定することになると思います。

f:id:hildsoft:20170706033350p:plain

var aim = this.targetBlack.transform.position - this.transform.position;
var look = Quaternion.LookRotation(aim, Vector3.up);
this.transform.localRotation = look;

Vector3.upを指定した場合は、引数が一つのメソッドと同じ動きをします。

キューブのY軸(緑の棒)を見ると分かると思いますが、上を向いている状態です。

初期の状態でY軸がどこを向いていても、Y軸が上を向く(正確に言うとX軸が水平になる)ように回転します。

この場合は設定を省略できるので、引数が一つのメソッドを使う方が良いでしょう。

var aim = this.targetBlack.transform.position - this.transform.position;
var look = Quaternion.LookRotation(aim, this.transform.up);
this.transform.localRotation = look;

こちらのコードは、transform.upを指定した場合です。

単純に黒いキューブを向くだけで、そのあとの自身の回転を行いません。

補足

カメラで使用する場合は、前者は重力を考慮した動き、後者は無重力状態や飛行機などの重力をあまり考慮しない動きをする際に用いられることが多いと思います。

ただ単に方向を変えたいだけなら、Quaternionを使わずに

this.transform.LookAt(this.targetBlack.transform);
this.transform.LookAt(this.targetBlack.transform, this.transform.up);
this.transform.LookAt(this.targetBlack.transform.position);
this.transform.LookAt(this.targetBlack.transform.position, this.transform.up);

でも可能です。

Quaternionを使わないならこちらの方がシンプルになります。

関連リンク

Unity - スクリプトリファレンス: Quaternion

Unity - スクリプトリファレンス: Quaternion.LookRotation

Unityで使うQuaternionのサンプルコード(事前準備)

f:id:hildsoft:20170706031459p:plain

Unityで使うQuaternion

クォータニオン(Quaternion)の理解はそこそこでも良いからとりあえずサンプルコードが欲しい、 サンプルコードを動かしながら理解したい人向けの準備記事です。

もう少し詳しく知りたい方はこちら。

www.hildsoft.com

www.hildsoft.com

根本的な数学のレベルから理解したい方はこちら。

qiita.com

最低限押さえてほしいこと

流石に全く知らないと説明も難しいので、最低限この3つだけは押さえてください。

1.クォータニオンの持つ意味

クォータニオンは、ある状態から別の状態へ回転させる操作、または回転後の状態です。

f:id:hildsoft:20170706013913p:plain

Unityでは基本状態では、Rotationの値が、X:0,Y:0,Z:0で作成されます。

これを基本状態(無回転状態)と考えてください。

この状態をクォータニオンではQuaternion.identityとしています。

Quaternion.identityの状態にするには、下記のコードで可能です。

this.transform.localRotation = Quaternion.identity; // 親の座標系
//this.transform.rotation = Quaternion.identity; // グローバル座標系

2.transform.rotationとtransform.localRotationの違い

f:id:hildsoft:20170706021933p:plain

ヒエラルキー上で親子関係があるオブジェクトです。

分かりやすいように、キューブにx,y,z軸を表す棒を付けています。

ここで子オブジェクトを基本状態に回転させます。


f:id:hildsoft:20170706022046p:plain

this.transform.rotation = Quaternion.identity; // グローバル座標系

transform.rotationはグローバル座標系が基準です。


f:id:hildsoft:20170706022054p:plain

this.transform.localRotation = Quaternion.identity; // 親の座標系

transform.localRotationは親の座標系が基準です。


オブジェクトがルートの直下にあるときは、親がグローバルになるため、

transform.localRotationもtransform.rotationも同じ結果になります。

3.クォータニオンの計算

クォータニオンは回転操作なので、計算によりオブジェクトを回転させることができます。

その計算方法ですが、回転させたいものに対して、左側からかけることで可能です。

通常の実数での掛け算は積の交換法則が成り立つので、実数a,bに対してa * b = b * aが成り立ちます。

しかし、クォータニオンの計算では行列の計算と同じく、一般的に積の交換法則は成り立ちません。

X=(今の状態)
A=(回転操作)
B=(結果の状態)

とすると、

B(結果の状態) = A(回転操作) * X(今の状態)

で計算することができますが、

B(結果の状態) = X(今の状態) * A(回転操作)

では意図しない結果になる場合があります。

サンプルコード

記事を書き次第追加していきます。

code.hildsoft.com

Unityでクリックした位置にprefabを作成する(Perspective編)

Unityでクリックした位置にprefabを作成する(Perspective編)

検証環境

Unity:5.6.1f1

カメラの設定

Unityのカメラ設定でProjectionという項目があります。

どのように空間を映すかという設定なのですが、

  • Orthographic
  • Perspective

の2パターンがあります。

この記事ではPerspectiveの場合について説明します。

Orthographicはこちら code.hildsoft.com

Perspective

f:id:hildsoft:20170704073703p:plain

設定はカメラのProjectionで変更可能です。

OrthographicもPerspectiveも3次元を2次元に投影する方法ですが、
PerspectiveではPerspectiveとは違いカメラに対して前後の概念も奥行きも影響してきます。

遠くにあるものは小さく、近くにるものは大きく表現されます。

f:id:hildsoft:20170704073846p:plain

Z正方向を見たカメラに2つのキューブを置きました。

シーンビューで斜め上からPerspectiveで見た図がこちらです。

赤いキューブのZ座標は-5で、緑のキューブのZ座標は0です。

緑から青色の長方形で構成される四角錘台に含まれるものがカメラに映されます。


f:id:hildsoft:20170704073923p:plain

実際にカメラに映し出されるのがこの図になります。

同じ大きさのキューブですが、赤は遠くにあるため小さく、緑は近くにあるため大きく見えます。これがPerspectiveの特徴です。

通常のカメラでの撮影や人の目の見え方はこの方式になります。

クリックした座標のとり方

画面のクリック位置を取る方法はこちらの記事を参照してください。

code.hildsoft.com

prefabを作成

public GameObject prefab; // prefabはインスペクタから与えてください。

private Camera cam;

void Start ()
{
    this.cam = FindObjectOfType<Camera>();
}
    
void Update () {
    if (Input.GetMouseButtonUp(0))
    {
        var mousePosition = Input.mousePosition;

        mousePosition.z += 18f;  // カメラのZ座標に+18
        //mousePosition.z -= this.cam.transform.position.z; // XY平面上に
        var worldPoint = this.cam.ScreenToWorldPoint(mousePosition);

        GameObject.Instantiate(this.prefab, worldPoint, Quaternion.identity);
    }
}

Input.mousePositionのZは常に0です。

Camera.ScreenToWorldPoint(Vector3 v)

で変換すると、カメラが見ている範囲のx,y座標に変換してくれますが、Zはカメラの座標からの相対値が返されます。

Orthographicとは違い、カメラからの距離によって大きさが変わるため、Zの値によってx,y座標も変わってきます。

Zの値を変更しないとカメラと同じ位置にインスタンスを作成されて見えないので、少し前に移動させます。

Orthographicでもそうですが、ScreenToWorldPointを使用しても座標が変わらない、オブジェクトが見えないなどはこの辺が原因であることが多いです。

この例ではカメラのZ=-20に対して+18に設定し、赤と緑のキューブの間に生成されるようにしました。

f:id:hildsoft:20170704074817p:plain

実行して適当にクリックしてみたのがこの図です。

赤と緑のキューブの間にprefabで指定したキューブが作成されています。

奥行きについて

f:id:hildsoft:20170704075527p:plain

画面上は同じ座標になっても、奥に行くほどX,Y座標は変わっていきます。

同じ大きさの比較できる物体があれば前後感は分かるようになります。

しかし、遠くに大きな物、近くに小さなものを配置すると画面上の大きさは同じようになります。 Orthographicと同様プログラム内で位置を知りたい場合はraycastなどで調べることになります。

補足

Perspectiveを使う時に大事なパラメータにField of Viewという項目があります。

これは視野を調整するもので、カメラを動かしたときに人間の目との違和感を感じやすい部分になるので3D酔いの原因になります。

人により差があるため、できればユーザーが設定できるようにプログラムを作成した方が良いでしょう。

その際にクリック位置などの補正が必要になることもあるので注意してください。

参考サイト

tsubakit1.hateblo.jp

関連リンク

Unity - スクリプトリファレンス: Camera

Unity - スクリプトリファレンス: Camera.orthographic

Unityでクリックした位置にprefabを作成する(Orthographic編)

Unityでクリックした位置にprefabを作成する(Orthographic編)

検証環境

Unity:5.6.1f1

カメラの設定

Unityのカメラ設定でProjectionという項目があります。

どのように空間を映すかという設定なのですが、

  • Orthographic
  • Perspective

の2パターンがあります。

この記事ではOrthographicの場合について説明します。

Perspectiveはこちら code.hildsoft.com

Orthographic

f:id:hildsoft:20170621203458p:plain

設定はカメラのProjectionで変更可能です。

OrthographicもPerspectiveも3次元を2次元に投影する方法ですが、
Orthographicではカメラに対して前後の概念はあっても「奥行き」というものがありません。

遠くにあっても、近くにあっても同じ大きさで表現されます。

f:id:hildsoft:20170621203824p:plain

Z正方向を見たカメラに2つのキューブを置きました。

シーンビューで斜め上からPerspectiveで見た図がこちらです。

片方のZ座標は-5で、もう片方は0です。


f:id:hildsoft:20170621204042p:plain

実際にカメラに映し出されるのがこの図になります。

奥にあるにもかかわらず、同じ大きさに見えます。これがOrthographicの特徴です。

2Dゲームなどではこの投影方式を選択することが多いですが、Perspectiveでも画角を調整することで奥行きの感じられない2D的なゲームを作成することも可能です。

クリックした座標のとり方

画面のクリック位置を取る方法はこちらの記事を参照してください。

code.hildsoft.com

prefabを作成

public GameObject prefab; // prefabはインスペクタから与えてください。

private Camera cam;

void Start ()
{
    this.cam = FindObjectOfType<Camera>();
}
    
void Update () {
    if(Input.GetMouseButtonUp(0))
    {
        var mousePosition = Input.mousePosition;
        mousePosition.z += 1f; // カメラのZ座標に+1
        // mousePosition.z -= this.cam.transform.position.z; // XY平面上に
        var worldPoint = this.cam.ScreenToWorldPoint(mousePosition);

        GameObject.Instantiate(this.prefab, worldPoint, Quaternion.identity);
    }
}

Input.mousePositionのZは常に0です。

Camera.ScreenToWorldPoint(Vector3 v)

で変換すると、カメラが見ている範囲のx,y座標に変換してくれますが、Zはカメラの座標からの相対値が返されます。

Zの値を変更しないとカメラと同じ位置にインスタンスを作成されて見えないので、少し前に移動させます。

Perspectiveでもそうですが、ScreenToWorldPointを使用しても座標が変わらない、オブジェクトが見えないなどはこの辺が原因であることが多いです。

この例では+1だけしていますが、他のオブジェクトとの前後関係を考慮した値を設定してください。

f:id:hildsoft:20170621211901p:plain

実行して適当にクリックしてみたのがこの図です。

カメラのすぐ前方に緑のキューブが作成されています。

奥行きについて

f:id:hildsoft:20170621211042p:plain

画面上は点でも、実際には奥行きがある事を理解しておいてください。

また、線上のどこにあるかは画面からは判断できません。

プログラム内でレイヤーに分けて管理するか、raycastなどで調べることになります。

補足

Orthographicを使う際は、Z軸に平行で、x,y平面を映すようにした方が良いです。

カメラに角度をつけてしまうと上記の方法ではズレが生じてしまいます。

もちろん補正してしまえば問題ないのですが、バグの原因にもなりかねませんので必要が無ければ避けた方が良いです。

2次元の被写体全部を傾けるよりは楽なので、その場合はカメラを傾けた方が楽になります。

参考サイト

tsubakit1.hateblo.jp

関連リンク

Unity - スクリプトリファレンス: Camera

Unity - スクリプトリファレンス: Camera.orthographic