如果在x軸上依序取一些點,然後算出在這些點上對應的sin函數的值,那麼,當我們把這些由x軸上的點以及其對應的sin函數的值所構成的二維座標點畫出來時,就可以看到由這個sin函數所產生的像波一樣的圖案,也就是波型(wave pattern)。不同樣式的波型,可以用來設計生物的軀幹或肢體,也可以用來模擬像水這類柔軟的表面。
在計算波型時,我們需要設定三個變數:
angle = 0
delta_angle = 0.2
amplitude = 25
接著利用for
迴圈,把所有要畫的點的座標位置算出來,然後畫上去。例如,在x軸上每隔24個像素取點,程式可以這樣寫:
for x in range(0, WIDTH, 24):
y = amplitude*math.sin(angle) + HEIGHT//2
pygame.draw.circle(screen, (0, 0, 0), (x, y), 12, 1)
angle += delta_angle
這裡的WIDTH
和HEIGHT
,分別是畫面的寬度和高度。另外,從這段程式的最後一行也可以看出來,delta_angle
的角色,其實就類似於角速度的角色。
下面這個例子,就是用上述的方式來畫出波型的。
Example 3.8: Static Wave
# python version 3.10.9
import math
import sys
import pygame # version 2.3.0
pygame.init()
pygame.display.set_caption("Example 3.8: Static Wave")
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
WIDTH, HEIGHT = screen_size = (640, 360)
screen = pygame.display.set_mode(screen_size)
FPS = 60
frame_rate = pygame.time.Clock()
delta_angle = 0.2
amplitude = 100
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill(WHITE)
angle = 0
for x in range(0, WIDTH, 24):
y = amplitude*math.sin(angle) + HEIGHT//2
pygame.draw.circle(screen, BLACK, (x, y), 24, 1)
angle += delta_angle
pygame.display.update()
frame_rate.tick(FPS)
不同的delta_angle
值對波型有什麼影響呢?下面這張圖,就是使用不同的delta_angle
值所畫出來的波型。
在描述波的時候,週期是指波運動一個完整的循環所需的時間;而「波長」(wavelength),則是指波運動一個完整的循環所行經的距離。從這張圖可以看出來,delta_angle
值越大,則所得到的波長會越短。
從圖中還可以觀察到一個現象,當波長越短時,因為圖形的變化越大,所以相鄰兩個圓圈越難以緊密地連接在一起,嚴重時,會讓圖形看起來像是中間斷掉一樣。要避免這樣子的問題,可以多取一些點,讓圓圈圈更密集一些;但是這種做法,很明顯的會增加很多計算量,電腦速度不夠快的話,在模擬時,很可能會出現畫面卡頓的現象。另一種比較好的做法,是把相鄰的兩個點用直線連接起來。這樣子做,額外增加的計算量會少很多,而且在pygame
中,也提供了draw.lines()
這個函數來做這件事,不需要自己寫。
上面的寫法所畫出來的圖是靜態的,也就是說,那個波完全不會動。那要怎麼讓它動呢?
要讓畫出來的波會動,做法其實挺簡單的。先前畫出來的波之所以不會動,是因為我們每次都從同樣的地方開始畫起。所以,每一幀畫面顯示出來的,都會是一模一樣的波,不僅形狀一樣,連位置都一樣,這樣子當然就看不到波動的效果。所以,要讓波動起來,關鍵就在每一次畫的時候,起始點都給它稍微變動一下,從不同的位置開始畫。要這麼做,可以設定一個值會慢慢增加的變數start_angle
,然後把原本的
angle = 0
改成
angle += start_angle
因為start_angle
的值會慢慢增加,所以每次畫的時候,就會從不同的起始點開始畫。值得一提的是,start_angle
改變的速度,會影響波動的速度,start_angle
改變的速度越快,波動的速度也會越快。因此,我們可以藉由調整start_angle
改變的速度,來調控波動的速度。
Example 3.9: The Wave
# python version 3.10.9
import math
import sys
import pygame # version 2.3.0
pygame.init()
pygame.display.set_caption("Example 3.9: The Wave")
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
WIDTH, HEIGHT = screen_size = (640, 360)
screen = pygame.display.set_mode(screen_size)
FPS = 60
frame_rate = pygame.time.Clock()
start_angle = 0
delta_angle = 0.23
amplitude = HEIGHT//2
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill(WHITE)
angle = start_angle
for x in range(0, WIDTH, 24):
y = amplitude*math.sin(angle) + HEIGHT//2
pygame.draw.circle(screen, BLACK, (x, y), 24, 1)
angle += delta_angle
start_angle += 0.02
pygame.display.update()
frame_rate.tick(FPS)
Exercise 3.10
# python version 3.10.9
import math
import sys
import noise # version 1.2.2
import pygame # version 2.3.0
pygame.init()
pygame.display.set_caption("Exercise 3.10")
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
WIDTH, HEIGHT = screen_size = (640, 360)
screen = pygame.display.set_mode(screen_size)
FPS = 60
frame_rate = pygame.time.Clock()
start_angle = 0
delta_angle = 0.03
amplitude = HEIGHT//2
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill(WHITE)
angle = start_angle
for x in range(0, WIDTH, 24):
y = amplitude*noise.pnoise1(angle) + HEIGHT//2
pygame.draw.circle(screen, BLACK, (x, y), 24, 1)
angle += delta_angle
start_angle += 0.02
pygame.display.update()
frame_rate.tick(FPS)
Exercise 3.11
class Wave:
def __init__(self, amplitude, delta_angle, wave_width, position):
# 取得顯示畫面
self.screen = pygame.display.get_surface()
self.amplitude = amplitude
self.delta_angle = delta_angle
self.wave_width = wave_width
self.position = position
self.start_angle = 0
self.body = []
def generate(self):
self.body = []
angle = self.start_angle
for x in range(0, self.wave_width, 6):
y = self.amplitude*math.sin(angle)
self.body.append((x+self.position[0], y+self.position[1]))
angle += self.delta_angle
self.start_angle += 0.02
def show(self):
x, y = self.position
x += self.wave_width//2
y -= 100
for i in range(len(self.body)):
pygame.draw.line(self.screen, (0, 0, 0), (x, y), self.body[i], 1)
# python version 3.10.9
import math
import sys
import pygame # version 2.3.0
pygame.init()
pygame.display.set_caption("Exercise 3.11")
WHITE = (255, 255, 255)
WIDTH, HEIGHT = screen_size = (640, 360)
screen = pygame.display.set_mode(screen_size)
FPS = 60
frame_rate = pygame.time.Clock()
delta_angle = 0.08
amplitude = 20
position = (75, 200)
wave_width = 100
wave1 = Wave(amplitude, delta_angle, wave_width, position)
delta_angle = 0.3
amplitude = 40
position = (275, 200)
wave_width = 300
wave2 = Wave(amplitude, delta_angle, wave_width, position)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill(WHITE)
wave1.generate()
wave1.show()
wave2.generate()
wave2.show()
pygame.display.update()
frame_rate.tick(FPS)
Exercise 3.12
# python version 3.10.9
import math
import sys
import pygame # version 2.3.0
pygame.init()
pygame.display.set_caption("Exercise 3.12")
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
WIDTH, HEIGHT = screen_size = (640, 360)
screen = pygame.display.set_mode(screen_size)
FPS = 60
frame_rate = pygame.time.Clock()
start_angle = 0
delta_angle = 0.2
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill(WHITE)
angle = start_angle
for x in range(0, WIDTH, 8):
y = 50*math.cos(angle) + 35*math.sin(1.5*angle) + HEIGHT//2
pygame.draw.circle(screen, BLACK, (x, y), 24, 1)
angle += delta_angle
start_angle += 0.02
pygame.display.update()
frame_rate.tick(FPS)