プログラム用のきれいな等幅フォント

2011.12.08: MacBook Air に環境構築。
2016.11.20: MacBook Pro 導入に伴って、再度インストールしました。内容は更新されています。

表題の件で何かないかと探したら、Ricty というフォントがあるそうな。早速入れてみました。

詳しいインストール方法は Ricty フォントのページに書かれています。

プログラミング用フォント Ricty

以下はインストール記録です。

前準備として、必要になるコマンドをインストールします。

Homebrew というパッケージ管理システムをインストールします。
インストール方法は Homebrew に書かれています。

```bash
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```

次に Homebrew を使って FontForge をインストールします。

```bash
$ brew install fontforge --use-clang
```

これで前準備は完了。次はフォントファイルと生成スクリプトプログラミング用フォント Ricty 内のリンクを辿ってダウンロード。

  • Inconsolata.otf
  • Inconsolata.zip
  • migu-1m-20150712.zip
  • ricty_generator.sh
  • ricty_discord_converter.pe

まず、ダウンロードしたファイルを一つのディレクトリに展開します。展開したディレクトリで ls コマンドを実行した結果です。

```bash
$ ls
Inconsolata-Bold.ttf migu-1m-regular.ttf
Inconsolata-Regular.ttf mplus-TESTFLIGHT-060
ipag00303 ricty_discord_converter.pe
migu-1m-bold.ttf ricty_generator.sh
```

続いて、Ricty 生成スクリプトを実行します。

```bash
$ bash ricty_generator.sh Inconsolata-Regular.ttf Inconsolata-Bold.ttf migu-1m-regular.ttf migu-1m-bold.ttf
```

実行が完了すると、Ricty ディレクトリに以下のファイルが生成されます。

  • Ricty-Bold.ttf
  • Ricty-Regular.ttf
  • RictyDiscord-Bold.ttf
  • RictyDiscord-Regular.ttf

これらのファイルを Finder でダブルクリックして、フォントをインストールします。

プログラミング用フォント Ricty に合わせて以下のフォントもインストールします。

  • Inconsolata-Regular.ttf
  • Inconsolata-Bold.ttf
  • migu-1m-regular.ttf
  • migu-1m-bold.ttf

以上で完了です。

参考:
http://knagayama.net/blog/2011/07/27/generate-ricty-font/
http://d.hatena.ne.jp/gar_sue/20111112/1321131923
http://d.hatena.ne.jp/hiroe_orz17/20111113/1321168274

libGDX 2D UI Library - Widget (Button)

概要

libGDX の 2D UI Library のうち、Button 系 Widget についてまとめます。

目次

確認環境

  • OS X El Capitan (10.11.6)
  • Xcode 8.0
  • Android Studio 2.2.2
    • Multi-OS Engine Plugin 1.2.1
    • Kotlin 1.0.4
    • buildToolVersion 25.0.0
    • compileSdkVersion 24
  • libGDX 1.9.5-SNAPSHOT

参考情報

解説

Button

Table クラスのサブクラス。

Button (libgdx API)

設定項目 説明
child Actor 子の Actor。
style Button.ButtonStyle 背景の Drawable (up, down, checked, over, checkedOver, disabled)。child のオフセット (unpressedOffset[X|Y], pressedOffset[X|Y], checkedOffset[X|Y])。
Skin

状態遷移(一部)

サイズ

min pref max
width/height prefと同じ child のサイズ 0

実行例

up checked (offsetX = 100, offsetY = 100)

サンプルコード

    private val actor by lazy {
        val imageButton = ImageButton(
                SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                    color = Color.RED
                }),
                SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                    color = Color.BLUE
                }),
                SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                    color = Color.GREEN
                })
        )
        Button(imageButton,
                Button.ButtonStyle().apply {
                    up = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.YELLOW
                    })
                    down = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.MAGENTA
                    })
                    checked = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.CYAN
                    })
                    checkedOffsetX = 100f
                    checkedOffsetY = 100f
                }
        ).apply {
            width = this@MyGdxGame.stage.width
            height = this@MyGdxGame.stage.height
        }
    }

    override fun create() {
        ...
        stage.addActor(actor)
    }

ImageButton

Button クラスのサブクラス。

ImageButton (libgdx API)

設定項目 説明
style ImageButton.ImageButtonStyle imageUp, imageDown, imageChecked, imageOver, imageCheckedOver, imageDisabled。加えて、Button.ButtonStyle の項目。
Skin

サイズ

min pref max
width/height prefと同じ image のサイズ 0

実行例

up checked

サンプルコード

    private val actor by lazy {
        ImageButton(
                ImageButton.ImageButtonStyle().apply {
                    up = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.YELLOW
                    })
                    imageUp = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.RED
                    })
                    down = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.MAGENTA
                    })
                    imageDown = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.BLUE
                    })
                    checked = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.CYAN
                    })
                    imageChecked = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.GREEN
                    })
                    disabled = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.GRAY
                    })
                    imageDisabled = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.WHITE
                    })
                }
        ).apply {
            width = this@MyGdxGame.stage.width
            height = this@MyGdxGame.stage.height
            isDisabled = false
        }
    }

    override fun create() {
        ...
        stage.addActor(actor)
    }

TextButton

Button クラスのサブクラス。

TextButton (libgdx API)

設定項目 説明
text String ボタンテキスト。改行文字 ('\n') で改行される。
style TextButton.TextButtonStyle font, fontColor, downFontColor, checkedFontColor, overFontColor, checkedOverFontColor, disabledFontColor。加えて、Button.ButtonStyle の項目
Skin
label.* Label の設定項目。

サイズ

min pref max
width/height prefと同じ text が収まるサイズを算出 0

実行例

up checked

サンプルコード

    private val actor by lazy {
        TextButton("OK\n(Enter)",
                TextButton.TextButtonStyle().apply {
                    font = BitmapFont()
                    fontColor = Color.RED
                    downFontColor = Color.BLUE
                    checkedFontColor = Color.GREEN
                    disabledFontColor = Color.GRAY
                    up = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.YELLOW
                    })
                    down = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.MAGENTA
                    })
                    checked = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.CYAN
                    })
                    disabled = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.WHITE
                    })
                }
        ).apply {
            width = this@MyGdxGame.stage.width
            height = this@MyGdxGame.stage.height
            label.setFontScale(5f, 10f)
            isDisabled = false
        }
    }

    override fun create() {
        ...
        stage.addActor(actor)
    }

CheckBox

TextButton クラスのサブクラス。

CheckBox (libgdx API)

設定項目 説明
text String ボタンテキスト。改行文字 ('\n') で改行される。
style CheckBox.CheckBoxStyle Drawable (checkboxOff, checkboxOn, checkboxOffDisabled, checkboxOnDisabled, checkboxOver)。 加えて、TextButton.TextButtonStyle の項目。
Skin
label.* Label の設定項目。

サイズ

min pref max
width/height prefと同じ text と checkbox (Drawable) が収まるサイズを算出 0

実行例

up checked

サンプルコード

    private val actor by lazy {
        CheckBox("On",
                CheckBox.CheckBoxStyle().apply {
                    font = BitmapFont()
                    checkboxOff = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.RED
                    })
                    checkboxOn = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.GREEN
                    })
                }
        ).apply {
            width = this@MyGdxGame.stage.width
            height = this@MyGdxGame.stage.height
            label.setFontScale(5f, 10f)
        }
    }

    override fun create() {
        ...
        stage.addActor(actor)
    }

libGDX Scene2d の基礎

概要

libGDX で 2D UI Widget (Button, Label, CheckBox, etc.) を使用するための基礎となる Stage, Viewport, Layout についてまとめます。 (ゲームライブラリとしてではなく、マルチプラットフォームGUI ライブラリとしての使用を模索中です。)

確認環境

  • OS X El Capitan (10.11.6)
  • Xcode 8.0
  • Android Studio 2.2.2
    • Multi-OS Engine Plugin 1.2.1
    • Kotlin 1.0.4
    • buildToolVersion 25.0.0
    • compileSdkVersion 24
  • libGDX 1.9.5-SNAPSHOT

参考情報

解説

Scene2d は基本的な 2D scene graph の機能を提供する。 Scene2d により以下が提供される。

  • アクター (actor)
    • 2D scene graph のノード。位置 (position)、サイズ (rectangular size)、原点 (origin)、スケール (scale)、回転 (rotation)、色 (color) を持つ。
  • グループ (group)
    • アクターのグループ。
  • 描画 (drawing)
    • 回転 (rotation)、スケール (scale) を考慮した描画を行う。
  • 当たり判定 (hit detection)
    • 回転 (rotation)、スケール (scale) を考慮した当たり判定を行う。
  • イベント (event)
    • 各アクターに入力イベントを分配する。
  • アクション (action)
    • 各アクターを時間に応じて変化させる。

Scene2d を基礎とした UI Widget が用意されている。 UI Widget 関連のクラスを図に示す。

Stage

Stage は 複数の Actor を子に持ち、Actor の root 要素になる。 Stage は InputProcessor であり、入力イベントを各 Actor に分配する。 Viewport により Stage を Screen に投影する方法を指定する。

基本的な使い方は以下のとおり。

class MyGdxGame : ApplicationAdapter() {

    private val stage by lazy {
        Stage()
    }

    override fun create() {
        Gdx.input.inputProcessor = stage
        Gdx.graphics.isContinuousRendering = false
        stage.addActor(Image(Texture("badlogic.jpg")))
    }

    override fun resize(width: Int, height: Int) {
        stage.viewport.update(width, height, true)
    }

    override fun render() {
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
        stage.act(Gdx.graphics.deltaTime)
        stage.draw()
    }

    override fun dispose() {
        stage.dispose()
    }
}

stage プロパティは var lateinit stage でも良いが、stage プロパティを変更不可としたいため val stage by lazy を使用している。 by lazy を使用することにより、最初の get 呼び出しでインスタンスが生成されるようになる。

Viewport

Viewport により Stage を Screen に投影する方法を指定する。 Viewport のクラス階層を以下に示す。

各 Viewport における、Screen サイズ (単位はピクセル) と Stage サイズ (単位はユニット) の関係を以下に示す。 Stage を構成する単位 (Stage の論理的な画素) を 1 ユニットとする。

  • ScreenViewport
    • Stage サイズ = Screen サイズ × 1 ピクセル当たりのユニット (unitsPerPixel)
    • デフォルトでは、1 ユニット = 1 ピクセル (unitsPerPixcel = 1)。
  • ScalingViewport
    • Stage サイズを指定し、Screen に合わせて Scale (拡大/縮小) して表示する。Stage サイズは指定したサイズ。
    • Scale 方法は以下のとおり。
      • none : 1 ユニット = 1 ピクセルとする。
      • fit : 縦横一方を Stage = Screen となるように、一方を Stage < Screen となるように、Stage のアスペクト比を変えずに拡大/縮小する。
      • fill : 縦横一方を Stage = Screen となるように、一方を Stage > Screen となるように、Stage のアスペクト比を変えずに拡大/縮小する。
      • stretch : 縦横共に Stage = Screen となるように、Stage のアスペクト比を変えて拡大/縮小する。
  • FitViewport
    • ScalingViewport の fit と同じ。
  • FillViewport
    • ScalingViewport の fill と同じ。
  • StretchViewport
    • ScalingViewport の stretch と同じ。
  • ExtendViewport
    • Stage サイズを指定し、Screen に合わせて拡大/縮小して表示する。拡大/縮小に合わせて Stage サイズを変更する。
    • FitViewport と同様に拡大/縮小した後に、Stage < Screen となっている辺の Stage サイズを変更する。
      • 最大サイズが指定されていない場合は、Stage = Screen となるように Stage サイズを変更する。
      • 最大サイズが指定されている場合は、指定した最大サイズに Stage サイズを変更する。

言葉では分かりづらいため図示すると以下のようになる。 赤は Screen サイズ (単位はピクセル)。 黒は Stage サイズ (単位はユニット)。

ScreenViewport (unitsPerPixcel = 1) ScreenViewport (unitsPerPixcel = 2)

ScalingViewport (none, 640, 480) ScalingViewport (fit, 640, 480)
ScalingViewport (fill, 640, 480) ScalingViewport (stretch, 640, 480)

ExtendViewport (640, 480) ExtendViewport (640, 480, 800, 480) ExtendViewport (640, 480, 800, 640)

表示例を得るために以下のソースコードを使用した。

class MyGdxGame : ApplicationAdapter() {

    companion object {
        private val TAG = MyGdxGame::class.java.name
    }

    private val stage by lazy {
        Stage(ScreenViewport())
        /*
        Stage(ScreenViewport().apply {
            unitsPerPixel = 2f
        })
        */
        //Stage(ScalingViewport(Scaling.none, 640f, 480f))
        //Stage(ScalingViewport(Scaling.fit, 640f, 480f))
        //Stage(FitViewport(640f, 480f))
        //Stage(ScalingViewport(Scaling.fill, 640f, 480f))
        //Stage(FillViewport(640f, 480f))
        //Stage(ScalingViewport(Scaling.stretch, 640f, 480f))
        //Stage(StretchViewport(640f, 480f))
        //Stage(ExtendViewport(640f, 480f))
        //Stage(ExtendViewport(640f, 480f, 800f, 480f))
        //Stage(ExtendViewport(640f, 480f, 800f, 640f))
    }

    override fun create() {
        Gdx.app.logLevel = Application.LOG_DEBUG
        Gdx.app.debug(TAG, "create")
        Gdx.graphics.isContinuousRendering = false
        stage.addActor(object : Actor() {
            override fun draw(batch: Batch, parentAlpha: Float) {
                batch.end()
                ShapeRenderer().run {
                    projectionMatrix = batch.projectionMatrix
                    begin(ShapeRenderer.ShapeType.Filled)
                    color = Color.RED
                    rect(0f, 0f, stage.width, stage.height)
                    Gdx.app.debug(TAG, "${stage.width} ${stage.height}")
                    end()
                }
                batch.begin()
            }
        })
        stage.addActor(Image(Texture("badlogic.jpg")))
    }

    override fun resize(width: Int, height: Int) {
        Gdx.app.debug(TAG, "resize $width $height")
        stage.viewport.update(width, height, true)
    }

    override fun render() {
        Gdx.app.debug(TAG, "render")
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
        stage.draw()
    }

    override fun dispose() {
        Gdx.app.debug(TAG, "dispose")
        stage.dispose()
    }
}

レイアウト

UI Widget は自身のサイズと位置を決定しない。 親の UI Widget が子の UI Widget のサイズと位置を決定する。 UI Widget は親がサイズと位置を決定するためのヒントとなる最小サイズ、推奨サイズ、最大サイズを提供する。

UI Widget が描画 (draw) されるとき、最初に validate メソッドが呼ばれる。 UI Widget のレイアウトが invalid のとき、描画の前に layout メソッドが呼ばれる。 描画においては、layout によりキャッシュされたレイアウト情報 (サイズと位置) を用いる。 invalidate, invalidateHierarchy メソッドを呼ぶことで、レイアウトを invalid にできる。

UI Widget の状態が変化してキャッシュされたレイアウト情報を再計算する場合には invalidate メソッドを呼ぶ。 但し、最小サイズ、推奨サイズ、最大サイズに影響がない場合に限る。 サイズが変化せず、親 Widget が影響を受けない場合に使用する。

UI Widget の状態が変化して最小サイズ、推奨サイズ、最大サイズに影響が及ぶ場合には invalidateHierarcy メソッドを呼ぶ。 親 Widget のレイアウトに影響が及ぶため、root までの全ての親 Widgetinvalidate メソッドが呼ばれる。

libGDX アプリケーションのライフサイクル

概要

libGDX アプリケーションのライフサイクルの説明です。 言語は Kotlin、Android/iOS アプリで動作確認をしています。

確認環境

  • OS X El Capitan (10.11.6)
  • Xcode 8.0
  • Android Studio 2.2.2
    • Multi-OS Engine Plugin 1.2.1
    • Kotlin 1.0.4
    • buildToolVersion 25.0.0
    • compileSdkVersion 24
  • libGDX 1.9.5-SNAPSHOT

参考情報

解説

libGDX のアプリケーションを作成するには ApplicationListener インターフェイスを実装したクラスを作成する。 ApplicationAdapter クラスは ApplicationListener インターフェイスを実装し、各メソッドのデフォルト実装を提供する。

class MyGdxGame : ApplicationAdapter() {

    companion object {
        private val TAG = MyGdxGame::class.java.name
    }

    override fun create() {
        Gdx.app.logLevel = Application.LOG_DEBUG
        Gdx.app.debug(TAG, "create")
        //Gdx.graphics.isContinuousRendering = false
    }

    override fun pause() {
        Gdx.app.debug(TAG, "pause")
    }

    override fun resume() {
        Gdx.app.debug(TAG, "resume")
    }

    override fun resize(width: Int, height: Int) {
        Gdx.app.debug(TAG, "resize")
    }

    override fun render() {
        Gdx.app.debug(TAG, "render")
    }

    override fun dispose() {
        Gdx.app.debug(TAG, "dispose")
    }
}

ライフサイクルの図は The life cycle · libgdx/libgdx Wiki · GitHub を参照。 各操作に応じて呼ばれるメソッドは以下のとおり。

操作 Android iOS
アプリ起動 create, resize create, resize
ホーム pause pause
タスクリスト pause pause
pause状態でアプリ起動 resume, resize resume
タスクリストでアプリ起動 resume, resize resume
タスクリストでアプリ終了 dispose
戻る(アプリ終了) pause, dispose 操作不可
回転 resize resize, resize

(Android はタスクリストでアプリを終了しても dispose が呼ばれないのは仕様なのか?バグなのか? 呼ばれているがログに出ていないだけなのか?)

render メソッドは連続して呼ばれる。 使用しているハードウェアに応じて 1 秒間に 30-50-80 回の頻度で呼ばれる。 連続して呼び出される状態ではバッテリー消費が増える。 Gdx.graphics.isContinuousRendering = false により連続呼び出しを無効にできる。 無効にした場合は、以下のタイミングでのみ render メソッドが呼ばれる。

  • 入力イベントが発生した
  • Gdx.graphics.requestRendering メソッドが呼ばれた
  • Gdx.app.postRunnable メソッドが呼ばれた

UI Action (フェードイン、フェードアウトなど) では render メソッドが連続して呼ばれる。 デフォルトでは有効になっている。 stage.actionsRequestRendering = false により無効にできる。

(Java の場合、isContinuousRendering, actionsRequestRendering は set メソッド呼び出しの形式で書く。)

変更履歴

2016-11-05

以下のようにしていましたが、kotlin-refrect を追加すると APK サイズが大きくなるとの意見を見かけたので kotlin-reflect を使わないようにしました。 kotlin-reflect を追加すると APK サイズが 0.7MB 増えます。

class MyGdxGame : ApplicationAdapter() {

    companion object {
        private val TAG = MyGdxGame::class.qualifiedName
    }

    ...
}

上記のコードを実行するためには build.gradle に kotlin-reflect への依存を追加する必要がある。 MyGdxGame::class.qualifiedName が kotlin-reflect を必要とする。

dependencies {
    ...
    compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}

libGDX ロギング

概要

libGDX のロギング機能の使い方です。Android/iOS アプリで動作確認をしています。

確認環境

  • OS X El Capitan (10.11.6)
  • Xcode 8.0
  • Android Studio 2.2.2
    • Multi-OS Engine Plugin 1.2.1
    • Kotlin 1.0.4
    • buildToolVersion 25.0.0
    • compileSdkVersion 24
  • libGDX 1.9.5-SNAPSHOT

参考情報

解説

libGDX のログレベルは 3 段階。

  • ERROR
  • INFO
  • DEBUG

各ログレベルに対応したログ出力を行うメソッドが存在する。

  • ERROR : Gdx.app.error
  • INFO : Gdx.app.log
  • DEBUG: Gdx.app.debug

logLevel プロパティにより出力するログレベルを変更することができる。 実行環境によってデフォルトのログレベルが異なっている。 (logLevel を設定せずに実行すると Android/iOS で出力結果が異なる。)

使用例。

import com.badlogic.gdx.Application
import com.badlogic.gdx.ApplicationAdapter
import com.badlogic.gdx.Gdx

class MyGdxGame : ApplicationAdapter() {

    override fun create() {
        Gdx.app.logLevel = Application.LOG_DEBUG
        Gdx.app.debug("MyTag", "Debug level message")
        Gdx.app.log("MyTag", "Info level message")
        Gdx.app.error("MyTag", "Error level message")

        Gdx.app.logLevel = Application.LOG_ERROR
        Gdx.app.debug("MyTag", "Debug level message") // 出力されない
        Gdx.app.log("MyTag", "Info level message") // 出力されない
        Gdx.app.error("MyTag", "Error level message")
    }
}

ios-moe の出力結果。

MyTag 3 Debug level message
MyTag 4 Info level message
MyTag 6 Error level message
MyTag 6 Error level message

android の出力結果。logcat に出力される。

D/MyTag: Debug level message
I/MyTag: Info level message
E/MyTag: Error level message
E/MyTag: Error level message

Kotlin で iOS アプリを作る (libGDX 編)

概要

Intel Multi-OS Engine を使うことにより Android StudioiOS アプリを作成できます。 しかし、UI 部分は各 OS によってライブラリが異なるため、共通化できる部分は限られます。 そこで、ゲーム用フレームワーク libGDX を使って描画の共通化を計ります。 本記事では、プロジェクトの作成とサンプルアプリの起動までを行います。

確認環境

  • OS X El Capitan (10.11.6)
  • Xcode 8.0
  • Android Studio 2.2.2
    • Multi-OS Engine Plugin 1.2.1
    • Kotlin 1.0.4
    • buildToolVersion 25.0.0
    • compileSdkVersion 24
  • libGDX 1.9.5-SNAPSHOT

参考情報

解説

手順は下記のとおり。

  1. libGDX の Setup App (gdx-setup.jar) を libgdx からダウンロード
  2. Setup App を実行してプロジェクトを作成
  3. Android Studio にプロジェクトをインポート
  4. プロジェクトの設定を変更
  5. Android/iOS アプリ (Java版) を実行
  6. Kotlin に変換
  7. Android/iOS アプリ (Kotlin版) を実行

プロジェクトの作成

プロジェクトの作成では、Setup App (gdx-setup.jar) を起動して DestinationAndroid SDK のパス、Sub projects を指定する。 Destination は何処でも構わない。 今回は Android Studio にインポートするため、AndroidStudioProjects の配下に置くことにした。 Android SDK は適当なプロジェクトを開いた上で File > Project Structure... > SDK Location と辿ればパスを知ることができる。 Sub projects は AndroidIos-moe を選択する。

Generate を選択するといくつかのダイアログが表示される。

You have a more recent version of android build tools than recomended. Do you want to use your more recent version?

Android Build Tools のバージョンが推奨するバージョンよりも新しいが、新しいバージョンを使用するか? とのことなので「はい」を選択した。

You have a more recent Android API than the recomended. Do you want to use your more recent version?

Android API のバージョンが推奨するバージョンよりも新しいが、新しいバージョンを使用するか? とのことなので「はい」を選択した。

ダウンロードが行われるなど、暫く待つことになる。 遅いのは最初の1回だけ。

プロジェクトのインポート

作成されたプロジェクトの build.gradle を指定してインポート。

プロジェクトの設定変更

本記事執筆時点では、インポートすると ios-moe/java フォルダ配下の IOSMoeLauncher でエラーが発生する。 (libGDX version 1.9.4 で発生するが、1.9.5 になれば修正されると思われる。) エラーの理由は Pointer クラスのパッケージが異なることによる。 libGDX では com.intel.moe.natj.general.Pointer だが、MOE では org.moe.natj.general.Pointer に変更されている。 プロジェクトの build.gradle で gdxVersion を 1.9.4 から 1.9.5-SNAPSHOT に変更すればエラーはなくなる。

アプリの実行

Android Emulator / SimulatorApp でそれぞれ実行する。

Kotlin に変換

build.gradle に下記を追加する。

build.gradle (Project)

buildscript {
    ext.kotlin_version = '1.0.4'
    ... 
    dependencies {
        ...
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin'

...

build.gradle (Module: android)

apply plugin: 'kotlin-android'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

...

build.gradle (Module: core)

apply plugin: 'kotlin'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

...

build.gradle (Module: ios-moe)

apply plugin: 'kotlin'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

...

android, core, ios-moe の各モジュールの java ディレクトリを選択して Code > Convert Java File to Kotlin File を実行する。

core モジュールの MyGdxGame クラスでエラーが発生する。 internalprivate lateinit に変更する。 internal lateinit でも構わないがアクセス修飾子はできる限り狭い範囲のみアクセスできるようにする。

class MyGdxGame : ApplicationAdapter() {
    private lateinit var batch: SpriteBatch
    private lateinit var img: Texture

    ...
}

ios-moe モジュールの IOSMoeLauncher クラスでは警告が発生する。 constructor の protected は削除する。 Kotlin では クラスが final 扱いとなるためである。 IOSMoeLauncher::class.java!!.getName()IOSMoeLauncher::class.java.name でよい。 get メソッドは Kotlin ではプロパティを使う。 !! は null があり得る場合に使用するが、null となることはないので不要。

再びアプリの実行

Android Emulator / SimulatorApp でそれぞれ実行する。 AndroidiOS で縦向き、横向きが異なる。 AndroidManifest.xml にて screenOrientation="sensor" とすれば Android も縦向きとなる。

Kotlin で iOS アプリを作る

概要

Kotlin で iOS アプリを作成します。 開発環境には Android Studio を使用し、Intel Multi-OS Engine を使って iOS アプリに変換して実行します。

確認環境

参考情報

解説

前提

Xcode, Android Studio, Kotlin Plugin はインストール済みとする。

Multi-OS Engine のインストール

Android Studio に Multi-OS Engine (MOE) Plugin をインストールする。

プロジェクトの作成

プロジェクトの作成手順は通常の Android プロジェクトと同じ。

Multi-OS Engine モジュールの作成

iOS アプリは Multi-OS Engine モジュールとして作成する。

モジュールを作成したら、実行可能な状態になっているので Run 'ios' を選択して実行する。 実機での実行は Developer Team ID が必須な様子(How to configure the Development Team id with MOE 1.2.0 - Support - Multi-OS Engine)。 Simulator では実行された。

Kotlin に変換

Multi-OS Engine モジュールのソースコードJava で書かれている。 Java を Kotlin に変換する。

まずは、build.gradle に Kotlin の設定を加える。 以下を行うとプロジェクトと app (Android アプリ) モジュールに Kotlin の設定が追加される。

ios (Multi-OS Engine モジュール) の build.gradle にはエディタで以下を追記する。

apply plugin: 'kotlin'

dependencies {
    ...
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

次に、Javaソースコードを Kotlin に変換する。

app (Android アプリ) モジュールは java フォルダを選択し Code > Convert Java File to Kotlin File を選択すれば変換される。

ios (Multi-OS Engine モジュール) でも java フォルダを選択し Code > Convert Java File to Kotlin File を選択すれば変換されるが、一部編集が必要。

変換前の Java ファイルと変換後の Kotlin ファイルを記載する。

Main.java

@RegisterOnStartup
public class Main extends NSObject implements UIApplicationDelegate {

    public static void main(String[] args) {
        UIKit.UIApplicationMain(0, null, null, Main.class.getName());
    }

    @Selector("alloc")
    public static native Main alloc();

    protected Main(Pointer peer) {
        super(peer);
    }

    private UIWindow window;

    @Override
    public boolean applicationDidFinishLaunchingWithOptions(UIApplication application, NSDictionary launchOptions) {
        return true;
    }

    @Override
    public void setWindow(UIWindow value) {
        window = value;
    }

    @Override
    public UIWindow window() {
        return window;
    }
}

Main.kt

@RegisterOnStartup
class Main protected constructor(peer: Pointer) : NSObject(peer), UIApplicationDelegate {

    companion object {

        @JvmStatic fun main(args: Array<String>) {
            UIKit.UIApplicationMain(0, null, null, Main::class.java.name)
        }

        @Selector("alloc")
        @JvmStatic external fun alloc(): Main
    }

    private var window: UIWindow? = null

    override fun applicationDidFinishLaunchingWithOptions(application: UIApplication?, launchOptions: NSDictionary<*, *>?): Boolean {
        return true
    }

    override fun setWindow(value: UIWindow?) {
        window = value
    }

    override fun window(): UIWindow? {
        return window
    }
}

alloc 関数では override を削除し @JvmStatic external を追加する。 window 関数の戻り値の型が UIWindow になっているため UIWindow? に変更する。

AppViewController.java

@org.moe.natj.general.ann.Runtime(ObjCRuntime.class)
@ObjCClassName("AppViewController")
@RegisterOnStartup
public class AppViewController extends UIViewController {

    @Owned
    @Selector("alloc")
    public static native AppViewController alloc();

    @Selector("init")
    public native AppViewController init();

    protected AppViewController(Pointer peer) {
        super(peer);
    }

    public UILabel statusText = null;
    public UIButton helloButton = null;

    @Override
    public void viewDidLoad() {
        statusText = getLabel();
        helloButton = getHelloButton();
    }

    @Selector("statusText")
    @Property
    public native UILabel getLabel();

    @Selector("helloButton")
    @Property
    public native UIButton getHelloButton();

    @Selector("BtnPressedCancel_helloButton:")
    public void BtnPressedCancel_button(NSObject sender){
        statusText.setText("Hello Intel Multi-OS Engine!");
    }
}

AppViewController.kt

@org.moe.natj.general.ann.Runtime(ObjCRuntime::class)
@ObjCClassName("AppViewController")
@RegisterOnStartup
class AppViewController protected constructor(peer: Pointer) : UIViewController(peer) {

    @Selector("init")
    external override fun init(): AppViewController

    val statusText: UILabel
        get() = getStatusTextSel()

    val helloButton: UIButton
        get() = getHelloButtonSel()

    override fun viewDidLoad() {}

    @Selector("statusText")
    @Property
    external fun getStatusTextSel(): UILabel

    @Selector("helloButton")
    @Property
    external fun getHelloButtonSel(): UIButton

    @Selector("BtnPressedCancel_helloButton:")
    fun BtnPressedCancel_button(sender: NSObject) {
        statusText.setText("Hello Intel Multi-OS Engine!")
    }

    companion object {

        @Owned
        @Selector("alloc")
        @JvmStatic external fun alloc(): AppViewController
    }
}

alloc 関数は override を削除して @JvmStatic external とする。 実装の無い Selector は external を付ける。 関数名を getStatusText とするとプロパティの statusText と衝突してしまうため getStatusTextSel としている。 自動変換では getLabel 関数が label プロパティに変換されてしまうので修正する。 statusText, helloButton プロパティは var から val に変更し、get をオーバーライドしている。 Java と同様に viewDidLoad 関数が呼ばれたタイミングでフィールドにオブジェクトを保持したい場合は lateinit var を使えば良い。

以上で変換は完了となるため、再度実行して動作することを確認する。

共通モジュールの作成

app (Android モジュール) と ios (Multi-OS Engine モジュール) で共有するモジュール (Java モジュール) を作成する。 app と同様に Kotlin に変換できる。

app と ios モジュールの依存関係に common モジュールを追加する。

以上により共通モジュールのクラスを app と ios モジュールから使用できる。

今後の予定

  • iOS アプリの UI 編集方法の検証
  • 通化できる範囲の検証

Kotlin の書籍