本系列博客介紹以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()。利用這個函數可以檢測兩個組之間的沖突,他返回一個字典。(鍵-值對)
好了大概常用的幾種沖突檢測函數我們已經了解完了,下面我們做一個小小的實例實際運用一下上面學到的知識。
二、沖突檢測實例---吃蘋果小游戲
先看一下效果圖:
游戲開始會在屏幕上隨機生成一些蘋果,玩家通過上下左右方向鍵來控制人物去吃蘋果。
吃到一個蘋果,能量條就會增長一些,直到吃完所有的蘋果,游戲結束。


【源代碼+素材下載地址】
網盤下載地址: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個方向的行走動畫,為了簡便,我們只是使用了其中的四方向,如圖:

通過行的數目就可以來方便的區分,動畫是向左走還是向右走的。現在說起來可能有點比較難以理解,看完下面的代碼就比較好理解了。我們還為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()
在下個博客里面我們將一起學習在游戲里面常用的一些數據結構: 數據,列表,元組,隊列,棧。
