簡單的動畫 (numpy & PySimpleGUI)

Jason990420發表於2020-03-26

檔案建立日期: 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本身的定時, 來處理所有的物件的移更新, 看起來也是可以很快很自然的效果. 而且設計起來比多執行緒更簡單多, 而且容易除錯.

目標:

  1. 定點定時噴出氣泡(水珠)任意個.

  2. 每個氣泡都有自己的方向及速度, 為了便於處理, 定為X方向及Y方向的速度.

  3. 氣泡在過程中, 有三件事要處理

    • 受重力加速度影響, 會向下掉落
    • 落下的過程會逐漸分散, 因為分散效果不明顯, 改採用顏色趨向背景色
    • 氣泡的位置如果超出顯示區, 必須刪除, 否則氣泡物件會越來越多.

輸出畫面

輸出畫面

程式碼及說明

  1. 匯入相關的庫
import numpy as np
import PySimpleGUI as sg
import random
  1. 建立氣泡類

    • 顏色不再使用文字方式, 如 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)
  1. 氣泡生成
    • 初始位置都是固定的, 數目為 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))
  1. 氣泡移動

    • 方向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
  1. 更泡更新
    • 顏色的變化, 由原氣泡顏色, 依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)
  1. 檢查氣泡是否已出界
    • 使用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]
  1. 建立氣泡例項及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')
  1. 執行主程式
    • 內容很簡單 - 產生氣泡, 更新位置及速度, 檢查是否出界, 更新氣泡位置及顏色, 就這樣一直迴圈下去, 直到使用者關閉視窗.
    • 扣除空行, 全部只有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 協議》,轉載必須註明作者和本文連結
Jason Yang

相關文章