2024-07-16|閱讀時間 ‧ 約 30 分鐘

The Nature of Code閱讀心得與Python實作:1.7 Motion with Vectors

在Example 1.2中,我們利用向量來寫模擬物體運動的程式。在程式中,我們先算出球的位置,然後在那個位置畫出球:

position += velocity
pygame.draw.circle(screen, BLACK, position, RADIUS)

這就是模擬物體運動的最基本做法,這個做法,可以歸納為如下的演算法:

Motion 101
1. 將位置向量加上速度以得到新的位置向量
2. 在新位置畫出物體

題外話。看到101這個數字,大概可以猜到代表的意思。不過,實在很好奇,為什麼是101,不是100或123?網上搜尋了一下,在維基百科:101 (number)找到了答案。

言歸正傳。在那個例子中,是把Motion 101相關的程式直接寫在主程式中,但這並不是個好做法。比較好的做法,是把這部分的程式封裝在一個類別中,以發揮物件導向設計的優勢。接下來,就來設計一個叫做Mover的類別,把這部分的程式封裝在其中。

在設計Mover這個類別時,必須考慮兩個問題:

  1. Mover的物件裡頭包含了哪些資料?
  2. Mover的物件具有哪些功能?

這兩個問題的答案就在Motion 101這個演算法中。

一個Mover的物件,應該含有兩份資料:位置及速度,也就是positionvelocity這兩個向量。至於它應具備的功能,其實也很簡單,就是移動及現身被看見。我們會把移動相關的程式碼寫在update()這個方法中;而現身被看見,其實也就是在畫面上畫出物件樣貌,這部分的程式碼,則寫在show()這個方法中。程式碼如下:

def update(self):
    self.position += self.velocity

def show(self):
    pygame.draw.circle(self.screen, (0, 0, 0), self.position, 24)

在建立Mover的物件時,需要先設定,也就是初始化(initialize)的東西,如起始位置、初始速度等,就寫在__init__()中:

def __init__(self):
# 取得顯示畫面的大小
self.screen = pygame.display.get_surface()
self.width, self.height = self.screen.get_size()

# 設定物件的起始位置
x = random.uniform(0, self.width)
y = random.uniform(0, self.height)
self.position = pygame.Vector2(x, y)

# 設定物件的初始速度
vx = random.uniform(-2, 2)
vy = random.uniform(-2, 2)
self.velocity = pygame.Vector2(vx, vy)

最後,我們要決定物件碰到邊界時,會怎麼反應。假設我們希望碰到邊界時,物件會繞一圈從另一邊跑出來,程式的寫法如下:

def check_edges(self):
if self.position.x > self.width:
self.position.x = 0
elif self.position.x < 0:
self.position.x = self.width

if self.position.y > self.height:
self.position.y = 0
elif self.position.y < 0:
self.position.y = self.height

這樣就完成了Mover這個類別的設計。

下面的例子,就是使用Mover這個類別產生一個物件mover,然後讓這個mover在畫面上移動。程式如下:

Example 1.7: Motion 101 (Velocity)

class Mover:
def __init__(self):
# 取得顯示畫面的大小
self.screen = pygame.display.get_surface()
self.width, self.height = self.screen.get_size()

# 設定物件的起始位置
x = random.uniform(0, self.width)
y = random.uniform(0, self.height)
self.position = pygame.Vector2(x, y)

# 設定物件的初始速度
vx = random.uniform(-2, 2)
vy = random.uniform(-2, 2)
self.velocity = pygame.Vector2(vx, vy)

def update(self):
self.position += self.velocity

def show(self):
pygame.draw.circle(self.screen, (0, 0, 0), self.position, 24)

def check_edges(self):
if self.position.x > self.width:
self.position.x = 0
elif self.position.x < 0:
self.position.x = self.width

if self.position.y > self.height:
self.position.y = 0
elif self.position.y < 0:
self.position.y = self.height


# python version 3.10.9
import random
import sys

import pygame # version 2.3.0

pygame.init()

pygame.display.set_caption("Example 1.7: Motion 101 (Velocity)")

WHITE = (255, 255, 255)

screen_size = (640, 360)
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

mover = Mover()

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

mover.update()
mover.check_edges()
mover.show()

pygame.display.update()
frame_rate.tick(FPS)

Motion 101這個例子其實挺單調的,因為那顆球永遠都是氣定神閒地以相同的速度移動著,既不會加速,也不會減速。想要讓球的移動方式更多變、更真實一些,就必須引入加速度(acceleration)這個大家耳熟能詳的物理量。


分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.