pygame网络游戏_4_3:人物行走_自动寻路

1.本章效果

在讲代码之前,先看看本章实现的效果吧~

pygame网络游戏_4_3:人物行走_自动寻路

看完效果是不是迫不及待的想学习了?开搞开搞!

 

2.计算鼠标点中了地图的哪个格子

寻路之前,我们肯定得知道角色要去的终点坐标吧。

那么先看看我们已知什么:

1.鼠标点击窗口的坐标,用pygame.mouse.get_pos()就可以获取

2.地图的绘图坐标x,y

我们要求的是:

鼠标点中地图格子的坐标(列和行)mx,my

我们来看一张图,帮助我们理解:

pygame网络游戏_4_3:人物行走_自动寻路

我们的窗口是装不下地图的,当角色移动的时候,地图会随着角色滚动(下一章将实现这个功能)。

根据这张图,很好得出鼠标在地图上的像素坐标是mouse_x-x,mouse_y-y,因为x,y是负数,所以相减就是加。

那么我们要求的mx,my就等于:

mx=(mouse_x-x)//32

my=(mouse_y-y)//32

因为每个格子占32像素,所以要除以32。

 

3.引入A星算法

关于A星算法是什么,小伙伴们可以自行百度,我这里就不再多做解释了。

a星算法我很早以前就用python实现了:python实现的A星算法

现在拿来用就完事啦~

新建astar.py(先别看里面的代码,目前会用就行):

class Point:
  """
  表示一个点
  """

  def __init__(self, x, y):
    self.x = x
    self.y = y

  def __eq__(self, other):
    if self.x == other.x and self.y == other.y:
      return True
    return False

  def __str__(self):
    return "x:" + str(self.x) + ",y:" + str(self.y)


class AStar:
  """
  AStar算法的Python3.x实现
  """

  class Node: # 描述AStar算法中的节点数据
    def __init__(self, point, endPoint, g=0):
      self.point = point # 自己的坐标
      self.father = None # 父节点
      self.g = g # g值,g值在用到的时候会重新算
      self.h = (abs(endPoint.x - point.x) + abs(endPoint.y - point.y)) * 10 # 计算h值

  def __init__(self, map2d, startPoint, endPoint, passTag=0):
    """
    构造AStar算法的启动条件
    :param map2d: Array2D类型的寻路数组
    :param startPoint: Point或二元组类型的寻路起点
    :param endPoint: Point或二元组类型的寻路终点
    :param passTag: int类型的可行走标记(若地图数据!=passTag即为障碍)
    """
    # 开启表
    self.openList = []
    # 关闭表
    self.closeList = []
    # 寻路地图
    self.map2d = map2d
    # 起点终点
    if isinstance(startPoint, Point) and isinstance(endPoint, Point):
      self.startPoint = startPoint
      self.endPoint = endPoint
    else:
      self.startPoint = Point(*startPoint)
      self.endPoint = Point(*endPoint)

    # 可行走标记
    self.passTag = passTag

  def getMinNode(self):
    """
    获得openlist中F值最小的节点
    :return: Node
    """
    currentNode = self.openList[0]
    for node in self.openList:
      if node.g + node.h < currentNode.g + currentNode.h:
        currentNode = node
    return currentNode

  def pointInCloseList(self, point):
    for node in self.closeList:
      if node.point == point:
        return True
    return False

  def pointInOpenList(self, point):
    for node in self.openList:
      if node.point == point:
        return node
    return None

  def endPointInCloseList(self):
    for node in self.openList:
      if node.point == self.endPoint:
        return node
    return None

  def searchNear(self, minF, offsetX, offsetY):
    """
    搜索节点周围的点
    :param minF:F值最小的节点
    :param offsetX:坐标偏移量
    :param offsetY:
    :return:
    """
    # 越界检测
    if minF.point.x + offsetX < 0 or minF.point.x + offsetX > self.map2d.w - 1 or minF.point.y + offsetY < 0 or minF.point.y + offsetY > self.map2d.h - 1:
      return
    # 如果是障碍,就忽略
    if self.map2d[minF.point.x + offsetX][minF.point.y + offsetY] != self.passTag:
      return
    # 如果在关闭表中,就忽略
    currentPoint = Point(minF.point.x + offsetX, minF.point.y + offsetY)
    if self.pointInCloseList(currentPoint):
      return
    # 设置单位花费
    if offsetX == 0 or offsetY == 0:
      step = 10
    else:
      step = 14
    # 如果不再openList中,就把它加入openlist
    currentNode = self.pointInOpenList(currentPoint)
    if not currentNode:
      currentNode = AStar.Node(currentPoint, self.endPoint, g=minF.g + step)
      currentNode.father = minF
      self.openList.append(currentNode)
      return
    # 如果在openList中,判断minF到当前点的G是否更小
    if minF.g + step < currentNode.g: # 如果更小,就重新计算g值,并且改变father
      currentNode.g = minF.g + step
      currentNode.father = minF

  def start(self):
    """
    开始寻路
    :return: None或Point列表(路径)
    """
    # 判断寻路终点是否是障碍
    if self.map2d[self.endPoint.x][self.endPoint.y] != self.passTag:
      return None

    # 1.将起点放入开启列表
    startNode = AStar.Node(self.startPoint, self.endPoint)
    self.openList.append(startNode)
    # 2.主循环逻辑
    while True:
      # 找到F值最小的点
      minF = self.getMinNode()
      # 把这个点加入closeList中,并且在openList中删除它
      self.closeList.append(minF)
      self.openList.remove(minF)
      # 判断这个节点的上下左右节点
      self.searchNear(minF, 0, -1)
      self.searchNear(minF, 0, 1)
      self.searchNear(minF, -1, 0)
      self.searchNear(minF, 1, 0)
      # 判断是否终止
      point = self.endPointInCloseList()
      if point: # 如果终点在关闭表中,就返回结果
        # print("关闭表中")
        cPoint = point
        pathList = []
        while True:
          if cPoint.father:
            pathList.append(cPoint.point)
            cPoint = cPoint.father
          else:
            # print(pathList)
            # print(list(reversed(pathList)))
            # print(pathList.reverse())
            return list(reversed(pathList))
      if len(self.openList) == 0:
        return None

咋用呢?很简单:

path = AStar(map2d, start_point, end_point).start()

如果path为None,那么就是没有找到路径,否则path就是一个存了Point对象的列表。

Point对象有两个属性:x,y

 

4.改造CharWalk类

在__init__方法最后加上:

    # 寻路路径
    self.path = []
    # 当前路径下标
    self.path_index = 0

self.path用于存放寻路路径

self.path_index是角色当前在self.path中的下标

然后再给CharWalk类加上find_path方法:


  def find_path(self, map2d, end_point):
    """
    :param map2d: 地图
    :param end_point: 寻路终点
    """
    start_point = (self.mx, self.my)
    path = AStar(map2d, start_point, end_point).start()
    if path is None:
      return

    self.path = path
    self.path_index = 0

这个方法就是用于寻路的,end_point是寻路终点,角色当前位置是寻路起点。

再给CharWalk类添加logic方法:

  def logic(self):
    self.move()

    # 如果角色正在移动,就不管它了
    if self.is_walking:
      return

    # 如果寻路走到终点了
    if self.path_index == len(self.path):
      self.path = []
      self.path_index = 0
    else: # 如果没走到终点,就往下一个格子走
      self.goto(self.path[self.path_index].x, self.path[self.path_index].y)
      self.path_index += 1

这个逻辑注释已经讲得很清楚了,我们在主循环中调用logic就行了。

 

Game类中的修改:

  def __init_game(self):
    """
    我们游戏的一些初始化操作
    """
    self.hero = pygame.image.load('./img/character/hero.png').convert_alpha()
    self.map_bottom = pygame.image.load('./img/map/0.png').convert_alpha()
    self.map_top = pygame.image.load('./img/map/0_top.png').convert_alpha()
    self.game_map = GameMap(self.map_bottom, self.map_top, 0, 0)
    self.game_map.load_walk_file('./img/map/0.map')
    self.role = CharWalk(self.hero, 48, CharWalk.DIR_DOWN, 5, 10)

  def update(self):
    while True:
      self.clock.tick(self.fps)
      # 逻辑更新
      self.role.logic()
      self.event_handler()
      # 画面更新
      self.game_map.draw_bottom(self.screen_surf)
      self.role.draw(self.screen_surf, self.game_map.x, self.game_map.y)
      self.game_map.draw_top(self.screen_surf)
      # self.game_map.draw_grid(self.screen_surf)
      pygame.display.update()

最后我们还得接收鼠标事件:

  def event_handler(self):
    for event in pygame.event.get():
      if event.type == pygame.QUIT:
        sys.exit()
      elif event.type == pygame.MOUSEBUTTONDOWN:
        mouse_x, mouse_y = pygame.mouse.get_pos()
        mx = (mouse_x - self.game_map.x) // 32
        my = (mouse_y - self.game_map.y) // 32
        self.role.find_path(self.game_map, (mx, my))

大功告成,运行看看效果吧:

pygame网络游戏_4_3:人物行走_自动寻路

本章完