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?
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.