The Nature of Code閱讀心得與Python實作:4.4 A Particle Emitter

閱讀時間約 20 分鐘

到目前為止,我們已經建立了描述單一粒子的Particle類別,並且能夠利用list這個資料結構,來處理由Particle類別產生的大量粒子物件。在這一節,我們要把這一些整合起來,建立一個Emitter類別。有了這個類別,就可以讓主程式寫起來長成這個樣子:

emitter = Emitter()

while True:
:
:
emitter.run()

除此之外,也可以讓我們能夠比較輕鬆地一次處理多個由許多粒子構成的系統。

這個Emitter類別,就是在4.1節提到的ParticleSystem類別,但加入了一個新功能,用來設定產生粒子的位置。所以,這個Emitter類別,其實也就是4.2節提到的粒子的發射器。Emitter類別的程式碼如下:

class Emitter:
def __init__(self, x, y, mass):
self.mass = mass
self.size = 16*self.mass

self.particles = []

# 發射器位置
self.origin = pygame.Vector2(x, y)

def add_particle(self):
self.particles.append(Particle(self.origin.x, self.origin.y, self.mass))

def run(self):
for particle in self.particles:
particle.apply_force(gravity)
particle.update()

# 移除壽命已到的粒子
self.particles = list(filter(lambda particle: not particle.is_dead(), self.particles))

for particle in self.particles:
particle.show()

要注意的是,在run()方法裡頭,作用在粒子上的重力gravity,是個全域變數;這樣子的寫法顯然不怎麼好。不過,在目前的架構下,好像也只能這樣寫了。

下面這個例子,就是利用Emitter類別,在畫面的指定位置,不斷地噴發出粒子。

Example 4.3: A Single Particle Emitter

# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

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

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)

emitter = Emitter(320, 50, 1)

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

screen.fill(WHITE)

emitter.add_particle()
emitter.run()

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

Exercise 4.3

從滑鼠游標發射粒子。

# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 4.3")

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)

x, y = pygame.mouse.get_pos()
emitter = Emitter(x, y, 1)

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

screen.fill(WHITE)

x, y = pygame.mouse.get_pos()
emitter.origin = pygame.Vector2(x, y)

emitter.add_particle()
emitter.run()

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

Exercise 4.4

raw-image

修改Spaceship類別。完整的Spaceship類別程式碼以及主程式如下:

class Spaceship:
def __init__(self, mass=1, heading=90):
# 取得顯示畫面及其大小
self.screen = pygame.display.get_surface()
self.width, self.height = self.screen.get_size()

self.mass = mass

# 航向,單位是「度」
self.heading = heading # in degree

# 起始位置在畫面中央
x, y = self.width//2, self.height//2
self.position = pygame.Vector2(x, y)

# 最高速率
self.top_speed = 5

# 起始速度、加速度
self.velocity = pygame.Vector2(0, 0)
self.acceleration = pygame.Vector2(0, 0)

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

# 在surface上畫出spaceship
body = [(35, 15), (5, 0), (5, 30)]
pygame.draw.polygon(self.surface, (0, 0, 0), body)

nozzle1 = pygame.Rect(0, 5, 5, 5)
pygame.draw.rect(self.surface, (0, 0, 0), nozzle1)

nozzle2 = pygame.Rect(0, 20, 5, 5)
pygame.draw.rect(self.surface, (0, 0, 0), nozzle2)

# 噴嘴發射器位置
nozzle1_location = pygame.Vector2(0, 5+5/2)
nozzle2_location = pygame.Vector2(0, 20+5/2)

# 建造發射器
emitter1 = Emitter(nozzle1_location.x, nozzle1_location.y, 0.3)
emitter2 = Emitter(nozzle2_location.x, nozzle2_location.y, 0.3)
self.emitters = [emitter1, emitter2]

# 中心點至噴嘴發射器向量,用於計算在不同航向時,發射器在顯示畫面上之位置
ship_center = pygame.Vector2(35/2, 30/2)
nozzle1_vec = nozzle1_location - ship_center
nozzle2_vec = nozzle2_location - ship_center
self.nozzle_vecs = [nozzle1_vec, nozzle2_vec]

self.thrusting = False

def set_heading(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.heading += 5
elif keys[pygame.K_RIGHT]:
self.heading -= 5

def generate_thrust(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_z]:
self.thrusting = True
thrust = pygame.Vector2.from_polar((1, self.heading))
thrust.scale_to_length(0.1)
thrust.y = -thrust.y
else:
self.thrusting = False
# 沒有按下z鍵,減速到停下為止
if spaceship.velocity.length() > 1e-5:
thrust = -spaceship.velocity
thrust.scale_to_length(0.05)
else:
thrust = pygame.Vector2(0, 0)

return thrust

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

def update(self):
self.velocity += self.acceleration
if self.velocity.length() < 1e-5:
# 速度很小時,直接判定為靜止狀態
self.velocity *= 0
else:
# 限速
if self.velocity.length() > self.top_speed:
self.velocity.scale_to_length(self.top_speed)

self.position += self.velocity

self.acceleration *= 0

def show(self):
rotated_surface = pygame.transform.rotate(self.surface, self.heading)
rect_new = rotated_surface.get_rect(center=self.position)

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

if self.thrusting:
emitter_direction = pygame.Vector2.from_polar((1, self.heading+180))
emitter_direction.y = -emitter_direction.y

# 計算發射器在顯示畫面上之位置,並裝填及設定粒子之速度、壽命
for i in range(2):
self.emitters[i].origin = self.position + self.nozzle_vecs[i].rotate(-self.heading)
self.emitters[i].add_particle()
self.emitters[i].particles[-1].velocity = 0.5*emitter_direction
self.emitters[i].particles[-1].lifespan = 100

for emitter in self.emitters:
emitter.run()

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("Exercise 4.4")

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)

spaceship = Spaceship()

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

screen.fill(WHITE)

spaceship.set_heading()

thrust = spaceship.generate_thrust()
spaceship.apply_force(thrust)

spaceship.update()
spaceship.check_edges()
spaceship.show()

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


15會員
128內容數
寫點東西自娛娛人
留言0
查看全部
發表第一個留言支持創作者!
ysf的沙龍 的其他內容
有了描述個別粒子的Particle類別之後,這一節就來看看要怎麼做,才能同時掌握許多粒子的動向,特別是這些粒子的數量是隨時都在變動的。
在開始真正處理粒子系統之前,得先寫個用來描述單一粒子的類別。這個類別,就把它叫做Particle。
之所以要研究粒子系統,除了可以用來模擬許多自然界中的現象之外,另一個更重要的原因是:在我們的模擬世界中,會有許多物體存在,而這些物體可能會形成一群一群的群體
粒子系統(particle system)指的是,由許多微小粒子組成,呈現出模糊外觀的物體。這一章的重點會放在探討利用物件導向技術實作粒子系統時,該採用什麼樣的程式架構、描述個別粒子和整個系統的資料該如何管理等方面。
這一節要模擬的是擺(pendulum)這個裝置中,構造最簡單、具有理想化性質的單擺(simple pendulum)。
我們曾經利用sin函數來模擬彈簧吊錘(bob)的運動,雖然這樣子的做法程式很容易寫,但是卻沒辦法模擬彈簧吊錘受到如風力、重力等環境中其他作用力的影響下,在空間中的運動狀況。要克服這樣子的問題,就不能再倚靠sin函數,而必須改用能夠用來計算彈簧彈力的虎克定律(Hooke's law)。
有了描述個別粒子的Particle類別之後,這一節就來看看要怎麼做,才能同時掌握許多粒子的動向,特別是這些粒子的數量是隨時都在變動的。
在開始真正處理粒子系統之前,得先寫個用來描述單一粒子的類別。這個類別,就把它叫做Particle。
之所以要研究粒子系統,除了可以用來模擬許多自然界中的現象之外,另一個更重要的原因是:在我們的模擬世界中,會有許多物體存在,而這些物體可能會形成一群一群的群體
粒子系統(particle system)指的是,由許多微小粒子組成,呈現出模糊外觀的物體。這一章的重點會放在探討利用物件導向技術實作粒子系統時,該採用什麼樣的程式架構、描述個別粒子和整個系統的資料該如何管理等方面。
這一節要模擬的是擺(pendulum)這個裝置中,構造最簡單、具有理想化性質的單擺(simple pendulum)。
我們曾經利用sin函數來模擬彈簧吊錘(bob)的運動,雖然這樣子的做法程式很容易寫,但是卻沒辦法模擬彈簧吊錘受到如風力、重力等環境中其他作用力的影響下,在空間中的運動狀況。要克服這樣子的問題,就不能再倚靠sin函數,而必須改用能夠用來計算彈簧彈力的虎克定律(Hooke's law)。
你可能也想看
Google News 追蹤
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
上兩篇有關List的文章,此篇文上兩章的延續,整理一些常用的方法和操作。 [Python]List(列表)新增、修改、刪除元素 [Python基礎]容器 list(列表),tuple(元組) 還有一些常用的 list 方法和操作,讓你能更靈活地處理列表數據
Thumbnail
打開 jupyter notebook 寫一段 python 程式,可以完成五花八門的工作,這是玩程式最簡便的方式,其中可以獲得很多快樂,在現今這種資訊發達的時代,幾乎沒有門檻,只要願意,人人可享用。 下一步,希望程式可以隨時待命聽我吩咐,不想每次都要開電腦,啟動開發環境,只為完成一個重複性高
在Python中, 要寫一個完整的「符元化類 Tokenizer Class」, 這個Class需要的功能有: 1.「編碼 Encode」:將「文本 Text」分割成「符元 Token」。 2.「詞彙 Vocabulary」:將「符元 Token」映射到「符元ID TokenID
Thumbnail
一切從"物件(Object)"開始 1.建立新物件 2.編輯物件內容 3.在物件中新增區塊
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
本章節旨在介紹JavaScript中的物件導向編程。內容包括類別(Class)的定義和使用,建構子的作用,以及公開,私有,受保護(Protected)等不同訪問修飾符的概念。此外,還涵蓋了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型、反射等物件導向的主要觀念。
Thumbnail
前幾篇討論到各種裝飾器的用法,本文將介紹另外一種裝飾器,可以將方法轉換成屬性來使用。 property也可以動態的取出物件的值,隨著時間或其他運算改變所產生的值,讓我們繼續往下看更多介紹吧。
Thumbnail
題目敘述 題目會給我們一組定義好的界面和需求,要求我們設計一個資料結構,可以滿足平均O(1)的插入元素、刪除元素、隨機取得元素的操作。 RandomizedSet() 類別建構子 bool insert(int val) 插入元素的function界面 bool remove(int val
Thumbnail
本文讓我們來淺談一下類別是什麼? 若想看詳細一點的python官方教學可點此連結 Python 的類別(Class)是一種面向物件導向程式設計的概念,讓你能夠創建具有屬性和方法的物件。類別是對現實世界中事物的抽象,它包含數據和操作這些數據的方法。它非常的抽象,想像一個類別就像是一個蛋糕模具,
Thumbnail
Python 產生器(Generator)是一種特殊的迭代器,能夠以更有效率的方式處理大量數據。本文將介紹產生器的基礎概念、使用方法,並提供實際應用範例
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
上兩篇有關List的文章,此篇文上兩章的延續,整理一些常用的方法和操作。 [Python]List(列表)新增、修改、刪除元素 [Python基礎]容器 list(列表),tuple(元組) 還有一些常用的 list 方法和操作,讓你能更靈活地處理列表數據
Thumbnail
打開 jupyter notebook 寫一段 python 程式,可以完成五花八門的工作,這是玩程式最簡便的方式,其中可以獲得很多快樂,在現今這種資訊發達的時代,幾乎沒有門檻,只要願意,人人可享用。 下一步,希望程式可以隨時待命聽我吩咐,不想每次都要開電腦,啟動開發環境,只為完成一個重複性高
在Python中, 要寫一個完整的「符元化類 Tokenizer Class」, 這個Class需要的功能有: 1.「編碼 Encode」:將「文本 Text」分割成「符元 Token」。 2.「詞彙 Vocabulary」:將「符元 Token」映射到「符元ID TokenID
Thumbnail
一切從"物件(Object)"開始 1.建立新物件 2.編輯物件內容 3.在物件中新增區塊
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
本章節旨在介紹JavaScript中的物件導向編程。內容包括類別(Class)的定義和使用,建構子的作用,以及公開,私有,受保護(Protected)等不同訪問修飾符的概念。此外,還涵蓋了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型、反射等物件導向的主要觀念。
Thumbnail
前幾篇討論到各種裝飾器的用法,本文將介紹另外一種裝飾器,可以將方法轉換成屬性來使用。 property也可以動態的取出物件的值,隨著時間或其他運算改變所產生的值,讓我們繼續往下看更多介紹吧。
Thumbnail
題目敘述 題目會給我們一組定義好的界面和需求,要求我們設計一個資料結構,可以滿足平均O(1)的插入元素、刪除元素、隨機取得元素的操作。 RandomizedSet() 類別建構子 bool insert(int val) 插入元素的function界面 bool remove(int val
Thumbnail
本文讓我們來淺談一下類別是什麼? 若想看詳細一點的python官方教學可點此連結 Python 的類別(Class)是一種面向物件導向程式設計的概念,讓你能夠創建具有屬性和方法的物件。類別是對現實世界中事物的抽象,它包含數據和操作這些數據的方法。它非常的抽象,想像一個類別就像是一個蛋糕模具,
Thumbnail
Python 產生器(Generator)是一種特殊的迭代器,能夠以更有效率的方式處理大量數據。本文將介紹產生器的基礎概念、使用方法,並提供實際應用範例