python 戰棋遊戲代碼實現(1):生物行走和攻擊選擇

遊戲介紹

致敬永遠的經典英雄無敵3, 本想在網上找個戰棋遊戲學習下,無奈沒有發現python版本的,那就自己來寫一個把。

遊戲實現了類似英雄無敵3 中戰鬥場景的回合制玩法:

  • 對戰雙方每個生物每一輪有一次行動機會,可以行走或攻擊對方。
  • 每個生物屬性有:行走範圍,速度,生命,傷害,防禦,攻擊 和 是否是遠程兵種。
  • 當把對方生物都消滅時,即勝利。
  • 實現了簡單的AI。

python 戰棋遊戲代碼實現(2):六邊形地圖尋路和顯示

生物行走選擇截圖如下:
圖1生物行走
圖1 中的步兵行走範圍屬性值是4,表示可以走4個方格,圖中背景是深藍色的方格就是該步兵可以走的方格,不可以直接到斜對角的方格。步兵頭頂紅色的是血條,當前輪到行動的生物背景方格顏色會變成淺藍色。

遊戲截圖如下:
圖2 rpg2
圖2中,目前輪到行動的生物是我方的左下角背景爲淺藍色的步兵,可以看到背景爲深藍色的方格爲步兵可以行走的範圍。背景爲綠色的方格爲目前選定要行走到得方格。鼠標指向敵方生物,如果敵方生物背景方格顏色變成黃色,表示可以攻擊,可以看到允許攻擊斜對角的敵人。圖中還有石塊,表示不能移動到得方格。

下面圖3 是第二輪時我方開始行動時的截圖。
rpg3

代碼介紹

地圖類

source\component\map.py 中的 map類保存地圖信息,width 和 height 爲地圖的寬度和長度,bg_map 設置方格的背景顏色, entity_map 保存哪個方格上有生物。active_entity表示當前要行動的生物。

class Map():
   def __init__(self, width, height, grid):
       self.width = width
       self.height = height
       self.bg_map = [[0 for x in range(self.width)] for y in range(self.height)]
       self.entity_map = [[None for x in range(self.width)] for y in range(self.height)]
       self.active_entity = None

updateMap 函數,當我方生物選擇行動時,設置生物可行走範圍內的方格背景爲 c.BG_RANGE 。active_entity 的 inRange 函數判斷一個方格是否在行走範圍內。

    def updateMap(self):
       for y in range(self.height):
           for x in range(self.width):
               self.bg_map[y][x] = c.BG_EMPTY
               if self.entity_map[y][x] is not None and self.entity_map[y][x].isDead():
                   self.entity_map[y][x] = None

       if self.active_entity is None or self.active_entity.state != c.IDLE:
           return
       map_x, map_y = self.active_entity.map_x, self.active_entity.map_y
       self.bg_map[map_y][map_x] = c.BG_ACTIVE
       
       for y in range(self.height):
           for x in range(self.width):
               if not self.isMovable(x,y):
                   continue
               if self.active_entity.inRange(self, x, y):
                   self.bg_map[y][x] = c.BG_RANGE
       mouse_x, mouse_y = pg.mouse.get_pos()
       self.checkMouseMove(mouse_x, mouse_y)

drawBackground 函數根據 bg_map 中每個方格的類型 繪製方格的背景顏色, 然後繪製方格之前的黑色分隔線。

    def drawBackground(self, surface):
       pg.draw.rect(surface, c.LIGHTYELLOW, pg.Rect(0, 0, c.MAP_WIDTH, c.MAP_HEIGHT))
       
       for y in range(self.height):
           for x in range(self.width):
               if self.bg_map[y][x] == c.BG_EMPTY:
                   color = c.LIGHTYELLOW
               elif self.bg_map[y][x] == c.BG_ACTIVE:
                   color = c.SKY_BLUE
               elif self.bg_map[y][x] == c.BG_RANGE:
                   color = c.NAVYBLUE
               elif self.bg_map[y][x] == c.BG_SELECT:
                   color = c.GREEN
               elif self.bg_map[y][x] == c.BG_ATTACK:
                   color = c.GOLD
               pg.draw.rect(surface, color, (x * c.REC_SIZE, y * c.REC_SIZE, 
                       c.REC_SIZE, c.REC_SIZE))
       
       surface.blit(self.map_image, self.rect)

       for y in range(self.height):
           # draw a horizontal line
           start_pos = (0, 0 + c.REC_SIZE * y)
           end_pos = (c.MAP_WIDTH, c.REC_SIZE * y)
           pg.draw.line(surface, c.BLACK, start_pos, end_pos, 1)

       for x in range(self.width):
           # draw a horizontal line
           start_pos = (c.REC_SIZE * x, 0) 
           end_pos = (c.REC_SIZE * x, c.MAP_HEIGHT)
           pg.draw.line(surface, c.BLACK, start_pos, end_pos, 1)

確定人物行走範圍

source\component\entity.py 中的entity類保存生物的狀態和屬性。group表示 生物屬於敵我哪一方, map_x 和 map_y 表示初始時生物在地圖上的位置。sheet是生物的圖像,EntityAttr 類保存生物的所有屬性。

class Entity():
  def __init__(self, group, sheet, map_x, map_y, data):
      self.group = group
      self.group_id = group.group_id
      self.map_x = map_x
      self.map_y = map_y
      self.frames = []
      self.frame_index = 0
      self.loadFrames(sheet)
      self.image = self.frames[self.frame_index]
      self.rect = self.image.get_rect()
      self.rect.x = self.map_x * c.REC_SIZE + 5
      self.rect.y = self.map_y * c.REC_SIZE + 8
      
      self.attr = EntityAttr(data)
      self.health = self.attr.max_health
      self.weapon = None
      self.enemy = None
      self.state = c.IDLE
      self.animate_timer = 0.0
      self.current_time = 0.0
      self.move_speed = c.MOVE_SPEED

從圖2 可以看到如果在生物的行走範圍中有其他生物或石塊時,行走範圍就不能直接判斷出來。
Entity類中 inRange函數使用A尋路算法計算出到某一個方格的距離,判斷是否在行走範圍內。
A
算法的實現說明可以看我之前的一篇文章 A*算法實現

    def inRange(self, map, map_x, map_y):
      location = AStarSearch.AStarSearch(map, (self.map_x, self.map_y), (map_x, map_y))
      if location is not None:
          _, _, distance = AStarSearch.getFirstStepAndDistance(location)
          if distance <= self.attr.range:
              return True
      return False

人物行走代碼

A*算法可以獲取從起始位置到目標位置的一條最短路徑,所以人物行走就是沿着這個路徑從起始位置移動到目標位置。
walkToDestination 函數中 self.rect.x 和 self.rect.y 表示生物當前的座標位置,self.next_x 和self.next_y 表示路徑中下一個方格的座標位置。

  • 當前的座標位置和下一個方格的座標位置相同時,選取路徑中下一個方格的座標位置。
  • 當前的座標位置和下一個方格的座標位置不同時,生物就繼續行走。
   def walkToDestination(self, map):
       if self.rect.x == self.next_x and self.rect.y == self.next_y:
           source = (self.rect.x//c.REC_SIZE, self.rect.y//c.REC_SIZE)
           dest = (self.dest_x//c.REC_SIZE, self.dest_y//c.REC_SIZE)
           location = AStarSearch.AStarSearch(map, source, dest)
           if location is not None:
               map_x, map_y, _ = AStarSearch.getFirstStepAndDistance(location)
               self.next_x = map_x * c.REC_SIZE + 5
               self.next_y = map_y * c.REC_SIZE + 8
           else:
               self.state = c.IDLE
               print('Error no path to walk to dest(%d,%d)' % (self.next_x, self.next_y))
       if self.rect.x != self.next_x:
           self.rect.x += self.move_speed if self.rect.x < self.next_x else -self.move_speed
       elif self.rect.y != self.next_y:
           self.rect.y += self.move_speed if self.rect.y < self.next_y else -self.move_speed

生物狀態機實現

生物目前有3個狀態:

  • c.IDLE:沒有輪到該生物行動時,不做任何操作
  • c.WALK:輪到該生物行動時,從目前位置移動到指定的目標位置(self.dest_x, self.dest_y)
  • c.ATTACK: 如果有可攻擊的敵人時,按照生物是遠程還是近程兵種,進行攻擊。
   def update(self, game_info, map):
       self.current_time = game_info[c.CURRENT_TIME]
       if self.state == c.WALK:
           if (self.current_time - self.animate_timer) > 250:
               if self.frame_index == 0:
                   self.frame_index = 1
               else:
                   self.frame_index = 0
               self.animate_timer = self.current_time

           if self.rect.x != self.dest_x or self.rect.y != self.dest_y:
               self.walkToDestination(map)
           else:
               map.setEntity(self.map_x, self.map_y, None)
               self.map_x = self.dest_x // c.REC_SIZE
               self.map_y = self.dest_y // c.REC_SIZE
               map.setEntity(self.map_x, self.map_y, self)
               if self.enemy is None:
                   self.state = c.IDLE
               else:
                   self.state = c.ATTACK
       elif self.state == c.ATTACK:
           if self.attr.remote:
               if self.weapon is None:
                   self.shoot(self.enemy)
               else:
                   self.weapon.update()
                   if self.weapon.done:
                       self.weapon = None
                       self.enemy = None
                       self.state = c.IDLE
           else:
               self.putHurt(self.enemy)
               self.enemy = None
               self.state = c.IDLE
       
       if self.state == c.IDLE:
           self.frame_index = 0

完整代碼

遊戲實現代碼的github鏈接 戰棋遊戲
這邊是csdn的下載鏈接 戰棋遊戲

編譯運行

1.編譯環境
python3.7 + pygame1.9
2.運行
直接運行根目錄下的 main.py
$ python main.py

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