继续介绍python游戏编程,仍然是基于pgzero。关于该软件包的基础使用技巧可参考本人专栏文章:
老娄:python游戏编程之pgzero使用介绍zhuanlan.zhihu.com思考
- 本项目中的游戏场景可以有多个,代表不同的关卡。实现的时候是通过在外部创建一个坐标文件来代表不同角色的位置,这样不同关卡就可以通过读入相应文件来生成,有效地将代码逻辑与数据进行了解耦。文件示例如下:
附录的参考图书中是open文件,再用readline()逐行读取,这个还是比较偏传统文件读取的方法,老娄推荐大家尽可能用一些高效的接口来完成这类底层文件操作。考虑到这个文件格式其实是非常标准化的,行、列固定,分隔符固定,所以可以用pandas.read_csv 来尝试。在windows上其实也是可以直接通过pip来安装的:
在本项目中,-1代表空白,0代表地板,1代表墙壁,2代表箱子,3代表玩家,4代表目标位置。定义一个读取坐标文件的函数如下:
def load_pos_file(path):
f = pandas.read_csv(path,header=None) #返回对象为dataframe
row_num, col_num = f.shape #行数,列数
for i in range(row_num):
for j in range(col_num):
value = f.iloc[i,j]
if value == 0:
box_floor = Actor("pushbox_floor")
box_floor.topleft = (i*SIZE,j*SIZE)
box_floors.append(box_floor)
elif value == 1:
wall = Actor("pushbox_wall")
wall.topleft = (i*SIZE,j*SIZE)
walls.append(wall)
elif value == 2:
box = Actor("pushbox_box")
box.topleft = (i*SIZE,j*SIZE)
boxes.append(box)
elif value == 3:
player.topleft = (i*SIZE,j*SIZE)
elif value == 4:
target = Actor("pushbox_target")
target.topleft = (i*SIZE,j*SIZE)
targets.append(target)
else:
pass
在draw函数中打印出各个角色后,效果如下图所示:
游戏区的范围小于窗口的整体大小,这是因为有很多-1的位置代表了空白。
- 关于角色的移动。本项目中角色移动的复杂点在于玩家可以推着箱子走,同时在有墙壁的地方又是无法前进的。老娄这里是给每个角色添加了一个属性,用于表示它的可移动方向候选集。
代码
import pgzrun
import pgzero
from random import randint
import pandas
#这里游戏场景宽、高需要根据关卡数据文件(定义了每个坐标上的角色)来定
SIZE = 48
WIDTH = SIZE * 11
HEIGHT = SIZE * 9
global FINISH
FINISH = False
global level #游戏关卡,对应不同的数据配置文件
level = 1
global walls,boxes,box_floors,targets,player
walls = []
boxes = []
box_floors = []
targets = []
player = Actor("pushbox_up")
def load_level_file(level):
global walls, boxes, box_floors, targets, player
walls = []
boxes = []
box_floors = []
targets = []
player = Actor("pushbox_up")
player.direction = 'up'
path = r"C:Usersadminmu_codemapsmap" + str(level) + '.txt'
f = pandas.read_csv(path,header=None) #返回对象为dataframe
row_num, col_num = f.shape #行数,列数
for i in range(row_num):
for j in range(col_num):
value = f.iloc[i,j]
if value == 0:
box_floor = Actor("pushbox_floor")
box_floor.topleft = (i*SIZE,j*SIZE)
box_floors.append(box_floor)
elif value == 1:
wall = Actor("pushbox_wall")
wall.topleft = (i*SIZE,j*SIZE)
walls.append(wall)
elif value == 2:
box = Actor("pushbox_box")
box.topleft = (i*SIZE,j*SIZE)
box.avaliable_directions = ["up", "down", "left", "right"]
box.placed = False
boxes.append(box)
elif value == 3:
player.topleft = (i*SIZE,j*SIZE)
player.avaliable_directions = ["up", "down", "left", "right"]
elif value == 4:
target = Actor("pushbox_target")
target.topleft = (i*SIZE,j*SIZE)
targets.append(target)
else:
pass
load_level_file(1)
def update():
# move_player()
global FINISH
if FINISH:
return
if level_up():
FINISH = True
clock.schedule(set_level,3)
def draw():
screen.fill((255,255,255))
for w in walls:
w.draw()
for bf in box_floors:
bf.draw()
for t in targets:
t.draw()
for b in boxes:
b.draw()
player.draw()
if FINISH:
screen.draw.text("THIS ROUND FINISHED", topleft=(20, 100), color="green", fontsize=60)
return
def on_key_down(key):
player.avaliable_directions = get_available_direction(player,"player")
player_tx, player_ty = player.topleft
speed = SIZE
if keyboard.UP and "up" in player.avaliable_directions:
player.direction = "up"
player.image = "pushbox_up"
#如果该方向上有箱子,且箱子已经不可移动,则玩家也不能移动
if (player_tx,player_ty-SIZE) in [b.topleft for b in boxes]:
b = boxes[[b.topleft for b in boxes].index((player_tx,player_ty-SIZE))]
if "up" not in b.avaliable_directions:
return
player.y -= speed
elif keyboard.DOWN and "down" in player.avaliable_directions:
player.direction = "down"
player.image = "pushbox_down"
#如果该方向上有箱子,且箱子已经不可移动,则玩家也不能移动
if (player_tx,player_ty+SIZE) in [b.topleft for b in boxes]:
b = boxes[[b.topleft for b in boxes].index((player_tx,player_ty+SIZE))]
if "down" not in b.avaliable_directions:
return
player.y += speed
elif keyboard.LEFT and "left" in player.avaliable_directions:
player.direction = "left"
player.image = "pushbox_left"
#如果该方向上有箱子,且箱子已经不可移动,则玩家也不能移动
if (player_tx-SIZE,player_ty) in [b.topleft for b in boxes]:
b = boxes[[b.topleft for b in boxes].index((player_tx-SIZE,player_ty))]
if "left" not in b.avaliable_directions:
return
player.x -= speed
elif keyboard.RIGHT and "right" in player.avaliable_directions:
player.direction = "right"
player.image = "pushbox_right"
#如果该方向上有箱子,且箱子已经不可移动,则玩家也不能移动
if (player_tx+SIZE,player_ty) in [b.topleft for b in boxes]:
b = boxes[[b.topleft for b in boxes].index((player_tx+SIZE,player_ty))]
if "right" not in b.avaliable_directions:
return
player.x += speed
else:
pass
for b in boxes:
move_box(b)
def move_box(box):
player.avaliable_directions = get_available_direction(player,"player")
box.avaliable_directions = get_available_direction(box,"box")
box_tx,box_ty = box.topleft
boxes_tf = [b.topleft for b in boxes]
#注意还要判断箱子的旁边是否有另一个箱子,如果是,则无法前进
if player.direction == "left" and "left" in player.avaliable_directions and "left" in box.avaliable_directions and player.colliderect(box) and (box_tx-SIZE,box_ty) not in boxes_tf:
box.x -= SIZE
elif player.direction == "right" and "right" in player.avaliable_directions and "right" in box.avaliable_directions and player.colliderect(box) and (box_tx+SIZE,box_ty) not in boxes_tf:
box.x += SIZE
elif player.direction == "up" and "up" in player.avaliable_directions and "up" in box.avaliable_directions and player.colliderect(box) and (box_tx,box_ty-SIZE) not in boxes_tf:
box.y -= SIZE
elif player.direction == "down" and "down" in player.avaliable_directions and "down" in box.avaliable_directions and player.colliderect(box) and (box_tx,box_ty+SIZE) not in boxes_tf:
box.y += SIZE
else:
pass
if box.topleft in [tg.topleft for tg in targets]:
box.placed = True
box.image = "pushbox_box_hit"
def get_available_direction(role,type):
#获取角色role可以前进的方向候选集
#对于玩家与箱子,判断是否能前进的逻辑有所不同,因为通过type来判断是哪个角色
#如果是玩家,则遇到墙壁不可行,遇到箱子且箱子本身不可移动,也不可行
#如果是箱子,则遇到墙壁不可行,旁边是箱子,也不可行
#如果旁边是墙壁或者其它箱子,则不可往该方向前进
avaliable_directions = ["up","down","left","right"]
walls_topleft = [w.topleft for w in walls]
boxes_tl = [b.topleft for b in boxes]
top_x,top_y = role.topleft
#依次判断上下左右是否可行
if ((top_x,top_y-SIZE) in walls_topleft) or (type == "box" and (top_x,top_y-SIZE) in boxes_tl):
avaliable_directions.remove("up")
if ((top_x,top_y+SIZE) in walls_topleft) or (type == "box" and (top_x,top_y+SIZE) in boxes_tl):
avaliable_directions.remove("down")
if ((top_x-SIZE,top_y) in walls_topleft) or (type == "box" and (top_x-SIZE,top_y) in boxes_tl):
avaliable_directions.remove("left")
if ((top_x+SIZE,top_y) in walls_topleft) or (type == "box" and (top_x+SIZE,top_y) in boxes_tl):
avaliable_directions.remove("right")
return avaliable_directions
def level_up():
#当目标位置都被箱子占据时,本轮游戏结束
for b in boxes:
if not b.placed:
return False
return True
def set_level():
global level, FINISH
FINISH = False
level += 1
load_level_file(level)
pgzrun.go()
效果
知乎视频www.zhihu.com参考资料
《趣学python游戏编程》何青 著