You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的生命值?为什么内部类的状态无法持久化?


原因分析

核心问题有两个:

  1. Enemy实现类的currentHealth实现错误:如果MarcusKnight这类Enemy实现类中,currentHealth是通过自定义getter每次返回新的Health实例(比如override val currentHealth: Health get() = Health(10000)),那么每次访问currentHealth都会得到全新的Health对象——你调用hurtBy()修改的只是临时对象的状态,方法结束后该对象被销毁,下次访问又会拿到新实例,状态自然回滚。
  2. 不可变状态的更新逻辑错误: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正确感知变化:

  1. 修改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)
    }
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 09:43:10