本系列部落格介紹以python+pygame庫進行小遊戲的開發。有寫的不對之處還望各位海涵。
前幾期部落格我們一起學習了,pygame中的沖突檢測技術以及一些常用的資料結構。
這次我們來一起做一個簡單的酷跑類遊戲綜合運用以前學到的知識。
程式下載下傳位址:https://pan.baidu.com/s/1Ji2Ubsds6z2brBx8Gz1OOw 提取碼:dff4
源代碼網盤位址:https://pan.baidu.com/s/1T7tlYbTNUPRhtJ45B6PAPw 提取碼:mhip
github位址:https://github.com/XINCGer/catRunFast
效果圖:
現在我們來分析一下制作流程:
遊戲中一共有嗷大喵,惡龍,火焰,爆炸動畫和果實(就是上方藍色的矩形塊)這幾種精靈。這裡我們使用到了前幾期部落格中的MyLibrary.py。上述這幾個精靈都是 MySprite類執行個體化的對象。
為了友善管理。我們建立了幾個精靈組,并且将一些精靈塞到了裡面:
#建立精靈組
group = pygame.sprite.Group()
group_exp = pygame.sprite.Group()
group_fruit = pygame.sprite.Group()
#建立怪物精靈
dragon = MySprite()
dragon.load("dragon.png", 260, 150, 3)
dragon.position = 100, 230
group.add(dragon)
#建立爆炸動畫
explosion = MySprite()
explosion.load("explosion.png",128,128,6)
#建立玩家精靈
player = MySprite()
player.load("sprite.png", 100, 100, 4)
player.position = 400, 270
group.add(player)
#建立子彈精靈
arrow = MySprite()
arrow.load("flame.png", 40, 16, 1)
arrow.position = 800,320
group.add(arrow)
在程式開始的時候我們可以看到有一個歡迎界面,為了簡單我這裡是直接在ps裡面做好了圖檔,然後加載到程式中的:
interface = pygame.image.load("interface.png")
界面上面還有一個按鈕,當滑鼠經過的時候,會變成灰底的,是以我們設計一個button類:
簡單來說就是預先加載一張正常狀态下在的button圖檔和一個按下狀态的button圖檔,然後判斷滑鼠的pos是否和button的位置有重合,如果有則顯示button被按下時的圖檔。
關于button的設計我參考了這位博友的教程:http://www.cnblogs.com/SRL-Southern/p/4949624.html,他的教程寫的非常不錯。
#定義一個按鈕類
class Button(object):
def __init__(self, upimage, downimage,position):
self.imageUp = pygame.image.load(upimage).convert_alpha()
self.imageDown = pygame.image.load(downimage).convert_alpha()
self.position = position
self.game_start = False
def isOver(self):
point_x,point_y = pygame.mouse.get_pos()
x, y = self. position
w, h = self.imageUp.get_size()
in_x = x - w/2 < point_x < x + w/2
in_y = y - h/2 < point_y < y + h/2
return in_x and in_y
def render(self):
w, h = self.imageUp.get_size()
x, y = self.position
if self.isOver():
screen.blit(self.imageDown, (x-w/2,y-h/2))
else:
screen.blit(self.imageUp, (x-w/2, y-h/2))
def is_start(self):
if self.isOver():
b1,b2,b3 = pygame.mouse.get_pressed()
if b1 == 1:
self.game_start = True
bg_sound.play_pause()
btn_sound.play_sound()
bg_sound.play_sound()
可以看到這個button類裡面我還添加了一個isStart的方法,他是用來判斷是否開始遊戲的。當滑鼠的位置與button重合,且按下滑鼠左鍵的時候,遊戲就開始。
(将game_start變量置為True)然後通過btn_sound.play_sound(),bg_sound.play_sound() 這兩句來播放按鈕被按下的聲音和遊戲的背景音樂。
關于pygame中聲音的操作,我稍後介紹一下。
可以看到程式中還有一個不停滾動的地圖,讓我們來實作這個滾動地圖類:
#定義一個滾動地圖類
class MyMap(pygame.sprite.Sprite):
def __init__(self,x,y):
self.x = x
self.y = y
self.bg = pygame.image.load("background.png").convert_alpha()
def map_rolling(self):
if self.x < -600:
self.x = 600
else:
self.x -=5
def map_update(self):
screen.blit(self.bg, (self.x,self.y))
def set_pos(x,y):
self.x =x
self.y =y
建立兩個地圖對象:
#建立地圖對象
bg1 = MyMap(0,0)
bg2 = MyMap(600,0)
在程式中直接調用update和rolling方法就可以讓地圖無限的滾動起來了。
bg1.map_update()
bg2.map_update()
bg1.map_rolling()
bg2.map_rolling()
你看明白這個無限滾動地圖是如何工作的了嗎。首先渲染兩張地圖背景,一張展示在螢幕上面,一張在螢幕之外預備着(我們暫時看不到),如下圖所示:
然後兩張地圖一起以相同的速度向左移動:
當地圖1完全離開螢幕範圍的時候,再次将它的坐标置為600,0(這樣就又回到了狀态1):
這樣通過兩張圖檔的不斷颠倒位置,然後平移,在我們的視覺中就形成了一張不斷滾動的地圖了。
下面介紹一下如何在pygame中加載并且使用聲音:
1.初始化音頻子產品:
我們要使用的音頻系統包含在了pygame的pygame.mixer子產品裡面。是以在使用音頻之前要初始化這個子產品:
pygame.mixer.init()
這個初始化子產品語句在程式中執行一次就好。
2.加載音頻檔案:
使用的是pygame.mixer.Sound類來加載和管理音頻檔案,pygame支援兩種音頻檔案:未壓縮的WAV和OGG音頻檔案,如果要播放長時間的音樂,我推薦你使用OGG格式音頻檔案,因為它的體積比較小,适合長時間的加載和播放。當你要播放比較短的音頻的時候可以選擇WAV。
hit_au = pygame.mixer.Sound("exlposion.wav")
3.播放音樂:
上面的pygame.mixer.Sound函數傳回了一個sound對象,我們可以使用play和stop方法來播放和停止播放音樂。
但是這裡我們介紹一種更為進階的用法,使用pygame.mixer.Channel,這個類提供了比sound對象更為豐富的功能。
首先我們先申請一個可用的音頻頻道:
channel = pygame.mixer.find_channel(True)
一旦有了頻道之後我們就可以使用Channel.play()方法來播放一個sound對象了。
channel.play(sound)
好了現在讓我們來實作一下和音頻有關的子產品:
首先定義一個初始化的函數,它初始化了音頻子產品,并且加載了一些音頻檔案以友善我們在程式中使用:
def audio_init():
global hit_au,btn_au,bg_au,bullent_au
pygame.mixer.init()
hit_au = pygame.mixer.Sound("exlposion.wav")
btn_au = pygame.mixer.Sound("button.wav")
bg_au = pygame.mixer.Sound("background.ogg")
bullent_au = pygame.mixer.Sound("bullet.wav")
然後我們實作了一個Music類,這個類可以控制聲音的播放和暫停(set_volume函數是用來設定音樂聲音大小的):
class Music():
def __init__(self,sound):
self.channel = None
self.sound = sound
def play_sound(self):
self.channel = pygame.mixer.find_channel(True)
self.channel.set_volume(0.5)
self.channel.play(self.sound)
def play_pause(self):
self.channel.set_volume(0.0)
self.channel.play(self.sound)
跳躍函數:
當按下空格鍵的時候,嗷大喵會跳起,這個是如何實作的呢?
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
keys = pygame.key.get_pressed()
if keys[K_ESCAPE]:
pygame.quit()
sys.exit()
elif keys[K_SPACE]:
if not player_jumping:
player_jumping = True
jump_vel = -12.0
當按下空格鍵的時候,會将player_jumping變量置為True 并且給jump_vel一個初速度-12.0
然後在每次循環的時候,将jump_vel 加0.6,當嗷大喵回到起跳位置的時候,将速度置為0,使人物不再在y方向上有移動。
#檢測玩家是否處于跳躍狀态
if player_jumping:
if jump_vel <0:
jump_vel += 0.6
elif jump_vel >= 0:
jump_vel += 0.8
player.Y += jump_vel
if player.Y > player_start_y:
player_jumping = False
player.Y = player_start_y
jump_vel = 0.0
然後我們還需要一個不斷發出的子彈:
#更新子彈
if not game_over:
arrow.X -= arrow_vel
if arrow.X < -40: reset_arrow()
#重置火箭函數
def reset_arrow():
y = random.randint(270,350)
arrow.position = 800,y
bullent_sound.play_sound()
關于嗷大喵和子彈沖突檢測我們使用了之前學過的矩形沖突檢測技術,當玩家和子彈産生沖突的時候,重置子彈,播放爆炸動畫,然後将人物的x坐标值向左移動10,以表示人物受到傷害。惡龍和子彈的沖突和這個是一樣的,這裡就不再贅述了。
#碰撞檢測,子彈是否擊中玩家
if pygame.sprite.collide_rect(arrow, player):
reset_arrow()
explosion.position =player.X,player.Y
player_hit = True
hit_sound.play_sound()
if p_first:
group_exp.add(explosion)
p_first = False
player.X -= 10
然後我們還需要考慮一下玩家被惡龍追上的時候的情形,還是應用矩形檢測技術:
if pygame.sprite.collide_rect(player, dragon):
game_over = True
為了使果實移動,我們需要周遊group_fruit裡面的果實,然後依次将他們左移5個機關,然後我們還需要判斷玩家吃到果實的場景,果實會消失,然後玩家的積分增加。
這裡使用了之前學過的pygame.sprite.spritecollide(sprite,sprite_group,bool)。
調用這個函數的時候,一個組中的所有精靈都會逐個地對另外一個單個精靈進行沖突檢測,發生沖突的精靈會作為一個清單傳回。
這個函數的第一個參數就是單個精靈,第二個參數是精靈組,第三個參數是一個bool值,最後這個參數起了很大的作用。當為True的時候,會删除組中所有沖突的精靈,False的時候不會删除沖突的精靈。是以我們這裡将第三個參數設定為True,這樣就會删除掉和精靈沖突的對象了,看起來就好像是玩家吃掉了這些果實一樣。
#周遊果實,使果實移動
for e in group_fruit:
e.X -=5
collide_list = pygame.sprite.spritecollide(player,group_fruit,False)
score +=len(collide_list)
最後還是看一下全部的代碼:
1 # -*- coding: utf-8 -*-
2 import sys, time, random, math, pygame,locale
3 from pygame.locals import *
4 from MyLibrary import *
5
6 #重置火箭函數
7 def reset_arrow():
8 y = random.randint(270,350)
9 arrow.position = 800,y
10 bullent_sound.play_sound()
11
12 #定義一個滾動地圖類
13 class MyMap(pygame.sprite.Sprite):
14
15 def __init__(self,x,y):
16 self.x = x
17 self.y = y
18 self.bg = pygame.image.load("background.png").convert_alpha()
19 def map_rolling(self):
20 if self.x < -600:
21 self.x = 600
22 else:
23 self.x -=5
24 def map_update(self):
25 screen.blit(self.bg, (self.x,self.y))
26 def set_pos(x,y):
27 self.x =x
28 self.y =y
29 #定義一個按鈕類
30 class Button(object):
31 def __init__(self, upimage, downimage,position):
32 self.imageUp = pygame.image.load(upimage).convert_alpha()
33 self.imageDown = pygame.image.load(downimage).convert_alpha()
34 self.position = position
35 self.game_start = False
36
37 def isOver(self):
38 point_x,point_y = pygame.mouse.get_pos()
39 x, y = self. position
40 w, h = self.imageUp.get_size()
41
42 in_x = x - w/2 < point_x < x + w/2
43 in_y = y - h/2 < point_y < y + h/2
44 return in_x and in_y
45
46 def render(self):
47 w, h = self.imageUp.get_size()
48 x, y = self.position
49
50 if self.isOver():
51 screen.blit(self.imageDown, (x-w/2,y-h/2))
52 else:
53 screen.blit(self.imageUp, (x-w/2, y-h/2))
54 def is_start(self):
55 if self.isOver():
56 b1,b2,b3 = pygame.mouse.get_pressed()
57 if b1 == 1:
58 self.game_start = True
59 bg_sound.play_pause()
60 btn_sound.play_sound()
61 bg_sound.play_sound()
62
63 def replay_music():
64 bg_sound.play_pause()
65 bg_sound.play_sound()
66
67 #定義一個資料IO的方法
68 def data_read():
69 fd_1 = open("data.txt","r")
70 best_score = fd_1.read()
71 fd_1.close()
72 return best_score
73
74
75 #定義一個控制聲音的類和初始音頻的方法
76 def audio_init():
77 global hit_au,btn_au,bg_au,bullent_au
78 pygame.mixer.init()
79 hit_au = pygame.mixer.Sound("exlposion.wav")
80 btn_au = pygame.mixer.Sound("button.wav")
81 bg_au = pygame.mixer.Sound("background.ogg")
82 bullent_au = pygame.mixer.Sound("bullet.wav")
83 class Music():
84 def __init__(self,sound):
85 self.channel = None
86 self.sound = sound
87 def play_sound(self):
88 self.channel = pygame.mixer.find_channel(True)
89 self.channel.set_volume(0.5)
90 self.channel.play(self.sound)
91 def play_pause(self):
92 self.channel.set_volume(0.0)
93 self.channel.play(self.sound)
94
95 #主程式部分
96 pygame.init()
97 audio_init()
98 screen = pygame.display.set_mode((800,600),0,32)
99 pygame.display.set_caption("嗷大喵快跑!")
100 font = pygame.font.Font(None, 22)
101 font1 = pygame.font.Font(None, 40)
102 framerate = pygame.time.Clock()
103 upImageFilename = 'game_start_up.png'
104 downImageFilename = 'game_start_down.png'
105 #建立按鈕對象
106 button = Button(upImageFilename,downImageFilename, (400,500))
107 interface = pygame.image.load("interface.png")
108
109 #建立地圖對象
110 bg1 = MyMap(0,0)
111 bg2 = MyMap(600,0)
112 #建立一個精靈組
113 group = pygame.sprite.Group()
114 group_exp = pygame.sprite.Group()
115 group_fruit = pygame.sprite.Group()
116 #建立怪物精靈
117 dragon = MySprite()
118 dragon.load("dragon.png", 260, 150, 3)
119 dragon.position = 100, 230
120 group.add(dragon)
121
122 #建立爆炸動畫
123 explosion = MySprite()
124 explosion.load("explosion.png",128,128,6)
125 #建立玩家精靈
126 player = MySprite()
127 player.load("sprite.png", 100, 100, 4)
128 player.position = 400, 270
129 group.add(player)
130
131 #建立子彈精靈
132 arrow = MySprite()
133 arrow.load("flame.png", 40, 16, 1)
134 arrow.position = 800,320
135 group.add(arrow)
136
137
138
139 #定義一些變量
140 arrow_vel = 10.0
141 game_over = False
142 you_win = False
143 player_jumping = False
144 jump_vel = 0.0
145 player_start_y = player.Y
146 player_hit = False
147 monster_hit = False
148 p_first = True
149 m_first = True
150 best_score = 0
151 global bg_sound,hit_sound,btn_sound,bullent_sound
152 bg_sound=Music(bg_au)
153 hit_sound=Music(hit_au)
154 btn_sound=Music(btn_au)
155 bullent_sound =Music(bullent_au)
156 game_round = {1:'ROUND ONE',2:'ROUND TWO',3:'ROUND THREE',4:'ROUND FOUR',5:'ROUND FIVE'}
157 game_pause = True
158 index =0
159 current_time = 0
160 start_time = 0
161 music_time = 0
162 score =0
163 replay_flag = True
164 #循環
165 bg_sound.play_sound()
166 best_score = data_read()
167 while True:
168 framerate.tick(60)
169 ticks = pygame.time.get_ticks()
170 for event in pygame.event.get():
171 if event.type == pygame.QUIT:
172 pygame.quit()
173 sys.exit()
174 keys = pygame.key.get_pressed()
175 if keys[K_ESCAPE]:
176 pygame.quit()
177 sys.exit()
178
179 elif keys[K_SPACE]:
180 if not player_jumping:
181 player_jumping = True
182 jump_vel = -12.0
183
184 screen.blit(interface,(0,0))
185 button.render()
186 button.is_start()
187 if button.game_start == True:
188 if game_pause :
189 index +=1
190 tmp_x =0
191 if score >int (best_score):
192 best_score = score
193 fd_2 = open("data.txt","w+")
194 fd_2.write(str(best_score))
195 fd_2.close()
196 #判斷遊戲是否通關
197 if index == 6:
198 you_win = True
199 if you_win:
200 start_time = time.clock()
201 current_time =time.clock()-start_time
202 while current_time<5:
203 screen.fill((200, 200, 200))
204 print_text(font1, 270, 150,"YOU WIN THE GAME!",(240,20,20))
205 current_time =time.clock()-start_time
206 print_text(font1, 320, 250, "Best Score:",(120,224,22))
207 print_text(font1, 370, 290, str(best_score),(255,0,0))
208 print_text(font1, 270, 330, "This Game Score:",(120,224,22))
209 print_text(font1, 385, 380, str(score),(255,0,0))
210 pygame.display.update()
211 pygame.quit()
212 sys.exit()
213
214 for i in range(0,100):
215 element = MySprite()
216 element.load("fruit.bmp", 75, 20, 1)
217 tmp_x +=random.randint(50,120)
218 element.X = tmp_x+300
219 element.Y = random.randint(80,200)
220 group_fruit.add(element)
221 start_time = time.clock()
222 current_time =time.clock()-start_time
223 while current_time<3:
224 screen.fill((200, 200, 200))
225 print_text(font1, 320, 250,game_round[index],(240,20,20))
226 pygame.display.update()
227 game_pause = False
228 current_time =time.clock()-start_time
229
230 else:
231 #更新子彈
232 if not game_over:
233 arrow.X -= arrow_vel
234 if arrow.X < -40: reset_arrow()
235 #碰撞檢測,子彈是否擊中玩家
236 if pygame.sprite.collide_rect(arrow, player):
237 reset_arrow()
238 explosion.position =player.X,player.Y
239 player_hit = True
240 hit_sound.play_sound()
241 if p_first:
242 group_exp.add(explosion)
243 p_first = False
244 player.X -= 10
245
246 #碰撞檢測,子彈是否擊中怪物
247 if pygame.sprite.collide_rect(arrow, dragon):
248 reset_arrow()
249 explosion.position =dragon.X+50,dragon.Y+50
250 monster_hit = True
251 hit_sound.play_sound()
252 if m_first:
253 group_exp.add(explosion)
254 m_first = False
255 dragon.X -= 10
256
257 #碰撞檢測,玩家是否被怪物追上
258 if pygame.sprite.collide_rect(player, dragon):
259 game_over = True
260 #周遊果實,使果實移動
261 for e in group_fruit:
262 e.X -=5
263 collide_list = pygame.sprite.spritecollide(player,group_fruit,False)
264 score +=len(collide_list)
265 #是否通過關卡
266 if dragon.X < -100:
267 game_pause = True
268 reset_arrow()
269 player.X = 400
270 dragon.X = 100
271
272
273
274 #檢測玩家是否處于跳躍狀态
275 if player_jumping:
276 if jump_vel <0:
277 jump_vel += 0.6
278 elif jump_vel >= 0:
279 jump_vel += 0.8
280 player.Y += jump_vel
281 if player.Y > player_start_y:
282 player_jumping = False
283 player.Y = player_start_y
284 jump_vel = 0.0
285
286
287 #繪制背景
288 bg1.map_update()
289 bg2.map_update()
290 bg1.map_rolling()
291 bg2.map_rolling()
292
293 #更新精靈組
294 if not game_over:
295 group.update(ticks, 60)
296 group_exp.update(ticks,60)
297 group_fruit.update(ticks,60)
298 #循環播放背景音樂
299 music_time = time.clock()
300 if music_time > 150 and replay_flag:
301 replay_music()
302 replay_flag =False
303 #繪制精靈組
304 group.draw(screen)
305 group_fruit.draw(screen)
306 if player_hit or monster_hit:
307 group_exp.draw(screen)
308 print_text(font, 330, 560, "press SPACE to jump up!")
309 print_text(font, 200, 20, "You have get Score:",(219,224,22))
310 print_text(font1, 380, 10, str(score),(255,0,0))
311 if game_over:
312 start_time = time.clock()
313 current_time =time.clock()-start_time
314 while current_time<5:
315 screen.fill((200, 200, 200))
316 print_text(font1, 300, 150,"GAME OVER!",(240,20,20))
317 current_time =time.clock()-start_time
318 print_text(font1, 320, 250, "Best Score:",(120,224,22))
319 if score >int (best_score):
320 best_score = score
321 print_text(font1, 370, 290, str(best_score),(255,0,0))
322 print_text(font1, 270, 330, "This Game Score:",(120,224,22))
323 print_text(font1, 370, 380, str(score),(255,0,0))
324 pygame.display.update()
325 fd_2 = open("data.txt","w+")
326 fd_2.write(str(best_score))
327 fd_2.close()
328 pygame.quit()
329 sys.exit()
330 pygame.display.update()