-
Notifications
You must be signed in to change notification settings - Fork 49
功能簡單的蛇
在這個章節會先撰寫一隻功能簡單的蛇,並製作一個場景讓蛇在裡面移動。
在開始製作遊戲核心前,先在 games
下建立新資料夾作為遊戲資料夾,命名為 snake
。在 snake
資料夾內新增檔案 __init__.py
,讓這個遊戲資料夾成為一個 python package,並新增一個資料夾 game
,作為存放遊戲程式碼的地方,也在裡面新增一個 __init__.py
檔案。剛建立好的遊戲資料夾檔案結構如下:
games/
└── snake/
├── game/
│ └── __init__.py
└── __init__.py
在遊戲程式碼資料夾下(games/snake/game
)新增一個檔案 gameobject.py
,準備用來定義遊戲物件。
設計蛇身為大小 10 x 10 的矩形,而蛇頭與蛇身有不同的顏色。建立一個蛇身的類別 SnakeBody
:
from pygame import Rect, Surface, draw
from pygame.sprite import Sprite
class SnakeBody(Sprite):
def __init__(self, init_pos):
super().__init__()
self.rect = Rect(init_pos[0], init_pos[1], 10, 10)
先匯入製作遊戲物件需要的類別進來。
SnakeBody
是遊戲中的可視物件,繼承自 Sprite
,注意要在 __init__
中呼叫父類別的 __init__
。__init__
可以指定蛇身的起始位置。屬性 rect
定義蛇身的位置還有大小。
create_surface()
用來定義蛇身的樣貌:
def create_surface(self, color):
width = self.rect.width
height = self.rect.height
surface = Surface((width, height))
surface.fill(color)
draw.line(surface, (0, 0, 0), (width - 1, 0), (width - 1, height - 1))
draw.line(surface, (0, 0, 0), (0, height - 1), (width - 1, height - 1))
self.image = surface
create_surface()
需要指定顏色,蛇身的 surface 會儲存在屬性 image
。為了讓蛇身之間有明顯的間隔,利用 pygame.draw.line()
在蛇身的 surface 的右邊與下邊各畫上一條黑線。要注意的是指定畫線的座標是 9 不是 10,以下圖為例:
粗黑線的範圍是 surface 的範圍,可以看到 surface 的像素座標範圍是 0 ~ 9,藍色區域是黑線實際畫出的位置。如果指定畫線座標為 10,則會畫到紅色區域,因而超出 surface 的範圍,即使有畫線也不會顯示出來。
再來加入可以直接取得 SnakeBody
物件位置的函式 pos()
:
@property
def pos(self):
return self.rect.topleft
pos()
會回傳 rect 的 topleft
座標,也就是該 SnakeBody
物件的位置(一個 rect 物件的 xy 座標是位在該物件的左上角)。pos()
上加的 @property
可以讓函式以屬性的方式使用(類似 getter),像是 snake_body.pos
。pos()
用途是幫助簡化取得位置的程式碼及增加可讀性,snake_body.pos
比起 snake_body.rect.topleft
容易理解。
另外也加入 pos
的 setter,可以直接指定 SnakeBody
的位置。要注意 pos
的 setter 要宣告在 pos
之後。
@pos.setter
def pos(self, value):
self.rect.topleft = value
先建立一個基本的蛇 Snake
,由一個蛇頭與三個蛇身組成,一開始方向朝下:
from collections import deque
class Snake:
def __init__(self):
self.head = SnakeBody((40, 40))
self.head.create_surface((31, 204, 42)) # Green
self.body_color = (255, 255, 255) # White
self.body = deque()
# Note the ordering of appending elements
self.body.append(SnakeBody((40, 30)))
self.body.append(SnakeBody((40, 20)))
self.body.append(SnakeBody((40, 10)))
for body in self.body:
body.create_surface(self.body_color)
蛇頭是綠色的蛇身,一開始在 (40, 40) 的位置,而蛇身是白色的,從蛇頭往上長。
蛇身用 deque
來管理,為了方便管理蛇身的順序。要注意將蛇身加入到 deque
的順序,距離蛇頭最近的先加入。deque
是 double-ended queue,也就是說元素可以從 queue 的兩端加入或移除,deque
也特別優化這項操作(使用 list
也可以,只是對於從 list
頭插入或移除元素會比 deque
慢)。deque
定義在 collections
套件中。
蛇的移動以下圖為例:
蛇身上的數字代表在 deque
中的順序,左圖是移動前,右圖是往下一步的樣貌。可以看到把最後一個元素放到 deque
的前端就可以達成蛇身順序的更新。至於被移動的那個蛇身位置會等於前一動的蛇頭位置,而蛇頭就繼續往下一步。在 Snake
定義 move()
來移動蛇身:
def move(self):
tail = self.body.pop()
tail.pos = self.head.pos
self.body.appendleft(tail)
self.head.rect.move_ip(0, 10)
pygame.Rect.move_ip()
會直接移動 rect 一定的距離,這邊先用來移動蛇頭的位置。(後面讓蛇可以走不同方向時,這段程式碼會被取代)
當然蛇的功能還不完全,只會往下走,不過功能會在後面慢慢補齊。這裡先將剛製作好的蛇放到場景中,看看效果。
場景功能為管理遊戲物件,像是設置物件位置、安排物件的更新順序。首先在遊戲程式碼資料夾(games/snake/game
)下新增一個 gamecore.py
檔案,用來定義場景 Scene
:
from pygame import Rect
from pygame.sprite import Group
from .gameobject import Snake
class Scene:
area_rect = Rect(0, 0, 300, 300)
除了 pygame 相關的類別之外,也要從 gameobject
匯入要使用的遊戲物件。
area_rect
是類別變數,利用 Rect
來定義場景的大小為 300 x 300。使用 Rect
是為了能夠以 width
或 height
屬性來取得場景大小的資訊,提高程式碼易讀性。如果要使用 tuple 來定義也可以,如 area_size = (300, 300)
。兩者差別以取得場景寬度為例:area_rect.width
與 area_size[0]
,前者比較容易辨識。
def __init__(self):
self._create_scene()
def _create_scene(self):
self._snake = Snake()
self._draw_group = Group()
self._draw_group.add(self._snake.head, *self._snake.body)
def draw_gameobjects(self, surface):
self._draw_group.draw(surface)
在 __init__()
中會呼叫 _create_scene()
來配置場景物件,而目前的場景物件只有蛇。在建立蛇後,把每個蛇身加到 pygame.sprite.Group
中,以便在 draw_gameobjects()
中一次把遊戲物件畫到傳入的畫布上。使用 add()
將蛇頭與每個蛇身加入到自己定義的 _draw_group
中。
當變數或函式的命名以 _
為開頭,則代表這個變數或函式是 private
的,也就是說希望這類變數或函式只在該類別中使用,如:_create_scene()
、_snake
。在 python 中可以透過 __
(雙底線)來達成真正的 private
,如:__create_scene()
。(詳見 python 文件)
def update(self):
self._snake.move()
update()
是用來更新場景的函式,每一次更新都要讓蛇移動。