The Nature of Code閱讀心得與Python實作:4.2 A Single Particle

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

在開始真正處理粒子系統之前,得先寫個用來描述單一粒子的類別。這個類別,就把它叫做Particle

對我們而言,一個粒子就是一個在畫面上移動的獨立物體,它有位置、速度、加速度。所以,在Particle這個類別中,應該要有用來紀錄粒子的位置、速度、加速度等資訊的變數,以及更新這些資訊的方法。當然啦,把粒子顯示到畫面上的方法,也是一定要有的。這些林林總總的變數、方法,在先前第二章所建立的Mover類別中都有;既然如此,那就把Mover類別當成建造Particle類別的模板,稍微改一改,並加入需要的新功能就可以了。

有些粒子系統會利用發射器(emitter)來產生粒子,並藉以設定粒子的初始狀態,讓粒子系統可以呈現出不同的模擬效果。這也是這章在設計粒子系統時,所採用的方式。

粒子系統的粒子有個不同於mover的特點,那就是粒子會出生和死亡:當粒子從發射器生出來之後,最終會因為某個事件而消失不見。這裡的「某個事件」,可以是碰到其他物體、跑出畫面,或者是已經存在夠長的時間等。為了簡化起見,在我們的Particle類別中,會先只根據粒子存在的時間長短,來決定粒子是否消失。

粒子存在時間的長短,其實就是粒子的壽命。要讓由Particle類別所製造出來的粒子有壽命,最直接的做法就是利用計數器,當計數器的數值低於某個門檻值時,就代表那個粒子該消失了。要達到這個目的,可以設定一個變數並給個初始值,然後當更新粒子的狀態時,也同時更新變數內的數值。例如,設定

lifespan = 255

當更新粒子的狀態時,執行

lifespan -= 2

就這樣不斷地倒數計時下去,直到lifespan低於門檻值時,就可以判定粒子的壽命到了,該消失了。

綜合上面的討論,Particle類別可以設計成這樣:

class Particle:
def __init__(self, x, y, mass):
# 取得顯示畫面
self.screen = pygame.display.get_surface()

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

# particle的質量越大,尺寸就會越大
self.size = 16*self.mass
self.radius = self.size/2

# particle的壽命
self.lifespan = 255

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

# 讓particle有隨機的初始速度
self.velocity = pygame.Vector2(random.uniform(-1, 1), random.uniform(-2, 0))

# particle的初始加速度
self.acceleration = pygame.Vector2(0, 0)

# 設定particle所在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

self.lifespan -= 2

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

# 畫出具有透明度的particle,並根據particle剩下的壽命長短來決定透明度
color = (0, 0, 0, self.lifespan)
center = (self.radius, self.radius)
pygame.draw.circle(self.surface, color, center, self.radius)

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

def is_dead(self):
return self.lifespan < 0

在這裡,我們用lifespan裡頭的數值來決定粒子的透明度,數值越小越透明;這樣隨著時間過去,粒子就會逐漸淡出畫面,最後完全看不見,就像是消失了一樣。

新加入的is_dead()方法,就是用來判斷粒子是不是壽命已到,該消失了。

下面這個例子,會在畫面上產生一個受重力作用而往下掉的粒子。隨著時間過去,粒子的顏色會越來越淡,最後消失不見。當粒子消失之後,會再產生一個新的粒子,繼續重複這個過程。

Example 4.1: A Single Particle

# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 4.1: A Single Particle")

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)

particle = Particle(320, 50, 1)

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

screen.fill(WHITE)

particle.apply_force(gravity)

particle.update()
if particle.is_dead():
# 原來的粒子壽終正寢之後,生個新的粒子出來取代它
particle = Particle(320, 50, 1)
else:
particle.show()

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

Exercise 4.1

def run(self, force):
self.apply_force(force)
self.update()
self.show()

主程式部分,把

particle.apply_force(gravity)
particle.update()
particle.show()

改成

particle.run(gravity)

這樣子寫,除了主程式可以比較簡短之外,看不出來有什麼其他的好處;嚴格來說,這樣寫反而會帶來一些問題。其中一個不那麼嚴重的問題是,因為把三個方法綁在一起,當需要處理更多樣化的作用力時,會因為比較沒有彈性而綁手綁腳的。

除了比較沒彈性的問題之外,上述寫法另一個比較嚴重的問題,是可能會把已經壽終正寢應該移除的粒子,給顯示在畫面上。怎麼說呢?依照原書在的做法,當更新粒子狀態之後,緊接著就會把粒子顯示在畫面上,然後再移除壽命已到的粒子。這也就是說,即便粒子在更新狀態之後的lifespan值已經低於設定好的門檻值,它還是會被顯示在畫面上,然後才被移除;這樣子的處理順序,顯然是有問題的。

既然原書的寫法有上述的問題,所以在寫Example 4.1時,就採用不同的寫法來加以避免。什麼樣的寫法呢?其實也就是把處理的順序稍微調整一下而已:在更新粒子狀態之後,就把壽命已到的粒子移除,然後再把還活著的粒子顯示在畫面上。依照這樣子的順序來處理,就不會把壽命已到的粒子顯示出來了。

Exercise 4.2

class Particle:
def __init__(self, x, y, mass):
# 取得顯示畫面
self.screen = pygame.display.get_surface()

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

# particle的質量越大,尺寸就會越大
self.size = 16*self.mass
self.radius = self.size/2

# particle的壽命
self.lifespan = 255

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

# 讓particle有隨機的初始速度
self.velocity = pygame.Vector2(random.uniform(-1, 1), random.uniform(-2, 0))

# particle的初始加速度
self.acceleration = pygame.Vector2(0, 0)

# particle的角速度,單位是「度」。向右飛時順時針旋轉;向左飛時逆時針旋轉
self.angular_velocity = -10*self.velocity.x

# particle的初始角度,單位是「度」
self.angle_deg = 0

# 設定particle所在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

self.lifespan -= 2

self.angle_deg += self.angular_velocity

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

# 畫出具有透明度的particle,並根據particle剩下的壽命長短來決定透明度
rect = pygame.Rect(0, 0, self.size, self.size)
color = (0, 0, 0, self.lifespan)
pygame.draw.rect(self.surface, color, rect)

# 旋轉角度的單位需由弳度轉換為度
rotated_surface = pygame.transform.rotate(self.surface, self.angle_deg)
rect_new = rotated_surface.get_rect(center=self.position)

# 把particle所在的surface貼到最後要顯示的畫面上
self.screen.blit(rotated_surface, rect_new)

def is_dead(self):
return self.lifespan < 0


# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 4.2")

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.05)

particle = Particle(320, 50, 1)

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

screen.fill(WHITE)

particle.apply_force(gravity)

particle.update()
if particle.is_dead():
# 原來的粒子壽終正寢之後,生個新的粒子出來取代它
particle = Particle(320, 50, 1)
else:
particle.show()

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


留言
avatar-img
留言分享你的想法!
avatar-img
ysf的沙龍
18會員
150內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
2024/10/25
粒子系統可以用來製作視覺特效(visual effect, VFX),而粒子外觀的呈現方式,以及粒子具有怎樣的紋理(texture),都會影響特效所展現出來的效果。本節介紹如何利用不同紋理的粒子圖片,以不同的混色模式(blending mode),透過粒子系統來製作模擬煙霧的特效。
Thumbnail
2024/10/25
粒子系統可以用來製作視覺特效(visual effect, VFX),而粒子外觀的呈現方式,以及粒子具有怎樣的紋理(texture),都會影響特效所展現出來的效果。本節介紹如何利用不同紋理的粒子圖片,以不同的混色模式(blending mode),透過粒子系統來製作模擬煙霧的特效。
Thumbnail
2024/10/21
在第二章模擬萬有引力時,曾經利用它來設計會吸引物體的吸子(attractor)。現在,如果想要在模擬粒子系統時,加入會排斥物體的斥子(repeller),那要怎麼做呢?
Thumbnail
2024/10/21
在第二章模擬萬有引力時,曾經利用它來設計會吸引物體的吸子(attractor)。現在,如果想要在模擬粒子系統時,加入會排斥物體的斥子(repeller),那要怎麼做呢?
Thumbnail
2024/10/18
到目前為止,我們所設計的粒子系統,都只有受到重力的作用。那如果作用力是風力呢?又該怎麼設計?
2024/10/18
到目前為止,我們所設計的粒子系統,都只有受到重力的作用。那如果作用力是風力呢?又該怎麼設計?
看更多
你可能也想看
Thumbnail
家中修繕或裝潢想要找各種小零件時,直接上網採買可以省去不少煩惱~看看Sylvia這回為了工地買了些什麼吧~
Thumbnail
家中修繕或裝潢想要找各種小零件時,直接上網採買可以省去不少煩惱~看看Sylvia這回為了工地買了些什麼吧~
Thumbnail
👜簡單生活,從整理包包開始!我的三款愛用包+隨身小物清單開箱,一起來看看我每天都帶些什麼吧🌿✨
Thumbnail
👜簡單生活,從整理包包開始!我的三款愛用包+隨身小物清單開箱,一起來看看我每天都帶些什麼吧🌿✨
Thumbnail
創作者營運專員/經理(Operations Specialist/Manager)將負責對平台成長及收入至關重要的 Partnership 夥伴創作者開發及營運。你將發揮對知識與內容變現、影響力變現的精準判斷力,找到你心中的潛力新星或有聲量的中大型創作者加入 vocus。
Thumbnail
創作者營運專員/經理(Operations Specialist/Manager)將負責對平台成長及收入至關重要的 Partnership 夥伴創作者開發及營運。你將發揮對知識與內容變現、影響力變現的精準判斷力,找到你心中的潛力新星或有聲量的中大型創作者加入 vocus。
Thumbnail
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
Thumbnail
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
Thumbnail
在物理的領域裏 不變的物質有著恆常的定律 於是乎 月球繞著地球轉 地球繞著太陽轉 太陽繞著銀河系   在化學的領域裏 物質隨著原子們的排列組合 形成了 形色多變銀河系 各有千秋太陽系 繽紛美麗的地球   在數學的領域裏 數字的跳躍翻轉變化萬千中 綜言是 正負平方開根
Thumbnail
在物理的領域裏 不變的物質有著恆常的定律 於是乎 月球繞著地球轉 地球繞著太陽轉 太陽繞著銀河系   在化學的領域裏 物質隨著原子們的排列組合 形成了 形色多變銀河系 各有千秋太陽系 繽紛美麗的地球   在數學的領域裏 數字的跳躍翻轉變化萬千中 綜言是 正負平方開根
Thumbnail
這一節談的是向量的定義,以及如何運用向量來建立模擬物體運動時,關於位置和速度間的關係式。
Thumbnail
這一節談的是向量的定義,以及如何運用向量來建立模擬物體運動時,關於位置和速度間的關係式。
Thumbnail
1.0 從函數到函算語法 1.2 函數概念小史 1.2.1 中譯的來源 1.2.2 一個速度問題 1.2.3 幾何的方法 1.2.4 微積分的記法  二 前面說過,牛頓關心的不是抽象的數學問題,他要解決的是天體運動的問題。他知道,假如他擁有該天體在任何一刻的瞬速數據,他便能夠從質量
Thumbnail
1.0 從函數到函算語法 1.2 函數概念小史 1.2.1 中譯的來源 1.2.2 一個速度問題 1.2.3 幾何的方法 1.2.4 微積分的記法  二 前面說過,牛頓關心的不是抽象的數學問題,他要解決的是天體運動的問題。他知道,假如他擁有該天體在任何一刻的瞬速數據,他便能夠從質量
Thumbnail
直觀理解 導數:考慮的是單一變數的函數,描述的是函數在某點的斜率或變化率。 偏導數:考慮的是多變數函數,描述的是函數在某個變數變化時的變化率,其他變數保持不變。  (針對各維度的調整 或者稱變化 你要調多少) 應用 導數:在物理學中應用廣泛,例如描述速度和加速度。 偏導數:在多變量分析、優
Thumbnail
直觀理解 導數:考慮的是單一變數的函數,描述的是函數在某點的斜率或變化率。 偏導數:考慮的是多變數函數,描述的是函數在某個變數變化時的變化率,其他變數保持不變。  (針對各維度的調整 或者稱變化 你要調多少) 應用 導數:在物理學中應用廣泛,例如描述速度和加速度。 偏導數:在多變量分析、優
Thumbnail
大語言模型(如GPT-3和GPT-4)的出現改變了我們與機器互動的方式。這些模型能夠理解和生成自然語言,實現許多以前無法想像的應用。然而,你可能會好奇,這些模型究竟是如何理解語言的?這裡,我們來探討一個關鍵的概念:「一切語義都是關係」。
Thumbnail
大語言模型(如GPT-3和GPT-4)的出現改變了我們與機器互動的方式。這些模型能夠理解和生成自然語言,實現許多以前無法想像的應用。然而,你可能會好奇,這些模型究竟是如何理解語言的?這裡,我們來探討一個關鍵的概念:「一切語義都是關係」。
Thumbnail
1. 凡所有相皆是虛妄,若見諸相非相,即見如來 2. 能量看不到,卻統籌物理世界(形而上統籌形而下) 3. 數學與物理的不同:數學「定理」:絕對真理,不因時空轉換;物理「定律」:找到自然背後的律,而非證明 4. 數學的本質:建立在不能再問的「公理」上 5. 歐式平
Thumbnail
1. 凡所有相皆是虛妄,若見諸相非相,即見如來 2. 能量看不到,卻統籌物理世界(形而上統籌形而下) 3. 數學與物理的不同:數學「定理」:絕對真理,不因時空轉換;物理「定律」:找到自然背後的律,而非證明 4. 數學的本質:建立在不能再問的「公理」上 5. 歐式平
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News