赞
踩
这次来制作一个Block Breaker游戏,即我们以前玩过的一个小游戏,用一块板子撞击小球,而小球碰到砖块会反弹,同时被碰到的砖块会消失,直到所有的砖块都消失,游戏结束,进入下一关。
那么接下来就好好介绍一下这个游戏的制作过程。
在这次制作中,上一章制作的MyLibrary.py有一些改动和添加,完整的新代码如下:
import random import sys import math import time import pygame from pygame.locals import * # 类中的X、Y、position用于设置精灵的位置 class MySprite(pygame.sprite.Sprite): def __init__(self): # 调用父类的初始化方法 pygame.sprite.Sprite.__init__(self) # self.image = pygame.image.load(image_file).convert_alpha() # self.rect = self.image.get_rect() self.master_image = None self.frame = 0 self.old_frame = -1 self.frame_width = 1 self.frame_height = 1 self.first_frame = 0 self.last_frame = 0 self.columns = 1 self.last_time = 0 self.direction = 0 self.velocity = Point(0.0, 0.0) def getx(self): return self.rect.x def setx(self, value): self.rect.x = value X = property(getx, setx) def gety(self): return self.rect.y def sety(self, value): self.rect.y = value Y = property(gety, sety) def getpos(self): return self.rect.topleft def setpos(self, pos): self.rect.topleft = pos position = property(getpos, setpos) def load(self, filename, width=0, height=0, columns=1): self.master_image = pygame.image.load(filename).convert_alpha() self.set_image(self.master_image, width, height, columns) def set_image(self, image, width=0, height=0, columns=1): self.master_image = image if width == 0 and height == 0: self.frame_width = image.get_width() self.frame_height = image.get_height() else: self.frame_width = width self.frame_height = height rect = self.master_image.get_rect() self.last_frame = (rect.width // width) * (rect.height // height) - 1 self.rect = Rect(0, 0, self.frame_width, self.frame_height) self.columns = columns def update(self, current_time, rate=30): # 帧变动 if self.last_frame > self.first_frame: if current_time > self.last_time + rate: self.frame += 1 if self.frame > self.last_frame: self.frame = self.first_frame self.last_time = current_time else: self.frame = self.first_frame # 当帧发生变化时,进行修改 if self.frame != self.old_frame: frame_x = (self.frame % self.columns) * self.frame_width frame_y = (self.frame // self.columns) * self.frame_height rect = Rect(frame_x, frame_y, self.frame_width, self.frame_height) # 将要展示的图片送给image属性,以便展示出来 self.image = self.master_image.subsurface(rect) self.old_frame = self.frame def __str__(self): return str(self.frame) + "," + str(self.first_frame) + "," + str(self.last_frame) + \ "," + str(self.frame_width) + "," + str(self.frame_height) + "," + \ str(self.columns) + "," + str(self.rect) class Point(object): def __init__(self, x, y): self.__x = x self.__y = y def getx(self): return self.__x def setx(self, x): self.__x = x x = property(getx, setx) def gety(self): return self.__y def sety(self, y): self.__y = y y = property(gety, sety) def __str__(self): return "{X:" + "{:.0f}".format(self.__x) + ",Y:" + "{:.0f}".format(self.__y) + "}" def print_text(font, x, y, text, color=(255, 255, 255), shadow=True): imgText = font.render(text, True, color) screen = pygame.display.get_surface() screen.blit(imgText, (x, y))
游戏中设置了4个关卡,存储在levels中,每一关的样子如下。
关卡1:
(
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
)
图形如下:
关卡2:
(
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2,
2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2,
2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
)
图形如下:
关卡3:
(
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3,
3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3,
3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3,
3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3,
3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
)
图形如下:
关卡4:
(
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 0, 0, 4, 4, 0, 0, 4, 4, 0, 0, 4,
4, 0, 0, 4, 4, 0, 0, 4, 4, 0, 0, 4,
4, 4, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4,
4, 4, 4, 0, 0, 4, 4, 4, 0, 0, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 4, 4,
4, 4, 0, 0, 0, 0, 4, 4, 0, 0, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 0, 0, 4, 4, 0, 0, 4, 4, 4,
4, 4, 4, 0, 0, 4, 4, 0, 0, 4, 4, 4
)
图形如下:
这一次,因为代码量比较大,所以我们将很多部分进行了模块化,游戏的一些初始化操作都封装到一个函数中:
# 游戏的一些初始化设置 def game_init(): global screen, font, timer global paddle_group, block_group, ball_group global paddle, block_image, block, ball pygame.init() screen = pygame.display.set_mode((800, 600)) pygame.display.set_caption("Block Breaker Game") font = pygame.font.Font(None, 40) pygame.mouse.set_visible(False) timer = pygame.time.Clock() # 创建精灵组 paddle_group = pygame.sprite.Group() block_group = pygame.sprite.Group() ball_group = pygame.sprite.Group() # 创建板子 paddle = MySprite() paddle.load("paddle.png") paddle.position = 400, 540 paddle_group.add(paddle) # 创建球 ball = MySprite() ball.load("ball.png") ball.position = 400, 300 ball_group.add(ball)
为了能够让函数中初始化后的变量能够在其他地方被使用,因此都是使用的global变量。
和关卡相关的一些操作其实就是对砖块的处理,共有3个函数:
# 加载关卡 def load_level(): global level, levels, block_image, block_group block_image = pygame.image.load("blocks.png").convert_alpha() # 先将砖块组中的对象清空 block_group.empty() for bx in range(0, 12): for by in range(0, 10): block = MySprite() block.set_image(block_image, 58, 28, 4) x = 40 + bx * (block.frame_width + 1) y = 60 + by * (block.frame_height + 1) block.position = x, y num = levels[level][by * 12 + bx] block.first_frame = num - 1 block.last_frame = num - 1 if num > 0: block_group.add(block) # 更新砖块 def update_blocks(): global block_group, waiting if len(block_group) == 0: goto_next_level() waiting = True block_group.update(ticks, 50) # 进入到下一个关卡 def goto_next_level(): global level, levels level += 1 if level > len(levels) - 1: level = 0 load_level()
需要移动的精灵图像分别是球和板子,其中球的处理如下:
# 重置球的速度 def reset_ball(): ball.velocity = Point(4.5, -7.0) # 移动球 def move_ball(): global waiting, ball, game_over, lives ball_group.update(ticks, 50) # 若球掉下去了,重新将球放到板子上 if waiting: ball.X = paddle.X + 40 ball.Y = paddle.Y - 20 ball.X += ball.velocity.x ball.Y += ball.velocity.y if ball.X < 0: ball.X = 0 ball.velocity.x *= -1 elif ball.X > 780: ball.X = 780 ball.velocity.x *= -1 if ball.Y < 0: ball.Y = 0 ball.velocity.y *= -1 elif ball.Y > 580: waiting = True lives -= 1 if lives < 1: game_over = True
板子的移动如下:
# 移动板子 def move_paddle(): global movex, movey, keys, waiting paddle_group.update(ticks, 50) # 可以通过空格、左右键控制板子 if keys[K_SPACE]: if waiting: waiting = False reset_ball() elif keys[K_LEFT]: paddle.velocity.x = -10.0 elif keys[K_RIGHT]: paddle.velocity.x = 10.0 else: if movex < -2: paddle.velocity.x = movex elif movex > 2: paddle.velocity.x = movex else: paddle.velocity.x = 0 paddle.X += paddle.velocity.x if paddle.X < 0: paddle.X = 0 elif paddle.X > 710: paddle.X = 710
对于碰撞检测,我们既需要检测球与板子之间的碰撞,也需要检测球与砖块之间的碰撞,其中球与板子之间的碰撞检测如下:
# 球与板子的冲突检测
def collision_ball_paddle():
if pygame.sprite.collide_rect(ball, paddle):
ball.velocity.y = -abs(ball.velocity.y)
bx = ball.X + 8
by = ball.Y + 8
px = paddle.X + paddle.frame_width / 2
py = paddle.Y + paddle.frame_height / 2
if bx < px:
ball.velocity.x = -abs(ball.velocity.x)
else:
ball.velocity.x = abs(ball.velocity.x)
这里可以看到,我们并不是简单地按照相反的方向反弹,而是要根据球落在板子的位置的不同,以不同的方式反弹。
球与砖块之间的碰撞检测如下:
# 球与砖块的冲突检测 def collision_ball_blocks(): global score, block_group, ball hit_block = pygame.sprite.spritecollideany(ball, block_group) if hit_block: score += 10 block_group.remove(hit_block) bx = ball.X + 8 by = ball.Y + 8 if bx > hit_block.X + 5 and bx < hit_block.X + hit_block.frame_width - 5: if by < hit_block.Y + hit_block.frame_height / 2: ball.velocity.y = -abs(ball.velocity.y) else: ball.velocity.y = abs(ball.velocity.y) elif bx < hit_block.X + 5: ball.velocity.x = -abs(ball.velocity.x) elif bx > hit_block.X + hit_block.frame_width - 5: ball.velocity.x = abs(ball.velocity.x) else: ball.velocity.y *= -1
这里用到了pygame.sprite.spritecollideany()函数进行检测,因为我们需要将被碰到的砖块消去,同时对碰撞后球的速度和方向的处理同前面一样,而不是简单的相反的方向。
完整的代码如下:
import random import sys import pygame from pygame.locals import * from MyLibrary import * levels = ( ( 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ), ( 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ), ( 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 ), ( 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 4, 4, 0, 0, 4, 4, 0, 0, 4, 4, 0, 0, 4, 4, 0, 0, 4, 4, 0, 0, 4, 4, 4, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 4, 4, 4, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 4, 4, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 4, 4, 0, 0, 4, 4, 4, 4, 4, 4, 0, 0, 4, 4, 0, 0, 4, 4, 4 ) ) # 游戏的一些初始化设置 def game_init(): global screen, font, timer global paddle_group, block_group, ball_group global paddle, block_image, block, ball pygame.init() screen = pygame.display.set_mode((800, 600)) pygame.display.set_caption("Block Breaker Game") font = pygame.font.Font(None, 40) pygame.mouse.set_visible(False) timer = pygame.time.Clock() # 创建精灵组 paddle_group = pygame.sprite.Group() block_group = pygame.sprite.Group() ball_group = pygame.sprite.Group() # 创建板子 paddle = MySprite() paddle.load("paddle.png") paddle.position = 400, 540 paddle_group.add(paddle) # 创建球 ball = MySprite() ball.load("ball.png") ball.position = 400, 300 ball_group.add(ball) # 加载关卡 def load_level(): global level, levels, block_image, block_group block_image = pygame.image.load("blocks.png").convert_alpha() # 先将砖块组中的对象清空 block_group.empty() for bx in range(0, 12): for by in range(0, 10): block = MySprite() block.set_image(block_image, 58, 28, 4) x = 40 + bx * (block.frame_width + 1) y = 60 + by * (block.frame_height + 1) block.position = x, y num = levels[level][by * 12 + bx] block.first_frame = num - 1 block.last_frame = num - 1 if num > 0: block_group.add(block) # 更新砖块 def update_blocks(): global block_group, waiting if len(block_group) == 0: goto_next_level() waiting = True block_group.update(ticks, 50) # 进入到下一个关卡 def goto_next_level(): global level, levels level += 1 if level > len(levels) - 1: level = 0 load_level() # 移动板子 def move_paddle(): global movex, movey, keys, waiting paddle_group.update(ticks, 50) # 可以通过空格、左右键控制板子 if keys[K_SPACE]: if waiting: waiting = False reset_ball() elif keys[K_LEFT]: paddle.velocity.x = -10.0 elif keys[K_RIGHT]: paddle.velocity.x = 10.0 else: if movex < -2: paddle.velocity.x = movex elif movex > 2: paddle.velocity.x = movex else: paddle.velocity.x = 0 paddle.X += paddle.velocity.x if paddle.X < 0: paddle.X = 0 elif paddle.X > 710: paddle.X = 710 # 重置球的速度 def reset_ball(): ball.velocity = Point(4.5, -7.0) # 移动球 def move_ball(): global waiting, ball, game_over, lives ball_group.update(ticks, 50) # 若球掉下去了,重新将球放到板子上 if waiting: ball.X = paddle.X + 40 ball.Y = paddle.Y - 20 ball.X += ball.velocity.x ball.Y += ball.velocity.y if ball.X < 0: ball.X = 0 ball.velocity.x *= -1 elif ball.X > 780: ball.X = 780 ball.velocity.x *= -1 if ball.Y < 0: ball.Y = 0 ball.velocity.y *= -1 elif ball.Y > 580: waiting = True lives -= 1 if lives < 1: game_over = True # 球与板子的冲突检测 def collision_ball_paddle(): if pygame.sprite.collide_rect(ball, paddle): ball.velocity.y = -abs(ball.velocity.y) bx = ball.X + 8 by = ball.Y + 8 px = paddle.X + paddle.frame_width / 2 py = paddle.Y + paddle.frame_height / 2 if bx < px: ball.velocity.x = -abs(ball.velocity.x) else: ball.velocity.x = abs(ball.velocity.x) # 球与砖块的冲突检测 def collision_ball_blocks(): global score, block_group, ball hit_block = pygame.sprite.spritecollideany(ball, block_group) if hit_block: score += 10 block_group.remove(hit_block) bx = ball.X + 8 by = ball.Y + 8 if bx > hit_block.X + 5 and bx < hit_block.X + hit_block.frame_width - 5: if by < hit_block.Y + hit_block.frame_height / 2: ball.velocity.y = -abs(ball.velocity.y) else: ball.velocity.y = abs(ball.velocity.y) elif bx < hit_block.X + 5: ball.velocity.x = -abs(ball.velocity.x) elif bx > hit_block.X + hit_block.frame_width - 5: ball.velocity.x = abs(ball.velocity.x) else: ball.velocity.y *= -1 if __name__ == "__main__": game_init() game_over = False waiting = True score = 0 lives = 3 level = 3 load_level() while True: timer.tick(30) ticks = pygame.time.get_ticks() for event in pygame.event.get(): if event.type == QUIT: sys.exit() elif event.type == MOUSEMOTION: movex, movey = event.rel elif event.type == MOUSEBUTTONUP: if waiting: waiting = False reset_ball() elif event.type == KEYUP: if event.key == K_RETURN: goto_next_level() keys = pygame.key.get_pressed() if keys[K_ESCAPE]: sys.exit() if not game_over: update_blocks() move_paddle() move_ball() collision_ball_paddle() collision_ball_blocks() screen.fill((50, 50, 100)) block_group.draw(screen) ball_group.draw(screen) paddle_group.draw(screen) print_text(font, 0, 0, "SCORE:" + str(score)) print_text(font, 200, 0, "LEVEL:" + str(level + 1)) print_text(font, 400, 0, "BLOCKS:" + str(len(block_group))) print_text(font, 670, 0, "BALLS:" + str(lives)) if game_over: print_text(font, 300, 380, "G A M E O V E R") pygame.display.update()
运行结果如下:
添加颜色的部分如下,首先添加一个颜色的元组:
colors = (
(0, 0, 0), (0, 100, 50), (50, 50, 50), (100, 100, 100), (200, 200, 200), (150, 200, 255)
)
然后在循环外面申明一个color赋初值:
color = colors[0]
最后在循环内部:
if ticks % 50 == 0:
i = colors.index(color)
color = colors[(i + 1) % len(colors)]
screen.fill(color)
而添加计时功能则如下:
times = 0.0
times = ticks / 1000
print_text(font, 640, 0, "TIME:" + "{:.0f}".format(times) + "s")
因为ticks记录的是毫秒,因此将其转换成秒缩短表示的长度
这个问题只需要修改一下collision_ball_paddle()函数即可,如下:
# 球与板子的冲突检测
def collision_ball_paddle():
if pygame.sprite.collide_rect(ball, paddle):
ball.velocity.y = -abs(random.randint(5, 15))
bx = ball.X + 8
by = ball.Y + 8
px = paddle.X + paddle.frame_width / 2
py = paddle.Y + paddle.frame_height / 2
if bx < px:
ball.velocity.x = -abs(random.randint(5, 15))
else:
ball.velocity.x = abs(random.randint(5, 15))
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。