遊戲介紹
致敬永遠的經典英雄無敵3, 本想在網上找個戰棋遊戲學習下,無奈沒有發現python版本的,那就自己來寫一個把。
遊戲實現了類似英雄無敵3 中戰鬥場景的回合制玩法:
- 對戰雙方每個生物每一輪有一次行動機會,可以行走或攻擊對方。
- 每個生物屬性有:行走範圍,速度,生命,傷害,防禦,攻擊 和 是否是遠程兵種。
- 當把對方生物都消滅時,即勝利。
- 實現了簡單的AI。
生物行走選擇截圖如下:
圖1
圖1 中的步兵行走範圍屬性值是4,表示可以走4個方格,圖中背景是深藍色的方格就是該步兵可以走的方格,不可以直接到斜對角的方格。步兵頭頂紅色的是血條,當前輪到行動的生物背景方格顏色會變成淺藍色。
遊戲截圖如下:
圖2
圖2中,目前輪到行動的生物是我方的左下角背景爲淺藍色的步兵,可以看到背景爲深藍色的方格爲步兵可以行走的範圍。背景爲綠色的方格爲目前選定要行走到得方格。鼠標指向敵方生物,如果敵方生物背景方格顏色變成黃色,表示可以攻擊,可以看到允許攻擊斜對角的敵人。圖中還有石塊,表示不能移動到得方格。
下面圖3 是第二輪時我方開始行動時的截圖。
代碼介紹
地圖類
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