一.目标
当新的一天开始的时候,会有概率出现下雨的天气。完成下雨的动画效果,并且下雨的时候农田会被自动灌溉
二.代码的编写
首先控制下雨的代码会写到sky.py中,让我们先新建这个文件
我们创建雨滴动画效果的思路就是,把雨滴分为两种,如下图所示,一种是落到地上的,一种是在空中的。
我们书写一个雨滴精灵,图像分别为上述的两种雨滴,如果下雨,我们不断在整个屏幕的随机位置生成雨滴精灵,并且在400毫秒到500毫秒之后把它清除掉,这样就会达到雨滴落下的视觉效果。但是正在空中的雨滴,我们还需要他有一个向下的动画,这个很简单,首先判断这个雨滴是否是在空中的雨滴,这个只需要在传递参数的时候传递一个叫move的参数,如果move为true,就说明这个雨滴是正在空中的雨滴,是需要向左下方移动的,只要给他一个向左下的二维矢量当作速度,像我们之前写玩家的移动一样让它动起来即可。
里面的代码基本都在写player类或者泥土类的时候反复写过,没什么好说的
初始化就是把上述两种雨滴的图片导入进来,其中每种雨滴都有三张不同的图片,我们会随机选择一张来充当雨滴的样子。
因为我们需要在地图上的随机位置生成雨滴,所以后续会用到randint(0,地图的宽/高),所以提前获取地图的宽高
接下来是生成在地板上的雨滴的函数,生成地板上的雨滴不需要移动,所以moving设为False
接下来,写一个update函数,在update函数里调用这两个create函数
这样,我们在外面创建一个Rain的对象,然后调用这个对象的update方法,就会在地图的随机地方,分别生成一个落在地上的雨滴,和在空中的雨滴,经过400到500ms之后再消失。如果是雨天,我们就每一帧调用一次update函数,就会呈现朦胧细雨的效果
这就需要我们来到level.py中,先把Rain这个类导入进来
接下来我们需要一个bool值来表示现在是否下雨,我们需要实现随机下雨的效果,所以生成一个从0到9的随机数,如果这个数字大于8,就下雨
当然,每当进入新的一天,需要重新随机决定是否今天会下雨(下图的代码写错了,应该是 > 8 而不是 > 3)具体大于多少可以根据自己喜好调节,如果大于3,就会经常下雨
接下来,我们需要对我们的soil.py文件进行改动,让它知道现在是否正在下雨,如果下雨了,就把农田全都浇灌了
首先实现给所有被开垦过的农田都浇水的效果,这个没什么好多说的,刚好与移除所有浇水效果翻过来
这个函数会遍历grid这个比较大的二维列表,显然是比较费时间的,所以我们不能让level的run函数调用它,来每一帧检测是否下雨,如果下雨就浇灌全部的土地,肯定会很卡。
我们只需要想想什么情况下需要自动浇灌土地,无非如下两种情况:新的一天开始了,今天是下雨天,就把所有被开垦过的土地浇水。还有一种情况是今天是下雨天,玩家开垦了新的土地,这个土地也会被自动浇水。
接下来回到level.py中,每当开始新的一天的时候,判断是否下雨,如果下雨,就调用water_all
接下来我们在下雨天新开垦土地的时候,就会自动灌溉,所以我们需要在soil.py中知道今天是否下雨,按照之前的方法,我们得再实例化Soil_layer对象的时候把self.raining当成参数传进去,不过这次我们使用另一种方法,直接在level.py中给soil_layer创建一个新的变量,当然,我们的level中有两处地方创建raining,所以别忘了两处都需要给soil_layer传递变量
这样,我们就能直接在Soil_layer类中直接使用这个变量了,我们直接在开垦土地的时候判断今天是否会下雨,如果下雨,就自动浇灌这块土地即可
此时运行程序,发现下雨天之前开垦的土地并不会被灌溉,但是新开垦的土地会被灌溉,发现问题出在:每当新开始一天的时候,需要先清除水迹,再判断是否下雨并且water_all,如果顺序反过来了,就会刚刚water_all的水迹立刻又被清楚了
三.完整代码
import pygame
from settings import *
from player import Player
from overlay import Overlay
from sprites import *
from pytmx.util_pygame import load_pygame
from support import *
from transition import Transition
from soil import SoilLayer
from sky import Rain
class Level():
def __init__(self):
#得到屏幕的画面,得到的这个画面与main.py中的screen相同
self.display_surface = pygame.display.get_surface()
#创建精灵组
self.all_sprites = CameraGroup()
#具有碰撞箱的精灵组
self.collision_sprites = pygame.sprite.Group()
#树木精灵组
self.tree_sprites = pygame.sprite.Group()
#特殊区域精灵组
self.interaction_sprites = pygame.sprite.Group()
#调用setup方法
self.setup()
#创建工具和种子显示图层
self.overlay = Overlay(self.player)
#创建transition对象
self.transition = Transition(self.reset,self.player)
#实例化Rain对象
self.rain = Rain(self.all_sprites)
#一个bool值,表示是否正在下雨,有1/10的概率为true
self.raining = randint(0,10) > 8
self.soil_layer.raining = self.raining
def setup(self):
#土地管理类
self.soil_layer = SoilLayer(self.all_sprites)
#载入.tmx文件
tmx_data = load_pygame('../data/map.tmx')
#绘制房子与栅栏,他们都属于Generic类
for layer in ['HouseFloor', 'HouseFurnitureBottom']:
for x, y, surf in tmx_data.get_layer_by_name(layer).tiles():
Generic((x * TILE_SIZE, y * TILE_SIZE), surf, self.all_sprites, LAYERS['house bottom'])
for layer in ['HouseWalls', 'HouseFurnitureTop','Fence']:
for x, y, surf in tmx_data.get_layer_by_name(layer).tiles():
Generic((x * TILE_SIZE, y * TILE_SIZE), surf, [self.all_sprites, self.collision_sprites])
#水流
water_frames = import_folder('../graphics/water')
for x, y, surf in tmx_data.get_layer_by_name('Water').tiles():
Water((x * TILE_SIZE, y * TILE_SIZE), water_frames, self.all_sprites)
#树木
for obj in tmx_data.get_layer_by_name('Trees'):
Tree(
pos=(obj.x, obj.y),
surf=obj.image,
groups=[self.all_sprites, self.collision_sprites, self.tree_sprites],
name=obj.name,
player_add=self.player_add)
#野花
for obj in tmx_data.get_layer_by_name('Decoration'):
WildFlower((obj.x, obj.y), obj.image,[self.all_sprites, self.collision_sprites])
#空气墙
for x, y, surf in tmx_data.get_layer_by_name('Collision').tiles():
Generic((x * TILE_SIZE, y * TILE_SIZE), pygame.Surface((TILE_SIZE, TILE_SIZE)), self.collision_sprites)
#玩家
for obj in tmx_data.get_layer_by_name('Player'):
if obj.name == 'Start':
self.player = Player(
pos=(obj.x, obj.y),
group=self.all_sprites,
collision_sprites=self.collision_sprites,
tree_sprites=self.tree_sprites,
interaction = self.interaction_sprites,
soil_layer = self.soil_layer)
if obj.name == 'Bed':
Interaction((obj.x, obj.y), (obj.width, obj.height), self.interaction_sprites, obj.name)
Generic(
pos = (0,0),
surf = pygame.image.load('../graphics/world/ground.png').convert_alpha(),
groups = self.all_sprites,
z = LAYERS['ground']
)
def run(self,dt):
#窗口的背景设为黑色
self.display_surface.fill('black')
#调用精灵组的draw方法
self.all_sprites.custom_draw(self.player)
#调用精灵组的update方法
self.all_sprites.update(dt)
self.overlay.display()
#如果在睡觉,执行相应的函数
if self.player.sleep:
self.transition.play()
#如果在下雨,就生成雨滴
if self.raining:
self.rain.update()
def player_add(self, item):
#item是一个str类型的数据,代表要对哪一种物品加一
self.player.item_inventory[item] += 1
def reset(self):
# 清除农田里的水渍
self.soil_layer.remove_water()
#每天重新随机一下是否要下雨
self.raining = randint(0, 10) > 8
self.soil_layer.raining = self.raining
#如果新的一天下雨了,就自动灌溉所有被开垦过的土地
if self.raining:
self.soil_layer.water_all()
#苹果重新长在树上
for tree in self.tree_sprites.sprites():
for apple in tree.apple_sprites.sprites():
apple.kill()
tree.create_fruit()
class CameraGroup(pygame.sprite.Group):
def __init__(self):
super().__init__()
#获取窗口
self.display_surface = pygame.display.get_surface()
#这是一个偏移量,代表的是玩家的实际位置与屏幕中间的矢量
self.offset = pygame.math.Vector2()
def custom_draw(self,player):
self.offset.x = player.rect.centerx - SCREEN_WIDTH / 2
self.offset.y = player.rect.centery - SCREEN_HEIGHT / 2
for layer in LAYERS.values():#按照z轴从小到达绘制
for sprite in sorted(self.sprites(),key = lambda sprite: sprite.rect.centery):
if sprite.z == layer:#如果该精灵的z值等于当前要绘制的z值,才绘制
offset_rect = sprite.rect.copy()
offset_rect.center -= self.offset
#if sprite == player:
#print("player.rect.center为:(" +
# str(player.rect.centerx)+"," + str(player.rect.centery)+")")
#print("offset_rect为:(" + str(offset_rect.x)
# +"," +str(offset_rect.y)+")")
self.display_surface.blit(sprite.image,offset_rect)
import pygame
from settings import *
from sprites import Generic
from random import *
from support import import_folder
#雨滴精灵
class Drop(Generic):
def __init__(self, surf, pos, moving, groups, z):
#参数中moving表示是否是移动的雨滴,雨滴分为两种,已经落到地上的我们就不让他移动,在空中的我们给他一个向着左下的速度
super().__init__(pos, surf, groups, z)
#雨滴这个精灵的存活时间
self.lifetime = randint(400, 500)
#计时器的开始时间
self.start_time = pygame.time.get_ticks()
#如果moving为true,表示这个雨滴是需要移动的
self.moving = moving
#精灵移动所需要的三个参数
if self.moving:
self.pos = pygame.math.Vector2(self.rect.topleft)
self.direction = pygame.math.Vector2(-2, 4)
self.speed = randint(200, 250)
def update(self, dt):
#让雨滴动起来
if self.moving:
self.pos += self.direction * self.speed * dt
self.rect.topleft = (round(self.pos.x), round(self.pos.y))
#过了存活时间,把这个精灵kill掉
if pygame.time.get_ticks() - self.start_time >= self.lifetime:
self.kill()
#掌管下雨的类
class Rain:
def __init__(self,all_sprites):
self.all_sprites = all_sprites
#导入雨滴正在落下的图片,一共三张,以列表的形式返回
self.rain_drops = import_folder('../graphics/rain/drops/')
# 导入雨滴落到地板的图片,一共三张,以列表的形式返回
self.rain_floor = import_folder('../graphics/rain/floor/')
#整张地图的宽度和高度,我们需要这两个数据取随机数,在随机的位置生成雨滴
self.floor_w, self.floor_h = pygame.image.load('../graphics/world/ground.png').get_size()
#生成落到地板上的雨滴
def create_floor(self):
Drop(
surf=choice(self.rain_floor),
pos=(randint(0, self.floor_w), randint(0, self.floor_h)),
moving=False,
groups=self.all_sprites,
z=LAYERS['rain floor'])
#生成在空中的雨滴
def create_drops(self):
Drop(
surf=choice(self.rain_drops),
pos=(randint(0, self.floor_w), randint(0, self.floor_h)),
moving=True,
groups=self.all_sprites,
z=LAYERS['rain drops'])
def update(self):
self.create_floor()
self.create_drops()
import pygame
from settings import *
from pytmx.util_pygame import load_pygame
from support import *
from random import choice
#开垦过的土地
class SoilTile(pygame.sprite.Sprite):
def __init__(self, pos, surf, groups):
super().__init__(groups)
self.image = surf
self.rect = self.image.get_rect(topleft = pos)
self.z = LAYERS['soil']
#浇过水的土地
class WaterTile(pygame.sprite.Sprite):
def __init__(self, pos, surf, groups):
super().__init__(groups)
self.image = surf
self.rect = self.image.get_rect(topleft = pos)
self.z = LAYERS['soil water']
#管理所有土地的类
class SoilLayer:
def __init__(self,all_sprites):
#all_sprites精灵组
self.all_sprites = all_sprites
#创建开垦过的土地精灵组
self.soil_sprites = pygame.sprite.Group()
#浇过水的土地精灵组
self.water_sprites = pygame.sprite.Group()
#导入开垦过的土地的图片
self.soil_surf = pygame.image.load('../graphics/soil/o.png')
#获取浇水图片,一共有三张
self.water_surfs = import_folder('../graphics/soil_water')
self.create_soil_grid()
self.create_hit_rects()
def create_soil_grid(self):
#导入地图的图片
ground = pygame.image.load('../graphics/world/ground.png')
#地图的宽度,高度除以64,就是一共有多少块(64*64)的土地
h_tiles, v_tiles = ground.get_width() // TILE_SIZE, ground.get_height() // TILE_SIZE
#创建一个三维列表,最一开始,所有土地的属性都是空的
self.grid = [[[] for col in range(h_tiles)] for row in range(v_tiles)]
#如果是可以被耕种的土地块,就给他的列表添加一个标记'F'
for x, y, _ in load_pygame('../data/map.tmx').get_layer_by_name('Farmable').tiles():
self.grid[y][x].append('F')
def create_hit_rects(self):
#列表里存放的是所有能被耕种的土地,数据类型是pygame里提供的Rect数据类型,包含了坐标和宽高
self.hit_rects = []
for index_row, row in enumerate(self.grid):
for index_col, cell in enumerate(row):
#如果是能被耕种的
if 'F' in cell:
x = index_col * TILE_SIZE
y = index_row * TILE_SIZE
rect = pygame.Rect(x, y, TILE_SIZE, TILE_SIZE)
self.hit_rects.append(rect)
def get_hit(self, point):
#point是传入的参数,代表的是玩家挥舞的锄头的落点的坐标
for rect in self.hit_rects:
#如果该可耕种的土地块与玩家的锄头坐标发生了重叠,说明玩家锄的就是这块地,就给他的属性再加上一个标记'X'
if rect.collidepoint(point):
#相反,这里得把pygame的坐标系转换成tmx文件中的坐标系
x = rect.x // TILE_SIZE
y = rect.y // TILE_SIZE
if 'F' in self.grid[y][x]:
self.grid[y][x].append('X')
SoilTile(
pos=(rect.x,rect.y),
surf=self.soil_surf,
groups=[self.all_sprites, self.soil_sprites])
if self.raining:
self.water(point)
#self.create_soil_tiles()
def water(self, target_pos):
for soil_sprite in self.soil_sprites.sprites():
if soil_sprite.rect.collidepoint(target_pos):
x = soil_sprite.rect.x // TILE_SIZE
y = soil_sprite.rect.y // TILE_SIZE
#给grid添加”W“标记
self.grid[y][x].append('W')
pos = soil_sprite.rect.topleft
#随机选择三张之中的一张图片,来作为这个精灵的image
surf = choice(self.water_surfs)
WaterTile(pos, surf, [self.all_sprites, self.water_sprites])
def remove_water(self):
#删除所有的精灵
for sprite in self.water_sprites.sprites():
sprite.kill()
#同时别忘了清理grid中的标记
for row in self.grid:
for cell in row:
if 'W' in cell:
cell.remove('W')
#给所有被开垦过的农田浇水
def water_all(self):
for index_row, row in enumerate(self.grid):
for index_col, cell in enumerate(row):
if 'X' in cell and 'W' not in cell:
cell.append('W')
x = index_col * TILE_SIZE
y = index_row * TILE_SIZE
WaterTile((x, y), choice(self.water_surfs), [self.all_sprites, self.water_sprites])
"""
def create_soil_tiles(self):
self.soil_sprites.empty()
for index_row, row in enumerate(self.grid):
for index_col, cell in enumerate(row):
if 'X' in cell:
SoilTile(
pos=(index_col * TILE_SIZE, index_row * TILE_SIZE),
surf=self.soil_surf,
groups=[self.all_sprites, self.soil_sprites])"""