-
Notifications
You must be signed in to change notification settings - Fork 49
蛇吃食物
貪食蛇遊戲中不能沒有食物,本章節會在場景中加入食物,並在蛇吃到食物後讓蛇成長。
食物的大小一樣是 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
屬性來直接取得與設置物件的位置。
接著到 gamecore.py
的 Scene
類別中加入 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.py
的 Snake
類別加入兩個輔助函式,一個是取得蛇頭的位置,另一個是檢查指定的位置上有沒有蛇身:
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.width
與 Scene.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()
蛇吃到食物後,要讓蛇成長一格。在貪食蛇遊戲中,在吃到食物的當下還不會生長,而是在移動下一步時往前生長,也就是前後兩步的尾巴位置並沒有改變,如下圖的例子:
因此需在程式中達成這樣的效果。
在 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)
可以看到每一影格是先移動蛇再檢查有沒有吃到食物,所以在蛇吃到食物成長的當下,新的蛇身其實先「藏」在蛇尾後面,等到下一幀移動蛇時,新的蛇身就會被移動到蛇頭的位置,如此一來前後兩個影格的蛇尾位置就不會變動,看起來就像蛇往前長了:
左邊是新蛇身生成時的位置,右圖是移動後的樣子。
記得把新的蛇身加到 _draw_group
中,蛇身才會被繪製出來。
執行遊戲看看成果: