ผลต่างระหว่างรุ่นของ "Oop lab/arcade/snake"

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
แถว 258: แถว 258:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
จากนั้นให้เขียนเมทอด <tt>on_key_press</tt> ใน World ให้ปรับทิศของงูตามปุ่มที่กด
+
จากนั้นใน <tt>models.py</tt> ให้เพิ่มบรรทัดด้านล่างตอนต้น
 +
 
 +
import arcade.key
 +
 
 +
เพื่อนำเข้าค่าคงที่ของปุ่มกด แล้วให้เขียนเมทอด <tt>on_key_press</tt> ใน World ให้ปรับทิศของงูตามปุ่มที่กด
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">

รุ่นแก้ไขเมื่อ 02:12, 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 แล้วก็

Gitmark.png commit งานด้วย!

sprite และ snake

เราจะใช้รูปด้านล่างขนาด 16 x 16 แทนตัวงู

Block.png

ดาวน์โหลดที่ [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()

ทดลองรัน

ทดลองรัน ถ้าทำงานได้ อย่าลืมเพิ่มไฟล์รูป และ

Gitmark.png commit งานด้วย

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()

ทดลองรัน

ถ้าพบว่างูวิ่งเร็วมาก แปลว่าทำงานได้ ให้

Gitmark.png commit งานด้วย

วิ่งเป็นจังหวะ

เราจะแก้ให้งูค่อย ๆ ขยับเป็นจังหวะ แนวคิดหลัก ๆ คือเราจะสะสมเวลา 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

ทดลองรัน

ถ้าทำงานได้ อย่าลืม

Gitmark.png commit

บังคับทิศทาง

เราจะเพิ่มค่าคงที่สำหรับจัดการทิศทางไว้ตอนต้นของ 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 จุด

ทดลองรัน

ถ้าทำงานได้ อย่าลืม

Gitmark.png commit

จัดการกับการกดปุ่ม

ใน 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

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

ทดลองรัน

ถ้าทำงานได้ อย่าลืม

Gitmark.png commit

งูที่ยาวขึ้น

กินอาหาร