使用pygame进行游戏开发
Pygame是一个开源的Python模块,专门用于多媒体应用(如电子游戏)的开发,其中包含对图像、声音、视频、事件、碰撞等的支持。Pygame建立在SDL的基础上,SDL是一套跨平台的多媒体开发库,用C语言实现,被广泛的应用于游戏、模拟器、播放器等的开发。而Pygame让游戏开发者不再被底层语言束缚,可以更多的关注游戏的功能和逻辑。
具体功能可以查看pygame的官方网站
五子棋游戏制作
制作游戏窗口
可以通过pygame中draw模块的函数在窗口上绘图,可以绘制的图形包括:线条、矩形、多边形、圆、椭圆、圆弧等。需要说明的是,屏幕坐标系是将屏幕左上角设置为坐标原点(0, 0)
,向右是x轴的正向,向下是y轴的正向,在表示位置或者设置尺寸的时候,我们默认的单位都是像素。所谓像素就是屏幕上的一个点,你可以用浏览图片的软件试着将一张图片放大若干倍,就可以看到这些点。pygame中表示颜色用的是色光三原色表示法,即通过一个元组或列表来指定颜色的RGB值,每个值都在0~255之间,因为是每种原色都用一个8位(bit)的值来表示,三种颜色相当于一共由24位构成,这也就是常说的“24位颜色表示法”。
import pygame
def main():
# 初始化导入的pygame中的模块
pygame.init()
screen = pygame.display.set_mode([640, 640]) # 初始化用于显示的窗口并设置窗口尺寸
pygame.display.set_caption('五子棋') # 设置当前窗口的标题
screen.fill([218, 165, 105]) # 填充背景色 红绿蓝三原色
running = True
while running: # 开启一个事件循环处理发生的事件
for event in pygame.event.get(): # 从消息队列中获取事件并对事件进行处理
if event.type == pygame.QUIT:
running = False
if __name__ == '__main__':
main()
画棋盘
创建棋子类对象和棋盘类对象,棋盘类里面创建一个15*15的数组的属性,再写一个重置棋盘的方法,令这个棋盘数组全部等于空,还有画棋盘的方法,通过pygame的draw模块在窗口上绘画棋盘、天元、边框和棋子,还有一个判断该位置是否有棋子,可以下的方法
class Chess(object):
"""棋子类"""
def __init__(self, x, y, is_black):
self._is_black = is_black
self._row = x
self._col = y
@property
def is_black(self):
return self._is_black
@property
def row(self):
return self._row
@property
def col(self):
return self._col
class RenjuBoard(object):
"""棋盘类"""
def __init__(self):
self._board = [[]] * 15
self.reset()
def reset(self):
"""重置棋盘"""
for row in range(len(self._board)):
self._board[row] = [EMPTY] * 15
def move(self, row, col, is_black):
"""判断是否有棋子"""
if self._board[row][col] == EMPTY:
self._board[row][col] = BLACK if is_black else WHITE
return True
return False
def draw(self, screen):
"""画棋盘"""
for i in range(1, 16): # 画格子:窗口,颜色,起始位置,结束位置,粗细
pygame.draw.line(screen, black_color, [40, 40 * i], [600, 40 * i], 1)
pygame.draw.line(screen, black_color, [40 * i, 40], [40 * i, 600], 1)
pygame.draw.rect(screen, black_color, [36, 36, 568, 568], 4) # 画边框
# 画圆 窗口,颜色,位置,半径,0实心
pygame.draw.circle(screen, black_color, [320, 320], 4, 0)
pygame.draw.circle(screen, black_color, [160, 160], 3, 0)
pygame.draw.circle(screen, black_color, [160, 480], 3, 0)
pygame.draw.circle(screen, black_color, [480, 480], 3, 0)
pygame.draw.circle(screen, black_color, [480, 160], 3, 0)
for row in range(len(self._board)):
for col in range(len(self._board[row])):
if self._board[row][col] != EMPTY:
ccolor = black_color if self._board[row][col] == BLACK else white_color
pos = [40 * (col + 1), 40 * (row + 1)]
pygame.draw.circle(screen, ccolor, pos, 20, 0)
完善主函数 事件处理
判断输赢,绘画文字,获取鼠标点击事件,绘画棋盘等
def main():
board = RenjuBoard() # 创建棋盘对象
chess = [[EMPTY] * 16 for _ in range(16)] # 棋子位置
is_black = True # 黑白棋分辨变量
pygame.init() # 初始化pygame
screen = pygame.display.set_mode([640, 640]) # 初始化用于显示的窗口并设置窗口尺寸
pygame.display.set_caption('五子棋') # 设置当前窗口的标题
screen.fill([218, 165, 105]) # 填充背景色 红绿蓝三原色
board.draw(screen) # 画棋盘
pygame.display.flip() # 刷新窗口
runing = True # 是否退出游戏变量
gameover = True # 游戏是否结束变量
while runing: # 开启一个事件循环处理发生的事件
for event in pygame.event.get(): # 从消息队列中获取事件并对事件进行处理
if event.type == pygame.QUIT:
runing = False
elif event.type == pygame.KEYUP:
if event.key == pygame.K_F2:
board.reset()
gameover = True
is_black = True
pygame.display.flip()
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and gameover: # 鼠标点击事件
x, y = event.pos
if 20 < x <= 600 and 20 < y < 600:
row = round((y - 40) / 40)
col = round((x - 40) / 40)
if board.move(row, col, is_black):
screen.fill([218, 165, 105])
board.draw(screen)
chess[col][row] = 1 if is_black else 2
pygame.display.flip()
if board.who_wins(col, row, chess):
my_font = pygame.font.SysFont("宋体", 60) # 字体,大小
if is_black:
position = my_font.render('game over, black win', True,
(255, 0, 0)) # 内容,抗锯齿,颜色,背景色(可选)
else:
position = my_font.render('game over, white win', True, (255, 0, 0))
screen.blit(position, (120, 310))
pygame.display.flip()
gameover = False
chess = [[EMPTY] * 16 for _ in range(16)]
is_black = not is_black
pygame.quit()
判断输赢
def who_wins(self, x, y, chess):
"""判断输赢"""
# 三维数组记录横向,纵向,左斜,右斜
dir = [[[-1, 0], [1, 0]], [[0, -1], [0, 1]], [[-1, -1], [1, 1]], [[1, -1], [-1, 1]]]
tempx = x
tempy = y
# 循环四个大方向
for i in range(4):
count = 1
# 循环两边方向
for j in range(2):
flag = True # 一直向一个方向遍历,有相同的,count+1,否则置flag为False
while flag:
tempx += dir[i][j][0]
tempy += dir[i][j][1]
if chess[x][y] == chess[tempx][tempy]:
count += 1
else:
flag = False
tempx = x
tempy = y
if count >= 5:
return True
return False
还可以有很多拓展,比如添加背景音乐和下棋时的音乐,可以使用pygame的music和mixer,还可以写成联网游戏,用网络编程来实现
如果开发3d游戏,可以使用Panda3D
运行结果图
飞机大战游戏
我自己结合所学知识用pygame做的一个小游戏,用了一些老师没就讲的碰撞精灵和音乐知识
首先,我创建了一个游戏对象的大类,有横坐标,纵坐标,速度,图片四个公共属性,并写成抽象类,然后分别创建了玩家类,敌机类,背景类,子弹类,并让他们继承来游戏对象类,除背景外,因为要用到碰撞精灵,所以使用了多重继承让他们还继承了pygame.sprite.Sprite。并重写了update精灵行为方法
from abc import ABCMeta,abstractclassmethod
import pygame
class GameObject(object, metaclass=ABCMeta):
"""游戏对象"""
def __init__(self, x=0, y=0,speed=0, image=None):
self._x = x
self._y = y
self._speed = speed
self._image = image
@property
def image(self):
return self._image
@property
def x(self):
return self._x
@property
def y(self):
return self._y
@property
def speed(self):
return self._speed
@x.setter
def x(self,x):
self._x = x
@y.setter
def y(self, y):
self._y = y
@speed.setter
def speed(self, speed):
self._speed = speed
@image.setter
def image(self, image):
self._image = image
class Player(GameObject, pygame.sprite.Sprite):
"""玩家"""
def __init__(self):
super().__init__(260, 600, 6)
pygame.sprite.Sprite.__init__(self)
self._image = pygame.image.load('picture/Player01.png')
self.rect = self._image.get_rect()
self.rect.topleft = (self._x, self._y)
self._score = 0 # 分数
def draw(self, screen):
"""绘制玩家"""
screen.blit(self._image, (self.rect.left, self.rect.top))
def update(self,dir,screen):
"""玩家移动"""
if dir[pygame.K_d] and self.rect.left + self._speed < 520:
self.rect.left += self._speed
if dir[pygame.K_a] and self.rect.left + self._speed > 0:
self.rect.left -= self._speed
if dir[pygame.K_w] and self.rect.top > 0:
self.rect.top -= self._speed
if dir[pygame.K_s] and self.rect.top +self._speed < 645:
self.rect.top += self._speed
self.draw(screen)
@property
def score(self):
return self._score
@score.setter
def score(self,score):
self._score = score
class Enemy(GameObject,pygame.sprite.Sprite):
"""敌机类"""
def __init__(self, x, y, speed):
super().__init__(x, y, speed)
pygame.sprite.Sprite.__init__(self)
self._image = pygame.image.load('picture/enemy_0.png')
self.rect = self._image.get_rect()
self.rect.topleft = (self._x,self._y)
def update(self):
self.rect.top += self._speed
if self.rect.top > 680:
self.kill()
class Background(GameObject):
"""背景类"""
def __init__(self):
super().__init__(0, 0, 2)
bg_color1 = pygame.image.load('picture/Background.png')
bg_color2 = pygame.image.load('picture/Background.png')
self._image = [bg_color1, bg_color2]
def move(self, screen):
"""背景移动"""
self._y += self._speed
if self._y >= 960:
self._image = self._image[1:] + self._image[:1]
self._y = 0
screen.blit(self._image[0], (self._x, self._y))
screen.blit(self._image[1], (self._x, self._y - 960))
class Bullet(GameObject,pygame.sprite.Sprite):
"""子弹类"""
def __init__(self, x, y, image, speed, speed2=0):
super().__init__(x, y, speed, image)
pygame.sprite.Sprite.__init__(self)
self._speed2 = speed2
self.rect = self._image.get_rect()
self.rect.topleft = (self._x, self._y)
def update(self):
self.rect.top -= self._speed
self.rect.left += self._speed2
if self.rect.top < 0 or self.rect.top > 680:
self.kill()
主函数
首先用pygame创建窗口,窗口名,背景色,玩家对象,玩家子弹精灵组,敌机子弹精灵组,敌机精灵组,死亡敌机精灵组(因为要在死亡时产生爆炸效果),爆炸效果图片数组
创建了一个播放音乐的方法,然后用线程启动它,首先用mixer.music.load('dfj.MP3')来加载音乐文件,然后用mixer.music.play启动音乐
创建背景对象,在while循环里调用对象的move进行移动
编写方法:创建玩家子弹并移动和绘画,创建敌机子弹并移动和绘画,创建敌机并移动和绘画,绘画使用精灵的内部方法draw方法,把精灵组的全部精灵绘画到窗口上
然后在主函数判断游戏是否结束,如果没有,就获取键盘输入事件,再调用上面所创建的方法进行绘画和移动
接着检测玩家子弹与敌机是否相碰,两个精灵组相碰用的是sprite的groupcollide方法,相碰的话两者都消失,并把相碰的敌机添加到死亡敌机精灵组,再遍历敌机精灵组,绘画爆炸效果,就是五张图片连续播放出来
继续检测敌机子弹或敌机是否与玩家相撞,一个精灵与一组精灵相撞,使用sprite的spritecollideany的方法,如果相撞,gameover置为True游戏结束,游戏结束会绘画一段字体在屏幕上,按f2会重开游戏,鼠标点击窗口关闭按钮会退出游戏
代码如下:
import pygame
from threading import Thread
from random import *
import GameObject
def main():
def creat_player_bullet(dir):
"""创建玩家子弹"""
nonlocal count2
count2 += 1
if dir[pygame.K_j] and count2 % 10 == 0:
speed = 3
image = pygame.image.load('picture/shotw_0.png')
bullet_player = GameObject.Bullet(player.rect.left + 14, player.rect.top, image, speed, 0)
bullet_player_group.add(bullet_player)
bullet_player_group.update() # 玩家子弹移动
bullet_player_group.draw(screen) # 绘制玩家子弹
def creat_enemy():
"""创建敌机"""
if randint(1, 20) == 1:
x = randint(20, 520)
enemy_group.add(GameObject.Enemy(x, 0, randint(3, 5)))
enemy_group.update() # 敌机移动
enemy_group.draw(screen) # 绘制敌机
def creat_enemy_bullet():
"""创建敌机子弹"""
nonlocal count1
count1 += 1
if count1 % 50 == 0:
for i in enemy_group:
speed = -6
speed2 = int((player.rect.left - i.rect.left) * 0.008571428571428572)
image = pygame.image.load('picture/Bullet.png')
bullet_enemy = GameObject.Bullet(i.rect.left, i.rect.top, image, speed, speed2)
bullet_enemy_group.add(bullet_enemy)
bullet_enemy_group.update()
bullet_enemy_group.draw(screen)
def resetgame():
"""重置游戏"""
nonlocal player, bullet_enemy_group, bullet_player_group, enemy_group, gameover
player = GameObject.Player()
bullet_player_group = pygame.sprite.Group()
bullet_enemy_group = pygame.sprite.Group()
enemy_group = pygame.sprite.Group()
pygame.display.update()
gameover = False
def music_play():
"""播放音乐"""
pygame.mixer.music.load('dfj.MP3')
pygame.mixer.music.play(loops=0, start=0.0)
pygame.init()
player = GameObject.Player() # 创建玩家
bullet_player_group = pygame.sprite.Group() # 创建玩家子弹精灵组
bullet_enemy_group = pygame.sprite.Group() # 创建敌机子弹精灵组
enemy_group = pygame.sprite.Group() # 创建敌机精灵组
enemy_hit_group = pygame.sprite.Group() # 死亡敌机精灵组
enemy_bomb_picture = [] # 爆炸效果图片数组
for i in range(6):
enemy_bomb_picture.append(pygame.image.load('picture/bomb_enemy_%d.png' % i))
background = GameObject.Background() # 创建背景
screen = pygame.display.set_mode((540, 680)) # 创建游戏窗口
pygame.display.set_caption('雷电') # 窗口名
screen.fill((230, 230, 230)) # 填充背景色
Thread(target=music_play, daemon=True).start()
gameover = False
running = True
count = count1 = count2 = 0
while running:
background.move(screen) # 背景移动
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYUP:
if event.key == pygame.K_F2: # f2重置游戏
resetgame()
if not gameover:
dir = pygame.key.get_pressed() # 获取键盘事件
player.update(dir,screen) # 玩家移动并绘画
creat_player_bullet(dir) # 创建玩家子弹并移动和绘画
creat_enemy() # 创建敌机并移动和绘画
creat_enemy_bullet() # 创建敌机子弹
if enemy_hit_group.add(pygame.sprite.groupcollide\
(enemy_group, bullet_player_group, True, True)): # 检测玩家子弹与敌机碰撞
player.score += 1
if pygame.sprite.spritecollideany(player,bullet_enemy_group) \
or pygame.sprite.spritecollideany(player, enemy_group): # 检测玩家与敌机和敌机子弹碰撞
gameover = True
for enemy_hit in enemy_hit_group: # 爆炸
screen.blit(enemy_bomb_picture[count], enemy_hit.rect)
# time.sleep(0.001)
if count < 5:
count += 1
else:
enemy_hit_group.remove(enemy_hit)
pygame.display.flip()
else:
my_font1 = pygame.font.SysFont("宋体", 60)
position1 = my_font1.render('game over', True, (255, 0, 0))
screen.blit(position1, (200, 300))
pygame.display.flip()
pygame.quit()
if __name__ == '__main__':
main()
游戏运行截图:
贪吃蛇
上面两个一个没动,一个连续移动,所以没用帧数控制,而贪吃蛇需要一节一节的动,就需要用帧数控制
蛇是由一个一个蛇节点对象构成,移动的时候把最后的节点删除,然后在头部新增一个头结点
吃食物使用头结点判断,然后添加在尾节点上
其他逻辑没什么想说的,直接上代码。。。
import pygame
import json
from threading import Thread
from random import *
from abc import ABCMeta, abstractclassmethod
BLACK_COLOR = (0, 0, 0)
FOOD_COLOR = (236, 189, 187)
GREEN_COLOR = (0, 255, 0)
UP = 0
RIGHT = 1
DOWN = 2
LEFT = 3
class GameObject(object, metaclass=ABCMeta):
"""游戏对象"""
def __init__(self, x=0, y=0, color=BLACK_COLOR):
self._x = x
self._y = y
self._color = color
@abstractclassmethod
def draw(self, screen):
pass
@property
def x(self):
return self._x
@property
def y(self):
return self._y
class Wall(GameObject):
"""围墙类"""
def __init__(self, x, y, lenth, width, color=BLACK_COLOR):
super().__init__(x, y, color)
self._lenth = lenth
self._width = width
def draw(self, screen):
pygame.draw.rect(screen, self._color, (self._x, self._y, self._width, self._lenth), 4)
@property
def lenth(self):
return self._lenth
@property
def width(self):
return self._width
class Food(GameObject):
"""食物类"""
def __init__(self, x, y, length, color=FOOD_COLOR):
super().__init__(x, y, color)
self._length = length
self._hidden = False # 是否隐藏
def draw(self, screen):
if not self._hidden:
pygame.draw.circle(screen, self._color,
(self._x + self._length // 2, self._y + self._length // 2),
self._length // 2, 0)
self._hidden = not self._hidden
class SnakeNode(GameObject):
"""蛇身节点类"""
def __init__(self, x, y, size, color=GREEN_COLOR):
super().__init__(x, y, color)
self._size = size
def draw(self, screen):
pygame.draw.rect(screen, self._color,
(self._x, self._y, self._size, self._size), 0)
pygame.draw.rect(screen, BLACK_COLOR,
(self._x, self._y, self._size, self._size), 1)
@property
def size(self):
return self._size
class Snake(GameObject):
"""蛇"""
def __init__(self):
super().__init__()
self._dir = LEFT # 方向
self._nodes = [] # 身体数组
for index in range(5):
node = SnakeNode(290 + index * 20, 250, 20)
self._nodes.append(node)
def collide(self, wall):
"""撞墙"""
head = self.head
return head.x < wall.x or head.y < wall.y or head.x + head.size > wall.x + wall.lenth\
or head.y + head.size > wall.y + wall.width
def eat_food(self, food):
"""吃食物"""
if self.head.x == food.x and self.head.y == food.y:
tail = self._nodes[-1]
self._nodes.append(tail)
return True
return False
def eat_self(self):
for i in range(4, len(self.nodes)):
if self.head.x == self.nodes[i].x and self.head.y == self.nodes[i].y:
return True
return False
def draw(self, screen):
"""画蛇"""
for node in self._nodes:
node.draw(screen)
def move(self):
"""移动"""
head = self.head
snake_dir = self._dir
x, y, size = head.x, head.y, head.size
if snake_dir == UP:
y -= size
elif snake_dir == RIGHT:
x += size
elif snake_dir == DOWN:
y += size
else:
x -= size
new_head = SnakeNode(x, y, size)
self._nodes.insert(0, new_head)
self._nodes.pop()
@property
def dir(self):
return self._dir
@property
def nodes(self):
return self._nodes
@property
def head(self):
return self._nodes[0]
def change_dir(self, new_dir):
if (self._dir + new_dir) % 2 != 0:
self._dir = new_dir
def main():
def refresh():
"""刷新游戏窗口"""
screen.fill((242, 242, 242))
wall.draw(screen) # 画围墙
food.draw(screen) # 画食物
snake.draw(screen) # 画蛇
show_info(screen, snake)
pygame.display.flip()
def reset_game():
nonlocal food, snake, gameover, rank
food = creat_food()
snake = Snake()
gameover = False
def handle_key_event(key_event):
"""处理按键事件"""
key = key_event.key
new_dir = snake.dir
if key == pygame.K_w or key == pygame.K_UP:
new_dir = UP
if key == pygame.K_d or key == pygame.K_RIGHT:
new_dir = RIGHT
if key == pygame.K_s or key == pygame.K_DOWN:
new_dir = DOWN
if key == pygame.K_a or key == pygame.K_LEFT:
new_dir = LEFT
if key == pygame.K_F2:
reset_game()
return
if new_dir != snake.dir:
snake.change_dir(new_dir)
def creat_food():
"""吃食物"""
row = randint(0, 29)
col = randint(0, 29)
return Food(10 + 20 * col, 10 + 20 * row, 20)
def show_info(screen1, snake1):
"""展示分数"""
my_font2 = pygame.font.SysFont("宋体", 30)
position3 = my_font2.render('high score: %d' % max(rank), True, (255, 0, 0))
screen1.blit(position3, (453, 10))
position2 = my_font2.render('score: %d' % (len(snake1.nodes) - 5), True, (255, 0, 0))
screen1.blit(position2, (500, 30))
pygame.display.flip()
try:
with open('rank.json', 'r', encoding='utf-8') as fs:
if len(fs.read()) == 0:
rank = [0] # 排行榜
else:
with open('rank.json', 'r', encoding='utf-8') as fs2:
rank = json.load(fs2)
except IOError as e:
print(e)
pygame.init()
screen = pygame.display.set_mode((620, 620)) # 创建游戏窗口
pygame.display.set_caption('贪吃蛇') # 窗口名
screen.fill((242, 242, 242)) # 填充背景色
pygame.display.flip() # 刷新窗口
clock = pygame.time.Clock() # 创建计时器,控制帧数
wall = Wall(10, 10, 600, 600) # 围墙
food = creat_food() # 食物
snake = Snake() # 蛇
running = True
gameover = False
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
try:
with open('rank.json', 'w', encoding='utf-8') as fs:
json.dump(rank, fs)
except IOError as e:
print(e)
running = False
elif event.type == pygame.KEYDOWN:
handle_key_event(event)
elif event.type == pygame.MOUSEBUTTONDOWN:
pass
if not gameover:
Thread(target=refresh,daemon=True).start()
snake.move()
if snake.collide(wall) or snake.eat_self():
gameover = True
if snake.eat_food(food):
food = creat_food()
if gameover:
rank.append(len(snake.nodes) - 5)
else:
my_font1 = pygame.font.SysFont("宋体", 60)
position1 = my_font1.render('game over', True, (255, 0, 0))
screen.blit(position1, (200, 300))
pygame.display.flip()
clock.tick(6) # 6帧
pygame.quit()
if __name__ == '__main__':
main()
游戏运行结果: