檔案建立日期: 2020/03/26
最後修訂日期: None
相關軟體資訊:
Windows 10 | Python 3.7.6 | PySimpleGUI 4.16.0 | Numpy 1.18.2 | PIL/Pillow 7.1.1 |
說明: 本文請隨意引用或更改, 只須標示出處及作者, 作者不保證內容絶對正確無誤, 如造成任何後果, 請自行負責.
標題: 簡單的動畫 (numpy & PySimpleGUI)
以前總是使用多執行緒來達到多物件的運動處理, 其實在很多的GUI中都有定時處理的設計, 因此, 在不使用多執行緒的情況下, 試著利用GUI
本身的定時, 來處理所有的物件的移更新, 看起來也是可以很快很自然的效果. 而且設計起來比多執行緒更簡單多, 而且容易除錯.
目標:
定點定時噴出氣泡(水珠)任意個.
每個氣泡都有自己的方向及速度, 為了便於處理, 定為X方向及Y方向的速度.
氣泡在過程中, 有三件事要處理
- 受重力加速度影響, 會向下掉落
- 落下的過程會逐漸分散, 因為分散效果不明顯, 改採用顏色趨向背景色
- 氣泡的位置如果超出顯示區, 必須刪除, 否則氣泡物件會越來越多.
輸出畫面
程式碼及說明
- 匯入相關的庫
import numpy as np
import PySimpleGUI as sg
import random
建立氣泡類
顏色不再使用文字方式, 如
yellow
,green
, 因為顏色要在運動的過程中改變, 所改採RGB
的方式#RRGGBB
來定義.一開始要為氣泡建立空的陣列, 每個氣泡都有5個屬性
影像ID - 畫在畫布上的物件
X , Y 氣泡生成時的被初始位置座標
V_x, V_y 氣泡生成時的被初始速度, 兩者構成其運動方向
class Bubble():
def __init__(self):
self.width, self.height = self.size = (301, 501)
self.x0, self.y0 = self.origin = self.width//2, self.height-50
self.number = 10 # 10 bubbles generate each time
self.range = 30
self.time = 10
self.rate = self.time/40
self.color = '#FFFFFF'
self.bg = '#0080FF'
self.radius = 3
self.bubbles = np.empty((0, 5), dtype=np.float)
- 氣泡生成
- 初始位置都是固定的, 數目為
self.number
- 速度由隨機數成, 就會有不同方向, 不同速度的氣泡
- 依序畫上氣泡, 並存下其影像
id
, 供後更新或刪除使用.
- 初始位置都是固定的, 數目為
def create(self):
new_bubbles = np.hstack(
(np.full((self.number, 1), 0.0),
np.full((self.number, 1), self.x0),
np.full((self.number, 1), self.y0),
(np.random.rand(self.number, 1)-0.5)*self.range,
(np.random.rand(self.number, 1)-0.5)*self.range
)).astype(np.float)
for bubble in new_bubbles:
color = self.color
bubble[0] = draw.DrawCircle((bubble[1], bubble[2]),
self.radius, fill_color=color, line_color=color)
self.bubbles = np.vstack((self.bubbles, new_bubbles))
氣泡移動
方向X的速度不變, 其位置 X_{n+1} = X_n + V_x * dt
方向Y的速度受重力影響
位置 Y_{n+1} = Y_n - V_y * dt + g*t^2/2, 這裡的V_y為了簡單起見, 只取常數
速度 V_{n+1} = V_n-g*dt
這些式子有可能再簡化,例如時間差就為1,重力加速度也為1,那計算的式子就更簡單了。
def change(self):
self.bubbles[:, 1] += self.bubbles[:, 3]*self.rate
self.bubbles[:, 2] += self.bubbles[:, 4]*self.rate - 9.8*self.rate**2/2
self.bubbles[:, 4] -= 9.8*self.rate
- 更泡更新
- 顏色的變化, 由原氣泡顏色, 依y軸的位置來比例更新成背景的顏色.
- 由於
PySimpleGUI
不能直接更新影像的顏色, 所以先刪除原位置的影像, 再於新位置重新畫上影像.
def update(self):
b = 255
color = self.color
for bubble in self.bubbles:
r = min(int(255 - 255*(self.y0-bubble[2])/self.y0), 255)
g = min(int(255 - 128*(self.y0-bubble[2])/self.y0), 255)
color = "#%02x%02x%02x" % (r, g, b)
draw.DeleteFigure(int(bubble[0]))
bubble[0] = draw.DrawCircle((bubble[1], bubble[2]),
self.radius, fill_color=color, line_color=color)
- 檢查氣泡是否已出界
- 使用
Numpy
, 依據氣泡的位置, 確認是否出界, 建立boolean
索引. - 根據索引匯出要刪除的影像, 以及更新仍存在氣泡列表
- 使用
def check(self):
filter = np.logical_and(
np.logical_and(self.bubbles[:,1]>0, self.bubbles[:,1]<self.width),
np.logical_and(self.bubbles[:,2]>0, self.bubbles[:,2]<self.height))
discard = self.bubbles[np.logical_not(filter)]
for key in discard[:, 0]:
draw.DeleteFigure(int(key))
self.bubbles = self.bubbles[filter]
建立氣泡例項及GUI介面
GUI
介面很簡單, 就是一張畫布.
B = Bubble()
layout = [[sg.Graph(B.size, background_color=B.bg, key='Graph', pad=(0, 0),
graph_bottom_left=(0, 0), graph_top_right=B.size)]]
window = sg.Window('氣球', layout=layout, finalize=True, margins=(0, 0))
draw = window.find_element('Graph')
- 執行主程式
- 內容很簡單 - 產生氣泡, 更新位置及速度, 檢查是否出界, 更新氣泡位置及顏色, 就這樣一直迴圈下去, 直到使用者關閉視窗.
- 扣除空行, 全部只有
65
行.
B.create()
while True:
event, values = window.read(timeout=B.time)
if event == None:
break
B.change()
B.check()
B.update()
B.create()
window.close()
本作品採用《CC 協議》,轉載必須註明作者和本文連結