天天看點

【python遊戲程式設計之旅】第七篇---pygame中的沖突檢測技術

本系列部落格介紹以python+pygame庫進行小遊戲的開發。有寫的不對之處還望各位海涵。

上一個部落格我們一起學習了pygame中的Sprite子產品和如何加載動畫:http://www.cnblogs.com/msxh/p/5013555.html

這次我們來一起學習pygame中的沖突檢測技術。

pygame支援非常多的沖突檢測技術,我們來一一的看一下他們是如何使用的:

一、精靈與精靈之間的沖突檢測

1.兩個精靈之間的矩形檢測

在隻有兩個精靈的時候我們可以使用pygame.sprite.collide_rect()函數來進行一對一的沖突檢測。這個函數需要傳遞2個參數,并且每個參數都是需要繼承自pygame.sprite.Sprite。

舉個例子:

spirte_1 = MySprite("sprite_1.png",200,200,1)
sprite_2 = MySprite("sprite_2.png",50,50,1)
result = pygame.sprite.collide_rect(sprite_1,sprite_2)
if result:
    print "Collision occurred"      

MySprite使我們上個部落格中建立的類,他繼承自sprite。

Hint:這個函數還有一個非常有用的變體:pygame.sprite.collide_rect_ratio()。這個函數需要一個額外的浮點類型的參數。這個參數用來指定檢測矩形的百分比。

有的時候我們希望沖突檢測更精準一些的話,就可以收縮檢測的區域,讓矩形更小一些,就是通過這個參數控制的。使用方法如下:

result = pygame.sprite.collide_rect_ratio( 0.5 )(sprite_1,sprite_2)      

2.兩個精靈之間的圓檢測

矩形沖突檢測并不适用于所有形狀的精靈,是以pygame中還有個圓形沖突檢測。pygame.sprite.collide_circle(),這個函數是基于每個精靈的半徑值來進行檢測的。

你可以自己指定半徑,或者讓函數自己計算半徑。

result = pygame.sprite.collide_circle(sprite_1,sprite_2)
if result:
    print "Collision occurred"      

這個函數也有一個變體:pygame.sprite.collide_circle_ratio()。函數的功能和用法和上面的pygame.sprite.collide_rect_ratio()是類似的。

3.兩個精靈之間的像素遮罩檢測

如果矩形檢測和圓形檢測都不能滿足我們的需求怎麼辦?别擔心,pygame還為我們提供了一個更加精确的檢測:pygame.sprite.collide_mask()。

這個函數接收兩個精靈作為參數,傳回值是一個bool變量。

if pygame.sprite.collide_mask(sprite_1,sprite_2):
    print ("Collision occurred")      

4.精靈群組之間的矩形沖突檢測

pygame.sprite.spritecollide(sprite,sprite_group,bool)。調用這個函數的時候,一個組中的所有精靈都會逐個地對另外一個單個精靈進行沖突檢測,發生沖突的精靈會作為一個清單傳回。

這個函數的第一個參數就是單個精靈,第二個參數是精靈組,第三個參數是一個bool值,最後這個參數起了很大的作用。當為True的時候,會删除組中所有沖突的精靈,False的時候不會删除沖突的精靈

list_collide = pygame.sprite.spritecollide(sprite,sprite_group,False);      

另外這個函數也有一個變體:pygame.sprite.spritecollideany()。這個函數在判斷精靈組和單個精靈沖突的時候,會傳回一個bool值。

5.精靈組之間的矩形沖突檢測

pygame.sprite.groupcollide()。利用這個函數可以檢測兩個組之間的沖突,他傳回一個字典。(鍵-值對)

好了大概常用的幾種沖突檢測函數我們已經了解完了,下面我們做一個小小的執行個體實際運用一下上面學到的知識。

二、沖突檢測執行個體---吃蘋果小遊戲

先看一下效果圖:

遊戲開始會在螢幕上随機生成一些蘋果,玩家通過上下左右方向鍵來控制人物去吃蘋果。

吃到一個蘋果,能量條就會增長一些,直到吃完所有的蘋果,遊戲結束。

【python遊戲程式設計之旅】第七篇---pygame中的沖突檢測技術
【python遊戲程式設計之旅】第七篇---pygame中的沖突檢測技術

【源代碼+素材下載下傳位址】

網盤下載下傳位址:http://yunpan.cn/c3SttjmY2yYPk  通路密碼 4a7b

github位址:https://github.com/XINCGer/Eat-apple-Game

1.子產品化程式設計

這個遊戲會使用到我們上個部落格建立的MySprite類,為了讓這個類變的更具有可重用性,我們将它做成一個子產品。

隻要将類的實作代碼放進一個單獨的py,然後在使用的時候引入他就可以了。比如我們将這個單獨的py取名為:MyLibrary.py

import MyLibrary      

這樣在使用這個子產品裡面的函數和類的時候我們隻需要這樣做:MyLibrary.fun()。但是這樣看起來也不是很友善的說,是以我們使用import的變體:

from MyLibrary import *
#将檔案中的所有内容引入      

2.進階行走動畫

通過效果圖,我們可以看到程式裡面用到了進階的行走動畫,人物一共有上下左右四個方向的行走動畫。

實際上這個精靈序列圖裡面一共有8個方向的行走動畫,為了簡便,我們隻是使用了其中的四方向,如圖:

【python遊戲程式設計之旅】第七篇---pygame中的沖突檢測技術

通過行的數目就可以來友善的區分,動畫是向左走還是向右走的。現在說起來可能有點比較難以了解,看完下面的代碼就比較好了解了。我們還為Mysprite這個類增加了一個velocity屬性,以便精靈可以根據其方向來移動。

class MySprite(pygame.sprite.Sprite):
    
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.master_image = None
        self.frame = 0
        self.old_frame = -1
        self.frame_width = 1
        self.frame_height = 1
        self.first_frame = 0
        self.last_frame = 0
        self.columns = 1
        self.last_time = 0
        self.direction = 0
        #新增了velocity屬性,他是一個point
        self.velocity = Point(0.0,0.0)           

當按UP鍵的時候,将方向設定為0(向上),按DOWN鍵的時候,将方向設定為4(向下),按LEFT鍵,将方向設定為6(向左),按RIGHT鍵,将方向設定為2(向右)

if keys[K_ESCAPE]: sys.exit()
    elif keys[K_UP] or keys[K_w]:
        player.direction = 0
        player_moving = True
    elif keys[K_RIGHT] or keys[K_d]:
        player.direction = 2
        player_moving = True
    elif keys[K_DOWN] or keys[K_s]:
        player.direction = 4
        player_moving = True
    elif keys[K_LEFT] or keys[K_a]:
        player.direction = 6
        player_moving = True      

這個方向就是我們之前說的用來決定使用動畫幀的範圍方法。并且還有一個player_moving變量,在按鍵按下的時候将它置為True,也就是按鍵按下的時候才會有行走動畫,否則人物将會是靜止的。

3.判斷人物與蘋果的沖突

為了獲得更精準的沖突,我們組合使用了不同的沖突函數。

首先用pygame.sprite.spritecollideany來判斷玩家是否與任意的蘋果産生了碰撞,如果産生碰撞,則再使用pygame.sprite.collide_circle_ratio縮小檢測範圍做一次檢測,

看看到底是哪個蘋果和人物産生了沖突,然後将産生碰撞的果實從精靈組中移除(remove)。

#檢測玩家是否與食物沖突,是否吃到果實
        attacker = None
        attacker = pygame.sprite.spritecollideany(player, food_group)
        if attacker != None:
            if pygame.sprite.collide_circle_ratio(0.65)(player,attacker):
                player_health +=2;
                food_group.remove(attacker);      

吃了果實以後,能量值會增加,然後我們通過繪制一個矩形的能量條來反映給使用者。

好了最後上一下全部的源代碼(不包含MyLibrary子產品):

import itertools, sys, time, random, math, pygame
from pygame.locals import *
from MyLibrary import *

def calc_velocity(direction, vel=1.0):
    velocity = Point(0,0)
    if direction == 0: #上
        velocity.y = -vel
    elif direction == 2: #右
        velocity.x = vel
    elif direction == 4: #下
        velocity.y = vel
    elif direction == 6: #左
        velocity.x = -vel
    return velocity

pygame.init()
screen = pygame.display.set_mode((800,600))
pygame.display.set_caption("吃蘋果")
font = pygame.font.Font(None, 36)
timer = pygame.time.Clock()

#建立精靈組
player_group = pygame.sprite.Group()
food_group = pygame.sprite.Group()

#初始化玩家精靈組
player = MySprite()
player.load("farmer walk.png", 96, 96, 8)
player.position = 80, 80
player.direction = 4
player_group.add(player)

#初始化food精靈組

for n in range(1,50):
    food = MySprite();
    food.load("food_low.png", 35, 35, 1)
    food.position = random.randint(0,780),random.randint(0,580)
    food_group.add(food)

game_over = False
player_moving = False
player_health = 0


while True:
    timer.tick(30)
    ticks = pygame.time.get_ticks()

    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
    keys = pygame.key.get_pressed()
    if keys[K_ESCAPE]: sys.exit()
    elif keys[K_UP] or keys[K_w]:
        player.direction = 0
        player_moving = True
    elif keys[K_RIGHT] or keys[K_d]:
        player.direction = 2
        player_moving = True
    elif keys[K_DOWN] or keys[K_s]:
        player.direction = 4
        player_moving = True
    elif keys[K_LEFT] or keys[K_a]:
        player.direction = 6
        player_moving = True
    else:
        player_moving = False


    if not game_over:
        #根據角色的不同方向,使用不同的動畫幀
        player.first_frame = player.direction * player.columns
        player.last_frame = player.first_frame + player.columns-1
        if player.frame < player.first_frame:
            player.frame = player.first_frame

        if not player_moving:
            #當停止按鍵(即人物停止移動的時候),停止更新動畫幀
            player.frame = player.first_frame = player.last_frame
        else: 
            player.velocity = calc_velocity(player.direction, 1.5)
            player.velocity.x *= 1.5
            player.velocity.y *= 1.5

        #更新玩家精靈組
        player_group.update(ticks, 50)

        #移動玩家
        if player_moving:
            player.X += player.velocity.x
            player.Y += player.velocity.y
            if player.X < 0: player.X = 0
            elif player.X > 700: player.X = 700
            if player.Y < 0: player.Y = 0
            elif player.Y > 500: player.Y = 500

        #檢測玩家是否與食物沖突,是否吃到果實
        attacker = None
        attacker = pygame.sprite.spritecollideany(player, food_group)
        if attacker != None:
            if pygame.sprite.collide_circle_ratio(0.65)(player,attacker):
                player_health +=2;
                food_group.remove(attacker);
        if player_health > 100: player_health = 100
        #更新食物精靈組
        food_group.update(ticks, 50)

        if len(food_group) == 0:
            game_over = True
    #清屏
    screen.fill((50,50,100))

    #繪制精靈
    food_group.draw(screen)
    player_group.draw(screen)

    #繪制玩家血量條
    pygame.draw.rect(screen, (50,150,50,180), Rect(300,570,player_health*2,25))
    pygame.draw.rect(screen, (100,200,100,180), Rect(300,570,200,25), 2)

    if game_over:
        print_text(font, 300, 100, "G A M E   O V E R")
    
    pygame.display.update()
          

在下個部落格裡面我們将一起學習在遊戲裡面常用的一些資料結構: 資料,清單,元組,隊列,棧。