Categories

Tags

Table of Contents

  1. 计划执行器(Plan Runner)

计划执行器(Plan Runner)

从规划器得到解决方案后,会尝试按顺序执行每个行动,并把行动效果应用到世界状态上。如果任务执行失败,计划也会失败,并且强制规划器重新规划。因为在实际执行过程中,世界状态不在是静态的,收诸多因素的影响,可能会发生变化,导致计划中的某项行动的前提条件不在满足,从而任务失败。

简单的实现,暂不考虑其它功能

require "HTN.Core.Planner"

local PlanRunner = {}


function PlanRunner:DoPlan (_ws,_plan,_planer)
	for i, a in ipairs(_plan) do
		ApplyActionEffects(_ws,a)
	end
	--删除执行完毕后的世界状态
	PrintWorldState(_ws,0)
end

return PlanRunner

实际执行计划的过程中,需要考虑行动的执行条件是否满足,因为世界状态不像规划过程时那样是静态,相反,是无时无刻发生变化的,而且需要在执行失败后重新进行规划。

另外,行动的执行不是即时完成的,有些行动需要花时间完成,如移动,动画播放等。

还有就是可以给行动设置最大执行时间,超过执行时间则意味着执行失败。

因此,执行器的进一步实现是,变成异步响应。

改进后,变为由行动自主通知,行动执行完毕后调用End就可以

local PlanRunner = {}


function PlanRunner:DoPlan (_ws,_plan,_planer)
	self.Plan = _plan
	self.WS = _ws
	self:DoAction(1)
end

function PlanRunner:DoAction(_index)
	local a = self.Plan[_index]
	if IsFitPreconds(self.WS,a.preconds) then
		self.curIndex = _index
		print("执行行为"..a.name)

		--执行完毕后回调 FinishAction
		a.End = function()
			self:FinishAction(_index)
		end
		a:Execute(self.WS)
		--可以添加到计时队列,判断是否超时,来终止计划

	else
		print("条件不满足,计划终止")
		return
	end
end

function PlanRunner:FinishAction(_index)
	local a = self.Plan[_index]
	ApplyActionEffects(self.WS,a)

	if _index == #self.Plan then
		-- 完成最后一个任务
		PrintWorldState(self.WS,0)
	else
		-- 开始下一个任务
		self:DoAction(_index + 1)
	end
end

return PlanRunner

Action的定义也做一些小改动

Actions.Move = function(...)
	local input = {...}
	local env = setmetatable(
		{
			f = input[1],
			t = input[2]
		},
		{__index = _G}
	)
	local action = {}
	action.Effects = {
		positive = {
			Substitute("BoatAt([t])",env)
		},
		negative = {
			Substitute("BoatAt([f])",env)
		}
	}
	function action:Execute(_ws)
		-- TODO 实现
		self.End()
	end
	return action
end

打印输出解决方案的执行结果

----当前世界状态 { BoatAt LocA ,LocA.M = 3,LocA.C = 3 ,LocB.M = 0,LocB.C = 0 }
解决方案步数	33
执行行为Load(LocA,1,1)
----当前世界状态 { BoatAt LocA ,LocA.M = 2,LocA.C = 2 ,LocB.M = 0,LocB.C = 0 }
执行行为Move(LocA,LocB)
----当前世界状态 { BoatAt LocB ,LocA.M = 2,LocA.C = 2 ,LocB.M = 0,LocB.C = 0 }
执行行为UnLoad(LocB,1,1)
----当前世界状态 { BoatAt LocB ,LocA.M = 2,LocA.C = 2 ,LocB.M = 1,LocB.C = 1 }
执行行为Load(LocB,1,0)
----当前世界状态 { BoatAt LocB ,LocA.M = 2,LocA.C = 2 ,LocB.M = 0,LocB.C = 1 }
执行行为Move(LocB,LocA)
----当前世界状态 { BoatAt LocA ,LocA.M = 2,LocA.C = 2 ,LocB.M = 0,LocB.C = 1 }
执行行为UnLoad(LocA,1,0)
----当前世界状态 { BoatAt LocA ,LocA.M = 3,LocA.C = 2 ,LocB.M = 0,LocB.C = 1 }
执行行为Load(LocA,1,1)
----当前世界状态 { BoatAt LocA ,LocA.M = 2,LocA.C = 1 ,LocB.M = 0,LocB.C = 1 }
执行行为Move(LocA,LocB)
----当前世界状态 { BoatAt LocB ,LocA.M = 2,LocA.C = 1 ,LocB.M = 0,LocB.C = 1 }
执行行为UnLoad(LocB,1,1)
----当前世界状态 { BoatAt LocB ,LocA.M = 2,LocA.C = 1 ,LocB.M = 1,LocB.C = 2 }
执行行为Load(LocB,1,0)
----当前世界状态 { BoatAt LocB ,LocA.M = 2,LocA.C = 1 ,LocB.M = 0,LocB.C = 2 }
执行行为Move(LocB,LocA)
----当前世界状态 { BoatAt LocA ,LocA.M = 2,LocA.C = 1 ,LocB.M = 0,LocB.C = 2 }
执行行为UnLoad(LocA,1,0)
----当前世界状态 { BoatAt LocA ,LocA.M = 3,LocA.C = 1 ,LocB.M = 0,LocB.C = 2 }
执行行为Load(LocA,2,0)
----当前世界状态 { BoatAt LocA ,LocA.M = 1,LocA.C = 1 ,LocB.M = 0,LocB.C = 2 }
执行行为Move(LocA,LocB)
----当前世界状态 { BoatAt LocB ,LocA.M = 1,LocA.C = 1 ,LocB.M = 0,LocB.C = 2 }
执行行为UnLoad(LocB,2,0)
----当前世界状态 { BoatAt LocB ,LocA.M = 1,LocA.C = 1 ,LocB.M = 2,LocB.C = 2 }
执行行为Load(LocB,1,1)
----当前世界状态 { BoatAt LocB ,LocA.M = 1,LocA.C = 1 ,LocB.M = 1,LocB.C = 1 }
执行行为Move(LocB,LocA)
----当前世界状态 { BoatAt LocA ,LocA.M = 1,LocA.C = 1 ,LocB.M = 1,LocB.C = 1 }
执行行为UnLoad(LocA,1,1)
----当前世界状态 { BoatAt LocA ,LocA.M = 2,LocA.C = 2 ,LocB.M = 1,LocB.C = 1 }
执行行为Load(LocA,2,0)
----当前世界状态 { BoatAt LocA ,LocA.M = 0,LocA.C = 2 ,LocB.M = 1,LocB.C = 1 }
执行行为Move(LocA,LocB)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 2 ,LocB.M = 1,LocB.C = 1 }
执行行为UnLoad(LocB,2,0)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 2 ,LocB.M = 3,LocB.C = 1 }
执行行为Load(LocB,1,0)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 2 ,LocB.M = 2,LocB.C = 1 }
执行行为Move(LocB,LocA)
----当前世界状态 { BoatAt LocA ,LocA.M = 0,LocA.C = 2 ,LocB.M = 2,LocB.C = 1 }
执行行为UnLoad(LocA,1,0)
----当前世界状态 { BoatAt LocA ,LocA.M = 1,LocA.C = 2 ,LocB.M = 2,LocB.C = 1 }
执行行为Load(LocA,1,1)
----当前世界状态 { BoatAt LocA ,LocA.M = 0,LocA.C = 1 ,LocB.M = 2,LocB.C = 1 }
执行行为Move(LocA,LocB)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 1 ,LocB.M = 2,LocB.C = 1 }
执行行为UnLoad(LocB,1,1)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 1 ,LocB.M = 3,LocB.C = 2 }
执行行为Load(LocB,1,0)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 1 ,LocB.M = 2,LocB.C = 2 }
执行行为Move(LocB,LocA)
----当前世界状态 { BoatAt LocA ,LocA.M = 0,LocA.C = 1 ,LocB.M = 2,LocB.C = 2 }
执行行为UnLoad(LocA,1,0)
----当前世界状态 { BoatAt LocA ,LocA.M = 1,LocA.C = 1 ,LocB.M = 2,LocB.C = 2 }
执行行为Load(LocA,1,1)
----当前世界状态 { BoatAt LocA ,LocA.M = 0,LocA.C = 0 ,LocB.M = 2,LocB.C = 2 }
执行行为Move(LocA,LocB)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 0 ,LocB.M = 2,LocB.C = 2 }
执行行为UnLoad(LocB,1,1)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 0 ,LocB.M = 3,LocB.C = 3 }

从结果来看,先不看结果是否正确,可以发现TFD算法存在一些缺陷。就和寻路算法一样,简单的前向搜索可以保证得到解决方案,但不保证是最优,而且,往往是比较长(差)的结果。针对本次测试,得到的解决方案一共有33步,除以3 也就是11个Transport任务(Transport任务可以分解为3个基元任务,也就是行动)。

1.全部 States 以及 Actions

下一步,对规划器进行优化,使用一些启发算法来进行优化,减少无用计算,看看可不可以减少一下步数。


学习资料

开始时打算使用RxLua来做的,但是由于需要花挺多时间和精力去研究,所以就暂时打消这个念头,采用一些简单粗暴的方法。不过有时简单粗暴并不意味着差,反而比较清晰了高效。