Pygame学习之路 - 植物大战僵尸(二) 场景布置和卡片放置

文章讲述了在开发植物大战僵尸游戏的过程中,如何完善JSON文件以存储卡片信息,并创建Text类和SeedBank类来管理背景图片、阳光槽和植物种子布局。作者详细介绍了代码的修改过程,包括设置背景图片、创建种子银行、处理卡片点击事件以及实现阳光和植物卡槽的显示与更新。
摘要由CSDN通过智能技术生成

        注:这个系列文章的全部内容里面包含自己写的一些思路,难免会有时候同一个文章中需要多次修改代码的情况,但是编程就是这样(个人觉得)在修改中不断的完善代码,慢慢解决bug,最后的效果我虽然不清楚能不能完全做出来,但是不试试怎么知道 !

        在游戏框架搭建完成后,那么就要开始完善json文件了,card.json文件当中储存了一些卡片的信息,资料为了方便保存,打包暂时放到百度云当中,下面是卡片的资料链接

       卡片素材

        植物素材

        之后的json文件也需要去添加指定的路径,不止是单纯的根目录,后面需要对应上指定的名字,同时需要再来一个文件夹,取名叫OtherPicture,这个文件用来储存一些其他的素材,如背景图片和阳光槽等。

        那么首先,我们需要先去创建好背景,在这之前,我们需要设置好背景图片的链接,为了方便后续的调用,所以在这里,我把地图的全部数据读取下来了,然后通过setting.py文件来确定,后面的话就只需要在关卡数据当中第一行加入使用的地图就好啦,下面是setting.py文件

        其他素材

import os

WIDTH = 760
HEIGHT = 600

# 全部的背景图片素材,每一关都有对应的编号,会写在关卡配置信息当中,直接读取这些图片路径作为背景
BackGround = [f"File/OtherPicture/BackGround{i}"
              for i in os.listdir("File/OtherPicture/BackGround")]

        其它素材当中的,我们存入了这些数据,有阳光的数据,有植物卡槽和背景,因为背景的图片有点多,所以单独建一个文件夹来进行储存。

        接下来就可以在main函数当中编辑地图的内容了,因为每一关需要去初始一些数据,如地图的图片信息等,所以我们需要在原来main.py文件当中去修改一些内容,加入一个初始化的代码。

import pygame
import Setting
from pygame.locals import *

pygame.init()
window = pygame.display.set_mode((Setting.WIDTH, Setting.HEIGHT))  # 窗口
BG = pygame.image.load(Setting.BackGround[0])  # 先准备加载素材


def mouse_click():
    """鼠标的点击事件,用于处理卡片选择和植物种植,以及铲子的功能"""


def mouse_right():
    """右键事件,点击后取消卡片选择和铲子的功能"""


def set_up():
    """初始化数据加载,每一关开始就运行一次,对于一些数据的初始化"""
    pass


def other_event():
    """一些其他事件,如点击了关闭按钮"""
    for event in pygame.event.get():
        if event.type == QUIT:
            return True


def draw():
    """绘制函数,控制绘制内容的主逻辑"""
    window.blit(BG, (-200, 0))


def run():
    """控制整个程序的主逻辑"""
    clock = pygame.time.Clock()  # 开启游戏时钟
    clock.tick(60)  # 设置游戏帧率

    pygame.display.set_caption("植物大战僵尸 - Pygame")  # 设置窗口标题

    while True:
        if other_event():  # 如点击了退出,那么程序结束
            break

        draw()  # 运行绘制函数
        pygame.display.update()  # 更新屏幕


if __name__ == '__main__':
    run()

        背景布置完成后需要根据图片大小适当调整图片的位置和参考大小,因为之前的760太小了,所以在setting中,扩展到了820,在电脑上的位置还是比较满意的 

        setting.py数据修改了一下

WIDTH = 820
HEIGHT = 600

        下面是目前的窗口状态

        

        然后再加入植物种子槽的布局,并且绘制出来

Seed_Bank = pygame.image.load("File/OtherPicture/SeedBank.png")  # 植物银行,存放植物种子
window.blit(Seed_Bank, (10, 0))

        然后就形成了下面的效果

        因为种子图片(种子槽变化后)和代表阳光的文字都有可能会发生改变,所以考虑现在将它们封装起来,这样修改就会方便很多,但是这个是在主程序不方便放那么多类,所以我们需要在Other_function.py里面进行修改

        并且将左边的阳光储存和后面的植物卡槽分解开,后面的植物卡槽需要和植物的大小相关,暂时不知道多大,但是卡片先放着 60 * 80 大小,后续再看看是不是需要进行修改

        在Other_function.py文件中,我们需要有一些数据,如阳光数量,用于阳光的显示,并且要确保其在中间的位置,在pygame中,可以使用get_rect获取一些点,下面的博文链接是get_rect数据参考的内容,很直观:

        Pygame中get_rect( )方法——一首歌的时间学会_一个兴趣使然的程序猿罢了的博客-CSDN博客

        因为阳光数量是随时在变化的,显示要有文字的功能,所以在这里,我创建了两个类,分别是文本的Text类和植物卡槽的SeedBank类

        在Text类当中,方便修改,需要这样的数据

class Text:
    def __init__(self, window, src: str, x: int = 100, y: int = 200,
                 font_size: int = 24,
                 color: str = "black",
                 font_name: str = None,
                 anti_alias: bool = True,
                 **kwargs):
        """
        实例化一个文本内容
        :param window: 绘制的窗口
        :param src: 绘制的内容
        :param x: 字符串的要被居中对齐的x位置
        :param y: 字符串的要被居中对齐的y位置
        :param font_size: 文本大小
        :param color: 文本颜色
        :param anti_alias: 抗锯齿
        :param kwargs: 其他参数
        """

        在SeedBank类当中,方便修改,可能会使用到下面的数据,先创建

class SeedBank(pygame.sprite.Sprite):
    def __init__(self, window, sun: int, num: int, **kwargs):
        """
        植物卡槽
        :param window: 绘制的窗口
        :param sun: 阳光的数量
        :param num: 几个卡片,涉及卡槽长度
        :param kwargs: 用于Text对象的调用
        """

        为了Text类操作,可能需要用到这些功能,先找准功能,下面的内容为Text类下面的函数

    def update_pos(self):
        """位置/数据更新,文本变化后需要更新一下位置,确保居中"""

    def change_src(self, src, color="black"):
        """更新数据内容"""

    def click(self):
        """鼠标点击事件"""

    def draw(self):
        """用于绘制文字内容的函数"""

        同时我们需要对初始init函数进行一些参数的设置,具体的说明已经有注释了,在这里,写**kwargs是为了方便传参,在植物种子槽中,可以直接对Text里面的参数操作

    def __init__(self, window, src: str, x: int = 100, y: int = 200,
                 font_size: int = 24,
                 color: str = "black",
                 font_name: str = None,
                 anti_alias: bool = True,
                 **kwargs):
        """
        实例化一个文本内容
        :param window: 绘制的窗口
        :param src: 绘制的内容
        :param x: 字符串的要被居中对齐的x位置
        :param y: 字符串的要被居中对齐的y位置
        :param font_size: 文本大小
        :param color: 文本颜色
        :param anti_alias: 抗锯齿
        :param kwargs: 其他参数
        """
        self.window = window  # 绘制的窗口
        self.font_size = font_size
        self.src = src  # 内容
        self.x, self.y = x, y  # 位置
        self.color = color  # 颜色
        self.anti_alias = anti_alias  # 抗锯齿

        self.font = pygame.font.Font(font_name, font_size)  # 创建字体
        self.text = self.font.render(self.src, self.anti_alias, self.color)  # 显示内容
        self.center = [0, 0]
        self.w, self.h = self.text.get_rect().size  # 获取大小
        self.update_pos()

        更新位置的函数中,为确保文字居中,需要使用到center,刚开始我也不是很理解,后面搜索后发现这个链接下回答还是不错的,所以就尝试的拿去使用了,结果确实可以放置好位置:

如何将pygame显示的文本居中? - 问答 - Python中文网

    def update_pos(self):
        """位置/数据更新,文本变化后需要更新一下位置,确保居中"""
        self.center = self.text.get_rect(center=(self.x, self.y - self.font_size / 2))

        在更新数据内容的函数当中,需要重新加载字体,并且设置大小和颜色,之后为了文字居中,需要调用到update_pos函数,如50阳光变100阳光,那么数字变大,之前的居中肯定不可以了,需要重新进行计算,剩下的就交给调用了

    def change_src(self, src, color="black"):
        """更新数据内容"""
        self.src = src
        self.color = color
        self.text = self.font.render(self.src, self.anti_alias, self.color)  # 显示内容
        self.update_pos()

        click函数是我这边预留的,后续不一定用的到,但是先放着不影响运行

        之后的话就剩下绘制的功能了,绘制的话是很简单的,一行代码即可

def draw(self): self.window.blit(self.text, self.center)

        到这就是全部文字对象内容的组成了,下面是完整版

class Text:
    def __init__(self, window, src: str, x: int = 100, y: int = 200,
                 font_size: int = 24,
                 color: str = "black",
                 font_name: str = None,
                 anti_alias: bool = True,
                 **kwargs):
        """
        实例化一个文本内容
        :param window: 绘制的窗口
        :param src: 绘制的内容
        :param x: 字符串的要被居中对齐的x位置
        :param y: 字符串的要被居中对齐的y位置
        :param font_size: 文本大小
        :param color: 文本颜色
        :param anti_alias: 抗锯齿
        :param kwargs: 其他参数
        """
        self.window = window  # 绘制的窗口
        self.font_size = font_size
        self.src = src  # 内容
        self.x, self.y = x, y  # 位置
        self.color = color  # 颜色
        self.anti_alias = anti_alias  # 抗锯齿

        self.font = pygame.font.Font(font_name, font_size)  # 创建字体
        self.text = self.font.render(self.src, self.anti_alias, self.color)  # 显示内容
        self.center = [0, 0]
        self.w, self.h = self.text.get_rect().size  # 获取大小
        self.update_pos()

    def update_pos(self):
        """位置/数据更新,文本变化后需要更新一下位置,确保居中"""
        self.center = self.text.get_rect(center=(self.x, self.y - self.font_size / 2))

    def change_src(self, src, color="black"):
        """更新数据内容"""
        self.src = src
        self.color = color
        self.text = self.font.render(self.src, self.anti_alias, self.color)  # 显示内容
        self.update_pos()

    def click(self):
        """鼠标点击事件"""
        # mouse_x, mouse_y = pygame.mouse.get_pos()
        # if self.x < mouse_x < self.x + self.w and self.y < mouse_y < self.y + self.h:
        #     return True

    def draw(self): self.window.blit(self.text, self.center)

        之后,就要准备编辑植物种子槽了,但是植物种子槽涉及到加减阳光的功能,所以在这里,需要先对卡片对象Card.py文件进行编辑,创建好卡片角色,最后在考虑加减阳光的逻辑,因为阳光居中在阳光槽中显示,所以阳光的量也一同给封装进去了,方便对阳光操作,所以阳光槽的逻辑还是有点复杂的,并且现在暂时不知道需要怎么去编辑好这个类,涉及卡片的操作,所以这里应该先做Card.py文件当中的卡片类。

        在这里我们先暂时确定好卡片可能需要哪些逻辑

# Card.py
import pygame
import time


class card(pygame.sprite.Sprite):
    def __init__(self, window, data, column):
        """
        卡片角色
        :param window: 当前的绘图窗口
        :param data: 卡片附带的数据
        :param column: 这是第几个卡牌,布局使用,从0开始传入
        """
        pygame.sprite.Sprite.__init__(self)  # 初始格式化为角色
        self.cd = time.time()

    def CD(self):
        """进入CD的冷却动画,矩形填充"""

    def click(self):
        """鼠标点击的事件"""

    def draw(self):
        """绘制功能实现"""

        暂定好卡片的逻辑后,再去看看卡片的位置需要怎么去放置,返回main.py界面,在种子卡槽后面我们再创建一个变量,用来确定图片位置,经过实验,发现原来想默认的60 * 80太大了,在这里及时调整,设置为60 * 75后就比较正常了

card = pygame.transform.scale(pygame.image.load("File/Card/SunFlower.png"), (60, 75))

        再在draw函数当中调整位置,尝试画出10张卡片的全部布局,确定好位置内容,改变main.py文件当中的draw函数,发现在96 + i * 60和5的时候比较好,当然,因你的窗口可能会有些浮动,所以要慢慢的调整!

def draw():
    """绘制函数,控制绘制内容的主逻辑"""
    window.blit(BG, (-200, 0))
    Seed_Bank.draw()
    for i in range(10):
        window.blit(card, (96 + i * 60, 5))

        调整好后,效果如下

        布局到这,发现合适,那么我们就可以开始编辑Card.py文件了,因为我们Card.py文件传入了当前是第几个卡牌,所以,我们可以公式套进去,经过一些考虑,暂时确定Card.py文件的内容如下:

import pygame
import time


class card(pygame.sprite.Sprite):
    def __init__(self, window, data, column):
        """
        卡片角色
        :param window: 当前的绘图窗口
        :param data: 卡片附带的数据
        :param column: 这是第几个卡牌,布局使用,从0开始传入
        """
        pygame.sprite.Sprite.__init__(self)  # 初始格式化为角色
        self.window = window
        self.data = data
        self.column = column
        self.ima = pygame.image.load(self.data["CardImage"])  # 图片内容加载
        self.x, self.y = 96 + self.column * 60, 5  # 植物卡片的x和y位置
        self.w, self.h = 60, 75  # 一张图片的高度和宽度
        self.ima = pygame.transform.scale(self.ima, (self.w, self.h))  # 重设大小

        self.cd = time.time()

    def CD(self):
        """进入CD的冷却动画,矩形填充"""

    def click(self):
        """鼠标点击的事件"""

    def draw(self):
        """绘制功能实现"""
        self.window.blit(self.ima, (self.x, self.y))  # 绘制卡片

    def run(self):
        """此角色类的运行逻辑"""

        到这为止,卡片功能暂时确定,那么就可以在Other_function.py文件当中进行编辑了,考虑到卡片也加入到植物槽当中,所以包括card类,我们需要引入到 Other_function.py中的SeedBank类,并且在SeedBank类当中就需要写上卡片逻辑,包括点击事件的功能,所以后面我修改了一下SeedBank类的调用,改为传入植物列表,那么后续就方便一些了吧。。。

        下面是Other_function.py文件当中的SeedBank类

class SeedBank(pygame.sprite.Sprite):
    def __init__(self, window, sun: int, card_list: list, **kwargs):
        """
        植物卡槽
        :param window: 绘制的窗口
        :param sun: 阳光的数量
        :param card_list: 卡片列表,传入全部卡片,实例化使用
        :param kwargs: 用于Text对象的调用
        """
        pygame.sprite.Sprite.__init__(self)  # 初始格式化
        self.window = window  # 窗口
        self.sun = sun  # 当前的阳光
        self.card_list = card_list  # 植物的数量,决定种子银行的宽度,一张图片的大小是 60 * 80
        self.num = len(self.card_list)  # 得到卡片的数量决定卡槽大小
        self.w = self.num * 60 + (self.num - 1) * 2  # 宽度等于卡牌的总长度加上间隙

        self.sun_img = pygame.image.load("File/OtherPicture/SUN.png")
        self.seed_bank = pygame.transform.scale(  # 植物卡槽的图片信息
            pygame.image.load("File/OtherPicture/seed_bank.png"), (self.w, 87))
        self.sun_pos = (20, 0)  # 获取阳光显示位置的坐标,用于阳光的数量显示
        self.seed_pos = (85, 0)  # 获取到种子银行的坐标

        self.Sun = Text(self.window, str(self.sun),
                        self.sun_pos[0] + self.sun_img.get_rect().size[0] / 2,
                        self.sun_pos[1] + self.sun_img.get_rect().bottom,
                        **kwargs)

    def load_card(self):
        pass

    def change_sun(self):
        """更新阳光数据内容"""
        self.Sun.change_src(str(self.sun))

    def draw(self):
        self.window.blit(self.seed_bank, self.seed_pos)  # 画出种子银行
        self.window.blit(self.sun_img, self.sun_pos)  # 画出阳光槽
        self.Sun.draw()

    def run(self):
        pass

        那么SeedBank类发生了改变,我们就需要在main.py文件当中改变一下调用,不能再传入一个数字了,同时,绘制的卡片我们也可以注释或者删除了,下面是修改后的main.py文件

import pygame
import Setting
import Other_function
from pygame.locals import *

pygame.init()
window = pygame.display.set_mode((Setting.WIDTH, Setting.HEIGHT))  # 窗口
BG = pygame.image.load(Setting.BackGround[0])  # 先准备加载素材
Seed_Bank = Other_function.SeedBank(window, 50, [str(i) for i in range(1, 7)])  # 植物银行,存放植物种子
# card = pygame.transform.scale(pygame.image.load("File/Card/SunFlower.png"), (60, 75))


def mouse_click():
    """鼠标的点击事件,用于处理卡片选择和植物种植,以及铲子的功能"""


def mouse_right():
    """右键事件,点击后取消卡片选择和铲子的功能"""


def set_up():
    """初始化数据加载,每一关开始就运行一次,对于一些数据的初始化"""
    pass


def other_event():
    """一些其他事件,如点击了关闭按钮"""
    for event in pygame.event.get():
        if event.type == QUIT:
            return True


def draw():
    """绘制函数,控制绘制内容的主逻辑"""
    window.blit(BG, (-200, 0))
    Seed_Bank.draw()
    # for i in range(10):
    #     window.blit(card, (96 + i * 60, 5))


def run():
    """控制整个程序的主逻辑"""
    clock = pygame.time.Clock()  # 开启游戏时钟
    clock.tick(60)  # 设置游戏帧率

    pygame.display.set_caption("植物大战僵尸 - Pygame")  # 设置窗口标题

    while True:
        if other_event():  # 如点击了退出,那么程序结束
            break

        draw()  # 运行绘制函数
        pygame.display.update()  # 更新屏幕


if __name__ == '__main__':
    run()

        当时没考虑周到,直到做到这里为止,发现Card.json文件中,键应该是对应的id,而不是英文,篇幅问题,文件上传到了云盘,需要可以自取,或者自己手动改变全部英文为指定id,方便植物卡槽的调用数据实例化。

json数据       

        涉及到json文件读取,所以我需要在Other_function.py文件当中导入json环境,方便读取json数据的文件,我们使用import在开始导入就好了

import json

        很明显可以看见,我们的SeedBank.py文件当中有设计专门读取文件的代码,那么在种子卡槽创建完毕后,我们就可以调用一次,那么这个功能算种子卡槽的核心功能了吧。

        接下来先读取json文件内容,json的库是标准库,可以解析json数据,不需要额外的pip,介绍情况文档:

json库 - 官方文档

        在读取完json文件后,我们就需要使用到click函数来控制点击卡片后的效果了,先在Card.py文件当中找到卡片类里面的click函数,检测鼠标位置是不是在这张图片上

Card.py里面的函数

    def click(self):
        """鼠标点击的事件"""
        mouse_x, mouse_y = pygame.mouse.get_pos()
        if self.x < mouse_x < self.x + self.w and self.y < mouse_y < self.y + self.h:
            return True  # 当鼠标处于图片这个区间内,返回真的内容,点击事件需要额外被调用

        点击事件额外来进行调用会更好一点,所以需要在Other_function.py文件当中找到SeedBank类,并且也给它添加点击事件,一开始点击事件我想被SeedBank对象调用的,但是发现很容易出现点击关闭按钮没办法关闭窗口的情况,所以,后面我把点击事件单独放到了main.py文件当中,下面的内容是SeedBank对象里面的全部代码,新增了click的函数,同时方便使用,在init初始化中,也加入了一下卡片的东西,下面是SeedBank类的全部代码

class SeedBank(pygame.sprite.Sprite):
    def __init__(self, window, sun: int, card_list: list, **kwargs):
        """
        植物卡槽
        :param window: 绘制的窗口
        :param sun: 阳光的数量
        :param card_list: 卡片列表,传入全部卡片,实例化使用
        :param kwargs: 用于Text对象的调用
        """
        pygame.sprite.Sprite.__init__(self)  # 初始格式化
        self.window = window  # 窗口
        self.sun = sun  # 当前的阳光
        self.card_list = card_list  # 植物的数量,决定种子银行的宽度,一张图片的大小是 60 * 80
        self.num = len(self.card_list)  # 得到卡片的数量决定卡槽大小
        self.w = self.num * 60 + (self.num - 1) * 2 + self.num  # 宽度等于卡牌的总长度加上间隙

        self.card = []  # 植物卡槽里面的种子
        self.draw_plant = None  # 绘制出鼠标跟随的植物,即选中植物后的操作

        self.sun_img = pygame.image.load("File/OtherPicture/SUN.png")
        self.seed_bank = pygame.transform.scale(  # 植物卡槽的图片信息
            pygame.image.load("File/OtherPicture/seed_bank.png"), (self.w, 87))
        self.sun_pos = (20, 0)  # 获取阳光显示位置的坐标,用于阳光的数量显示
        self.seed_pos = (85, 0)  # 获取到种子银行的坐标

        self.Sun = Text(self.window, str(self.sun),
                        self.sun_pos[0] + self.sun_img.get_rect().size[0] / 2,
                        self.sun_pos[1] + self.sun_img.get_rect().bottom,
                        **kwargs)
        self.load_card()

    def load_card(self):
        with open("File/FileData/Card.json", "r", encoding="utf-8") as f:
            data = json.load(f)
        for num, i in enumerate(self.card_list):  # 遍历全部的编号数据内容
            card = Card.card(self.window, data[i], num)  # 实例化卡片内容
            self.card.append(card)  # 添加进实例化后的内容

    def click(self, click_type='LEFT'):
        """判断鼠标是否有被点击"""
        if click_type == "LEFT" and not self.draw_plant:  # 点击左键,并且没有绘制植物的情况
            for i in self.card:
                if i.click():
                    self.draw_plant = pygame.image.load(i.data["image"])  # 加载图片
                    break
        elif click_type == "RIGHT" and self.draw_plant:
            self.draw_plant = None
        # for event in pygame.event.get():
        #     if event.type == pygame.MOUSEBUTTONDOWN:
        #         if event.button == 1 and not self.draw_plant:  # 鼠标左键按下事件
        #             for i in self.card:
        #                 if i.click():
        #                     self.draw_plant = pygame.image.load(i.data["image"])  # 加载植物图片
        #         elif event.button == 2:  # 鼠标右键按下事件
        #             if self.draw_plant:
        #                 self.draw_plant = None

    def change_sun(self):
        """更新阳光数据内容"""
        self.Sun.change_src(str(self.sun))

    def draw(self):
        self.window.blit(self.seed_bank, self.seed_pos)  # 画出种子银行
        self.window.blit(self.sun_img, self.sun_pos)  # 画出阳光槽
        self.Sun.draw()
        for i in self.card:
            i.draw()
        if self.draw_plant:  # 如果选择了植物,那么就绘制选中的植物
            x, y = pygame.mouse.get_pos()
            x -= self.draw_plant.get_width() / 2
            y -= self.draw_plant.get_height() / 2
            self.window.blit(self.draw_plant, (x, y))

    def run(self):
        pass

        再接下来就是main.py文件下的点击事件加入新的内容

mouse_dicts = {1: "LEFT", 2: "MIDDLE", 3: "RIGHT"}  # 鼠标按下后对应按钮返回的数据,写在种子银行这个类创建之后一行
def other_event():
    """一些其他事件,如点击了关闭按钮"""
    for event in pygame.event.get():
        if event.type == QUIT:
            return True
        elif event.type == pygame.MOUSEBUTTONDOWN:
            """鼠标点击下的事件"""
            Seed_Bank.click(mouse_dicts[event.button])

        到这里,就算是完成了第二步了,那么我们下期见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值