The Nature of Code閱讀心得與Python實作:2.2 Force Accumulation

更新 發佈閱讀 11 分鐘

這一節要來看看,有許多個力同時作用時,該怎麼處理。

如果想要讓windgravity這兩個力同時作用在mover上,程式可以這樣寫

mover.apply_force(wind+gravity)

這樣子的寫法,其實就是先算出合力,然後讓合力作用在mover上。

另一種處理合力的方法,是將apply_force()改寫為

def apply_force(self, force):
    self.acceleration += force

這樣當每次呼叫apply_force()時,新的作用力就會累加進去,而不是取代掉前面的作用力。所以,上一節程式的寫法

mover.apply_force(wind)
mover.apply_force(gravity)

就不會發生windgravity取代掉而沒有作用的情形,而可以達到讓windgravity同時作用在mover上的目的。

處理完合力的問題之後,還有個地方必須要處理,否則mover將會感受到永不停止的作用力。

假設當我們按下滑鼠左鍵時,會有一陣風吹向mover,而當放開滑鼠左鍵時,風就停止。寫成程式就是

if pygame.mouse.get_pressed()[0]:
    mover.apply_force(wind)

放開滑鼠左鍵後,因為已經沒有風了,所以沒有力量作用在mover上,它的加速度會是0,而根據牛頓第一運動定律,mover會維持等速運動。但是,當我們呼叫update()來計算mover的速度和位置時,因為update()的寫法是

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

這時候acceleration的值,還是維持著呼叫apply_force()時所得到的值;即便來自wind的作用力早就不存在了。這顯然不是我們所要的結果,我們要的是當合力為0時,acceleration的值是0。那程式該怎麼寫呢?其實挺簡單的,在update()的最後,讓acceleration的值歸零就可以了:

def update(self):
    self.velocity += self.acceleration
    self.position += self.velocity
    self.acceleration *= 0

這樣子,對於acceleration來說,每一幀畫面都是新的開始,不會留有從上一幀畫面而來的記憶,在現在這一幀畫面中有多大的作用力,mover的加速度就會有多大的值,完全符合牛頓運動定律。

在模擬時,有個東西很重要:時間步長(time step)。時間步長一般記做dt,指的是「delta time」、「時間的變化量」的意思,它的大小會影響模擬的準確度;這也就是為什麼許多物理引擎會把時間步長當成一個可以調整的參數,讓使用者可以調整其大小,來得到更好的模擬結果。

既然時間步長這麼重要,那它究竟是什麼東西,又為什麼會影響模擬的準確度呢?原書在這裡並沒有詳細解釋,而只用「the rate at which the simulation updates」這樣一句話來說明什麼是時間步長。單憑這句話就想搞清楚什麼是時間步長,實在是挺困難的。下面就試著用一個例子來解釋什麼是時間步長。透過這個例子,應該就能比較清楚的知道,原書那句關於時間步長的說明,到底是在說些什麼。

假設我們要模擬一顆被揮棒打擊出去的棒球的運動軌跡,因為我們是使用電腦進行模擬,而電腦每次只能針對某一個時間點,計算出當時棒球的各項運動數據。所以,整個模擬的過程,就是一個時間點接著一個時間點,計算出棒球的運動數據,直到模擬結束為止;而這一個一個時間點之間的間隔,就是時間步長。很明顯的,如果我們要模擬某段時間內棒球的運動軌跡,時間點之間的間隔,也就是時間步長,如果越小,那電腦必須計算並更新數據的時間點數量就會越多;反之,如果時間步長越大,電腦必須計算並更新數據的時間點數量就會越少。原書關於時間步長的那句話,說的其實就是這個。

知道了時間步長到底是什麼之後,接下來就來看看,它的大小會如何影響模擬的準確度。還是用棒球的運動軌跡那個例子來說明。假設我們想知道棒球在落地前飛了多久,那我們會看看,在哪個時間點棒球還在飛,而在下一個時間點則在地上滾,這樣就可以知道棒球至少飛了多少時間。例如,假設時間步長設定為1秒,而棒球在第5秒鐘時還在飛,但在第6秒鐘時則在地上滾,這時就可以知道,棒球的飛行時間介於5~6秒鐘之間。所以,時間步長設定為1秒,我們所得到的棒球飛行時間,誤差範圍會在1秒鐘內。同樣的道理,如果時間步長設定為0.1秒,那我們應該可以得到誤差範圍在0.1秒內的棒球飛行時間。這也就是說,時間步長越小,模擬的準確度會越高。

既然時間步長越小,模擬的準確度越高,那是不是不管三七二十一,就把時間步長設定得非常非常小就好了?這樣一來,不就不用擔心準確度不夠了嗎?理論上是如此啦!但是,你有那麼多時間、有那麼雄厚的資本嗎?時間步長從1秒改成0.1秒,需要計算的時間點數量會變成10倍。電腦模擬需要時間,計算量越大,需要的時間會越長;換台速度快一點的電腦可以縮短模擬的時間,但這也意味著要從口袋掏出更多的錢出來。

總之,時間步長不管太大或太小都有缺點,剛剛好就好。但是多大才是剛剛好呢?這沒有標準答案,只能視情況而定;不過為了能夠專注於瞭解在模擬時所用到的主要原理,一直到第六章介紹物理引擎之前,我們都會假設,主程式中跑完一次while迴圈,就相當於一個時間步長。

Exercise 2.1

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

# 氣球半徑
self.size = 16

# 物件的起始位置、初始速度、初始加速度
self.position = pygame.Vector2(self.width//2, self.height-self.size)
self.velocity = pygame.Vector2(0, 0)
self.acceleration = pygame.Vector2(0, 0)

def apply_force(self, force):
self.acceleration += force

def update(self):
self.velocity += self.acceleration
self.position += self.velocity
self.acceleration *= 0

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

def check_edges(self):
# 非彈性碰撞恢復係數。氣球撞壁後,依據此係數來降低移動速率
restitution = 0.5

if self.position.x > self.width:
self.position.x = self.width
self.velocity.x = -restitution*self.velocity.x
elif self.position.x < 0:
self.position.x = 0
self.velocity.x = -restitution*self.velocity.x

if self.position.y < 0:
self.position.y = 0
self.velocity.y = -restitution*self.velocity.y


# python version 3.10.9
import random
import sys

import noise # version 1.2.2
import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 2.1")

WHITE = (255, 255, 255)

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

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

balloon = Balloon()

# 浮力
buoyancy = pygame.Vector2(0, -0.01)

# 最大風力強度
maximum_intensity = 0.03

# Perlin noise的參數起始值
xoff, yoff = 0, 10000

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

screen.fill(WHITE)

# 依循Perlin noise變化的風力
xoff, yoff = xoff+0.01, yoff+0.01
wind = maximum_intensity*pygame.Vector2(noise.pnoise1(xoff), noise.pnoise1(yoff))

balloon.apply_force(buoyancy)
balloon.apply_force(wind)

balloon.update()
balloon.check_edges()
balloon.show()

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





留言
avatar-img
留言分享你的想法!
avatar-img
ysf的沙龍
19會員
157內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
2024/08/12
到目前為止,我們所模擬的萬有引力,是一個物體吸引另一個物體,或者是一個物體吸引多個物體。然而,在真實世界中,每個物體都會互相吸引,所以在這一節中,就來把模擬的情境,擴展成多個物體互相吸引。
2024/08/12
到目前為止,我們所模擬的萬有引力,是一個物體吸引另一個物體,或者是一個物體吸引多個物體。然而,在真實世界中,每個物體都會互相吸引,所以在這一節中,就來把模擬的情境,擴展成多個物體互相吸引。
2024/08/09
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
Thumbnail
2024/08/09
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
Thumbnail
2024/08/02
在真實世界中有各式各樣的作用力影響著我們,那在模擬世界中呢?要怎麼在本來無一物的模擬世界中,製造出作用力呢?
2024/08/02
在真實世界中有各式各樣的作用力影響著我們,那在模擬世界中呢?要怎麼在本來無一物的模擬世界中,製造出作用力呢?
看更多
你可能也想看
Thumbnail
透過蝦皮分潤計畫,輕鬆賺取零用金!本文分享5-6月實測心得,包含數據流程、實際收入、平臺優點及注意事項,並推薦高分潤商品,教你如何運用空閒時間創造被動收入。
Thumbnail
透過蝦皮分潤計畫,輕鬆賺取零用金!本文分享5-6月實測心得,包含數據流程、實際收入、平臺優點及注意事項,並推薦高分潤商品,教你如何運用空閒時間創造被動收入。
Thumbnail
單身的人有些會養寵物,而我養植物。畢竟寵物離世會傷心,植物沒養好再接再厲就好了~(笑)
Thumbnail
單身的人有些會養寵物,而我養植物。畢竟寵物離世會傷心,植物沒養好再接再厲就好了~(笑)
Thumbnail
不知你有沒有過這種經驗?衛生紙只剩最後一包、洗衣精倒不出來,或電池突然沒電。這次一次補貨,從電池、衛生紙到洗衣精,還順便分享使用心得。更棒的是,搭配蝦皮分潤計畫,愛用品不僅自己用得安心,分享給朋友還能賺回饋。立即使用推薦碼 X5Q344E,輕鬆上手,隨時隨地賺取分潤!
Thumbnail
不知你有沒有過這種經驗?衛生紙只剩最後一包、洗衣精倒不出來,或電池突然沒電。這次一次補貨,從電池、衛生紙到洗衣精,還順便分享使用心得。更棒的是,搭配蝦皮分潤計畫,愛用品不僅自己用得安心,分享給朋友還能賺回饋。立即使用推薦碼 X5Q344E,輕鬆上手,隨時隨地賺取分潤!
Thumbnail
身為一個典型的社畜,上班時間被會議、進度、KPI 塞得滿滿,下班後只想要找一個能夠安靜喘口氣的小角落。對我來說,畫畫就是那個屬於自己的小樹洞。無論是胡亂塗鴉,還是慢慢描繪喜歡的插畫人物,那個專注在筆觸和色彩的過程,就像在幫心靈按摩一樣,讓緊繃的神經慢慢鬆開。
Thumbnail
身為一個典型的社畜,上班時間被會議、進度、KPI 塞得滿滿,下班後只想要找一個能夠安靜喘口氣的小角落。對我來說,畫畫就是那個屬於自己的小樹洞。無論是胡亂塗鴉,還是慢慢描繪喜歡的插畫人物,那個專注在筆觸和色彩的過程,就像在幫心靈按摩一樣,讓緊繃的神經慢慢鬆開。
Thumbnail
中學數學基礎練習—分數乘法
Thumbnail
中學數學基礎練習—分數乘法
Thumbnail
中學數學基礎練習—一元二次方程式
Thumbnail
中學數學基礎練習—一元二次方程式
Thumbnail
中學數學基礎練習—一元二次方程式
Thumbnail
中學數學基礎練習—一元二次方程式
Thumbnail
Python語法包括條件語句、迴圈、函數和變數的使用。條件語句如if、elif和else用於進行條件判斷,for和while是兩種主要的迴圈,def用於定義函數。變數可以被賦予數字或字符串,並可使用類型提示來指定變數的類型。註解可以是單行或多行,並可用於解釋函數或類的用途和作用。
Thumbnail
Python語法包括條件語句、迴圈、函數和變數的使用。條件語句如if、elif和else用於進行條件判斷,for和while是兩種主要的迴圈,def用於定義函數。變數可以被賦予數字或字符串,並可使用類型提示來指定變數的類型。註解可以是單行或多行,並可用於解釋函數或類的用途和作用。
Thumbnail
每天 用三分力氣  打起精神  好好吃飯 用三分力氣  學習新的知識和技術
Thumbnail
每天 用三分力氣  打起精神  好好吃飯 用三分力氣  學習新的知識和技術
Thumbnail
Thumbnail
Thumbnail
編輯的基本功,是對文字的敏感度。
Thumbnail
編輯的基本功,是對文字的敏感度。
Thumbnail
蜜柑公子為大家分享這個「動態反思解說技巧」,希望可以幫助大家在生活中運用。
Thumbnail
蜜柑公子為大家分享這個「動態反思解說技巧」,希望可以幫助大家在生活中運用。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News