Jetpack Compose中Enemy内部Health类状态无法更新的问题
我正在用Jetpack Compose开发一款小型游戏,实现了管理生命值的Health类,代码如下:
class Health(private var maxHealth: Long) { private var _currentHealth = maxHealth val currentHealth: Long get() = _currentHealth val currentMaxHealth: Long get() = maxHealth fun applyAction(hp: Long, action: Action): Long { when (action) { Action.HURT -> _currentHealth -= hp Action.HEAL -> _currentHealth = max(maxHealth, _currentHealth + hp) Action.INCREASE_MAX_HP -> maxHealth += hp } return currentHealth } fun hurtBy(hp: Long): Long { return applyAction(hp, Action.HURT) } fun healBy(hp: Long): Long { return applyAction(hp, Action.HEAL) } fun increaseMaxHpBy(hp: Long): Long { return applyAction(hp, Action.INCREASE_MAX_HP) } enum class Action { HURT, HEAL, INCREASE_MAX_HP } }
调用hurtBy方法更新Enemy生命值时,状态无法持久化——调试显示Health内部状态会临时更新,但方法结束后又恢复原状。
我的ViewModel用MutableStateFlow管理状态,相关代码:
//ViewModel Class private val _state = MutableStateFlow(Storage()) val state = _state.asStateFlow() private val data get() = _state.value fun attack() { var damageToEnemy = data.player.weapon.damage.rollDamage() if (data.enemy is MarcusKnight) { damageToEnemy - if ((data.enemy as MarcusKnight).defending) (data.enemy.currentArmor * 1.5).toLong() else data.enemy.currentArmor } damageToEnemy = max(damageToEnemy, 0) data.enemy.currentHealth.hurtBy(damageToEnemy) //<--问题出在这里 updateBattleText("You attacking by $damageToEnemy damage") nextTurn() } data class Storage( val state: State = State.EXPLORING, val currentLocation: Location = Garden(), val player: Player = Player(), val currentText: String = "", val enemy: Enemy = MarcusKnight(), val playersTurn: Boolean = true )
Enemy是一个接口:
interface Enemy { val name: String val currentWeapon: Weapon val currentHealth: Health val currentArmor: Long val inventory: MutableList<Item> fun rollTurn(): EnemyTurn fun defend() fun claim(): Enemy enum class EnemyTurn { ATTACK, DEFEND, NONE, USE_ITEM } }
我试过这样更新状态,但没用:
_state.update { it.copy(enemy = data.enemy.apply { currentHealth.hurtBy(damageToEnemy) }) }
测试发现Enemy攻击Player时,Player的Health状态能正常更新:
Enemy.EnemyTurn.ATTACK -> { val playerArmor = if (data.player.isDefending) (data.player.armor * 1.5).toLong() else data.player.armor val damage = data.enemy.currentWeapon.damage.rollDamage() - playerArmor data.player.health.hurtBy(damage) //生命值正常变化 updateBattleText("${data.enemy.name} attacks you by $damage") }
Player类代码:
class Player { var isDefending = false var weapon: Weapon = HeroSword() val health: Health = Health(5000) val armor: Long = 100 private val inventory = mutableListOf<Item>() val playerInventory get() = inventory fun addItem(item: Item) { inventory.add(item) } fun useItem(item: Item): ConsumableEffect? { if (item !is Consumable) return null return if (inventory.remove(item)) item.consume() else null } fun dropItem(item: Item) { inventory.remove(item) } fun defend() {isDefending = true} fun stopDefending() { isDefending = false} }
我还试过把Enemy改成抽象类,将currentHealth设为非抽象属性时状态更新正常,但这不是理想实现:
abstract class Enemy { abstract val name: String abstract val currentWeapon: Weapon val currentHealth: Health = Health(10000) // <-- 正常工作 // abstract val currentHealth: Health // <-- 不工作 abstract val currentArmor: Long abstract val inventory: MutableList<Item> abstract fun rollTurn(): EnemyTurn (...) }
请问如何正确更新Enemy的生命值?为什么内部类的状态无法持久化?
核心问题有两个:
Enemy实现类的currentHealth实现错误:如果MarcusKnight这类Enemy实现类中,currentHealth是通过自定义getter每次返回新的Health实例(比如override val currentHealth: Health get() = Health(10000)),那么每次访问currentHealth都会得到全新的Health对象——你调用hurtBy()修改的只是临时对象的状态,方法结束后该对象被销毁,下次访问又会拿到新实例,状态自然回滚。- 不可变状态的更新逻辑错误:
Storage是data class(不可变),MutableStateFlow依赖实例引用变化来感知状态更新。即使currentHealth是同一个实例,修改其内部状态后,Storage和Enemy的引用都没有变化,StateFlow不会触发UI更新,且若有其他地方重新创建Enemy实例,状态会丢失。
反观Player类正常工作的原因:Player是普通类,health是固定成员变量,每次访问都是同一个Health实例,修改后状态能保留;且UI若直接监听player.health.currentHealth,能获取到内部状态变化。
方案1:修正Enemy实现类的currentHealth定义
确保Enemy实现类(如MarcusKnight)的currentHealth是成员变量,而非每次返回新实例的getter:
data class MarcusKnight( override val name: String = "Marcus Knight", override val currentWeapon: Weapon = EnemySword(), override val currentHealth: Health = Health(10000), // 声明为成员变量,固定实例 override val currentArmor: Long = 200, override val inventory: MutableList<Item> = mutableListOf() ) : Enemy { // ...其他方法实现 }
方案2:遵循不可变状态设计(推荐)
将Health改为不可变data class,每次修改都生成新实例,同时更新Enemy和Storage的引用,让StateFlow正确感知变化:
- 修改
Health类:
data class Health(val currentHealth: Long, val maxHealth: Long) { fun hurtBy(hp: Long): Health { return copy(currentHealth = max(currentHealth - hp, 0)) } fun healBy(hp: Long): Health { return copy(currentHealth = min(currentHealth + hp, maxHealth)) } fun increaseMaxHpBy(hp: Long): Health { return copy(maxHealth = maxHealth + hp, currentHealth = currentHealth + hp) } }
- 在ViewModel中正确更新状态:
fun attack() { var damageToEnemy = data.player.weapon.damage.rollDamage() if (data.enemy is MarcusKnight) { damageToEnemy = damageToEnemy - if ((data.enemy as MarcusKnight).defending) (data.enemy.currentArmor * 1.5).toLong() else data.enemy.currentArmor } damageToEnemy = max(damageToEnemy, 0) // 生成新的Enemy实例,包含更新后的Health val updatedEnemy = (data.enemy as MarcusKnight).copy( currentHealth = data.enemy.currentHealth.hurtBy(damageToEnemy) ) // 更新StateFlow的Storage实例 _state.update { it.copy(enemy = updatedEnemy) } updateBattleText("You attacking by $damageToEnemy damage") nextTurn() }
方案3:强制触发StateFlow更新(不推荐)
如果坚持使用可变Health类,在修改状态后手动强制StateFlow更新(不符合不可变状态设计原则,仅作临时 workaround):
data.enemy.currentHealth.hurtBy(damageToEnemy) // 强制触发更新,即使Storage引用没变 _state.value = _state.value
状态无法持久化的根本原因是:要么Enemy实现类每次返回新的Health实例,要么没有遵循不可变状态的更新逻辑,导致StateFlow无法感知状态变化。推荐使用不可变状态设计,确保每次修改都生成新实例,这样状态能稳定持久化,UI也能正确更新。
内容的提问来源于stack exchange,提问作者AdisAlagic

