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

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