動(dòng)畫精靈和碰撞檢測(cè)
一、動(dòng)畫精靈
動(dòng)畫精靈:四處移動(dòng)的單個(gè)圖像或圖像部分稱為動(dòng)畫精靈(sprite),pygame有一個(gè)特殊的模塊幫助跟蹤屏幕上移動(dòng)的大量圖像。利用這個(gè)模塊,可以更容易地移動(dòng)圖形對(duì)象。
具備特征:
- 圖像(image):為動(dòng)畫精靈顯示的圖片。
- 矩形區(qū)(rect):包含動(dòng)畫精靈的矩形區(qū)域。
![](/d/20211017/874cbf205985b9e6732ce2f11bd12c15.gif)
![](/d/20211017/446028c11cf407362f693ff3f46e51cb.gif)
①、一堆沙灘球都反彈
Pygame 的 sprite 模塊提供了一個(gè)動(dòng)畫精靈基類Sprite,基于pygame.sprite.Sprite 來(lái)創(chuàng)建自己的子類。
這里用了常規(guī)的python列表
import sys, pygame
class MyBallClass(pygame.sprite.Sprite):
def __init__(self, image_file, location):
pygame.sprite.Sprite.__init__(self) #初始化動(dòng)畫精靈
self.image = pygame.image.load(image_file) #加載圖片
self.rect = self.image.get_rect() #得到定義圖像邊界矩形
self.rect.left, self.rect.top = location #設(shè)置球的初始位置
#設(shè)置窗口大小和顏色
size = width, height = 640, 480
screen = pygame.display.set_mode(size)
screen.fill([255, 255, 255])
img_file = "beach_ball.png"
balls = []
#將球增加到列表
for row in range(0, 3):
for column in range(0, 3):
location = [column * 180 + 10, row * 180 + 10]
ball = MyBallClass(img_file, location)
balls.append(ball)
for ball in balls:
screen.blit(ball.image, ball.rect)
pygame.display.flip()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
顯示效果:
![](/d/20211017/6d792756b5afc8af7c2bce7a2d45b8a5.gif)
②、讓小球動(dòng)起來(lái)
![](/d/20211017/f34a0bb417ca93935ca623c3924e5478.gif)
move() 方法
創(chuàng)建一個(gè)新的類方法
def move(self):
self.rect = self.rect.move(self.speed)
#碰到窗口左右兩邊
if self.rect.left 0 or self.rect.right > width:
self.speed[0] = -self.speed[0]
#碰到窗口上下兩邊
if self.rect.top 0 or self.rect.bottom > height:
self.speed[1] = -self.speed[1]
###
其中 self.speed告訴對(duì)象要移動(dòng)多遠(yuǎn),包含2個(gè)列表。
import sys, pygame
from random import *
class MyBallClass(pygame.sprite.Sprite):
def __init__(self, image_file, location, speed):
pygame.sprite.Sprite.__init__(self) #初始化動(dòng)畫精靈
self.image = pygame.image.load(image_file) #加載圖片
self.rect = self.image.get_rect() #得到定義圖像邊界矩形
self.rect.left, self.rect.top = location #設(shè)置球的初始位置
self.speed = speed #創(chuàng)建一個(gè)速度
def move(self):
self.rect = self.rect.move(self.speed)
if self.rect.left 0 or self.rect.right > width:
self.speed[0] = -self.speed[0]
if self.rect.top 0 or self.rect.bottom > height:
self.speed[1] = -self.speed[1]
#設(shè)置窗口大小和顏色
size = width, height = 640, 480
screen = pygame.display.set_mode(size)
screen.fill([255, 255, 255])
img_file = "beach_ball.png"
balls = []
#將球增加到列表
for row in range(0, 3):
for column in range(0, 3):
location = [column * 180 + 10, row * 180 + 10]
speed = [choice([-2, 2]), choice([-2, 2])] #讓每個(gè)球變得隨機(jī)性
ball = MyBallClass(img_file, location, speed)
print("y = ", ball.rect)
balls.append(ball)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
#這里不是單獨(dú)擦除(覆蓋各個(gè)球),直接用白色填充窗口,重新繪制
pygame.time.delay(20)
screen.fill([255, 255, 255])
for ball in balls:
ball.move()
screen.blit(ball.image, ball.rect)
pygame.display.flip()
pygame.quit()
二、碰撞檢測(cè)
上面的動(dòng)畫,僅僅只是移動(dòng)和反彈,球與球之間的碰撞還不能反彈.
![](/d/20211017/9b6657621f7d14f48f53a445b5c8f7a7.gif)
Pygame 中已經(jīng)內(nèi)置有這種碰撞檢測(cè)。
Pygame 還提供了一種方法對(duì)動(dòng)畫精靈分組。 Pygame 的 group 類。例如,在保齡球游戲中,所有球瓶可能在一組,球則在另一組。
動(dòng)畫精靈模塊的spritecollide()函數(shù)用來(lái)檢測(cè)某個(gè)精靈是否與制定組的其他精靈發(fā)生碰撞,
這個(gè)函數(shù)的形式如下:
spritecollide(被檢測(cè)的精靈(sprite),指定組(group),是否重疊(False))
要檢查組中精靈之間的碰撞:
- 從這個(gè)組中刪除這個(gè)精靈;
- 檢查這個(gè)精靈與組中其他精靈之間的碰撞;
- 再把這個(gè)精靈添加回原來(lái)的組中。
問(wèn):為什么要先從組刪除?
答:如果開(kāi)始時(shí)沒(méi)有從組中刪除這個(gè)精靈,spritecollide() 會(huì)檢測(cè)到這個(gè)精靈與它自身發(fā)生了碰撞,因?yàn)樗苍谶@個(gè)組中
import sys, pygame
from random import *
class MyBallClass(pygame.sprite.Sprite):
def __init__(self, image_file, location, speed):
pygame.sprite.Sprite.__init__(self) #初始化動(dòng)畫精靈
self.image = pygame.image.load(image_file) #加載圖片
self.rect = self.image.get_rect() #得到定義圖像邊界矩形
self.rect.left, self.rect.top = location #設(shè)置球的初始位置
self.speed = speed #創(chuàng)建一個(gè)速度
def move(self):
self.rect = self.rect.move(self.speed)
if self.rect.left 0 or self.rect.right > width:
self.speed[0] = -self.speed[0]
if self.rect.top 0 or self.rect.bottom > height:
self.speed[1] = -self.speed[1]
def animate(group):
screen.fill([255, 255, 255])
for ball in group:
group.remove(ball) #從組刪除精靈
#檢查精靈與組的碰撞
if pygame.sprite.spritecollide(ball, group, False):
ball.speed[0] = -ball.speed[0]
ball.speed[1] = -ball.speed[1]
group.add(ball)
ball.move()
screen.blit(ball.image, ball.rect)
pygame.display.flip()
pygame.time.delay(20)
#設(shè)置窗口大小和顏色
size = width, height = 640, 480
screen = pygame.display.set_mode(size)
screen.fill([255, 255, 255])
img_file = "beach_ball.png"
group = pygame.sprite.Group() #創(chuàng)建精靈組
#將球增加到列表
for row in range(0, 2):
for column in range(0, 2):
location = [column * 180 + 10, row * 180 + 10]
speed = [choice([-2, 2]), choice([-2, 2])] #讓每個(gè)球變得隨機(jī)性
ball = MyBallClass(img_file, location, speed)
group.add(ball)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
#這里不是單獨(dú)擦除(覆蓋各個(gè)球),直接用白色填充窗口,重新繪制
pygame.time.delay(20)
screen.fill([255, 255, 255])
animate(group)
pygame.quit()
上述代碼存在問(wèn)題:
- 球碰撞時(shí),它們會(huì)“顫抖”或者發(fā)生兩次 碰撞;
- 有時(shí)球會(huì)“卡”在窗口邊界上,顫抖一段時(shí)間。
可能原因:上述代碼是先移動(dòng)一個(gè)球,檢查碰撞,然后移動(dòng)球在檢查下一個(gè)。這樣子可能造成球的滯后性
修改animate函數(shù)()
def animate(group):
screen.fill([255, 255, 255])
for ball in group:
ball.move()
for ball in group:
group.remove(ball) #從組刪除精靈
#檢查精靈與組的碰撞
if pygame.sprite.spritecollide(ball, group, False):
ball.speed[0] = -ball.speed[0]
ball.speed[1] = -ball.speed[1]
group.add(ball)
#ball.move()
screen.blit(ball.image, ball.rect)
pygame.display.flip()
pygame.time.delay(20)
矩形碰撞與像素完美結(jié)合
在觀察可以方向,球“碰撞”時(shí)并不是完全接觸。因?yàn)?spritecollide() 沒(méi)有使用球的圓形輪廓來(lái)檢測(cè)碰撞。它使用了球的 rect,也就是球的外圍矩形。
如果希望球的圓形部分(而不是矩形邊界)真正接觸時(shí)球才會(huì)相互反彈,就必須使用一種稱為“像素完美碰撞檢測(cè) ” 的 方 法。
![](/d/20211017/ecc1336761ec73bd202a393cd82ae4cc.gif)
![](/d/20211017/3e94aeb8e2d56fd69508a3a84d4f1698.gif)
三、統(tǒng)計(jì)時(shí)間
在之前我們都用time.delay(50)來(lái)控制動(dòng)畫運(yùn)行的快慢,
![](/d/20211017/9fc9f1f8b727600157ee7a83c7ef5851.gif)
在計(jì)算機(jī)圖形學(xué)中,每個(gè)動(dòng)畫步叫做一幀,游戲程序員討論圖形更新的快慢時(shí)都會(huì)提到幀速率(每秒幀數(shù),fps)
問(wèn):要怎么控制未知代碼運(yùn)行時(shí)間?
——Pygame 的 time 模塊提供了這樣的工具:一個(gè)名為 Clock 的類。
用 pygame.time.Clock() 控制幀速率
并不是向每個(gè)循環(huán)增加一個(gè)延遲, pygame.time.Clock() 會(huì)控制每個(gè)循環(huán)多長(zhǎng)時(shí)間運(yùn)行一次。
clock = pygame.time.Clock()
clock.tick(60)
#此處的60指示:這個(gè)循環(huán)應(yīng)當(dāng)每秒運(yùn)行 60 次。
每秒 60 個(gè)循環(huán)(或幀)時(shí),每個(gè)循環(huán)需要 1000 / 60 = 16.66 ms(大約 17 ms)。
如果循環(huán)中的代碼運(yùn)行時(shí)間超過(guò) 17 ms,在 clock 指出開(kāi)始下一次循環(huán)時(shí)當(dāng)前循環(huán)將無(wú)法完成。
檢查幀速率
clock.get_fps() 知道程序能以多快的速度運(yùn)行,檢查幀速率
調(diào)整幀速率
假設(shè)設(shè)置運(yùn)行 clock.tick(30),每秒39幀,但 clock.get_fps()檢測(cè)實(shí)際得到的速率為20fps,說(shuō)明每個(gè)循環(huán)運(yùn)行的時(shí)間比預(yù)計(jì)的長(zhǎng),這樣達(dá)不到原來(lái)要的效果,需要調(diào)整幀速率。
以小球?yàn)槔?,原?lái)每秒30幀,可以將小球移動(dòng)比較遠(yuǎn),但是目前的代碼每秒只能運(yùn)行20幀,達(dá)不到預(yù)期的要求,需要在限有的時(shí)間內(nèi)到達(dá)移動(dòng)的距離,就需要更改小球移動(dòng)的速度。可以按期望幀頻率與實(shí)際幀速率的比值來(lái)增加
如果小球?qū)ο缶嚯x是10,期望的幀速率是30fps,程序?qū)嶋H運(yùn)行速率為20fps.
object_speed = current_speed * (desired fps / actual fps)
object_speed = 10 * (30 / 20)
object_speed = 15
沙灘球程序中使用 Clock 和 get_fps()
import sys, pygame
from random import *
class MyBallClass(pygame.sprite.Sprite):
def __init__(self, image_file, location, speed):
pygame.sprite.Sprite.__init__(self) #初始化動(dòng)畫精靈
self.image = pygame.image.load(image_file) #加載圖片
self.rect = self.image.get_rect() #得到定義圖像邊界矩形
self.rect.left, self.rect.top = location #設(shè)置球的初始位置
self.speed = speed #創(chuàng)建一個(gè)速度
def move(self):
self.rect = self.rect.move(self.speed)
if self.rect.left 0 or self.rect.right > width:
self.speed[0] = -self.speed[0]
if self.rect.top 0 or self.rect.bottom > height:
self.speed[1] = -self.speed[1]
def animate(group):
screen.fill([255, 255, 255])
for ball in group:
ball.move()
for ball in group:
group.remove(ball) #從組刪除精靈
#檢查精靈與組的碰撞
if pygame.sprite.spritecollide(ball, group, False):
ball.speed[0] = -ball.speed[0]
ball.speed[1] = -ball.speed[1]
group.add(ball)
screen.blit(ball.image, ball.rect)
pygame.display.flip()
pygame.time.delay(20)
#設(shè)置窗口大小和顏色
size = width, height = 640, 480
screen = pygame.display.set_mode(size)
screen.fill([255, 255, 255])
img_file = "beach_ball.png"
clock = pygame.time.Clock()
group = pygame.sprite.Group() #創(chuàng)建精靈組
#將球增加到列表
for row in range(0, 2):
for column in range(0, 2):
location = [column * 180 + 10, row * 180 + 10]
speed = [choice([-4, 4]), choice([-4, 4])] #讓每個(gè)球變得隨機(jī)性
ball = MyBallClass(img_file, location, speed)
group.add(ball)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
frame_rate = clock.get_fps()
print( "frame rate = ", frame_rate)
animate(group)
clock.tick(30)
pygame.quit()
到此這篇關(guān)于利用pygame完成動(dòng)畫精靈和碰撞檢測(cè)的文章就介紹到這了,希望對(duì)大家有幫助,更多相關(guān)pygame內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持腳本之家!
您可能感興趣的文章:- pygame學(xué)習(xí)筆記(5):游戲精靈
- pygame 精靈的行走及二段跳的實(shí)現(xiàn)方法(必看篇)
- python使用pygame創(chuàng)建精靈Sprite
- python飛機(jī)大戰(zhàn)pygame碰撞檢測(cè)實(shí)現(xiàn)方法分析
- Python Pygame中精靈和碰撞檢測(cè)詳解