网上有很多篮球类游戏,这里应用 pygame 编写了一个。游戏中有一个投篮手,一个防守者。投篮手运球避开防守,跳起投篮,投中得一分。投篮手离篮筐越近,投篮准确率越高,但离篮筐越近,越可能碰到防守者,如碰到,游戏完结。下边是游戏的效果图。
游戏背景是篮球场,如上图。有 3 个角色,投篮手、防守者和篮球各 1 个,别离用类来定义。角色动画造型采纳火柴人,这是因为火柴人造型容易找到,即便本人画一个也不难。惋惜自己画图能力太差,只能从一段视频中一帧一帧抠出造型。所有图形背景都要设置为通明。3 个角色所有图形如下:
投篮手运球有 4 个彩色造型,帧号 0 -3,当按空格键将启动跳投,将帧号批改为 4,跳投有 12 个彩色造型,帧号 4 -15,所有造型用来实现投篮手运球和跳投动画。在前 1 篇博文“篮球游戏中的运球_用列表保留每帧图片”中,能够看到在每秒 4 帧图形状况下,4 个造型就能够实现运球动画,留神球也是造型的一部分。但在每秒 4 帧图形状况下,跳投动画仅用 4 帧图形是不可能的,试了一下,每秒 4 帧,感觉要用 12 帧图形,即跳投从起跳到再落下需用 12*\0.25 秒 = 3 秒工夫,才有称心的动画成果。跳投的 12 个图形有有点对付事,但还是能看到动画成果的,如把 12 个图形认真画一下,可能成果更好。留神,跳投的第 4、5 和 6 帧中图形是有篮球的,后边帧无篮球,在适当时候 (本例在第 8 帧),篮球角色将呈现在投篮者上方,以此为终点向篮板静止,在第 13 帧碰到篮板,第 14 和 15 帧球向下静止,如投中,间接掉到蓝中,如未投中,从侧方向下掉。篮球只有一个图形。
首先要解决的问题是那种状况投篮能中,那种状况投篮不中。网上投篮游戏投中规定形形色色。本游戏规则是:间隔篮筐越远投篮越不准,在某一点投篮,那次投中,那次投不中无规律,或者说是随机的,但投中概率是定值。设从投篮点到篮筐间隔为 L,令 n =(L//100)取整数,应用随机数发生器产生 1 到 n + 1 之间随机整数。规定随机数为 1,投中,其它随机整数投不中。如点到篮筐架间隔 Y <100,n=0,投中率为 100%;如 200>L>99,n=1,投中率为 50%;如 300>L>199,n=2,投中率为 33%,等等。
防守者只有 2 个黄色造型,仿佛少了点,在本例勉强够用。当防守者和投篮手间隔大于 200,防守者退到防守初始地位。小于 200,而且投篮手正在运球,防守者迫近防守,即防守者面向投篮手挪动若干间隔 (应为整数), 如碰到投篮手,游戏完结。所谓面向,即防守者沿防守者和投篮手连线挪动。这样,这个连线 L,以及防守者和投篮手两点沿 x、y 轴的差值 dx 和 dy 组成一个直角三角形。如防守者每帧沿 x 轴挪动 dx1,沿 y 轴挪动 dy1,只有 dx1/dy1=dx/dy,就保障防守者面向投篮手挪动。可令 dy1=7,则 dx1=7dx/dy。但 dy 为 0,要产生被 0 除谬误。为防止此种状况,可先判断 dx 和 dy 大小,如 dx<dy,采纳上式,否则,dx1=7,dy1=7dy/dx。留神 dx 和 dy 是不可能同时为 0 的,因同时为 0 前,投篮手和防守者就会靠近,产生碰撞,导致游戏完结。这样做,不同方向挪动的间隔可能不同,上例中,最大值为两直角边都为 7,间隔为 9.899,只能取整数为:9,如较小直角边为 0,则间隔为 7。另外,沿 x、y 轴增量都只能是整数,因而方向也有误差。
以下是游戏全副程序,做了具体注解,应该较容易读懂程序。程度无限,未免有考虑不周之处,欢送批评指正。仅拷贝源程序不能正确运行,须要制作投篮手和防守者全副造型,球造型,背景篮球场,放到源程序所在文件夹中。将把源程序和所有图像打包上传,有须要的读者可下载。玩网络游戏时,因为投篮者随鼠标挪动,在关上程序或重玩游戏时,必须把鼠标移到程序窗口外边界,防止程序一开始就和防守者产生碰撞,导致游戏完结。
import pygame
import math
import random
import os
class Ball(): #篮球类
def __init__(self,screen): #screen 是游戏主窗体,Surface 类实例
self.screen=screen
b=pygame.image.load('b.png').convert_alpha() #失去篮球图形
r=b.get_rect()
self.p=pygame.transform.scale(b,(r.width//2,r.height//2)) #放大图形
self.x,self.y,self.xi,self.yi=0,0,0,0#(x,y)篮球坐标,(xi,yi)是篮球两个地位间增量
self.frameNum=9 #篮球帧编号(1-8),=9, 篮球不可见
self.mark=0 #此次投篮中否,= 0 不中,= 1 中
self.score=0 #投篮投中次数(得分)
def draw(self): #主程序调用,实现篮球动画
if self.frameNum==9: #篮球帧编号 =9, 篮球不可见
return
if self.frameNum==1: #第 1 帧计算必要数据, 下句坐标 (self.x,self.y) 是球运行终点
dx,dy=(400-self.x),(40-self.y) #坐标 (400,530) 点是球碰到篮板上的点
self.xi=dx//6 #篮球从起始点到篮板每帧沿 x 轴后退的增量
self.yi=dy//6 #篮球从起始点到篮板每帧沿 y 轴后退的增量
dist=math.sqrt((dx**2)+(dy**2)) #投篮点间隔篮板间隔
n=int(dist//100) #除数越小,总投中率越低
if random.randint(1,n+1)==1: #随机数为 1 投中,n+ 1 防止 dist<100 为 0
self.mark=1 #投中标记为 1
else:
self.mark=0 #投不中为 0
if self.frameNum>=1 and self.frameNum<6: #从第 1 帧到第 5 帧, 球按此法后退
self.x+=self.xi #篮球每帧沿 x 轴减少 1 个增量值
self.y+=self.yi #篮球每帧沿 y 轴减少 1 个增量值
self.frameNum+=1
elif self.frameNum==6: #此帧球将碰到篮板,要精确管制碰到篮板的落点
self.x=400 #球碰到篮板的 x 坐标
self.frameNum+=1
if self.mark==1: #投中,篮球落点 y 轴方向凑近篮筐
self.y=90
else: #投不中,篮球落点 y 轴方向离篮筐较远
self.y=70
else: #篮球着落的两个点,即第 7,8 帧
if self.mark==0: #球未投中, 球除着落, 还沿 x 轴方向挪动, 球从篮筐两侧落下
if self.xi>=0: #如球从左到右, 最初两帧, 球沿 x 轴方向持续从左向右挪动
self.x+=30
else:
self.x-=30 #否则最初两帧,球沿 x 轴方向持续从右向左挪动
self.y+=25 #如投中 x 坐标不变,即球间接着落穿过篮筐
self.frameNum+=1
self.screen.blit(self.p, (self.x, self.y)) #在屏幕指定地位绘制篮球
if self.frameNum==9 and self.mark==1: #球所有动作实现,判断得分是否加 1
self.score+=1
class Guard(): #防守者类
def __init__(self,screen): ##screen 是游戏主窗体,Surface 类实例
self.screen=screen
self.images=[]
for n in range(2): #将 2 帧图像保留到列表中
p = pygame.image.load(str(n+16)+'.png').convert_alpha()# 文件名为 16.png,17.png
r=p.get_rect()
p = pygame.transform.scale(p, (r.width//6, r.height//6)) #调整图像的大小
self.images.append(p)
self.frameNum=0 #帧编号,0-1
self.x,self.y=400,300 #防守运动员在窗体的初始坐标
self.PlayerX,self.PlayerY=0,0 #此时投篮手坐标
self.PlayerFrameNum=0 #此时投篮手帧号
self.rect=None# 调用 blit 绘制图形, 返回 rect 记录图形在 screen 坐标和图形宽和高, 用来检测碰撞
def draw(self): #主程序调用,实现防守者动画
p=self.images[self.frameNum] #取出以后帧图形
if self.PlayerX-self.x<0: #面向投篮手
p=pygame.transform.flip(p,True,False)
dx,dy=self.PlayerX-self.x,self.PlayerY-self.y #防守者和投篮手两点沿 x、y 轴的差值 dx 和 dy
dist=math.sqrt((dx**2)+(dy**2)) #计算投篮手和防守者间隔
dx1,dy1=0,0 #防守者每帧沿 x 轴挪动 dx1,沿 y 轴挪动 dy1
if dist>200: #如距投篮者 >200, 返回初始点
self.x,self.y=400,300
elif self.PlayerFrameNum<4: #如投篮者未投篮,迫近投篮者,如投篮者投篮,防守者地位不变
if abs(dx)<abs(dy): #保障 abs(dy)不为 0,使下句 dx/dy 肯定不会被 0 除
d=abs(dx/dy)
dy1=7 #如矩形长边为 7,dx1=int((dy1*d)//1) #dx1 可能是:0,1,2,3,4,5,6,7
else: #保障 abs(dx)不为 0,使下句 dy/dx 肯定不会被 0 除
d=abs(dy/dx)
dx1=7 #如矩形长边为 7,dy1=int((dx1*d)//1) #dy1 可能是:0,1,2,3,4,5,6,7
if dx<0: #失去 dx 的正负号
dx1=-dx1
if dy<0: #失去 dy 的正负号
dy1=-dy1
self.x+=dx1 #防守者挪动
self.y+=dy1# 下句返回 rect 用来检测碰撞, 其属性 x,y 是图形在游戏窗口坐标,width,hight 是图形宽和高
self.rect=self.screen.blit(p,(self.x,self.y)) #在屏幕指定地位绘制防守者
self.frameNum+=1
if self.frameNum==2:
self.frameNum=0
class Player(): #投篮手类
def __init__(self,screen): #screen 是游戏主窗体,Surface 类实例
self.screen=screen
self.images=[]
for n in range(16): #将 16 帧图像 (包含运球和跳投图像) 保留到列表中
p = pygame.image.load(str(n)+'.png').convert_alpha()# 文件名为 1.png,2.png...
r=p.get_rect()
p = pygame.transform.scale(p, (r.width//6, r.height//6)) #调整图像的大小
self.images.append(p)
self.frameNum=0 #帧编号,运球为 0 到 3,跳投为 4 到 15
self.x,self.y=0,0 #图像在窗体的坐标
self.mouseX,self.mouseY=0,0 #此时鼠标坐标值
self.jumpUpOrDown=-10 #按空格键后投篮手向上跳,初始值为正数。到最高点后着落,为负数
self.rect=None# 调用 blit 绘制图形, 返回 rect 记录图形在 screen 坐标和图形宽和高, 用来检测碰撞
def dribble(self): #运球动画
p=self.images[self.frameNum]
if self.mouseX-self.x<0: #面向鼠标
p=pygame.transform.flip(p,True,False)
self.x,self.y=self.mouseX,self.mouseY #投篮手坐标 = 鼠标坐标
if self.x<1: #管制投篮手必须在篮球场中
self.x=1
if self.x+90>width:
self.x=width-90
if self.y<230:
self.y=230
if self.y+120>height:
self.y=height-120
self.rect=self.screen.blit(p,(self.x,self.y)) #在指定地位绘制图形, 返回 rect
self.frameNum+=1
if self.frameNum==4:
self.frameNum=0
def jumpShot(self): #跳投动画
p=self.images[self.frameNum]
if self.x>width/2: #面向篮板
p=pygame.transform.flip(p,True,False)
self.screen.blit(p, (self.x, self.y)) #跳投初始地位是运球转跳投时地位
self.y+=self.jumpUpOrDown #当前先向上(y 值缩小),到最高点后降落
self.frameNum+=1
if self.frameNum==9: #开始着落,着落值为正
self.jumpUpOrDown=10
if self.frameNum==16: #=16,跳起投篮完结,转运球
self.frameNum=0
self.jumpUpOrDown=-10
pygame.init()
os.environ['SDL_VIDEO_WINDOW_POS']="%d,%d"%(200,40) #游戏窗口距左侧和顶部点数为 200,40
size = width, height = 800,600 #创立游戏窗口大小
screen = pygame.display.set_mode(size)
pygame.display.set_caption("投手运球和跳投") #设置窗口题目
bg_img = pygame.image.load("篮球场 1.png").convert() #背景篮球场图像
fclock = pygame.time.Clock() #创立管制频率的 clock
fps = 4 #定义刷新频率
player=Player(screen) #投篮手类实例
ball=Ball(screen) #篮球类实例
guard=Guard(screen) #防守者类实例
font1 = pygame.font.SysFont('宋体', 50, True) #创立字体
gameOver=False #该次游戏是否完结,初始不完结
running = True #程序是否完结,初始运行
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT: #解决退出事件
running = False #程序完结
if event.type == pygame.MOUSEMOTION: #鼠标挪动事件
player.mouseX,player.mouseY=event.pos #将鼠标地位传递给投篮手用于运球
if event.type == pygame.KEYUP: #按键后抬起事件,防止长按键不抬起
if event.key == pygame.K_SPACE: #按空格键后抬起
if player.frameNum<4: #如在运球状态,转投篮状态
player.frameNum=4 #已在投篮状态不解决
if event.key == pygame.K_r and gameOver==True: #按 r 键后抬起,重玩游戏
gameOver=False
ball.score=0
screen.blit(bg_img, (0, 0)) #绘制篮球场背景
surface1=font1.render('score:'+str(ball.score),True,[255,0,0]) #不能显示中文
screen.blit(surface1, (20, 20)) #显示进球数(得分)
if gameOver==True: #如果该次游戏完结,后边程序不再执行
fclock.tick(fps) #fps 是每秒多少帧, 减去程序运行工夫,为实现 fps, 还需延迟时间
continue
if player.frameNum>=4: #如果投篮手帧号 >=4, 投篮手正在跳投
player.jumpShot()
if player.frameNum==8: #第 8 帧跳起手中无球,篮球要呈现并开始向篮板静止
ball.frameNum=1 #球向篮板静止第 1 帧
ball.x=player.x #球向篮板静止的起始地位
ball.y=player.y
else: #如果投篮手帧号 <4, 投篮手正在运球
player.dribble()
ball.draw() #篮球动画
guard.PlayerX,guard.PlayerY=player.x,player.y #将投篮手地位传递给防守者
guard.PlayerFrameNum=player.frameNum #将投篮手帧号传递给防守者
guard.draw() #防守运动员动画
if player.frameNum<4: #仅在投篮手运球时,判断和防守者是否产生碰撞
if player.rect.colliderect(guard.rect): #检测投篮者和防守者是否产生碰撞
gameOver=True #产生碰撞,游戏完结
surface2=font1.render('if play again,press key r',True,[255,0,0])
screen.blit(surface2, (20, 100)) #显示如持续玩,按 r 键
pygame.display.flip() #刷新游戏场景
fclock.tick(fps) #fps 是每秒多少帧, 减去程序运行工夫,为实现 fps, 还需延迟时间
pygame.quit()