ผลต่างระหว่างรุ่นของ "Oop lab/arcade/snake"
Jittat (คุย | มีส่วนร่วม) |
Jittat (คุย | มีส่วนร่วม) |
||
แถว 271: | แถว 271: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | ในการเปลี่ยนทิศทาง ให้แก้ attr <tt>direction</tt> ใน self.snake โดยตรงไปก่อนเลย วิธีนี้อาจไม่ใช่วิธีที่ดีที่สุดในทุกกรณี แต่กรณีนี้ทำได้สะดวกและไม่น่ามีปัญหาอะไร | |
+ | |||
+ | นอกจากนี้ในการเขียน ถ้าเป็นไปได้ ให้พยายามเขียนโดยไม่ใช้ if แต่ใช้ dict เหมือนตัวอย่างทิศทางข้างต้น แต่ถ้าจะ if ก็ไม่เป็นไร สังเกตว่าเมื่อกดเปลี่ยนทิศแล้ว งูจะวิ่งทิศทางดังกล่าวไปตลอด | ||
==== ทดลองรัน ==== | ==== ทดลองรัน ==== |
รุ่นแก้ไขเมื่อ 02:16, 15 กันยายน 2560
- หน้านี้เป็นส่วนหนึ่งของ oop lab
เนื้อหา
จุดวิ่ง
ในส่วนแรกเราจะทำงูขนาด 1 ช่องวิ่งไปมาก่อน
เริ่มด้วยเกมว่าง ๆ
ก่อนเริ่ม อย่าลืมสร้าง git repository ไว้ที่ที่จะทำด้วย โดยสั่ง
git init
เราจะเริ่มโดยสร้างคลาส SnakeWindow ว่าง ๆ ไว้ก่อน ทั้งหมดนี้เขียนในไฟล์ snake.py
import arcade
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600
class SnakeWindow(arcade.Window):
def __init__(self, width, height):
super().__init__(width, height)
arcade.set_background_color(arcade.color.BLACK)
if __name__ == '__main__':
window = SnakeWindow(SCREEN_WIDTH, SCREEN_HEIGHT)
arcade.set_window(window)
arcade.run()
ทดลองรัน
ถ้าทดลองรันได้ อย่าลืม git add snake.py แล้วก็
sprite และ snake
เราจะใช้รูปด้านล่างขนาด 16 x 16 แทนตัวงู
ดาวน์โหลดที่ [1] แล้วเซฟในโพลเดอร์ images ในชื่อ block.png
จากนั้นแก้ SnakeWindow ดังนี้
สร้าง arcade.Sprite ใน __init__
def __init__(self, width, height):
# ... ละบรรทัดอื่นไว้
self.snake_sprite = arcade.Sprite('images/block.png')
self.snake_sprite.set_position(300,300)
และสร้างเมท็อด on_draw มาวาด sprite
def on_draw(self):
arcade.start_render()
self.snake_sprite.draw()
ทดลองรัน
ทดลองรัน ถ้าทำงานได้ อย่าลืมเพิ่มไฟล์รูป และ
World, Snake, และ ModelSprite
เราจะแยกโครงสร้างของคลาสในเกมแบบเดียวกับเกม space นั่นคือเราจะมี World เพื่อเก็บข้อมูลของเกมทั้งหมด คลาสงู (Snake) จะอยู่ใน world ส่วนที่แสดงผล ในขั้นแรกนี้เราจะใช้วิธีแบบเดิมคือจะสร้าง ModelSprite เพื่อแสดงงู แต่อีกหน่อยถ้างูยาวขึ้นได้เราจะใช้วิธีอื่น (แต่ ModelSprite ก็ยังจะมีประโยชน์อยู่ในการแสดงผลไม้)
ในส่วนนี้เราจะตัดและแก้จากโค้ด space เลย นิสิตควรจะพิจารณาโค้ดทั้งหมดและพยายามทำความเข้าใจว่าแต่ละส่วนทำงานประสานกันได้อย่างไรก่อนจะทำขั้นถัดไป
ไฟล์ models.py
class Snake:
def __init__(self, world, x, y):
self.world = world
self.x = x
self.y = y
def update(self, delta):
if self.x > self.world.width:
self.x = 0
self.x += 5
class World:
def __init__(self, width, height):
self.width = width
self.height = height
self.snake = Snake(self, width // 2, height // 2)
def update(self, delta):
self.snake.update(delta)
คลาส ModelSprite ใน snake.py ใส่ไว้ก่อน SnakeWindow
class ModelSprite(arcade.Sprite):
def __init__(self, *args, **kwargs):
self.model = kwargs.pop('model', None)
super().__init__(*args, **kwargs)
def sync_with_model(self):
if self.model:
self.set_position(self.model.x, self.model.y)
def draw(self):
self.sync_with_model()
super().draw()
คลาส SnakeWindow ใน snake.py ที่แก้ไขแล้ว
class SnakeWindow(arcade.Window):
def __init__(self, width, height):
super().__init__(width, height)
arcade.set_background_color(arcade.color.BLACK)
self.world = World(SCREEN_WIDTH, SCREEN_HEIGHT)
self.snake_sprite = ModelSprite('images/block.png',
model=self.world.snake)
self.snake_sprite.set_position(300,300)
def update(self, delta):
self.world.update(delta)
def on_draw(self):
arcade.start_render()
self.snake_sprite.draw()
ทดลองรัน
ถ้าพบว่างูวิ่งเร็วมาก แปลว่าทำงานได้ ให้
วิ่งเป็นจังหวะ
เราจะแก้ให้งูค่อย ๆ ขยับเป็นจังหวะ แนวคิดหลัก ๆ คือเราจะสะสมเวลา delta (ซึ่งเป็นเวลาระหว่างการเรียก update แต่ละครั้ง) ไว้จนเกินค่าหนึ่ง แล้วจึงค่อยขยับ เราจะเก็บค่าเวลาที่จะรอไว้ในตัวแปร MOVE_WAIT สังเกตว่าเราจะใช้เป็นตัวใหญ่เพื่อระบุว่าเป็นค่าคงที่ ถ้าอีกหน่อยเราต้องการให้เกมปรับความเร็วได้ เราค่อยแก้ไขส่วนนี้ต่อไป
เราจะประกาศไว้ในคลาส Snake
class Snake:
MOVE_WAIT = 0.2
ตัวแปรที่ประกาศลักษณะนี้จะเป็นตัวแปรที่ติดกับคลาส จะอ้างได้โดยเรียก Snake.MOVE_WAIT หรือจะเรียกผ่านวัตถุของคลาสก็ได้ แต่ถ้ามีการกำหนดค่าจากวัตถุของคลาส ค่านั้นจะไป "แทน" ค่าของคลาส
เราจะเก็บค่าเวลารอใน self.wait_time ซึ่งกำหนดค่าเริ่มต้นเป็น 0 ใน __init__
def __init__(self, world, x, y):
# ละตอนต้นไว้
self.wait_time = 0
เราจะตรวจสอบค่านี้ในเมท็อด update ดังด้านล่าง สังเกตว่าเราแก้ให้งูขยับทีละ 16 จุด (เท่ากับขนาดช่อง)
def update(self, delta):
self.wait_time += delta
if self.wait_time < Snake.MOVE_WAIT:
return
if self.x > self.world.width:
self.x = 0
self.x += 16
self.wait_time = 0
ทดลองรัน
ถ้าทำงานได้ อย่าลืม
บังคับทิศทาง
เราจะเพิ่มค่าคงที่สำหรับจัดการทิศทางไว้ตอนต้นของ models.py
DIR_UP = 1
DIR_RIGHT = 2
DIR_DOWN = 3
DIR_LEFT = 4
DIR_OFFSET = { DIR_UP: (0,1),
DIR_RIGHT: (1,0),
DIR_DOWN: (0,-1),
DIR_LEFT: (-1,0) }
ในคลาส Snake เราจะเพิ่ม attribute direction ไว้เก็บทิศทาง โดยให้เริ่มที่เคลื่อนที่ไปทางขวา
class Snake:
# ... ละส่วนอื่นไว้
def __init__(self, world, x, y):
# ... ละส่วนอื่นไว้
self.direction = DIR_RIGHT
เพื่อให้ไม่ต้องเขียนเลขพิเศษ 16 ในโค้ด เราจะประกาศค่าคงที่ขนาด block ของงูไว้ที่ตอนต้นคลาส Snake ด้วย
class Snake:
BLOCK_SIZE = 16
# ... ละส่วนอื่นไว้
จากนั้นใน update ให้แก้ให้ปรับพิกัด x และ y ตามทิศทาง
def update(self, delta):
self.wait_time += delta
if self.wait_time < Snake.MOVE_WAIT:
return
# เพิ่มโค้ดปรับค่า self.x และ self.y ที่นี่ ให้ใช้ DIR_OFFSET อย่าเขียนโดยใช้ if
self.x ...................................
self.y ...................................
self.wait_time = 0
ให้ทดลองรันว่างูขยับทางขวาหรือไม่ ถ้าได้ให้ลองเปลี่ยนทิศเริ่มต้นของงู (self.direction) ให้เป็นค่าต่าง ๆ เพื่อทดสอบว่าโค้ดที่เขียนถูกต้อง อย่าลืมว่าให้ขยับทีละ Snake.BLOCK_SIZE จุด
ทดลองรัน
ถ้าทำงานได้ อย่าลืม
จัดการกับการกดปุ่ม
ใน SnakeWindow เพิ่มเมทอด on_key_press ดังนี้
# ... ใน SnakeWindow
def on_key_press(self, key, key_modifiers):
self.world.on_key_press(key, key_modifiers)
จากนั้นใน models.py ให้เพิ่มบรรทัดด้านล่างตอนต้น
import arcade.key
เพื่อนำเข้าค่าคงที่ของปุ่มกด แล้วให้เขียนเมทอด on_key_press ใน World ให้ปรับทิศของงูตามปุ่มที่กด
class World:
# ... ละส่วนอื่นไว้
def on_key_press(self, key, key_modifiers):
# เพิ่มโค้ดตรวจสอบปุ่มและเปลี่ยนทิศทางของ self.snake
ในการเปลี่ยนทิศทาง ให้แก้ attr direction ใน self.snake โดยตรงไปก่อนเลย วิธีนี้อาจไม่ใช่วิธีที่ดีที่สุดในทุกกรณี แต่กรณีนี้ทำได้สะดวกและไม่น่ามีปัญหาอะไร
นอกจากนี้ในการเขียน ถ้าเป็นไปได้ ให้พยายามเขียนโดยไม่ใช้ if แต่ใช้ dict เหมือนตัวอย่างทิศทางข้างต้น แต่ถ้าจะ if ก็ไม่เป็นไร สังเกตว่าเมื่อกดเปลี่ยนทิศแล้ว งูจะวิ่งทิศทางดังกล่าวไปตลอด
ทดลองรัน
ถ้าทำงานได้ อย่าลืม