The Nature of Code閱讀心得與Python實作:2.4 Creating Forces

更新於 發佈於 閱讀時間約 26 分鐘

在真實世界中有各式各樣的作用力影響著我們,那在模擬世界中呢?要怎麼在本來無一物的模擬世界中,製造出作用力呢?

在真實世界中的作用力,可以透過物理定律的數學式來描述。不過可別誤會,並不是大自然利用數學式子來產生作用力,而是先有作用力,然後人們才找出可以描述這些作用力的數學式子。跟真實世界不同的是,在模擬世界中,本來是沒有什麼東西的,更不用說有什麼作用力存在。在模擬世界中的一切一切,都是我們創造出來的,所以,要想在模擬世界中創造出作用力,我們必須先設計好描述作用力的數學式子,然後利用這些式子來產生作用力。

有兩種方式可以用來設計描述虛擬世界作用力的數學式子,一種是自訂規則,完全依照自己的想法來設計,不管那數學式的長相如何,反正只要能呈現出我們所要的效果就可以。另一種方式,則是中規中矩,套用真實世界中的物理定律公式,而利用這種方式所製造出來的作用力,可以呈現出比較接近真實世界物理現象的效果。在這一節中,我們先來看看第一種方式要怎麼做,下一節再來看第二種方式。

不利用物理定律公式,而依照自己設定的規則來打造作用力,最簡單的方式,就是直接給一個數字。例如,要讓一股由左向右的風,吹在mover這個由Mover類別產生的物件上,程式可以這麼寫:

wind = pygame.Vector2(0.01, 0)
mover.apply_force(wind)

下面這個例子則多了點變化,只有在按下滑鼠左鍵時才有風,而且除了風之外,還加入向下的重力。

Example 2.1: Forces

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

# 物件的質量
self.mass = 1

# 物件的起始位置、初始速度、初始加速度
self.position = pygame.Vector2(30, 30)
self.velocity = pygame.Vector2(0, 0)
self.acceleration = pygame.Vector2(0, 0)

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

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

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 = self.width
self.velocity.x = -self.velocity.x
elif self.position.x < 0:
self.position.x = 0
self.velocity.x = -self.velocity.x

if self.position.y > self.height:
self.position.y = self.height
self.velocity.y = -self.velocity.y


# python version 3.10.9
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 2.1: Forces")

WHITE = (255, 255, 255)

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

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

# 方向向下的重力
gravity = pygame.Vector2(0, 0.1)

# 由左向右吹的風
wind = pygame.Vector2(0.1, 0)

mover = Mover()

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

screen.fill(WHITE)

# 施加重力
mover.apply_force(gravity)

# 按下滑鼠左鍵時才有風
if pygame.mouse.get_pressed()[0]:
mover.apply_force(wind)

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

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

這個例子有點單調,因為就只有一個形單影隻的mover而已。接下來,就來讓這個模擬世界熱鬧一些,讓許多大小不一有胖有瘦的mover,一起加入這個模擬世界。

要能夠很容易又快速地製造出許多胖瘦不一的mover,就不能把Mover類別裡頭的質量這個變數寫死,而要改成由參數傳遞數值來設定。另外,mover生出來的位置,也可以用這樣子的方式來設定,這樣就可以很容易地讓mover從不同的地方冒出來。修改後的Mover類別如下:

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

# 讓傳遞進來的數值來決定物體的質量
self.mass = mass

# 物體的質量越大,尺寸就會越大
self.size = 16*self.mass

# 讓傳遞進來的數值來決定物體的起始位置
self.position = pygame.Vector2(x, y)

# 物件的初始速度、初始加速度
self.velocity = pygame.Vector2(0, 0)
self.acceleration = pygame.Vector2(0, 0)

# 設定mover所在surface的格式為per-pixel alpha
self.surface = pygame.Surface((self.size, self.size), pygame.SRCALPHA)

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

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

def show(self):
# 使用具透明度的白色把mover所在的surface清空
self.surface.fill((255, 255, 255, 0))

# 畫出具有透明度的mover
radius = self.size/2
center = pygame.Vector2(radius, radius)
pygame.draw.circle(self.surface, (0, 0, 0, 50), center, radius)

# 把mover所在的surface貼到最後要顯示的畫面上
self.screen.blit(self.surface, self.position-center)

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

if self.position.y > self.height:
self.position.y = self.height
self.velocity.y = -self.velocity.y

當要製造一個質量10,起始位置為(20, 30)mover時,程式就可以這樣寫:

mover = Mover(20, 30, 10)

依樣畫葫蘆,我們就可以製造出許多大小不一,散佈在畫面上的mover。下面這個例子,就是用這樣子的方式造出一大一小兩個受重力和風力影響的mover

Example 2.2: Forces Acting on Two Objects

# python version 3.10.9
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 2.2: Forces Acting on Two Objects")

WHITE = (255, 255, 255)

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

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

# 方向向下的重力
gravity = pygame.Vector2(0, 0.1)

# 由左向右吹的風
wind = pygame.Vector2(0.1, 0)

moverA = Mover(200, 30, 5)
moverB = Mover(500, 30, 2)

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

screen.fill(WHITE)

# 施加重力
moverA.apply_force(gravity)
moverB.apply_force(gravity)

# 按下滑鼠左鍵時才有風
if pygame.mouse.get_pressed()[0]:
moverA.apply_force(wind)
moverB.apply_force(wind)

moverA.check_edges()
moverA.update()
moverA.show()

moverB.check_edges()
moverB.update()
moverB.show()

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

這個例子的寫法,是分別針對moverAmoverB來處理,所以控制mover的程式碼都寫了兩次。這樣子的寫法,當mover的數量一多時,就會變得非常繁雜無效率。比較好的做法是用listarray來處理,這會在後續的內容中介紹。

Exercise 2.3

物體越靠近邊緣,反推的力量越大,這股看不到的作用力大小,可以這樣子設定:

上方邊緣反推力 = 1 - 物體距上方邊緣的距離 / 畫面高度

下方邊緣反推力 = 1 - 物體距下方邊緣的距離 / 畫面高度

左側邊緣反推力 = 1 - 物體距左側邊緣的距離 / 畫面寬度

右側邊緣反推力 = 1 - 物體距右側邊緣的距離 / 畫面寬度

Mover這個類別新增一個distances_to_edges()方法,用來計算物體距視窗4個邊緣的距離:

def distances_to_edges(self):
distances = {}
distances["top"] = self.position.y
distances["bottom"] = self.height - self.position.y
distances["left"] = self.position.x
distances["right"] = self.width - self.position.x

利用上述設定反推力大小的公式,並考慮反推力的方向,即可算出4個邊緣作用在物體上的反推力。主程式如下:

# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 2.3")

WHITE = (255, 255, 255)

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

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

# x、y方向的單位向量
vec_x = pygame.Vector2(1, 0)
vec_y = pygame.Vector2(0, 1)

push_back_force = {}

mover = Mover(random.uniform(0, WIDTH), random.uniform(0, HEIGHT), 2)

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

screen.fill(WHITE)

distances = mover.distances_to_edges()

# 計算4個邊緣作用在mover上的反推力大小
push_back_force["top"] = (1-distances["top"]/HEIGHT)*vec_y
push_back_force["bottom"] = -(1-distances["bottom"]/HEIGHT)*vec_y
push_back_force["left"] = (1-distances["left"]/WIDTH)*vec_x
push_back_force["right"] = -(1-distances["right"]/WIDTH)*vec_x

# 將反推力作用在mover上
for f in push_back_force:
mover.apply_force(push_back_force[f])

mover.update()
mover.show()

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


Exercise 2.4

修改check_edges()即可。假設圓的半徑是radius

def check_edges(self):
if self.position.x > self.width - self.radius:
self.position.x = self.width - self.radius
self.velocity.x = -self.velocity.x
elif self.position.x < self.radius:
self.position.x = self.radius
self.velocity.x = -self.velocity.x

if self.position.y > self.height - self.radius:
self.position.y = self.height - self.radius
self.velocity.y = -self.velocity.y
elif self.position.y < self.radius:
self.position.y = self.radius
self.velocity.y = -self.velocity.y


Exercise 2.5

從滑鼠到mover位置的單位向量,就是風力的方向。算出風力的單位向量之後,就可以縮放成實際的風力大小。

wind = mover.position - pygame.Vector2(pygame.mouse.get_pos())
if wind.length() > 0:
    wind.scale_to_length(0.3)

執行Example 2.2時應該會發現,當力量作用時,小的那一個球反應會明顯比大的那一個快。之所以會如此,是因為在apply_force()這個方法中,計算加速度時,是用力量除以質量,所以質量越大的物體,算出來的加速度就會越小,因而速度的改變就越慢。對於風力而言,這樣子的結果挺合理的,質量越大的物體,當然越難推動;但是對於重力而言,就不是這麼回事了。

眾所周知,當不同質量的兩個物體從相同高度同時往下掉時,它們到達地面的時間是一樣的;這也就是說,它們往下掉的速度是一樣的。所以,照道理Example 2.2中的兩個球,它們往下掉的速度應該都一樣才對;可是畫面顯示出來的,顯然不是這樣。那問題出在哪裡呢?

我們在模擬的時候,是設定作用力,然後把作用力作用在物體上,進而算出加速度。所以,在相同的作用力下,根據牛頓第二運動定律,質量越大的物體加速度會越小。但是真實世界的實際狀況是,質量越大的物體,受到的重力作用會越大,而不是如我們程式所設定的,所有物體感受到的重力都一樣大。就因為這樣子的差異,所以造成模擬的結果跟實際狀況不一樣。

既然質量越大的物體受到的重力作用會越大,那在模擬時,該怎麼設定重力的大小呢?其實重力有個特性,那就是不同質量的物體在重力的作用下,都會有相同的加速度,這個相同的加速度,就是大家熟知的重力加速度。當然啦,這個結果是有許多假設前提的,不過一般的使用上,這些假設都不會導致什麼太大的差異,所以可以放心的使用。

知道不同質量的物體在重力的作用下,都會有相同的加速度這個特性後,重力大小的設定問題就水到渠成了。既然不管質量大小,重力加速度的值都一樣,那根據牛頓第二運動定律,作用在物體上的重力除以物體的質量所得到的值,一定會是個常數。所以,只要把要作用在物體上的作用力,給他乘上物體的質量,這樣當我們呼叫apply_force()計算加速度時,就會把物體的質量給消掉,讓它沒辦法作怪。

程式需修改的地方不多,就只需讓作用在mover上的重力,是gravity乘上mover的質量就可以了。

Example 2.3: Gravity Scaled by Mass

# python version 3.10.9
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 2.3: Gravity Scaled by Mass")

WHITE = (255, 255, 255)

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

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

# 方向向下的重力
gravity = pygame.Vector2(0, 0.1)

# 由左向右吹的風
wind = pygame.Vector2(0.1, 0)

moverA = Mover(200, 30, 5)
moverB = Mover(500, 30, 2)

# 修正作用在mover上的重力
gravityA = gravity*moverA.mass
gravityB = gravity*moverB.mass

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

screen.fill(WHITE)

# 施加重力
moverA.apply_force(gravityA)
moverB.apply_force(gravityB)

# 按下滑鼠左鍵時才有風
if pygame.mouse.get_pressed()[0]:
moverA.apply_force(wind)
moverB.apply_force(wind)

moverA.check_edges()
moverA.update()
moverA.show()

moverB.check_edges()
moverB.update()
moverB.show()

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

修改過後,大、小兩個球向下掉的速度都已經變成一樣了。不過,比較小的球橫向移動的速度仍然會比較快,這是因為我們並沒有去改wind,所以作用在大、小兩個球上的風力都一樣大,質量比較小的球自然跑得比較快。

其實原書這個例子的寫法很容易讓人混淆,因為在真實世界中,這樣的重力作用方式,根本就不存在。在真實世界中,物體感受到的重力,也就是其重量,等於其質量乘上重力加速度,而且重力加速度是定值。既然作用在moverAmoverB上的重力同樣都是gravity,而且重力加速度都一樣,那moverAmoverB一定會有相同的質量;這個結論和程式中moverAmoverB有不同質量的設定,是矛盾的。所以,以真實世界的重力作用方式來看,程式中的gravity,其實是重力加速度;而gravityAgravityB,才是作用在moverAmoverB上的重力。

雖然這個例子的設定方式並未遵守真實世界的物理定律,但畢竟模擬世界是我們所創造的,我們愛怎麼設定就怎麼設定,那本來就是個可以不遵守真實世界物理定律的地方。不過,話又說回來,既然要讓模擬世界呈現出真實世界中的物理現象,那套用真實世界物理定律的公式,應該會是比較好的做法。


avatar-img
15會員
131內容數
寫點東西自娛娛人
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
ysf的沙龍 的其他內容
到目前為止,為了簡化問題,我們都假設物體的質量是1。接下來,我們將移除這個假設,然後將完全符合牛頓第二運動定律的apply_force()方法,整合到Mover這個類別中。
這一節要來看看,有許多個力同時作用時,該怎麼處理。
這一節談的是牛頓的三大運動定律,以及力對於物體運動狀態的影響。
這一章介紹的是力(force),以及力與加速度間的關係。
介紹以物件導向的方式,以向量來實作物體運動的模擬程式。
介紹如何在模擬物體運動時,引入加速度這個物理量。
到目前為止,為了簡化問題,我們都假設物體的質量是1。接下來,我們將移除這個假設,然後將完全符合牛頓第二運動定律的apply_force()方法,整合到Mover這個類別中。
這一節要來看看,有許多個力同時作用時,該怎麼處理。
這一節談的是牛頓的三大運動定律,以及力對於物體運動狀態的影響。
這一章介紹的是力(force),以及力與加速度間的關係。
介紹以物件導向的方式,以向量來實作物體運動的模擬程式。
介紹如何在模擬物體運動時,引入加速度這個物理量。
你可能也想看
Google News 追蹤
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
這篇內容,將會講解什麼是變數,以及與變數相關的知識。包括變數、資料型態、變數賦值、變數的命名規則、變數的作用區域、變數的可重複性、內建變數。
魔導書也許只在異世界有用,因為異世界的物理法則和我們這世界不同。
Thumbnail
何謂真實世界? 一般人眼中世界的構成,除了每天接收到的片面外來訊息,加上實體接觸的物質元素,還有就是個體本身的內在慾望了。 我只聽我想聽的,我只看我想看的,我只相信我認為的,這就是專屬於每個人的真實 。 在眾多個體的真實之上,說不定有著一個大架構的劇本在運轉,真正的真實就在這個劇本之外,在我們
Thumbnail
在舊世界劇本,我們所認為的腳踏實地是認為去接受生命中的各種限制,但當我們真的開始通透顯化的本質,了解其實我們在舊世界劇本的限制不存在的時候,很多人會開始飄。對魔法開始有錯誤的想像,以為施法只需要空想就好了。 在新世界,人生如戲、創造自己的人生劇本並不是一個比喻。在跟大家一起修練魔法的過程中,我
Thumbnail
【城獸】、【蒼海一生笑滔滔湧暗潮】、【一本小說的虛構不存在型態】
Thumbnail
顯化教練分享了改變內在狀態的重要性,以及如何透過顯化法則創造想要的現實。透過控制內在世界,會影響外在現實。文章中強調了外在世界只是內在狀態的反映,並提供了一系列的採取行動和創造新故事的方法。這篇文章可以幫助讀者更瞭解顯化法則和內在狀態的影響。
Thumbnail
人性是自然的賦予,生而公正平等。 學習了解自己創造力模式,明白如何創造自己的世界,進而有能力經營自己滿意的世界。 法則一、存在即是創造力表現的偉大結果。 法則二、能量佔據空間時形成物質。 法則三、能量佔據時間時形成事件。
Thumbnail
1. 凡所有相皆是虛妄,若見諸相非相,即見如來 2. 能量看不到,卻統籌物理世界(形而上統籌形而下) 3. 數學與物理的不同:數學「定理」:絕對真理,不因時空轉換;物理「定律」:找到自然背後的律,而非證明 4. 數學的本質:建立在不能再問的「公理」上 5. 歐式平
面對了一個假設 卻也只是個假設 裝載了不可思議的幻想 加入誰才可以實現 如果一切成真 那些念頭 要努力的想要讓自己快樂 從黑變成白的超能力 攤開手掌 空無一物 卻也擁有了世界 像是這樣的話語 掌握了腦海 沒有比對想像中的快樂 只是比想像
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
這篇內容,將會講解什麼是變數,以及與變數相關的知識。包括變數、資料型態、變數賦值、變數的命名規則、變數的作用區域、變數的可重複性、內建變數。
魔導書也許只在異世界有用,因為異世界的物理法則和我們這世界不同。
Thumbnail
何謂真實世界? 一般人眼中世界的構成,除了每天接收到的片面外來訊息,加上實體接觸的物質元素,還有就是個體本身的內在慾望了。 我只聽我想聽的,我只看我想看的,我只相信我認為的,這就是專屬於每個人的真實 。 在眾多個體的真實之上,說不定有著一個大架構的劇本在運轉,真正的真實就在這個劇本之外,在我們
Thumbnail
在舊世界劇本,我們所認為的腳踏實地是認為去接受生命中的各種限制,但當我們真的開始通透顯化的本質,了解其實我們在舊世界劇本的限制不存在的時候,很多人會開始飄。對魔法開始有錯誤的想像,以為施法只需要空想就好了。 在新世界,人生如戲、創造自己的人生劇本並不是一個比喻。在跟大家一起修練魔法的過程中,我
Thumbnail
【城獸】、【蒼海一生笑滔滔湧暗潮】、【一本小說的虛構不存在型態】
Thumbnail
顯化教練分享了改變內在狀態的重要性,以及如何透過顯化法則創造想要的現實。透過控制內在世界,會影響外在現實。文章中強調了外在世界只是內在狀態的反映,並提供了一系列的採取行動和創造新故事的方法。這篇文章可以幫助讀者更瞭解顯化法則和內在狀態的影響。
Thumbnail
人性是自然的賦予,生而公正平等。 學習了解自己創造力模式,明白如何創造自己的世界,進而有能力經營自己滿意的世界。 法則一、存在即是創造力表現的偉大結果。 法則二、能量佔據空間時形成物質。 法則三、能量佔據時間時形成事件。
Thumbnail
1. 凡所有相皆是虛妄,若見諸相非相,即見如來 2. 能量看不到,卻統籌物理世界(形而上統籌形而下) 3. 數學與物理的不同:數學「定理」:絕對真理,不因時空轉換;物理「定律」:找到自然背後的律,而非證明 4. 數學的本質:建立在不能再問的「公理」上 5. 歐式平
面對了一個假設 卻也只是個假設 裝載了不可思議的幻想 加入誰才可以實現 如果一切成真 那些念頭 要努力的想要讓自己快樂 從黑變成白的超能力 攤開手掌 空無一物 卻也擁有了世界 像是這樣的話語 掌握了腦海 沒有比對想像中的快樂 只是比想像