简单的动画 (numpy & PySimpleGUI)

文件建立日期: 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
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!