游戏战斗系统中的"一切皆状态"设计
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. 总结与展望
"一切皆状态"设计为复杂战斗系统提供了一种高度统一的抽象方式,通过将各种战斗元素统一到状态概念下,极大地简化了系统设计和代码实现。这种设计的核心价值在于:
- 统一抽象:简化系统复杂度
- 组合能力:通过状态组合创造丰富玩法
- 可扩展性:轻松添加新功能而不影响现有系统
- 可维护性:代码结构清晰,逻辑明确
未来发展方向:
- 状态编辑器:可视化配置状态和效果
- 状态网络同步:高效同步状态变化
- 状态预测系统:客户端预测状态变化减少延迟感
- 状态持久化:支持游戏存档和断线重连
通过"一切皆状态"的设计理念,战斗系统可以在保持高度灵活性的同时,实现清晰的代码结构和简洁的实现逻辑,为复杂游戏战斗系统提供了一种优雅的解决方案。

浙公网安备 33010602011771号