LoginSignup
17

More than 5 years have passed since last update.

Kotlin+Processingのススメ

Posted at

初めまして。

今回、音ゲーイベントの「BOFU2016」に自作曲を1曲出すことになりまして、その曲のBGA(所謂MVですね)をProcessingで作ってみることにしました。Kotlin信者である私は当然開発言語にKotlinを選んだわけですが、作ってみると思っていた以上にKotlinの機能が活用できることが分かったのでここに書き留めておきたいと思います。

ちなみに宣伝になりますが、私の作品はこちらです。音ゲーマーの皆さんは是非プレイしてくださいね。 http://manbow.nothing.sh/event/event.cgi?action=More_def&num=302&event=110

導入方法はProcessingをIntelliJ IDEA+Kotlinで書くに詳しいため省略します。

さらば執拗なドット(.)

シーン別にクラスを定義して、描画をそれぞれで行いたいという時、普通はこんな感じで書くと思います。

class MyApplet: PApplet() {
  // scenes: Array<Scene>
  override fun draw() {
    scenes.forEach { it.draw(this) }
  }
}

class Scene1(): Scene {
  override fun draw(app: MyApplet) {
    app.noStroke()
    app.fill(1f)
    app.pushMatrix()
    app.translate(640f, 360f)
    app.rect(-50f, -50f, 100f, 100f)
    // ...
  }
}

このapp.、邪魔じゃないですか?
そこでwith()を使います。

class Scene1(): Scene {
  override fun draw(app: MyApplet) {
    with(app) {
      noStroke()
      fill(1f)
      pushMatrix()
      translate(640f, 360f)
      rect(-50f, -50f, 100f, 100f)
      // ...
    }
  }
}

with(app)内ではthis=appなので、app.draw()内で描画コードを呼び出しているかのように扱うことができるのです。

この手法は同様にPGraphicsにも使えます。PAppletPGraphicsで描画のメソッド名は同じなので、直接描画していたものをバッファに描画したいという時などは(その逆も)、該当コードをwith()で括ってしまえばいいわけです。

PGraphicsに描画する時によくbeginDraw() endDraw()し忘れるそこのあなた。このような関数を定義してしまえば、

inline fun <R> withGraphics(receiver: PGraphics, f: PGraphics.() -> R) {
  receiver.beginDraw()
  receiver.f()
  receiver.endDraw()
}
class MyScene(val app: MyApplet) {
  val g = app.createGraphics(1280, 720)

  override fun draw(app: MyApplet) {
    with(app) {
      withGraphics(g) {
        noStroke()
        fill(1f)
        pushMatrix()
        translate(640f, 360f)
        rect(-50f, -50f, 100f, 100f)
        // ...
      }
    }
  }
}

withGraphics()で囲むだけ。労力最小ですね。

with(app)の中にネストすることで、各種数学関数(sin, norm, ...)なども同様に使えます。

関数

Processingやってると、単純なイージング関数とか、作りたくなりますよね。関数型言語のエッセンスを取り入れたKotlinなら、{}return無しで簡単に書けてしまいます。

class MyApplet: PApplet() {
  // ...

  // 二次
  fun quad1(t: Float) = -t * (t - 2f)
  // n次
  fun pow1(t: Float, e: Float) = -pow(-t + 1f , e) + 1f

 // 範囲外の値を取らないnormを定義 (おすすめ!)
  fun cnorm(value: Float, start: Float, stop: Float) = constrain(norm(value, start, stop), 0f, 1f)
}
// 16拍目から18拍目までで二次のイーズイン
val t = cnorm(beat, 16f, 18f)
val s = quad1(t)
tint(lerp(0.5f, 0f, s))

描画系メソッドを拡張できる

// 整数座標に直して画像を描画する(パフォーマンスアップ)
fun PGraphics.iimage(img: PImage, x: Float, y: Float) = this.image(img, x.toInt().toFloat(), y.toInt().toFloat())

class MyScene(val app: MyApplet) {
  val g = app.createGraphics(1280, 720)

  override fun draw(app: MyApplet) {
    with(app) {
      withGraphics(g) {
        iimage(img, 0.5f, 0.5f)
        // ...
      }
    }
  }
}

スクリーンへの描画を拡張する時は、同様にPAppletに対して拡張関数を定義するか、PAppletを継承したクラスにメソッドを定義しましょう。

もうpopし忘れない

inline fun <R> PGraphics.saveMatrix(f: () -> R) {
  this.pushMatrix()
  f()
  this.popMatrix()
}

こんな風に定義しておけば、

saveMatrix {
  translate(640f, 360f)
  scale(1.5f)
  rotate(PI / 4)
  // ...
  saveMatrix {
    rotate(PI / 8)
    // ...
  }
  saveMatrix {
    rotate(-PI / 8)
    // ...
  }
}

saveMatrixで括られた部分をブロックのように扱え(実際はラムダを渡しているのですが)、行列が保存されている部分がネストされて読みやすいし、popMatrix()を忘れてしまうというミスが起きないのです。

同様にPAppletにも定義しておきましょう。


如何でしたか?
Javaで疲弊した人、是非KotlinでProcessingしてみてください。
間違いを見つけた方や「こんな使い方もあるよ!」という方はコメントしてください。お待ちしています。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17