RingGame 设计理念:从核心玩法到智能AI的架构演进
Part 1: 游戏灵感与核心规则
灵感来源
RingGame
是一款多人在线对战游戏,其核心玩法的灵感直接来源于独立游戏《恶魔轮盘》(Buckshot Roulette)。在原作中,玩家与AI庄家进行一对一的生死对决,围绕一把装填了未知数量实弹与空弹的左轮手枪展开博弈。我保留了这一核心机制的精髓——即通过概率计算、风险评估和心理博弈来决定生死——并将其扩展为多人在线对战模式。
游戏规则概述
核心机制
游戏以回合制进行,每个玩家在轮到自己时必须做出一个关键抉择:
开枪 (Shoot)
- 射击对手: 若为实弹,目标损失1点生命值;若为空弹,无事发生
- 射击自己: 若为实弹,自己损失1点生命值;若为空弹,不仅安然无恙,还将获得一个额外回合
使用道具 (Use Item)
- 窥视镜: 查看下一发子弹的类型
- 退弹饮: 直接退出当前子弹
- 狂暴剂: 下一次实弹射击伤害+1
- 护身符: 抵挡一次实弹伤害
游戏模式
- 经典混战: 各自为战,最后存活者获胜
- 团队对抗: 分为红蓝两队,消灭所有敌方成员获胜
- 背叛者模式: 生命值降至1点时成为背叛者,胜利条件变更为"淘汰任意2名玩家"或"成为最后存活的2人之一"
角色系统
每位玩家会被分配一个独特角色,拥有被动技能:
- 赌徒: 对自己射击空弹时,50%几率额外获得随机道具
- 医师: 护身符可抵挡2次伤害
- 庄家: 每轮子弹装填时预知第一发子弹类型
- 佣兵: 狂暴剂效果持续2次射击
设计挑战
这样一个充满不确定性和心理博弈的游戏,为AI设计带来了独特的挑战:
- 信息不完全: AI无法预知子弹序列,必须基于概率和当前局势做出决策
- 多重目标: 需要平衡生存、进攻、道具使用等多个目标
- 心理博弈: AI的行为模式需要足够"人性化",避免被对手轻易预测
- 角色差异: 不同角色需要表现出不同的行为倾向和策略偏好
传统的"if-else"或简单评分系统很难应对这些挑战,因此我们选择了更高级的AI架构。
Part 2: AI架构设计——为何选择GOAP?
为这样一个充满不确定性和心理博弈的游戏设计AI,是一个巨大的挑战。简单的"if-else"或评分系统很容易产生重复、可预测的行为。为了让AI表现得更像一个会"思考"的玩家,我们选择了 GOAP (Goal-Oriented Action Planning,目标导向行为规划) 架构。
GOAP的核心思想
GOAP的核心在于,我们不直接告诉AI"做什么",而是告诉它"想要什么"。AI系统包含三个核心部分:
- 世界状态 (World State):对当前游戏世界的认知,如
我的血量=3
, 敌人血量=2
, 我有护身符=true
。
- 目标 (Goals):AI希望达成的状态。例如,一个最高优先级的目标是
(isGameWon=true)
。
- 动作 (Actions):AI可以执行的操作。每个动作都有其 前置条件 (执行所需的状态) 和 效果 (执行后对世界状态的改变)。
规划器 (Planner) 会在运行时,根据当前的世界状态,自动寻找一个能够达成某个目标的、成本最低的动作序列(即"计划"),然后执行该计划的第一步。
RingGame中的GOAP实现
在我们的项目中,我们具体实现了以下GOAP组件:
- Goals:
WinGameGoal
(获胜), StayAliveGoal
(存活), EliminateEnemyGoal
(消灭敌人), HaveUsefulItemGoal
(获取道具)。
- Actions:
ShootSelfAction
, ShootOpponentAction
, UseScopeAction
, UseAmuletAction
, UseBerserkAgentAction
, UseEjectionDrinkAction
等。每个Action都精确定义了其执行条件和后果。
核心代码实现
1. 世界状态表示
/// <summary>
/// 表示GOAP系统中的世界状态,使用键值对存储各种状态信息
/// </summary>
public class GoapState : Dictionary<string, object>
{
/// <summary>
/// 检查当前状态是否满足指定的条件
/// </summary>
public bool Satisfies(GoapState conditions)
{
foreach (var condition in conditions)
{
if (!ContainsKey(condition.Key) || !this[condition.Key].Equals(condition.Value))
return false;
}
return true;
}
}
2. 目标定义
/// <summary>
/// GOAP目标的基类,定义了目标的基本属性和评估方法
/// </summary>
public abstract class IGoapGoal
{
/// <summary>
/// 目标的优先级,数值越高优先级越高
/// </summary>
public abstract int Priority { get; }
/// <summary>
/// 检查当前世界状态是否已经满足此目标
/// </summary>
public abstract bool IsSatisfied(GoapState worldState);
/// <summary>
/// 获取此目标期望的世界状态
/// </summary>
public abstract GoapState GetDesiredState();
}
/// <summary>
/// 获胜目标:游戏胜利
/// </summary>
public class WinGameGoal : IGoapGoal
{
public override int Priority => 100; // 最高优先级
public override bool IsSatisfied(GoapState worldState)
{
return worldState.ContainsKey("isGameWon") && (bool)worldState["isGameWon"];
}
public override GoapState GetDesiredState()
{
return new GoapState { ["isGameWon"] = true };
}
}
3. 动作定义
/// <summary>
/// GOAP动作的基类,定义了动作的前置条件、效果和成本
/// </summary>
public abstract class IGoapAction
{
/// <summary>
/// 动作的前置条件
/// </summary>
public abstract GoapState Preconditions { get; }
/// <summary>
/// 动作执行后的效果
/// </summary>
public abstract GoapState Effects { get; }
/// <summary>
/// 动作的成本,用于规划器选择最优路径
/// </summary>
public abstract int Cost { get; }
/// <summary>
/// 检查动作是否可以在当前状态下执行
/// </summary>
public virtual bool CanExecute(GoapState worldState)
{
return worldState.Satisfies(Preconditions);
}
}
/// <summary>
/// 对自己开枪的动作
/// </summary>
public class ShootSelfAction : IGoapAction
{
public override GoapState Preconditions => new GoapState
{
["isMyTurn"] = true,
["hasBullets"] = true
};
public override GoapState Effects => new GoapState
{
["isMyTurn"] = false,
["hasBullets"] = false,
["myHealth"] = "decreased", // 可能减少生命值
["extraTurn"] = "possible" // 可能获得额外回合
};
public override int Cost => 10; // 基础成本
}
4. 角色特定的动作成本调整
/// <summary>
/// 赌徒角色的动作成本调整器
/// </summary>
public class GamblerActionCostModifier : IActionCostModifier
{
public int ModifyCost(IGoapAction action, GoapState worldState)
{
// 赌徒更愿意对自己开枪
if (action is ShootSelfAction)
{
return action.Cost - 5; // 降低5点成本
}
// 赌徒也更愿意使用狂暴剂
if (action is UseBerserkAgentAction)
{
return action.Cost - 3;
}
return action.Cost;
}
}
/// <summary>
/// 医师角色的动作成本调整器
/// </summary>
public class MedicActionCostModifier : IActionCostModifier
{
public int ModifyCost(IGoapAction action, GoapState worldState)
{
// 医师更愿意使用护身符
if (action is UseAmuletAction)
{
return action.Cost - 4;
}
// 医师更不愿意对自己开枪
if (action is ShootSelfAction)
{
return action.Cost + 3; // 增加3点成本
}
return action.Cost;
}
}
5. GOAP规划器
/// <summary>
/// GOAP规划器,负责从当前状态到目标状态的路径规划
/// </summary>
public class GoapPlanner
{
private readonly List<IGoapAction> _availableActions;
public GoapPlanner(List<IGoapAction> actions)
{
_availableActions = actions;
}
/// <summary>
/// 规划从当前状态到目标状态的最优动作序列
/// </summary>
public List<IGoapAction> Plan(GoapState currentState, GoapState goalState)
{
var plan = new List<IGoapAction>();
var current = new GoapState(currentState);
// 使用A*算法寻找最优路径
var path = FindPath(current, goalState);
if (path != null)
{
// 提取动作序列
for (int i = 0; i < path.Count - 1; i++)
{
var action = GetActionBetweenStates(path[i], path[i + 1]);
if (action != null)
plan.Add(action);
}
}
return plan;
}
private IGoapAction GetActionBetweenStates(GoapState from, GoapState to)
{
return _availableActions.FirstOrDefault(action =>
action.CanExecute(from) &&
WouldResultInState(from, action, to));
}
}
6. AI决策流程
/// <summary>
/// AI角色,整合GOAP规划器和角色特定的行为调整
/// </summary>
public class AICharacter
{
private readonly GoapPlanner _planner;
private readonly List<IGoapGoal> _goals;
private readonly List<IGoapAction> _actions;
private readonly IActionCostModifier _costModifier;
public AICharacter(RoleType role)
{
_goals = InitializeGoals();
_actions = InitializeActions();
_planner = new GoapPlanner(_actions);
_costModifier = CreateCostModifier(role);
}
/// <summary>
/// 根据当前游戏状态决定下一步行动
/// </summary>
public GameAction DecideAction(GameState gameState)
{
// 1. 将游戏状态转换为GOAP世界状态
var worldState = ConvertToGoapState(gameState);
// 2. 选择最高优先级的目标
var goal = SelectBestGoal(worldState);
// 3. 规划达成目标的动作序列
var plan = _planner.Plan(worldState, goal.GetDesiredState());
// 4. 执行计划的第一步
if (plan.Count > 0)
{
return ConvertToGameAction(plan[0], gameState);
}
return GameAction.Skip(); // 默认跳过
}
private IGoapGoal SelectBestGoal(GoapState worldState)
{
return _goals
.Where(g => !g.IsSatisfied(worldState))
.OrderByDescending(g => g.Priority)
.FirstOrDefault();
}
}
利用GOAP实现AI角色多样性
GOAP架构最优雅的一点,就是它能以极低的成本实现行为多样性。我们通过调整GOAP的参数,而不是重写代码,来塑造不同角色的"性格":
赌徒 (Gambler)
- 实现方式: 大幅降低
ShootSelfAction
的基础 成本(Cost)。这意味着在规划器的计算中,"对自己开枪"这个选项总是显得非常"划算",使赌徒AI更倾向于冒险以获取额外回合。
医师 (Medic)
- 实现方式: 提高
StayAliveGoal
的 优先级,并降低 UseAmuletAction
的 成本。这使得医师AI在做决策时,会优先考虑任何能提升自己或队友生存率的计划。
庄家 (Dealer)
- 实现方式:
UseScopeAction
的 成本 极低,且 HaveUsefulItemGoal
(持有道具)的优先级高。AI会倾向于优先使用窥视镜来获取信息,并基于确切信息来制定后续计划,其行为模式表现为"谋定而后动"。
佣兵 (Mercenary)
- 实现方式: 赋予
EliminateEnemyGoal
极高的 优先级,同时降低 ShootOpponentAction
和 UseBerserkAgentAction
的 成本。这让它的行为模型高度聚焦于"如何最快地对敌人造成伤害"。
通过这种方式,我们不仅构建了一个能做出合理决策的AI,更构建了一个能模拟不同玩家风格、为游戏提供丰富挑战和持久乐趣的AI系统。