UNIT MOVEMENT ANIMATION

LET'S FUKKEN GO
This commit is contained in:
yairm210 2024-07-09 23:57:45 +03:00
parent 7c1e0c0d25
commit f021fb692d
4 changed files with 65 additions and 21 deletions

View File

@ -11,7 +11,7 @@ import com.unciv.ui.components.tilegroups.layers.TileLayerFeatures
import com.unciv.ui.components.tilegroups.layers.TileLayerMisc import com.unciv.ui.components.tilegroups.layers.TileLayerMisc
import com.unciv.ui.components.tilegroups.layers.TileLayerOverlay import com.unciv.ui.components.tilegroups.layers.TileLayerOverlay
import com.unciv.ui.components.tilegroups.layers.TileLayerTerrain import com.unciv.ui.components.tilegroups.layers.TileLayerTerrain
import com.unciv.ui.components.tilegroups.layers.TileLayerUnitArt import com.unciv.ui.components.tilegroups.layers.TileLayerUnitSprite
import com.unciv.ui.components.tilegroups.layers.TileLayerUnitFlag import com.unciv.ui.components.tilegroups.layers.TileLayerUnitFlag
import com.unciv.utils.DebugUtils import com.unciv.utils.DebugUtils
import kotlin.math.pow import kotlin.math.pow
@ -49,7 +49,7 @@ open class TileGroup(
@Suppress("LeakingThis") val layerBorders = TileLayerBorders(this, groupSize) @Suppress("LeakingThis") val layerBorders = TileLayerBorders(this, groupSize)
@Suppress("LeakingThis") val layerMisc = TileLayerMisc(this, groupSize) @Suppress("LeakingThis") val layerMisc = TileLayerMisc(this, groupSize)
@Suppress("LeakingThis") val layerOverlay = TileLayerOverlay(this, groupSize) @Suppress("LeakingThis") val layerOverlay = TileLayerOverlay(this, groupSize)
@Suppress("LeakingThis") val layerUnitArt = TileLayerUnitArt(this, groupSize) @Suppress("LeakingThis") val layerUnitArt = TileLayerUnitSprite(this, groupSize)
@Suppress("LeakingThis") val layerUnitFlag = TileLayerUnitFlag(this, groupSize) @Suppress("LeakingThis") val layerUnitFlag = TileLayerUnitFlag(this, groupSize)
@Suppress("LeakingThis") val layerCityButton = TileLayerCityButton(this, groupSize) @Suppress("LeakingThis") val layerCityButton = TileLayerCityButton(this, groupSize)

View File

@ -13,7 +13,7 @@ import com.unciv.ui.components.tilegroups.layers.TileLayerFeatures
import com.unciv.ui.components.tilegroups.layers.TileLayerMisc import com.unciv.ui.components.tilegroups.layers.TileLayerMisc
import com.unciv.ui.components.tilegroups.layers.TileLayerOverlay import com.unciv.ui.components.tilegroups.layers.TileLayerOverlay
import com.unciv.ui.components.tilegroups.layers.TileLayerTerrain import com.unciv.ui.components.tilegroups.layers.TileLayerTerrain
import com.unciv.ui.components.tilegroups.layers.TileLayerUnitArt import com.unciv.ui.components.tilegroups.layers.TileLayerUnitSprite
import com.unciv.ui.components.tilegroups.layers.TileLayerUnitFlag import com.unciv.ui.components.tilegroups.layers.TileLayerUnitFlag
import com.unciv.ui.components.widgets.ZoomableScrollPane import com.unciv.ui.components.widgets.ZoomableScrollPane
import kotlin.math.max import kotlin.math.max
@ -106,7 +106,7 @@ class TileGroupMap<T: TileGroup>(
val featureLayers = ArrayList<TileLayerFeatures>() val featureLayers = ArrayList<TileLayerFeatures>()
val borderLayers = ArrayList<TileLayerBorders>() val borderLayers = ArrayList<TileLayerBorders>()
val miscLayers = ArrayList<TileLayerMisc>() val miscLayers = ArrayList<TileLayerMisc>()
val pixelUnitLayers = ArrayList<TileLayerUnitArt>() val pixelUnitLayers = ArrayList<TileLayerUnitSprite>()
val circleFogCrosshairLayers = ArrayList<TileLayerOverlay>() val circleFogCrosshairLayers = ArrayList<TileLayerOverlay>()
val unitLayers = ArrayList<TileLayerUnitFlag>() val unitLayers = ArrayList<TileLayerUnitFlag>()
val cityButtonLayers = ArrayList<TileLayerCityButton>() val cityButtonLayers = ArrayList<TileLayerCityButton>()

View File

@ -9,28 +9,30 @@ import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
import com.unciv.ui.components.tilegroups.TileGroup import com.unciv.ui.components.tilegroups.TileGroup
private class UnitArtSlot : Group() { class UnitSpriteSlot : Group() {
var imageLocation = "" var imageLocation = ""
} }
class TileLayerUnitArt(tileGroup: TileGroup, size: Float) : TileLayer(tileGroup, size) { class TileLayerUnitSprite(tileGroup: TileGroup, size: Float) : TileLayer(tileGroup, size) {
override fun act(delta: Float) {} override fun act(delta: Float) {}
override fun hit(x: Float, y: Float, touchable: Boolean): Actor? = null override fun hit(x: Float, y: Float, touchable: Boolean): Actor? = null
private var civilianSlot: UnitArtSlot = UnitArtSlot() private var civilianSlot: UnitSpriteSlot = UnitSpriteSlot()
private var militarySlot: UnitArtSlot = UnitArtSlot() private var militarySlot: UnitSpriteSlot = UnitSpriteSlot()
init { init {
addActor(civilianSlot) addActor(civilianSlot)
addActor(militarySlot) addActor(militarySlot)
} }
fun getSpriteSlot(unit:MapUnit) = if (unit.isCivilian()) civilianSlot else militarySlot
private fun showMilitaryUnit(viewingCiv: Civilization) = tileGroup.isForceVisible private fun showMilitaryUnit(viewingCiv: Civilization) = tileGroup.isForceVisible
|| viewingCiv.viewableInvisibleUnitsTiles.contains(tileGroup.tile) || viewingCiv.viewableInvisibleUnitsTiles.contains(tileGroup.tile)
|| !tileGroup.tile.hasEnemyInvisibleUnit(viewingCiv) || !tileGroup.tile.hasEnemyInvisibleUnit(viewingCiv)
private fun updateSlot(slot: UnitArtSlot, unit: MapUnit?, isShown: Boolean) { private fun updateSlot(slot: UnitSpriteSlot, unit: MapUnit?, isShown: Boolean) {
var location = "" var location = ""
var nationName = "" var nationName = ""

View File

@ -53,7 +53,7 @@ import com.unciv.ui.components.tilegroups.TileGroup
import com.unciv.ui.components.tilegroups.TileGroupMap import com.unciv.ui.components.tilegroups.TileGroupMap
import com.unciv.ui.components.tilegroups.TileSetStrings import com.unciv.ui.components.tilegroups.TileSetStrings
import com.unciv.ui.components.tilegroups.WorldTileGroup import com.unciv.ui.components.tilegroups.WorldTileGroup
import com.unciv.ui.components.widgets.UnitGroup import com.unciv.ui.components.widgets.UnitIconGroup
import com.unciv.ui.components.widgets.ZoomableScrollPane import com.unciv.ui.components.widgets.ZoomableScrollPane
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.basescreen.BaseScreen
@ -299,8 +299,10 @@ class WorldMapHolder(
// Since this runs in a different thread, even if we check movement.canReach() // Since this runs in a different thread, even if we check movement.canReach()
// then it might change until we get to the getTileToMoveTo, so we just try/catch it // then it might change until we get to the getTileToMoveTo, so we just try/catch it
val tileToMoveTo: Tile val tileToMoveTo: Tile
val pathToTile: List<Tile>
try { try {
tileToMoveTo = selectedUnit.movement.getTileToMoveToThisTurn(targetTile) tileToMoveTo = selectedUnit.movement.getTileToMoveToThisTurn(targetTile)
pathToTile = selectedUnit.movement.getDistanceToTiles().getPathToTile(targetTile)
} catch (ex: Exception) { } catch (ex: Exception) {
when (ex) { when (ex) {
is UnitMovement.UnreachableDestinationException -> { is UnitMovement.UnreachableDestinationException -> {
@ -308,9 +310,7 @@ class WorldMapHolder(
// Or telling a ship to run onto a coastal land tile. // Or telling a ship to run onto a coastal land tile.
// Do nothing // Do nothing
} }
else -> { else -> Log.error("Exception in getTileToMoveToThisTurn", ex)
Log.error("Exception in getTileToMoveToThisTurn", ex)
}
} }
return@run // can't move here return@run // can't move here
} }
@ -325,6 +325,7 @@ class WorldMapHolder(
// but until it reaches the headTowards the board has changed and so the headTowards fails. // but until it reaches the headTowards the board has changed and so the headTowards fails.
// I can't think of any way to avoid this, // I can't think of any way to avoid this,
// but it's so rare and edge-case-y that ignoring its failure is actually acceptable, hence the empty catch // but it's so rare and edge-case-y that ignoring its failure is actually acceptable, hence the empty catch
val previousTile = selectedUnit.currentTile
selectedUnit.movement.moveToTile(tileToMoveTo) selectedUnit.movement.moveToTile(tileToMoveTo)
if (selectedUnit.isExploring() || selectedUnit.isMoving()) if (selectedUnit.isExploring() || selectedUnit.isMoving())
selectedUnit.action = null // remove explore on manual move selectedUnit.action = null // remove explore on manual move
@ -335,6 +336,9 @@ class WorldMapHolder(
if (selectedUnit.currentMovement > 0) worldScreen.bottomUnitTable.selectUnit(selectedUnit) if (selectedUnit.currentMovement > 0) worldScreen.bottomUnitTable.selectUnit(selectedUnit)
worldScreen.shouldUpdate = true worldScreen.shouldUpdate = true
animateMovement(previousTile, selectedUnit, targetTile, pathToTile)
if (selectedUnits.size > 1) { // We have more tiles to move if (selectedUnits.size > 1) { // We have more tiles to move
moveUnitToTargetTile(selectedUnits.subList(1, selectedUnits.size), targetTile) moveUnitToTargetTile(selectedUnits.subList(1, selectedUnits.size), targetTile)
} else removeUnitActionOverlay() //we're done here } else removeUnitActionOverlay() //we're done here
@ -349,6 +353,44 @@ class WorldMapHolder(
} }
} }
private fun animateMovement(
previousTile: Tile,
selectedUnit: MapUnit,
targetTile: Tile,
pathToTile: List<Tile>
) {
val tileGroup = tileGroups[previousTile]!!
// Steal the current sprites to our new group
val unitSpriteAndIcon = Group().apply { setPosition(tileGroup.x, tileGroup.y) }
val unitSpriteSlot = tileGroup.layerUnitArt.getSpriteSlot(selectedUnit)
for (spriteImage in unitSpriteSlot.children) unitSpriteAndIcon.addActor(spriteImage)
tileGroup.parent.addActor(unitSpriteAndIcon)
// Disable the final tile, so we won't have one image "merging into" the other
val targetTileSpriteSlot = tileGroups[targetTile]!!.layerUnitArt.getSpriteSlot(selectedUnit)
targetTileSpriteSlot.isVisible = false
unitSpriteAndIcon.addAction(
Actions.sequence(
*pathToTile.map { tile ->
Actions.moveTo(
tileGroups[tile]!!.x,
tileGroups[tile]!!.y,
0.5f / pathToTile.size
)
}.toTypedArray(),
Actions.run {
// Re-enable the final tile
targetTileSpriteSlot.isVisible = true
worldScreen.shouldUpdate = true
},
Actions.removeActor(),
)
)
}
private fun swapMoveUnitToTargetTile(selectedUnit: MapUnit, targetTile: Tile) { private fun swapMoveUnitToTargetTile(selectedUnit: MapUnit, targetTile: Tile) {
markUnitMoveTutorialComplete(selectedUnit) markUnitMoveTutorialComplete(selectedUnit)
selectedUnit.movement.swapMoveToTile(targetTile) selectedUnit.movement.swapMoveToTile(targetTile)
@ -505,9 +547,9 @@ class WorldMapHolder(
} }
for (unit in unitList) { for (unit in unitList) {
val unitGroup = UnitGroup(unit, 48f).surroundWithCircle(68f, resizeActor = false) val unitIconGroup = UnitIconGroup(unit, 48f).surroundWithCircle(68f, resizeActor = false)
unitGroup.circle.color = Color.GRAY.cpy().apply { a = 0.5f } unitIconGroup.circle.color = Color.GRAY.cpy().apply { a = 0.5f }
if (unit.currentMovement == 0f) unitGroup.color.a = 0.66f if (unit.currentMovement == 0f) unitIconGroup.color.a = 0.66f
val clickableCircle = ClickableCircle(68f) val clickableCircle = ClickableCircle(68f)
clickableCircle.touchable = Touchable.enabled clickableCircle.touchable = Touchable.enabled
clickableCircle.onClick { clickableCircle.onClick {
@ -515,8 +557,8 @@ class WorldMapHolder(
worldScreen.shouldUpdate = true worldScreen.shouldUpdate = true
removeUnitActionOverlay() removeUnitActionOverlay()
} }
unitGroup.addActor(clickableCircle) unitIconGroup.addActor(clickableCircle)
table.add(unitGroup) table.add(unitIconGroup)
} }
addOverlayOnTileGroup(tileGroups[tile]!!, table) addOverlayOnTileGroup(tileGroups[tile]!!, table)
@ -546,7 +588,7 @@ class WorldMapHolder(
} }
val firstUnit = dto.unitToTurnsToDestination.keys.first() val firstUnit = dto.unitToTurnsToDestination.keys.first()
val unitIcon = if (dto.unitToTurnsToDestination.size == 1) UnitGroup(firstUnit, smallerCircleSizes) val unitIcon = if (dto.unitToTurnsToDestination.size == 1) UnitIconGroup(firstUnit, smallerCircleSizes)
else dto.unitToTurnsToDestination.size.toString().toLabel(fontColor = firstUnit.civ.nation.getInnerColor()).apply { setAlignment(Align.center) } else dto.unitToTurnsToDestination.size.toString().toLabel(fontColor = firstUnit.civ.nation.getInnerColor()).apply { setAlignment(Align.center) }
.surroundWithCircle(smallerCircleSizes).apply { circle.color = firstUnit.civ.nation.getOuterColor() } .surroundWithCircle(smallerCircleSizes).apply { circle.color = firstUnit.civ.nation.getOuterColor() }
unitIcon.y = buttonSize - unitIcon.height unitIcon.y = buttonSize - unitIcon.height
@ -575,7 +617,7 @@ class WorldMapHolder(
} }
) )
val unitIcon = UnitGroup(dto.unit, smallerCircleSizes) val unitIcon = UnitIconGroup(dto.unit, smallerCircleSizes)
unitIcon.y = buttonSize - unitIcon.height unitIcon.y = buttonSize - unitIcon.height
swapWithButton.addActor(unitIcon) swapWithButton.addActor(unitIcon)
@ -595,7 +637,7 @@ class WorldMapHolder(
} }
) )
val unitIcon = UnitGroup(dto.unit, smallerCircleSizes) val unitIcon = UnitIconGroup(dto.unit, smallerCircleSizes)
unitIcon.y = buttonSize - unitIcon.height unitIcon.y = buttonSize - unitIcon.height
connectRoadButton.addActor(unitIcon) connectRoadButton.addActor(unitIcon)