【Pygame】第15章 游戏人工智能基础、行为控制与寻路算法实现
摘要人工智能是游戏开发中的重要组成部分它能够赋予非玩家角色更自然的行为表现使游戏世界显得更加真实、生动并且具有挑战性。在 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 系统的提示词块请用 Pygame 实现一个完整的敌人 AI 系统要求 1. 使用有限状态机组织 AI 行为 2. 实现巡逻、追击、攻击、逃跑等行为 3. 集成 A 星寻路算法 4. 支持视野检测和距离判断 5. 支持路径重新规划 6. 提供完整可运行代码 7. 代码中加入详细中文注释 8. 使用字体文件路径加载字体不使用系统字体枚举 9. 结构清晰方便后续扩展如果你希望代码更偏项目化也可以补充额外要求 1. 敌人发现玩家后播放警觉状态 2. 玩家离开视野后返回巡逻 3. 追击时遇到障碍会自动寻路 4. 支持地图网格碰撞 5. 敌人距离玩家过近时进入攻击状态15.10 综合实战敌人巡逻、追击与 A 星寻路演示下面这个示例会把本章的核心内容整合起来包括基础 AI 行为巡逻和追击A 星寻路网格地图障碍检测目标视野判断安全字体加载为了让示例更清晰这里使用方格地图来表示障碍和可通行区域。importpygameimportsysimportosimportmathimportheapqimportrandom pygame.init()screenpygame.display.set_mode((960,640))pygame.display.set_caption(游戏AI基础与寻路算法演示)clockpygame.time.Clock()defget_font(size):font_paths[rC:\Windows\Fonts\simhei.ttf,rC:\Windows\Fonts\msyh.ttc,rC:\Windows\Fonts\simsun.ttc,]forpathinfont_paths:ifos.path.exists(path):try:returnpygame.font.Font(path,size)except:passreturnpygame.font.Font(None,size)fontget_font(24)TILE_SIZE32GRID_W30GRID_H20grid[[0for_inrange(GRID_W)]for_inrange(GRID_H)]forxinrange(GRID_W):grid[0][x]1grid[GRID_H-1][x]1foryinrange(GRID_H):grid[y][0]1grid[y][GRID_W-1]1forxinrange(4,10):grid[6][x]1foryinrange(8,15):grid[y][14]1forxinrange(17,24):grid[12][x]1classNode:def__init__(self,x,y):self.xx self.yy self.g0self.h0self.f0self.parentNonedef__lt__(self,other):returnself.fother.fdef__eq__(self,other):returnself.xother.xandself.yother.ydef__hash__(self):returnhash((self.x,self.y))defheuristic(a,b):returnabs(a.x-b.x)abs(a.y-b.y)defastar(grid_map,start,end):rowslen(grid_map)colslen(grid_map[0])ifrows0else0start_nodeNode(start[0],start[1])end_nodeNode(end[0],end[1])open_list[]closed_setset()heapq.heappush(open_list,start_node)whileopen_list:currentheapq.heappop(open_list)closed_set.add((current.x,current.y))ifcurrentend_node:path[]whilecurrent:path.append((current.x,current.y))currentcurrent.parentreturnpath[::-1]neighbors[(0,-1),(0,1),(-1,0),(1,0)]fordx,dyinneighbors:nxcurrent.xdx nycurrent.ydyifnx0ornxcolsorny0ornyrows:continueifgrid_map[ny][nx]1:continueif(nx,ny)inclosed_set:continueneighborNode(nx,ny)neighbor.gcurrent.g1neighbor.hheuristic(neighbor,end_node)neighbor.fneighbor.gneighbor.h neighbor.parentcurrent existingnext((nforninopen_listifnneighbor),None)ifexistingandexisting.gneighbor.g:continueheapq.heappush(open_list,neighbor)returnNoneclassPlayer:def__init__(self,x,y):self.positionpygame.math.Vector2(x,y)self.speed3self.radius12defupdate(self):keyspygame.key.get_pressed()movepygame.math.Vector2(0,0)ifkeys[pygame.K_LEFT]:move.x-1ifkeys[pygame.K_RIGHT]:move.x1ifkeys[pygame.K_UP]:move.y-1ifkeys[pygame.K_DOWN]:move.y1ifmove.length()0:movemove.normalize()*self.speed self.positionmove self.position.xmax(16,min(self.position.x,GRID_W*TILE_SIZE-16))self.position.ymax(16,min(self.position.y,GRID_H*TILE_SIZE-16))defdraw(self,surface):pygame.draw.circle(surface,(60,180,255),(int(self.position.x),int(self.position.y)),self.radius)classEnemy:def__init__(self,x,y,grid_map):self.positionpygame.math.Vector2(x,y)self.speed2.2self.radius12self.grid_mapgrid_map self.statepatrolself.targetNoneself.waypoints[pygame.math.Vector2(3*TILE_SIZE16,3*TILE_SIZE16),pygame.math.Vector2(10*TILE_SIZE16,3*TILE_SIZE16),pygame.math.Vector2(10*TILE_SIZE16,10*TILE_SIZE16),pygame.math.Vector2(3*TILE_SIZE16,10*TILE_SIZE16),]self.waypoint_index0self.path[]self.path_index0self.path_timer0defcan_see_player(self,player):distance(player.position-self.position).length()returndistance220defmove_towards(self,target_pos):directiontarget_pos-self.positionifdirection.length()0:directiondirection.normalize()self.positiondirection*self.speeddefupdate_patrol(self):targetself.waypoints[self.waypoint_index]self.move_towards(target)if(target-self.position).length()6:self.waypoint_index(self.waypoint_index1)%len(self.waypoints)defupdate_path_chase(self,player):self.path_timer1ifself.path_timer30ornotself.path:self.path_timer0start(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.pathastar(self.grid_map,start,end)self.path_index0ifself.pathandself.path_indexlen(self.path):txself.path[self.path_index][0]*TILE_SIZETILE_SIZE/2tyself.path[self.path_index][1]*TILE_SIZETILE_SIZE/2target_pospygame.math.Vector2(tx,ty)self.move_towards(target_pos)if(target_pos-self.position).length()6:self.path_index1defupdate(self,player):distance(player.position-self.position).length()ifdistance40:self.stateattackelifself.can_see_player(player):self.statechaseself.targetplayerelse:self.statepatrolself.targetNoneself.path[]self.path_index0ifself.statepatrol:self.update_patrol()elifself.statechase:self.update_path_chase(player)elifself.stateattack:passdefdraw(self,surface):color(255,70,70)ifself.state!attackelse(255,220,80)pygame.draw.circle(surface,color,(int(self.position.x),int(self.position.y)),self.radius)ifself.path:points[]forgx,gyinself.path:points.append((gx*TILE_SIZETILE_SIZE//2,gy*TILE_SIZETILE_SIZE//2))iflen(points)1:pygame.draw.lines(surface,(255,255,0),False,points,2)playerPlayer(100,100)enemyEnemy(300,300,grid)runningTruewhilerunning:dtclock.tick(60)/1000.0foreventinpygame.event.get():ifevent.typepygame.QUIT:runningFalseplayer.update()enemy.update(player)screen.fill((25,25,35))foryinrange(GRID_H):forxinrange(GRID_W):rectpygame.Rect(x*TILE_SIZE,y*TILE_SIZE,TILE_SIZE,TILE_SIZE)ifgrid[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)info1font.render(方向键控制玩家移动,True,(255,255,255))info2font.render(敌人会巡逻 发现玩家后进行 A 星追击,True,(255,255,255))info3font.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 星算法加入对角线移动。实现多个敌人共享同一张地图的寻路系统。下章预告在下一章中我们将学习存档系统掌握游戏数据的持久化保存、读取和恢复技术。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2480966.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!