2024-10-07|閱讀時間 ‧ 約 0 分鐘

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

到目前為止,我們已經建立了描述單一粒子的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

修改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)


分享至
成為作者繼續創作的動力吧!
從 Google News 追蹤更多 vocus 的最新精選內容從 Google News 追蹤更多 vocus 的最新精選內容

作者的相關文章

ysf的沙龍 的其他內容

你可能也想看

發表回應

成為會員 後即可發表留言
© 2024 vocus All rights reserved.