pygame網絡遊戲_4_3:人物行走_自動尋路

1.本章效果

在講代碼之前,先看看本章實現的效果吧~

看完效果是不是迫不及待的想學習了?開搞開搞!

 

2.計算鼠標點中了地圖的哪個格子

尋路之前,我們肯定得知道角色要去的終點座標吧。

那麼先看看我們已知什麼:

1.鼠標點擊窗口的座標,用pygame.mouse.get_pos()就可以獲取

2.地圖的繪圖座標x,y

我們要求的是:

鼠標點中地圖格子的座標(列和行)mx,my

我們來看一張圖,幫助我們理解:

我們的窗口是裝不下地圖的,當角色移動的時候,地圖會隨着角色滾動(下一章將實現這個功能)。

根據這張圖,很好得出鼠標在地圖上的像素座標是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))

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

本章完

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章