pygame模块中的小游戏。

终于到最后一个项目了,一个叫做’天上掉秤砣的游戏‘, 从逻辑上理解这个游戏挺容易的,说到底就是不让秤砣砸中香蕉,大家小时候也玩过的一种游戏,还记得小时候玩过的“小霸王游戏机”吗?作为一个游戏,那肯定要有开始,运行和结束。所有的一切由你控制,最重要的当然是运行,游戏的规则逻辑都在运行里面实现,而这个游戏是用pygame模块实现的,想用pygame模块刚开始就必须初始化pygame.init(),初始化他的一些基本属性,例如窗口,背景,图片之类的。游戏一般都是动画和文字的组合,动画就是连续多个图片的组合,比如30帧/秒,而游戏就是时时刻刻根据你的输入而更新图片的状态位置。

先看看游戏刚开始的配置代码config.py:

banana_image = 'banana.png'
weight_image = 'weight.png'
splash_image = 'weight.png'

screen_size = 800,600
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
里面给出了香蕉和秤砣的图片,规定了窗口的大小和背景颜色为白色,文字的大小,还有秤砣的降落速度和香蕉的移动速度,以及降落10个秤砣为一个关卡等一些基本的配置信息。

接着看游戏对象,objects.py

import pygame, config, os
from random import randrange


class SquishSprite(pygame.sprite.Sprite):

    def __init__(self, image):
        pygame.sprite.Sprite.__init__(self)
        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):

    def __init__(self, speed):
        SquishSprite.__init__(self, config.weight_image)
        self.speed = speed
        self.reset()

    def reset(self):
        x = randrange(self.area.left, self.area.right)
        self.rect.midbottom = x, 0

    def update(self):
        self.rect.top += self.speed
        self.landed = self.rect.top >= self.area.bottom


class Banana(SquishSprite):

    def __init__(self):
        SquishSprite.__init__(self, 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):
        self.rect.centerx = pygame.mouse.get_pos()[0]
        self.rect = self.rect.clamp(self.area)

    def touches(self, other):
        bounds = self.rect.inflate(-self.pad_side, -self.pad_top)
        bounds.bottom = self.rect.bottom
        return bounds.colliderect(other.rect)










类SquishSprite继承了类pygame.sprite.Spirte,子类覆盖了父类的构造函数,设定 image,rect和area的属性,调用pygame.diosplay.get_surface()返回surfuce对象,然后指定在一定范围内移动。

Weight类继承了上面的类SquishSprite,一开始就调用父类的构造函数初始化了秤砣的图片,位置和移动区域,接着再初始化秤砣降落的速度speed,方法reset()是将秤砣移动到屏幕顶端,放置到任意水平位置上。然后是一个更新方法update(),根据速度来计算秤砣图片的位置是否已经触到屏幕底端,以此来设置landed的属性。

同样,类Banana也是继承了SquishSprite类,除了调用父类的构造函数来初始化图片,位置的一些特性外,还要设定一些秤砣和香蕉碰撞的一些参数(和香蕉的边缘和上面碰撞的参数),就是什么程度算碰撞上了,然后也是个update方法,把香蕉的中心设置为鼠标所在位置的横坐标,以用鼠标来控制香蕉的移动,香蕉也就只能在底端水平方向移动,然后调用clamp方法来控制香蕉只能在屏幕这块区域移动,出不去屏幕。最后是方法touches,使用rect的inflate方法对香蕉图片的边缘和上方进行填充,缩小香蕉图片的边界,移动边界,将他们放置到Banana的底部,检查边界是否和其他对象的rect接触,来判定秤砣是否撞到了香蕉。


最后是主函数,squish.py

import os, sys, pygame
from pygame.locals import *
import objects, config

class State:
    def handle(self,event):
        if event.type == QUIT:
            sys.exit()
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            sys.exit()

    def firstDisplay(self, screen):
        screen.fill(config.background_color)
        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 += (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()

        if self.banana.touches(self.weight):
            game.nextState = GameOver()

        elif self.weight.landed:
            self.weight.reset()
            self.remaining -= 1
            if self.remaining == 0:
                game.nextState = LevelCleared(self.number)

    def display(self, screen):
        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.handle(self, event)
        if event.type in [MOUSEBUTTONDOWN,KEYDOWN]:
            self.finished = 1

    def update(self, game):
        if self.finished:
            game.nextState = self.nextState()

    def firstDisplay(self, screen):
        screen.fill(config.background_color)

        font = pygame.font.Font(None, config.font_size)
        lines = self.text.strip().splitlines()

        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()

            r = image.get_rect()
            top += r.height // 2

            r.midbottom = center, top -20

            screen.blit(image, r)

        antialias = 1
        black = 0,0,0


        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):

    nextState = Level
    text = '''
    In this game you are a banana,
    trying to survive a course in
    self-defense against fruit,where the
    participants will 'defend' themselves
    against you with a 16 ton weight.'''

class StartUp(Paused):

    nextState = Info
    image = config.splash_image
    text = '''
    Welcome to Squish.
    the game of Fruit Self-Defense'''

class LevelCleared(Paused):

    def __init__(self, number):
            self.number = number
            self.text = '''Level %i cleared
            Click to start next level''' % self.number

    def nextState(self):
            return Level(self.number + 1)

class GameOver(Paused):
    nextState = 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

        self.nextState = StartUp()

    def run(self):
        pygame.init()

        flag = 0

        if config.full_screen:
                flag = FULLSCREEN
        screen_size = config.screen_size
        screen = pygame.display.set_mode(screen_size)

        pygame.display.set_caption('Fruit Self Defense')
        pygame.mouse.set_visible(False)

        while True:
            if self.state != self.nextState:
                self.state = self.nextState
                self.state.firstDisplay(screen)

            for event in pygame.event.get():
                self.state.handle(event)
            self.state.update(self)

            self.state.display(screen)

if __name__ == '__main__':
    game = Game(*sys.argv)
    game.run()
主程序中导入了上面的两个模块,控制了游戏的主要逻辑。

状态类State是下面类的基类,该类中的方法hanle用于处理退出事件,只要你点了关闭按钮或者在键盘上按下了ESC按钮,都会被获取到,从而退出游戏。方法fistDisplay用于第一次显示 状态,fill方法是填充屏幕的背景颜色,然后必须调用flip方法,来使更改可见,下面这个display方法是个什么都不做的方法。

类Level继承了State,默认下number特性为1,他指的是关卡,weights_per_level是还需要降落多少秤砣才可以到下一个关卡,接着是降落的速度,speed += (self.number - 1) *config.speed_increase是为每个大于1关卡都增加一个速度值(speed_increase),下面就是创建类Weight和类Banana的对象,其中Weight中需要传递参数speed,秤砣的下降速度,然后将两个对象作为元组放在both中,接着又创建了类RenderUpdates的对象。方法update用于更新游戏状态,self.sprite.update()就是更新所有的子图形,然后是判别香蕉是否接触到了秤砣,如果接触到了游戏状态就切换到GameOver状态,否则判别秤砣的landed特性,他记录了秤砣是否到屏幕底下,reset方法是让秤砣重新复位,会发哦屏幕方法,还要记录落下多少个秤砣,每一关是10个,落完就会切换到LevelCleared状态,下面是个方法display,填充屏幕的背景颜色,draw方法是绘图方法,他返回一个矩形列表,然后再更新这个矩形列表。

类Paused也继承了State,finished记录了按键是否被按下,真为按下,方法update是更新游戏状态,如果finished为真,则切换到下一个状态,方法firstDisplay是当暂停状态出现的时候,屏幕的一些图像和文本,self.text.strip().splitlines()是用来获取text中的文本行,并忽略开头和结尾的空行,然后是计算文本的高度和文本应放置的位置,然后是对图片的一些操作,只是这个暂停状态没加载入图片,然后是对一些文本位置的操作,最后是显示。

类Info显示游戏的一些信息,在level状态后显示。

类StartUp是显示图片和欢迎信息的状态,在Info状态后显示。

类LebelCleared提示用户过关后的暂停信息,在next level状态后显示。比如第几关的信息, 方法nextState中返回了下一关的信息。

类GameOver是提示用户游戏结束的信息,在firstlevel后显示。

最后是类Game,负责主事件循环的游戏对象,构造函数中调用了os.path,获取游戏和图像放置的目录,run方法中设置了窗口模式和窗口标题,隐藏鼠标,主循环中时各种状态之间的转换,并且每更新一个状态就显示一个状态。






  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值