ผลต่างระหว่างรุ่นของ "สร้างเกมด้วย Pygame"
Chaiporn (คุย | มีส่วนร่วม) |
Chaiporn (คุย | มีส่วนร่วม) |
||
(ไม่แสดง 56 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน) | |||
แถว 1: | แถว 1: | ||
+ | : ''วิกินี้เป็นส่วนหนึ่งของรายวิชา [[01204223]]'' | ||
+ | |||
[http://pygame.org Pygame] เป็นโมดูลภาษาไพทอนที่ออกแบบมาเพื่อความสะดวกในการพัฒนาเกม วิกินี้ยกตัวอย่างการสร้างเกมอย่างง่ายที่อาศัยบอร์ดไมโครคอนโทรลเลอร์ในการควบคุมผู้เล่น | [http://pygame.org Pygame] เป็นโมดูลภาษาไพทอนที่ออกแบบมาเพื่อความสะดวกในการพัฒนาเกม วิกินี้ยกตัวอย่างการสร้างเกมอย่างง่ายที่อาศัยบอร์ดไมโครคอนโทรลเลอร์ในการควบคุมผู้เล่น | ||
− | == | + | == การเตรียมตัว == |
− | + | === ติดตั้งไลบรารี Pygame === | |
+ | <b>ระบบปฏิบัติการ Ubuntu Linux</b> ใช้คำสั่ง <tt>apt-get</tt> ติดตั้งได้โดยตรง | ||
sudo apt-get install python-pygame | sudo apt-get install python-pygame | ||
− | + | <b>ระบบปฏิบัตการ Mac OS X</b> ดาวน์โหลดตัวติดตั้งจากเว็บไซท์ [http://pygame.org/download.shtml http://pygame.org/download.shtml] | |
+ | * ดาวน์โหลดตัวติดตั้งที่ใช้งานร่วมกับไพทอนที่มีมาให้กับ OS X อยู่แล้ว โดยเลือกให้ตรงกับเวอร์ชันของไพทอนในเครื่อง เช่นไพทอนเวอร์ชัน 2.7 ให้ดาวน์โหลดไฟล์ [http://www.pygame.org/ftp/pygame-1.9.2pre-py2.7-macosx10.7.mpkg.zip pygame-1.9.2pre-py2.7-macosx10.7.mpkg.zip] | ||
+ | * ติดตั้งไลบรารี [http://xquartz.macosforge.org/landing/ XQuartz] เพิ่มเติม | ||
+ | |||
+ | === เตรียมบอร์ดไมโครคอนโทรลเลอร์ === | ||
+ | บอร์ดไมโครคอนโทรลเลอร์ที่นำมาใช้เป็นตัวควบคุมผู้เล่นในวิกินี้ต้องถูกโปรแกรมเฟิร์มแวร์ให้สามารถอ่านค่าแสงผ่านพอร์ท USB ได้แล้ว ให้แน่ใจว่า | ||
+ | * ได้พัฒนาเฟิร์มแวร์ตามขั้นตอนของวิกิ [[การติดต่อกับบอร์ด MCU ผ่าน USB ด้วย Arduino]] หรือ [[การจำลองบอร์ด MCU เป็นอุปกรณ์ USB]] (ภาษาซีล้วน) | ||
+ | * เฟิร์มแวร์รองรับการอ่านค่าแสง และได้แก้ไขโมดูล <tt>peri.py</tt> ให้สามารถอ่านค่าแสงในช่วง 0-1023 จากเมท็อด <tt>getLight()</tt> ได้อย่างถูกต้องตามที่ระบุไว้ในแบบฝึกหัดท้ายสไลด์บรรยาย [http://www.cpe.ku.ac.th/~cpj/204223/slides/h7-usb.pdf การสื่อสารกับบอร์ด MCU ผ่านพอร์ต USB] | ||
== เกมตัวอย่าง: สควอช == | == เกมตัวอย่าง: สควอช == | ||
แถว 18: | แถว 28: | ||
|} | |} | ||
− | == | + | == โค้ดต้นแบบ == |
ดาวน์โหลดโค้ดต้นแบบจากลิ้งค์ [http://www.cpe.ku.ac.th/~cpj/204223/squash.py http://www.cpe.ku.ac.th/~cpj/204223/squash.py] แล้วนำมาบันทึกไว้ในไดเรคตอรีเดียวกันกับโมดูล <tt>practicum.py</tt> และ <tt>peri.py</tt> ที่ได้มาจากการปฏิบัติตามขั้นตอนในวิกิ [[การติดต่อกับบอร์ด MCU ผ่าน USB ด้วย Arduino]] ทดลองรันโปรแกรมด้วยไพทอน | ดาวน์โหลดโค้ดต้นแบบจากลิ้งค์ [http://www.cpe.ku.ac.th/~cpj/204223/squash.py http://www.cpe.ku.ac.th/~cpj/204223/squash.py] แล้วนำมาบันทึกไว้ในไดเรคตอรีเดียวกันกับโมดูล <tt>practicum.py</tt> และ <tt>peri.py</tt> ที่ได้มาจากการปฏิบัติตามขั้นตอนในวิกิ [[การติดต่อกับบอร์ด MCU ผ่าน USB ด้วย Arduino]] ทดลองรันโปรแกรมด้วยไพทอน | ||
แถว 29: | แถว 39: | ||
* หากรับลูกพลาดและลูกกระทบกำแพงด้านซ้ายมือถือเป็นการจบเกม | * หากรับลูกพลาดและลูกกระทบกำแพงด้านซ้ายมือถือเป็นการจบเกม | ||
* กดปุ่ม ESC เพื่อออกจากเกมได้ตลอดเวลา | * กดปุ่ม ESC เพื่อออกจากเกมได้ตลอดเวลา | ||
+ | |||
+ | เราจะใช้โค้ดนี้เป็นฐานในการเพิ่มฟีเจอร์อื่น ๆ ให้กับเกม | ||
+ | |||
+ | == ควบคุมผู้เล่นด้วยบอร์ดไมโครคอนโทรลเลอร์ == | ||
+ | เริ่มต้นด้วยการอิมพอร์ตฟังก์ชัน <tt>findDevices()</tt> และคลาส <tt>PeriBoard</tt> จากโมดูล <tt>practicum</tt> และ <tt>peri</tt> ตามลำดับ | ||
+ | import pygame | ||
+ | from pygame.locals import * | ||
+ | <span style="color:green;"><b>from practicum import findDevices | ||
+ | from peri import PeriBoard | ||
+ | from usb import USBError</b></span> | ||
+ | |||
+ | จากนั้นทำให้ระบุว่าได้ว่าอ็อบเจกต์ <tt>Player</tt> ที่สร้างขึ้นจะผูกกับบอร์ดไมโครคอนโทรลเลอร์ใด โดยกำหนดไว้ในคอนสตรัคเตอร์ของคลาส <tt>Player</tt> และเพิ่มเมท็อต <tt>move()</tt> เพื่อให้เมนลูปเรียกใช้ในการคำนวณตำแหน่งของผู้เล่นตามความเข้มแสง | ||
+ | class Player(object): | ||
+ | |||
+ | THICKNESS = 10 | ||
+ | |||
+ | def __init__(self, <span style="color:green;"><b>board,</b></span> pos=WINDOW_SIZE[1]/2, width=100, color=WHITE): | ||
+ | self.width = width | ||
+ | self.pos = pos | ||
+ | self.color = color | ||
+ | <span style="color:green;"><b>self.board = board</b></span> | ||
+ | |||
+ | <span style="color:green;"><b>def move(self): | ||
+ | try: | ||
+ | self.pos = self.board.getLight() | ||
+ | except USBError: | ||
+ | pass</b></span> | ||
+ | สังเกตว่ามีการใช้บล็อก <tt>try..except</tt> ครอบการเรียกใช้เมท็อด <tt>getLight()</tt> เอาไว้เพื่อมองข้าม exception <tt>UsbError</tt> ที่เกิดจากการที่บางครั้งบอร์ดไมโครคอนโทรลเลอร์ไม่ตอบสนองต่อการร้องขอที่ส่งไปจากโฮสท์ | ||
+ | |||
+ | ในฟังก์ชัน <tt>main()</tt> ให้เรียกฟังก์ชัน <tt>findDevices()</tt> เพื่อค้นหาบอร์ดไมโครคอนโทรลเลอร์ และนำบอร์ดแรกที่พบมาสร้างเป็นอ็อบเจกต์ขึ้นจากคลาส <tt>PeriBoard</tt> จากนั้นให้ผูกบอร์ดนี้เข้ากับอ็อบเจกต์ <tt>Player</tt> ที่สร้างขึ้น ในลูป while ให้ยกเลิกการกำหนดตำแหน่งไม้ตีจากปุ่มลูกศร และเรียกเมท็อด <tt>move()</tt> ของอ็อบเจกต์ <tt>Player</tt> แทนเพื่อกำหนดค่าตำแหน่งไม้ตีจากความเข้มแสง | ||
+ | def main(): | ||
+ | <i>:</i> | ||
+ | ball = Ball(speed=(200,50)) | ||
+ | <span style="color:green;"><b>board = PeriBoard(findDevices()[0])</b></span> | ||
+ | player = Player(<span style="color:green;"><b>board,</b></span> color=pygame.Color('green'),pos=100) | ||
+ | |||
+ | while not game_over: | ||
+ | for event in pygame.event.get(): # process events | ||
+ | if (event.type == QUIT) or \ | ||
+ | (event.type == KEYDOWN and event.key == K_ESCAPE): | ||
+ | game_over = True | ||
+ | |||
+ | <span style="color:red;"><s>if pygame.key.get_pressed()[K_UP]:</s></span> | ||
+ | <span style="color:red;"><s>player.pos -= 5</s></span> | ||
+ | <span style="color:red;"><s>elif pygame.key.get_pressed()[K_DOWN]:</s></span> | ||
+ | <span style="color:red;"><s>player.pos += 5</s></span> | ||
+ | |||
+ | display.fill(BLACK) # clear screen | ||
+ | display.blit(score_image, (10,10)) # draw score | ||
+ | <span style="color:green;"><b>player.move() # move player</b></span> | ||
+ | player.draw(display) # draw player | ||
+ | |||
+ | จะเห็นว่าโค้ดข้างต้นถือว่าต้องมีบอร์ดไมโครคอนโทรลเลอร์เสียบอยู่อย่างน้อยหนึ่งบอร์ดเสมอ โปรแกรมจะแสดงความผิดพลาดและจบการทำงานทันทีหากไม่มีบอร์ดเสียบอยู่ | ||
+ | |||
+ | ทดลองเสียบบอร์ดและรันโปรแกรม ตอนนี้ไม้ตีควรเลื่อนไปมาได้จากการเอามือบังแสงไปมา | ||
+ | |||
+ | == ลดการส่ายของไม้ตี == | ||
+ | แม้ว่าเกมที่ปรับแก้ไปในขั้นตอนที่แล้วจะทำให้เราควบคุมไม้ตีด้วยความเข้มแสงได้ แต่จะเห็นว่าตำแหน่งไม้ตีค่อนข้างสั่นไปมา โดยเฉพาะอย่างยิ่งหากใช้งานในห้องที่ใช้หลอดไฟแบบฟลูออเรสเซ้นท์ที่มีการกระพริบถี่ ๆ ตลอดเวลา เราสามารถเกลี่ยตำแหน่งไม้ตีให้เรียบขึ้นได้โดยอาศัยกลไก [http://en.wikipedia.org/wiki/Exponential_smoothing exponential smoothing] ซึ่งมีสูตรดังนี้ | ||
+ | |||
+ | <math> | ||
+ | \begin{align} | ||
+ | s_0& = x_0\\ | ||
+ | s_{t}& = \alpha x_t + (1-\alpha)s_{t-1} | ||
+ | \end{align} | ||
+ | </math> | ||
+ | |||
+ | โดยที่ α มีค่าอยู่ระหว่าง 0 ถึง 1 ลำดับ <i>x<sub>0</sub></i>,<i>x<sub>1</sub></i>,<i>x<sub>2</sub></i>,... เป็นข้อมูลดิบ ส่วนลำดับ <i>s<sub>0</sub></i>,<i>s<sub>1</sub></i>,<i>s<sub>2</sub></i>,... เป็นลำดับที่ผ่านการปรับให้เรียบแล้ว | ||
+ | |||
+ | สูตรนี้เป็นการนำเอาค่าก่อนหน้ามาคำนวณแบบถ่วงน้ำหนักร่วมกับค่าที่อ่านได้ในปัจจุบัน โดยหากกำหนดให้ α มีค่ามากทำให้ค่า <i>s<sub>t</sub></i> (ในที่นี้คือตำแหน่งปัจจุบันของไม้ตี) ขึ้นอยู่กับค่าก่อนหน้าค่อนข้างมาก มีผลทำให้การเปลี่ยนแปลงค่าของ <i>s<sub>t</sub></i> เป็นไปอย่างช้า ๆ และราบเรียบ แต่ก็ทำให้การตอบสนองต่อค่า <i>x<sub>t</sub></i> ที่เข้ามาใหม่ (ในที่นี้คือค่าความเข้มแสง) ช้าลงเช่นกัน | ||
+ | |||
+ | โค้ดด้านล่างนำเอาสูตร exponential smoothing มาประยุกต์ใช้ โดยให้ α มีค่าเท่ากับ 0.1 คือให้ความสำคัญกับค่าใหม่ 10% และค่าเก่า 90% | ||
+ | class Player(object): | ||
+ | <i>:</i> | ||
+ | def move(self): | ||
+ | try: | ||
+ | <span style="color:red;"><s>self.pos = self.board.getLight()</s></span> | ||
+ | <span style="color:green;"><b>self.pos = 0.1*self.board.getLight() + 0.9*self.pos</b></span> | ||
+ | except: | ||
+ | pass | ||
+ | ลองรันโปรแกรมและสังเกตการเปลี่ยนแปลงตำแหน่งของไม้ตี เทียบกับการที่ไม่มีการใช้ exponential smoothing ทดลองกับ α ค่าอื่น ๆ เพื่อดูพฤติกรรมการตอบสนองของไม้ตี | ||
+ | |||
+ | == รองรับผู้เล่นหลายคน == | ||
+ | ขั้นตอนต่อไปคือทำให้เกมรองรับผู้เล่นได้หลายคน จำนวนไม้ตีที่เพิ่มมากขึ้นอาจสร้างความสับสนให้ผู้เล่นได้มาก เราจะกำหนดสีที่แตกต่างกันให้กับผู้เล่นโดยเริ่มต้นจากการนิยามค่าคงที่เพื่อเก็บรายการสีไว้ดังนี้ | ||
+ | FPS = 50 | ||
+ | WINDOW_SIZE = (500,500) | ||
+ | BLACK = pygame.Color('black') | ||
+ | WHITE = pygame.Color('white') | ||
+ | GREY = pygame.Color('grey') | ||
+ | <span style="color:green;"><b>PLAYER_COLORS = ('green','yellow','red','cyan')</b></span> | ||
+ | |||
+ | ในฟังก์ชัน <tt>main()</tt> กำจัดโค้ดส่วนที่ดึงเฉพาะบอร์ดไมโครคอนโทรลเลอร์บอร์ดแรกที่พบออก แล้วแทนที่ด้วยลูปที่สร้างอ็อบเจกต์ <tt>Player</tt> หนึ่งตัวต่อหนึ่งบอร์ดที่พบ โดยกำหนดสีให้กับผู้เล่นที่สร้างขึ้นตามรายการสี <tt>PLAYER_COLORS</tt> ที่นิยามเอาไว้ตั้งแต่แรก พร้อมทั้งรายงานว่าผู้เล่นรหัสประจำตัวใดกำลังควบคุมไม้ตีสีใด | ||
+ | def main(): | ||
+ | <i>:</i> | ||
+ | ball = Ball(speed=(200,50)) | ||
+ | <span style="color:red;"><s>board = PeriBoard(findDevices()[0])</s></span> | ||
+ | <span style="color:red;"><s>player = Player(board, color=pygame.Color('green'),pos=100)</s></span> | ||
+ | <span style="color:green;"><b>players = [] | ||
+ | for i,dev in enumerate(findDevices()): | ||
+ | color = PLAYER_COLORS[i % len(PLAYER_COLORS)] | ||
+ | board = PeriBoard(dev) | ||
+ | players.append(Player(board,color=pygame.Color(color),pos=100,width=150)) | ||
+ | print "Player#%d (%s): %s" % (i+1, color, board.getDeviceName())</b></span> | ||
+ | สังเกตว่าโค้ดในฟังก์ชัน <tt>main()</tt> มีการเรียกใช้ฟังก์ชันพิเศษของไพทอนคือ <tt>enumerate()</tt> ฟังก์ชันนี้รับรายการใด ๆ แล้วสร้างเป็นรายการใหม่ของคู่ลำดับ (i,d) โดยที่ d เป็นข้อมูลแต่ละตัวในรายการเดิม และ i เป็นลำดับของข้อมูลในรายการที่เริ่มต้นนับจาก 0 | ||
+ | |||
+ | พิจารณาตัวอย่างการใช้งานฟังก์ชัน <tt>enumerate()</tt> ผ่านไพทอนเชลล์ | ||
+ | $ <b>python</b> | ||
+ | >>> <b>data = ['a','b','c']</b> | ||
+ | >>> <b>list(enumerate(data))</b> | ||
+ | [(0, 'a'), (1, 'b'), (2, 'c')] | ||
+ | >>> <b>for i,x in enumerate(data):</b> | ||
+ | ... <b>print i,x</b> | ||
+ | ... | ||
+ | 0 a | ||
+ | 1 b | ||
+ | 2 c | ||
+ | >>> | ||
+ | |||
+ | จากนั้นในลูป while ให้วนลูปอัพเดทและวาดผู้เล่นทั้งหมดที่มี แทนที่จะอัพเดทแค่ผู้เล่นเดียวเหมือนที่ผ่านมา และส่งรายการผู้เล่นทั้งหมดที่มีให้อ็อบเจกต์ <tt>ball</tt> เพื่อให้ลูกบอลคำนวณการเคลื่อนที่ของตัวเองได้จากการพิจารณาตำแหน่งไม้ตีทุกอัน | ||
+ | while not game_over: | ||
+ | <i>:</i> | ||
+ | display.fill(BLACK) # clear screen | ||
+ | display.blit(score_image, (10,10)) # draw score | ||
+ | |||
+ | <span style="color:red;"><s>player.move()</s></span> | ||
+ | <span style="color:red;"><s>player.draw(display) # draw player</s></span> | ||
+ | <span style="color:green;"><b>for p in players: | ||
+ | p.move() # move player | ||
+ | p.draw(display) # draw player</b></span> | ||
+ | |||
+ | ball.move(1./FPS, display, <span style="color:red;"><s>player</s></span> <span style="color:green;"><b>players</b></span>) # move ball | ||
+ | ball.draw(display) # draw ball | ||
+ | |||
+ | เนื่องจากตอนนี้เราส่งผู้เล่นมาเป็นรายการให้อ็อบเจกต์ของคลาส <tt>Ball</tt> จึงต้องมีการคำนวณตำแหน่งและความเร็วจากไม้ตีของผู้เล่นทุกคน ให้แก้ไขเมท็อด <tt>move()</tt> ของคลาส <tt>Ball</tt> ดังนี้ | ||
+ | class Ball(object): | ||
+ | <i>:</i> | ||
+ | def move(self, delta_t, display, <span style="color:red;"><s>player</s></span> <span style="color:green;"><b>players</b></span>): | ||
+ | global score, game_over | ||
+ | self.x += self.vx*delta_t | ||
+ | self.y += self.vy*delta_t | ||
+ | |||
+ | # player-hitting check | ||
+ | <span style="color:red;"><s>if player.can_hit(self):</s></span> | ||
+ | <span style="color:red;"><s>score += 1</s></span> | ||
+ | <span style="color:red;"><s>render_score()</s></span> | ||
+ | <span style="color:red;"><s>self.vx = abs(self.vx) # bounce ball back</s></span> | ||
+ | <span style="color:green;"><b>for p in players:</b></span> | ||
+ | <span style="color:green;"><b>if p.can_hit(self):</b></span> | ||
+ | <span style="color:green;"><b>score += 1</b></span> | ||
+ | <span style="color:green;"><b>render_score()</b></span> | ||
+ | <span style="color:green;"><b>self.vx = abs(self.vx) # bounce ball back</b></span> | ||
+ | |||
+ | จับกลุ่มกับเพื่อน ๆ และลองเสียบบอร์ดไมโครคอนโทรลเลอร์ตั้งแต่สองบอร์ดขึ้นไป รันโปรแกรมเพื่อทดสอบความถูกต้อง | ||
+ | |||
+ | == ปรับความเร็วลูกเมื่อกระทบไม้ตี == | ||
+ | เพื่อเพิ่มอรรถรสในการเล่น เมื่อผู้เล่นสามารถรับลูกได้ควรให้ลูกเพิ่มความเร็วให้มากขึ้น รวมถึงสุ่มให้ลูกกระดอนออกไปในทิศทางที่แตกต่างกัน | ||
+ | |||
+ | === เพิ่มความเร็วลูกหลังถูกตี === | ||
+ | โค้ดด้านล่างเป็นการแก้ไขให้ลูกมีความเร็วเพิ่มขึ้น 20% หลังจากกระทบไม้ แต่จำกัดความเร็วไว้ไม่ให้เกิน 1000 จุดต่อวินาที | ||
+ | class Ball(object): | ||
+ | <i>:</i> | ||
+ | def move(self, delta_t, display, players): | ||
+ | <i>:</i> | ||
+ | for p in players: | ||
+ | if p.can_hit(self): | ||
+ | score += 1 | ||
+ | render_score() | ||
+ | <span style="color:red;"><s>self.vx = abs(self.vx) # bounce ball back</s></span> | ||
+ | <span style="color:green;"><b>self.vx = min(1.2*abs(self.vx),1000) # bounce ball back</b></span> | ||
+ | |||
+ | === ปรับให้ลูกกระดอนตามตำแหน่งที่ตกกระทบไม้ตี === | ||
+ | เพื่อให้ผู้เล่นสามารถควบคุมทิศทางของลูกได้บ้าง เราจะนำเอาตำแหน่งที่ลูกตกกระทบไม้มาพิจารณาว่าอยู่ห่างจากจุดกึ่งกลางของหน้าไม้เท่าใด จากนั้นนำค่าที่ได้ไปรวมกับความเร็วในแกน y เดิมที่มีอยู่แล้ว ผลที่ได้คือเมื่อลูกกระทบบริเวณด้านบนของหน้าไม้จะทำให้ความเร็วในแกน y เปลี่ยนไปในทิศที่ชี้ขึ้น (แต่ลูกอาจจะยังวิ่งไปในทิศทางลงอยู่หากกำลังเคลื่อนที่ลงด้วยความเร็วสูงพอ) และให้ผลตรงกันข้ามเมื่อกระทบบริเวณด้านล่างของหน้าไม้ ปรับตัวคูณจาก 2 เป็นค่าอื่นเพื่อเพิ่มหรือลดระดับการกระดอนตามต้องการ | ||
+ | class Ball(object): | ||
+ | <i>:</i> | ||
+ | def move(self, delta_t, display, players): | ||
+ | <i>:</i> | ||
+ | for p in players: | ||
+ | if p.can_hit(self): | ||
+ | score += 1 | ||
+ | render_score() | ||
+ | self.vx = min(1.2*abs(self.vx),1000) # bounce ball back | ||
+ | <span style="color:green;"><b>self.vy += (self.y-p.pos)*2</b></span> | ||
+ | |||
+ | ทดสอบเกมที่ปรับแก้ไขแล้วเพื่อสังเกตพฤติกรรมของลูกบอล ซึ่งอาจต้องลองรับลูกให้ได้หลาย ๆ ครั้งก่อนจะเริ่มเห็นความเปลี่ยนแปลงที่ชัดเจน เนื่องจากตัวเกมถูกโปรแกรมให้จบการทำงานทันทีที่รับลูกพลาด แนะนำว่าให้คอมเม้นต์โค้ดส่วนที่ตรวจสอบการชนกำแพงด้านซ้ายแล้วจบเกมออกไปก่อนหากไม่ต้องการเริ่มต้นการทดสอบใหม่ทุกครั้งที่รับลูกพลาด | ||
+ | |||
+ | == เพิ่มจำนวนลูก == | ||
+ | เกมที่มีลูกบอลเพียงลูกเดียวแต่มีผู้เล่นหลายคนนั้นค่อนข้างน่าเบื่อ เราจะแก้ไขโปรแกรมให้เพิ่มจำนวนลูกตามต้องการเมื่อกด space bar บอลลูกใหม่จะปรากฏขึ้นที่กึ่งกลางหน้าจอโดยถูกสุ่มให้ความเร็วในแนวดิ่งต่าง ๆ กัน | ||
+ | |||
+ | ในที่นี้เราอาศัยฟังก์ชัน <tt>randrange()</tt> จากโมดูล <tt>random</tt> | ||
+ | <span style="color:green;"><b>from random import randrange</b></span> | ||
+ | import pygame | ||
+ | from pygame.locals import * | ||
+ | from practicum import findDevices | ||
+ | from peri import PeriBoard | ||
+ | |||
+ | เปลี่ยนตัวแปร <tt>ball</tt> ที่ใช้เก็บลูกบอลเพียงลูกเดียวมาเป็นตัวแปร <tt>balls</tt> เพื่อเก็บเป็นลิสต์ของลูกบอลแทน โดยเริ่มต้นจากการมีลูกบอลเพียงหนึ่งลูก | ||
+ | def main(): | ||
+ | <i>:</i> | ||
+ | <span style="color:red;"><s>ball = Ball(speed=(200,50))</s></span> | ||
+ | <span style="color:green;"><b>balls = [Ball(speed=(100,randrange(-50,50)))]</b></span> | ||
+ | |||
+ | ในลูป while ตรวจสอบการเคาะแป้น หากเป็นคีย์ space bar ให้สร้างลูกบอลลูกใหม่ที่มีความเร็วไปในทิศทางขวา 100 จุด/วินาที และสุ่มความเร็วในแนวดิ่งในช่วง -50 ถึง 50 จุด/วินาที จากนั้นเพิ่มอ็อบเจกต์บอลลูกใหม่ลงไปในรายการ <tt>balls</tt> | ||
+ | <i>:</i> | ||
+ | while not game_over: | ||
+ | for event in pygame.event.get(): # process events | ||
+ | if (event.type == QUIT) or \ | ||
+ | (event.type == KEYDOWN and event.key == K_ESCAPE): | ||
+ | game_over = True | ||
+ | <span style="color:green;"><b>if (event.type == KEYDOWN and event.key == K_SPACE):</b></span> | ||
+ | <span style="color:green;"><b>newBall = Ball(speed=(100,randrange(-50,50)))</b></span> | ||
+ | <span style="color:green;"><b>balls.append(newBall)</b></span> | ||
+ | |||
+ | แก้ไขโค้ดให้อัพเดทและวาดลูกบอลทุกลูกในรายการ เป็นอันเสร็จขั้นตอน | ||
+ | <i>:</i> | ||
+ | <span style="color:red;"><s>ball.move(1./FPS, display, players) # move ball</s></span> | ||
+ | <span style="color:red;"><s>ball.draw(display) # draw ball</s></span> | ||
+ | <span style="color:green;"><b>for b in balls:</b></span> | ||
+ | <span style="color:green;"><b>b.move(1./FPS, display, players) # move ball</b></span> | ||
+ | <span style="color:green;"><b>b.draw(display) # draw ball</b></span> | ||
+ | |||
+ | ทดลองเล่นเกมแล้วเคาะแป้น space bar บอลลูกใหม่ต้องปรากฏขึ้นที่กึ่งกลางหน้าต่างเกม | ||
+ | |||
+ | == เพิ่มแรงโน้มถ่วงตามแนวดิ่ง == | ||
+ | ทำให้ลูกบอลเคลื่อนที่อยู่ภายใต้แรงโน้มถ่วงที่มีความเร่ง 100 จุด/วินาที<sup>2</sup> โดยปรับโค้ดการคำนวณการเคลื่อนที่ให้เพิ่มความเร็วในแกน y ของลูกบอลดังนี้ | ||
+ | class Ball(object): | ||
+ | <i>:</i> | ||
+ | def move(self, delta_t, display, players): | ||
+ | global score, game_over | ||
+ | <span style="color:green;"><b>self.vy += 100*delta_t</b></span> | ||
+ | self.x += self.vx*delta_t | ||
+ | self.y += self.vy*delta_t | ||
+ | |||
+ | ทดสอบโปรแกรมควรจะเห็นว่าลูกบอลเคลื่อนที่เป็นวิถีโค้งแบบพาราโบลา ทดลองคิดสูตรความเร่งในรูปแบบต่าง ๆ เช่นมีความเร่งเข้าสู่จุดศูนย์กลางของหน้าจอเกม เสมือนว่ามีหลุมดำอยู่ ณ จุดนั้น | ||
+ | |||
+ | == ตัวอย่างแนวคิดอื่น ๆ การการปรับปรุงเกม == | ||
+ | * เนื่องจากสภาพแสงในขณะเล่น เช่นแสงน้อยเกินไปหรือมากเกินไป มีผลต่อการควบคุม อาจมีโหมดให้ผู้เล่นวัดช่วงแสง (calibrate) ก่อนเริ่มเล่นเกม | ||
+ | * กระจายให้ผู้เล่นคุมกำแพงกันคนละทิศ | ||
+ | * ปรับให้เล่นแบบแข่งขันกันและคิดคะแนนแยกตามผู้เล่น | ||
+ | * ยอมให้ผู้เล่นรับลูกพลาดได้มากกว่าหนึ่งครั้ง อาจอาศัย LED บนบอร์ดพ่วงแสดงชีวิตที่เหลือ | ||
+ | * สุ่มให้มีไอเท็มพิเศษปรากฏขึ้นเพื่อเพิ่ม/ลดความสามารถของผู้เล่นที่เก็บได้ | ||
+ | * เปลี่ยนรูปแบบการเล่นจากการตีสควอชเป็นเตะตะกร้อลอดห่วง | ||
+ | |||
+ | == เอกสารเพิ่มเติม == | ||
+ | * [http://www.pygame.org/docs/ เอกสารอธิบายการใช้งานไลบรารี Pygame] |
รุ่นแก้ไขปัจจุบันเมื่อ 10:27, 29 พฤศจิกายน 2557
- วิกินี้เป็นส่วนหนึ่งของรายวิชา 01204223
Pygame เป็นโมดูลภาษาไพทอนที่ออกแบบมาเพื่อความสะดวกในการพัฒนาเกม วิกินี้ยกตัวอย่างการสร้างเกมอย่างง่ายที่อาศัยบอร์ดไมโครคอนโทรลเลอร์ในการควบคุมผู้เล่น
เนื้อหา
การเตรียมตัว
ติดตั้งไลบรารี Pygame
ระบบปฏิบัติการ Ubuntu Linux ใช้คำสั่ง apt-get ติดตั้งได้โดยตรง
sudo apt-get install python-pygame
ระบบปฏิบัตการ Mac OS X ดาวน์โหลดตัวติดตั้งจากเว็บไซท์ http://pygame.org/download.shtml
- ดาวน์โหลดตัวติดตั้งที่ใช้งานร่วมกับไพทอนที่มีมาให้กับ OS X อยู่แล้ว โดยเลือกให้ตรงกับเวอร์ชันของไพทอนในเครื่อง เช่นไพทอนเวอร์ชัน 2.7 ให้ดาวน์โหลดไฟล์ pygame-1.9.2pre-py2.7-macosx10.7.mpkg.zip
- ติดตั้งไลบรารี XQuartz เพิ่มเติม
เตรียมบอร์ดไมโครคอนโทรลเลอร์
บอร์ดไมโครคอนโทรลเลอร์ที่นำมาใช้เป็นตัวควบคุมผู้เล่นในวิกินี้ต้องถูกโปรแกรมเฟิร์มแวร์ให้สามารถอ่านค่าแสงผ่านพอร์ท USB ได้แล้ว ให้แน่ใจว่า
- ได้พัฒนาเฟิร์มแวร์ตามขั้นตอนของวิกิ การติดต่อกับบอร์ด MCU ผ่าน USB ด้วย Arduino หรือ การจำลองบอร์ด MCU เป็นอุปกรณ์ USB (ภาษาซีล้วน)
- เฟิร์มแวร์รองรับการอ่านค่าแสง และได้แก้ไขโมดูล peri.py ให้สามารถอ่านค่าแสงในช่วง 0-1023 จากเมท็อด getLight() ได้อย่างถูกต้องตามที่ระบุไว้ในแบบฝึกหัดท้ายสไลด์บรรยาย การสื่อสารกับบอร์ด MCU ผ่านพอร์ต USB
เกมตัวอย่าง: สควอช
เกมที่เราจะใช้เป็นตัวอย่างเรียกว่า Squash ดัดแปลงมาจากเกม Pong ที่เป็นคลาสสิคสุดฮิต ลักษณะการเล่นจะเป็นผู้เล่นตั้งแต่หนึ่งคนขึ้นไปตีลูกกระทบกำแพง และพยายามรับลูกที่สะท้อนกลับมาให้ได้
โค้ดต้นแบบ
ดาวน์โหลดโค้ดต้นแบบจากลิ้งค์ http://www.cpe.ku.ac.th/~cpj/204223/squash.py แล้วนำมาบันทึกไว้ในไดเรคตอรีเดียวกันกับโมดูล practicum.py และ peri.py ที่ได้มาจากการปฏิบัติตามขั้นตอนในวิกิ การติดต่อกับบอร์ด MCU ผ่าน USB ด้วย Arduino ทดลองรันโปรแกรมด้วยไพทอน
python squash.py
ควรปรากฏผลลัพธ์ดังรูปตัวอย่างข้างต้น เกมต้นแบบมีกติกาและการควบคุมดังนี้
- มีผู้เล่นคนเดียว
- ใช้ปุ่มลูกศรขึ้น/ลงเลื่อนไม้ตีของผู้เล่นเพื่อรับลูก
- ทุกครั้งที่รับลูกได้จะได้คะแนนเพิ่ม 1 คะแนน
- หากรับลูกพลาดและลูกกระทบกำแพงด้านซ้ายมือถือเป็นการจบเกม
- กดปุ่ม ESC เพื่อออกจากเกมได้ตลอดเวลา
เราจะใช้โค้ดนี้เป็นฐานในการเพิ่มฟีเจอร์อื่น ๆ ให้กับเกม
ควบคุมผู้เล่นด้วยบอร์ดไมโครคอนโทรลเลอร์
เริ่มต้นด้วยการอิมพอร์ตฟังก์ชัน findDevices() และคลาส PeriBoard จากโมดูล practicum และ peri ตามลำดับ
import pygame
from pygame.locals import *
from practicum import findDevices
from peri import PeriBoard
from usb import USBError
จากนั้นทำให้ระบุว่าได้ว่าอ็อบเจกต์ Player ที่สร้างขึ้นจะผูกกับบอร์ดไมโครคอนโทรลเลอร์ใด โดยกำหนดไว้ในคอนสตรัคเตอร์ของคลาส Player และเพิ่มเมท็อต move() เพื่อให้เมนลูปเรียกใช้ในการคำนวณตำแหน่งของผู้เล่นตามความเข้มแสง
class Player(object): THICKNESS = 10 def __init__(self, board, pos=WINDOW_SIZE[1]/2, width=100, color=WHITE): self.width = width self.pos = pos self.color = color self.board = board def move(self): try: self.pos = self.board.getLight() except USBError: pass
สังเกตว่ามีการใช้บล็อก try..except ครอบการเรียกใช้เมท็อด getLight() เอาไว้เพื่อมองข้าม exception UsbError ที่เกิดจากการที่บางครั้งบอร์ดไมโครคอนโทรลเลอร์ไม่ตอบสนองต่อการร้องขอที่ส่งไปจากโฮสท์
ในฟังก์ชัน main() ให้เรียกฟังก์ชัน findDevices() เพื่อค้นหาบอร์ดไมโครคอนโทรลเลอร์ และนำบอร์ดแรกที่พบมาสร้างเป็นอ็อบเจกต์ขึ้นจากคลาส PeriBoard จากนั้นให้ผูกบอร์ดนี้เข้ากับอ็อบเจกต์ Player ที่สร้างขึ้น ในลูป while ให้ยกเลิกการกำหนดตำแหน่งไม้ตีจากปุ่มลูกศร และเรียกเมท็อด move() ของอ็อบเจกต์ Player แทนเพื่อกำหนดค่าตำแหน่งไม้ตีจากความเข้มแสง
def main(): : ball = Ball(speed=(200,50)) board = PeriBoard(findDevices()[0]) player = Player(board, color=pygame.Color('green'),pos=100) while not game_over: for event in pygame.event.get(): # process events if (event.type == QUIT) or \ (event.type == KEYDOWN and event.key == K_ESCAPE): game_over = Trueif pygame.key.get_pressed()[K_UP]:player.pos -= 5elif pygame.key.get_pressed()[K_DOWN]:player.pos += 5display.fill(BLACK) # clear screen display.blit(score_image, (10,10)) # draw score player.move() # move player player.draw(display) # draw player
จะเห็นว่าโค้ดข้างต้นถือว่าต้องมีบอร์ดไมโครคอนโทรลเลอร์เสียบอยู่อย่างน้อยหนึ่งบอร์ดเสมอ โปรแกรมจะแสดงความผิดพลาดและจบการทำงานทันทีหากไม่มีบอร์ดเสียบอยู่
ทดลองเสียบบอร์ดและรันโปรแกรม ตอนนี้ไม้ตีควรเลื่อนไปมาได้จากการเอามือบังแสงไปมา
ลดการส่ายของไม้ตี
แม้ว่าเกมที่ปรับแก้ไปในขั้นตอนที่แล้วจะทำให้เราควบคุมไม้ตีด้วยความเข้มแสงได้ แต่จะเห็นว่าตำแหน่งไม้ตีค่อนข้างสั่นไปมา โดยเฉพาะอย่างยิ่งหากใช้งานในห้องที่ใช้หลอดไฟแบบฟลูออเรสเซ้นท์ที่มีการกระพริบถี่ ๆ ตลอดเวลา เราสามารถเกลี่ยตำแหน่งไม้ตีให้เรียบขึ้นได้โดยอาศัยกลไก exponential smoothing ซึ่งมีสูตรดังนี้
โดยที่ α มีค่าอยู่ระหว่าง 0 ถึง 1 ลำดับ x0,x1,x2,... เป็นข้อมูลดิบ ส่วนลำดับ s0,s1,s2,... เป็นลำดับที่ผ่านการปรับให้เรียบแล้ว
สูตรนี้เป็นการนำเอาค่าก่อนหน้ามาคำนวณแบบถ่วงน้ำหนักร่วมกับค่าที่อ่านได้ในปัจจุบัน โดยหากกำหนดให้ α มีค่ามากทำให้ค่า st (ในที่นี้คือตำแหน่งปัจจุบันของไม้ตี) ขึ้นอยู่กับค่าก่อนหน้าค่อนข้างมาก มีผลทำให้การเปลี่ยนแปลงค่าของ st เป็นไปอย่างช้า ๆ และราบเรียบ แต่ก็ทำให้การตอบสนองต่อค่า xt ที่เข้ามาใหม่ (ในที่นี้คือค่าความเข้มแสง) ช้าลงเช่นกัน
โค้ดด้านล่างนำเอาสูตร exponential smoothing มาประยุกต์ใช้ โดยให้ α มีค่าเท่ากับ 0.1 คือให้ความสำคัญกับค่าใหม่ 10% และค่าเก่า 90%
class Player(object): : def move(self): try:self.pos = self.board.getLight()self.pos = 0.1*self.board.getLight() + 0.9*self.pos except: pass
ลองรันโปรแกรมและสังเกตการเปลี่ยนแปลงตำแหน่งของไม้ตี เทียบกับการที่ไม่มีการใช้ exponential smoothing ทดลองกับ α ค่าอื่น ๆ เพื่อดูพฤติกรรมการตอบสนองของไม้ตี
รองรับผู้เล่นหลายคน
ขั้นตอนต่อไปคือทำให้เกมรองรับผู้เล่นได้หลายคน จำนวนไม้ตีที่เพิ่มมากขึ้นอาจสร้างความสับสนให้ผู้เล่นได้มาก เราจะกำหนดสีที่แตกต่างกันให้กับผู้เล่นโดยเริ่มต้นจากการนิยามค่าคงที่เพื่อเก็บรายการสีไว้ดังนี้
FPS = 50
WINDOW_SIZE = (500,500)
BLACK = pygame.Color('black')
WHITE = pygame.Color('white')
GREY = pygame.Color('grey')
PLAYER_COLORS = ('green','yellow','red','cyan')
ในฟังก์ชัน main() กำจัดโค้ดส่วนที่ดึงเฉพาะบอร์ดไมโครคอนโทรลเลอร์บอร์ดแรกที่พบออก แล้วแทนที่ด้วยลูปที่สร้างอ็อบเจกต์ Player หนึ่งตัวต่อหนึ่งบอร์ดที่พบ โดยกำหนดสีให้กับผู้เล่นที่สร้างขึ้นตามรายการสี PLAYER_COLORS ที่นิยามเอาไว้ตั้งแต่แรก พร้อมทั้งรายงานว่าผู้เล่นรหัสประจำตัวใดกำลังควบคุมไม้ตีสีใด
def main(): : ball = Ball(speed=(200,50))board = PeriBoard(findDevices()[0])player = Player(board, color=pygame.Color('green'),pos=100)players = [] for i,dev in enumerate(findDevices()): color = PLAYER_COLORS[i % len(PLAYER_COLORS)] board = PeriBoard(dev) players.append(Player(board,color=pygame.Color(color),pos=100,width=150)) print "Player#%d (%s): %s" % (i+1, color, board.getDeviceName())
สังเกตว่าโค้ดในฟังก์ชัน main() มีการเรียกใช้ฟังก์ชันพิเศษของไพทอนคือ enumerate() ฟังก์ชันนี้รับรายการใด ๆ แล้วสร้างเป็นรายการใหม่ของคู่ลำดับ (i,d) โดยที่ d เป็นข้อมูลแต่ละตัวในรายการเดิม และ i เป็นลำดับของข้อมูลในรายการที่เริ่มต้นนับจาก 0
พิจารณาตัวอย่างการใช้งานฟังก์ชัน enumerate() ผ่านไพทอนเชลล์
$ python >>> data = ['a','b','c'] >>> list(enumerate(data)) [(0, 'a'), (1, 'b'), (2, 'c')] >>> for i,x in enumerate(data): ... print i,x ... 0 a 1 b 2 c >>>
จากนั้นในลูป while ให้วนลูปอัพเดทและวาดผู้เล่นทั้งหมดที่มี แทนที่จะอัพเดทแค่ผู้เล่นเดียวเหมือนที่ผ่านมา และส่งรายการผู้เล่นทั้งหมดที่มีให้อ็อบเจกต์ ball เพื่อให้ลูกบอลคำนวณการเคลื่อนที่ของตัวเองได้จากการพิจารณาตำแหน่งไม้ตีทุกอัน
while not game_over: : display.fill(BLACK) # clear screen display.blit(score_image, (10,10)) # draw scoreplayer.move()player.draw(display) # draw playerfor p in players: p.move() # move player p.draw(display) # draw player ball.move(1./FPS, display,playerplayers) # move ball ball.draw(display) # draw ball
เนื่องจากตอนนี้เราส่งผู้เล่นมาเป็นรายการให้อ็อบเจกต์ของคลาส Ball จึงต้องมีการคำนวณตำแหน่งและความเร็วจากไม้ตีของผู้เล่นทุกคน ให้แก้ไขเมท็อด move() ของคลาส Ball ดังนี้
class Ball(object): : def move(self, delta_t, display,playerplayers): global score, game_over self.x += self.vx*delta_t self.y += self.vy*delta_t # player-hitting checkif player.can_hit(self):score += 1render_score()self.vx = abs(self.vx) # bounce ball backfor p in players: if p.can_hit(self): score += 1 render_score() self.vx = abs(self.vx) # bounce ball back
จับกลุ่มกับเพื่อน ๆ และลองเสียบบอร์ดไมโครคอนโทรลเลอร์ตั้งแต่สองบอร์ดขึ้นไป รันโปรแกรมเพื่อทดสอบความถูกต้อง
ปรับความเร็วลูกเมื่อกระทบไม้ตี
เพื่อเพิ่มอรรถรสในการเล่น เมื่อผู้เล่นสามารถรับลูกได้ควรให้ลูกเพิ่มความเร็วให้มากขึ้น รวมถึงสุ่มให้ลูกกระดอนออกไปในทิศทางที่แตกต่างกัน
เพิ่มความเร็วลูกหลังถูกตี
โค้ดด้านล่างเป็นการแก้ไขให้ลูกมีความเร็วเพิ่มขึ้น 20% หลังจากกระทบไม้ แต่จำกัดความเร็วไว้ไม่ให้เกิน 1000 จุดต่อวินาที
class Ball(object): : def move(self, delta_t, display, players): : for p in players: if p.can_hit(self): score += 1 render_score()self.vx = abs(self.vx) # bounce ball backself.vx = min(1.2*abs(self.vx),1000) # bounce ball back
ปรับให้ลูกกระดอนตามตำแหน่งที่ตกกระทบไม้ตี
เพื่อให้ผู้เล่นสามารถควบคุมทิศทางของลูกได้บ้าง เราจะนำเอาตำแหน่งที่ลูกตกกระทบไม้มาพิจารณาว่าอยู่ห่างจากจุดกึ่งกลางของหน้าไม้เท่าใด จากนั้นนำค่าที่ได้ไปรวมกับความเร็วในแกน y เดิมที่มีอยู่แล้ว ผลที่ได้คือเมื่อลูกกระทบบริเวณด้านบนของหน้าไม้จะทำให้ความเร็วในแกน y เปลี่ยนไปในทิศที่ชี้ขึ้น (แต่ลูกอาจจะยังวิ่งไปในทิศทางลงอยู่หากกำลังเคลื่อนที่ลงด้วยความเร็วสูงพอ) และให้ผลตรงกันข้ามเมื่อกระทบบริเวณด้านล่างของหน้าไม้ ปรับตัวคูณจาก 2 เป็นค่าอื่นเพื่อเพิ่มหรือลดระดับการกระดอนตามต้องการ
class Ball(object):
:
def move(self, delta_t, display, players):
:
for p in players:
if p.can_hit(self):
score += 1
render_score()
self.vx = min(1.2*abs(self.vx),1000) # bounce ball back
self.vy += (self.y-p.pos)*2
ทดสอบเกมที่ปรับแก้ไขแล้วเพื่อสังเกตพฤติกรรมของลูกบอล ซึ่งอาจต้องลองรับลูกให้ได้หลาย ๆ ครั้งก่อนจะเริ่มเห็นความเปลี่ยนแปลงที่ชัดเจน เนื่องจากตัวเกมถูกโปรแกรมให้จบการทำงานทันทีที่รับลูกพลาด แนะนำว่าให้คอมเม้นต์โค้ดส่วนที่ตรวจสอบการชนกำแพงด้านซ้ายแล้วจบเกมออกไปก่อนหากไม่ต้องการเริ่มต้นการทดสอบใหม่ทุกครั้งที่รับลูกพลาด
เพิ่มจำนวนลูก
เกมที่มีลูกบอลเพียงลูกเดียวแต่มีผู้เล่นหลายคนนั้นค่อนข้างน่าเบื่อ เราจะแก้ไขโปรแกรมให้เพิ่มจำนวนลูกตามต้องการเมื่อกด space bar บอลลูกใหม่จะปรากฏขึ้นที่กึ่งกลางหน้าจอโดยถูกสุ่มให้ความเร็วในแนวดิ่งต่าง ๆ กัน
ในที่นี้เราอาศัยฟังก์ชัน randrange() จากโมดูล random
from random import randrange
import pygame
from pygame.locals import *
from practicum import findDevices
from peri import PeriBoard
เปลี่ยนตัวแปร ball ที่ใช้เก็บลูกบอลเพียงลูกเดียวมาเป็นตัวแปร balls เพื่อเก็บเป็นลิสต์ของลูกบอลแทน โดยเริ่มต้นจากการมีลูกบอลเพียงหนึ่งลูก
def main(): :ball = Ball(speed=(200,50))balls = [Ball(speed=(100,randrange(-50,50)))]
ในลูป while ตรวจสอบการเคาะแป้น หากเป็นคีย์ space bar ให้สร้างลูกบอลลูกใหม่ที่มีความเร็วไปในทิศทางขวา 100 จุด/วินาที และสุ่มความเร็วในแนวดิ่งในช่วง -50 ถึง 50 จุด/วินาที จากนั้นเพิ่มอ็อบเจกต์บอลลูกใหม่ลงไปในรายการ balls
: while not game_over: for event in pygame.event.get(): # process events if (event.type == QUIT) or \ (event.type == KEYDOWN and event.key == K_ESCAPE): game_over = True if (event.type == KEYDOWN and event.key == K_SPACE): newBall = Ball(speed=(100,randrange(-50,50))) balls.append(newBall)
แก้ไขโค้ดให้อัพเดทและวาดลูกบอลทุกลูกในรายการ เป็นอันเสร็จขั้นตอน
:ball.move(1./FPS, display, players) # move ballball.draw(display) # draw ballfor b in balls: b.move(1./FPS, display, players) # move ball b.draw(display) # draw ball
ทดลองเล่นเกมแล้วเคาะแป้น space bar บอลลูกใหม่ต้องปรากฏขึ้นที่กึ่งกลางหน้าต่างเกม
เพิ่มแรงโน้มถ่วงตามแนวดิ่ง
ทำให้ลูกบอลเคลื่อนที่อยู่ภายใต้แรงโน้มถ่วงที่มีความเร่ง 100 จุด/วินาที2 โดยปรับโค้ดการคำนวณการเคลื่อนที่ให้เพิ่มความเร็วในแกน y ของลูกบอลดังนี้
class Ball(object):
:
def move(self, delta_t, display, players):
global score, game_over
self.vy += 100*delta_t
self.x += self.vx*delta_t
self.y += self.vy*delta_t
ทดสอบโปรแกรมควรจะเห็นว่าลูกบอลเคลื่อนที่เป็นวิถีโค้งแบบพาราโบลา ทดลองคิดสูตรความเร่งในรูปแบบต่าง ๆ เช่นมีความเร่งเข้าสู่จุดศูนย์กลางของหน้าจอเกม เสมือนว่ามีหลุมดำอยู่ ณ จุดนั้น
ตัวอย่างแนวคิดอื่น ๆ การการปรับปรุงเกม
- เนื่องจากสภาพแสงในขณะเล่น เช่นแสงน้อยเกินไปหรือมากเกินไป มีผลต่อการควบคุม อาจมีโหมดให้ผู้เล่นวัดช่วงแสง (calibrate) ก่อนเริ่มเล่นเกม
- กระจายให้ผู้เล่นคุมกำแพงกันคนละทิศ
- ปรับให้เล่นแบบแข่งขันกันและคิดคะแนนแยกตามผู้เล่น
- ยอมให้ผู้เล่นรับลูกพลาดได้มากกว่าหนึ่งครั้ง อาจอาศัย LED บนบอร์ดพ่วงแสดงชีวิตที่เหลือ
- สุ่มให้มีไอเท็มพิเศษปรากฏขึ้นเพื่อเพิ่ม/ลดความสามารถของผู้เล่นที่เก็บได้
- เปลี่ยนรูปแบบการเล่นจากการตีสควอชเป็นเตะตะกร้อลอดห่วง