# VPython進階教學:按鈕
> 作者:王一哲
> 第1版:2018/7/23
> 第2版:2021/8/22
<br />
由於我們之前做的動畫,都是在按在 F5 後自動開始執行,如果我們想要在動畫中新增按鈕,讓使用者可以自己控制動畫,應該要怎麼做呢?我們希望按鈕的功能有:
1. **執行**:按下時開始執行動畫,按鈕上的文字變為 **暫停**;再按一下暫停動畫,按鈕上的文字變為 **執行**。
2. **重置**:按下時重置木塊的位置
3. 用滑桿控制木塊的速度
我們使用最簡單的動畫〈[等速度直線運動](https://hackmd.io/@yizhewang/HJ7Ejj-GX)〉的程式碼來改寫,這是[線上版連結](https://www.glowscript.org/#/user/yizhe/folder/Public/program/APButton),成果如下:
<iframe src="https://www.glowscript.org/#/user/yizhe/folder/Public/program/APButton" width="800" height="1400"></iframe>
## 程式碼
```python=
from vpython import *
Web VPython 3.2
"""
1. 參數設定, 設定變數及初始值
"""
d = 10 # 木塊邊長
L = 200 # 地板長度
t = 0 # 時間
dt = 0.01 # 時間間隔
re = False # 重置狀態
running = False # 物體運動狀態
"""
2. 畫面及函式設定
"""
# 初始畫面設定
scene = canvas(width=800, height=400, x=0, y=0, background=vec(0, 0.6, 0.6), center=vec(0, 5, 0))
scene.title = "1D Motion\n\n"
floor = box(pos=vec(0, 0, 0), size=vec(L, 0.1*d, 0.5*L), color=color.blue)
cube = box(pos=vec(0, 0.55*d, 0), size=vec(d, d, d), v=vec(0, 0, 0), color=color.red)
gd = graph(width=600, height=450, x=0, y=400, xtitle="<i>t</i> (s)", ytitle="<i>x</i> (cm)", fast=False)
gd2 = graph(width=600, height=450, x=0, y=850, xtitle="<i>t</i> (s)", ytitle="<i>v</i> (cm/s)", ymin=-6.0, ymax=6.0, fast=False)
xt = gcurve(graph=gd, color=color.red)
vt = gcurve(graph=gd2, color=color.red)
# 執行按鈕
def run(b1):
global running
running = not running
if running: b1.text = "暫停"
else: b1.text = "執行"
b1 = button(text="執行", pos=scene.title_anchor, bind=run)
# 重置按鈕
def reset(b2):
global re
re = not re
b2 = button(text="重置", pos=scene.title_anchor, bind=reset)
# 設定速度滑桿
def setv(vslider):
cube.v.x = vslider.value
vtext.text = "{:1.1f} cm/s".format(vslider.value)
vslider = slider(min=-5.0, max=5.0, value=1.0, length=200, bind=setv, right=15, pos=scene.title_anchor)
vtext = wtext(text="{:1.1f} cm/s".format(vslider.value), pos=scene.title_anchor)
cube.v.x = vslider.value
# 重置用, 初始化
def init():
global re, running, t
cube.pos = vec(0, 0.55*d, 0)
cube.v.x = vslider.value
t = 0
xt.delete()
vt.delete()
re = False
running = False
b1.text = "執行"
# 更新狀態
def update():
global t
cube.pos += cube.v*dt
xt.plot(pos=(t, cube.pos.x))
vt.plot(pos=(t, cube.v.x))
t += dt
"""
3. 主程式
"""
while True:
rate(1000)
if (cube.pos.x <= -L*0.5+d*0.5 and cube.v.x < 0) or (cube.pos.x >= L*0.5-d*0.5 and cube.v.x > 0): cube.v.x = 0
if running: update()
if re: init()
```
<br />
### 參數設定
除了一些物件的資料之外,另外設定了**重置狀態re**、**物體運動狀態running**等2個變數,預設值皆為False。
### 畫面及函式設定
1. 先用以下的程式碼新增動畫視窗、木塊、地板。
```python
scene = canvas(width=800, height=400, x=0, y=0, background=vec(0, 0.6, 0.6), center=vec(0, 5, 0))
scene.title = "1D Motion\n\n"
floor = box(pos=vec(0, 0, 0), size=vec(L, 0.1*d, 0.5*L), color=color.blue)
cube = box(pos=vec(0, 0.55*d, 0), size=vec(d, d, d), v=vec(0, 0, 0), color=color.red)
```
2. 設定執行按鈕**b1**,分為2個部分:
```python
def run(b1):
global running
running = not running
if running: b1.text = "暫停"
else: b1.text = "執行"
b1 = button(text="執行", pos=scene.title_anchor, bind=run)
```
(1) 自訂函式**run(b1)**,函式內使用了全域變數**running**,當b1被按下時,將running的狀態反過來,若原為False則改為True,若原為True則改為False;改變按鈕上的文字,若running的狀態為True則文字改為**暫停**,若running的狀態為False則文字改為**執行**。
(2) 用**button**產生按鈕b1,其中**text**為按鈕上顯示的文字,**pos**為按鈕位置,在此設定為scene的標題位置(scene.title_anchot),**bind**則是用來將這個按鈕物件連結到自訂函式run。
3. 設定重置按鈕**b2**,分為3個部分:
```python
def reset(b2):
global re
re = not re
b2 = button(text="重置", pos=scene.title_anchor, bind=reset)
```
```python
def init():
global re, running
cube.pos = vec(0, size*0.55, 0)
cube.v.x = vslider.value
t = 0
xt.delete()
vt.delete()
re = False
running = False
b1.text = "執行"
```
(1) 自訂函式**reset(b2)**,函式內使用了全域變數**re**,當b2被按下時,將re的狀態反過來,若原為False則改為True,若原為True則改為False。
(2) 用**button**產生按鈕b2並連結到自訂函式reset。
(3) 自訂函式**init()**,執行此函式時,木塊會回到初位置,清除x-t圖、v-t圖中的資料,將running設定為False,將按鈕b1的文字變為**執行**。
4. 設定控制速度的滑桿,分為4個部分:
```python
def setv(vslider):
cube.v.x = vslider.value
vtext.text = '{:1.1f} cm/s'.format(vslider.value)
vslider = slider(min=-5.0, max=5.0, value=1.0, length=200, bind=setv, right=15,
pos=scene.title_anchor)
vtext = wtext(text='{:1.1f} cm/s'.format(vslider.value), pos=scene.title_anchor)
cube.v.x = vslider.value
```
(1) 自訂函式**setv(vslider)**,修改木塊的速度、滑桿右側顯示速度的文字。
(2) 用**slider**產生滑桿vslider並連結到自訂函式setv。
(3) 用**wtext**產生顯示速度的文字vtext。
(4) 設定木塊的初速度。
6. 自訂函式**update()**,執行此函式時,更新木塊的位置,繪製x-t圖、v-t圖,更新時間。
<br />
### 主程式
由於我們將大部分的內容及效果寫在自訂函式中,所以主程式非常短,只有以下幾行
```python
while True:
rate(1000)
if (cube.pos.x <= -L*0.5+size*0.5 and cube.v.x < 0) or (cube.pos.x >= L*0.5-size*0.5 and cube.v.x > 0): cube.v.x = 0
if running: update()
if re: init()
```
**注意事項:rate(1000) 一定要放在 while 迴圈裡面,否則動畫無法順利執行。**
<br />
1. 如果木塊碰到地板左側邊緣且速度向左,或是木塊碰到地板右側邊緣且速度向右,將速度速度設定為0。
2. 如果running的值為True,呼叫自訂函式update()更新狀態。
3. 如果re的值為True,呼叫自訂函式init()。
<br />
## 執行效果
我們可以用滑桿控制木塊的速度使木塊來回移動,當木塊抵達地板邊緣時,木塊會停止移動,但是時間仍會繼續增加,這是和第1版程式最大的差異。
<img style="display: block; margin-left: auto; margin-right: auto" height="60%" width="60%" src="https://i.imgur.com/GKfxaIJ.png">
<div style="text-align:center">用滑桿控制木塊來回移動的x-t圖</div>
<br />
## 結語
用VPython製作一個有操作界面的動畫,雖然也能做出不錯的效果,但是我們需要花較多的時間寫程式,用來處理物理模型的時間相對上較少,所以我們只簡單地做了這個動畫,之後的課程還是以處理物理模型為主。如果對於VPython的按鈕、滑桿、選單、核取方塊……等元件的用法有興趣,請參考GlowScript網站上的範例自行改寫。
<br />
## 參考資料
1. VPython官方說明書**Widgets**: http://www.glowscript.org/docs/VPythonDocs/controls.html
2. GlowScript網站範例**Buttons Sliders Menus**: http://www.glowscript.org/#/user/GlowScriptDemos/folder/Examples/program/ButtonsSlidersMenus-VPython
---
###### tags: `VPython`