乐趣区

关于pygame:向量使用1pygame编写篮球游戏火柴人运球避开防守跳起投篮向量法处理防守者逼近投篮者前进数据

编写“火柴人运球避开防守跳起投篮”游戏时,未零碎学习 pygame,边看边编程,并不知道 pygame 中有向量。游戏中投篮者凑近篮板投篮,防守者迫近防守,向投篮者方向挪动若干间隔。原计算后退间隔的代码没应用向量,因而逻辑关系较简单,精度差,还破费较多工夫。后发现 pygame 中有向量,改为向量法实现,可简化编程,进步了精度。程序中篮球从投篮处向篮板后退代码,也可应用向量,但改后和原代码没有劣势,因而未改。向量是解决这类问题罕用的办法,是编写游戏必须把握的常识。

文章中特别强调:对于向量的一个要点是,它们仅示意绝对方向和大小,一个向量的地位是没有意义的。这是指数学意义上的向量。但实际上,在程序中解决的向量是在游戏坐标系中的有向线段,地位还是必要的。能够这样了解,有向线段是指定了地位的向量,因而,向量的许多概念和办法也实用于有向线段。另外,请认真学习向量差的物理意义。
Guard 类的办法 draw(self)中被改写代码如下。请留神是投篮者向量减防守者向量,从而产生从防守者到投篮者的向量,防守者从本人所在位置登程沿向量方向迫近投篮者。这里防守者处是静止的出发点,投篮者处是目的地,为方便使用请记住:出发点到目标点向量,是目的地减出发点。很多游戏要用到这些概念,例如,投篮、踢球和射击等。

vGuard=pygame.math.Vector2(self.x,self.y)               #防守者向量, 参数是防守者坐标
vPlayer=pygame.math.Vector2(self.PlayerX,self.PlayerY)  #投篮者向量, 参数是投篮者坐标
vG_P=vPlayer-vGuard         #留神投篮者向量减防守者向量,产生从防守者到投篮者的向量
dist=vG_P.length()                             #防守者到投篮者间隔
#vG_P=vG_P.normalize()                         #可先变单位向量
#vG_P=vG_P*5                                   #向量长度变为 5
vG_P.scale_to_length(5) #也可间接将向量长度变为 5,这是防守者向投篮者方向 1 帧后退间隔
if dist>200:                                    #如距投篮者 >200, 返回初始点
    self.x,self.y=400,300
elif self.PlayerFrameNum<4:                     #如投篮者未投篮,迫近投篮者
    self.x+=int(vG_P.x)                         #防守者挪动        
    self.y+=int(vG_P.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)
        vGuard=pygame.math.Vector2(self.x,self.y)               #防守者向量, 参数是防守者坐标
        vPlayer=pygame.math.Vector2(self.PlayerX,self.PlayerY)  #投篮者向量, 参数是投篮者坐标
        vG_P=vPlayer-vGuard         #留神投篮者向量减防守者向量,产生从防守者到投篮者的向量
        dist=vG_P.length()                             #防守者到投篮者间隔
        #vG_P=vG_P.normalize()                         #可先变单位向量
        #vG_P=vG_P*5                                   #向量长度变为 5
        vG_P.scale_to_length(5) #也可间接将向量长度变为 5,这是防守者向投篮者方向 1 帧后退间隔
        if dist>200:                                    #如距投篮者 >200, 返回初始点
            self.x,self.y=400,300
        elif self.PlayerFrameNum<4: #如投篮者未投篮,迫近投篮者,如投篮者投篮,防守者地位不变
            self.x+=int(vG_P.x)                         #防守者挪动        
            self.y+=int(vG_P.y)
        #下句返回 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()
退出移动版