目录
0.开篇废话(允许跳过)
接下来,系列“游戏制作,但是使用Python,还能离线玩?干!”会为您介绍使用Python完成游戏创作的相关内容,但是,请注意:Python不能用于开发过度精细的3D游戏,如想尝试3D游戏,请移步:虚幻引擎5 - Unreal Engine
请认准系列名称,都在部分0.中,学习前请确认,以免造成内容不连续
今天的内容为 Pygame 优秀样例
1.今日样例
1.俄罗斯方块(Tetris)
2.扫雷(Minesweeper)
3.贪吃蛇(Snake)
4.飞机大战(Airplane War)
2.开始制作游戏
优秀样例分享不会过多的解释原理,我将会介绍4个游戏的制作方法
2.1 俄罗斯方块(Tetris)
- 导入Pygame,创建游戏窗口并初始化Pygame库
- 加载游戏素材,包括背景图、方块图片、音乐、素材不同造型等(也可以使用Pygame绘画)
- 定义游戏状态,包括方块和游戏区域
- 实现方块下落和旋转功能
- 实现消除行和计分功能
- 实现游戏结束判定
- 实现主循环并调用游戏函数
代码:
import pygame
import random
"""
10 x 20 square grid
shapes: S, Z, I, O, J, L, T
represented in order by 0 - 6
"""
pygame.font.init()
# GLOBALS VARS
s_width = 800
s_height = 700
play_width = 300 # meaning 300 // 10 = 30 width per block
play_height = 600 # meaning 600 // 20 = 20 height per blo ck
block_size = 30
top_left_x = (s_width - play_width) // 2
top_left_y = s_height - play_height
# SHAPE FORMATS
S = [['.....',
'.....',
'..00.',
'.00..',
'.....'],
['.....',
'..0..',
'..00.',
'...0.',
'.....']]
Z = [['.....',
'.....',
'.00..',
'..00.',
'.....'],
['.....',
'..0..',
'.00..',
'.0...',
'.....']]
I = [['..0..',
'..0..',
'..0..',
'..0..',
'.....'],
['.....',
'0000.',
'.....',
'.....',
'.....']]
O = [['.....',
'.....',
'.00..',
'.00..',
'.....']]
J = [['.....',
'.0...',
'.000.',
'.....',
'.....'],
['.....',
'..00.',
'..0..',
'..0..',
'.....'],
['.....',
'.....',
'.000.',
'...0.',
'.....'],
['.....',
'..0..',
'..0..',
'.00..',
'.....']]
L = [['.....',
'...0.',
'.000.',
'.....',
'.....'],
['.....',
'..0..',
'..0..',
'..00.',
'.....'],
['.....',
'.....',
'.000.',
'.0...',
'.....'],
['.....',
'.00..',
'..0..',
'..0..',
'.....']]
T = [['.....',
'..0..',
'.000.',
'.....',
'.....'],
['.....',
'..0..',
'..00.',
'..0..',
'.....'],
['.....',
'.....',
'.000.',
'..0..',
'.....'],
['.....',
'..0..',
'.00..',
'..0..',
'.....']]
shapes = [S, Z, I, O, J, L, T]
shape_colors = [(0, 255, 0), (255, 0, 0), (0, 255, 255), (255, 255, 0), (255, 165, 0), (0, 0, 255), (128, 0, 128)]
# index 0 - 6 represent shape
class Piece(object):
rows = 20 # y
columns = 10 # x
def __init__(self, column, row, shape):
self.x = column
self.y = row
self.shape = shape
self.color = shape_colors[shapes.index(shape)]
self.rotation = 0 # number from 0-3
def create_grid(locked_positions={}):
grid = [[(0,0,0) for x in range(10)] for x in range(20)]
for i in range(len(grid)):
for j in range(len(grid[i])):
if (j,i) in locked_positions:
c = locked_positions[(j,i)]
grid[i][j] = c
return grid
def convert_shape_format(shape):
positions = []
format = shape.shape[shape.rotation % len(shape.shape)]
for i, line in enumerate(format):
row = list(line)
for j, column in enumerate(row):
if column == '0':
positions.append((shape.x + j, shape.y + i))
for i, pos in enumerate(positions):
positions[i] = (pos[0] - 2, pos[1] - 4)
return positions
def valid_space(shape, grid):
accepted_positions = [[(j, i) for j in range(10) if grid[i][j] == (0,0,0)] for i in range(20)]
accepted_positions = [j for sub in accepted_positions for j in sub]
formatted = convert_shape_format(shape)
for pos in formatted:
if pos not in accepted_positions:
if pos[1] > -1:
return False
return True
def check_lost(positions):
for pos in positions:
x, y = pos
if y < 1:
return True
return False
def get_shape():
global shapes, shape_colors
return Piece(5, 0, random.choice(shapes))
def draw_text_middle(text, size, color, surface):
font = pygame.font.SysFont('comicsans', size, bold=True)
label = font.render(text, 1, color)
surface.blit(label, (top_left_x + play_width/2 - (label.get_width() / 2), top_left_y + play_height/2 - label.get_height()/2))
def draw_grid(surface, row, col):
sx = top_left_x
sy = top_left_y
for i in range(row):
pygame.draw.line(surface, (128,128,128), (sx, sy+ i*30), (sx + play_width, sy + i * 30)) # horizontal lines
for j in range(col):
pygame.draw.line(surface, (128,128,128), (sx + j * 30, sy), (sx + j * 30, sy + play_height)) # vertical lines
def clear_rows(grid, locked):
# need to see if row is clear the shift every other row above down one
inc = 0
for i in range(len(grid)-1,-1,-1):
row = grid[i]
if (0, 0, 0) not in row:
inc += 1
# add positions to remove from locked
ind = i
for j in range(len(row)):
try:
del locked[(j, i)]
except:
continue
if inc > 0:
for key in sorted(list(locked), key=lambda x: x[1])[::-1]:
x, y = key
if y < ind:
newKey = (x, y + inc)
locked[newKey] = locked.pop(key)
def draw_next_shape(shape, surface):
font = pygame.font.SysFont('comicsans', 30)
label = font.render('Next Shape', 1, (255,255,255))
sx = top_left_x + play_width + 50
sy = top_left_y + play_height/2 - 100
format = shape.shape[shape.rotation % len(shape.shape)]
for i, line in enumerate(format):
row = list(line)
for j, column in enumerate(row):
if column == '0':
pygame.draw.rect(surface, shape.color, (sx + j*30, sy + i*30, 30, 30), 0)
surface.blit(label, (sx + 10, sy- 30))
def draw_window(surface):
surface.fill((0,0,0))
# Tetris Title
font = pygame.font.SysFont('comicsans', 60)
label = font.render('TETRIS', 1, (255,255,255))
surface.blit(label, (top_left_x + play_width / 2 - (label.get_width() / 2), 30))
for i in range(len(grid)):
for j in range(len(grid[i])):
pygame.draw.rect(surface, grid[i][j], (top_left_x + j* 30, top_left_y + i * 30, 30, 30), 0)
# draw grid and border
draw_grid(surface, 20, 10)
pygame.draw.rect(surface, (255, 0, 0), (top_left_x, top_left_y, play_width, play_height), 5)
# pygame.display.update()
def main():
global grid
locked_positions = {} # (x,y):(255,0,0)
grid = create_grid(locked_positions)
change_piece = False
run = True
current_piece = get_shape()
next_piece = get_shape()
clock = pygame.time.Clock()
fall_time = 0
level_time = 0
fall_speed = 0.27
score = 0
while run:
grid = create_grid(locked_positions)
fall_time += clock.get_rawtime()
level_time += clock.get_rawtime()
clock.tick()
if level_time/1000 > 4:
level_time = 0
if fall_speed > 0.15:
fall_speed -= 0.005
# PIECE FALLING CODE
if fall_time/1000 >= fall_speed:
fall_time = 0
current_piece.y += 1
if not (valid_space(current_piece, grid)) and current_piece.y > 0:
current_piece.y -= 1
change_piece = True
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.display.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
current_piece.x -= 1
if not valid_space(current_piece, grid):
current_piece.x += 1
elif event.key == pygame.K_RIGHT:
current_piece.x += 1
if not valid_space(current_piece, grid):
current_piece.x -= 1
elif event.key == pygame.K_UP:
# rotate shape
current_piece.rotation = current_piece.rotation + 1 % len(current_piece.shape)
if not valid_space(current_piece, grid):
current_piece.rotation = current_piece.rotation - 1 % len(current_piece.shape)
if event.key == pygame.K_DOWN:
# move shape down
current_piece.y += 1
if not valid_space(current_piece, grid):
current_piece.y -= 1
'''if event.key == pygame.K_SPACE:
while valid_space(current_piece, grid):
current_piece.y += 1
current_piece.y -= 1
print(convert_shape_format(current_piece))''' # todo fix
shape_pos = convert_shape_format(current_piece)
# add piece to the grid for drawing
for i in range(len(shape_pos)):
x, y = shape_pos[i]
if y > -1:
grid[y][x] = current_piece.color
# IF PIECE HIT GROUND
if change_piece:
for pos in shape_pos:
p = (pos[0], pos[1])
locked_positions[p] = current_piece.color
current_piece = next_piece
next_piece = get_shape()
change_piece = False
# call four times to check for multiple clear rows
if clear_rows(grid, locked_positions):
score += 10
draw_window(win)
draw_next_shape(next_piece, win)
pygame.display.update()
# Check if user lost
if check_lost(locked_positions):
run = False
draw_text_middle("You Lost", 40, (255,255,255), win)
pygame.display.update()
pygame.time.delay(2000)
def main_menu():
run = True
while run:
win.fill((0,0,0))
draw_text_middle('Press any key to begin.', 60, (255, 255, 255), win)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
main()
pygame.quit()
win = pygame.display.set_mode((s_width, s_height))
pygame.display.set_caption('Tetris')
main_menu() # start game
2.2 扫雷(Minesweeper)
- 创建游戏窗口并初始化Pygame库
- 加载游戏素材,包括背景图和方格图片
- 定义游戏状态,包括方格、地雷和游戏区域
- 实现方格揭开、插旗、判定是否为地雷功能
- 实现游戏胜利判定
- 实现游戏失败判定
- 实现主循环并调用游戏函数
代码:
import random, pygame, sys
from pygame.locals import *
# set constants
FPS = 30
WINDOWWIDTH = 800
WINDOWHEIGHT = 900
BOXSIZE = 30
GAPSIZE = 5
FIELDWIDTH = 20
FIELDHEIGHT = 20
XMARGIN = int((WINDOWWIDTH-(FIELDWIDTH*(BOXSIZE+GAPSIZE)))/2)
YMARGIN = XMARGIN
MINESTOTAL = 60
# assertions
assert MINESTOTAL < FIELDHEIGHT*FIELDWIDTH, 'More mines than boxes'
assert BOXSIZE^2 * (FIELDHEIGHT*FIELDWIDTH) < WINDOWHEIGHT*WINDOWWIDTH, 'Boxes will not fit on screen'
assert BOXSIZE/2 > 5, 'Bounding errors when drawing rectangle, cannot use half-5 in drawMinesNumbers'
# assign colors
LIGHTGRAY = (225, 225, 225)
DARKGRAY = (160, 160, 160)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 128, 0)
# set up major colors
BGCOLOR = WHITE
FIELDCOLOR = BLACK
BOXCOLOR_COV = DARKGRAY # covered box color
BOXCOLOR_REV = LIGHTGRAY # revealed box color
MINECOLOR = BLACK
TEXTCOLOR_1 = BLUE
TEXTCOLOR_2 = RED
TEXTCOLOR_3 = BLACK
HILITECOLOR = GREEN
RESETBGCOLOR = LIGHTGRAY
MINEMARK_COV = RED
# set up font
FONTTYPE = 'Courier New'
FONTSIZE = 20
def main():
# initialize global variables & pygame module, set caption
global FPSCLOCK, DISPLAYSURFACE, BASICFONT, RESET_SURF, RESET_RECT, SHOW_SURF, SHOW_RECT
pygame.init()
pygame.display.set_caption('Minesweeper')
FPSCLOCK = pygame.time.Clock()
DISPLAYSURFACE = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
BASICFONT = pygame.font.SysFont(FONTTYPE, FONTSIZE)
# obtain reset & show objects and rects
RESET_SURF, RESET_RECT = drawButton('RESET', TEXTCOLOR_3, RESETBGCOLOR, WINDOWWIDTH/2, WINDOWHEIGHT-120)
SHOW_SURF, SHOW_RECT = drawButton('SHOW ALL', TEXTCOLOR_3, RESETBGCOLOR, WINDOWWIDTH/2, WINDOWHEIGHT-95)
# stores XY of mouse events
mouse_x = 0
mouse_y = 0
# set up data structures and lists
mineField, zeroListXY, revealedBoxes, markedMines = gameSetup()
# set background color
DISPLAYSURFACE.fill(BGCOLOR)
# main game loop
while True:
# check for quit function
checkForKeyPress()
# initialize input booleans
mouseClicked = False
spacePressed = False
# draw field
DISPLAYSURFACE.fill(BGCOLOR)
pygame.draw.rect(DISPLAYSURFACE, FIELDCOLOR, (XMARGIN-5, YMARGIN-5, (BOXSIZE+GAPSIZE)*FIELDWIDTH+5, (BOXSIZE+GAPSIZE)*FIELDHEIGHT+5))
drawField()
drawMinesNumbers(mineField)
# event handling loop
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
terminate()
elif event.type == MOUSEMOTION:
mouse_x, mouse_y = event.pos
elif event.type == MOUSEBUTTONDOWN:
mouse_x, mouse_y = event.pos
mouseClicked = True
elif event.type == KEYDOWN:
if event.key == K_SPACE:
spacePressed = True
elif event.type == KEYUP:
if event.key == K_SPACE:
spacePressed = False
# draw covers
drawCovers(revealedBoxes, markedMines)
# mine marker tip
tipFont = pygame.font.SysFont(FONTTYPE, 16) ## not using BASICFONT - too big
drawText('Tip: Highlight a box and press space (rather than click the mouse)', tipFont, TEXTCOLOR_3, DISPLAYSURFACE, WINDOWWIDTH/2, WINDOWHEIGHT-60)
drawText('to mark areas that you think contain mines.', tipFont, TEXTCOLOR_3, DISPLAYSURFACE, WINDOWWIDTH/2, WINDOWHEIGHT-40)
# determine boxes at clicked areas
box_x, box_y = getBoxAtPixel(mouse_x, mouse_y)
# mouse not over a box in field
if (box_x, box_y) == (None, None):
# check if reset box is clicked
if RESET_RECT.collidepoint(mouse_x, mouse_y):
highlightButton(RESET_RECT)
if mouseClicked:
mineField, zeroListXY, revealedBoxes, markedMines = gameSetup()
# check if show box is clicked
if SHOW_RECT.collidepoint(mouse_x, mouse_y):
highlightButton(SHOW_RECT)
if mouseClicked:
revealedBoxes = blankRevealedBoxData(True)
# mouse currently over box in field
else:
# highlight unrevealed box
if not revealedBoxes[box_x][box_y]:
highlightBox(box_x, box_y)
# mark mines
if spacePressed:
markedMines.append([box_x, box_y])
# reveal clicked boxes
if mouseClicked:
revealedBoxes[box_x][box_y] = True
# when 0 is revealed, show relevant boxes
if mineField[box_x][box_y] == '[0]':
showNumbers(revealedBoxes, mineField, box_x, box_y, zeroListXY)
# when mine is revealed, show mines
if mineField[box_x][box_y] == '[X]':
showMines(revealedBoxes, mineField, box_x, box_y)
gameOverAnimation(mineField, revealedBoxes, markedMines, 'LOSS')
mineField, zeroListXY, revealedBoxes, markedMines = gameSetup()
# check if player has won
if gameWon(revealedBoxes, mineField):
gameOverAnimation(mineField, revealedBoxes, markedMines, 'WIN')
mineField, zeroListXY, revealedBoxes, markedMines = gameSetup()
# redraw screen, wait clock tick
pygame.display.update()
FPSCLOCK.tick(FPS)
def blankField():
# creates blank FIELDWIDTH x FIELDHEIGHT data structure
field = []
for x in range(FIELDWIDTH):
field.append([])
for y in range(FIELDHEIGHT):
field[x].append('[ ]')
return field
def placeMines(field):
# places mines in FIELDWIDTH x FIELDHEIGHT data structure
# requires blank field as input
mineCount = 0
xy = []
while mineCount < MINESTOTAL:
x = random.randint(0,FIELDWIDTH-1)
y = random.randint(0,FIELDHEIGHT-1)
xy.append([x,y])
if xy.count([x,y]) > 1:
xy.remove([x,y])
else:
field[x][y] = '[X]'
mineCount += 1
def isThereMine(field, x, y):
# checks if mine is located at specific box on field
return field[x][y] == '[X]'
def placeNumbers(field):
# places numbers in FIELDWIDTH x FIELDHEIGHT data structure
# requires field with mines as input
for x in range(FIELDWIDTH):
for y in range(FIELDHEIGHT):
if not isThereMine(field, x, y):
count = 0
if x != 0:
if isThereMine(field, x-1, y):
count += 1
if y != 0:
if isThereMine(field, x-1, y-1):
count += 1
if y != FIELDHEIGHT-1:
if isThereMine(field, x-1, y+1):
count += 1
if x != FIELDWIDTH-1:
if isThereMine(field, x+1, y):
count += 1
if y != 0:
if isThereMine(field, x+1, y-1):
count += 1
if y != FIELDHEIGHT-1:
if isThereMine(field, x+1, y+1):
count += 1
if y != 0:
if isThereMine(field, x, y-1):
count += 1
if y != FIELDHEIGHT-1:
if isThereMine(field, x, y+1):
count += 1
field[x][y] = '[%s]' %(count)
def blankRevealedBoxData(val):
# returns FIELDWIDTH x FIELDHEIGHT data structure different from the field data structure
# each item in data structure is boolean (val) to show whether box at those fieldwidth & fieldheight coordinates should be revealed
revealedBoxes = []
for i in range(FIELDWIDTH):
revealedBoxes.append([val] * FIELDHEIGHT)
return revealedBoxes
def gameSetup():
# set up mine field data structure, list of all zeros for recursion, and revealed box boolean data structure
mineField = blankField()
placeMines(mineField)
placeNumbers(mineField)
zeroListXY = []
markedMines = []
revealedBoxes = blankRevealedBoxData(False)
return mineField, zeroListXY, revealedBoxes, markedMines
def drawField():
# draws field GUI and reset button
for box_x in range(FIELDWIDTH):
for box_y in range(FIELDHEIGHT):
left, top = getLeftTopXY(box_x, box_y)
pygame.draw.rect(DISPLAYSURFACE, BOXCOLOR_REV, (left, top, BOXSIZE, BOXSIZE))
DISPLAYSURFACE.blit(RESET_SURF, RESET_RECT)
DISPLAYSURFACE.blit(SHOW_SURF, SHOW_RECT)
def drawMinesNumbers(field):
# draws mines and numbers onto GUI
# field should have mines and numbers
half = int(BOXSIZE*0.5)
quarter = int(BOXSIZE*0.25)
eighth = int(BOXSIZE*0.125)
for box_x in range(FIELDWIDTH):
for box_y in range(FIELDHEIGHT):
left, top = getLeftTopXY(box_x, box_y)
center_x, center_y = getCenterXY(box_x, box_y)
if field[box_x][box_y] == '[X]':
pygame.draw.circle(DISPLAYSURFACE, MINECOLOR, (left+half, top+half), quarter)
pygame.draw.circle(DISPLAYSURFACE, WHITE, (left+half, top+half), eighth)
pygame.draw.line(DISPLAYSURFACE, MINECOLOR, (left+eighth, top+half), (left+half+quarter+eighth, top+half))
pygame.draw.line(DISPLAYSURFACE, MINECOLOR, (left+half, top+eighth), (left+half, top+half+quarter+eighth))
pygame.draw.line(DISPLAYSURFACE, MINECOLOR, (left+quarter, top+quarter), (left+half+quarter, top+half+quarter))
pygame.draw.line(DISPLAYSURFACE, MINECOLOR, (left+quarter, top+half+quarter), (left+half+quarter, top+quarter))
else:
for i in range(1,9):
if field[box_x][box_y] == '[' + str(i) + ']':
if i in range(1,3):
textColor = TEXTCOLOR_1
else:
textColor = TEXTCOLOR_2
drawText(str(i), BASICFONT, textColor, DISPLAYSURFACE, center_x, center_y)
def showNumbers(revealedBoxes, mineField, box_x, box_y, zeroListXY):
# modifies revealedBox data strucure if chosen box_x & box_y is [0]
# show all boxes using recursion
revealedBoxes[box_x][box_y] = True
revealAdjacentBoxes(revealedBoxes, box_x, box_y)
for i,j in getAdjacentBoxesXY(mineField, box_x, box_y):
if mineField[i][j] == '[0]' and [i,j] not in zeroListXY:
zeroListXY.append([i,j])
showNumbers(revealedBoxes, mineField, i, j, zeroListXY)
def showMines(revealedBoxes, mineField, box_x, box_y):
# modifies revealedBox data strucure if chosen box_x & box_y is [X]
for i in range(FIELDWIDTH):
for j in range(FIELDHEIGHT):
if mineField[i][j] == '[X]':
revealedBoxes[i][j] = True
def revealAdjacentBoxes(revealedBoxes, box_x, box_y):
# modifies revealedBoxes data structure so that all adjacent boxes to (box_x, box_y) are set to True
if box_x != 0:
revealedBoxes[box_x-1][box_y] = True
if box_y != 0:
revealedBoxes[box_x-1][box_y-1] = True
if box_y != FIELDHEIGHT-1:
revealedBoxes[box_x-1][box_y+1] = True
if box_x != FIELDWIDTH-1:
revealedBoxes[box_x+1][box_y] = True
if box_y != 0:
revealedBoxes[box_x+1][box_y-1] = True
if box_y != FIELDHEIGHT-1:
revealedBoxes[box_x+1][box_y+1] = True
if box_y != 0:
revealedBoxes[box_x][box_y-1] = True
if box_y != FIELDHEIGHT-1:
revealedBoxes[box_x][box_y+1] = True
def getAdjacentBoxesXY(mineField, box_x, box_y):
# get box XY coordinates for all adjacent boxes to (box_x, box_y)
adjacentBoxesXY = []
if box_x != 0:
adjacentBoxesXY.append([box_x-1,box_y])
if box_y != 0:
adjacentBoxesXY.append([box_x-1,box_y-1])
if box_y != FIELDHEIGHT-1:
adjacentBoxesXY.append([box_x-1,box_y+1])
if box_x != FIELDWIDTH-1:
adjacentBoxesXY.append([box_x+1,box_y])
if box_y != 0:
adjacentBoxesXY.append([box_x+1,box_y-1])
if box_y != FIELDHEIGHT-1:
adjacentBoxesXY.append([box_x+1,box_y+1])
if box_y != 0:
adjacentBoxesXY.append([box_x,box_y-1])
if box_y != FIELDHEIGHT-1:
adjacentBoxesXY.append([box_x,box_y+1])
return adjacentBoxesXY
def drawCovers(revealedBoxes, markedMines):
# uses revealedBox FIELDWIDTH x FIELDHEIGHT data structure to determine whether to draw box covering mine/number
# draw red cover instead of gray cover over marked mines
for box_x in range(FIELDWIDTH):
for box_y in range(FIELDHEIGHT):
if not revealedBoxes[box_x][box_y]:
left, top = getLeftTopXY(box_x, box_y)
if [box_x, box_y] in markedMines:
pygame.draw.rect(DISPLAYSURFACE, MINEMARK_COV, (left, top, BOXSIZE, BOXSIZE))
else:
pygame.draw.rect(DISPLAYSURFACE, BOXCOLOR_COV, (left, top, BOXSIZE, BOXSIZE))
def drawText(text, font, color, surface, x, y):
# function to easily draw text and also return object & rect pair
textobj = font.render(text, True, color)
textrect = textobj.get_rect()
textrect.centerx = x
textrect.centery = y
surface.blit(textobj, textrect)
def drawButton(text, color, bgcolor, center_x, center_y):
# similar to drawText but text has bg color and returns obj & rect
butSurf = BASICFONT.render(text, True, color, bgcolor)
butRect = butSurf.get_rect()
butRect.centerx = center_x
butRect.centery = center_y
return (butSurf, butRect)
def getLeftTopXY(box_x, box_y):
# get left & top coordinates for drawing mine boxes
left = XMARGIN + box_x*(BOXSIZE+GAPSIZE)
top = YMARGIN + box_y*(BOXSIZE+GAPSIZE)
return left, top
def getCenterXY(box_x, box_y):
# get center coordinates for drawing mine boxes
center_x = XMARGIN + BOXSIZE/2 + box_x*(BOXSIZE+GAPSIZE)
center_y = YMARGIN + BOXSIZE/2 + box_y*(BOXSIZE+GAPSIZE)
return center_x, center_y
def getBoxAtPixel(x, y):
# gets coordinates of box at mouse coordinates
for box_x in range(FIELDWIDTH):
for box_y in range(FIELDHEIGHT):
left, top = getLeftTopXY(box_x, box_y)
boxRect = pygame.Rect(left, top, BOXSIZE, BOXSIZE)
if boxRect.collidepoint(x, y):
return (box_x, box_y)
return (None, None)
def highlightBox(box_x, box_y):
# highlight box when mouse hovers over it
left, top = getLeftTopXY(box_x, box_y)
pygame.draw.rect(DISPLAYSURFACE, HILITECOLOR, (left, top, BOXSIZE, BOXSIZE), 4)
def highlightButton(butRect):
# highlight button when mouse hovers over it
linewidth = 4
pygame.draw.rect(DISPLAYSURFACE, HILITECOLOR, (butRect.left-linewidth, butRect.top-linewidth, butRect.width+2*linewidth, butRect.height+2*linewidth), linewidth)
def gameWon(revealedBoxes, mineField):
# check if player has revealed all boxes
notMineCount = 0
for box_x in range(FIELDWIDTH):
for box_y in range(FIELDHEIGHT):
if revealedBoxes[box_x][box_y] == True:
if mineField[box_x][box_y] != '[X]':
notMineCount += 1
if notMineCount >= (FIELDWIDTH*FIELDHEIGHT)-MINESTOTAL:
return True
else:
return False
def gameOverAnimation(mineField, revealedBoxes, markedMines, result):
# makes background flash red (loss) or blue (win)
origSurf = DISPLAYSURFACE.copy()
flashSurf = pygame.Surface(DISPLAYSURFACE.get_size())
flashSurf = flashSurf.convert_alpha()
animationSpeed = 20
if result == 'WIN':
r, g, b = BLUE
else:
r, g, b = RED
for i in range(5):
for start, end, step in ((0, 255, 1), (255, 0, -1)):
for alpha in range(start, end, animationSpeed*step): # animation loop
checkForKeyPress()
flashSurf.fill((r, g, b, alpha))
DISPLAYSURFACE.blit(origSurf, (0, 0))
DISPLAYSURFACE.blit(flashSurf, (0, 0))
pygame.draw.rect(DISPLAYSURFACE, FIELDCOLOR, (XMARGIN-5, YMARGIN-5, (BOXSIZE+GAPSIZE)*FIELDWIDTH+5, (BOXSIZE+GAPSIZE)*FIELDHEIGHT+5))
drawField()
drawMinesNumbers(mineField)
tipFont = pygame.font.SysFont(FONTTYPE, 16) ## not using BASICFONT - too big
drawText('Tip: Highlight a box and press space (rather than click the mouse)', tipFont, TEXTCOLOR_3, DISPLAYSURFACE, WINDOWWIDTH/2, WINDOWHEIGHT-60)
drawText('to mark areas that you think contain mines.', tipFont, TEXTCOLOR_3, DISPLAYSURFACE, WINDOWWIDTH/2, WINDOWHEIGHT-40)
RESET_SURF, RESET_RECT = drawButton('RESET', TEXTCOLOR_3, RESETBGCOLOR, WINDOWWIDTH/2, WINDOWHEIGHT-120)
SHOW_SURF, SHOW_RECT = drawButton('SHOW ALL', TEXTCOLOR_3, RESETBGCOLOR, WINDOWWIDTH/2, WINDOWHEIGHT-95)
drawCovers(revealedBoxes, markedMines)
pygame.display.update()
FPSCLOCK.tick(FPS)
def terminate():
# simple function to exit game
pygame.quit()
sys.exit()
def checkForKeyPress():
# check if quit or any other key is pressed
if len(pygame.event.get(QUIT)) > 0:
terminate()
keyUpEvents = pygame.event.get(KEYUP)
if len(keyUpEvents) == 0:
return None
if keyUpEvents[0].key == K_ESCAPE:
terminate()
return keyUpEvents[0].key
# run code
if __name__ == '__main__':
main()
2.3 贪吃蛇(Snake)
- 创建游戏窗口并初始化Pygame库
- 加载游戏素材,包括背景图和蛇和食物的图片
- 定义游戏状态,包括蛇、食物和游戏区域
- 实现蛇的移动功能
- 实现食物的生成和蛇的长度增加功能
- 实现游戏结束判定
- 实现主循环并调用游戏函数
代码:
import pygame, sys, time, random
# Difficulty settings
# Easy -> 10
# Medium -> 25
# Hard -> 40
# Harder -> 60
# Impossible-> 120
difficulty = 25
# Window size
frame_size_x = 720
frame_size_y = 480
# Checks for errors encountered
check_errors = pygame.init()
# pygame.init() example output -> (6, 0)
# second number in tuple gives number of errors
if check_errors[1] > 0:
print(f'[!] Had {check_errors[1]} errors when initialising game, exiting...')
sys.exit(-1)
else:
print('[+] Game successfully initialised')
# Initialise game window
pygame.display.set_caption('Snake Eater')
game_window = pygame.display.set_mode((frame_size_x, frame_size_y))
# Colors (R, G, B)
black = pygame.Color(0, 0, 0)
white = pygame.Color(255, 255, 255)
red = pygame.Color(255, 0, 0)
green = pygame.Color(0, 255, 0)
blue = pygame.Color(0, 0, 255)
# FPS (frames per second) controller
fps_controller = pygame.time.Clock()
# Game variables
snake_pos = [100, 50]
snake_body = [[100, 50], [100-10, 50], [100-(2*10), 50]]
food_pos = [random.randrange(1, (frame_size_x//10)) * 10, random.randrange(1, (frame_size_y//10)) * 10]
food_spawn = True
direction = 'RIGHT'
change_to = direction
score = 0
# Game Over
def game_over():
my_font = pygame.font.SysFont('times new roman', 90)
game_over_surface = my_font.render('YOU DIED', True, red)
game_over_rect = game_over_surface.get_rect()
game_over_rect.midtop = (frame_size_x/2, frame_size_y/4)
game_window.fill(black)
game_window.blit(game_over_surface, game_over_rect)
show_score(0, red, 'times', 20)
pygame.display.flip()
time.sleep(3)
pygame.quit()
sys.exit()
# Score
def show_score(choice, color, font, size):
score_font = pygame.font.SysFont(font, size)
score_surface = score_font.render('Score : ' + str(score), True, color)
score_rect = score_surface.get_rect()
if choice == 1:
score_rect.midtop = (frame_size_x/10, 15)
else:
score_rect.midtop = (frame_size_x/2, frame_size_y/1.25)
game_window.blit(score_surface, score_rect)
# pygame.display.flip()
# Main logic
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# Whenever a key is pressed down
elif event.type == pygame.KEYDOWN:
# W -> Up; S -> Down; A -> Left; D -> Right
if event.key == pygame.K_UP or event.key == ord('w'):
change_to = 'UP'
if event.key == pygame.K_DOWN or event.key == ord('s'):
change_to = 'DOWN'
if event.key == pygame.K_LEFT or event.key == ord('a'):
change_to = 'LEFT'
if event.key == pygame.K_RIGHT or event.key == ord('d'):
change_to = 'RIGHT'
# Esc -> Create event to quit the game
if event.key == pygame.K_ESCAPE:
pygame.event.post(pygame.event.Event(pygame.QUIT))
# Making sure the snake cannot move in the opposite direction instantaneously
if change_to == 'UP' and direction != 'DOWN':
direction = 'UP'
if change_to == 'DOWN' and direction != 'UP':
direction = 'DOWN'
if change_to == 'LEFT' and direction != 'RIGHT':
direction = 'LEFT'
if change_to == 'RIGHT' and direction != 'LEFT':
direction = 'RIGHT'
# Moving the snake
if direction == 'UP':
snake_pos[1] -= 10
if direction == 'DOWN':
snake_pos[1] += 10
if direction == 'LEFT':
snake_pos[0] -= 10
if direction == 'RIGHT':
snake_pos[0] += 10
# Snake body growing mechanism
snake_body.insert(0, list(snake_pos))
if snake_pos[0] == food_pos[0] and snake_pos[1] == food_pos[1]:
score += 1
food_spawn = False
else:
snake_body.pop()
# Spawning food on the screen
if not food_spawn:
food_pos = [random.randrange(1, (frame_size_x//10)) * 10, random.randrange(1, (frame_size_y//10)) * 10]
food_spawn = True
# GFX
game_window.fill(black)
for pos in snake_body:
# Snake body
# .draw.rect(play_surface, color, xy-coordinate)
# xy-coordinate -> .Rect(x, y, size_x, size_y)
pygame.draw.rect(game_window, green, pygame.Rect(pos[0], pos[1], 10, 10))
# Snake food
pygame.draw.rect(game_window, white, pygame.Rect(food_pos[0], food_pos[1], 10, 10))
# Game Over conditions
# Getting out of bounds
if snake_pos[0] < 0 or snake_pos[0] > frame_size_x-10:
game_over()
if snake_pos[1] < 0 or snake_pos[1] > frame_size_y-10:
game_over()
# Touching the snake body
for block in snake_body[1:]:
if snake_pos[0] == block[0] and snake_pos[1] == block[1]:
game_over()
show_score(1, white, 'consolas', 20)
# Refresh game screen
pygame.display.update()
# Refresh rate
fps_controller.tick(difficulty)
2.4 飞机大战(Airplane War)
- 创建游戏窗口并初始化Pygame库
- 加载游戏素材,包括背景图和飞机、子弹、敌机的图片
- 定义游戏状态,包括飞机、子弹、敌机和游戏区域
- 实现飞机射击、敌机移动和被子弹打中后的消失功能
- 实现敌机的生成和碰撞判断功能
- 实现游戏暂停和结束判定
- 实现主循环并调用游戏函数
代码:
import pygame
import random
import math
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption('飞机大战')
icon = pygame.image.load('ufo.png')
pygame.display.set_icon(icon)
bgImg = pygame.image.load('bg.png')
pygame.mixer.music.load('bg.wav')
pygame.mixer.music.play(-1)
bao_sound = pygame.mixer.Sound('exp.wav')
playerImg = pygame.image.load('player.png')
playerX = 400
playerY = 500
playerStep = 0
score = 0
font = pygame.font.Font('freesansbold.ttf', 32)
def show_score():
text = f'Score: {score}'
score_render = font.render(text, True, (255,0,0))
screen.blit(score_render, (10,10))
is_over = False
over_font = pygame.font.Font('freesansbold.ttf', 64)
def check_is_over():
if is_over:
text = "Game Over"
render = over_font.render(text, True, (255,0,0))
screen.blit(render, (200,250))
def distance(bx, by, ex, ey):
a = bx - ex
b = by - ey
return math.sqrt(a*a + b*b)
class Enemy():
def __init__(self):
self.img = pygame.image.load('enemy.png')
self.x = random.randint(200, 600)
self.y = random.randint(50, 250)
self.step = random.randint(2, 6)
def reset(self):
self.x = random.randint(200, 600)
self.y = random.randint(50, 200)
enemies = []
for i in range(6):
enemies.append(Enemy())
def show_enemy():
global is_over
for e in enemies:
screen.blit(e.img,(e.x, e.y))
e.x += e.step
if(e.x > 736 or e.x < 0):
e.step *= -1
e.y += 40
if e.y > 450:
is_over = True
enemies.clear()
class Bullet():
def __init__(self):
self.img = pygame.image.load('bullet.png')
self.x = playerX + 16
self.y = playerY + 10
self.step = 10
def hit(self):
global score
for e in enemies:
if(distance(self.x, self.y, e.x, e.y) < 30):
bao_sound.play()
bullets.remove(self)
e.reset()
score += 1
bullets = []
def show_bullets():
for b in bullets:
screen.blit(b.img, (b.x, b.y))
b.hit()
b.y -= b.step
if b.y < 0:
bullets.remove(b)
def move_player():
global playerX
playerX += playerStep
if playerX > 736:
playerX = 736
if playerX < 0:
playerX = 0
running = True
while running:
screen.blit(bgImg,(0,0))
show_score()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
playerStep = 5
elif event.key == pygame.K_LEFT:
playerStep = -5
elif event.key == pygame.K_SPACE:
bullets.append(Bullet())
if event.type == pygame.KEYUP:
playerStep = 0
screen.blit(playerImg, (playerX, playerY))
move_player()
show_enemy()
show_bullets()
check_is_over()
pygame.display.update()
这个游戏需要亿点素材,链接:https://raw.githubusercontent.com/liuxinleiup/Pygame_AirplaneWar/main/%E7%B4%A0%E6%9D%90.zip
建议打开Github加速器后使用高速下载器下载,或者使用网盘:
链接:https://pan.baidu.com/s/1WphKeWRPxJ2RSYBv_jP-jA
提取码:fvyv
注意,下载后解压素材包,目录与 *.py 文件在一起
以上是本期的全部内容,4个Pygame高质量作品,更多的作品等待你探索,可以去Pygame官网和Github上获取资讯或代码,如果你喜欢文章的话,可以收藏起来,或者分享给更多人,总之,你的支持是我创作的动力。
The End