1. 程式人生 > 其它 >python中pygame簡單利用——開發貪吃蛇

python中pygame簡單利用——開發貪吃蛇

技術標籤:python遊戲開發

如何利用python開發一個貪吃蛇

文章目錄


前言

今天將用python開發一個貪吃蛇遊戲,主要是面向物件開發和pygame的基本使用


提示:以下是本篇文章正文內容,下面案例可供參考

一、pygame是什麼

本次python運用PyCharm2020,3x64版本編寫,python的版本為3.9
在這裡插入圖片描述
pygame是轉為開發電子遊戲而設計的跨平臺的Python包(Package)所謂模組,是指能夠獨立完成一定功能的程式集合,程式設計師利用pygame提供的API,可以方便地實現,比如說你可以建立圖形介面,監聽使用者鍵盤或滑鼠操作,播放音訊等等,這三個在本章中都有運用到。

關於pygame的安裝,一開始費了很多時間,在python的官網下載,並看教程,反反覆覆失敗了n次,最後發現在python3.9版本中安裝的方法很簡單隻需要一行指令即可
在這裡插入圖片描述
雖然版本不對,但是仍可執行。。。
驗證:在命令提示符輸入以下命令,如果能夠看到aliens遊戲正常啟動並執行,就說明已經安裝成功了。

python3 -m pygame.examples.aliens

二、貪吃蛇遊戲規則

2.1開始和結束

1.貪吃蛇一開始出生在左上角,只有一節身體一個頭
2.蛇如果碰到了自己的身體或者碰到了遊戲邊界,那麼就直接死亡
3.如果死亡或者想要暫停可以按下空格鍵

2.2怎麼運動和控制

1.我們使用監聽鍵盤上的方向鍵(↑、↓、←、→)來控制蛇的運動軌跡

2.3得分

1.得分設為吃到一個豆子得5分,初始為0分
2.食物必須滿足是在遊戲視窗隨機生成的,如果蛇頭跟食物碰到了,那就代表蛇吃到了食物,然後食物再次重新整理隨機位置
3.食物出現30s內,貪吃蛇沒吃到,那麼食物就重新整理
4.遊戲會隨著你蛇的增長,也會變快
程式碼如下(示例):

2.4 建立四個類

根據遊戲的規則我們要建立4種物件分別是遊戲物件、蛇、標籤、食物

三、開發過程

3.1主要模組

新建專案及檔案準備
本文創立了兩個模組,一個是game.py,還有一個是game_items.py,並且還需要將需要的音樂素材和圖片素材都放在同一個資料夾中,
pygame的初始化和推出
pygame為了程式設計師更加方便地使用包中模組,提供了兩個方法——init和quit

·init一次性初始化pygame的所有模組,後續開發可以直接使用。
·quit方法可以取消初始化之前已經初始化過的模組,所以quit方法不是必須呼叫的
程式碼如下

import pygame #匯入pygame
from game_items import *
    if __name__ == '__main__':
        pygame.init()  # 初始化所有模組

        # 遊戲程式碼
        Game().start()  # 建立遊戲物件並且啟動遊戲

        pygame.quit()  # 取消初始化所有模組
    pygame.display.update()
    for event in pygame.event.get():

        # 判斷事件型別是否是退出事件
        if event.type == pygame.QUIT:
            print("遊戲退出...")

            # quit 解除安裝所有的模組
            pygame.quit()

            # exit() 直接終止當前正在執行的程式
            exit()

遊戲主視窗
因為要實現互動介面,所以進入貪吃蛇遊戲前,我設定了一個開始介面,點選了開始遊戲即可進入遊戲,進入遊戲的過程就是重新整理視窗用到pygame中的pygame.display.update()
使用set_mode方法可以非常方便的建立一個遊戲主視窗

window=pygame.display.set_mode((800,600))
pygame.display.set_caption("Greedy Snake")
clock =pygame.time.Clock()
start_window = pygame.Surface(window.get_size())    #   充當開始介面的畫布
start_window2 = pygame.Surface(window.get_size())  #  充當第一關的畫布介面暫時佔位(可以理解為遊戲開始了)
start_window = start_window.convert()
start_window2 = start_window2.convert()
start_window.fill((255,255,255))  # 白色畫布
start_window2.fill((0,0,0))

遊戲介面的圖形插入
我們可以使用如下程式碼將圖片插入

# 載入各個素材圖片 並且賦予變數名
i1 = pygame.image.load("InitialStart.png")
i1.convert()
i11 = pygame.image.load("ChosenStart.png")
i11.convert()

遊戲迴圈和遊戲時鐘
要做到遊戲程式啟動執行之後,不會立即退出,需要在遊戲程式中加入一個遊戲迴圈,即無限迴圈

def start(self):
   while True:
   pass

當我們不設定一個遊戲時鐘時,我們會發現,cpu 的佔用率極高,大約有20%,重新整理幀率只要能夠達到每秒60幀,就能達到一定效果,pygame的time模組中專門提供了一個Clock類,可以非常方便地設定重新整理幀率pygame.time.Clock
事件監聽
遊戲迴圈中需要做四件事情:
事件監聽、繪製遊戲元素、更新顯示、設定重新整理率
在同一時刻,可能發生多個時間,因此應該使用for來遍歷事件列表;使用event.type可以判斷時間地型別:例如退出時間、按鍵事件等等;如果按鍵事件,使用event.key可以判斷具體的按鍵

 # 事件監聽
                for event in pygame.event.get():  # 遍歷同一時刻發生的事件列表
                    if event.type == pygame.QUIT:  # 判斷退出事件
                        return
                    elif event.type == pygame.KEYDOWN:  # 判斷按鍵事件
                        if event.key == pygame.K_ESCAPE:
                            return
                        elif event.key == pygame.K_SPACE:
                            if self.is_game_over:
                                self.reset_game()
                            else:
                                self.is_pause = not self.is_pause

繪製圖形
pygame的draw模組中專門提供了一系列方法,可以繪製圖形以蛇類為例
draw模組

    def draw(self, window):
        # 遍歷繪製每一節身體
        for idx, rect in enumerate(self.body_list):
            pygame.draw.rect(window,
                             self.color,
                             rect.inflate(-2, -2),  # 縮小矩形區域
                             idx == 0)              # 蛇頭繪製邊框不填充

隨機食物
在game_item.py模組的頂部,匯入random模組,以方便使用隨機數程式碼如下import random
在Food類中定義random_rect方法,隨機確定遊戲視窗的任意小個子設定食物出現的位置,程式碼如下:

class Food(object):
    """食物類"""
    def __init__(self):
        self.color = (0, 255, 0)  # 綠色食物
        self.score = 5  # 每顆食物得分 5 分
        self.rect = pygame.Rect(0, 0, CELL_SIZE, CELL_SIZE)  # 食物位置

        self.random_rect()  # 設定食物隨機位置

    def random_rect(self):
        col = SCREEN_RECT.w / CELL_SIZE - 1  # 螢幕上小格子的列數
        row = SCREEN_RECT.h / CELL_SIZE - 1  # 螢幕上小格子的行數

        x = random.randint(0, col) * CELL_SIZE
        y = random.randint(0, row) * CELL_SIZE

        self.rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
        # 食物初始不可見
        self.rect.inflate_ip(-CELL_SIZE, -CELL_SIZE)

        pygame.time.set_timer(FOOD_UPDATE_EVENT, 30000)  # 設定更新食物事件

定時器的設定
按照先前的設計,如果30s內貪吃蛇沒有吃到食物,那麼食物要再次重新整理。現在如果暫時不考慮貪吃蛇吃食物的情況,要實現遊戲規則需求,我們只要每隔30s呼叫一下random_rect方法即可,這時候我們就可以用到pygame 的time模組中提供的set_timer方法,就是專門用來定時器事件的。

FOOD_UPDATE_EVENT = pygame.USEREVENT            # 食物更新事件

蛇死亡判定思路
既要判斷捨得死亡又要記錄死亡時的狀態
①在Snake類定義is_dead方法:如果碰到邊界或者身體測返回True
②完善update方法:一旦發現移動身體之後貪吃蛇掛了,則回覆之前的身體資料,並且返回Flase,表示無法移動

    def is_dead(self):
        """判斷貪吃蛇是否死亡

        :return: 死亡返回 True,否則返回 False
        """
        # 1. 記錄蛇頭的矩形區域
        head = self.body_list[0]

        # 2. 判斷蛇頭是否移出螢幕
        if not SCREEN_RECT.contains(head):
            return True

        # 3. 判斷是否與身體其他部分重疊
        for body in self.body_list[1:]:
            if head.contains(body):
                return True

        return False

加入背景音樂
①先將所需要的背景音樂下載至同一個資料夾,通過pygame 的模組呼叫,程式碼如下

# 音樂的路徑
file=r'浪子康 - 小星星(咚鼓版)(翻自 汪蘇瀧).mp3'
file1=r'林俊杰 - 交換餘生.mp3'

# 初始化
pygame.mixer.init()
# 載入音樂檔案
track = pygame.mixer.music.load(file)
pygame.mixer.music.queue(file1)
# 開始播放音樂流
pygame.mixer.music.play()

3.2原始碼

game.py程式碼如下:

import pygame
from game_items import *
# 音樂的路徑
file=r'浪子康 - 小星星(咚鼓版)(翻自 汪蘇瀧).mp3'
file1=r'林俊杰 - 交換餘生.mp3'

# 初始化
pygame.mixer.init()
# 載入音樂檔案
track = pygame.mixer.music.load(file)
pygame.mixer.music.queue(file1)
# 開始播放音樂流
pygame.mixer.music.play()
pygame.init()
window=pygame.display.set_mode((800,600))
pygame.display.set_caption("Greedy Snake")
clock =pygame.time.Clock()
start_window = pygame.Surface(window.get_size())    #   充當開始介面的畫布
start_window2 = pygame.Surface(window.get_size())  #  充當第一關的畫布介面暫時佔位(可以理解為遊戲開始了)
start_window = start_window.convert()
start_window2 = start_window2.convert()
start_window.fill((255,255,255))  # 白色畫布
start_window2.fill((0,0,0))
# 載入各個素材圖片 並且賦予變數名
i1 = pygame.image.load("InitialStart.png")
i1.convert()
i11 = pygame.image.load("ChosenStart.png")
i11.convert()
#  以下為選擇開始介面滑鼠檢測結構。
n1 = True
while n1:
    clock.tick(30)
    buttons = pygame.mouse.get_pressed()
    x1, y1 = pygame.mouse.get_pos()
    if x1 >= 227 and x1 <= 555 and y1 >= 261 and y1 <=327:
        start_window.blit(i11, (200, 240))
        if buttons[0]:
            n1 = False




    window.blit(start_window,(0,0))
    pygame.display.update()


    # 下面是監聽退出動作

    # 監聽事件
    for event in pygame.event.get():

        # 判斷事件型別是否是退出事件
        if event.type == pygame.QUIT:
            print("遊戲退出...")

            # quit 解除安裝所有的模組
            pygame.quit()

            # exit() 直接終止當前正在執行的程式
            exit()



#  以下可以寫貪吃蛇的程式碼了
n2 = True
while n2:
    clock.tick(30)
    from game_items import *  # 匯入遊戲元素模組中的所有類和全域性變數


    class Game(object):
        """遊戲類"""

        def __init__(self):
            self.main_window = pygame.display.set_mode((640, 480))
            pygame.display.set_caption("Greedy Snake")

            self.score_label = Label()  # 得分文字標籤
            self.tip_label = Label(24, False)  # 提示標籤

            self.is_game_over = False  # 遊戲結束標記
            self.is_pause = False  # 遊戲暫停標記

            self.food = Food()  # 食物
            self.snake = Snake()  # 貪吃蛇

            print(self.snake.body_list)

        def reset_game(self):
            """遊戲復位"""
            self.is_game_over = False  # 遊戲結束標記
            self.is_pause = False  # 遊戲暫停標記

            self.food.random_rect()  # 重新設定食物位置
            self.snake.reset_snake()  # 設定蛇屬性

        def start(self):
            """開始貪吃蛇遊戲"""
            clock = pygame.time.Clock()  # 遊戲時鐘

            while True:

                # 事件監聽
                for event in pygame.event.get():  # 遍歷同一時刻發生的事件列表
                    if event.type == pygame.QUIT:  # 判斷退出事件
                        return
                    elif event.type == pygame.KEYDOWN:  # 判斷按鍵事件
                        if event.key == pygame.K_ESCAPE:
                            return
                        elif event.key == pygame.K_SPACE:
                            if self.is_game_over:
                                self.reset_game()
                            else:
                                self.is_pause = not self.is_pause

                    # 僅在遊戲狀態才會更新食物、移動蛇的位置以及改變蛇的運動方向
                    if not self.is_pause and not self.is_game_over:
                        if event.type == FOOD_UPDATE_EVENT:  # 判斷更新食物事件
                            self.food.random_rect()
                        if event.type == SNAKE_UPDATE_EVENT:  # 判斷貪吃蛇移動事件
                            self.is_game_over = not self.snake.update()
                        if event.type == pygame.KEYDOWN:  # 判斷按鍵事件
                            # 判斷是否方向鍵
                            if event.key in (pygame.K_LEFT, pygame.K_RIGHT,
                                             pygame.K_UP, pygame.K_DOWN):
                                self.snake.change_dir(event.key)

                # 依次繪製遊戲元素
                self.main_window.fill(BACKGROUND_COLOR)

                # 判斷遊戲狀態
                if self.is_game_over:

                    self.tip_label.draw(self.main_window, "遊戲結束,按空格鍵開啟新遊戲...")

                elif self.is_pause:

                    self.tip_label.draw(self.main_window, "遊戲暫停,按空格鍵繼續...")

                else:
                    # 判斷是否吃到食物
                    if self.snake.has_eat(self.food):
                        self.food.random_rect()

                self.score_label.draw(self.main_window, "得分:%d" % self.snake.score)
                self.food.draw(self.main_window)
                self.snake.draw(self.main_window)

                # 更新顯示
                pygame.display.update()

                clock.tick(60)  # 重新整理幀率


    if __name__ == '__main__':
        pygame.init()  # 初始化所有模組

        # 遊戲程式碼
        Game().start()  # 建立遊戲物件並且啟動遊戲

        pygame.quit()  # 取消初始化所有模組
    pygame.display.update()
    for event in pygame.event.get():

        # 判斷事件型別是否是退出事件
        if event.type == pygame.QUIT:
            print("遊戲退出...")

            # quit 解除安裝所有的模組
            pygame.quit()

            # exit() 直接終止當前正在執行的程式
            exit()

game_items.py程式碼如下:

import random
import pygame

# 全域性變數定義
SCREEN_RECT = pygame.Rect(0, 0, 640, 480)       # 遊戲視窗矩形區域
CELL_SIZE = 20                                  # 小格子大小

BACKGROUND_COLOR = (255,255, 255)              # 主視窗背景顏色
SCORE_TEXT_COLOR = (0, 0, 0)              # 分數文字顏色
TIP_TEXT_COLOR = (0, 0, 255)                   # 提示文字顏色

FOOD_UPDATE_EVENT = pygame.USEREVENT            # 食物更新事件
SNAKE_UPDATE_EVENT = pygame.USEREVENT + 1       # 貪吃蛇移動事件


class Label(object):
    """文字標籤類"""
    def __init__(self, size=48, is_score=True):
        """初始化方法

        :param size: 字型大小
        :param is_score: 是否分數
        """
        self.font = pygame.font.SysFont("simhei", size)  # 黑體字
        self.is_score = is_score

    def draw(self, window, text):
        """在視窗中繪製文字內容

        :param window: 遊戲主視窗
        :param text: 要顯示的文字內容
        """
        # 1. 使用字型渲染文字內容
        color = SCORE_TEXT_COLOR if self.is_score else TIP_TEXT_COLOR
        text_surface = self.font.render(text, True, color)

        # 獲得文字影象的矩形區域
        text_rect = text_surface.get_rect()
        # 獲得主視窗的矩形區域
        window_rect = window.get_rect()

        # 設定位置
        if self.is_score:  # 分數文字顯示在右下角
            text_rect.bottomright = window_rect.bottomright
        else:  # 其他文字顯示在中間
            text_rect.center = window_rect.center

        # 2. 在遊戲視窗中繪製渲染結果
        window.blit(text_surface, text_rect)


class Food(object):
    """食物類"""
    def __init__(self):
        self.color = (0, 255, 0)  # 綠色食物
        self.score = 5  # 每顆食物得分 5 分
        self.rect = pygame.Rect(0, 0, CELL_SIZE, CELL_SIZE)  # 食物位置

        self.random_rect()  # 設定食物隨機位置

    def random_rect(self):
        col = SCREEN_RECT.w / CELL_SIZE - 1  # 螢幕上小格子的列數
        row = SCREEN_RECT.h / CELL_SIZE - 1  # 螢幕上小格子的行數

        x = random.randint(0, col) * CELL_SIZE
        y = random.randint(0, row) * CELL_SIZE

        self.rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
        # 食物初始不可見
        self.rect.inflate_ip(-CELL_SIZE, -CELL_SIZE)

        pygame.time.set_timer(FOOD_UPDATE_EVENT, 30000)  # 設定更新食物事件

    def draw(self, window):

        # # 判斷寬度是否達到小格子寬度
        if self.rect.w < CELL_SIZE:
            self.rect.inflate_ip(2, 2)  # 向四周各自放大 1 個畫素

        pygame.draw.ellipse(window, self.color, self.rect)


class Snake(object):
    """貪吃蛇類"""
    def __init__(self):
        self.dir = pygame.K_RIGHT  # 初始向右運動
        self.score = 0  # 初始得分
        self.time_interval = 250 # 運動間隔時間
        self.color = (255, 144, 255)  # 身體顏色-紫粉

        self.body_list = []  # 身體列表

        self.reset_snake()

    def reset_snake(self):
        """重置蛇屬性"""
        self.dir = pygame.K_RIGHT
        self.score = 0
        self.time_interval = 250

        self.body_list.clear()  # 清空身體列表

        for i in range(2):  # 新增一節身體
            self.add_node()

    def add_node(self):
        """在蛇的運動方向上,增加一節身體"""
        # 1. 判斷是否有身體
        if self.body_list:
            head = self.body_list[0].copy()
        else:
            head = pygame.Rect(-CELL_SIZE, 0, CELL_SIZE, CELL_SIZE)

        # 2. 根據運動方向,調整 head 的位置
        if self.dir == pygame.K_RIGHT:
            head.x += CELL_SIZE
        elif self.dir == pygame.K_LEFT:
            head.x -= CELL_SIZE
        elif self.dir == pygame.K_UP:
            head.y -= CELL_SIZE
        elif self.dir == pygame.K_DOWN:
            head.y += CELL_SIZE

        # 3. 將蛇頭插入到身體列表第 0 項
        self.body_list.insert(0, head)

        # 4. 設定貪吃蛇移動定時器
        pygame.time.set_timer(SNAKE_UPDATE_EVENT, self.time_interval)

    def draw(self, window):
        # 遍歷繪製每一節身體
        for idx, rect in enumerate(self.body_list):
            pygame.draw.rect(window,
                             self.color,
                             rect.inflate(-2, -2),  # 縮小矩形區域
                             idx == 0)              # 蛇頭繪製邊框不填充

    def update(self):
        """移動貪吃蛇的整個身體

        一旦發現貪吃蛇移動後會死亡,則恢復整個身體資料

        :return: 移動成功返回 True,貪吃蛇死亡表示不能移動,返回 False
        """
        # 1. 備份身體列表
        body_list_copy = self.body_list.copy()

        # 2. 移動身體
        self.add_node()         # 沿著運動方向在蛇頭位置增加一節身體
        self.body_list.pop()    # 刪除蛇尾

        # 3. 判斷是否死亡,如果是,恢復備份的身體
        if self.is_dead():
            self.body_list = body_list_copy

            return False

        return True

    def change_dir(self, to_dir):
        """改變貪吃蛇的運動方向

        :param to_dir: 要變化的方向
        """
        hor_dirs = (pygame.K_RIGHT, pygame.K_LEFT)      # 水平方向
        ver_dirs = (pygame.K_UP, pygame.K_DOWN)         # 垂直方向

        # 判斷當前運動方向及要修改的方向
        if ((self.dir in hor_dirs and to_dir not in hor_dirs) or
                (self.dir in ver_dirs and to_dir not in ver_dirs)):

            self.dir = to_dir

    def has_eat(self, food):
        """判斷蛇頭是否與食物相遇 - 吃到食物

        :param food: 食物物件
        :return: 是否吃到食物
        """

        if self.body_list[0].contains(food.rect):
            self.score += food.score    # 增加分數

            # 修改運動時間間隔
            if self.time_interval > 100:
                self.time_interval -= 50

            self.add_node()             # 增加一節身體

            return True

        return False

    def is_dead(self):
        """判斷貪吃蛇是否死亡

        :return: 死亡返回 True,否則返回 False
        """
        # 1. 記錄蛇頭的矩形區域
        head = self.body_list[0]

        # 2. 判斷蛇頭是否移出螢幕
        if not SCREEN_RECT.contains(head):
            return True

        # 3. 判斷是否與身體其他部分重疊
        for body in self.body_list[1:]:
            if head.contains(body):
                return True

        return False

總結

此次由於時間的倉促,在開發貪吃蛇遊戲的過程中,雖然遊戲整體可以執行,但是互動仍有待提高,其中存在了一些問題還沒有來得及去解決,比如死亡時候bgm要暫停,或者是玩家自己暫停遊戲,bgm也要停止,這些都沒有時間去完善,再就是遊戲過於單一,沒有設計出遊戲難度的選擇,只有開始遊戲這一選項,需要日後學習完善。
以下是展示視訊