網上有很多籃球類游戲,這里使用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()
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/287817.html
標籤:其他
上一篇:技術人玩小游戲,如何“不戰而勝”
