注:这个系列文章的全部内容里面包含自己写的一些思路,难免会有时候同一个文章中需要多次修改代码的情况,但是编程就是这样(个人觉得)在修改中不断的完善代码,慢慢解决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( )方法——一首歌的时间学会_一个兴趣使然的程序猿罢了的博客
因为阳光数量是随时在变化的,显示要有文字的功能,所以在这里,我创建了两个类,分别是文本的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文件读取,所以我需要在Other_function.py文件当中导入json环境,方便读取json数据的文件,我们使用import在开始导入就好了
import json
很明显可以看见,我们的SeedBank.py文件当中有设计专门读取文件的代码,那么在种子卡槽创建完毕后,我们就可以调用一次,那么这个功能算种子卡槽的核心功能了吧。
接下来先读取json文件内容,json的库是标准库,可以解析json数据,不需要额外的pip,介绍情况文档:
在读取完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])
到这里,就算是完成了第二步了,那么我们下期见