สร้างเกมเดาะตะกร้อด้วย VPython

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา

การเตรียมตัว

ไพทอนและไลบรารีที่เกี่ยวข้อง

วิกินี้ใช้ภาษาไพทอนและไลบรารีที่เกี่ยวข้องดังนี้

บอร์ดไมโครคอนโทรลเลอร์และโมดูลไดรเวอร์

บอร์ดไมโครคอนโทรลเลอร์ที่นำมาใช้เป็นตัวควบคุมผู้เล่นในวิกินี้ต้องถูกโปรแกรมเฟิร์มแวร์ให้สามารถอ่านค่าแสงผ่านพอร์ท USB ได้แล้ว ให้แน่ใจว่า

องค์ประกอบของเกม

Takro-basic.png

ตัวเกมพื้นฐานประกอบไปด้วย

  • กำแพงและเพดาน (สีแดง) สร้างขึ้นจากคลาส vis.curve เพื่อใช้แสดงขอบเขตด้านซ้าย ขวา และบน ลูกตะกร้อจะกระดอนกลับเมื่อเคลื่อนที่เลยขอบเขตนี้
  • ลูกตะกร้อ (สีเหลือง) สร้างขึ้นจากคลาส Ball ซึ่งสืบสกุลมาจากคลาส vis.sphere มีทิศทางการเคลื่อนที่ในสองมิติซึ่งคำนวณจากกฎการเคลื่อนที่
  • แป้นรับลูกที่ควบคุมด้วยผู้เล่น (สีน้ำเงิน) สร้างขึ้นจากคลาส Player ซึ่งสืบสกุลมาจากคลาส vis.cylinder มีการเคลื่อนที่ในทิศทางซ้ายและขวาตามความเข้มของแสงที่อ่านได้จากบอร์ดไมโครคอนโทรลเลอร์

ระบบพิกัด

VPython ใช้ระบบพิกัดในปริภูมิ 3 มิติ อย่างไรก็ตามตำแหน่งต่าง ๆ ของผู้เล่น ลูกตะกร้อ และกำแพง จะวางตัวอยู่ในระนาบ z=0 โดยที่พิกัด (x,y) มีทิศทางและขอบเขตดังภาพ ในที่นี้ BORDER เป็นค่าคงที่ที่กำหนดขึ้นมาในโปรแกรมซึ่งสามารถปรับเปลี่ยนขอบเขตได้ตามต้องการ

Takro-coord.png

สร้างสนามและตั้งค่าคงที่เพื่อใช้ในเกม

ใช้เท็กซ์เอดิเตอร์สร้างไฟล์ชื่อ takro.py ตามโค้ดด้านล่าง

 1 import vis
 2 
 3 ANIM_RATE = 30
 4 BORDER = 10
 5 scene = vis.display(title='Takro',x=100,y=100,width=500,height=500)
 6 scene.center = (0,0)
 7 scene.range = BORDER*1.2
 8 
 9 border = vis.curve(pos=[
10     (-BORDER,-BORDER),
11     (-BORDER,+BORDER),
12     (+BORDER,+BORDER),
13     (+BORDER,-BORDER)
14     ],radius=0.1,color=vis.color.red)
15 
16 while True:
17     vis.rate(ANIM_RATE)
  • บรรทัดที่ 1 อิมพอร์ตโมดูล vis ซึ่งเป็นโมดูลหลักของ VPython
  • บรรทัดที่ 3-4 กำหนดค่าคงที่เพื่อใช้ในโปรแกรม โดย ANIM_RATE ใช้กำหนดอัตราการอัพเดตหน้าจอต่อวินาที และ BORDER ใช้กำหนดขอบเขตของสนามตามที่อธิบายไว้ในหัวข้อระบบพิกัดข้างต้น
  • บรรทัดที่ 5-7 สร้างหน้าต่างขนาด 500x500 ที่มีหัวข้อ "Takro" ขึ้นมา หน้าต่างนี้ทำหน้าที่เสมือนช่องที่มองเข้าไปในปริภูมิสามมิติของ VPython โดยให้พิกัด (0,0) อยู่ที่จุดศูนย์กลางของหน้าต่าง และช่องหน้าต่างมีขอบเขตครอบคลุมพิกัด (-BORDER,-BORDER) - (+BORDER,+BORDER) ออกไป 20%
  • บรรทัดที่ 9-14 สร้างกำแพงและเพดานเพื่อแสดงขอบเขตสนามด้วยเส้นสีแดง เริ่มต้นจากพิกัด (-BORDER,-BORDER) → (-BORDER,+BORDER) → (+BORDER,+BORDER) และจบลงที่พิกัด (+BORDER,-BORDER) เส้นแสดงกำแพงแต่ละท่อนมีรัศมี 0.1 หน่วย
  • บรรทัดที่ 16-17 เป็นลูปอนันต์เพื่อให้โปรแกรมไม่จบการทำงาน ฟังก์ชัน vis.rate() ทำหน้าที่หน่วงเวลาเพื่อให้ลูปทำงานไม่เกินอัตราที่กำหนด (ในที่นี่คือ 30 รอบต่อวินาที) และยังทำหน้าที่ในการประมวลผลอินพุทจากเมาส์และคีย์บอร์ด ซึ่งการกดแป้น Ctrl ค้างไว้แล้วคลิ้กลากเมาส์ไปมาจะทำให้หน้าต่างหมุนเปลี่ยนมุมมอง ในขณะที่การกดแป้น Alt ค้างไว้แล้วลากเมาส์เป็นการซูมเข้าออก

รันสคริปต์ด้วยไพทอน (ที่มากับ Anaconda) ควรเห็นหน้าต่างขนาด 500x500 ปรากฏขึ้นพร้อมกำแพงสีแดงล้อมรอบสามทิศทาง

python takro.py
Takro-1.png

สร้างลูกตะกร้อ

คลาส Ball

นิยามคลาส Ball เพื่อใช้สร้างเป็นลูกตะกร้อ โดยให้สืบสกุลมาจากคลาสทรงกลม vis.sphere ของ VPython ดังโค้ดด้านล่าง

 1 import vis
 2  
 3 ANIM_RATE = 30
 4 BORDER = 10
 5 scene = vis.display(title='Takro',x=100,y=100,width=500,height=500)
 6 scene.center = (0,0)
 7 scene.range = BORDER*1.2
 8 
 9 ################################################################
10 class Ball(vis.sphere):
11 
12     BALL_SIZE = 0.5
13 
14     def __init__(self,color=vis.color.white,vel=(0,0),acc=(0,0)):
15         vis.sphere.__init__(self,pos=(0,0),radius=self.BALL_SIZE,color=color,
16                 make_trail=True,retain=10)
17         self.vel = vis.vector(vel)
18         self.acc = vis.vector(acc)
19 
20     def move(self,dt):
21         self.vel += self.acc*dt
22         self.pos += self.vel*dt
23 
24 ################################################################
25 border = vis.curve(pos=[
26     (-BORDER,-BORDER),
27     (-BORDER,+BORDER),
28     (+BORDER,+BORDER),
29     (+BORDER,-BORDER)
30     ],radius=0.1,color=vis.color.red)
31 ball = Ball(color=vis.color.yellow,vel=(5,10),acc=(0,-5))
32 
33 while True:
34     vis.rate(ANIM_RATE)
35     ball.move(1.0/ANIM_RATE)
  • บรรทัดที่ 14-18 นิยามคอนสตรัคเตอร์ของคลาส โดยให้ไปเรียกใช้คอนสตรัคเตอร์ของคลาส vis.sphere เพื่อสร้างทรงกลมตามสีและขนาดที่กำหนดไว้ที่ตำแหน่ง (0,0) อาร์กิวเมนต์ make_trail และ retain ถูกกำหนดเอาไว้เพื่อให้เห็นวิถีการเคลื่อนที่ในช่วง 10 เฟรมที่ผ่านมา คุณสมบัติ vel และ acc</vel> เก็บค่าเวกเตอร์ความเร็วและความเร่งตามลำดับ เพื่อนำไปใช้ในการคำนวณการเคลื่อนที่ของลูกตะกร้อต่อไป
  • บรรทัดที่ 20-22 นิยามเมท็อด move เพื่ออัพเดตตำแหน่งและความเร็วของลูกตะกร้อในช่วงเวลา dt วินาทีที่ผ่านมา การคำนวณเป็นไปตามกฎการเคลื่อนที่ตามที่ได้อธิบายไว้ในวิกิ จำลองการเคลื่อนที่ด้วยคณิตศาสตร์แบบเวกเตอร์ใน VPython
  • บรรทัดที่ 31 สร้างอ็อบเจ็กต์ขึ้นจากคลาส Ball โดยกำหนดให้มีสีเหลือง มีความเร็วต้นตามเวกเตอร์ (5,10) และความเร่ง (0,-5) ซึ่งหมายความว่าเมื่อเริ่มต้นลูกตะกร้อจะเคลื่อนที่ไปทางขวาด้วยความเร็ว 5 หน่วย/วินาที และไปด้านบนด้วยความเร็ว 10 หน่วยต่อวินาที และเสมือนว่าถูกแรงดึงดูดลงด้านล่างด้วยความเร่งในแนวดิ่ง 5 หน่วย/วินาที2 อ็อบเจ็กต์นี้ถูกอ้างอิงด้วยตัวแปร ball
  • บรรทัดที่ 35 สั่งให้ลูกตะกร้ออัพเดตตำแหน่งเมื่อผ่านไป 1 ลูป ซึ่งจากการใช้ฟังก์ชัน vis.rate() เราทราบได้ว่าเวลาจะผ่านไป 1/ANIM_RATE วินาที จึงต้องส่งค่าเวลานี้ไปให้กับเมท็อด Ball.move() เพื่อใช้ในสูตรคำนวณการเคลื่อนที่

ทดลองรันสคริปต์จะเห็นว่าลูกตะกร้อปรากฏขึ้นที่พิกัด (0,0) และมีการเคลื่อนที่ไปในทิศทางบนขวาตามความเร็วต้นและความเร่งที่กำหนดให้ ทดลองเปลี่ยนโค้ดในบรรทัดที่ 31 และสังเกตพฤติกรรมที่เปลี่ยนไปของลูกตะกร้อ อย่างไรก็ตามจะเห็นว่าลูกตะกร้อมีการเคลื่อนที่โดยไม่สนใจกำแพงที่สร้างเอาไว้

Takro-2.png

ทำให้ตะกร้อกระดอนกำแพง

เพิ่มโค้ดตรวจสอบขอบเขตของลูกตะกร้อตามแนวกำแพงและเพดาน เมื่อพบว่าลูกเคลื่อนที่เลยขอบให้ดึงลูกกลับเข้ามาติดกับขอบแล้วเปลี่ยนทิศทางความเร็วตามแนวที่ตกกระทบโดยคงอัตราเร็วไว้เท่าเดิม

10 class Ball(vis.sphere):
11 
12     BALL_SIZE = 0.5
13 
14     def __init__(self,color=vis.color.white,vel=(0,0),acc=(0,0)):
15         vis.sphere.__init__(self,pos=(0,0),radius=self.BALL_SIZE,color=color,
16                 make_trail=True,retain=10)
17         self.vel = vis.vector(vel)
18         self.acc = vis.vector(acc)
19 
20     def move(self,dt):
21         self.vel += self.acc*dt
22         self.pos += self.vel*dt
23         if self.pos.x+self.BALL_SIZE > BORDER:  # hit right wall
24             self.pos.x = BORDER-self.BALL_SIZE
25             self.vel.x = -abs(self.vel.x)
26         if self.pos.x-self.BALL_SIZE < -BORDER: # hit left wall
27             self.pos.x = -BORDER+self.BALL_SIZE
28             self.vel.x = +abs(self.vel.x)
29         if self.pos.y+self.BALL_SIZE > BORDER:  # hit ceiling
30             self.pos.y = BORDER-self.BALL_SIZE
31             self.vel.y = -abs(self.vel.y)

สร้างผู้เล่น

คลาส Player

33 ################################################################
34 class Player(vis.cylinder):
35 
36     def __init__(self,controller):
37         vis.cylinder.__init__(self,pos=(0,-BORDER),axis=(0,0.2),
38                 radius=2,color=vis.color.blue)
39         self.controller = controller
40 
41     def move(self):
42         if self.controller == None:
43             self.pos.x = ball.pos.x
44 
45     def hitBall(self,ball):
46         return ball.pos.y-BALL_SIZE < (self.pos.y+self.axis.y) and \
47                 abs(ball.pos.x-self.pos.x) < self.radius
48 
49 ################################################################
50 border = vis.curve(pos=[
51     (-BORDER,-BORDER),
52     (-BORDER,+BORDER),
53     (+BORDER,+BORDER),
54     (+BORDER,-BORDER)
55     ],radius=0.1,color=vis.color.red)
56 ball = Ball(color=vis.color.yellow,vel=(5,10),acc=(0,-5))
57 player = Player(None)
58  
59 while True:
60     vis.rate(ANIM_RATE)
61     player.move()
62     ball.move(1.0/ANIM_RATE)