前言
這一期我們會帶大家進一步復現我們的魔塔小游戲,主要內容包括英雄類的定義與其基礎行動的實作,行動程序中觸發不同層的切換等功能,
廢話不多說,讓我們愉快地開始吧~
開發工具
Python版本: 3.7.4
相關模塊:
pygame模塊;
以及一些python自帶的模塊,
環境搭建
安裝Python并添加到環境變數,pip安裝需要的相關模塊即可,
原理簡介
上一期,我們實作了游戲的基礎畫面定義,類似這樣:

細心的小伙伴肯定發現了,地圖里怎么沒有我們的勇士呢?沒有他我們還怎么去拯救公主呀~別急,這期就帶大家來實作這部分內容,
首先,我們來定義一下我們的英雄勇士類:
'''定義我們的主角勇士'''
class Hero(pygame.sprite.Sprite):
def __init__(self, imagepaths, blocksize, position, fontpath=None):
pygame.sprite.Sprite.__init__(self)
# 設定基礎屬性
self.font = pygame.font.Font(fontpath, 40)
# 加載對應的圖片
self.images = {}
for key, value in imagepaths.items():
self.images[key] = pygame.transform.scale(pygame.image.load(value), (blocksize, blocksize))
self.image = self.images['down']
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = position
# 設定等級等資訊
self.level = 1
self.life_value = 1000
self.attack_power = 10
self.defense_power = 10
self.num_coins = 0
self.experience = 0
self.num_yellow_keys = 0
self.num_purple_keys = 0
self.num_red_keys = 0
'''將勇士系結到螢屏上'''
def draw(self, screen):
screen.blit(self.image, self.rect)
將其系結到游戲主界面之后的效果如下:

看起來是不是哪里不對?沒錯,左邊原來有文字顯示勇士當前的狀態呀!現在都沒了!不過沒關系,問題不大,我們寫幾行代碼將英雄的資訊顯示在左邊的面板上面即可:
font_renders = [
self.font.render(str(self.level), True, (255, 255, 255)),
self.font.render(str(self.life_value), True, (255, 255, 255)),
self.font.render(str(self.attack_power), True, (255, 255, 255)),
self.font.render(str(self.defense_power), True, (255, 255, 255)),
self.font.render(str(self.num_coins), True, (255, 255, 255)),
self.font.render(str(self.experience), True, (255, 255, 255)),
self.font.render(str(self.num_yellow_keys), True, (255, 255, 255)),
self.font.render(str(self.num_purple_keys), True, (255, 255, 255)),
self.font.render(str(self.num_red_keys), True, (255, 255, 255)),
]
rects = [fr.get_rect() for fr in font_renders]
rects[0].topleft = (160, 80)
for idx in range(1, 6):
rects[idx].topleft = 160, 127 + 42 * (idx - 1)
for idx in range(6, 9):
rects[idx].topleft = 160, 364 + 55 * (idx - 6)
for fr, rect in zip(font_renders, rects):
screen.blit(fr, rect)
效果是這樣子的:

完成了勇士類最基礎的定義,接下來我們就該讓他動起來啦,具體而言,我們先實作一個勇士行動的類函式:
'''行動'''
def move(self, direction):
assert direction in self.images
self.image = self.images[direction]
move_vector = {
'left': (-self.blocksize, 0),
'right': (self.blocksize, 0),
'up': (0, -self.blocksize),
'down': (0, self.blocksize),
}[direction]
self.rect.left += move_vector[0]
self.rect.top += move_vector[1]
然后寫個按鍵檢測,并根據玩家按下的鍵值來決定勇士的行動方向:
key_pressed = pygame.key.get_pressed()
if key_pressed[pygame.K_w] or key_pressed[pygame.K_UP]:
self.hero.move('up')
elif key_pressed[pygame.K_s] or key_pressed[pygame.K_DOWN]:
self.hero.move('down')
elif key_pressed[pygame.K_a] or key_pressed[pygame.K_LEFT]:
self.hero.move('left')
elif key_pressed[pygame.K_d] or key_pressed[pygame.K_RIGHT]:
self.hero.move('right')
如果你覺得我上面的代碼寫的沒問題,大功告成了,這樣寫是有兩個問題的,
首先,這樣子寫會導致玩家按一次上鍵,勇士就移動很多格,導致玩家不好控制勇士的位置,此時我們可以添加一個行動冷卻變數:
# 行動冷卻
self.move_cooling_count = 0
self.move_cooling_time = 5
self.freeze_move_flag = False
在冷卻中的時候進行計數:
if self.freeze_move_flag:
self.move_cooling_count += 1
if self.move_cooling_count > self.move_cooling_time:
self.move_cooling_count = 0
self.freeze_move_flag = False
計數完成后英雄方可恢復行動能力,于是move可以重寫成:
'''行動'''
def move(self, direction):
if self.freeze_move_flag: return
assert direction in self.images
self.image = self.images[direction]
move_vector = {
'left': (-self.blocksize, 0),
'right': (self.blocksize, 0),
'up': (0, -self.blocksize),
'down': (0, self.blocksize),
}[direction]
self.rect.left += move_vector[0]
self.rect.top += move_vector[1]
self.freeze_move_flag = True
感興趣的小伙伴可以自行去掉這段代碼實際感受一下鍵盤操作我們的勇士時是否會存在區別,
另外一個問題,也是最嚴重的問題,那就是行動會不合法,比如勇士會出現在這樣的位置:

因此,我們需要再添加額外的移動是否合法的判斷:
'''行動'''
def move(self, direction, map_parser):
if self.freeze_move_flag: return
assert direction in self.images
self.image = self.images[direction]
move_vector = {'left': (-1, 0), 'right': (1, 0), 'up': (0, -1), 'down': (0, 1)}[direction]
block_position = self.block_position[0] + move_vector[0], self.block_position[1] + move_vector[1]
if block_position[0] >= 0 and block_position[0] < map_parser.map_size[1] and \
block_position[1] >= 0 and block_position[1] < map_parser.map_size[0]:
if map_parser.map_matrix[block_position[1]][block_position[0]] in ['0']:
self.block_position = block_position
elif map_parser.map_matrix[block_position[1]][block_position[0]] in ['24']:
self.dealcollideevent(
elem=map_parser.map_matrix[block_position[1]][block_position[0]],
block_position=block_position,
map_parser=map_parser,
)
self.rect.left, self.rect.top = self.block_position[0] * self.blocksize + self.offset[0], self.block_position[1] * self.blocksize + self.offset[1]
self.freeze_move_flag = True
這里,為了方便判斷,我們將原來采用的像素坐標改成了游戲地圖中的元素塊坐標(即上一期設計的游戲地圖里,每個數字在地圖矩陣中的位置索引),另外,這里我們還需要想到的一個點是未來進一步復現游戲的程序中,我們需要在勇士和地圖中一些元素發生碰撞時作出對應的回應,例如勇士和怪物進行決斗,撿到鑰匙等等事件,因此我們也在上面的move函式中嵌入了dealcollideevent來處理這樣的情況,一個簡單效果展示如下:

當然,理論上按照原版的游戲這里應該是有一個背景故事的對話框的,這部分我們下一期再實作,本期我們主要實作一些基礎的功能,比如一些簡單事件的觸發,包括遇到門,撿到鑰匙等等:
'''處理撞擊事件'''
def dealcollideevent(self, elem, block_position, map_parser):
# 遇到不同顏色的門, 有鑰匙則打開, 否則無法前進
if elem in ['2', '3', '4']:
flag = False
if elem == '2' and self.num_yellow_keys > 0:
self.num_yellow_keys -= 1
flag = True
elif elem == '3' and self.num_purple_keys > 0:
self.num_purple_keys -= 1
flag = True
elif elem == '4' and self.num_red_keys > 0:
self.num_red_keys -= 1
flag = True
if flag: map_parser.map_matrix[block_position[1]][block_position[0]] = '0'
return flag
# 撿到不同顏色的鑰匙
elif elem in ['6', '7', '8']:
if elem == '6': self.num_yellow_keys += 1
elif elem == '7': self.num_purple_keys += 1
elif elem == '8': self.num_red_keys += 1
map_parser.map_matrix[block_position[1]][block_position[0]] = '0'
return True
# 撿到寶石
elif elem in ['9', '10']:
if elem == '9': self.defense_power += 3
elif elem == '10': self.attack_power += 3
map_parser.map_matrix[block_position[1]][block_position[0]] = '0'
return True
# 遇到仙女, 進行對話, 并左移一格
elif elem in ['24']:
map_parser.map_matrix[block_position[1]][block_position[0] - 1] = elem
map_parser.map_matrix[block_position[1]][block_position[0]] = '0'
return False
最后,我們來實作一下勇士上下樓梯時切換當前游戲地圖的效果,這咋聽起來似乎有點難辦,但其實不然,只需要將發生上下樓梯事件的命令回傳到游戲主回圈:
# 上下樓梯
elif elem in ['13', '14']:
if elem == '13': events = ['upstairs']
elif elem == '14': events = ['downstairs']
return True, events
'''行動'''
def move(self, direction, map_parser):
# 判斷是否冷凍行動
if self.freeze_move_flag: return
assert direction in self.images
self.image = self.images[direction]
# 移動勇士
move_vector = {'left': (-1, 0), 'right': (1, 0), 'up': (0, -1), 'down': (0, 1)}[direction]
block_position = self.block_position[0] + move_vector[0], self.block_position[1] + move_vector[1]
# 判斷該移動是否合法, 并觸發對應的事件
events = []
if block_position[0] >= 0 and block_position[0] < map_parser.map_size[1] and \
block_position[1] >= 0 and block_position[1] < map_parser.map_size[0]:
# --合法移動
if map_parser.map_matrix[block_position[1]][block_position[0]] in ['0']:
self.block_position = block_position
# --觸發事件
elif map_parser.map_matrix[block_position[1]][block_position[0]] in ['2', '3', '4', '6', '7', '8', '9', '10', '13', '14', '24']:
flag, events = self.dealcollideevent(
elem=map_parser.map_matrix[block_position[1]][block_position[0]],
block_position=block_position,
map_parser=map_parser,
)
if flag: self.block_position = block_position
# 重新設定勇士位置
self.rect.left, self.rect.top = self.block_position[0] * self.blocksize + self.offset[0], self.block_position[1] * self.blocksize + self.offset[1]
# 冷凍行動
self.freeze_move_flag = True
# 回傳需要在主回圈里觸發的事件
return events
然后在主回圈中進行回應即可:
# --觸發游戲事件
for event in move_events:
if event == 'upstairs':
self.map_level_pointer += 1
self.loadmap()
elif event == 'downstairs':
self.map_level_pointer -= 1
self.loadmap()
效果如下:

不知道大家有沒有發現一個問題,就是勇士上樓之后所在的位置其實不對,理論上應該是在當前地圖的下樓梯口附近的,而不是上一張游戲地圖里勇士最后上樓時所在的位置,那么這部分應該如何實作呢?其實很簡單,一個簡單的解決方案是在定義游戲地圖的時候,在上下樓梯處定義一個00變數:

畫游戲地圖的時候還是按照0元素去畫:
if elem in self.element_images:
image = self.element_images[elem][self.image_pointer]
image = pygame.transform.scale(image, (self.blocksize, self.blocksize))
screen.blit(image, position)
elif elem in ['00', 'hero']:
image = self.element_images['0'][self.image_pointer]
image = pygame.transform.scale(image, (self.blocksize, self.blocksize))
screen.blit(image, position)
但是上下樓梯切換游戲地圖時,我們可以利用該識別符號重置角色所在的位置:
# --觸發游戲事件
for event in move_events:
if event == 'upstairs':
self.map_level_pointer += 1
self.loadmap()
self.hero.placenexttostairs(self.map_parser, 'down')
elif event == 'downstairs':
self.map_level_pointer -= 1
self.loadmap()
self.hero.placenexttostairs(self.map_parser, 'up')
其中重置位置的函式實作如下:
'''放置到上/下樓梯口旁'''
def placenexttostairs(self, map_parser, stairs_type='up'):
assert stairs_type in ['up', 'down']
for row_idx, row in enumerate(map_parser.map_matrix):
for col_idx, elem in enumerate(row):
if (stairs_type == 'up' and elem == '13') or (stairs_type == 'down' and elem == '14'):
if row_idx > 0 and map_parser.map_matrix[row_idx - 1][col_idx] == '00':
self.block_position = col_idx, row_idx - 1
elif row_idx < map_parser.map_size[0] - 1 and map_parser.map_matrix[row_idx + 1][col_idx] == '00':
self.block_position = col_idx, row_idx + 1
elif col_idx > 0 and map_parser.map_matrix[row_idx][col_idx - 1] == '00':
self.block_position = col_idx - 1, row_idx
elif col_idx < map_parser.map_size[1] - 1 and map_parser.map_matrix[row_idx][col_idx + 1] == '00':
self.block_position = col_idx + 1, row_idx
self.rect.left, self.rect.top = self.block_position[0] * self.blocksize + self.offset[0], self.block_position[1] * self.blocksize + self.offset[1]
重新測驗一下看看:

總結一下,主要就是實作了我們的勇士角色,以及他和地圖中一些元素相遇后需要發生的一些簡單的事件回應,本期完整源代碼可以私信獲取
文章到這里就結束了,感謝你的觀看,Python29個小游戲系列,下篇文章分享魔塔小游戲呀(3)
為了感謝讀者們,我想把我最近收藏的一些編程干貨分享給大家,回饋每一個讀者,希望能幫到你們,
往期回顧
Python從零開始帶大家實作一個魔塔小游戲(1)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/337713.html
標籤:python
