游戏战斗系统中的"一切皆状态"设计

1. 引言

在当今复杂的游戏开发环境中,战斗系统常常是游戏的核心玩法之一。随着玩家对游戏体验要求的提高,战斗系统也变得越来越复杂,开发者需要面对各种挑战:技能多样化、状态繁杂、交互效果增多、平衡性难以调整等。

传统战斗系统通常采用分离式设计,将战斗元素划分为截然不同的概念:技能系统、Buff系统、属性系统、触发器系统等。这种设计在系统简单时工作良好,但随着系统复杂度增加,各子系统间的耦合越来越严重,代码维护成本急剧上升。

例如,当设计一个"受伤时反击"的效果时,传统方法可能需要在伤害计算函数中添加特殊判断,检查单位是否具有反击效果,然后触发反击逻辑。而当需求变更为"被火系伤害时反击",又需要修改代码增加伤害类型判断。这种设计导致代码中充斥着复杂的条件判断和特例处理。

"一切皆状态"设计理念应运而生,它将战斗系统中的各种元素抽象为统一的状态概念,通过组合不同状态实现复杂功能,大幅提升系统的可维护性和扩展性。

2. 战斗系统基础架构

战斗系统的核心组件

一个完整的战斗系统通常包括以下核心组件:

- 战斗管理器:负责战斗流程控制和状态管理
- 战斗单位:战场上的参与者,包括角色、怪物等
- 技能系统:定义各种主动和被动技能
- 属性系统:管理单位的各种属性数值
- Buff系统:处理持续性效果
- 触发器系统:响应战斗中的各种事件
- 战报系统:记录战斗过程和结果

战斗流程划分

典型的战斗流程可分为以下阶段:

1. 战前准备阶段
- 战斗初始化:创建战场环境、设置随机种子
- 单位准备:加载参战单位数据,初始化状态
- 触发器注册:设置各种事件监听

2. 战斗执行阶段
- 回合开始处理:Buff效果、状态更新
- 行动顺序决定:基于速度和先攻值
- 单位行动执行:技能选择与释放
- 效果计算:伤害、治疗、状态应用
- 回合结束处理:检查胜负条件

3. 战斗结算阶段
- 胜负判定:根据战场状态确定结果
- 战报生成:汇总战斗数据
- 奖励计算:基于战斗结果分配奖励

4. 战后处理阶段
- 数据持久化:保存战报、更新统计
- 清理资源:释放战斗对象
- 触发后续逻辑:任务进度、成就等

3. 触发器系统设计

触发器系统是连接战斗各个环节的核心机制,也是实现"一切皆状态"的基础。

触发器基本概念与作用

触发器本质上是一种观察者模式的实现,由三部分组成:
- 触发点:战斗流程中的特定时刻或事件
- 触发条件:决定是否执行处理函数的判断逻辑
- 处理函数:满足条件时执行的具体操作

// 触发器结构
type Trigger struct {
    ID          string             // 唯一标识
    Type        string             // 触发类型
    Priority    int                // 优先级
    Condition   TriggerCondition   // 触发条件
    Handler     TriggerHandler     // 处理函数
    IsOnce      bool               // 是否只触发一次
}

触发点设计

战斗中的关键触发点例如:

// 常见触发点
const (
    TriggerRoundStart       = "round_start"   // 回合开始
    TriggerRoundEnd         = "round_end"     // 回合结束
    TriggerBeforeAction     = "before_action" // 行动前
    TriggerAfterAction      = "after_action"  // 行动后
    TriggerHpChanged        = "hp_changed"    // 血量变化
    TriggerBeforeDamage     = "before_damage" // 伤害前
    TriggerAfterDamage      = "after_damage"  // 伤害后
    TriggerBuffAdded        = "buff_added"    // Buff添加
)

实际应用示例

例如,实现一个"受到伤害时10%几率反击"的效果:

// 注册反击触发器
battle.TriggerManager.Register(&Trigger{
    Type: TriggerAfterDamage,
    Condition: func(ctx *TriggerContext) bool {
        // 检查是否被攻击且在近身范围
        return ctx.Target == unit && 
               IsInMeleeRange(unit, ctx.Source) &&
               RollChance(10) // 10%几率
    },
    Handler: func(ctx *TriggerContext) bool {
        // 执行反击
        damage := CalculateCounterAttackDamage(unit, ctx.Source)
        ctx.Source.ChangeHp(-damage, unit, "counter")
        
        // 记录反击效果到战报
        unit.Battle().ReportCounterAttack(unit, ctx.Source, damage)
        return true
    },
    Owner: unit,
})

4. "一切皆状态"设计理念

状态的核心概念

"一切皆状态"将战斗中的所有效果抽象为状态:
- 状态:对游戏对象的一种持续性影响
- 状态管理器:负责状态的添加、删除和更新
- 状态槽:承载状态的容器,可以限制特定类型的状态数量

状态与传统设计的对比

传统设计中,Buff、技能效果、控制效果等是不同的概念:
- Buff系统处理持续效果
- 技能系统处理技能释放
- 控制系统处理行动限制

而在"一切皆状态"设计中:
- Buff是一种修改属性的状态
- 技能是状态转换的触发器
- 控制效果是一种限制行动的状态

状态的分类

根据效果可将状态分为:
1. 属性修改型:增强攻击力、降低防御力等
2. 行为限制型:眩晕、沉默、缠绕等
3. 持续效果型:持续伤害、持续恢复等
4. 触发器型:在特定条件下触发效果
5. 形态转换型:改变单位的基本属性和行为方式

判断状态的规则

一个行为或效果应被定义为状态的判断标准:
1. 持续性:效果在一段时间内有效
2. 可叠加/可移除性:效果可被移除或叠加
3. 影响对象状态:改变对象属性或行为
4. 有明确生命周期:有清晰的开始和结束条件

5. 状态系统的实现

状态基础结构

// 状态接口
type State interface {
    ID() string            // 状态唯一标识
    Type() StateType       // 状态类型
    Priority() int         // 状态优先级
    Duration() int         // 持续回合数
    OnAdd(unit *Unit)      // 添加时调用
    OnRemove(unit *Unit)   // 移除时调用
    OnUpdate(unit *Unit)   // 更新时调用
    CanStack() bool        // 是否可叠加
    StackCount() int       // 叠加层数
}

// 状态管理器
type StateManager struct {
    unit        *Unit              // 所属单位
    states      map[string]State   // 当前状态列表
    statesByType map[StateType][]State // 按类型索引的状态
}

  

状态生命周期管理

// 添加状态
func (sm *StateManager) AddState(state State) bool {
    // 检查是否存在同类型更高优先级的状态
    // 处理状态叠加逻辑
    // 调用状态的OnAdd方法
}

// 移除状态
func (sm *StateManager) RemoveState(stateID string) {
    // 找到对应状态
    // 调用状态的OnRemove方法
    // 从状态列表中移除
}

// 更新所有状态
func (sm *StateManager) UpdateStates() {
    // 遍历所有状态
    // 减少持续时间
    // 移除到期状态
    // 调用状态的OnUpdate方法
}

将触发器转换为状态

触发器可以被实现为一种特殊的状态:

// 触发器状态
type TriggerState struct {
    BaseState
    triggerType string
    condition   func(*TriggerContext) bool
    effect      func(*TriggerContext)
}

func (ts *TriggerState) OnAdd(unit *Unit) {
    // 注册触发器到战斗系统
    unit.Battle().RegisterTrigger(ts.triggerType, unit, ts)
}

func (ts *TriggerState) OnRemove(unit *Unit) {
    // 从战斗系统中移除触发器
    unit.Battle().UnregisterTrigger(ts.triggerType, unit, ts)
}

6. 典型战斗元素的状态化实现

Buff的状态化实现

// 属性修改状态
type AttributeModifierState struct {
    BaseState
    attrType   AttributeType
    value      float64
    isPercent  bool
}

func (ams *AttributeModifierState) OnAdd(unit *Unit) {
    if ams.isPercent {
        unit.AddAttributePercentModifier(ams.attrType, ams.value)
    } else {
        unit.AddAttributeFixedModifier(ams.attrType, ams.value)
    }
}

func (ams *AttributeModifierState) OnRemove(unit *Unit) {
    if ams.isPercent {
        unit.RemoveAttributePercentModifier(ams.attrType, ams.value)
    } else {
        unit.RemoveAttributeFixedModifier(ams.attrType, ams.value)
    }
}

  

技能的状态化实现

技能可以看作多个状态的组合:

// 技能施法状态
type SkillCastingState struct {
    BaseState
    skillID   string
    castTime  int
    onComplete func()
}

func (scs *SkillCastingState) OnAdd(unit *Unit) {
    // 设置单位为施法状态
    unit.SetCasting(true)
}

func (scs *SkillCastingState) OnUpdate(unit *Unit) {
    // 减少施法时间
    scs.castTime--
    if scs.castTime <= 0 {
        // 施法完成
        unit.SetCasting(false)
        if scs.onComplete != nil {
            scs.onComplete()
        }
        // 移除施法状态
        unit.StateManager.RemoveState(scs.ID())
    }
}

func (scs *SkillCastingState) OnRemove(unit *Unit) {
    // 清除施法状态
    unit.SetCasting(false)
}

复杂联动效果的实现

例如,实现"暴击时附加燃烧效果":

// 暴击时触发燃烧的状态
type CritBurnState struct {
    BaseState
    burnDuration int
    burnDamage   int
}

func (cbs *CritBurnState) OnAdd(unit *Unit) {
    // 注册暴击触发器
    triggerState := &TriggerState{
        triggerType: TriggerAttackCrit,
        condition: func(ctx *TriggerContext) bool {
            return ctx.Source == unit
        },
        effect: func(ctx *TriggerContext) {
            // 添加燃烧状态到目标
            burnState := &BurnState{
                duration: cbs.burnDuration,
                damage: cbs.burnDamage,
            }
            ctx.Target.StateManager.AddState(burnState)
        },
    }
    unit.StateManager.AddState(triggerState)
}

7. 案例分析

案例一:眩晕触发伤害联动

需求:当角色眩晕敌人时,造成额外100%攻击力的伤害

// 眩晕伤害被动状态
type StunDamagePassiveState struct {
    BaseState
    damagePercent float64
}

func (s *StunDamagePassiveState) OnAdd(unit *Unit) {
    triggerState := &TriggerState{
        triggerType: TriggerStun,
        condition: func(ctx *TriggerContext) bool {
            return ctx.Source == unit
        },
        effect: func(ctx *TriggerContext) {
            // 计算额外伤害
            attackDamage := unit.GetAttack()
            extraDamage := int(float64(attackDamage) * s.damagePercent)
            
            // 造成额外伤害
            ctx.Target.ChangeHp(-extraDamage, unit, "stun_bonus")
        },
    }
    
    unit.StateManager.AddState(triggerState)
}

案例二:连击系统

需求:每次攻击增加连击计数,达到3次连击时释放强力攻击

// 连击状态
type ComboState struct {
    BaseState
    count int
    maxCombo int
    comboTimeout int
    currentTimeout int
}

func (cs *ComboState) OnAdd(unit *Unit) {
    // 注册攻击后触发器
    triggerState := &TriggerState{
        triggerType: TriggerAttack,
        condition: func(ctx *TriggerContext) bool {
            return ctx.Source == unit
        },
        effect: func(ctx *TriggerContext) {
            cs.count++
            cs.currentTimeout = cs.comboTimeout
            
            // 达到连击条件
            if cs.count >= cs.maxCombo {
                // 释放强力攻击
                comboAttackDamage := unit.GetAttack() * 2
                ctx.Target.ChangeHp(-comboAttackDamage, unit, "combo")
                
                // 重置连击计数
                cs.count = 0
            }
        },
    }
    unit.StateManager.AddState(triggerState)
    
    // 注册回合结束触发器,用于超时重置连击
    timeoutTrigger := &TriggerState{
        triggerType: TriggerRoundEnd,
        condition: func(ctx *TriggerContext) bool {
            return true
        },
        effect: func(ctx *TriggerContext) {
            cs.currentTimeout--
            if cs.currentTimeout <= 0 && cs.count > 0 {
                cs.count = 0 // 超时重置连击
            }
        },
    }
    unit.StateManager.AddState(timeoutTrigger)
}

案例三:变身系统

需求:角色可以变身为狼人形态,增加攻击力和速度,但降低防御力

// 变身状态
type WerewolfFormState struct {
    BaseState
    duration int
}

func (wfs *WerewolfFormState) OnAdd(unit *Unit) {
    // 添加形态属性修改
    unit.StateManager.AddState(&AttributeModifierState{
        attrType: AttrAttack,
        value: 50,
        isPercent: false,
    })
    
    unit.StateManager.AddState(&AttributeModifierState{
        attrType: AttrSpeed,
        value: 0.3,
        isPercent: true,
    })
    
    unit.StateManager.AddState(&AttributeModifierState{
        attrType: AttrDefense,
        value: -0.2,
        isPercent: true,
    })
    
    // 改变外观
    unit.SetModel("werewolf")
}

func (wfs *WerewolfFormState) OnRemove(unit *Unit) {
    // 变回人形
    unit.StateManager.RemoveState("attr_mod_attack")
    unit.StateManager.RemoveState("attr_mod_speed")
    unit.StateManager.RemoveState("attr_mod_defense")
    
    // 恢复外观
    unit.SetModel("human")
}

8. 性能优化

空间划分优化

对于大规模战斗,如多人AOE技能场景,可以使用空间划分提升性能:

// 网格系统
type BattleGrid struct {
    cellSize  float32
    gridWidth int
    gridHeight int
    cells     [][]*list.List
}

// 获取范围内单位
func (grid *BattleGrid) GetUnitsInRadius(x, y, radius float32) []*Unit {
    minCellX := int((x - radius) / grid.cellSize)
    maxCellX := int((x + radius) / grid.cellSize)
    minCellY := int((y - radius) / grid.cellSize)
    maxCellY := int((y + radius) / grid.cellSize)
    
    // 限制边界
    // 遍历对应网格查找单位
    // 返回结果
}

批处理优化

对于相同类型的多个效果,可以采用批处理:

// 批量处理持续伤害
func (battle *Battle) ProcessDotEffects() {
    dotEffects := make(map[*Unit][]*DotEffect)
    
    // 收集所有持续伤害效果
    for _, fighter := range battle.fighters {
        for _, unit := range fighter.GetAllUnits() {
            for _, state := range unit.StateManager.GetStatesByType(StateDot) {
                dotState, ok := state.(*DotState)
                if !ok {
                    continue
                }
                target := dotState.GetTarget()
                dotEffects[target] = append(dotEffects[target], &DotEffect{
                    Source: unit,
                    Damage: dotState.GetDamage(),
                    Type: dotState.GetDamageType(),
                })
            }
        }
    }
    
    // 批量应用伤害
    for target, effects := range dotEffects {
        totalDamage := 0
        for _, effect := range effects {
            totalDamage += effect.Damage
        }
        if totalDamage > 0 {
            target.ChangeHp(-totalDamage, nil, "dot")
        }
    }
}

对象池优化

减少对象创建和GC压力:

// 触发器上下文对象池
var triggerCtxPool = sync.Pool{
    New: func() interface{} {
        return &TriggerContext{}
    },
}

// 获取上下文
func GetTriggerContext() *TriggerContext {
    return triggerCtxPool.Get().(*TriggerContext)
}

// 释放上下文
func ReleaseTriggerContext(ctx *TriggerContext) {
    // 重置字段
    ctx.Source = nil
    ctx.Target = nil
    ctx.Value = 0
    ctx.Extra = nil
    
    triggerCtxPool.Put(ctx)
}

9. 设计优势与适用场景

代码组织与可维护性

"一切皆状态"设计的核心优势:

  • 统一接口:所有效果都通过相同的状态接口处理
  • 简化逻辑:避免复杂的条件判断和分支处理
  • 减少耦合:各状态相互独立,通过事件通信

系统扩展性

  • 添加新效果:只需创建新的状态类型,无需修改核心系统
  • 状态组合:通过组合不同状态创造复杂效果
  • 配置驱动:状态可以通过配置文件定义,便于策划调整

适用游戏类型

  • 回合制RPG:详细的战斗过程和复杂的状态系统
  • MOBA/ARPG:需要大量技能和效果组合
  • 卡牌游戏:各种卡牌效果可以通过状态实现
  • 策略游戏:单位状态和各种增益效果

10. 总结与展望

"一切皆状态"设计为复杂战斗系统提供了一种高度统一的抽象方式,通过将各种战斗元素统一到状态概念下,极大地简化了系统设计和代码实现。这种设计的核心价值在于:

  • 统一抽象:简化系统复杂度
  • 组合能力:通过状态组合创造丰富玩法
  • 可扩展性:轻松添加新功能而不影响现有系统
  • 可维护性:代码结构清晰,逻辑明确

未来发展方向:

  • 状态编辑器:可视化配置状态和效果
  • 状态网络同步:高效同步状态变化
  • 状态预测系统:客户端预测状态变化减少延迟感
  • 状态持久化:支持游戏存档和断线重连

通过"一切皆状态"的设计理念,战斗系统可以在保持高度灵活性的同时,实现清晰的代码结构和简洁的实现逻辑,为复杂游戏战斗系统提供了一种优雅的解决方案。

posted @ 2025-04-02 10:30  王鹏鑫  阅读(358)  评论(0)    收藏  举报