1. 程式人生 > >pygame模組中的小遊戲。

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方法中設定了視窗模式和視窗標題,隱藏滑鼠,主迴圈中時各種狀態之間的轉換,並且每更新一個狀態就顯示一個狀態。