開始你的第一個 python 小遊戲——pygame快速上手
pygame
這裏可以移步個人博客
pygame作爲一款優秀的python第三方庫,包含了很多遊戲方面的函數。今天我們就開始來快速上手做一個自己的小遊戲。這裏是 pygame官網 和 官方文檔,下面是遊戲的效果圖
- 在使用 pygame 的同時,可以幫我們更好的熟悉python的語法,爲後面的爬蟲、web和數據分析學習做鋪墊
- 這個遊戲中涉及的一些基本的角色移動跳躍和碰撞,也是大家開始編寫自己的小遊戲的基礎
- 遊戲的素材和代碼來自於油管,大家一起共同學習和借鑑
一、遊戲的基本邏輯和遊戲循環
下面是一個遊戲的最基本的流程
- 遊戲開始
- 遊戲循環
- 監聽鼠標鍵盤事件
- 更新元素的狀態
- 繪製屏幕
- 遊戲結束
而根據遊戲的基本流程,下面就是一個很常用的模板,根據模板來將這個這個遊戲的流程更加具象
import pygame
# 定義窗口大小和FPS
WIDTH = 360
HEIGHT = 400
FPS = 30
# 定義顏色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# 初始化pygame
pygame.init()
# 初始化聲音組件
pygame.mixier.init()
# 根據長寬創建窗口
screen = pygame.display.set_mode([WIDTH, HEIGHT])
# 窗口展示遊戲的名字
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
all_sprites = pygame.sprite.Group()
# 遊戲循環
running = True
while running:
# 保持一定速度
clock.tick(FPS)
# 事件輸入
for event in pygame.event.get():
# 檢查是否關閉窗口
if event.type == pygame.QUIT:
running = False
# 更新狀態
all_sprites.update()
# 繪製窗口
screen.fill(BLACK)
all_sprites.draw(screen)
pygame.display.flip()
pygame.quit()
二、模塊劃分和框架的構建
有了上面的模板我們就可以開始我們的遊戲創作了,然而隨着我們遊戲的邏輯的複雜,代碼全部擠在一個文件中很明顯就不利於我們的後期改進和調整,在這裏我們將其拆分爲大致三個文件:
main.py 用於控制整個遊戲循環和遊戲的開始和結束
import random
from sprites import *
class Game:
def __init__(self):
# 初始化窗口
# 初始化pygame創建窗口
pg.init()
self.screen = pg.display.set_mode([WIDTH, HEIGHT])
pg.display.set_caption(TITLE)
self.clock = pg.time.Clock()
def new(self):
# 創建遊戲所需的對象
def run(self):
# 遊戲循環
self.playing = True
while self.playing:
self.clock.tick(FPS)
self.events()
self.update()
self.draw()
def update(self):
# 更新狀態
def events(self):
# 檢測響應事件
for event in pg.event.get():
# 檢查是否關閉窗口
if event.type == pg.QUIT:
if self.playing:
self.playing = False
self.running = False
def draw(self):
# 繪製窗口
self.screen.fill(BACKGROUND_COLOR)
self.all_sprites.draw(self.screen)
pg.display.flip()
def show_start_screen(self):
# 展示開始屏幕
def show_go_screen(self):
# 展示結束屏幕
g = Game()
g.show_start_screen()
while g.running:
g.new()
g.show_go_screen()
sprite.py 用於我們創建角色,敵人之類的遊戲元素
from settings import *
import pygame as pg
class Player(pg.sprite.Sprite):
# 角色類
class Platform(pg.sprite.Sprite):
# 階梯類
settings.py 用於來專門放置我們遊戲中的參數,起到一個配置類的作用
# 定義遊戲名字
TITLE = "Jumpy !"
# 定義窗口大小和FPS
WIDTH = 400
HEIGHT = 600
FPS = 60
# 定義顏色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
BACKGROUND_COLOR = (0, 155, 155)
# 階梯的位置
PLATFORM_LIST = [(WIDTH / 2 - 30, HEIGHT * 0.8, 150, 20),
(25, HEIGHT - 350, 100, 20),
(350, 200, 100, 20),
(175, 100, 50, 20)]
三、角色、階梯的創建
在pygame中有一個很重要的類就是sprite
類,可以說是所有元素類的父類,爲每一種元素創建一個類繼承該類,並將所有創建的元素註冊到sprite
的組件中。
角色類
class Player(pg.sprite.Sprite):
def __init__(self, game):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((30, 40))
self.image.fill(YELLOW)
# 角色爲一個黃色的方塊
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
self.pos = vec(WIDTH / 2, HEIGHT / 2)
# 角色的初始位置
def update(self):
階梯類
class Platform(pg.sprite.Sprite):
def __init__(self, x, y, w, h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w, h))
self.image.fill(GREEN)
# 填充綠色
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
加入sprite
組件
def new(self):
self.all_sprites = pg.sprite.Group()
self.platforms = pg.sprite.Group()
self.player = Player(self)
self.all_sprites.add(self.player)
self.ground = Platform(0, HEIGHT - 20, WIDTH, 20)
self.all_sprites.add(self.ground)
self.platforms.add(self.ground)
for plat in PLATFORM_LIST:
p = Platform(*plat)
self.all_sprites.add(p)
self.platforms.add(p)
四、定義碰撞和重力效果
座標系
在這之前要先簡單的說明一下pygame中對座標的定義,即頂點在遊戲窗口左上角的直角座標系。他的正方向分別是向右和向下的。所以比如當角色要向上跳躍的時候參數應該是負的
加入重力
# 在角色類中定義
def update(self):
# 加入重力效果
self.acc = vec(0, PLAYER_GRAVITY)
但是運行遊戲之後你會發現小方塊會不停的下落直接穿過了我們定義的"地面"
定義碰撞
# 在整個遊戲類中定義
def update(self):
self.all_sprites.update()
# 確保只有角色是在向下落的時候撞到平臺才能站在平臺上
if self.player.vel.y > 0:
hits = pg.sprite.spritecollide(self.player, self.platforms, False)
if hits:
if self.player.pos.y < hit[0].rect.bottom:
self.player.pos.y = hit[0].rect.top
self.player.vel.y = 0
self.player.jumping = False
碰撞類是pygame中另一個比較重要的函數,角色的很多操作和狀態都會涉及到
五、角色的左右移動和跳躍
移動
在我們對角色進行左右移動的操作時,絕不能僅僅的只是角色的速度向左和向右的增加。而應該對他的加速度參數進行調整,並且加上一定的阻力,這樣角色纔不會一直運動下去,確保速度存在一個峯值。這其中就涉及到了運動學的公式,畢竟這個公式是可以約束地球上所有的物體的,不然整個遊戲給人的感覺就會有失違和。
def update(self):
# 加入重力效果
self.acc = vec(0, PLAYER_GRAVITY)
keys = pg.key.get_pressed()
if keys[pg.K_LEFT]:
self.acc.x = -PLAYER_ACC
if keys[pg.K_RIGHT]:
self.acc.x = PLAYER_ACC
# 增加摩擦
self.acc.x += self.vel.x * PLAYER_FRICTION
# 移動公式
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
# 讓角色保持在屏幕裏
if self.pos.x > WIDTH:
self.pos.x = 0
if self.pos.x < 0:
self.pos.x = WIDTH
跳躍
對於角色的跳躍操作我們也需要加以一些公式的約束從而使其達到一個合理的地球表面的跳躍效果
def jump(self):
# 引入一個角色是否在跳躍狀態的判斷,防止出現角色可以在空中一直跳躍類似起飛的效果
if not self.jumping:
self.jumping = True
self.vel.y = -PLAYER_JUMP
六、屏幕的滾動
- 正如前面一開始的gif中所展示的樣子,隨着小方塊的不斷的上移,我們整個遊戲的所覆蓋的畫面也應該向上移動。當由於屏幕是不可能如我們所要求的樣子向上滾動的,所以我們採用讓背景相對向下移動來達到同樣的效果
- 隨着屏幕的不斷上移我們就要不斷的來產生新的臺階來讓我們這個遊戲繼續下去,這裏用了隨機生成的方式
- 當越來越多的臺階的產生無疑對我們真的遊戲的運行來說是一種負擔,尤其是那些已經滾動到屏幕下方的臺階,雖然已經不在我們的視野裏面,但依然存在我們的內存裏。這裏我們就需要清楚哪些已經沒用的臺階
# 定義在遊戲類中
def update(self):
# 向上滾動屏幕即背景下移
if self.player.rect.top <= HEIGHT * 0.4:
self.player.pos.y += abs(int(self.player.vel.y))
for plat in self.platforms:
plat.rect.y += abs(int(self.player.vel.y))
if plat.rect.top > HEIGHT:
plat.kill()
# 當有臺階在屏幕的下方被清除就加分
self.score += 10
# 產生新平面
while len(self.platforms) < 6:
width = random.randrange(50, 100)
p = Platform(random.randrange(0, WIDTH - width),
random.randrange(-75, -30),
width, 20)
self.platforms.add(p)
self.all_sprites.add(p)
七、細節的處理和完善
長按和短按空格達成不一樣的跳躍效果
這裏需要對鍵盤的按下和鬆開即KEYDOWN
和KEYUP
進行監控,同時引入跳躍的狀態來控制
# 定義在遊戲類中
def events(self):
if event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
self.player.jump()
if event.type == pg.KEYUP:
if event.key == pg.K_SPACE:
self.player.jump_cut()
# 定義在角色類中
def jump(self):
if not self.jumping:
self.jumping = True
self.vel.y = -PLAYER_JUMP
def jump_cut(self):
if self.jumping:
if self.vel.y < -3:
self.vel.y = -3
加入一個角色死亡效果
# 定義在遊戲類中
def update(self):
# 當角色落入屏幕下方時,就回滾還剩下的屏幕下方的臺階
if self.player.rect.bottom > HEIGHT:
for sprite in self.all_sprites:
sprite.rect.y -= int(max(self.player.vel.y, 10))
if sprite.rect.bottom < 0:
sprite.kill()
if len(self.platforms) == 0:
self.playing = False
開始和結束屏幕的繪製
# 定義在遊戲類中
def show_start_screen(self):
# 繪製開始屏幕
self.screen.fill(BACKGROUND_COLOR)
self.draw_text(TITLE, 48, WHITE, WIDTH*0.5, HEIGHT*0.25)
self.draw_text("Arrows to move, Space to jump", 22, WHITE, WIDTH*0.5, HEIGHT*0.5)
self.draw_text("Press a key to play", 22, WHITE, WIDTH * 0.5, HEIGHT * 0.75)
pg.display.flip()
self.wait_for_key()
def show_go_screen(self):
# 繪製結束屏幕
self.screen.fill(BACKGROUND_COLOR)
self.draw_text("Congratulations !", 48, WHITE, WIDTH * 0.5, HEIGHT * 0.25)
self.draw_text("Score: " + str(self.score), 22, WHITE, WIDTH * 0.5, HEIGHT * 0.5)
self.draw_text("Press a key to play again", 22, WHITE, WIDTH * 0.5, HEIGHT * 0.75)
pg.display.flip()
self.wait_for_key()
def wait_for_key(self):
# 等待玩家確認
self.waiting = True
while self.waiting:
self.clock.tick(FPS)
for event in pg.event.get():
if event.type == pg.QUIT:
self.waiting = False
self.running = False
if event.type == pg.KEYDOWN:
self.waiting = False
def draw_text(self, text, size, color, x, y):
# 屏幕上繪製文字
font = pg.font.SysFont("arial", size)
text_surface = font.render(text, True, color)
text_rect = text_surface.get_rect()
text_rect.midtop = (int(x), int(y))
self.screen.blit(text_surface, text_rect)
八、程序打包
當你編寫完一個小程序肯定迫切的想分享給朋友們去試一下,但不是大家的電腦上都有python環境,這裏就需要我們來使用**pyinstaller
**來對我們的程序進行打包
-
使用
pip
安裝pyinstaller
(如果安裝速度太慢可以配置一下鏡像)pip install pyinstaller
-
進入程序所處目錄開始打包
pyinstaller -F -w main.py
-F
:只生產一個exe文件-w
:不創建cmd命令窗口,直接進入遊戲窗口
九、源碼
main.py
import random
from sprites import *
class Game:
def __init__(self):
# 初始化窗口
# 初始化pygame創建窗口
pg.init()
# pygame.mixier.init()
self.screen = pg.display.set_mode([WIDTH, HEIGHT])
pg.display.set_caption(TITLE)
self.clock = pg.time.Clock()
self.running = True
def new(self):
self.score = 0
self.all_sprites = pg.sprite.Group()
self.platforms = pg.sprite.Group()
self.player = Player(self)
self.all_sprites.add(self.player)
self.ground = Platform(0, HEIGHT - 20, WIDTH, 20)
self.all_sprites.add(self.ground)
self.platforms.add(self.ground)
for plat in PLATFORM_LIST:
p = Platform(*plat)
self.all_sprites.add(p)
self.platforms.add(p)
self.run()
def run(self):
# 遊戲循環
self.playing = True
while self.playing:
self.clock.tick(FPS)
self.events()
self.update()
self.draw()
def update(self):
self.all_sprites.update()
if self.player.vel.y > 0:
hits = pg.sprite.spritecollide(self.player, self.platforms, False)
if hits:
lowest = hits[0]
for hit in hits:
if hit.rect.bottom > lowest.rect.centery:
lowest = hit
if self.player.pos.y < lowest.rect.bottom:
self.player.pos.y = lowest.rect.top
self.player.vel.y = 0
self.player.jumping = False
# 向上滾動屏幕即背景下移
if self.player.rect.top <= HEIGHT * 0.4:
self.player.pos.y += abs(int(self.player.vel.y))
for plat in self.platforms:
plat.rect.y += abs(int(self.player.vel.y))
if plat.rect.top > HEIGHT:
plat.kill()
self.score += 10
if self.player.rect.bottom > HEIGHT:
for sprite in self.all_sprites:
sprite.rect.y -= int(max(self.player.vel.y, 10))
if sprite.rect.bottom < 0:
sprite.kill()
if len(self.platforms) == 0:
self.playing = False
# 產生新平面
while len(self.platforms) < 6:
width = random.randrange(50, 100)
p = Platform(random.randrange(0, WIDTH - width),
random.randrange(-75, -30),
width, 20)
self.platforms.add(p)
self.all_sprites.add(p)
def events(self):
for event in pg.event.get():
# 檢查是否關閉窗口
if event.type == pg.QUIT:
if self.playing:
self.playing = False
self.running = False
if event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
self.player.jump()
if event.type == pg.KEYUP:
if event.key == pg.K_SPACE:
self.player.jump_cut()
def draw(self):
self.screen.fill(BACKGROUND_COLOR)
self.all_sprites.draw(self.screen)
self.draw_text(str(self.score)+" score", 22, WHITE, WIDTH / 2, 25)
pg.display.flip()
def show_start_screen(self):
self.screen.fill(BACKGROUND_COLOR)
self.draw_text(TITLE, 48, WHITE, WIDTH*0.5, HEIGHT*0.25)
self.draw_text("Arrows to move, Space to jump", 22, WHITE, WIDTH*0.5, HEIGHT*0.5)
self.draw_text("Press a key to play", 22, WHITE, WIDTH * 0.5, HEIGHT * 0.75)
pg.display.flip()
self.wait_for_key()
def show_go_screen(self):
self.screen.fill(BACKGROUND_COLOR)
self.draw_text("Congratulations !", 48, WHITE, WIDTH * 0.5, HEIGHT * 0.25)
self.draw_text("Score: " + str(self.score), 22, WHITE, WIDTH * 0.5, HEIGHT * 0.5)
self.draw_text("Press a key to play again", 22, WHITE, WIDTH * 0.5, HEIGHT * 0.75)
pg.display.flip()
self.wait_for_key()
def wait_for_key(self):
self.waiting = True
while self.waiting:
self.clock.tick(FPS)
for event in pg.event.get():
if event.type == pg.QUIT:
self.waiting = False
self.running = False
if event.type == pg.KEYDOWN:
self.waiting = False
def draw_text(self, text, size, color, x, y):
font = pg.font.SysFont("arial", size)
text_surface = font.render(text, True, color)
text_rect = text_surface.get_rect()
text_rect.midtop = (int(x), int(y))
self.screen.blit(text_surface, text_rect)
g = Game()
g.show_start_screen()
while g.running:
g.new()
g.show_go_screen()
sprites.py
from settings import *
import pygame as pg
vec = pg.math.Vector2
class Player(pg.sprite.Sprite):
def __init__(self, game):
pg.sprite.Sprite.__init__(self)
self.game = game
self.jumping = False
self.image = pg.Surface((30, 40))
self.image.fill(YELLOW)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
self.pos = vec(WIDTH / 2, HEIGHT / 2)
self.vel = vec(0, 0)
self.acc = vec(0, 0)
def jump(self):
if not self.jumping:
self.jumping = True
self.vel.y = -PLAYER_JUMP
def jump_cut(self):
if self.jumping:
if self.vel.y < -3:
self.vel.y = -3
def update(self):
self.acc = vec(0, PLAYER_GRAVITY)
keys = pg.key.get_pressed()
if keys[pg.K_LEFT]:
self.acc.x = -PLAYER_ACC
if keys[pg.K_RIGHT]:
self.acc.x = PLAYER_ACC
# 增加摩擦
self.acc.x += self.vel.x * PLAYER_FRICTION
# 移動公式
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
if self.pos.x > WIDTH:
self.pos.x = 0
if self.pos.x < 0:
self.pos.x = WIDTH
self.rect.midbottom = self.pos
class Platform(pg.sprite.Sprite):
def __init__(self, x, y, w, h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w, h))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
settings.py
TITLE = "Jumpy !"
# 定義窗口大小
WIDTH = 400
HEIGHT = 600
FPS = 60
PLAYER_ACC = 0.5
PLAYER_FRICTION = -0.12
PLAYER_GRAVITY = 0.8
PLAYER_JUMP = 20
# 定義顏色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
BACKGROUND_COLOR = (0, 155, 155)
# 平面
PLATFORM_LIST = [(WIDTH / 2 - 30, HEIGHT * 0.8, 150, 20),
(25, HEIGHT - 350, 100, 20),
(350, 200, 100, 20),
(175, 100, 50, 20)]