Reorder TileGroup layers to draw borders under Pixel Units (#6980)

* Reorder TileGroup layers to draw borders under Pixel Units

* Reorder TileGroup layers to draw borders under Pixel Units - reviews
This commit is contained in:
SomeTroglodyte 2022-05-29 21:16:35 +02:00 committed by GitHub
parent 8ab686cb14
commit 69698e99ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 110 additions and 60 deletions

View File

@ -15,7 +15,6 @@ import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.map.TileGroupMap
import com.unciv.ui.popup.ToastPopup
@ -298,7 +297,7 @@ class CityScreen(
tileGroups.add(tileGroup)
}
val tilesToUnwrap = ArrayList<CityTileGroup>()
val tilesToUnwrap = mutableSetOf<CityTileGroup>()
for (tileGroup in tileGroups) {
val xDifference = cityInfo.getCenterTile().position.x - tileGroup.tileInfo.position.x
val yDifference = cityInfo.getCenterTile().position.y - tileGroup.tileInfo.position.y

View File

@ -5,17 +5,27 @@ import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.scenes.scene2d.Group
import com.unciv.logic.HexMath
import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap
import com.unciv.ui.cityscreen.CityTileGroup
import com.unciv.ui.tilegroups.ActionlessGroup
import com.unciv.ui.tilegroups.TileGroup
import com.unciv.ui.tilegroups.WorldTileGroup
import kotlin.math.max
import kotlin.math.min
/**
* A (potentially partial) map view
* @param T [TileGroup] or a subclass ([WorldTileGroup], [CityTileGroup])
* @param tileGroups Source of [TileGroup]s to include, will be **iterated several times**.
* @param tileGroupsToUnwrap For these, coordinates will be unwrapped using [TileMap.getUnWrappedPosition]
*/
class TileGroupMap<T: TileGroup>(
tileGroups: Collection<T>,
tileGroups: Iterable<T>,
private val leftAndRightPadding: Float,
private val topAndBottomPadding: Float,
worldWrap: Boolean = false,
tileGroupsToUnwrap: Collection<T>? = null
tileGroupsToUnwrap: Set<T>? = null
): Group() {
private var topX = -Float.MAX_VALUE
private var topY = -Float.MAX_VALUE
@ -33,7 +43,7 @@ class TileGroupMap<T: TileGroup>(
}
for (tileGroup in tileGroups) {
val positionalVector = if (tileGroupsToUnwrap != null && tileGroupsToUnwrap.contains(tileGroup)) {
val positionalVector = if (tileGroupsToUnwrap?.contains(tileGroup) == true) {
HexMath.hex2WorldCoords(
tileGroup.tileInfo.tileMap.getUnWrappedPosition(tileGroup.tileInfo.position)
)
@ -82,10 +92,11 @@ class TileGroupMap<T: TileGroup>(
val baseLayers = ArrayList<ActionlessGroup>()
val featureLayers = ArrayList<ActionlessGroup>()
val miscLayers = ArrayList<ActionlessGroup>()
val pixelUnitLayers = ArrayList<ActionlessGroup>()
val circleFogCrosshairLayers = ArrayList<ActionlessGroup>()
val unitLayers = ArrayList<Group>()
val unitImageLayers = ArrayList<ActionlessGroup>()
val cityButtonLayers = ArrayList<Group>()
val circleCrosshairFogLayers = ArrayList<ActionlessGroup>()
// Apparently the sortedByDescending is kinda memory-intensive because it needs to sort ALL the tiles
for (group in tileGroups.sortedByDescending { it.tileInfo.position.x + it.tileInfo.position.y }) {
@ -93,27 +104,32 @@ class TileGroupMap<T: TileGroup>(
baseLayers.add(group.baseLayerGroup.apply { setPosition(group.x,group.y) })
featureLayers.add(group.terrainFeatureLayerGroup.apply { setPosition(group.x,group.y) })
miscLayers.add(group.miscLayerGroup.apply { setPosition(group.x,group.y) })
pixelUnitLayers.add(group.pixelMilitaryUnitGroup.apply { setPosition(group.x,group.y) })
pixelUnitLayers.add(group.pixelCivilianUnitGroup.apply { setPosition(group.x,group.y) })
circleFogCrosshairLayers.add(group.highlightFogCrosshairLayerGroup.apply { setPosition(group.x,group.y) })
unitLayers.add(group.unitLayerGroup.apply { setPosition(group.x,group.y) })
unitImageLayers.add(group.unitImageLayerGroup.apply { setPosition(group.x,group.y) })
cityButtonLayers.add(group.cityButtonLayerGroup.apply { setPosition(group.x,group.y) })
circleCrosshairFogLayers.add(group.highlightCrosshairFogLayerGroup.apply { setPosition(group.x,group.y) })
if (worldWrap) {
for (mirrorTile in mirrorTileGroups[group.tileInfo]!!.toList()) {
baseLayers.add(mirrorTile.baseLayerGroup.apply { setPosition(mirrorTile.x,mirrorTile.y) })
featureLayers.add(mirrorTile.terrainFeatureLayerGroup.apply { setPosition(mirrorTile.x,mirrorTile.y) })
miscLayers.add(mirrorTile.miscLayerGroup.apply { setPosition(mirrorTile.x,mirrorTile.y) })
pixelUnitLayers.add(mirrorTile.pixelMilitaryUnitGroup.apply { setPosition(mirrorTile.x,mirrorTile.y) })
pixelUnitLayers.add(mirrorTile.pixelCivilianUnitGroup.apply { setPosition(mirrorTile.x,mirrorTile.y) })
circleFogCrosshairLayers.add(mirrorTile.highlightFogCrosshairLayerGroup.apply { setPosition(mirrorTile.x,mirrorTile.y) })
unitLayers.add(mirrorTile.unitLayerGroup.apply { setPosition(mirrorTile.x,mirrorTile.y) })
unitImageLayers.add(mirrorTile.unitImageLayerGroup.apply { setPosition(mirrorTile.x,mirrorTile.y) })
cityButtonLayers.add(mirrorTile.cityButtonLayerGroup.apply { setPosition(mirrorTile.x,mirrorTile.y) })
circleCrosshairFogLayers.add(mirrorTile.highlightCrosshairFogLayerGroup.apply { setPosition(mirrorTile.x,mirrorTile.y) })
}
}
}
for (group in baseLayers) addActor(group)
for (group in featureLayers) addActor(group)
for (group in miscLayers) addActor(group)
for (group in circleCrosshairFogLayers) addActor(group)
for (group in pixelUnitLayers) addActor(group)
for (group in circleFogCrosshairLayers) addActor(group)
for (group in tileGroups) addActor(group) // The above layers are for the visual layers, this is for the clickability of the tile
if (worldWrap) {
for (mirrorTiles in mirrorTileGroups.values) {
@ -125,7 +141,6 @@ class TileGroupMap<T: TileGroup>(
for (group in unitImageLayers) addActor(group) // This is so the individual textures for the units are rendered together
for (group in cityButtonLayers) addActor(group) // city buttons + clickability
// there are tiles "below the zero",
// so we zero out the starting position of the whole board so they will be displayed as well
// Map's width is reduced by groupSize if it is wrapped, because wrapped map will miss a tile on the right.
@ -149,6 +164,7 @@ class TileGroupMap<T: TileGroup>(
fun getMirrorTiles(): HashMap<TileInfo, Pair<T, T>> = mirrorTileGroups
// For debugging purposes
override fun draw(batch: Batch?, parentAlpha: Float) { super.draw(batch, parentAlpha) }
override fun act(delta: Float) { super.act(delta) }
override fun draw(batch: Batch?, parentAlpha: Float) { super.draw(batch, parentAlpha) }
@Suppress("RedundantOverride")
override fun act(delta: Float) { super.act(delta) }
}

View File

@ -0,0 +1,24 @@
package com.unciv.ui.tilegroups
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Group
/** A lot of the render time was spent on snapshot arrays of the TileGroupMap's groups, in the act() function.
* These classes are to avoid the overhead of useless act() calls. */
/** A [Group] with [actions] effectively disabled. */
abstract class ActionlessGroupWithHit : Group() {
override fun act(delta: Float) {}
}
/** A [Group] with [actions] and [hit] effectively disabled. */
open class ActionlessGroup() : ActionlessGroupWithHit() {
/** A [Group] with [actions], [hit] and scaling effectively disabled, pre-sized.
* @param groupSize [Sets size][setSize] initially */
constructor(groupSize: Float) : this() {
isTransform = false
@Suppress("LeakingThis") // works by setting fields only
setSize(groupSize, groupSize)
}
override fun hit(x: Float, y: Float, touchable: Boolean): Actor? = null
}

View File

@ -26,37 +26,36 @@ import java.lang.IllegalStateException
import kotlin.math.*
import kotlin.random.Random
/** A lot of the render time was spent on snapshot arrays of the TileGroupMap's groups, in the act() function.
* This class is to avoid the overhead of useless act() calls. */
open class ActionlessGroup(val checkHit:Boolean=false):Group() {
override fun act(delta: Float) {}
override fun hit(x: Float, y: Float, touchable: Boolean): Actor? {
if (checkHit)
return super.hit(x, y, touchable)
return null
}
}
open class TileGroup(var tileInfo: TileInfo, val tileSetStrings:TileSetStrings, private val groupSize: Float = 54f) : ActionlessGroup(true) {
open class TileGroup(
var tileInfo: TileInfo,
val tileSetStrings: TileSetStrings,
groupSize: Float = 54f
) : ActionlessGroupWithHit() {
/*
Layers:
Base image (+ overlay)
Feature overlay / city overlay
Misc: Units, improvements, resources, border, arrows
Highlight, Crosshair, Fog layer
City name
Layers (reordered in TileGroupMap):
Base image (+ overlay)
Terrain Feature overlay (including roads and pixel units)
Misc: improvements, resources, yields, worked, resources, border, arrows, and starting locations in editor
Pixel Units
Highlight, Fog, Crosshair layer (in that order)
Units
City button
City name
*/
/** Cache simple but frequent calculations. */
private val hexagonImageWidth = groupSize * 1.5f
/** Cache simple but frequent calculations. */
private val hexagonImageOrigin = Pair(hexagonImageWidth/2f, sqrt((hexagonImageWidth/2f).pow(2) - (hexagonImageWidth/4f).pow(2))) // Pair, not Vector2, for immutability. Second number is triangle height for hex center.
private val hexagonImageOrigin = Pair(hexagonImageWidth / 2f, sqrt((hexagonImageWidth / 2f).pow(2) - (hexagonImageWidth / 4f).pow(2)))
// Pair, not Vector2, for immutability. Second number is triangle height for hex center.
/** Cache simple but frequent calculations. */
private val hexagonImagePosition = Pair(-hexagonImageOrigin.first/3f, -hexagonImageOrigin.second/4f) // Honestly, I got these numbers empirically by printing `.x` and `.y` after `.center()`, and I'm not totally clear on the stack of transformations that makes them work. But they are still exact ratios, AFAICT.
private val hexagonImagePosition = Pair(-hexagonImageOrigin.first / 3f, -hexagonImageOrigin.second / 4f)
// Honestly, I got these numbers empirically by printing `.x` and `.y` after `.center()`, and I'm not totally
// clear on the stack of transformations that makes them work. But they are still exact ratios, AFAICT.
// For recognizing the group in the profiler
class BaseLayerGroupClass:ActionlessGroup()
val baseLayerGroup = BaseLayerGroupClass().apply { isTransform = false; setSize(groupSize, groupSize) }
class BaseLayerGroupClass(groupSize: Float) : ActionlessGroup(groupSize)
val baseLayerGroup = BaseLayerGroupClass(groupSize)
val tileBaseImages: ArrayList<Image> = ArrayList()
/** List of image locations comprising the layers so we don't need to change images all the time */
@ -66,9 +65,8 @@ open class TileGroup(var tileInfo: TileInfo, val tileSetStrings:TileSetStrings,
private var baseTerrainOverlayImage: Image? = null
private var baseTerrain: String = ""
class TerrainFeatureLayerGroupClass:ActionlessGroup()
val terrainFeatureLayerGroup = TerrainFeatureLayerGroupClass()
.apply { isTransform = false; setSize(groupSize, groupSize) }
class TerrainFeatureLayerGroupClass(groupSize: Float) : ActionlessGroup(groupSize)
val terrainFeatureLayerGroup = TerrainFeatureLayerGroupClass(groupSize)
// These are for OLD tiles - for instance the "forest" symbol on the forest
private var terrainFeatureOverlayImage: Image? = null
@ -77,14 +75,14 @@ open class TileGroup(var tileInfo: TileInfo, val tileSetStrings:TileSetStrings,
private var naturalWonderImage: Image? = null
private var pixelMilitaryUnitImageLocation = ""
var pixelMilitaryUnitGroup = ActionlessGroup().apply { isTransform = false; setSize(groupSize, groupSize) }
var pixelMilitaryUnitGroup = ActionlessGroup(groupSize)
private var pixelCivilianUnitImageLocation = ""
var pixelCivilianUnitGroup = ActionlessGroup().apply { isTransform = false; setSize(groupSize, groupSize) }
var pixelCivilianUnitGroup = ActionlessGroup(groupSize)
class MiscLayerGroupClass:ActionlessGroup(){
class MiscLayerGroupClass(groupSize: Float) : ActionlessGroup(groupSize) {
override fun draw(batch: Batch?, parentAlpha: Float) = super.draw(batch, parentAlpha)
}
val miscLayerGroup = MiscLayerGroupClass().apply { isTransform = false; setSize(groupSize, groupSize) }
val miscLayerGroup = MiscLayerGroupClass(groupSize)
var tileYieldGroupInitialized = false
val tileYieldGroup: YieldGroup by lazy { YieldGroup() }
@ -110,22 +108,31 @@ open class TileGroup(var tileInfo: TileInfo, val tileSetStrings:TileSetStrings,
@Suppress("LeakingThis") // we trust TileGroupIcons not to use our `this` in its constructor except storing it for later
val icons = TileGroupIcons(this)
class UnitLayerGroupClass:Group(){
class UnitLayerGroupClass(groupSize: Float) : Group() {
init {
isTransform = false
touchable = Touchable.disabled
setSize(groupSize, groupSize)
}
override fun draw(batch: Batch?, parentAlpha: Float) = super.draw(batch, parentAlpha)
override fun act(delta: Float) { // No 'snapshotting' since we trust it wil remain the same
override fun act(delta: Float) { // No 'snapshotting' since we trust it will remain the same
for (child in children)
child.act(delta)
}
}
class UnitImageLayerGroupClass:ActionlessGroup(){
class UnitImageLayerGroupClass(groupSize: Float) : ActionlessGroup(groupSize) {
override fun draw(batch: Batch?, parentAlpha: Float) = super.draw(batch, parentAlpha)
init {
touchable = Touchable.disabled
}
}
// We separate the units from the units' backgrounds, because all the background elements are in the same texture, and the units' aren't
val unitLayerGroup = UnitLayerGroupClass().apply { isTransform = false; setSize(groupSize, groupSize);touchable = Touchable.disabled }
val unitImageLayerGroup = UnitImageLayerGroupClass().apply { isTransform = false; setSize(groupSize, groupSize);touchable = Touchable.disabled }
val unitLayerGroup = UnitLayerGroupClass(groupSize)
val unitImageLayerGroup = UnitImageLayerGroupClass(groupSize)
class CityButtonLayerGroupClass(val tileInfo: TileInfo) :Group() {
class CityButtonLayerGroupClass(val tileInfo: TileInfo, groupSize: Float) : Group() {
override fun draw(batch: Batch?, parentAlpha: Float) {
if (!tileInfo.isCityCenter()) return
super.draw(batch, parentAlpha)
@ -138,12 +145,17 @@ open class TileGroup(var tileInfo: TileInfo, val tileSetStrings:TileSetStrings,
if (!tileInfo.isCityCenter()) return null
return super.hit(x, y, touchable)
}
init {
isTransform = false
setSize(groupSize, groupSize)
touchable = Touchable.childrenOnly
setOrigin(Align.center)
}
}
val cityButtonLayerGroup = CityButtonLayerGroupClass(tileInfo).apply { isTransform = false; setSize(groupSize, groupSize)
touchable = Touchable.childrenOnly; setOrigin(Align.center); }
val cityButtonLayerGroup = CityButtonLayerGroupClass(tileInfo, groupSize)
val highlightCrosshairFogLayerGroup = ActionlessGroup().apply { isTransform = false; setSize(groupSize, groupSize) }
val highlightFogCrosshairLayerGroup = ActionlessGroup(groupSize)
val highlightImage = ImageGetter.getImage(tileSetStrings.highlight) // for blue and red circles/emphasis on the tile
private val crosshairImage = ImageGetter.getImage(tileSetStrings.crosshair) // for when a unit is targeted
private val fogImage = ImageGetter.getImage(tileSetStrings.crosshatchHexagon )
@ -181,12 +193,11 @@ open class TileGroup(var tileInfo: TileInfo, val tileSetStrings:TileSetStrings,
this.addActor(baseLayerGroup)
this.addActor(terrainFeatureLayerGroup)
this.addActor(miscLayerGroup)
this.addActor(pixelMilitaryUnitGroup)
this.addActor(pixelCivilianUnitGroup)
this.addActor(unitLayerGroup)
this.addActor(cityButtonLayerGroup)
this.addActor(highlightCrosshairFogLayerGroup)
terrainFeatureLayerGroup.addActor(pixelMilitaryUnitGroup)
terrainFeatureLayerGroup.addActor(pixelCivilianUnitGroup)
this.addActor(highlightFogCrosshairLayerGroup)
updateTileImage(null)
@ -196,25 +207,25 @@ open class TileGroup(var tileInfo: TileInfo, val tileSetStrings:TileSetStrings,
isTransform = false // performance helper - nothing here is rotated or scaled
}
open fun clone(): TileGroup = TileGroup(tileInfo, tileSetStrings)
open fun clone() = TileGroup(tileInfo, tileSetStrings)
//region init functions
private fun addHighlightImage() {
highlightCrosshairFogLayerGroup.addActor(highlightImage)
highlightFogCrosshairLayerGroup.addActor(highlightImage)
setHexagonImageSize(highlightImage)
highlightImage.isVisible = false
}
private fun addFogImage() {
fogImage.color = Color.WHITE.cpy().apply { a = 0.2f }
highlightCrosshairFogLayerGroup.addActor(fogImage)
highlightFogCrosshairLayerGroup.addActor(fogImage)
setHexagonImageSize(fogImage)
}
private fun addCrosshairImage() {
crosshairImage.isVisible = false
highlightCrosshairFogLayerGroup.addActor(crosshairImage)
highlightFogCrosshairLayerGroup.addActor(crosshairImage)
setHexagonImageSize(crosshairImage)
}
//endregion
@ -544,7 +555,7 @@ open class TileGroup(var tileInfo: TileInfo, val tileSetStrings:TileSetStrings,
val sign = if (relativeWorldPosition.x < 0) -1 else 1
val angle = sign * (atan(sign * relativeWorldPosition.y / relativeWorldPosition.x) * 180 / PI - 90.0).toFloat()
val innerBorderImage = ImageGetter.getImage(
tileSetStrings.orFallback { getBorder(borderShapeString,"Inner") }
)
@ -568,7 +579,7 @@ open class TileGroup(var tileInfo: TileInfo, val tileSetStrings:TileSetStrings,
/** Create and setup Actors for all arrows to be drawn from this tile. */
private fun updateArrows() {
for (actorList in arrows.values)
for (actorList in arrows.values)
for (actor in actorList)
actor.remove()
arrows.clear()