本课任务在上一课基础上,实现下面几个任务。
第五步:窗体底部绘制一个挡板
第六步:用鼠标控制挡板左右移动
第七步:小球碰到挡板反弹,碰到底部结束
第八步:绘制游戏得分
第八步:绘制游戏结束界面文字
5.绘制挡板
代码:
import pygame
import sys
import random
# 背景白色
bg = (255,255,255)
# 屏幕宽度和高度
size =width,height=400,300
# 球的颜色 红色
ball_color = (255,0,0)
# 球的大小 半径
ball_size = 20
# 球的初始位置 设置在窗口中心位置
pos_x,pos_y = width//2-ball_size,height//2-ball_size
# 设置球的 初始速度
speed_x=random.randint(1,5)
speed_y=random.randint(1,5)
# 设置挡板的 颜色 蓝色
board_color = (0,0,255)
# 设置挡板的 宽和高
board_width,board_height=50,4
# 设置挡板的初始位置
board_x,board_y = (width-board_width)//2,height-board_height
class Ball:
def __init__(self,ball_color,pos,ball_size,ball_speed):
self.color=ball_color
self.pos=pos
self.size=ball_size
self.speed=ball_speed
class Board:
def __init__(self,board_color,board_pos):
self.color=board_color
self.pos=board_pos
class Game:
def __init__(self):
self.ball=Ball(ball_color,[pos_x,pos_y],ball_size,[speed_x,speed_y])
self.board = Board(board_color,[board_x,board_y,board_width,board_height])
def process_event(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
def run_logic(self):
if self.ball.pos[0] <self.ball.size or self.ball.pos[0] > width-self.ball.size:
self.ball.speed[0] = -self.ball.speed[0]
if self.ball.pos[1] <self.ball.size or self.ball.pos[1] > height-self.ball.size:
self.ball.speed[1] = -self.ball.speed[1]
# 更改位置
self.ball.pos[0] += self.ball.speed[0]
self.ball.pos[1] += self.ball.speed[1]
def display_frame(self,screen):
screen.fill(bg) pygame.draw.circle(screen,self.ball.color,self.ball.pos,self.ball.size)
pygame.draw.rect(screen,self.board.color,self.board.pos)
pygame.display.flip()
def main():
pygame.init()
screen = pygame.display.set_mode(size)
pygame.display.set_caption("弹球游戏")
game=Game()
while True:
game.process_event()
game.run_logic()
game.display_frame(screen)
pygame.time.wait(100)
main()
效果图:
解释:
绘制挡板和绘制小球是一个思路,挡板具有颜色,大小,绘制的位置。在前面设置了挡板的属性。
# 设置挡板的 颜色 蓝色
board_color = (0,0,255)
# 设置挡板的 宽和高
board_width,board_height=50,4
# 设置挡板的初始位置
board_x,board_y = (width-board_width)//2,height-board_height
挡板也是属于一个类,创建一个Board类,给它设置一个颜色board_color和一个位置board_pos属性,位置属性里面其实就包含了挡板的大小和绘制位置。
6.用鼠标控制挡板左右移动
代码:
import pygame
import sys
import random
# 背景白色
bg = (255,255,255)
# 屏幕宽度和高度
size =width,height=400,300
# 球的颜色 红色
ball_color = (255,0,0)
# 球的大小 半径
ball_size = 20
# 球的初始位置 设置在窗口中心位置
pos_x,pos_y = width//2-ball_size,height//2-ball_size
# 设置球的 初始速度
speed_x=random.randint(1,5)
speed_y=random.randint(1,5)
# 设置挡板的 颜色 蓝色
board_color = (0,0,255)
# 设置挡板的 宽和高
board_width,board_height=50,4
# 设置挡板的初始位置
board_x,board_y = (width-board_width)//2,height-board_height
class Ball:
def __init__(self,ball_color,pos,ball_size,ball_speed):
self.color=ball_color
self.pos=pos
self.size=ball_size
self.speed=ball_speed
def move(self):
if self.pos[0] < self.size or self.pos[0] > width-self.size:
self.speed[0] = -self.speed[0]
if self.pos[1] < self.size or self.pos[1] > height-self.size:
self.speed[1] = -self.speed[1]
# 更改位置
self.pos[0] += self.speed[0]
self.pos[1] += self.speed[1]
class Board:
def __init__(self,board_color,board_pos):
self.color=board_color
self.pos=board_pos
def move(self):
mouse_pos=pygame.mouse.get_pos()
self.pos[0]=mouse_pos[0]
class Game:
def __init__(self):
self.ball=Ball(ball_color,[pos_x,pos_y],ball_size,[speed_x,speed_y])
self.board = Board(board_color,[board_x,board_y,board_width,board_height])
def process_event(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
def run_logic(self):
# 小球移动
self.ball.move()
# 挡板跟随鼠标移动
self.board.move()
def display_frame(self,screen):
screen.fill(bg)
pygame.draw.circle(screen,self.ball.color,self.ball.pos,self.ball.size)
pygame.draw.rect(screen,self.board.color,self.board.pos)
pygame.display.flip()
def main():
pygame.init()
screen = pygame.display.set_mode(size)
pygame.display.set_caption("弹球游戏")
game=Game()
while True:
game.process_event()
game.run_logic()
game.display_frame(screen)
pygame.time.wait(100)
main()
效果:
解释:
挡板可以跟随鼠标移动,给挡板类添加一个move()的方法,在这里面获取鼠标的位置,由于挡板竖直方向不变,所以只需要更改x坐标位置就可以了。
def move(self):
mouse_pos=pygame.mouse.get_pos()
self.pos[0]=mouse_pos[0]
同理把之前小球移动的代码从Game中的run_logic里移动Ball中的新建的move方法,移过来后需要将原来的所有.ball给删除掉了(在自己的类中调用,就不用ball对象了)
def move(self):
if self.pos[0] < self.size or self.pos[0] > width-self.size:
self.speed[0] = -self.speed[0]
if self.pos[1] < self.size or self.pos[1] > height-self.size:
self.speed[1] = -self.speed[1]
# 更改位置
self.pos[0] += self.speed[0]
self.pos[1] += self.speed[1]
最后在Game类中的run_logic只需要直接调用小球和挡板的move方法就可以了。
def run_logic(self):
# 小球移动
self.ball.move()
# 挡板跟随鼠标移动
self.board.move()
7.小球碰到挡板反弹,碰到底部结束
代码:
import pygame
import sys
import random
# 背景白色
bg = (255,255,255)
# 屏幕宽度和高度
size =width,height=400,300
# 球的颜色 红色
ball_color = (255,0,0)
# 球的大小 半径
ball_size = 20
# 球的初始位置 设置在窗口中心位置
pos_x,pos_y = width//2-ball_size,height//2-ball_size
# 设置球的 初始速度
speed_x=random.randint(5,15)
speed_y=random.randint(5,15)
# 设置挡板的 颜色 蓝色
board_color = (0,0,255)
# 设置挡板的 宽和高
board_width,board_height=50,4
# 设置挡板的初始位置
board_x,board_y = (width-board_width)//2,height-board_height
class Ball:
def __init__(self,ball_color,pos,ball_size,ball_speed):
self.color=ball_color
self.pos=pos
self.size=ball_size
self.speed=ball_speed
def move(self):
if self.pos[0] < self.size or self.pos[0] > width-self.size:
self.speed[0] = -self.speed[0]
if self.pos[1] < self.size:
self.speed[1] = -self.speed[1]
# 更改位置
self.pos[0] += self.speed[0]
self.pos[1] += self.speed[1]
class Board:
def __init__(self,board_color,board_pos):
self.color=board_color
self.pos=board_pos
def move(self):
mouse_pos=pygame.mouse.get_pos()
self.pos[0]=mouse_pos[0]
class Game:
def __init__(self):
self.ball=Ball(ball_color,[pos_x,pos_y],ball_size,[speed_x,speed_y])
self.board = Board(board_color,[board_x,board_y,board_width,board_height])
#设置游戏状态标志标量
self.gameover=False
def process_event(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
return True
return False
def run_logic(self):
# 小球移动
self.ball.move()
# 挡板跟随鼠标移动
self.board.move()
# 如果球已经进入到board厚度以下就要开始检测是否碰撞
if self.ball.pos[1]>=self.board.pos[1]-self.ball.size:
# 如果球与board接触,就反弹
if self.board.pos[0]<=self.ball.pos[0]<=self.board.pos[0]+self.board.pos[2]:
self.ball.speed[1]=-self.ball.speed[1]
else:
# 没有碰到挡板就将游戏状态设置为结束
self.gameover=True
def display_frame(self,screen):
screen.fill(bg) pygame.draw.circle(screen,self.ball.color,self.ball.pos,self.ball.size)
pygame.draw.rect(screen,self.board.color,self.board.pos)
pygame.display.flip()
def main():
pygame.init()
screen = pygame.display.set_mode(size)
pygame.display.set_caption("弹球游戏")
game=Game()
# 当游戏没有结束就一直循环
while not game.gameover:
game.process_event()
game.run_logic()
game.display_frame(screen)
pygame.time.wait(100)
pygame.quit()
main()
解释:
这里是要检测小球运动的过程中是否碰到了挡板,碰到了就反弹将速度设置为相反;碰到底部就游戏结束。在Game类中的init中加上一个gameover的游戏状态标志变量,在run_logic加上逻辑判断的代码,当小球的y坐标self.ball.pos[1]已经小于等于此时的挡板的y坐标 self.board.pos[1]-self.ball.size时,游戏结束,将self.gameover设置为True,这里之所以要减去self.ball.size是因为小球的坐标是在圆心,而碰撞检测是在底部,所以有一个小球半径差。
class Game:
ef __init__(self):
# 原来的代码不变
#设置游戏状态标志标量
self.gameover=False
def run_logic(self):
# 小球移动
self.ball.move()
# 挡板跟随鼠标移动
self.board.move()
# 如果球已经进入到board厚度以下就要开始检测是否碰撞
if self.ball.pos[1]>=self.board.pos[1]-self.ball.size:
# 如果球与board接触,就反弹
if self.board.pos[0]<=self.ball.pos[0]<=self.board.pos[0]+self.board.pos[2]:
self.ball.speed[1]=-self.ball.speed[1]
else:
# 没有碰到挡板就将游戏状态设置为结束
self.gameover=True
在main函数中,将原来的while True循环改成while not gameover;这样当gameover变成True时,就会退出游戏循环,游戏也就结束了,结束后加上pygame.quit()。
def main():
pygame.init()
screen = pygame.display.set_mode(size)
pygame.display.set_caption("弹球游戏")
game=Game() # 当游戏没有结束就一直循环
while not game.gameover:
game.process_event()
game.run_logic()
game.display_frame(screen)
pygame.time.wait(100)
pygame.quit()
8.绘制游戏得分
import pygameimport sysimport random
# 背景白色bg = (255,255,255) # 屏幕宽度和高度size =width,height=400,300# 球的颜色 红色ball_color = (255,0,0)# 球的大小 半径ball_size = 20# 球的初始位置 设置在窗口中心位置pos_x,pos_y = width//2-ball_size,height//2-ball_size# 设置球的 初始速度speed_x=random.randint(5,15)speed_y=random.randint(5,15)
# 设置挡板的 颜色 蓝色board_color = (0,0,255)# 设置挡板的 宽和高board_width,board_height=50,4# 设置挡板的初始位置board_x,board_y = (width-board_width)//2,height-board_height
class Ball: def __init__(self,ball_color,pos,ball_size,ball_speed): self.color=ball_color self.pos=pos self.size=ball_size self.speed=ball_speed
def move(self): if self.pos[0] < self.size or self.pos[0] > width-self.size: self.speed[0] = -self.speed[0] if self.pos[1] < self.size: self.speed[1] = -self.speed[1] # 更改位置 self.pos[0] += self.speed[0] self.pos[1] += self.speed[1]
class Board: def __init__(self,board_color,board_pos): self.color=board_color self.pos=board_pos
def move(self): mouse_pos=pygame.mouse.get_pos() self.pos[0]=mouse_pos[0]
class Game: def __init__(self): self.ball=Ball(ball_color,[pos_x,pos_y],ball_size,[speed_x,speed_y]) self.board = Board(board_color,[board_x,board_y,board_width,board_height]) #设置游戏状态标志标量 self.gameover=False self.score = 0 def process_event(self): for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() return True return False
def run_logic(self): # 小球移动 self.ball.move() # 挡板跟随鼠标移动 self.board.move() # 如果球已经进入到board厚度以下就要开始检测是否碰撞 if self.ball.pos[1]>=self.board.pos[1]-self.ball.size: # 如果球与board接触,就反弹 if self.board.pos[0]<=self.ball.pos[0]<=self.board.pos[0]+self.board.pos[2]: self.ball.speed[1]=-self.ball.speed[1] self.score+=1 else: # 没有碰到挡板就将游戏状态设置为结束 self.gameover=True
def display_frame(self,screen,font): screen.fill(bg) pygame.draw.circle(screen,self.ball.color,self.ball.pos,self.ball.size) pygame.draw.rect(screen,self.board.color,self.board.pos) # 显示计分 text = font.render("score:"+str(self.score), True, (255,0,0)) screen.blit(text,(10,10)) pygame.display.flip()
def main(): pygame.init() screen = pygame.display.set_mode(size) pygame.display.set_caption("弹球游戏") game=Game() # 字体设置 font=pygame.font.SysFont("Arial",24) # 当游戏没有结束就一直循环 while not game.gameover: game.process_event() game.run_logic() game.display_frame(screen,font) pygame.time.wait(100) pygame.quit()
main()
效果:
解释:
现在添加一些小功能已经非常简单了,这一切都归功于一开始面向对象的设计方法。给Game类中添加也给score的属性,在run_logic碰撞检测中添加一行self.socre+=1就可以了。
self.score+=1
在display_flame中添加两行代码,创建文本对象,然后将其绘制到屏幕的左上角,同时给方法中传递一个font的字体对象。
def display_frame(self,screen,font): screen.fill(bg) pygame.draw.circle(screen,self.ball.color,self.ball.pos,self.ball.size) pygame.draw.rect(screen,self.board.color,self.board.pos) # 显示计分 text = font.render("score:"+str(self.score), True, (255,0,0)) screen.blit(text,(10,10)) pygame.display.flip()
在main中设置字体。
# 字体设置font=pygame.font.SysFont("Arial",24)
9.游戏结束和重新开始
代码:
import pygameimport sysimport random
# 背景白色bg = (255,255,255) # 屏幕宽度和高度size =width,height=400,300# 球的颜色 红色ball_color = (255,0,0)# 球的大小 半径ball_size = 20# 球的初始位置 设置在窗口中心位置pos_x,pos_y = width//2-ball_size,height//2-ball_size# 设置球的 初始速度speed_x=random.randint(5,15)speed_y=random.randint(5,15)
# 设置挡板的 颜色 蓝色board_color = (0,0,255)# 设置挡板的 宽和高board_width,board_height=50,4# 设置挡板的初始位置board_x,board_y = (width-board_width)//2,height-board_height
class Ball: def __init__(self,ball_color,pos,ball_size,ball_speed): self.color=ball_color self.pos=pos self.size=ball_size self.speed=ball_speed
def move(self): if self.pos[0] < self.size or self.pos[0] > width-self.size: self.speed[0] = -self.speed[0] if self.pos[1] < self.size: self.speed[1] = -self.speed[1] # 更改位置 self.pos[0] += self.speed[0] self.pos[1] += self.speed[1]
class Board: def __init__(self,board_color,board_pos): self.color=board_color self.pos=board_pos
def move(self): mouse_pos=pygame.mouse.get_pos() self.pos[0]=mouse_pos[0]
class Game: def __init__(self): self.ball=Ball(ball_color,[pos_x,pos_y],ball_size,[speed_x,speed_y]) self.board = Board(board_color,[board_x,board_y,board_width,board_height]) #设置游戏状态标志标量 self.gameover=False self.score = 0 def process_event(self): for event in pygame.event.get(): if event.type == pygame.QUIT: return True if event.type==pygame.KEYUP: if event.key==pygame.K_SPACE: if self.gameover: self.__init__() return False def run_logic(self): # 小球移动 self.ball.move() # 挡板跟随鼠标移动 self.board.move() # 如果球已经进入到board厚度以下就要开始检测是否碰撞 if self.ball.pos[1]>=self.board.pos[1]-self.ball.size: # 如果球与board接触,就反弹 if self.board.pos[0]<=self.ball.pos[0]<=self.board.pos[0]+self.board.pos[2]: self.ball.speed[1]=-self.ball.speed[1] self.score+=1 else: # 没有碰到挡板就将游戏状态设置为结束 self.gameover=True
def display_frame(self,screen,font,gameover_font): screen.fill(bg) if self.gameover: # 设置结束文本 gameover_text = gameover_font.render("Game Over", True, (255,0,0)) gamecontinue_text =font.render("Enter space to continue",True,(0,255,0)) # 获取结束surface 大小 surface_x,surface_y=gameover_text.get_size() continue_x,continue_y=gamecontinue_text.get_size() # 显示结束文本 screen.blit(gameover_text,((width-surface_x)//2,(height-surface_y)//2)) screen.blit(gamecontinue_text,((width-continue_x)//2,(height-continue_y)//2+40)) pygame.display.flip() else: pygame.draw.circle(screen,self.ball.color,self.ball.pos,self.ball.size) pygame.draw.rect(screen,self.board.color,self.board.pos) # 显示计分 text = font.render("score:"+str(self.score), True, (255,0,0)) screen.blit(text,(10,10)) pygame.display.flip()
def main(): pygame.init() screen = pygame.display.set_mode(size) pygame.display.set_caption("弹球游戏") game=Game() # 字体设置 font=pygame.font.SysFont("Arial",24) # 游戏结束字体设置 gameover_font=pygame.font.SysFont("Arial",48)
# 设置关闭游戏变量 close=False # 当游戏没有结束就一直循环 while not close: close=game.process_event() game.run_logic() game.display_frame(screen,font,gameover_font) pygame.time.wait(100) pygame.quit()
main()
效果:
这里要实现游戏结束界面,同时又可以按下空格键重启,点击关闭按钮可以退出游戏。上一个任务是直接在main函数中设置了也给gameover变量,用来判断游戏结束,现在要实现按下可以重启,所以就不能退出游戏循环,所以在这里新设置了一个close变量,只要不关闭就不会退出游戏循环,如果关闭了,process_event就会返回True。
# 设置关闭游戏变量close=False# 当游戏没有关闭就一直循环while not close: close=game.process_event()
那不退出循环,怎样表示游戏结束的状态呢,这里用到一个小技巧,在display_frame中将渲染内容分两部分,游戏结束时gameover=True时只渲染结束的字体和界面,没有结束时就渲染小球,挡板,游戏分数运行界面,从而实现游戏结束的一个效果。
def display_frame(self,screen,font,gameover_font): screen.fill(bg) if self.gameover: # 设置结束文本 gameover_text = gameover_font.render("Game Over", True, (255,0,0)) gamecontinue_text =font.render("Enter space to continue",True,(0,255,0)) # 获取结束surface 大小 surface_x,surface_y=gameover_text.get_size() continue_x,continue_y=gamecontinue_text.get_size() # 显示结束文本 screen.blit(gameover_text,((width-surface_x)//2,(height-surface_y)//2)) screen.blit(gamecontinue_text,((width-continue_x)//2,(height-continue_y)//2+40)) pygame.display.flip() else: pygame.draw.circle(screen,self.ball.color,self.ball.pos,self.ball.size) pygame.draw.rect(screen,self.board.color,self.board.pos) # 显示计分 text = font.render("score:"+str(self.score), True, (255,0,0)) screen.blit(text,(10,10)) pygame.display.flip()
最后如何实现按下空格键重新开始呢,这个比较简单,只需要在事件检测中检查是否有按下空格键,按下的化,并且游戏也是结束状态,设置重新初始化就可以大功告成了。
def process_event(self):for event in pygame.event.get(): if event.type == pygame.QUIT: return True if event.type==pygame.KEYUP: if event.key==pygame.K_SPACE: if self.gameover: self.__init__()return False
到这弹球游戏就结束了,有不理解的可以后面留言或者加我微信探讨。