我們將用面向對象的思維寫一個飛機大戰的小遊戲 。
分爲幾個類:
- 全局設置類:包括一些基本的參數以及音樂、圖片文件的路勁。
- 子彈基礎類
- 玩家子彈
- 敵人子彈
- 飛機基類
- 玩家飛機基類
- 敵人飛機類
目錄
(一)class Settings():
import sys, time, random
import pygame
from pygame.locals import *
import threading
SCREENWIDTH = 512
SCREENHEIGHT = 768
Gray = (128, 128, 128)
# 全局的設置類
class Settings():
def __init__(self):
# 定義遊戲窗口大小,爲背景圖片的一半
self.screen_size = (self.screen_width, self.screen_height) = (SCREENWIDTH, SCREENHEIGHT)
self.bg_color = Gray
self.bg_image = './images/bg2.jpg'
# 不能播放 mp3,所以轉成 wav
self.bg_music = './music/01.wav'
self.gameover_image = './images/gameover.jpg'
self.title = '飛機大戰'
# 英雄機參數
self.move_step = 5 # 鍵盤控制的速度
self.hero_style = './images/me.png'
# 敵機參數
self.enemy_speed = 4 # 敵機飛行速度
self.enemy_style_list = ['./images/e0.png', './images/e1.png', './images/e2.png']
# 子彈參數
self.bullet_style = './images/pd.png'
self.bullet_hero_v = 10 # 英雄機子彈速度
self.bullet_enemy_v = 8 # 敵機子彈速度
# 實例化設置對象
settings = Settings()
設置一個全局的類,其中包括一些基本的參數。
最後對於一個類,都需要經過初始化和實例化後纔可以發揮作用,類中使用__init__自動初始化,然後創建了一個settings對象,進行類的實例化。
(二)class Bullet:
# 子彈基類
class Bullet:
def __init__(self, screen, x, y):
self.x = x
self.y = y
self.screen = screen
self.image = pygame.image.load(settings.bullet_style)
def __del__(self):
pass
def bulletRect(self):
bulletrect = self.image.get_rect()
bulletrect.topleft = (self.x, self.y)
return bulletrect
def display(self):
self.screen.blit(self.image, (self.x, self.y))
def move(self):
return True
設置子彈的基礎,玩家的子彈和敵機的子彈都要從這個子彈基類繼承。
當一個Python程序結束之後,Python解釋器會自動調用__del__()方法來釋放內存;當然我們也可以根據需要自己調用__del__()方法來刪除對象,釋放佔用的內存。因爲我們想要提高計算機的效率,暫時用不到的事情可以先不讓計算機做。
pygame.image.load()方法用來加載圖片。
用blit()方法在一個Surface對象上面繪製一個Surface對象。
最後建立move()方法,調用這個方法返回布爾值,通過布爾值來確定書否移除這個子彈。
(三)玩家、敵人子彈類
玩家子彈類和英雄子彈類都繼承子彈類,實現的功能差不多。
# 英雄子彈類
class HeroBullet(Bullet):
def __init__(self, screen, x, y):
super().__init__(screen, x, y)
def move(self):
self.y -= settings.bullet_hero_v
# 判斷子彈是否出界
if self.y <= -20:
return True
# 敵機子彈類
class EnemyBullet(Bullet):
def __init__(self, screen, x, y):
super().__init__(screen, x, y)
def move(self):
self.y += settings.bullet_enemy_v
# 判斷子彈是否出界
if self.y >= settings.screen_height:
return True
Python在類中可任意通過__init__方法實現對實例中代碼實現自動初始化,但是Python本身不會自動執行初始化操作,但是我們可能要用帶繼承類的其他方法,所以要調用super().__init__()方法來來實現繼承類的初始化。
(四)class Plane:
# 飛機基類
class Plane:
def __init__(self, screen, style, geo):
self.screen = screen
self.image = pygame.image.load(style)
self.bullet_list = []
self.x = geo[0]
self.y = geo[1]
self.is_dead = False
self.finished = False
self.bomb_seq = ['4','4','3','3','2','2','1','1']
def __del__(self):
pass
def planeRect(self):
planerect = self.image.get_rect()
planerect.topleft = (self.x, self.y)
return planerect
def display(self):
for b in self.bullet_list:
b.display()
# 回收子彈
if b.move():
self.bullet_list.remove(b)
# 爆炸效果
if self.is_dead:
death_x = self.x
death_y = self.y
death_w = self.image.get_width()
death_h = self.image.get_height()
try:
bomb_image = './images/bomb'+self.bomb_seq.pop()+'.png'
self.image = pygame.image.load(bomb_image)
except:
self.image = pygame.image.load('./images/bomb4.png')
self.finished = True
finally:
x = death_x + (death_w - self.image.get_width())/2
y = death_y + (death_h - self.image.get_height())/2
self.screen.blit(self.image, (x, y))
else:
# 重新繪製飛機
self.screen.blit(self.image, (self.x, self.y))
def fire(self):
self.bullet_list.append(Bullet(self.screen, self.x, self.y))
print(len(self.bullet_list))
def over(self):
#print("Oops: plane over ...")
#del self
return self.finished
screen爲屏幕的Surface對象,表示一個矩形圖像。
pop()方法:用來刪除列表中最後一個元素。
>>> bomb_seq = ['4','4','3','3','2','2','1','1']
>>> bomb_seq.pop()
'1'
>>> bomb_seq
['4', '4', '3', '3', '2', '2', '1']
(五)玩家飛機
# 英雄飛機
class HeroPlane(Plane):
def __init__(self, screen):
# 英雄機初始位置
geo = (200, 600)
super().__init__(screen, settings.hero_style, geo)
self.step = settings.move_step
# 英雄機移動範圍
self.limit_left = -(self.image.get_width()/2)+10
self.limit_right = settings.screen_width-self.image.get_width()/2-10
self.limit_top = 5
self.limit_bottom = settings.screen_height-self.image.get_height()
def fire(self):
self.bullet_list.append(HeroBullet(self.screen, self.x+53, self.y))
def move_left(self):
if self.x <= self.limit_left:
pass
else:
self.x -= self.step
def move_right(self):
if self.x >= self.limit_right:
pass
else:
self.x += self.step
def move_up(self):
if self.y <= self.limit_top:
pass
else:
self.y -= self.step
def move_down(self):
if self.y >= self.limit_bottom:
pass
else:
self.y += self.step
其中玩家移動的範圍下左右都是由screen_size決定的,但是上方我們直接設置一個值就好了,這裏我們設置爲5:self.limit_top = 5,意思是玩家飛機最高能飛到5個像素的位置。
然後是英雄飛機左右上下的移動,先判斷是否超出邊界,如果在邊界範圍內的話,就進行“移動”,也就是更改相應方向的座標。
(六)敵人飛機
# 敵機
class EnemyPlane(Plane):
def __init__(self, screen):
geo = (random.choice(range(408)), -75)
enemy_style = settings.enemy_style_list[random.choice(range(3))]
super().__init__(screen, enemy_style, geo)
self.pipe_x = self.image.get_width()/2-1 # 1 for the width of bullet
self.pipe_y = self.image.get_height()
#self.planeRect.topleft = geo
#是否要刪除敵機,返回布爾值
def move(self, hero):
self.y += settings.enemy_speed
if self.y > settings.screen_height:
return True
# 飛機的碰撞檢測
#使用位置檢測碰撞:
#if self.x > hero.x-50 and self.x < hero.x+50 and self.y > hero.y-40 and self.y < hero.y+40:
#使用Rect對象的colliderect()方法:
if self.planeRect().colliderect(hero.planeRect()):
self.is_dead = True
hero.is_dead = True
# 看看我中彈了沒
for bo in hero.bullet_list:
if self.planeRect().colliderect(bo.bulletRect()):
#if bo.x > self.x+12 and bo.x < self.x+92 and bo.y < self.y+60 and bo.y > self.y:
hero.bullet_list.remove(bo)
# 爆炸
self.is_dead = True
# 看看英雄機中彈了沒
for bo in self.bullet_list:
if hero.planeRect().colliderect(bo.bulletRect()):
#if bo.x > hero.x+25 and bo.x < hero.x+75 and bo.y > hero.y+5 and bo.y < hero.y+50:
self.bullet_list.remove(bo)
hero.is_dead = True
def fire(self):
self.bullet_list.append(EnemyBullet(self.screen, self.x+self.pipe_x, self.y+self.pipe_y))
(七)設置方法
這裏寫一個事件檢測和繪製文字的函數,把功能放到這個函數裏面,選喲這兩個功能的時候就調用相應的函數就可以了。
def check_event(hero, usedbullet):
#Key event capture and key_control
which = pygame.key.get_pressed()
if which[K_SPACE]:
hero.fire()
usedbullet += 1
print("Space...")
if which[K_LEFT] or which[K_a]:
hero.move_left()
print("Left...")
elif which[K_RIGHT] or which[K_d]:
hero.move_right()
print("Right...")
elif which[K_UP] or which[K_w]:
hero.move_up()
print("Up...")
elif which[K_DOWN] or which[K_s]:
hero.move_down()
print("Down...")
for event in pygame.event.get():
if event.type == MOUSEMOTION:
hero.x = event.pos[0]
hero.y = event.pos[1]
if event.type == QUIT:
print("exit...")
sys.exit()
return usedbullet
#draw score
def drawText(text, font, screen, x, y):
TEXTCOLOR = (0, 0, 0)
textobj = font.render(text, 1, TEXTCOLOR)
textrect = textobj.get_rect()
textrect.topleft = (x, y)
screen.blit(textobj, textrect)
事件檢測這裏使用的是pygame.key.get_pressed()這個方法,在前面的遊戲中有使用pygame.event.get()這個方法,二者之間有一些不同,在這個遊戲裏面,用這兩種事件調用的方法遊戲的操作會有顯著的區別。
大家可以在上面代碼中修改一下,在遊戲操作裏面體驗一下有神麼不同。
看看官網中的介紹:
get()方法中:
This will get all the messages and remove them from the queue. If a type or sequence of types is given only those messages will be removed from the queue.
get_pressed()方法中:
Returns a sequence of boolean values representing the state of every key on the keyboard. Use the key constant values to index the array. A True
value means the that button is pressed.
"""
event = pygame.event.get()
if event.type == KEYDOWN:
if event.key == K_SPACE:
hero.fire()
print("Space...")
if event.key == K_LEFT or K_a:
hero.move_left()
if event.key == K_RIGHT or K_d:
hero.move_right()
if event.key == K_UP or K_w:
hero.move_up()
if event.key == K_DOWN or K_s:
hero.move_down()
"""
(八)主函數
def main():
# 初始化 Pygame
pygame.init()
# 創建一個遊戲窗口
screen = pygame.display.set_mode(settings.screen_size, 0, 0)
# 設置窗口標題
pygame.display.set_caption(settings.title)
# 在窗口中加載遊戲背景
background = pygame.image.load(settings.bg_image)
#設置積分器
font = pygame.font.SysFont(None, 48)
# 背景音樂
pygame.mixer.init()
pygame.mixer.music.load(settings.bg_music)
#pygame.mixer.music.play()
# 創建英雄機
hero = HeroPlane(screen)
play = True
enemylist = []
bg_y = -(settings.screen_height)
usedbullet = 0
score = 0
while True:
#檢查退出
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if not pygame.mixer.music.get_busy():
print("play music")
pygame.mixer.music.play()
# 從座標(0, -768)開始繪圖,所以看到的是背景圖片的下半部
screen.blit(background, (0, bg_y))
bg_y += 2
if bg_y >= 0:
bg_y = -(settings.screen_height)
if play:
drawText('Score:{}'.format(score),font,screen,256,256)
drawText('Used Bullet:{}'.format(usedbullet),font,screen,256,300)
# 繪製英雄機
hero.display()
# 隨機繪製敵機
if random.choice(range(50)) == 10:
enemylist.append(EnemyPlane(screen))
# 遍歷敵機並繪製移動
for em in enemylist:
em.display()
# 敵機隨機發炮彈
if random.choice(range(50)) == 10:
em.fire()
# 判斷敵機是否出界
if em.move(hero):
enemylist.remove(em)
# 判斷敵機是否炸燬
if em.over():
score += 1
enemylist.remove(em)
# 英雄機炸燬,遊戲結束
if hero.over():
play = False
#pygame.display.flip()
pygame.display.update()
else:
gameover = pygame.image.load(settings.gameover_image)
screen.blit(gameover, ((settings.screen_width-gameover.get_width())/2,
(settings.screen_height-gameover.get_height())/2))
pygame.display.update()
#print("Game Over")
#continue
# 檢查按鍵事件
usedbullet = check_event(hero,usedbullet)
time.sleep(0.04)
# 判斷是否爲主運行程序,是則調用 main()
if __name__ == '__main__':
main()
對於全局變量報錯:UnboundLocalError: local variable ‘usedbullet’ referenced before assignment的解釋:
在程序中設置的 sum 屬於全局變量,而在函數中沒有 sum 的定義,根據python訪問局部變量和全局變量的規則:當搜索一個變量的時候,python先從局部作用域開始搜索,如果在局部作用域沒有找到那個變量,那樣python就在全局變量中找這個變量,如果找不到拋出異常(NAMEERROR或者Unbound-LocalError,這取決於python版本。)
如果內部函數有引用外部函數的同名變量或者全局變量,並且對這個變量有修改.那麼python會認爲它是一個局部變量,又因爲函數中沒有sum的定義和賦值,所以報錯。
其中if __name__ == '__main__':是隻作爲腳本執行時,執行main()函數,如果作爲包導入其他腳本中時,則不執行。
這個遊戲修改了很多天,對於初學者,需要理解面向對象思維的編程特點。當然如果按照之前的面向過程的思路來寫的話,代碼可能會短一些,但是修改起來就會有很大的問題,這也是面向對象和麪型過程二者之間的區別,我會在下一個專欄中介紹這些,期待自己學習的進步吧!
這個飛機大戰的遊戲還是未完成的狀態,我們可以在以上代碼中進行一些修改,爲這個遊戲增加一些功能,讓這個遊戲變得更好玩,當然,在我們增加功能的過程中,也是對Python編程和pygame模塊的更深入的理解。
參考:
- http://c.biancheng.net/view/2371.html
- https://www.runoob.com/w3cnote/python-unboundlocalerror.html
- https://gitee.com/luhuadong/Python_Learning/repository/archive/master.zip