Search code examples
kotlinlibgdx

Libgdx custom Actor not firing clicked and touchedDown events


I am having trouble receiving click event on my custom actor class. I can do that with existing UI compoments like Buttons and Labels, but not my custom actor. I added it to stage, which is set as inputProcessor, I am calling act on stage in my render method of stage and I know from console output, that act on my actor is being called. Event however never gets fired. What else should I look at? My actor:

class TowerActor(val sprite: Sprite, val unit: Tower) : Actor() {

    init {
        touchable = Touchable.enabled
        addListener(TestListener())
        sprite.updatePosition(unit.position)
    }

    override fun draw(batch: Batch, parentAlpha: Float) {
        super.draw(batch, parentAlpha)
        sprite.draw(batch)
    }

    class TestListener : InputListener() {
        override fun touchUp(event: InputEvent?, x: Float, y: Float, pointer: Int, button: Int) {
            super.touchUp(event, x, y, pointer, button)
            println("touchUp")
            exitProcess(0)
        }

        fun clicked(event: InputEvent?, x: Float, y: Float) {
            println("clicked2")
            exitProcess(0)
        }
    }
}

My stage (removed unimportant parts):

class GameStage(
        val game: GameController,
        result: MapParser.Result,
        levelId: String? = null

) : Stage() {
    val level: Level = game.loadLevel(levelId ?: result.levelTypes.first().id)
    val gameRenderer: GameRenderer

    init {
        gameRenderer = GameRenderer(batch = batch as SpriteBatch, level = level, textureRepository = TextureRepository.fromTiles(result.tiles))
    }

    fun render(delta: Float) {
        act(delta)
        draw()
    }

    val towerActors: MutableMap<String, Actor> = mutableMapOf()
    val textureRepository = TextureRepository.fromTiles(result.tiles)

    override fun draw() {
        super.draw()
        level.container.towersForRead.forEach { unit ->
            val tower = towerActors.getOrPut(unit.id) {
                println("created tower")
                val sprite = Sprite(textureRepository.getRegion(unit.type))
                val t = TowerActor(sprite = sprite, unit = unit)
                addActor(t)
                t
            }
        }
    }
}

In my main game render method, I just call GameStage.render directly (I have more sophisticated structure with screens here, but I am calling this directly just to be sure structure isn't causing a problem).

Is there something wrong with this approach? What else could be problem?


Solution

  • Actors' input listeners are called not from their own act method, but from the Stage directly after calling hit methods down the Actor hierarchy to determine which Actors overlap the touch point.

    I think your issue is that you have not set a position and size on your Actor, so its hit method will always return false.

    I also want to recommend that you not use the Sprite class, especially with Actors. The Sprite class is not just an image asset (the TextureRegion it extends from) but also a bunch of data about its size, color rotation, scale, etc. So from a general standpoint, this class does a poor job of separation of concerns because it conflates an image asset with game state data. It is used within LibGDX only for particles, which is a highly optimized case where you never have to inspect the draw data of individual particles individually and there is a high number of them so the class needs to be as compact as possible.

    But especially when you combine it with Actor it is problematic because now your game state data exists in two places. Both the Actor and the Sprite contain position, color, rotation, scale, etc. data, so which one is the true data? It's ambiguous, and it would be highly error prone to try to keep them in sync.

    Instead, you should use a TextureRegion instead of Sprite. Then there is only one possible source of truth for your Actor's size, position and color: the Actor's properties.