python实现推箱子游戏 python推箱子游戏源代码_python


继续介绍python游戏编程,仍然是基于pgzero。关于该软件包的基础使用技巧可参考本人专栏文章:


老娄:python游戏编程之pgzero使用介绍zhuanlan.zhihu.com

python实现推箱子游戏 python推箱子游戏源代码_基于单片机的推箱子游戏设_02


思考

  • 本项目中的游戏场景可以有多个,代表不同的关卡。实现的时候是通过在外部创建一个坐标文件来代表不同角色的位置,这样不同关卡就可以通过读入相应文件来生成,有效地将代码逻辑与数据进行了解耦。文件示例如下:


python实现推箱子游戏 python推箱子游戏源代码_基于单片机的推箱子游戏设_03

不同关卡的数据文件,用不同的数字代表不同的游戏角色

老娄推荐大家尽可能用一些高效的接口来完成这类底层文件操作。考虑到这个文件格式其实是非常标准化的,行、列固定,分隔符固定,所以可以用pandas.read_csv 来尝试。在windows上其实也是可以直接通过pip来安装的:


python实现推箱子游戏 python推箱子游戏源代码_基于单片机的推箱子游戏设_04

在windows上安装pandas

python实现推箱子游戏 python推箱子游戏源代码_python实现推箱子游戏_05

通过pandas.read_csv读取关卡文件

在本项目中,-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函数中打印出各个角色后,效果如下图所示:


python实现推箱子游戏 python推箱子游戏源代码_游戏编程_06


游戏区的范围小于窗口的整体大小,这是因为有很多-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游戏编程》何青 著