1.添加網格線
爲了讓大家能更直觀的感受到地圖是一個個小格子,我在GameMap類中又新增了一個繪製網格線的方法:
def draw_grid(self, screen_surf):
"""
畫網格
"""
for x in range(self.w):
for y in range(self.h):
if self[x][y] == 0: # 不是障礙,畫空心的矩形
pygame.draw.rect(screen_surf, (255, 255, 255), (self.x + x * 32, self.y + y * 32, 32, 32), 1)
else: # 是障礙,畫黑色實心的矩形
pygame.draw.rect(screen_surf, (0, 0, 0), (self.x + x * 32 + 1, self.y + y * 32 + 1, 30, 30), 0)
功能很簡單:不是障礙就繪製空心矩形,是障礙就繪製實心矩形。我們在繪圖函數中調用:
def update(self):
while True:
self.clock.tick(self.fps)
# TODO:邏輯更新
self.event_handler()
# TODO:畫面更新
self.game_map.draw_bottom(self.screen_surf)
Sprite.draw(self.screen_surf, self.hero, 100, 100, 0, 0)
Sprite.draw(self.screen_surf, self.hero, 210, 120, 1, 1)
Sprite.draw(self.screen_surf, self.hero, 300, 100, 2, 2)
self.game_map.draw_top(self.screen_surf)
self.game_map.draw_grid(self.screen_surf)
pygame.display.update()
畫風突然鬼畜起來了,哈哈~
其中黑色的格子就是障礙啦。
2.人物行走類
在尋路之前,先得把我們的行走功能(動畫效果、人物面向)搞定。人物行走的邏輯相對之前的代碼是要複雜一些的,所以動手寫代碼之前,得好好分析幾個問題。
1.角色id:我們使用的是一張集成了所有角色的精靈圖,但是不同的角色在圖片中的位置都各不相同。這時,我們就需要一個角色id來確定每個角色在圖片中的位置。如何優雅的設計人物id呢?我們先看一張圖:
我將每一幀都進行了編號0~95。那麼第一個角色的id就是0,第二個角色id是3,第三個是6,第四個是9,第五個是48...
可以發現,我把每個角色第一幀的編號當作了它的id,而不是直接讓角色id按照1,2,3,4,5...的順序排下去。
這樣做的好處是什麼呢?
我們來回憶一下之前封裝的Sprite.draw:
draw(dest, source, x, y, cell_x, cell_y, cell_w=32, cell_h=32)
cell_x和cell_y代表精靈圖中的列和行
所以,我們就可以直接通過角色id計算出角色第一幀的cell_x和cell_y。
cell_x=角色id%12 ,因爲每行有12列,所以對12取餘是列數
cell_y=角色id//12,每行12個,所以除以12就是行數
2.只算出來第一幀可不行,每個角色一共4*3=12幀,怎麼確定角色當前是12幀的哪一幀呢?
我們可以用一個方向變量dir記錄角色當前的方向,取值範圍是0~3(一共4個方向),再用一個當前幀變量frame記錄角色在當前方向的第幾幀,取值範圍是0~2(每個方向有3幀)。
所以角色在移動過程中的列和行就是:
cell_x=角色id%12+frame
cell_y=角色id//12+dir
3.人物移動邏輯
假設我們已知:1.角色在地圖中的格子的行cur_my和列cur_mx 2.角色下一步將要去的格子的行next_my和列next_mx
那麼角色當前的繪圖座標是:cur_x=cur_mx*32,cur_y=cur_my*32。
角色的下一步格子的繪圖座標就是:dest_x=next_mx*32,dest_y=next_my*32。
因爲一個格子是32*32的,所以實際的繪圖座標需要*32。
然後,我們需要在遊戲主循環裏不斷的去執行以下邏輯:
如果cur_x大於dest_x,那麼cur_x-=2,其中這個2代表每次主循環角色移動的像素。
如果cur_x小於dest_x,那麼cur_x+=2。
cur_y也是同理
當cur_x==dest_x並且cur_y==dest_y的時候,就代表角色已經移動到目標位置了。
理解了上面說的三個問題之後(沒理解就配合下面的代碼再思考一遍),我們就可以開始編寫人物行走類了,在core.py中增加:
class CharWalk:
"""
人物行走類 char是character的縮寫
"""
DIR_DOWN = 0
DIR_LEFT = 1
DIR_RIGHT = 2
DIR_UP = 3
def __init__(self, hero_surf, char_id, dir, mx, my):
"""
:param hero_surf: 精靈圖的surface
:param char_id: 角色id
:param dir: 角色方向
:param mx: 角色所在的小格子座標
:param my: 角色所在的小格子座標
"""
self.hero_surf = hero_surf
self.char_id = char_id
self.dir = dir
self.mx = mx
self.my = my
self.is_walking = False # 角色是否正在移動
self.frame = 1 # 角色當前幀
self.x = mx * 32 # 角色相對於地圖的座標
self.y = my * 32
# 角色下一步需要去的格子
self.next_mx = 0
self.next_my = 0
# 步長
self.step = 2 # 每幀移動的像素
def draw(self, screen_surf, map_x, map_y):
cell_x = self.char_id % 12 + int(self.frame)
cell_y = self.char_id // 12 + self.dir
Sprite.draw(screen_surf, self.hero_surf, map_x + self.x, map_y + self.y, cell_x, cell_y)
def goto(self, x, y):
"""
:param x: 目標點
:param y: 目標點
"""
self.next_mx = x
self.next_my = y
# 設置人物面向
if self.next_mx > self.mx:
self.dir = CharWalk.DIR_RIGHT
elif self.next_mx < self.mx:
self.dir = CharWalk.DIR_LEFT
if self.next_my > self.my:
self.dir = CharWalk.DIR_DOWN
elif self.next_my < self.my:
self.dir = CharWalk.DIR_UP
self.is_walking = True
def move(self):
if not self.is_walking:
return
dest_x = self.next_mx * 32
dest_y = self.next_my * 32
# 向目標位置靠近
if self.x < dest_x:
self.x += self.step
if self.x >= dest_x:
self.x = dest_x
elif self.x > dest_x:
self.x -= self.step
if self.x <= dest_x:
self.x = dest_x
if self.y < dest_y:
self.y += self.step
if self.y >= dest_y:
self.y = dest_y
elif self.y > dest_y:
self.y -= self.step
if self.y <= dest_y:
self.y = dest_y
# 改變當前幀
self.frame = (self.frame + 0.1) % 3
# 角色當前位置
self.mx = int(self.x / 32)
self.my = int(self.y / 32)
# 到達了目標點
if self.x == dest_x and self.y == dest_y:
self.frame = 1
self.is_walking = False
其中,self.x和self.y就是問題分析中的cur_x,cur_y。
在draw函數中,傳入了兩個參數map_x和map_y,這是地圖的繪圖座標,我們的self.x,self.y都是相對於地圖的,所以要加上地圖繪圖座標纔是實際的角色繪圖座標。
這個類的代碼也不是很多,大家一定要理解透徹哦~
最後我們來看一下效果,讓我們的0號角色從(5,10)走到(14,10):
在__init_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, 0, CharWalk.DIR_DOWN, 5, 10)
self.role.goto(14, 10)
別忘了,在繪圖函數中顯示角色:
def update(self):
while True:
self.clock.tick(self.fps)
# 邏輯更新
self.role.move()
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)
pygame.display.update()
運行效果:
本章完,請完全理解本章內容後再繼續閱讀後續章節喔~
有問題可以直接在評論中留言。