无论是在《最高指挥官》和《全面战争》系列中常见的群体移动算法分析
单位集群的列线状态,用简单steer和offset模式驱动单位的整体移动和避开障碍,首先看无障碍情况下单位地移动方式,
移动
- 单位与单位间需要保持阵型不变,离开原先位置后,应该返回原位置
- 单位无法穿过其它单位(正常请跨下)
- 单位有速度限制,除非死亡,个体单位无法单独脱离组运动
Steer、Pursuit 、Arrive是常用于运动控制的术语,用于描述集群或群体中个体的运动行为。
-
Flock(集群):指的是一群个体以一种协调的方式一起移动,保持一定的群体形态和群体行为。在集群中,个体之间相互作用,通过观察和学习邻近个体的位置和速度来决定自己的运动方向和速度,以实现整体的协调性。
-
Steer(操纵):在集群运动中,个体通过操纵自己的运动方向和速度来实现对集群的调整和协调。Steer可以基于不同的策略和规则来实现,例如避免与其他个体碰撞、保持一定的距离、朝向群体中心或指定的目标等。
-
Arrive(到达):Arrive是一种基于目标到达的运动控制策略,个体通过调整自己的速度和方向来逐渐接近目标,并在到达目标附近时减速。这种策略可以用于模拟个体在到达目标点时的缓慢减速和停止行为。
结合这些运动控制术语通常与仿生学等领域中的模型和算法相结合,用于描述和实现集群或群体中个体的协调运动行为。利用这些方法,可以模拟和控制各种集体运动,如鸟群、鱼群、蚁群等。
带有平滑效果地单位路径控制
def seek(self,dest):
desire = dest - self.pos
distance = desire.get_magnitude()
# 到达行为
mul = 1
if distance<30:
mul = distance/30
desire = desire.get_normalize()*mul*self.maxspeed
else:
desire = desire.get_normalize()*self.maxspeed
return desire
def pursuit(self):
desire = self.seek(self.dest)
distance = self.dest - self.pos
if distance.get_magnitude() < 10:
return
dot = self.heading.get_normalize().dot(desire.get_normalize())
k = (dot-1.0)/-2.0
self.heading = lerp(self.heading , desire,0.94-k)
self.pos = self.pos + self.heading*10
lerp 通过线性插值将转向和目标驱动结合在一起,让单位转向更平滑
self.heading = leap(self.heading , desire,0.94-k)
而 Offset Pursuit 可以参考 《游戏人工智能编程案例精粹》 中的介绍
群体位置函数可以有,矩形,圆形,空心矩形等,这里用6人为一排进行排列
def rect_spawn(self,vec):
n = len(self.entities)
row_num = []
while n>6:
n = n - 6
row_num.append(6)
row_num.append(n%6)
wid = 6*50
height = len(row_num)*50
Perpendicular = Vector2(vec.y,-vec.x)
start = 0
for i in range(len(row_num)):
for j in range(row_num[i]):
ve = Vector2(0,0)
ve = ve + Perpendicular*(j-2.5)*50
ve = ve + vec*(len(row_num)-i-3)*50
self.entities[start+j].dest = ve + self.el.pos
start = start + row_num[i]
这里需要考虑到旋转和平移,所以一定要使用局部坐标系,单位有两个方向,一个是正面的方向(heading vector),一个是与正面垂直的方向(Perpendicular vector)
寻路算法
- 无需对每个单位经行寻路
- 物体间障碍物处理流程
- 避免过多计算造成抖动
经典的寻路算法是用于解决在图或网格上找到从起点到目标点的最短路径的问题。以下是几个经典的寻路算法:
-
Dijkstra算法:Dijkstra算法是最经典的最短路径算法之一。它使用贪心策略,在每一步选择当前最短路径的节点进行扩展,直到找到目标节点或遍历完所有可达节点。Dijkstra算法适用于权重非负的图。
-
A*算法:A-star 算法是一种启发式搜索算法,结合了Dijkstra算法和贪心算法的优点。它使用估计函数来评估每个节点的代价,并利用这个估计值来指导搜索方向。A star 算法通过在每个步骤中选择一个最优的节点进行扩展,逐渐逼近最短路径。A star 算法在搜索过程中既考虑了路径长度,也考虑了距离目标的启发式估计值。
本案例中使用基于Dijkstra-Flow 寻路算法
代码涉及到了寻路生成
def __init__(self):
self.map = self.create_map(20,15)
self.size = 40
self.textsize = self.size/2-4
self.star = [2,5]
self.reached = {}
self.frontier = deque()
self.frontier.append(self.star)
self.reached[self.id2num(self.star)] = {"distance":1}
def create_map(self,col,row):
array = []
for j in range(row):
row = []
for i in range(col):
row.append(1)
array.append(row)
return array
def get_neighb(self,pos):
arr = []
right = [pos[0]+1,pos[1]]
left = [pos[0]-1,pos[1]]
up = [pos[0],pos[1]-1]
down = [pos[0],pos[1]+1]
if self.valid_pos(right):
arr.append(right)
if self.valid_pos(left):
arr.append(left)
if self.valid_pos(up):
arr.append(up)
if self.valid_pos(down):
arr.append(down)
return arr
def valid_pos(self,pos):
if pos[0]<len(self.map[0]) and pos[0]>=0 and pos[1]<len(self.map) and pos[1]>=0:
if self.map[pos[1]][pos[0]]==0:
return False
else:
return True
else:
return False
def id2num(self,pos):
return pos[1]*len(self.map[0])+pos[0]
def num2id(self,num):
ix = math.floor(num/len(self.map[0]))
iy = num%len(self.map[0])
return [iy,ix]
def flood(self):
while len(self.frontier)>0:
current = self.frontier.popleft()
for next in self.get_neighb(current):
if self.id2num(next) not in self.reached:
self.frontier.append(next)
self.reached[self.id2num(next)] = {"distance":self.reached[self.id2num(current)]["distance"]+1}
self.update()
操作说明:
蓝色线条为群体控制点运动的路径
左键:设定新导航点,目标到达导航点后会停止运行
中键:移动画布
鼠标滚轮:缩放画布
内容说明
为了能运行本案例,请自行安装python环境 和相关依赖包,具体配置和安装在本文中不涉及也无需涉及,本案例不支大部分智能设备
算法未经严格优化,只为了说明算法的使用方法
本案例应用于非严肃领域,数值结果和算法探讨请@我,如需转载请注明