ต่อจาก
บทที่ ๑๗
ในบทที่ผ่านๆมาได้แสดงตัวอย่างการใช้ self.play() เพื่อทำให้วัตถุเปลี่ยนแปลงหรือเคลื่อนที่ไปหลายตัวอย่าง
แต่วัตถุที่เคลื่อนไหวหรือเปลี่ยนแปลงด้วย self.play() นั้นจะเคลื่อนไหวแค่ตัวมันเองเท่านั้น สำหรับในบทนี้จะพูดถึงวิธีต่างๆที่ทำให้การเปลี่ยนแปลงของวัตถุหนึ่งทำให้เกิดผลเพิ่มเติมไปด้วยตามมาโดยอัตโนมัติ
always_redraw()
หากมีวัตถุบางอย่างที่ต้องการจะให้วาดขึ้นมาใหม่ตลอดทุกเฟรมอาจทำการสร้างขึ้นมาโดยใช้ฟังก์ชัน always_redraw()
ตัวอย่างเช่นถ้าต้องการสร้างตัวหนังสือที่แสดงตำแหน่งของวัตถุ แต่ว่าวัตถุนั้นเคลื่อนที่ไปเรื่อยๆ แบบนี้ก็จำเป็นต้องดูค่าตำแหน่งขณะนั้นแล้วสร้างตัวหนังสือแสดงค่าใหม่ขึ้นมาในแต่ละเฟรม กรณีแบบนี้สามาถใช้ always_redraw() ได้
รูปแบบการใช้
mnm.always_redraw(ฟังก์ชันสร้างวัตถุ, อาร์กิวเมนต์ที่จะใส่ในฟังก์ชันนั้น)
ตัวอย่าง ลองสร้างจุดที่เคลื่อนที่ไปทางขวาเรื่อยๆ แล้วสร้างตัวหนังสือที่แสดงตำแหน่งแกน x ของจุด
import manimlib as mnm
import numpy as np
class Manimala(mnm.Scene):
def construct(self):
wongklom = mnm.Circle(arc_center=np.array([-4,-2,0]),
radius=0.5,
fill_opacity=0.6,
color='#f3ddba')
# ฟังก์ชันที่ใช้สร้างวัตถุนั้นใหม่ในทุกเฟรม
def sailek(m):
return mnm.Text(f'x = {m.get_x():.1f}',size=2.7,color='#79c582')
# กำหนดให้เรียกใช้ฟังก์ชันนี้ทุกครั้งที่เปลี่ยนเฟรม
lek = mnm.always_redraw(sailek,wongklom)
self.add(lek)
# เลื่อนวงกลมไปทางขวา
self.play(
wongklom.animate.shift(mnm.RIGHT*8),
run_time=2
)
อีกตัวอย่าง คราวนี้เพิ่มรายละเอียดฟังก์ชันให้ซับซ้อนขึ้น ให้แสดงตำแหน่งทั้งแกน x และ y และให้ตัวหนังสือทั้งเปลี่ยนไปด้วยและย้ายตำแหน่งไปเรื่อยๆด้วย
import manimlib as mnm
import numpy as np
class Manimala(mnm.Scene):
def construct(self):
chut = mnm.Dot(np.array([2.5,-1.5,0]))
def sailek(m):
x,y = m.get_x(),m.get_y()
# สร้างวัตถุตัวหนังสือ
text = mnm.Text(f'x = {x:.1f}, y = {y:.1f}')
# ปรับแต่งรายละเอียดตามเงื่อนไขค่า x และ y
if(x<0): # ถ้า x น้อยกว่า 0 ให้เป็นสีม่วง
text.set_color('#d6baf3')
else: # ไม่เช่นนั้นให้เป็นสีแดง
text.set_color('#c67982')
if(y<0): # ถ้า y น้อยกว่า 0 ให้วางด้านบน
text.next_to(chut,mnm.UP)
else: # ถ้า ไม่เช่นนั้นให้วางด้านล่าง
text.next_to(chut,mnm.DOWN)
return text
tamnaeng = mnm.always_redraw(sailek,chut)
self.add(tamnaeng)
self.play(
chut.animate.move_to(np.array([-2,1,0])),
run_time=2
)
.add_updater()
หากมีวัตถุที่ต้องการให้มีการเปลี่ยนแปลงไปตลอดในทุกเฟรมโดยไม่ต้องคอยสร้างใหม่เรื่อยๆอาจทำได้โดยใช้เมธอด .add_updater()
เช่นถ้าแค่ต้องการให้ตัวหนังสือย้ายตำแหน่งไปเรื่อยๆตามตำแหน่งของจุด แบบนี้ไม่จำเป็นต้องสร้างใหม่ตลอดทุกครั้ง แต่แค่ให้เรียกใช้ .next_to() เพื่อให้ตามไปเรื่อยๆ
รูปแบบการใช้
วัตถุ.add_updater(ฟังก์ชันที่ต้องการให้เรียกใช้ทุกเฟรม)
ตัวอย่าง
import manimlib as mnm
import numpy as np
class Manimala(mnm.Scene):
def construct(self):
chut = mnm.Dot(np.array([-2.5,-1.5,0]),radius=1)
yuni = mnm.Text('อยู่นี่',size=2,color='#f3baf2')
def yaipai(m):
yuni.next_to(chut,mnm.LEFT)
yuni.add_updater(yaipai)
self.add(yuni)
self.play(
mnm.Rotate(chut,np.radians(-120),about_point=np.array([1,-1,0])),
run_time=2
)
วิธีการใช้จะคล้ายกับ always_redraw() แต่จะใช้ทำให้เกิดการเปลี่ยนแปลงที่ตัววัตถุที่มีอยู่แล้วโดยไม่ต้องสร้างใหม่
DecimalNumber
หากต้องการจะแสดงค่าตัวเลขบางอย่างซึ่งมีการเปลี่ยนแปลงไปเรื่อยๆ กรณีแบบนี้ใช้ DecimalNumber จะสะดวกกว่าใช้ Text หรือ Tex ซึ่งต้องคอยสั่งสร้างใหม่ด้วย always_redraw อยู่เรื่อยๆ
คลาส DecimalNumber เป็นวัตถุสำหรับแสดงค่าตัวเลขโดยสามารถเปลี่ยนแปลงค่าได้หลังจากที่สร้างแล้วได้โดยใช้เมธอด .set_value() ซึ่งเมธอดนี้ก็สามารถใช้กับ .animate เพื่อทำภาพเคลื่อนไหวแสดงการเปลี่ยนแปลงค่าได้
ตัวอย่างการใช้ DecimalNumber
import manimlib as mnm
class Manimala(mnm.Scene):
def construct(self):
lek = mnm.DecimalNumber(99.99,
num_decimal_places=3,
include_sign=True,
edge_to_fix=mnm.RIGHT,
font_size=200)
self.add(lek)
self.play(
lek.animate.set_value(-7.654),
run_time=1.5
)
ค่าต่างๆที่ปรับได้มีดังนี้
คีย์เวิร์ด | ความหมาย | ค่าตั้งต้น |
num_decimal_places | จำนวนตำแหน่งทศนิยม | 3 |
include_sign | แสดงเครื่องหมายหรือไม่เมื่อเป็นค่าบวก | False |
edge_to_fix | ด้านที่จะตรึงตำแหน่ง | LEFT |
font_size | ขนาดอักษร | 48 |
DecimalNumber สามารถใช้คู่กับ .add_updater() เพื่อให้เปลี่ยนแปลงตัวเลขไปตามค่าอะไรที่ต้องการได้
ตัวอย่าง ให้แสดงตำแหน่งจุด คล้ายกับตัวอย่างก่อนหน้านี้ที่ใช้ Text สร้าง แต่แค่ปรับค่าไปเรื่อยๆ ไม่ต้องใช้ always_redraw() เพื่อสร้างใหม่เรื่อยๆ
import manimlib as mnm
import numpy as np
class Manimala(mnm.Scene):
def construct(self):
chut = mnm.Dot(np.array([-4,2,0]),radius=0.5,color='#ffc1c1')
lek = mnm.DecimalNumber(0,num_decimal_places=4,font_size=130)
def prapkha(m):
m.set_value(chut.get_x())
lek.add_updater(prapkha)
self.add(lek)
self.play(
chut.animate.shift(mnm.RIGHT*8),
run_time=2
)
อีกตัวอย่างหนึ่ง แสดงการปรับทั้งตัวเลขและตำแหน่งไปพร้อมๆกัน
import manimlib as mnm
import numpy as np
class Manimala(mnm.Scene):
def construct(self):
chut = mnm.Dot(np.array([-1,0,0])) # จุด
# แถบข้อความตัวเลขที่ใช้บอกตำแหน่งพิกัด x,y
lek = mnm.VGroup(mnm.Text('x=',color='#c3f3ba'),
mnm.DecimalNumber(0,include_sign=True),
mnm.Text(' y=',color='#f2f3ba'),
mnm.DecimalNumber(0,include_sign=True))
lek.next_to(chut,mnm.UP)
lek.arrange(mnm.RIGHT)
# ฟังก์ชันสำหรับใช้ใน .add_updater()
def prapkha(m):
m.next_to(chut,mnm.UP) # วางตำแหน่งไว้บนจุด
m[1].set_value(chut.get_x()) # ค่าตำแหน่งแกน x
m[3].set_value(chut.get_y()) # ค่าตำแหน่งแกน y
# ตั้งให้ฟังก์ชันนี้ทำงานเพื่อปรับค่าทุกครั้งที่ขึ้นเฟรมใหม่
lek.add_updater(prapkha)
self.add(lek)
self.play(
chut.animate.shift(mnm.DR*2),
run_time=1.5
)
Integer
คลาส Integer เป็นคลาสย่อยของ DecimalNumber ในกรณีที่ num_decimal_places=0 ก็คือไม่มีเลขทศนิยม เป็นจำนวนเต็ม วิธีการใช้ก็เหมือนกัน
ตัวอย่าง
import manimlib as mnm
import numpy as np
class Manimala(mnm.Scene):
def construct(self):
lek = mnm.Integer(0,font_size=225,edge_to_fix=mnm.RIGHT)
lek.move_to(np.array([3,0,0]))
def prapkha(m):
m.set_value(lek.get_x())
lek.add_updater(prapkha)
self.add(lek)
self.play(
lek.animate.shift(mnm.LEFT*6),
run_time=1.5
)
always()
ฟังก์ชัน always() เอาไว้ใช้เขียนย่อแทน .add_updater() ในบางกรณี
โดยวิธีการใช้ก็คือตั้งให้มีการใช้ฟังก์ชันอะไรบางอย่างตลอดทุกเฟรม
รูปแบบการใช้งาน
mnm.always(เมธอดของวัตถุ, อาร์กิวเมนต์ที่จะใส่ในเมธอดนั้น)
ตัวอย่าง สร้างข้อความที่ย้ายตำแหน่งไปตามสี่เหลี่ยม
import manimlib as mnm
import numpy as np
class Manimala(mnm.Scene):
def construct(self):
siliam = mnm.Rectangle(7,4,color='#d1ffc1')
text = mnm.Text('สี่เหลี่ยมผืนผ้า',color='#f7c1ff')
mnm.always(text.next_to,siliam,mnm.UP)
# แทนที่จะเขียน text.add_updater(lambda m:m.next_to(siliam,mnm.UP))
self.add(text)
self.play(
mnm.FadeOutToPoint(siliam,np.array([-3.5,-2,0])),
run_time=1.5
)
จะเห็นว่าเป็นการเขียนแทน .add_updater() ทำให้ดูสั้นลง แต่ผลการทำงานที่ได้ก็เหมือนกัน
f_always()
f_always() ก็เป็นอีกฟังก์ชันที่สามารถใช้เพื่อเขียนย่อแทน .add_updater() ได้ เช่นเดียวกับalways()
ข้อแตกต่างระหว่าง always() กับ f_always() ก็คืออาร์กิวเมนต์ตัวที่ ๒ ของ f_always() ที่จะต้องใส่นั้นจะเป็นฟังก์ชัน ที่จะให้ถูกนำมาเรียกใช้อีกที แต่ของ always() คือตัวค่าที่จะใส่
รูปแบบการใช้งาน
mnm.f_always(เมธอดของวัตถุ, ฟังก์ชันที่จะให้เรียกใช้เพื่อให้คืนค่าให้เมธอดนั้น)
ตัวอย่าง สร้างค่าตัวเลขบอกขนาดความกว้างของวงรี ซึ่งเปลี่ยนค่าและย้ายตำแหน่งไปตามวงรีตลอด
import manimlib as mnm
import numpy as np
class Manimala(mnm.Scene):
def construct(self):
wongri = mnm.Ellipse(width=6,height=3,color='#c1caff',stroke_width=7)
lek = mnm.DecimalNumber(0,font_size=100)
mnm.always(lek.next_to,wongri,mnm.UP)
# แทน lek.add_updater(lambda m:m.next_to(wongri,mnm.UP))
mnm.f_always(lek.set_value,wongri.get_width)
# แทน lek.add_updater(lambda m:m.set_value(wongri.get_width()))
self.add(lek)
self.play(
mnm.FadeOutToPoint(wongri,np.array([-3,-1.5,0])),
run_time=1.5
)
อ่านบทถัดไป >>
บทที่ ๑๙