Skip to content

蛇吃食物

LanKuDot edited this page Jun 15, 2020 · 4 revisions

貪食蛇遊戲中不能沒有食物,本章節會在場景中加入食物,並在蛇吃到食物後讓蛇成長。

食物 - Food

食物的大小一樣是 10 x 10 像素,是一個紅色的圓形。在 gameobject.py 中加入 Food 類別:

class Food(Sprite):
    def __init__(self):
        super().__init__()

        self.rect = Rect(0, 0, 10, 10)

        surface = Surface(self.rect.size)
        draw.circle(surface, (232, 54, 42), self.rect.center, 5)

        self.image = surface

    @property
    def pos(self):
        return self.rect.topleft

    @pos.setter
    def pos(self, value):
        self.rect.topleft = value

Food 一樣是要被繪製到畫面上的物件,繼承自 pygame.sprite.Sprite,在 __init__() 中一定要呼叫父類別的 __init__()。一樣有兩個屬性:

  • rect:指定為 10 x 10 像素大小,而座標會另外決定,先指定為 (0, 0)。
  • image:這裡使用 rect 的 size 屬性來指定 surface 的大小,size 即為 (width, height)。利用 pygame.draw.circle()surface 上畫出紅色圓形,最後存到 image 中。

Food 也提供 pos 屬性來直接取得與設置物件的位置。

在場景中加入 Food

接著到 gamecore.pyScene 類別中加入 Food 的物件:

from .gameobject import Snake, Food

class Scene:
    ...

    def _create_scene(self):
        self._snake = Snake()
        self._food = Food()

        self._draw_group = Group()
        self._draw_group.add(self._snake.head, *self._snake.body, self._food)

這邊只產生一個 Food 物件,在遊戲中會一直重複使用它(不會因為被吃掉而消失),並加入到 _draw_group 中。

決定食物的位置

貪食蛇遊戲中,食物的位置通常是隨機決定的,該位置不與蛇的位置重疊。

輔助函式

一開始在 gameobject.pySnake 類別加入兩個輔助函式,一個是取得蛇頭的位置,另一個是檢查指定的位置上有沒有蛇身:

class Snake:
    ...

    @property
    def head_pos(self):
        return self.head.pos

    def is_body_pos(self, position):
        """
        Check if there has a snake body at the given position
        """
        for body in self.body:
            if body.pos == position:
                return True

        return False

head_pos() 是個屬性函式,會回傳蛇頭的位置。而 is_body_pos() 中透過 for ... in ... 逐一取得蛇身,藉此檢查蛇身的位置有無與 position 指定的位置相同。不在 is_body_pos() 中一同檢查蛇頭的位置,是為了之後可以用來檢查蛇頭是否撞到蛇身,如果放在一起,那傳入蛇頭的位置就一定會回傳 True

隨機決定位置

接著,在 gamecore.py 中的 Scene 類別中加入隨機決定食物位置的函式 _random_food_pos()

import random

class Scene:
    ...

    def _random_food_pos(self):
        """
        Randomly set the position of the food
        """
        while True:
            candidate_pos = (
                random.randrange(0, Scene.area_rect.width, 10),
                random.randrange(0, Scene.area_rect.height, 10))

            if (candidate_pos != self._snake.head_pos and
                not self._snake.is_body_pos(candidate_pos)):
                break

        self._food.pos = candidate_pos

隨機位置是用 random.randrange 來決定的,指定的三個參數為:數字起始點、數字終點(但不包含)、級距,例如:randrange(0, 10, 2) 會隨機挑選 0, 2, 4, 6, 8 其中一個數字。分別使用 Scene.area_rect.widthScene.area_rect.height 來取得位置的邊界。不難發現遊戲的物件與移動都是以 10 為單位,因此指定的級距即為 10。產生的隨機位置是一個 (x, y) tuple,並存到 candidate_pos

接著檢查 candidate_pos 是否沒有在蛇頭或是任意蛇身上,如果有,就重新生成新的位置,否則就結束生成迴圈。最後將食物的位置設成決定的位置。

而一開始在建立 Food 物件的時候還沒決定它的位置,所以這邊要補上:

    def _create_scene(self):
        ...
        self._food = Food()
        self._random_food_pos()
        ...

食物被吃到後換位置

當蛇吃到食物之後就要決定下一個食物的位置。判定蛇吃到食物可以透過比較兩者的位置是否相同來達成,在 update() 中增加這個功能:

    def update(self, action):
        self._snake.move(action)

        if self._snake.head_pos == self._food.pos:
            self._random_food_pos()

蛇成長

蛇吃到食物後,要讓蛇成長一格。在貪食蛇遊戲中,在吃到食物的當下還不會生長,而是在移動下一步時往前生長,也就是前後兩步的尾巴位置並沒有改變,如下圖的例子:

Imgur

因此需在程式中達成這樣的效果。

更新遊戲物件 Snake

gameobject.py 中的 Snake 類別中新增函式 grow()

    def grow(self):
        """
        Add a new snake body
        """
        new_body = SnakeBody(self.body[-1].pos, self.body_color)
        self.body.append(new_body)

        return new_body

新的蛇身位置跟蛇尾的位置相同的原因在下面更新場景中會一同解釋。回傳的新身體是為了給場景加到 _draw_group 中。

更新場景

gamecore.py 中的場景也要配合更新,在食物被吃掉後,要讓蛇的身體成長,並把新身體加入到 _draw_group 中:

class Scene:
    ...

    def update(self, action):
        self._snake.move(action)

        if self._snake.head_pos = self._food.pos:
            self._random_food_pos()
            new_body = self._snake.grow()
            self._draw_group.add(new_body)

可以看到每一影格是先移動蛇再檢查有沒有吃到食物,所以在蛇吃到食物成長的當下,新的蛇身其實先「藏」在蛇尾後面,等到下一幀移動蛇時,新的蛇身就會被移動到蛇頭的位置,如此一來前後兩個影格的蛇尾位置就不會變動,看起來就像蛇往前長了:

Imgur

左邊是新蛇身生成時的位置,右圖是移動後的樣子。

記得把新的蛇身加到 _draw_group 中,蛇身才會被繪製出來。

執行遊戲

執行遊戲看看成果:

Imgur