ผลต่างระหว่างรุ่นของ "01204223/kivy"
Jittat (คุย | มีส่วนร่วม) |
Jittat (คุย | มีส่วนร่วม) |
||
(ไม่แสดง 11 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน) | |||
แถว 4: | แถว 4: | ||
การติดตั้ง สั่งคำสั่งต่อไปนี้ใน shell (อย่าลืม login เครือข่ายนนทรีก่อน) | การติดตั้ง สั่งคำสั่งต่อไปนี้ใน shell (อย่าลืม login เครือข่ายนนทรีก่อน) | ||
+ | sudo apt-get update | ||
sudo apt-get install python-setuptools python-pygame python-opengl \ | sudo apt-get install python-setuptools python-pygame python-opengl \ | ||
python-gst0.10 python-enchant gstreamer0.10-plugins-good python-dev \ | python-gst0.10 python-enchant gstreamer0.10-plugins-good python-dev \ | ||
แถว 10: | แถว 11: | ||
จากนั้นสั่ง | จากนั้นสั่ง | ||
+ | sudo pip install --upgrade cython | ||
sudo easy_install kivy | sudo easy_install kivy | ||
แถว 182: | แถว 184: | ||
ทดลองเรียกให้โปรแกรมทำงาน | ทดลองเรียกให้โปรแกรมทำงาน | ||
+ | |||
+ | เราจะปรับทิศทางของลูกบอลเมื่อชนขอบ ก่อนอื่นเราลองพิจารณาว่าเราควรจะเขียนเมท็อดดังกล่าวที่คลาสใด? | ||
+ | |||
+ | สังเกตว่าลูกบอลไม่มีข้อมูลของกรอบขอบมัน ดังนั้นถ้าเราไม่คิดจะเปลี่ยนโครงสร้างของการสร้างวัตถุเราก็ควรจะเขียนการปรับค่าดังกล่าวที่คลาสแม่ (นั่นคือ <tt>PongGame</tt>) | ||
+ | |||
+ | แก้คลาด <tt>PongGame</tt> โดยเพิ่มเมท็อด <tt>bounce_ball</tt> และแก้ <tt>update</tt> ดังนี้ | ||
+ | |||
+ | <pre> | ||
+ | class PongGame(Widget): | ||
+ | ball = ObjectProperty(None) | ||
+ | |||
+ | def bounce_ball(self): | ||
+ | if self.ball.y < 0 or self.ball.y > self.height: | ||
+ | self.ball.vy *= -1 | ||
+ | |||
+ | if self.ball.x < 0 or self.ball.x > self.width: | ||
+ | self.ball.vx *= -1 | ||
+ | |||
+ | def update(self, dt): | ||
+ | self.ball.move() | ||
+ | self.bounce_ball() | ||
+ | </pre> | ||
+ | |||
+ | ทดลองเรียกให้โปรแกรมทำงาน (ถ้าลูกบอลเคลื่อนที่ช้าเกินไป ให้ลองปรับความเร็วเริ่มต้นดู) สังเกตการเด้งของลูกบอลว่ามีอะไรผิดปกติหรือไม่? | ||
+ | |||
+ | == Observers == | ||
+ | เราจะแก้ปัญหาลูกบอลเด้งนอกกรอบ แต่ก่อนที่เราจะแก้ปัญหาอะไรได้ เราจะต้องลองหาสาเหตุเสียก่อน | ||
+ | |||
+ | บรรดา property ที่เรากำหนดค่าใน kv rules นั้น จะมีลักษณะพิเศษ เราจะปรับ property ของ <tt>Label</tt> (ที่ตอนแรกเป็นคำว่า hello) ที่เราระบุในตลาส <tt>PongGame</tt> | ||
+ | |||
+ | แก้แฟ้ม <tt>pong.kv</tt> ดังด้านล่างแล้วทดลองเรียกให้โปรแกรมทำงาน | ||
+ | |||
+ | <pre> | ||
+ | <PongGame>: | ||
+ | # ... | ||
+ | Label: | ||
+ | # ... | ||
+ | text: str(self.parent.ball.y) | ||
+ | # ... | ||
+ | </pre> | ||
+ | |||
+ | สังเกตว่าข้อความที่แสดงเปลี่ยนไปตาม property <tt>self.parent.ball.y</tt> สาเหตุที่การทำงานมีลักษณะดังกล่าวเป็นเพราะว่า property ต่าง ๆ ที่เรากำหนดได้ implement [http://en.wikipedia.org/wiki/Observer_pattern Observer pattern] เอาไว้ เมื่อมันมีการเปลี่ยนค่า มันจะแจ้ง property อื่น ๆ ที่ใช้ค่าจากมันให้ปรับค่าตามโดยอัตโนมัติ | ||
+ | |||
+ | พยายามตอบคำถามว่าทำไมลูกบอลจึงเด้งเลยขอบด้านบน และแก้โปรแกรมให้ลูกบอลเด้งภายในขอบเขต | ||
+ | |||
+ | หมายเหตุ: ทุก ๆ widget จะมี property <tt>height</tt> และ <tt>width</tt> ที่สามารถอ่านขนาดได้ | ||
+ | |||
+ | == Events == | ||
+ | ในส่วนนี้เราจะทดลองปรับโปรแกรมให้รับเหตุการณ์ที่เกิดขึ้นจากผู้ใช้ | ||
+ | |||
+ | การตอบสนองต่อเหตุการณ์ทำได้โดยเขียนฟังก์ชันที่เกี่ยวกับการแตะทำได้โดยเขียนฟังก์ชัน on_touch_down, on_touch_move, หรือ on_touch_up ([http://kivy.org/docs/api-kivy.uix.widget.html#kivy.uix.widget.Widget.on_touch_down]) | ||
+ | |||
+ | เราจะย้ายลูกบอลมาที่ตำแหน่งที่มีการแตะโดยเขียนเมท็อด <tt>on_touch_down</tt> ในคลาส <tt>PongGame</tt> | ||
+ | |||
+ | <pre> | ||
+ | class PongGame(Widget): | ||
+ | # ... | ||
+ | def on_touch_down(self, touch): | ||
+ | self.ball.center_x = touch.x | ||
+ | self.ball.center_y = touch.y | ||
+ | </pre> | ||
+ | |||
+ | โดยปกติแล้ว สำหรับเหตุการณ์ต่าง ๆ ถ้าเราไม่ได้เขียนเมท็อดเพื่อรองรับเอาไว้ สิ่งที่ widget จะทำคือส่งต่อเหตุการณ์นั้นไปยัง widget ลูก ๆ เรื่อย ๆ จนกระทั่งมีบาง widget คืนค่า True (ถ้าไม่มี ก็จะส่งต่อจนครบ) | ||
+ | |||
+ | ดังนั้นบางครั้งที่เรารับเหตุการณ์เมื่อเป็น widget ลูก เราอาจจะจำเป็นต้องตรวจสอบว่าตำแหน่งที่ได้รับมานั้นอยู่ในขอบเขตหรือไม่ เราสามารถใช้เมท็อด <tt>collide_point</tt> เพื่อตรวจสอบได้ | ||
+ | |||
+ | เราจะปรับให้ลูกบอลใหญ่ขึ้นถ้ามีการกดภายในขอบเขตของลูกบอล (ในขอบเขตกล่องสี่เหลี่ยมของลูกบอล) โดยแก้เมท็อด on_touch_move ดังนี้ | ||
+ | |||
+ | <pre> | ||
+ | def on_touch_down(self, touch): | ||
+ | self.ball.center_x = touch.x | ||
+ | self.ball.center_y = touch.y | ||
+ | |||
+ | if self.ball.collide_point(touch.x,touch.y): | ||
+ | self.ball.width += 10 | ||
+ | self.ball.height += 10 | ||
+ | </pre> | ||
+ | |||
+ | สังเกตว่า ถ้าเรากดที่จุดที่ไม่อยู่ในลูกบอล แต่อยู่ในขอบเขตสี่เหลี่ยม ลูกบอลก็ยังขยายขนาด ให้เขียนเมท็อด <tt>is_inside(self,x,y)</tt> ในคลาส <tt>PongBall</tt> เพื่อตรวจสอบว่าจุดที่กดอยู่ในวงกลมของลูกบอลหรือไม่ จากนั้นแก้เมท็อด <tt>on_touch_down</tt> ให้ใช้เมท็อดดังกล่าวแทน <tt>self.ball.collide_point(touch.x,touch.y)</tt> | ||
+ | |||
+ | == การติดต่อที่ซับซ้อนขึ้น == | ||
+ | ในส่วนนี้เราจะเรียนวิธีการ grab |
รุ่นแก้ไขปัจจุบันเมื่อ 03:08, 15 กรกฎาคม 2556
เอกสารส่วนนี้ดัดแปลงมาจาก Pong Game Tutorial
เนื้อหา
ติดตั้ง kivy
การติดตั้ง สั่งคำสั่งต่อไปนี้ใน shell (อย่าลืม login เครือข่ายนนทรีก่อน)
sudo apt-get update sudo apt-get install python-setuptools python-pygame python-opengl \ python-gst0.10 python-enchant gstreamer0.10-plugins-good python-dev \ build-essential libgl1-mesa-dev libgles2-mesa-dev cython python-pip
จากนั้นสั่ง
sudo pip install --upgrade cython sudo easy_install kivy
ทดลองโปรแกรมด้านล่าง
ให้สร้างไดเร็กทอรีย่อย จากนั้นป้อนโปรแกรมด้านล่างลงในแฟ้ม main.py
from kivy.app import App from kivy.uix.widget import Widget class PongGame(Widget): pass class PongApp(App): def build(self): return PongGame() if __name__ == '__main__': PongApp().run()
Kv Language และการสร้าง widget
Kivy แยกส่วนออกแบบหน้าจอออกมาเป็นแฟ้มนามสกุล kv ในส่วนนี้เราจะทดลองการใช้แฟ้มดังกล่าว และแนวคิดพื้นฐานเกี่ยวกับการตอบสนอง event และ observer patterns
เพิ่มแฟ้มชื่อ pong.kv ในไดเร็กทอรี
#:kivy 1.7.1 <PongGame>: Label: font_size: 70 center_x: (root.width * 3) / 4 top: root.top - 50 text: "hello"
ทดลองเรียก python main.py จะเห็นหน้าจอที่มีคำว่า hello
แฟ้ม pong.kv จะถูกอ่านโดยอัตโนมัติโดย App ที่ชื่อ PongApp
เพิ่มลูกบอล
เราจะเพิ่มคลาส PongBall ลงใน main.py
class PongBall(Widget): pass
จากนั้นแก้ไข pong.kv โดยเพิ่มข้อมูลเกี่ยวกับการวาดวัตถุของคลาส PongBall และเพิ่มลูกบอลลงใน widget PongGame
#:kivy 1.7.1 <PongBall>: size: 50, 50 canvas: Ellipse: pos: self.pos size: self.size <PongGame>: Label: font_size: 70 center_x: (root.width * 3) / 4 top: root.top - 50 text: "hello" PongBall: center: self.parent.center
จากนั้นทดลองรัน
ลูกบอลเคลื่อนที่: Clock, properties
ในส่วนนี้เราจะทำให้ลูกบอลเคลื่อนที่ได้ มีกิจกรรมที่เราต้องทำ 3 ขั้นตอน
1. การปรับสถานะเกม
เกมทั่วไปที่มีการเคลื่อนไหวจะมีการเปลี่ยนตำแหน่งวัตถุต่าง ๆ ตลอดเวลา ในการเขียนโปรแกรมจริง ๆ นั้น เราไม่สามารถปรับตำแหน่งตลอดเวลาได้ แต่เราจะใช้การตั้งเวลาให้ระบบเรียกฟังก์ชันในการปรับตำแหน่งของเราเป็นระยะ
เราจะสร้างเมท็อด update ในคลาส PongGame เพื่อปรับตำแหน่งลูกบอล
class PongGame(Widget): def update(self, dt): pass
จากนั้นแก้ไขคลาส PongApp ให้ตั้งเวลาเรียกฟังก์ชันดังกล่าวทุก ๆ 1/60 วินาที
class PongApp(App): def build(self): game = PongGame() Clock.schedule_interval(game.update, 1.0/60.0) return game
สังเกตว่าเราใช้ Clock ด้วย เราจะต้อง import ชื่อดังกล่าว โดยเพิ่ม
from kivy.clock import Clock
ไว้ที่ต้นโปรแกรม
โปรแกรมดังกล่าวเมื่อเรียกให้ทำงาน จะไม่เห็นผลลัพธ์เปลี่ยนแปลงใด ๆ เพราะว่าเรายังไม่ได้ทำงานอะไรเลยในเมท็อด update
2. ทำให้ PongGame รู้จักวัตถุ PongBall: properties และการกำหนด id
การที่เมท็อด update จะปรับตำแหน่งของลูกบอลได้นั้น เมท็อดดังกล่าวจะต้องอ้างถึงวัตถุที่แทนลูกบอลให้ได้เสียก่อน
เราสามารถ "ร้อย" วัตถุที่สร้างขึ้นในแฟ้ม kv ให้ "มองเห็น" กันได้
ขั้นแรกเราจะเพิ่ม property ball ในคลาส PongGame
class PongGame(Widget): ball = ObjectProperty(None) # ... (ละไว้) ...
ให้เพิ่มการ import ObjectProperty ที่หัวแฟ้มด้วย: from kivy.properties import ObjectProperty
จากนั้นปรับกฎในแฟ้ม kv ดังนี้
<PongGame>: ball: pong_ball # ... PongBall: id: pong_ball center: self.parent.center
สังเกตว่าเราให้ชื่อกับวัตถุคลาส PongBall ที่เราสร้างภายใน PongGame ว่า pong_ball จากนั้นเรากำหนด property ball ให้อ้างถึงวัตถุดังกล่าวด้วยชื่อ pong_ball
เพื่อทดลองว่าเราสามารถอ้างถึง pong_ball ได้จริง ๆ ให้แก้เมท็อด update ในคลาส PongGame เป็น
def update(self, dt): self.ball.x += 1
แล้วทดลองเรียกให้โปรแกรมทำงาน
3.เพิ่ม property เกี่ยวกับความเร็วให้ลูกบอลและปรับทิศทางเมื่อชนขอบ
สังเกตว่าถ้าเราต้องการให้ลูกบอลเด้งไปมาได้ ลูกบอลจะต้องมีข้อมูลเกี่ยวกับความเร็ว เราจะเพิ่ม property vx และ vy ให้กับลูกบอล และเขียนเมท็อด move ที่ PongBall เพื่อปรับตำแหน่งตามความเร็วนั้น
class PongBall(Widget): vx = NumericProperty(1) vy = NumericProperty(1) def move(self): self.x += self.vx self.y += self.vy
เรากำหนดค่าเริ่มต้นของ property ทั้งสองเป็น 1
สังเกตว่าเราใช้ NumericProperty ดังนั้นต้องไป import ด้วย โดยแก้หัวโปรแกรมเป็น
from kivy.properties import ObjectProperty, NumericProperty
จากนั้นแก้เมท็อด update ให้เป็น
def update(self, dt): self.ball.move()
ทดลองเรียกให้โปรแกรมทำงาน
เราจะปรับทิศทางของลูกบอลเมื่อชนขอบ ก่อนอื่นเราลองพิจารณาว่าเราควรจะเขียนเมท็อดดังกล่าวที่คลาสใด?
สังเกตว่าลูกบอลไม่มีข้อมูลของกรอบขอบมัน ดังนั้นถ้าเราไม่คิดจะเปลี่ยนโครงสร้างของการสร้างวัตถุเราก็ควรจะเขียนการปรับค่าดังกล่าวที่คลาสแม่ (นั่นคือ PongGame)
แก้คลาด PongGame โดยเพิ่มเมท็อด bounce_ball และแก้ update ดังนี้
class PongGame(Widget): ball = ObjectProperty(None) def bounce_ball(self): if self.ball.y < 0 or self.ball.y > self.height: self.ball.vy *= -1 if self.ball.x < 0 or self.ball.x > self.width: self.ball.vx *= -1 def update(self, dt): self.ball.move() self.bounce_ball()
ทดลองเรียกให้โปรแกรมทำงาน (ถ้าลูกบอลเคลื่อนที่ช้าเกินไป ให้ลองปรับความเร็วเริ่มต้นดู) สังเกตการเด้งของลูกบอลว่ามีอะไรผิดปกติหรือไม่?
Observers
เราจะแก้ปัญหาลูกบอลเด้งนอกกรอบ แต่ก่อนที่เราจะแก้ปัญหาอะไรได้ เราจะต้องลองหาสาเหตุเสียก่อน
บรรดา property ที่เรากำหนดค่าใน kv rules นั้น จะมีลักษณะพิเศษ เราจะปรับ property ของ Label (ที่ตอนแรกเป็นคำว่า hello) ที่เราระบุในตลาส PongGame
แก้แฟ้ม pong.kv ดังด้านล่างแล้วทดลองเรียกให้โปรแกรมทำงาน
<PongGame>: # ... Label: # ... text: str(self.parent.ball.y) # ...
สังเกตว่าข้อความที่แสดงเปลี่ยนไปตาม property self.parent.ball.y สาเหตุที่การทำงานมีลักษณะดังกล่าวเป็นเพราะว่า property ต่าง ๆ ที่เรากำหนดได้ implement Observer pattern เอาไว้ เมื่อมันมีการเปลี่ยนค่า มันจะแจ้ง property อื่น ๆ ที่ใช้ค่าจากมันให้ปรับค่าตามโดยอัตโนมัติ
พยายามตอบคำถามว่าทำไมลูกบอลจึงเด้งเลยขอบด้านบน และแก้โปรแกรมให้ลูกบอลเด้งภายในขอบเขต
หมายเหตุ: ทุก ๆ widget จะมี property height และ width ที่สามารถอ่านขนาดได้
Events
ในส่วนนี้เราจะทดลองปรับโปรแกรมให้รับเหตุการณ์ที่เกิดขึ้นจากผู้ใช้
การตอบสนองต่อเหตุการณ์ทำได้โดยเขียนฟังก์ชันที่เกี่ยวกับการแตะทำได้โดยเขียนฟังก์ชัน on_touch_down, on_touch_move, หรือ on_touch_up ([1])
เราจะย้ายลูกบอลมาที่ตำแหน่งที่มีการแตะโดยเขียนเมท็อด on_touch_down ในคลาส PongGame
class PongGame(Widget): # ... def on_touch_down(self, touch): self.ball.center_x = touch.x self.ball.center_y = touch.y
โดยปกติแล้ว สำหรับเหตุการณ์ต่าง ๆ ถ้าเราไม่ได้เขียนเมท็อดเพื่อรองรับเอาไว้ สิ่งที่ widget จะทำคือส่งต่อเหตุการณ์นั้นไปยัง widget ลูก ๆ เรื่อย ๆ จนกระทั่งมีบาง widget คืนค่า True (ถ้าไม่มี ก็จะส่งต่อจนครบ)
ดังนั้นบางครั้งที่เรารับเหตุการณ์เมื่อเป็น widget ลูก เราอาจจะจำเป็นต้องตรวจสอบว่าตำแหน่งที่ได้รับมานั้นอยู่ในขอบเขตหรือไม่ เราสามารถใช้เมท็อด collide_point เพื่อตรวจสอบได้
เราจะปรับให้ลูกบอลใหญ่ขึ้นถ้ามีการกดภายในขอบเขตของลูกบอล (ในขอบเขตกล่องสี่เหลี่ยมของลูกบอล) โดยแก้เมท็อด on_touch_move ดังนี้
def on_touch_down(self, touch): self.ball.center_x = touch.x self.ball.center_y = touch.y if self.ball.collide_point(touch.x,touch.y): self.ball.width += 10 self.ball.height += 10
สังเกตว่า ถ้าเรากดที่จุดที่ไม่อยู่ในลูกบอล แต่อยู่ในขอบเขตสี่เหลี่ยม ลูกบอลก็ยังขยายขนาด ให้เขียนเมท็อด is_inside(self,x,y) ในคลาส PongBall เพื่อตรวจสอบว่าจุดที่กดอยู่ในวงกลมของลูกบอลหรือไม่ จากนั้นแก้เมท็อด on_touch_down ให้ใช้เมท็อดดังกล่าวแทน self.ball.collide_point(touch.x,touch.y)
การติดต่อที่ซับซ้อนขึ้น
ในส่วนนี้เราจะเรียนวิธีการ grab