Wiki

Clone wiki

rxgdx / Home

Reactive gdx

Framework for crossplatform gui and 2d-game development base on libgdx

Curent state

This project in early prototype.

Implemented features

  • crossplatform (desktop, android, ios) thanks to libgdx!
  • DI-drived
  • modules + components + properties scene
  • reactive
  • events driven with reactive implementation
  • property bindings driven with reactive implementation
  • kotlin implementaion!

Features and compoents not implemented yet

  • automaticaly load and save scene state based on properties
  • ui skins
  • common ui widgets and layouts (except image, label, vertical layout and some more, that already implemented)

DI + Modules + Components + Properties

Components and Properties

:::kotlin
@DependentComponents(PositionComponent::class)
class AlignAtCenterOfWorldComponent(parent: Module) : AbstractComponent(parent) {

    private val position: PositionComponent by dependency()

    val atCenterOfWorldProperty: Property<Boolean> = property(true)
    var atCenterOfWorld:Boolean by atCenterOfWorldProperty

    @OnEvent(WorldSizeChangedEvent::class)
    fun onWorldChanged(event: WorldSizeChangedEvent) {
       if (atCenterOfWorld) {
           position.set(event.width/2f, event.height/2f)
       }
    }

}

Reactive

Loading assets,

:::kotlin
val subscription = loadDependencies()
        .withRunner(Runners.newThread(executorService)).map { loadData() }
        .withRunner(Runners.gdx).map { createAsset(it) }
        .subscribe { this.asset = it }

Events

Events is a reactive stream. Use filter to handle events:

:::kotlin

class SomeComponent(parent: Module) : AbstractComponent(parent) {

    override fun setup() {
        events().filterIs<UpdateEvent>().subscribe { doSomething() }
    }

}
... or use shortcut:
:::kotlin
// instead of events().filterIs<UpdateEvent>().subscribe { doSomething() }
onEvent<UpdateEvent> {
    doSomething()
}
or use annotation version of previous code:
:::kotlin
@OnEvent(UpdateEvent::class)
fun update(event:UpdateEvent) {
    doSomething()
}
// or
@OnUpdate
fun update() {
    doSomething()
}

Bindings

::kotlin
val stringProperty = property("300")

val intProperty1 = property(10)
val intProperty2 = property(20)

val binding = intProperty1 + intProperty2

stringProperty.bindOneWay(binding.asString())

Assert.assertEquals("30", stringProperty.value)

intProperty1.value = 100

Assert.assertEquals("120", stringProperty.value)

Example

:::kotlin

fun main(args: Array<String>) {
    startLwjglApplication {
        debug = true

        addInputTranslator(::InputToEventsProcessor)
        addInputTranslator { GestureDetector(GesturesToEventProcessor(it)) }

        buildScene("test-scene") {
            layer("main-layer") {
                withComponent<ImageCopierComponent>()
                withScreenSize()

                camera("camera") {
                    freeTransform()
                }

                projection("camera")
            }

            withModule(debugGuiLayer())
        }

        scheduleScene("test-scene")
    }
}

class ImageCopierComponent(parent: Module, val moduleFactory: ModuleFactory, val director: SceneDirector) : AbstractComponent(parent) {

    private var counter = 0

    private fun newSprite(x:Float, y:Float) = 
            spriteDefinition("sprite-${counter++}", "whiteflag.png") {
                withComponent<ActionsComponent>()
                withComponent<ClickToDetachComponent>()
                withPivotAtCenter()
                withValues {
                    transform.position(x, y)
                }
    }

    @OnEvent(TapGestureEvent::class)
    fun attachImage(event: AbstractScreenEvent) = poolable {
        event.handled()

        val sprite = moduleFactory.create(newSprite(event.worldX, event.worldY))

        parent.attach(sprite)

        val time = 3f.random() + 3f

        val target = immutableVec2(director.screen.width.random(), director.screen.height.random())

        val actions = sprite.actions()

        actions.runCircle {
            parallel {
                sequential {
                    scaleTo(ImmutableVec2f.zero, time / 2f, Interpolations.random())
                    scaleTo(3f.random().run { ImmutableVec2f.readonly(this, this) }, time / 2f, Interpolations.random())
                }
                sequential {
                    move(target, time / 2f, Interpolations.random())
                    move(target * -1f, time / 2f, Interpolations.random())
                }
                rotate(1800f.random(), time, Interpolations.random())
            }
        }

    }
}

class ClickToDetachComponent(parent: Module) : AbstractComponent(parent) {

    @OnEvent(TapGestureEvent::class)
    fun detach(event:TapGestureEvent) {
        parent.detach()
        event.handled()
    }

}

Updated