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
類SquishSprite繼承了類pygame.sprite.Spirte,子類覆蓋了父類的建構函式,設定 image,rect和area的屬性,呼叫pygame.diosplay.get_surface()返回surfuce物件,然後指定在一定範圍內移動。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)
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方法中設定了視窗模式和視窗標題,隱藏滑鼠,主迴圈中時各種狀態之間的轉換,並且每更新一個狀態就顯示一個狀態。