通过python基础教程(版本三)学习得到了提升
鼠标控制,全屏游戏,关卡游戏,不同的关卡会有不同的小黄人出现,难度也会随之不同
有背景音乐,有不同的小黄人图片
首先,先把需要的变量定义在config.py中
# 游戏squish的配置文件
# -------------------------------------
# 可根据偏好随意修改配置变量
# 如果游戏的节奏太快或太慢, 可尝试修改与速度相关的变量
# 要在这个游戏中使用其他图像, 可修改这些变量:
banana_image = 'banana.png'
splash_image = 'timg.jpg'
wav = 'SOY.mp3'
# 这些配置决定了游戏的总体外观:
screen_size = 1920, 1080
background_color = 255, 255, 255
margin = 30
full_screen = 1
font_size = 48
# 这些设置决定了游戏的行为:
drop_speed = 1
banana_speed = 10
speed_increase = 1
weights_per_level = 10
banana_pad_top = 40
banana_pad_side = 20
定义一个基本Sprite类,把小黄人和香蕉定义为它的子类,放在objects.py中
import pygame, config, os, random
from random import randrange
'这个模块包含游戏Squish使用的游戏对象'
class SquishSprite(pygame.sprite.Sprite):
'''
游戏Squish中所有精灵(sprite)的超类. 构造函数
加载一幅图像, 设置精灵的外接矩形和移动范围. 移
动范围取决于屏幕尺寸和边距
'''
def __init__(self, image):
super().__init__()
self.image = pygame.image.load(image).convert()
self.rect = self.image.get_rect()
screen = pygame.display.get_surface()
shrink = -config.margin * 2
self.area = screen.get_rect().inflate(shrink, shrink)
class Weight(SquishSprite):
'''
从天而降的铅锤. 它使用SquishSprite的构造函数来设置表
示铅锤的图像, 并以其构造函数的一个参数指定的速度下降
'''
def __init__(self, speed):
super().__init__('yellow ({}).png'.format(random.randrange(1, 8)))
self.speed = speed
self.reset()
def reset(self):
'''
将铅锤移到屏幕顶端(刚好看不到)的一个随机位置
'''
x = randrange(self.area.left, self.area.right)
self.rect.midbottom = x, 0
def update(self):
'''
根据铅锤的速度垂直向下移动相应的距离. 同时, 根据
铅锤是否已达到屏幕底部相应地设置属性landed
'''
self.rect.top += self.speed
self.landed = self.rect.top >= self.area.bottom
class Banana(SquishSprite):
'''
绝望的香蕉. 它使用SquishSprite的构造函数来设置香蕉图像, 并停留
在屏幕底部附近, 且水平位置由鼠标的当前位置决定(有一定的限制)
'''
def __init__(self):
super().__init__(config.banana_image)
self.rect.bottom = self.area.bottom
# 这些内边表示图像中不属于香蕉的部分
# 如果铅锤进入这些区域, 并不认为它砸到了香蕉:
self.pad_top = config.banana_pad_top
self.pad_side = config.banana_pad_side
def update(self):
'''
将香蕉中心的x坐标设置为鼠标当前x坐标, 再使用
矩形的方法clamp确保香蕉位于允许的移动范围内
'''
self.rect.centerx = pygame.mouse.get_pos()[0]
self.rect =self.rect.clamp(self.area)
def touches(self, other):
'''
判断香蕉是否与另一个精灵(如铅锤)发生了碰撞. 这里没有直接
使用矩形的方法colliderect, 而是先使用矩形的方法inflat
以及pad_side和pad_top计算出一个新的矩形, 这个矩形不包含
香蕉图像顶部和两边的'空白'区域
'''
# 通过剔除内边距来计算bounds:
bounds = self.rect.inflate(-self.pad_side, -self.pad_top)
# 将bounds移动到与香蕉底部对齐:
bounds.bottom = self.rect.bottom
# 检查bounds是否与另一个对象的rect重叠
return bounds.colliderect(other.rect)
把主程序放在Squish.py中
import os, sys, pygame
from pygame.locals import *
import objects, config
'这个模块包含游戏Squish的主游戏逻辑'
class State:
'''
游戏状态超类, 能够处理事件以及在指定表面上显示自己
'''
def handle(self, event):
'''
只处理退出事件的默认事件处理
'''
if event.type == QUIT:
sys.exit()
if event.type == KEYDOWN and event.key == K_ESCAPE:
sys.exit()
def first_display(self, screen):
'''
在首次显示状态时使用, 它使用背景色填充屏幕
'''
screen.fill(config.background_color)
# 别忘了调用filp, 把修改反映出来
pygame.display.flip()
def display(self, screen):
'''
在后续显示状态时使用, 其默认行为时什么都不做
'''
pass
class Level(State):
'''
游戏关卡. 他计算落下了多少个铅锤, 移动精灵并执行其他与游戏逻辑相关的任务
'''
def __init__(self, number = 1):
self.number = number
# 还需躲开多少个铅锤才能通过当前关卡?
self.remaining = config.weights_per_level
speed = config.drop_speed
# 每过一关都将速度提高speed_increase:
speed += (self.number - 1) * config.speed_increase
# 创建铅锤和香蕉:
self.weight = objects.Weight(speed)
self.banana = objects.Banana()
both = self.weight, self.banana # 可包含更多精灵
self.sprites = pygame.sprite.RenderUpdates(both)
def update(self, game):
'更新游戏状态'
# 更新所有的精灵
self.sprites.update()
# 如果香蕉和铅锤发生了碰撞, 就让游戏切换到GameOver状态:
if self.banana.touches(self.weight):
game.next_state = GameOver()
# 否则, 如果铅锤已落到地上, 就将其复位
if self.weight.landed:
self.weight.reset()
self.remaining -= 1
if self.remaining == 0:
game.next_state = LevelCleared(self.number)
def display(self, screen):
'''
在第一次显示(清屏)后显示状态. 不同于firstDisplay,
这个方法调用pygame.display.update并向他传递一个需要
更新的矩形列表, 这个列表是由self.sprites.draw提供的
'''
screen.fill(config.background_color)
updates = self.sprites.draw(screen)
pygame.display.update(updates)
class Paused(State):
'''
简单的游戏暂停状态, 用户可通过按任意键盘键或单击鼠标来结束这种状态
'''
finished = 0 # 用户结束暂停了吗?
image = None # 如果需要显示图像, 将这个属性设置为一个文件名
text = '' # 将这个属性设置为一些说明性文本
def handle(self, event):
'''
这样来处理事件:将这项任务委托给State(它只处理退出事件),
并对按键和鼠标单击做出相应, 如果用户按下了键盘键或单击了鼠标,
就将self.finish设置为True
'''
State.handle(self, event)
if event.type in [MOUSEBUTTONDOWN, KEYDOWN]:
self.finished = 1
def update(self, game):
'''
更新关卡. 如果用户按下了键盘键或单击了鼠标(即self.finished为True),
就让游戏切换到(由子类实现的方法)self.next_state()返回的状态
'''
if self.finished:
game.next_state = self.next_state()
def first_display(self, screen):
'''
在首次显示暂停状态时调用, 它绘制图像(如果指定了)并渲染文本
'''
# 首先, 通过使用背景色填充屏幕来清屏:
screen.fill(config.background_color)
# 创建一个使用默认外观和指定字号的Font对象:
font = pygame.font.Font(None, config.font_size)
# 获取self.text中的文本行, 但忽略开头和末尾的空行:
lines = self.text.strip().splitlines()
# 使用font.get_linesize()获取每行文本的高度, 并计算文本的总高度:
height = len(lines) * font.get_linesize()
# 计算文本的位置(在屏幕上居中):
center, top = screen.get_rect().center
top -= height // 2
# 如果有图像要显示:
if self.image:
# 加载该图像
image = pygame.image.load(self.image).convert()
# 获取其rect:
r = image.get_rect()
# 将文本下移图像一半的距离
top += r.height // 2
# 将图像放在文本上方20像素处:
r.midbottom = center, top - 20
# 将图像传输到屏幕上
screen.blit(image, r)
antialias = 1 # 消除文本的锯齿
black = 0, 0, 0 # 使用黑色渲染文本
# 从计算得到的top处开始渲染所有的文本行,
# 每渲染一行都向下移动font.get_linesize()像素:
for line in lines:
text = font.render(line.strip(), antialias, black)
r = text.get_rect()
r.midtop = center, top
screen.blit(text, r)
top += font.get_linesize()
# 显示所做的所有修改:
pygame.display.flip()
class Info(Paused):
'''
显示一些游戏信息的简单暂停状态, 紧跟在这个状态后面的是Level状态(第一关)
'''
next_state = Level
text = '''In this game you are a Banana'''
class StartUp(Paused):
'''
显示启动图像和欢迎信息的暂停状态, 紧跟在它后面的是Info状态
'''
next_state = Info
image = config.splash_image
class LevelCleared(Paused):
'''
指出用户已过关的暂停状态, 紧跟在它后面的是表示下一关的Level状态
'''
def __init__(self, number):
self.number = number
self.text = '''CLEARED
Click to start Level {}'''.format(self.number + 1)
def next_state(self):
return Level(self.number + 1)
class GameOver(Paused):
'''
指出游戏已结束的状态, 紧跟在它后面的是表示第一类的Level状态
'''
next_state = Level
text = '''
Game Over
Click to Restart, Esc to Quit'''
class Game:
'''
负责主事件循环(包括在不同游戏状态之间切换)的游戏对象
'''
def __init__(self, *args):
# 获取游戏和图像所在的目录:
path = os.path.abspath(args[0])
dir = os.path.split(path)[0]
# 切换到这个目录, 以便以后能够打开图像文件
os.chdir(dir)
# 最初不处于任何状态:
self.state = None
# 在一次事件循环中迭代中切换到StartUp状态:
self.next_state = StartUp()
def run(self):
'''
这个方法设置一些变量. 它执行一些重要的初始化任务, 并进入主事件循环
'''
pygame.init() # 初始化所有的Pygame模块
# 决定在窗口还是整个屏幕中显示游戏:
flag = 0 # 默认在窗口中显示游戏
if config.full_screen:
flag = FULLSCREEN # 全屏模式
screen_size = config.screen_size
screen = pygame.display.set_mode(screen_size, flag)
# 背景音乐播放
pygame.mixer.init()
pygame.mixer.music.load(config.wav)
pygame.mixer.music.play()
pygame.display.set_caption('Fruit Self Defense')
pygame.mouse.set_visible(False)
# 主事件循环:
while True:
# (1)如果nextState被修改, 就切换到修改后的状态并显示它(首次):
if self.state != self.next_state:
self.state = self.next_state
self.state.first_display(screen)
# (2)将事件处理工作委托给当前状态:
for event in pygame.event.get():
self.state.handle(event)
# (3)更新当前状态:
self.state.update(self)
# (4)显示当前状态:
self.state.display(screen)
if __name__ == '__main__':
game = Game(*sys.argv)
game.run()
运行测试
开始界面,BGM出现
游戏提示语
关卡进入
结束,完美