摘要
人工智能是游戏开发中的重要组成部分,它能够赋予非玩家角色更自然的行为表现,使游戏世界显得更加真实、生动,并且具有挑战性。
在 2D 游戏中,AI 通常并不追求真正意义上的"智能",而是通过一系列规则、状态和算法,让角色表现出像是"有意识"一样的行动方式。常见的 AI 行为包括巡逻、追逐、逃避、攻击和路径寻找等,它们虽然实现逻辑不同,但本质上都属于"根据当前状态做出下一步决策"的过程。
本章将系统介绍游戏 AI 的基础概念,说明常见行为模式的设计思路,并进一步讲解经典的 A 星寻路算法及其在地图环境中的应用。我们还会讨论状态机、视野检测、动态决策和路径更新等内容,帮助读者建立完整的 AI 思维模型。最后,本章将展示如何使用 GPT-5.4 来生成 AI 行为代码和寻路系统。由于国内无法访问 OpenAI 官网,因此使用国内镜像站可以合法注册使用 GPT-5.4 最新模型。翻墙行为违反中国法律法规,请大家遵守法律,不要翻墙。国内镜像站提供了稳定、合法的 AI 服务访问渠道,完全能够满足学习和开发需求。
注册入口:AIGCBAR 镜像站
API 站注册入口:API 独立站
通过本章的学习,读者将能够为游戏角色设计出较为完整的智能行为系统。
15.1 游戏 AI 的基本概念
游戏 AI 和学术意义上的人工智能并不完全相同。
在游戏中,AI 的目标通常不是"完美决策",而是"足够像真的"。
也就是说,它更关注可玩性、表现力和互动感,而不是数学意义上的最优解。
例如,敌人不一定要每次都做最优路径选择,但它需要让玩家觉得"这个敌人会思考,会追我,会躲避,会寻找机会"。
从系统结构上看,游戏 AI 往往可以理解为一个输入到输出的决策过程。
输入包括玩家位置、地图信息、距离、视野、血量、时间、随机因素等;输出则是角色下一步的行为,例如移动、攻击、等待、转向或者逃离。
如果把这种过程抽象成一个模型,可以写成:
a_t = f\\left(s_t\\right)
其中,( s_t ) 表示当前时刻的状态,( a_t ) 表示 AI 在该状态下采取的动作,( f ) 则是决策函数。
在实际游戏里,这个函数通常不是高深算法,而是一组规则、条件判断和状态切换逻辑。
15.2 常见 AI 行为的设计思路
游戏里的 NPC 行为虽然种类很多,但大多数都可以归纳为几种基本模式。
巡逻表示角色按照预设路线或节点移动;追逐表示角色在发现目标后主动接近;逃避则是远离威胁;攻击表示在满足条件时触发动作;寻路则是为了在复杂地形中找到通往目标的可行路线。
这些行为往往不是孤立存在的,而是组合在一起构成完整的敌人逻辑。
例如,一个守卫可能先在路点间巡逻,当玩家进入视野范围后切换为追击状态;如果玩家距离过近,它会尝试攻击;如果自身血量过低,又可能切换为逃跑。
这说明 AI 的核心不是"写一个动作",而是"管理动作之间的切换关系"。
在设计 AI 时,最重要的是让行为切换自然。
如果敌人一会儿追、一会儿停、一会儿又完全无视玩家,玩家就会觉得角色行为不稳定。
因此,实际项目中通常会结合状态机、行为树、感知系统和路径系统,让 AI 的决策更稳定、更可控。
15.3 追逐与逃避行为的基础原理
追逐和逃避是最基础的两类空间行为。
它们本质上都和方向向量有关。
假设角色当前位置为 ( \vec{p} ),目标位置为 ( \vec{t} ),那么朝目标移动的方向可以表示为:
\\vec{d} = \\vec{t} - \\vec{p}
如果要让角色以恒定速度朝目标移动,就需要把方向向量归一化,再乘上速度值:
\\vec{v} = \\frac{\\vec{d}}{\\lVert \\vec{d} \\rVert} \\cdot v_s
其中,( v_s ) 是速度大小。
逃避行为则相反,方向向量变成:
\\vec{d} = \\vec{p} - \\vec{t}
也就是说,追逐是"朝着目标加速",逃避是"朝着相反方向加速"。
这类行为非常适合用于基础敌人、跟踪单位、弹性敌人、恐慌单位等角色类型。
不过,单纯的追逐和逃避只是最基础的模型。
在复杂地形中,如果中间存在墙壁、障碍或不可通行区域,就不能只看直线距离,而必须结合路径系统来决定实际行动路线。
这也是寻路算法存在的原因。
15.4 行为状态机的基本思想
状态机是组织游戏 AI 最常见的方法之一。
它的核心思想是:一个角色在某一时刻只处于某一个状态中,而状态之间通过条件进行切换。
比如敌人可能拥有"巡逻""警觉""追击""攻击""返回原位"五种状态。
每个状态负责不同的行为逻辑,而状态转移则由距离、视野、血量、计时器等条件控制。
状态机的优势在于清晰、直观、容易调试。
如果 AI 行为并不特别复杂,状态机几乎是最实用的方案。
它不像一些复杂的学习型方法那样难以控制,而是更适合游戏开发中"可解释、可调参、可复现"的需求。
从逻辑上说,状态机可以写成一个有限状态系统。
如果状态集合为 ( S = { s_1, s_2, \dots, s_n } ),动作集合为 ( A ),那么状态转移可以表示为:
\\delta : S \\times A \\rightarrow S
也就是说,在当前状态和当前条件共同作用下,系统会转移到下一个状态。
这种表达方式非常适合 AI 的行为组织,也很容易扩展。
15.5 A 星寻路算法为什么重要
在没有障碍物的空旷环境中,追逐目标只需要朝直线方向移动即可。
但只要地图中存在墙壁、障碍、封闭区域或者复杂走廊,直线移动就会失效。
这时,AI 就需要一种能够在网格地图中找到可行路径的方法。
A 星算法就是最经典、最常用的寻路方案之一。
A 星寻路的核心思想,是在"已走成本"和"预估剩余成本"之间寻找平衡。
对于某个节点 ( n ),它的总代价通常表示为:
f(n) = g(n) + h(n)
其中:
- ( g(n) ):从起点走到当前节点的真实代价
- ( h(n) ):从当前节点到终点的启发式估计
- ( f(n) ):综合总代价
A 星算法每一步都会优先选择 ( f ) 值最小的节点进行扩展,因此它既能保证找到路径,又比纯粹盲目搜索更高效。
在网格地图中,常用的启发式函数是曼哈顿距离或者欧几里得距离。
如果只允许上下左右移动,曼哈顿距离最合适:
h = \|x_1 - x_2\| + \|y_1 - y_2\|
如果允许斜向移动,也可以使用更接近几何直线的估计方式。
启发式函数越合理,寻路效率通常越高。
15.6 A 星算法的搜索过程
A 星算法一般会维护两个集合:开放列表和关闭列表。
开放列表保存"还没有处理,但值得继续扩展"的节点;关闭列表保存"已经处理过,不需要重复访问"的节点。
算法每次从开放列表中取出代价最低的节点,检查它是否已经到达终点。如果没有,就继续扩展它的邻居节点,并计算它们的代价。
这个过程看上去像是在不断试探,但实际上它是非常有方向感的搜索。
因为启发式函数会引导搜索朝着目标靠近,所以 A 星不会像暴力遍历那样浪费太多时间。
对于大多数 2D 游戏地图而言,A 星已经足够实用。
不过,A 星也不是万能的。
如果地图特别大、障碍特别多,或者路径需要频繁重算,A 星也会有性能压力。
因此在实际项目中,常常会结合路径缓存、分区寻路、局部避障等手段进一步优化。
15.7 视野检测与敌人感知
AI 不是随时都应该知道玩家在哪。
为了让敌人的行为更自然,通常需要加入感知系统,例如视野范围、角度检测和距离判断。
只有当玩家进入敌人的视野范围后,它才从巡逻状态切换到警觉或追击状态。
最基础的感知方式是距离判断。
如果玩家与敌人之间的距离满足:
d = \\sqrt{\\left(x_1 - x_2\\right)\^2 + \\left(y_1 - y_2\\right)\^2}
并且 ( d ) 小于某个阈值,那么敌人就认为玩家进入了有效范围。
如果还需要判断方向,则可以进一步比较角度,确认玩家是否位于敌人前方。
这种感知系统可以让 AI 更像"看见"了玩家,而不是无条件全局追踪。
这样一来,敌人的行为会更合理,也更有游戏性。
15.8 路径更新与动态环境
在很多游戏中,地图并不是静态不变的。
门会打开,桥会塌陷,障碍会消失,角色会推动箱子,关卡结构也可能因为剧情或战斗而变化。
这意味着 AI 的路径不能永远固定不变,而需要定期重新计算。
这就是路径更新机制的重要性。
例如,敌人可以每隔若干帧重新规划一次路径,而不是每一帧都重新算。
这样既能保证它跟得上环境变化,也不会造成过高的计算负担。
在复杂场景中,有时还会加入局部避障逻辑。
也就是说,即使全局路径已经规划好,AI 在移动过程中仍然要检测前方有没有临时障碍,然后适当绕开。
这类思路可以让寻路系统既稳定又灵活。
15.9 使用 GPT-5.4 生成 AI 代码
在实际开发中,AI 行为和寻路系统往往涉及较多逻辑,尤其是状态切换、路径规划、目标检测和行为封装等部分。
这类内容很适合通过合理的提示词来生成基础框架,再由开发者根据项目需要进行修改和扩展。
下面给出一个适合生成敌人 AI 系统的提示词块:
text
请用 Pygame 实现一个完整的敌人 AI 系统,要求:
1. 使用有限状态机组织 AI 行为
2. 实现巡逻、追击、攻击、逃跑等行为
3. 集成 A 星寻路算法
4. 支持视野检测和距离判断
5. 支持路径重新规划
6. 提供完整可运行代码
7. 代码中加入详细中文注释
8. 使用字体文件路径加载字体,不使用系统字体枚举
9. 结构清晰,方便后续扩展
如果你希望代码更偏项目化,也可以补充:
text
额外要求:
1. 敌人发现玩家后播放警觉状态
2. 玩家离开视野后返回巡逻
3. 追击时遇到障碍会自动寻路
4. 支持地图网格碰撞
5. 敌人距离玩家过近时进入攻击状态
15.10 综合实战:敌人巡逻、追击与 A 星寻路演示
下面这个示例会把本章的核心内容整合起来,包括:
- 基础 AI 行为
- 巡逻和追击
- A 星寻路
- 网格地图障碍检测
- 目标视野判断
- 安全字体加载
为了让示例更清晰,这里使用方格地图来表示障碍和可通行区域。
python
import pygame
import sys
import os
import math
import heapq
import random
pygame.init()
screen = pygame.display.set_mode((960, 640))
pygame.display.set_caption("游戏AI基础与寻路算法演示")
clock = pygame.time.Clock()
def get_font(size):
font_paths = [
r"C:\Windows\Fonts\simhei.ttf",
r"C:\Windows\Fonts\msyh.ttc",
r"C:\Windows\Fonts\simsun.ttc",
]
for path in font_paths:
if os.path.exists(path):
try:
return pygame.font.Font(path, size)
except:
pass
return pygame.font.Font(None, size)
font = get_font(24)
TILE_SIZE = 32
GRID_W = 30
GRID_H = 20
grid = [[0 for _ in range(GRID_W)] for _ in range(GRID_H)]
for x in range(GRID_W):
grid[0][x] = 1
grid[GRID_H - 1][x] = 1
for y in range(GRID_H):
grid[y][0] = 1
grid[y][GRID_W - 1] = 1
for x in range(4, 10):
grid[6][x] = 1
for y in range(8, 15):
grid[y][14] = 1
for x in range(17, 24):
grid[12][x] = 1
class Node:
def __init__(self, x, y):
self.x = x
self.y = y
self.g = 0
self.h = 0
self.f = 0
self.parent = None
def __lt__(self, other):
return self.f < other.f
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
def heuristic(a, b):
return abs(a.x - b.x) + abs(a.y - b.y)
def astar(grid_map, start, end):
rows = len(grid_map)
cols = len(grid_map[0]) if rows > 0 else 0
start_node = Node(start[0], start[1])
end_node = Node(end[0], end[1])
open_list = []
closed_set = set()
heapq.heappush(open_list, start_node)
while open_list:
current = heapq.heappop(open_list)
closed_set.add((current.x, current.y))
if current == end_node:
path = []
while current:
path.append((current.x, current.y))
current = current.parent
return path[::-1]
neighbors = [
(0, -1), (0, 1), (-1, 0), (1, 0)
]
for dx, dy in neighbors:
nx = current.x + dx
ny = current.y + dy
if nx < 0 or nx >= cols or ny < 0 or ny >= rows:
continue
if grid_map[ny][nx] == 1:
continue
if (nx, ny) in closed_set:
continue
neighbor = Node(nx, ny)
neighbor.g = current.g + 1
neighbor.h = heuristic(neighbor, end_node)
neighbor.f = neighbor.g + neighbor.h
neighbor.parent = current
existing = next((n for n in open_list if n == neighbor), None)
if existing and existing.g <= neighbor.g:
continue
heapq.heappush(open_list, neighbor)
return None
class Player:
def __init__(self, x, y):
self.position = pygame.math.Vector2(x, y)
self.speed = 3
self.radius = 12
def update(self):
keys = pygame.key.get_pressed()
move = pygame.math.Vector2(0, 0)
if keys[pygame.K_LEFT]:
move.x -= 1
if keys[pygame.K_RIGHT]:
move.x += 1
if keys[pygame.K_UP]:
move.y -= 1
if keys[pygame.K_DOWN]:
move.y += 1
if move.length() > 0:
move = move.normalize() * self.speed
self.position += move
self.position.x = max(16, min(self.position.x, GRID_W * TILE_SIZE - 16))
self.position.y = max(16, min(self.position.y, GRID_H * TILE_SIZE - 16))
def draw(self, surface):
pygame.draw.circle(surface, (60, 180, 255), (int(self.position.x), int(self.position.y)), self.radius)
class Enemy:
def __init__(self, x, y, grid_map):
self.position = pygame.math.Vector2(x, y)
self.speed = 2.2
self.radius = 12
self.grid_map = grid_map
self.state = "patrol"
self.target = None
self.waypoints = [
pygame.math.Vector2(3 * TILE_SIZE + 16, 3 * TILE_SIZE + 16),
pygame.math.Vector2(10 * TILE_SIZE + 16, 3 * TILE_SIZE + 16),
pygame.math.Vector2(10 * TILE_SIZE + 16, 10 * TILE_SIZE + 16),
pygame.math.Vector2(3 * TILE_SIZE + 16, 10 * TILE_SIZE + 16),
]
self.waypoint_index = 0
self.path = []
self.path_index = 0
self.path_timer = 0
def can_see_player(self, player):
distance = (player.position - self.position).length()
return distance < 220
def move_towards(self, target_pos):
direction = target_pos - self.position
if direction.length() > 0:
direction = direction.normalize()
self.position += direction * self.speed
def update_patrol(self):
target = self.waypoints[self.waypoint_index]
self.move_towards(target)
if (target - self.position).length() < 6:
self.waypoint_index = (self.waypoint_index + 1) % len(self.waypoints)
def update_path_chase(self, player):
self.path_timer += 1
if self.path_timer >= 30 or not self.path:
self.path_timer = 0
start = (int(self.position.x // TILE_SIZE), int(self.position.y // TILE_SIZE))
end = (int(player.position.x // TILE_SIZE), int(player.position.y // TILE_SIZE))
self.path = astar(self.grid_map, start, end)
self.path_index = 0
if self.path and self.path_index < len(self.path):
tx = self.path[self.path_index][0] * TILE_SIZE + TILE_SIZE / 2
ty = self.path[self.path_index][1] * TILE_SIZE + TILE_SIZE / 2
target_pos = pygame.math.Vector2(tx, ty)
self.move_towards(target_pos)
if (target_pos - self.position).length() < 6:
self.path_index += 1
def update(self, player):
distance = (player.position - self.position).length()
if distance < 40:
self.state = "attack"
elif self.can_see_player(player):
self.state = "chase"
self.target = player
else:
self.state = "patrol"
self.target = None
self.path = []
self.path_index = 0
if self.state == "patrol":
self.update_patrol()
elif self.state == "chase":
self.update_path_chase(player)
elif self.state == "attack":
pass
def draw(self, surface):
color = (255, 70, 70) if self.state != "attack" else (255, 220, 80)
pygame.draw.circle(surface, color, (int(self.position.x), int(self.position.y)), self.radius)
if self.path:
points = []
for gx, gy in self.path:
points.append((gx * TILE_SIZE + TILE_SIZE // 2, gy * TILE_SIZE + TILE_SIZE // 2))
if len(points) > 1:
pygame.draw.lines(surface, (255, 255, 0), False, points, 2)
player = Player(100, 100)
enemy = Enemy(300, 300, grid)
running = True
while running:
dt = clock.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
player.update()
enemy.update(player)
screen.fill((25, 25, 35))
for y in range(GRID_H):
for x in range(GRID_W):
rect = pygame.Rect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE)
if grid[y][x] == 1:
pygame.draw.rect(screen, (70, 70, 80), rect)
else:
pygame.draw.rect(screen, (40, 40, 50), rect)
pygame.draw.rect(screen, (55, 55, 65), rect, 1)
player.draw(screen)
enemy.draw(screen)
info1 = font.render("方向键控制玩家移动", True, (255, 255, 255))
info2 = font.render("敌人会巡逻 发现玩家后进行 A 星追击", True, (255, 255, 255))
info3 = font.render(f"敌人状态: {enemy.state}", True, (255, 255, 255))
screen.blit(info1, (10, 10))
screen.blit(info2, (10, 40))
screen.blit(info3, (10, 70))
pygame.display.flip()
pygame.quit()
sys.exit()

15.11 本章总结
本章介绍了游戏 AI 的基础概念,重点讲解了追逐、逃避、巡逻、状态机和 A 星寻路等核心内容。
AI 在游戏中的作用,不是制造真正意义上的"智能",而是为角色赋予合理、可理解、可交互的行为模式。
当你能让敌人看起来会思考、会判断、会寻找路径时,游戏的可玩性和紧张感都会明显提升。
需要特别记住的是,AI 设计的重点不是复杂,而是适合游戏。
一个好的游戏 AI,应该在性能、可控性和表现力之间找到平衡。
它既要能做出像样的判断,也要方便调试和扩展。
本章所讲的状态机和 A 星算法,就是构建这类系统最常用、也最实用的基础工具。
本章知识点回顾
| 知识点 | 主要内容 |
|---|---|
| AI 行为 | 巡逻、追击、逃避、攻击 |
| 状态机 | 用有限状态组织行为 |
| 启发式搜索 | A 星算法的核心思想 |
| 路径代价 | ( f(n) = g(n) + h(n) ) |
| 视野检测 | 距离判断、范围判断 |
| 路径更新 | 动态地图中的重新规划 |
课后练习
- 为敌人增加视野锥检测。
- 实现攻击冷却时间。
- 让敌人在失去目标后回到巡逻点。
- 给 A 星算法加入对角线移动。
- 实现多个敌人共享同一张地图的寻路系统。
下章预告
在下一章中,我们将学习存档系统,掌握游戏数据的持久化保存、读取和恢复技术。