想象你第一天当外卖骑手:城市很大,你得先搞清楚哪条路能骑车、哪条路是死胡同——这叫导航网格;打开手机导航,App 帮你规划路线、逐段提醒——这叫导航代理;你一天的工作按“等单→取餐→送餐→休息”轮转——这叫状态机;下雨时你会先穿雨衣再出发,订单超时时你会优先送急单——这叫行为树;晚高峰路口上百个骑手互不碰撞,因为大家都遵守几条默契规则——这叫群体AI。Godot 这款开源游戏引擎把这些工具全部内置,本文就用骑手的一天,带你把这些“城市生存技能”一个个安装进游戏角色的大脑。

岗位关联

学完本文,你能在 Godot 中用导航网格、状态机、行为树和群体 AI 快速搭建独立游戏所需的 NPC 智能,这是游戏 AI 方向最实用的上手技能。

目录
  1. 可视化演示
  2. 导航网格概念
  3. 导航服务器烘焙
  4. NavigationAgent
  5. GDScript集成
  6. 有限状态机
  7. 行为树编排
  8. 群体AI协同
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Godot引擎NavigationServer导航网格烘焙、行为树决策遍历与有限状态机切换的交互过程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

NavigationServer导航网格 可行走区域 障碍物 NavMesh Bake 多边形裁剪合并

导航网格概念

第一天跑外卖,老骑手递给你一张手绘地图:“红色区域是人行道和商场中庭,电动车进不去;蓝色路线才是能骑车的路。”你瞬间明白,能走和不能走本身就是最重要的信息。游戏里这张“地图”叫导航网格(NavigationMesh),它把游戏地面切割成一块块凸多边形,只保留角色可以站立的区域,其余全部剔除。

导航网格不是一张贴图,而是一份结构化数据:每个多边形记录顶点坐标、相邻关系、通行代价。Godot 使用 NavigationMesh 资源保存这份数据,通过 NavigationRegion3D 节点挂进场景;寻路时引擎在路网上执行 A* 搜索——就像导航 App 在路网上寻找最快路线,而不是在卫星照片上硬走。

网格的精细度直接影响性能与手感:格子太粗,角色会“穿墙”或绕远路;格子太细,烘焙时间暴涨。多边形的权重(cost)代表路段的通过难度,例如泥地、陡坡的权重高,寻路会自动绕开。一条路径 P 的总代价,就是每一段代价的累加:

有了“可骑行地图”,骑手才知道哪条路省钱省力。导航网格就是游戏 AI 的第一块地基:它不负责做决定,只负责回答“哪里能走”。地图越准,后面所有的导航与决策才不会跑偏。

C(P)=∑i=1nwi×diC(P) = \displaystyle\sum_{i=1}^{n} w_i \times d_i
这段公式说:一条路线好不好走,看它是“每段路的难走程度 × 每段路的长度”加起来的结果,总代价越小,路线越优。
  1. 路径 P一条从起点到终点的路线,由 $n$ 条路段拼接而成。
    P={e1,e2,…,en}P = \{e_1, e_2, \dots, e_n\}
  2. 路段权重 $w_i$第 $i$ 段路的通行难度系数,泥地高、柏油路低。
    wiw_i
  3. 路段距离 $d_i$第 $i$ 段路的几何长度,由两端点坐标求得。
    di=∥p⃗i−p⃗i−1∥d_i = \|\vec{p}_{i} - \vec{p}_{i-1}\|
  4. 总代价累加每段路的“权重 × 距离”逐项相加,得到整条路径的代价。
    C(P)=∑i=1nwi×diC(P) = \sum_{i=1}^{n} w_i \times d_i

导航服务器烘焙

地图不是骑手自己画的,而是测绘公司的扫描车一点点扫出来的。在 Godot 里,NavigationServer3D 就是那家测绘公司:你把场景里的地形、墙体、平台交给它,它会自动分析哪些表面“可站立”,最终输出一份导航网格。

这个过程叫烘焙(Baking)。Godot 读取 MeshInstance3D 等几何体,再结合代理半径(agent radius)、最大爬坡角(max climb)、单元尺寸(cell size)等参数,把高低起伏的地形“翻译”成一层平整、连续、可查询的可行走网格。烘焙结果挂在 NavigationRegion3D 节点上,多个 Region 可以拼成一张完整的导航地图。

烘焙时引擎会逐面片检查坡度——太陡的坡不能走:

测绘车不是只扫一次。场景改了,可以用 NavigationRegion3D 局部重烘焙;突然出现的路障,则用 NavigationObstacle 动态绕行。记住:烘焙质量决定骑手体验,参数调得好,导航才顺畅。

θ=cos⁡−1(n⃗⋅u⃗∥n⃗∥ ∥u⃗∥)≤θmax⁡\theta = \cos^{-1}\left( \displaystyle\frac{\vec{n} \cdot \vec{u}}{\|\vec{n}\| \, \|\vec{u}\|} \right) \leq \theta_{\max}
意思是:地形小面片的倾斜角度,如果超过设定的最大爬坡角,这个面片就会被剔除,角色爬不上去。
  1. 面片法向量 $\vec{n}$地形小面片的朝向,垂直于该表面,如同屋顶的斜坡指向。
    n⃗\vec{n}
  2. 竖直向量 $\vec{u}$世界坐标的“上”方向。
    u⃗=(0,1,0)\vec{u} = (0, 1, 0)
  3. 夹角 $\theta$面片与水平面的夹角,用点积公式求得。
    cos⁡θ=n⃗⋅u⃗∥n⃗∥ ∥u⃗∥\cos\theta = \frac{\vec{n} \cdot \vec{u}}{\|\vec{n}\| \, \|\vec{u}\|}
  4. 可走判定夹角小于等于最大爬坡角才保留为可行走区域。
    θ≤θmax⁡\theta \leq \theta_{\max}

NavigationAgent

光有地图,人不会自己认路。骑手还要掏出手机导航 App,输入目的地,App 规划路线、逐段播报。NavigationAgent3D 就是挂在角色身上的导航 App:它不替你骑车,但负责“接下来往哪走”。

Agent 是一个 Node 节点,通常挂在 CharacterBody3D 下面。你只需要设置 target_position,它就会向 NavigationServer 查询路径,然后通过 get_next_path_position() 把“下一个路点”交给你。角色不需要理解整条路线,只需像听导航一样逐段执行,走到一个路点再取下一点。

Agent 还会给出期望速度:方向指向下一个路点,大小不超过移动上限:

导航 App 只负责播报,真正拧油门的是骑手。Agent 也一样:它给出速度建议和路径信息,但最后驱动身体的还是物理节点。把“大脑”和“手脚”分开,AI 才灵活可控、便于替换。

v⃗desired=p⃗next−p⃗current∥p⃗next−p⃗current∥×vmax⁡\vec{v}_{\text{desired}} = \displaystyle\frac{\vec{p}_{\text{next}} - \vec{p}_{\text{current}}}{\|\vec{p}_{\text{next}} - \vec{p}_{\text{current}}\|} \times v_{\max}
这段公式说:Agent 算出“朝着下一个路点的方向、以最大速度前进”的推荐速度,角色照着这个速度移动即可。
  1. 当前位置 $\vec{p}_{\text{current}}$角色当前在世界空间中的坐标。
    p⃗current\vec{p}_{\text{current}}
  2. 下一个路点 $\vec{p}_{\text{next}}$从 Agent 的路径队列中取出的下一站坐标。
    p⃗next\vec{p}_{\text{next}}
  3. 方向向量路点坐标减去当前位置,得到“指向目标”的向量。
    p⃗next−p⃗current\vec{p}_{\text{next}} - \vec{p}_{\text{current}}
  4. 单位化除以其模长,变成长度为一的方向向量,只保留朝向。
    p⃗next−p⃗current∥p⃗next−p⃗current∥\frac{\vec{p}_{\text{next}} - \vec{p}_{\text{current}}}{\|\vec{p}_{\text{next}} - \vec{p}_{\text{current}}\|}
  5. 乘最大速度方向乘以 $v_{\max}$,得到大小封顶的期望速度。
    v⃗desired=p⃗next−p⃗current∥p⃗next−p⃗current∥×vmax⁡\vec{v}_{\text{desired}} = \frac{\vec{p}_{\text{next}} - \vec{p}_{\text{current}}}{\|\vec{p}_{\text{next}} - \vec{p}_{\text{current}}\|} \times v_{\max}

GDScript集成

导航 App 播报完“前方右转”,骑手还得真正拧车把、打转向灯。游戏里这套“神经”就是 GDScript:它把 Agent 的决策与 CharacterBody3D 的物理身体连接起来,让“想法”变成“动作”。

标准写法是:在 _physics_process 中先从 Agent 取路点、计算速度,把结果赋给 velocity,再调用 move_and_slide() 推动身体移动;同时用信号监听“到达目标”“路径更新”等事件,和动画状态机联动——移动时播放跑步动画,停下时播放待机动画。

实际运行时,最终速度往往来自多个来源的叠加:导航给出的期望速度,加上避障系统的修正速度,合成为最终速度,再按物理帧时长积分成新位置:

大脑发指令、身体执行、动画反馈,三者通过 GDScript 的节点树与信号协作。就像骑手“看导航→拧油门→车身响应”,每一步都闭环,AI 角色才显得真实、灵活。

v⃗final=v⃗nav+v⃗avoid,p⃗t+1=p⃗t+v⃗final×Δt\vec{v}_{\text{final}} = \vec{v}_{\text{nav}} + \vec{v}_{\text{avoid}}, \qquad \vec{p}_{t+1} = \vec{p}_t + \vec{v}_{\text{final}} \times \Delta t
意思是:角色最终怎么动,由“导航想往哪走”和“避障不想撞谁”共同决定;速度再乘上每帧时间,就得到新位置。
  1. 导航速度 $\vec{v}_{\text{nav}}$Agent 根据路径给出的推荐前进速度。
    v⃗nav\vec{v}_{\text{nav}}
  2. 避障修正 $\vec{v}_{\text{avoid}}$RVO 避障算出的偏移量,避免与同伴或障碍重叠。
    v⃗avoid\vec{v}_{\text{avoid}}
  3. 速度合成两者相加,得到最终执行速度。
    v⃗final=v⃗nav+v⃗avoid\vec{v}_{\text{final}} = \vec{v}_{\text{nav}} + \vec{v}_{\text{avoid}}
  4. 位置积分用最终速度乘物理帧时长,更新坐标。
    p⃗t+1=p⃗t+v⃗final×Δt\vec{p}_{t+1} = \vec{p}_t + \vec{v}_{\text{final}} \times \Delta t

有限状态机

骑手的一天其实就那么几个状态:等单、取餐、送餐、休息。再忙也逃不出这几件事来回切换。把 AI 行为也这样“分格”管理,就是有限状态机(Finite State Machine,FSM)。

FSM 把行为拆成有限个互斥状态:idle(待机)、patrol(巡逻)、chase(追击)、attack(攻击)。每个状态只负责自己的行为逻辑——巡逻时沿路点走,追击时朝玩家移动——状态之间用转移条件切换,例如“玩家进入视野 → 进入追击”,“玩家丢失 3 秒 → 回到巡逻”。

Godot 没有内置 FSM 节点,但可以用几个 Node 轻松搭建:每个状态一个节点,公共的“状态机”节点维护当前状态和转移表。转移逻辑可以抽象成一个纯函数:

状态机像骑手的排班表,把复杂的日常压缩成几个格子,每个格子只做一件事。游戏里绝大多数 NPC 用 FSM 就够:简单、直观、好调试,是独立游戏性价比最高的 AI 起点。

St+1=δ(St,E)S_{t+1} = \delta(S_t, E)
意思是:下一时刻进入什么状态,由“当前状态”和“发生了什么事件”共同决定。
  1. 当前状态 $S_t$$t$ 时刻角色所处的行为状态,如“巡逻”。
    StS_t
  2. 触发事件 $E$会引发状态改变的外部信号或条件,如“发现玩家”。
    EE
  3. 转移函数 $\delta$根据当前状态与事件,查表得到下一状态。
    δ(St,E)\delta(S_t, E)
  4. 下一状态 $S_{t+1}$转移完成后进入的新状态。
    St+1S_{t+1}

行为树编排

老骑手的车上贴着一张决策手册:“下雨先穿雨衣;订单超时优先送急单;单量少就去商圈蹲单。”这种“如果……就……否则……”的分层决策结构,就是行为树(Behavior Tree)。

行为树是一棵从上往下“咨询”的树:根节点逐级下探,叶子节点是具体动作或条件判断。组合节点决定遍历方式——序列节点要求全部子节点成功才算成功,选择节点只要有一个成功就返回成功。例如“追击敌人”这个序列:先“检查距离条件”(成功)→ 再“朝敌人移动”(成功)→ 最后“播放攻击动画”(成功),全部完成才算真成功。

装饰节点负责包装子节点,添加“限时”“取反”“重试”等规则;黑板(Blackboard)则是共享数据区,让整棵树随时读写“敌人位置”“血量”等变量。数学上,序列与选择的逻辑分别就是“与”和“或”:

决策手册让骑手碰到任何情况都不慌。行为树特别适合 Boss 战、队友 AI 这类复杂角色:招式组合、阶段变化、救援条件都能直观编排,改起来比状态机更灵活,也更容易让策划读懂。

Rseq=⋀i=1nRi,Rsel=⋁i=1nRiR_{\text{seq}} = \bigwedge_{i=1}^{n} R_i, \qquad R_{\text{sel}} = \bigvee_{i=1}^{n} R_i
意思是:序列节点要求所有子节点都成功,选择节点只要一个成功即可;二者都会提前短路,节省计算。
  1. 子节点结果 $R_i$第 $i$ 个子节点执行后的结果,成功记 1,失败记 0。
    Ri∈{0,1}R_i \in \{0, 1\}
  2. 序列“与”逻辑$\bigwedge$ 表示全部成功才算成功。
    Rseq=⋀i=1nRiR_{\text{seq}} = \bigwedge_{i=1}^{n} R_i
  3. 选择“或”逻辑$\bigvee$ 表示有一个成功即成功。
    Rsel=⋁i=1nRiR_{\text{sel}} = \bigvee_{i=1}^{n} R_i
  4. 短路执行序列遇到失败立即返回,选择遇到成功立即返回,不再评估后续节点。

群体AI协同

晚高峰十字路口,几十个骑手同时穿行,没有交警却很少相撞——因为大家遵守默契:别贴太近、跟着人流方向、别掉队。这套默契写进代码就是群体 AI(Flocking)。

经典 Boids 模型的三大向量:分离(Separation)推开过近的同伴,对齐(Alignment)匹配周围的平均方向,聚拢(Cohesion)引向群体中心。三者加权求和,得到角色每一帧的最终移动方向:

Godot 的 NavigationServer3D 原生支持 RVO(速度障碍)避障:给 Agent 设置半径后,多个 Agent 之间会自动互相避让,无需手写碰撞判断。编队时还能给每个成员加一个相对“队长”的偏移量,实现整齐阵列。简单规则涌现复杂智慧——几百个骑手遵守三条默契,城市街道就流动起来了。

v⃗flock=ws v⃗sep+wa v⃗align+wc v⃗coh\vec{v}_{\text{flock}} = w_s \, \vec{v}_{\text{sep}} + w_a \, \vec{v}_{\text{align}} + w_c \, \vec{v}_{\text{coh}}
意思是:角色的移动方向是“躲开、跟队、归队”三个愿望的加权折中,权重越大,该愿望越强烈。
  1. 分离向量 $\vec{v}_{\text{sep}}$把过近同伴往外推的力,防止重叠。
    v⃗sep\vec{v}_{\text{sep}}
  2. 对齐向量 $\vec{v}_{\text{align}}$趋向周围同伴的平均方向,保持步调一致。
    v⃗align\vec{v}_{\text{align}}
  3. 聚拢向量 $\vec{v}_{\text{coh}}$引向群体几何中心,避免掉队。
    v⃗coh\vec{v}_{\text{coh}}
  4. 加权合成三个权重 $w_s$、$w_a$、$w_c$ 控制侧重,求和得到最终速度。
    v⃗flock=ws v⃗sep+wa v⃗align+wc v⃗coh\vec{v}_{\text{flock}} = w_s \, \vec{v}_{\text{sep}} + w_a \, \vec{v}_{\text{align}} + w_c \, \vec{v}_{\text{coh}}

AI 应用场景

独立游戏《小镇夜巡》NPC 智能

阶段:游戏 AI 行为设计

解决的问题:传统做法靠大量手写 if-else,NPC 一多逻辑就乱成一团;用导航网格加有限状态机,让每个居民拥有“回家→巡逻→睡觉”的日常节奏,还能对玩家入侵做出反应,代码量却少了一半。

具体案例:一款像素风小镇游戏里,30 个 NPC 各自拥有巡逻路径与作息状态。玩家深夜闯入商店时,守卫会从“巡逻”切换到“追击”,丢失目标 3 秒后进入“搜索”,再回到“巡逻”。全程由 NavigationAgent 提供路径、FSM 管理切换,角色行为自然流畅。

动作游戏 Boss 战行为编排

阶段:战斗 AI 决策

解决的问题:Boss 需要多种攻击招式组合与阶段切换,状态机嵌套会让代码爆炸;行为树把“选招式→前摇→攻击→后摇”组织成可复用的决策树,程序与策划都能看懂、能调整。

具体案例:一款横版动作游戏 Boss“铁壁将军”:血量高于 60% 时用序列节点执行“重锤三连击”;血量在 30%~60% 时用选择节点在“冲锋”与“投枪”间随机;低于 30% 时用限时装饰器强制进入狂化状态,每 5 秒必须释放一次大招。

城市交通流人群模拟

阶段:群体智能模拟

解决的问题:模拟几百个行人或车辆同时过路口时,两两互碰会让队伍卡死;RVO 避障加上分离、对齐、聚拢三规则,让群体自然分流,呈现真实的流动感。

具体案例:一个城市规划演示项目用 Godot 生成十字路口人群:200 个 NavigationAgent 从四个方向横穿马路,开启 RVO 后约 30 秒内全部安全通过;关闭 RVO 后,行人会在路口中央互相推挤,队伍彻底堵死。这一结果直接用于验证红绿灯配时方案。

Python 演示

python77 行
import heapqimport math # 迷你城市地图:0 可通行,1 障碍MAP = [    [0, 0, 0, 0, 1, 0, 0, 0],    [1, 1, 0, 0, 1, 0, 1, 0],    [0, 0, 0, 0, 0, 0, 1, 0],    [0, 1, 1, 1, 0, 0, 1, 0],    [0, 1, 0, 0, 0, 1, 0, 0],    [0, 0, 0, 0, 0, 0, 0, 0],] def astar(start, goal):    """A* 寻路:简化版导航网格路径查询"""    rows, cols = len(MAP), len(MAP[0])     def heuristic(a, b):        return math.hypot(a[0] - b[0], a[1] - b[1])     open_set = [(0, start)]    came_from = {}    g_score = {start: 0}     while open_set:        _, cur = heapq.heappop(open_set)        if cur == goal:            path = []            while cur in came_from:                path.append(cur)                cur = came_from[cur]            path.append(start)            return path[::-1]         for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1),                       (-1, -1), (-1, 1), (1, -1), (1, 1)]:            nx, ny = cur[0] + dx, cur[1] + dy            if 0 <= nx < rows and 0 <= ny < cols and MAP[nx][ny] == 0:                nxt = (nx, ny)                step_cost = 1.414 if dx != 0 and dy != 0 else 1.0                new_cost = g_score[cur] + step_cost                if new_cost < g_score.get(nxt, float('inf')):                    g_score[nxt] = new_cost                    came_from[nxt] = cur                    heapq.heappush(open_set, (new_cost + heuristic(nxt, goal), nxt))    return []  class Courier:    """骑手状态机:等单 -> 派送 -> 完成"""     def __init__(self, start, goal):        self.pos = start        self.goal = goal        self.route = astar(start, goal)        self.progress = 0        self.state = "等单"     def update(self):        if self.state == "等单" and self.route:            self.state = "派送"        elif self.state == "派送":            if self.progress < len(self.route):                self.pos = self.route[self.progress]                self.progress += 1            else:                self.state = "完成"        return self.state  if __name__ == "__main__":    courier = Courier((0, 0), (5, 7))    print("规划路线:", courier.route)    while courier.state != "完成":        print("状态:", courier.state, "位置:", courier.pos)        courier.update()    print("最终:", courier.state, "位置:", courier.pos)
目的:用 Python 复刻 Godot AI 的核心流程——A* 在“网格地图”上寻路(对应导航网格),Courier 类用状态机驱动骑手逐格移动(对应 FSM + NavigationAgent)。
  1. def astar(start, goal):实现 A* 寻路,等价于 Godot 导航服务器在 NavigationMesh 上查询路径。
  2. open_set = [(0, start)]优先队列,以“已走代价 + 预估代价”排序,保证每次都先探索最有希望的方向。
  3. step_cost = 1.414 if dx != 0 and dy != 0 else 1.0斜向移动代价约 1.414,直向为 1,模拟不同路段的通行权重。
  4. if new_cost < g_score.get(nxt, float('inf')):只有找到更短的路径才更新记录,确保算法收敛到最优解。
  5. self.pos = self.route[self.progress]状态机“派送”状态下,沿规划路线逐格更新位置,对应 Agent 逐段路点跟随。
  6. while courier.state != "完成":主循环持续驱动状态机,直到骑手送达,对应 Godot 每帧的 _physics_process 更新。

常见误区

误区我只要在 Godot 里烘焙好导航网格,角色就会自己动起来找路,什么都不用写了。

正解导航网格只是“地图”——它只回答“哪里能走”;问路、走路还要靠 NavigationAgent 查询路径,再靠 CharacterBody3D 的物理驱动真正移动。就像手机地图 App 不等于你会骑电动车:地图负责告诉路,骑车还得自己握把。

为何常见:很多教程演示“烘焙完就能寻路”时省略了 agent 和移动代码,新手误以为导航网格一步到位。

误区设置了 `target_position`,角色就应该自动走向目标,不需要我再写移动逻辑。

正解Agent 只负责查询路径并给出推荐速度,真正移动必须由你在 _physics_process 中把速度交给 move_and_slide() 执行。导航 App 播报“左转”,但拧车把的永远是骑手本人。

为何常见:Agent 翻译成“代理”,直觉上像“替身”,让人觉得它能替角色做所有事。

误区导航网格是静态数据,场景一改就得重新烘焙整张地图,开销巨大。

正解可以用 NavigationRegion3D 做局部更新,动态障碍用 NavigationObstacle 或 RVO 避障处理,完全不必全图重烘焙。骑手发现某段路临时施工,绕一下就够,没必要重绘整张城市地图。

为何常见:教程多演示静态封闭地图,让人以为导航只能整体烘焙、整体替换。

误区我用 if 判断敌人距离也能切换行为,何必学状态机?

正解状态机把“条件判断”和“行为执行”解耦:每个状态是独立封装的对象,转移由事件触发,逻辑清晰、可复用、易调试。if-else 像把所有菜谱挤在一张小纸片上,状态机则是分门别类的菜谱档案夹。

为何常见:小 Demo 里 if-else 确实能跑,但 NPC 一多、状态一交错就会失控,新手往往没踩过这个坑。

误区Godot 没内置行为树节点,所以必须装 LimboAI 之类的插件,否则做不了复杂 AI。

正解行为树的核心只是一套节点协议——序列、选择、装饰器、叶子动作,用 GDScript 几十行就能自实现;插件只是提供可视化编辑器与调试面板,属于“好用”而非“必需”。

为何常见:大家看到教程都用插件,误以为功能必须靠第三方完成,反而忽略了行为树本身的原理。

面试题

Q1. Godot 中用哪种数据结构来表示角色可站立的地面区域?

单选题 · easy

  • A.NavigationMesh 导航网格← 正确答案
  • B.Voxel 体素云数据
  • C.Point 点云地图数据
  • D.Height 高度图数据

答案:A。NavigationMesh(导航网格)把可行走地面切成多边形,是 Godot 导航的基础。体素云、点云、高度图是其他领域的概念,正如地图 App 用的是“路网图层”而非“卫星正射图”。

Q2. NavigationServer3D 烘焙导航网格时,以下哪项是核心输入?

单选题 · easy

  • A.角色的贴图素材
  • B.场景的几何体← 正确答案
  • C.背景的音效文件
  • D.玩家的输入数据

答案:B。烘焙把 MeshInstance3D 等场景几何体解析为可行走网格,贴图、音效、输入都与地图生成无关。就像测绘车扫描的是道路本身,而不是路灯上的广告。

Q3. NavigationAgent3D 的 target_position 属性主要用来做什么?

单选题 · mid

  • A.设置角色的朝向
  • B.设置目的地坐标← 正确答案
  • C.设置相机视角
  • D.设置动画状态

答案:B。target_position 告诉 Agent“要去哪里”,Agent 会查询路径并返回路点。朝向、相机、动画分别由其他模块负责,就像导航 App 只管目的地,不替骑手打转向灯。

Q4. 在 Godot 中让 AI 角色移动到目标点,正确的流程包含哪些步骤?

多选题 · mid

  • A.获取 Agent 的路径路点← 正确答案
  • B.计算期望速度的方向← 正确答案
  • C.使用瞬间传送代替移动
  • D.调用 move 方法驱动身体← 正确答案

答案:ABD。正确流程是“获取路点→计算速度→物理驱动”三步。瞬间传送不叫移动,还跳过了避障与动画。就像骑手要“看导航→踩油门→过路口”,而不能直接瞬移到下一单客户门口。

Q5. 有限状态机中,从一个状态切换到另一个状态需要什么?

单选题 · easy

  • A.转移条件← 正确答案
  • B.随机数值
  • C.玩家按键
  • D.网络同步

答案:A。状态切换由转移条件(事件或判断)触发,随机数、玩家按键、网络都不是必要条件。就像骑手从“送餐”切到“休息”,是因为“订单送达”这个条件成立,而不是抛硬币决定的。

Q6. 行为树中常见的组合节点类型有哪些?

多选题 · mid

  • A.序列节点← 正确答案
  • B.选择节点← 正确答案
  • C.装饰节点← 正确答案
  • D.渲染节点

答案:ABC。序列、选择、装饰器是行为树三大控制节点;渲染节点属于图形管线,与决策无关。就像骑手手册里只有三种句式:按顺序做、二选一、加条件重试。

Q7. 群体 AI 的三大经典规则不包括以下哪项?

单选题 · mid

  • A.分离规则
  • B.对齐规则
  • C.聚拢规则
  • D.隐身规则← 正确答案

答案:D。Boids 三规则是分离、对齐、聚拢;隐身属于渲染效果,与群体移动无关。骑手之间的默契是“别撞、同向、别掉队”,没有一条是“隐身”。

Q8. 在 Godot 中开启 RVO 避障后,NavigationAgent3D 的 velocity 属性返回什么?

单选题 · hard

  • A.角色当前的物理速度
  • B.避障后的推荐速度← 正确答案
  • C.服务器生成的随机速度
  • D.玩家输入的原始速度

答案:B。启用 RVO 后,velocity 返回的是融合避障修正的推荐速度,角色按它驱动即可避开其他 Agent。它既不是当前物理速度,也不是随机值。类似导航 App 播报“前方拥挤,建议减速变道”。

Q9. 下列关于 Godot 导航网格权重(cost)的说法,正确的是?

多选题 · hard

  • A.权重会影响路径的选择← 正确答案
  • B.权重会调度算力资源
  • C.权重值越大越容易被选
  • D.权重值越小越容易被选← 正确答案

答案:AD。代价(权重)越小表示“越好走”,寻路比较总代价 Σ(w × d),最小者胜出。权重不影响算力调度。就像骑手会选“路程短且好走”的路,而不是表面上近却堵车的路。

Q10. 行为树中装饰器节点的主要作用是什么?

单选题 · extreme

  • A.改变子节点执行规则← 正确答案
  • B.存储角色世界坐标
  • C.播放角色动作动画
  • D.烘焙导航网格地图

答案:A。装饰器不执行具体行为,而是包装子节点、改变执行方式(取反、限时、重试、限次)。坐标存储、动画播放、网格烘焙都另有其责。它像骑手手册里的“加粗红字”:不改变任务本身,却改变任务被对待的方式。