Rimworld Mod制作教程12 集群AI机制介绍

简介

相信看过上一章关于AI的介绍,你应该对行为树的基础知识有一些了解,并且知道游戏中角色AI是基于行为树实现的。在这一章我会简单介绍一些集群AI是如何工作的。

什么是集群AI?

集群AI听起来好像很复杂,实际上集群AI就是一群角色使用同一个AI而已。
创建一个行为节点的实例,想办法让一群角色当前执行这个节点实例,这样就实现了集群AI。

RimWorld源码中有没有一个节点用于集群AI?

有的,RW中有一棵额外的行为树叫做“LordDuty”,其中一个被挂载的节点是“ThinkNode_JoinVoluntarilyJoinableLord”,具有很高的优先级
在这里插入图片描述
我们找到这颗行为树,会发现上面有个名字叫做 “ThinkNode_Duty”的节点,集群AI的实现就依赖于这个节点。这个节点涉及一套职责系统来动态改变AI,集群AI只是职责系统的其中一部分功能。
在这里插入图片描述

职责节点

职责节点遍历了整个数据库中的职责定义数据,将每个职责对应的行为树作为子节点挂载在职责节点上。也就是说职责节点在运行时会拥有全部的职责定义中的行为树实例。然后会尝试根据角色当前的职责来分配对应职责的行为。

using System;
using Verse;
using Verse.AI;
using Verse.AI.Group;

namespace RimWorld
{
	// Token: 0x02000E3F RID: 3647
	public class ThinkNode_Duty : ThinkNode
	{
		// Token: 0x060052AB RID: 21163 RVA: 0x001BF624 File Offset: 0x001BD824
		public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobParams)
		{
			if (pawn.GetLord() == null)
			{
				Log.Error(pawn + " doing ThinkNode_Duty with no Lord.", false);
				return ThinkResult.NoJob;
			}
			if (pawn.mindState.duty == null)
			{
				Log.Error(pawn + " doing ThinkNode_Duty with no duty.", false);
				return ThinkResult.NoJob;
			}
			return subNodes[pawn.mindState.duty.def.index].TryIssueJobPackage(pawn, jobParams);
		}

		// Token: 0x060052AC RID: 21164 RVA: 0x001BF69C File Offset: 0x001BD89C
		protected override void ResolveSubnodes()
		{
			foreach (DutyDef dutyDef in DefDatabase<DutyDef>.AllDefs)
			{
				//处理每一个职责定义的节点和子节点
				dutyDef.thinkNode.ResolveSubnodesAndRecur();
				//将所有职责中的节点设置为当前节点的子节点
				this.subNodes.Add(dutyDef.thinkNode.DeepCopy());
			}
		}
	}
}

基类方法

/// <summary>
/// 对自身和所有字节点调用ResolveSubnodes
/// </summary>
public void ResolveSubnodesAndRecur()
{
	if (this.uniqueSaveKeyInt != -2)
	{
		return;
	}
	this.ResolveSubnodes();
	for (int i = 0; i < this.subNodes.Count; i++)
	{
		this.subNodes[i].ResolveSubnodesAndRecur();
	}
}

集群AI如何与职责节点联系?

很简单,集群AI拥有一组受其控制的角色,遍历所有的角色,将他们的职责设定为集群AI的职责,之后由于职责节点的高优先级,所有的角色会优先执行同样的行为。

集群AI

说了半天还没有见到集群AI的模块,集群AI在RW种是以“Lord”命名的。
首先要明确一点,集群AI和行为树不一样,集群AI是需要的时候动态生成的,而不是一开始就配置在角色身上的。所以你可能会在事件中看到集群AI的生成,比如袭击事件。

/// <summary>
/// 创建集群AI
/// </summary>
/// <param name="parms"></param>
/// <param name="pawns"></param>
public virtual void MakeLords(IncidentParms parms, List<Pawn> pawns)
{
	Map map = (Map)parms.target;
	//分组
	List<List<Pawn>> list = IncidentParmsUtility.SplitIntoGroups(pawns, parms.pawnGroups);
	int @int = Rand.Int;
	//每一组生成一个集群AI
	for (int i = 0; i < list.Count; i++)
	{
		List<Pawn> list2 = list[i];
		//生成集群AI 设置目标为list2
		Lord lord = LordMaker.MakeNewLord(parms.faction, this.MakeLordJob(parms, map, list2, @int), map, list2);
		lord.inSignalLeave = parms.inSignalEnd;
		QuestUtility.AddQuestTag(lord, parms.questTag);
		if (DebugViewSettings.drawStealDebug && parms.faction.HostileTo(Faction.OfPlayer))
		{
			Log.Message(string.Concat(new object[]
			{
				"Market value threshold to start stealing (raiders=",
				lord.ownedPawns.Count,
				"): ",
				StealAIUtility.StartStealingMarketValueThreshold(lord),
				" (colony wealth=",
				map.wealthWatcher.WealthTotal,
				")"
			}), false);
		}
	}
}

集群AI的工作模式与AI的工作模式类似,集群AI有一个LordJob,LordJob对应N个LordToil,和Job,Toil的机制很相似。不同的是,LordJob的流程不像Job一样按顺序执行。
在上一章我有介绍过,游戏AI的实现基本使用行为树或状态机。而LordJob的流程是一套状态机的系统。他可以在任意流程之间跳转。行为树有一个缺点就是必须按顺序执行,而与状态机混搭的方式可以规避掉这个缺点。

/// <summary>
/// 创建集群AI工作
/// </summary>
/// <param name="parms"></param>
/// <param name="map"></param>
/// <param name="pawns"></param>
/// <param name="raidSeed"></param>
/// <returns></returns>
protected override LordJob MakeLordJob(IncidentParms parms, Map map, List<Pawn> pawns, int raidSeed)
{
	//事件中心或第一个角色的坐标
	IntVec3 originCell = parms.spawnCenter.IsValid ? parms.spawnCenter : pawns[0].PositionHeld;
	//事件派系与玩家敌对
	if (parms.faction.HostileTo(Faction.OfPlayer))
	{
		//突击殖民者
		return new LordJob_AssaultColony(parms.faction, true, true, false, false, true);
	}
	//非敌对派系 比如动物 尝试在殖民地外寻找随机点突袭
	IntVec3 fallbackLocation;
	RCellFinder.TryFindRandomSpotJustOutsideColony(originCell, map, out fallbackLocation);
	return new LordJob_AssistColony(parms.faction, fallbackLocation);
}

LordToil与Toil的关系?

每个LordJob对应N个LordToil,也就是集群AI的工作流程之一。而LordToil会对应一个Duty职责,从而对应了一颗行为树。一颗行为树上可能有M个节点对应Job,每个节点有K个流程Toil。所以一个LordJob上可能涉及NMK个Toil。

集群AI的状态机

举个复杂的例子,突袭的集群AI,突袭的集群AI包含,突袭、绑架、偷窃、工兵、离开等集群流程。
“stateGraph ”就是LordJob生成的状态机。添加全部流程后,还需要添加某两个流程之间的跳转和触发跳转的条件。 至于集群突袭流程具体就不详细介绍了,突袭Action节点很复杂,仅是寻找合适的目标就有大概300行代码,有兴趣的自己看看。
突袭流程优先级最高的节点并不是突袭节点,而是服用化学增强药物。当集群AI组内受伤人数达到一定数量时,组内角色就会尝试服用药物。

/// <summary>
/// 创建状态图
/// </summary>
/// <returns></returns>
public override StateGraph CreateGraph()
{
    var stateGraph = new StateGraph();
    LordToil lordToil = null;
    //工兵流程
    if (sappers)
    {
        //工兵行为流程
        lordToil = new LordToil_AssaultColonySappers();
        //使用智能躲避格子
        if (useAvoidGridSmart)
        {
            lordToil.useAvoidGrid = true;
        }
        //加入状态图
        stateGraph.AddToil(lordToil);
        //过渡
        var transition = new Transition(lordToil, lordToil, true);
        //触发器 角色丢失
        transition.AddTrigger(new Trigger_PawnLost());
        //添加过渡
        stateGraph.AddTransition(transition);
    }
    //突击行为流程
    LordToil lordToil2 = new LordToil_AssaultColony();
    if (useAvoidGridSmart)
    {
        lordToil2.useAvoidGrid = true;
    }
    stateGraph.AddToil(lordToil2);
    //离开地图流程
    var lordToilExitMap = new LordToil_ExitMap(LocomotionUrgency.Jog, false, true)
    {
        useAvoidGrid = true
    };
    stateGraph.AddToil(lordToilExitMap);
    if (sappers)
    {
        //过渡 工兵流程 到 突击流程
        var transition2 = new Transition(lordToil, lordToil2);
        //触发器 不存在作战工兵
        transition2.AddTrigger(new Trigger_NoFightingSappers());
        stateGraph.AddTransition(transition2);
    }
    //突击者派系是类人派系
    if (assaulterFaction.def.humanlikeFaction)
    {
        //可以超时或逃离
        if (canTimeoutOrFlee)
        {
            //过渡 突击流程 到 离开地图流程
            var transition3 = new Transition(lordToil2, lordToilExitMap);
            //工兵流程存在
            if (lordToil != null)
            {
                //储存工兵流程
                transition3.AddSource(lordToil);
            }
            //添加过渡触发器 超时 
            transition3.AddTrigger(new Trigger_TicksPassed(sappers
                ? SapTimeBeforeGiveUp.RandomInRange
                : AssaultTimeBeforeGiveUp.RandomInRange));
            //触发前回调
            transition3.AddPreAction(new TransitionAction_Message(
                "MessageRaidersGivenUpLeaving".Translate(this.assaulterFaction.def.pawnsPlural.CapitalizeFirst(),
                    this.assaulterFaction.Name), null, 1f));
            stateGraph.AddTransition(transition3);
            //过渡 突击流程 到 离开地图流程
            var transition4 = new Transition(lordToil2, lordToilExitMap);
            if (lordToil != null)
            {
                transition4.AddSource(lordToil);
            }
            //伤害承受比例 取0.25到0.35随机数
            var floatRange = new FloatRange(0.25f, 0.35f);
            var randomInRange = floatRange.RandomInRange;
            //触发器 达到承受比例 或超过900点
            transition4.AddTrigger(new Trigger_FractionColonyDamageTaken(randomInRange, 900f));
            //突击者造成破坏 心满意足离开
            transition4.AddPreAction(new TransitionAction_Message(
                "MessageRaidersSatisfiedLeaving".Translate(assaulterFaction.def.pawnsPlural.CapitalizeFirst(),
                    assaulterFaction.Name)));
            stateGraph.AddTransition(transition4);
        }
        //允许绑架
        if (canKidnap)
        {
            var startingToil = stateGraph.AttachSubgraph(new LordJob_Kidnap().CreateGraph()).StartingToil;
            var transition5 = new Transition(lordToil2, startingToil, false, true);
            if (lordToil != null)
            {
                transition5.AddSource(lordToil);
            }
            transition5.AddPreAction(new TransitionAction_Message(
                "MessageRaidersKidnapping".Translate(this.assaulterFaction.def.pawnsPlural.CapitalizeFirst(),
                    this.assaulterFaction.Name), null, 1f));
            transition5.AddTrigger(new Trigger_KidnapVictimPresent());
            stateGraph.AddTransition(transition5, false);
        }
        //允许偷窃
        if (canSteal)
        {
            var startingToil2 = stateGraph.AttachSubgraph(new LordJob_Steal().CreateGraph()).StartingToil;
            //过渡 突击 到 偷窃
            var transition6 = new Transition(lordToil2, startingToil2, false, true);
            if (lordToil != null)
            {
                transition6.AddSource(lordToil);
            }
            transition6.AddPreAction(new TransitionAction_Message(
                "MessageRaidersStealing".Translate(this.assaulterFaction.def.pawnsPlural.CapitalizeFirst(),
                    this.assaulterFaction.Name), null, 1f));
            //触发方式 有高价值物品在附近 300个tick内集群无成员受伤
            transition6.AddTrigger(new Trigger_HighValueThingsAround());
            stateGraph.AddTransition(transition6, false);
        }
    }
    //过渡 突击流程 到 离开地图流程
    var transition7 = new Transition(lordToil2, lordToilExitMap, false, true);
    if (lordToil != null)
    {
        transition7.AddSource(lordToil);
    }
    //触发方式 阵营关系变为非敌对
    transition7.AddTrigger(new Trigger_BecameNonHostileToPlayer());
    transition7.AddPreAction(new TransitionAction_Message(
        "MessageRaidersLeaving".Translate(this.assaulterFaction.def.pawnsPlural.CapitalizeFirst(), this.assaulterFaction.Name),
        null, 1f));
    stateGraph.AddTransition(transition7, false);
    return stateGraph;
}
  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值