ผลต่างระหว่างรุ่นของ "Prg2/arcade1"

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
แถว 162: แถว 162:
  
 
== A moving circle ==
 
== A moving circle ==
=== วาดวงกลม ===
+
=== Drawing a circle ===
เราจะเริ่มเขียนจากโปรแกรมที่ทำอะไรไม่ค่อยได้ ไปหาโปรแกรมที่ซับซ้อนขึ้น โปรแกรมด้านล่างวาดรูปวงกลมที่กลางหน้าจอ เขียนและเซฟไว้ในไฟล์ชื่อ <tt>cir1.py</tt>
+
We will start from a very simple program and will move on to more functioning programs. The code below draws a circle at the center of the screen. Take the code and save it as <tt>cir1.py</tt>.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 191: แถว 191:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
'''ทดลอง:''' ให้ทดลองลบบรรทัด <tt>arcade.start_render()</tt> ออก ผลเป็นอย่างไร?
+
Try to run it. What do you see?
  
=== เคลื่อนที่ ===
+
'''Experiment:''' Try to remove the line <tt>arcade.start_render()</tt>.  What do you observe?
  
เราจะบังคับให้วงกลมเคลื่อนที่ โดยปรับค่าตำแหน่งตามความเร็วสองแกนคือ vx แทนความเร็วในแนวแกน x และ vy แทนความเร็วในแกน y
+
=== Move ===
  
เนื่องจากเราต้องการเปลี่ยนค่าตำแหน่งวงกลมอย่างต่อเนื่อง เราจึงต้องมีตัวแปรโกลบอลเก็บค่าตำแหน่ง คือ x และ y  ในฟังก์ชัน on_draw เราจะระบุว่าจะแก้ค่าตัวแปรดังกล่าวด้วย keyword '''global''' เราใส่ vx และ vy เผื่อไว้ด้วยเลย (เพราะว่าเราต้องการให้สะท้อนขอบจอ)
+
We will move the circle by having velocities <tt>vx</tt> (for the x-axis velocity) and <tt>vy</tt> (for the y-axis velocity).
  
โค้ดของ on_draw เป็นดังนี้ บรรทัดที่ประกาศ x,y,vx,vy เป็นการกำหนดค่าให้กับตัวแปรโกลบอล
+
Because we would like to keep track of the circle location, we will need global variables <tt>x</tt> and <tt>y</tt> to keep the circle co-ordinate.  We will use global variables for that and in function <tt>on_draw</tt> we will use keyword '''global''' to explicitly tell Python that we will modify these variables.  We will also declare that we will modify <tt>vx</tt> and <tt>vy</tt> (because later on we will let the circle bounce at the edges of the screen.)
 +
 
 +
The updated code for <tt>on_draw</tt> is shown below.  The lines that define <tt>x,y,vx,vy</tt> assign values to global variables.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 218: แถว 220:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== แบบฝึกหัด 1: ชนและสะท้อนที่ขอบ ===
+
=== Exercise 1: Bounce ===
  
 
ตอนนี้ลูกบอลเมื่อวิ่งทะลุขอบจอแล้วจะหายไปเลย ให้เพิ่มการตรวจสอบว่าลูกบอลทะลุจอไปแล้วแล้วปรับให้ลูกบอลเสมือนเด้งกลับมา
 
ตอนนี้ลูกบอลเมื่อวิ่งทะลุขอบจอแล้วจะหายไปเลย ให้เพิ่มการตรวจสอบว่าลูกบอลทะลุจอไปแล้วแล้วปรับให้ลูกบอลเสมือนเด้งกลับมา

รุ่นแก้ไขเมื่อ 18:32, 17 มกราคม 2562

This page is a part of Programming 2 (translated from Oop lab/oop in python)

We will start learning how to use the Arcade library and also get a quick review on OOP concepts.

Installing Arcade

We will use the arcade game library which requires at least Python 3.6.

Python 3.6

Let's make sure that you have a proper version of Python. Let's call python from the command line (or terminal)

python --version

If its version is at least 3.6, you are good to go and you can skill to the next step (pip installation). Otherwise, you have to get Python 3.6.

Installing Python 3.6 on Windows

You can follow this instruction. Don't forget to click "Add Python 3.6 to PATH".

Installing Python 3.6 on Ubuntu (version 16.10 onward)

Call

sudo apt-get update
sudo apt-get install python3.6

You can call python3.6 to start python 3.6.

Installing Python 3.6 on older Ubuntu

sudo add-apt-repository ppa:jonathonf/python-3.6
sudo apt-get update
sudo apt-get install python3.6

Installing Python 3.6 on Mac

Follow this instruction.

Installing pip/pip3

pip is a library package manager for Python. In a system with both Python 3 and Python 2, we would call pip3 to install libraries into Python 3 packages.

Try to call

pip

or

pip3

If it runs, you can skip the pip installation part.

1. Installing pip on Windows

pip จะมาพร้อมกับการติดตั้ง python ให้เลือก option ดังกล่าวด้วย ถ้าเปิด command แล้วเรียก pip ขึ้นก็แสดงว่าได้ติดตั้งเรียบร้อย ถ้าไม่มี ให้ลองติดตั้ง python3 อีกครั้ง และให้เลือก pip ในหน้า Optional Features ด้วย

2. Installing pip3 on Linux

ให้ติดตั้ง pip3 และโปรแกรมประกอบอื่น ๆ โดยสั่ง

sudo apt install -y python3-dev python3-pip libjpeg-dev zlib1g-dev

3. ติดตั้ง pip3 บน Mac

ถ้าติดตั้ง python3 แล้ว น่าจะมี pip3 มาแล้ว ทดลองเรียกดูใน terminal

บน Mac จะต้องติดตั้ง libjpg ด้วย ถ้ายังไม่มี ถ้าคุณมี homebrew อยู่แล้ว ให้สั่ง

brew install libjpeg

ถ้าไม่ได้ ให้ไปดาวน์โหลดและ install จาก [1] (เลือก libjpg)

Use pip to install arcade

For Ubuntu, ถ้าในการติดตั้ง python เราได้ลง pip มาแล้ว เราจะสามารถติดตั้ง arcade ผ่านทาง pip ได้โดยสั่ง

sudo pip3 install arcade

For Mac, use

pip3 install PyObjC arcade

For Windows, use

pip install arcade

หมายเหตุ: ถ้าติดตั้งด้วย pip แล้ว pip เอาไปลงกับ Python 3.5 ให้ ให้สั่งแบบนี้แทน (ขอบคุณศิรกร):

python3.6 -m pip install arcade

virtualenv

เราติดตั้ง arcade ลงใน library ของระบบเลย ซึ่งทำแบบนี้บ่อย ๆ อาจจะทำให้ library เละและตีกันได้ Python มีระบบสำหรับติดตั้ง library แยกกัน เรียกว่า virtualenv

Sorry NOT READY: to do - how to install with virtualenv

Testing your installation

Copy the following code into cirtest.py and try to run it.

import arcade
from random import randint

SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600

circle_size = 1
size_direction = 1
circle_xs = []
circle_ys = []
num_circles = 100

def random_locations():
    for i in range(num_circles):
        circle_xs.append(randint(10,SCREEN_WIDTH-10))
        circle_ys.append(randint(10,SCREEN_HEIGHT-10))

def on_draw(delta_time):
    global circle_size, size_direction
    
    circle_size += size_direction
    if circle_size > 50:
        size_direction = -1
    elif circle_size == 1:
        size_direction = 1

    arcade.start_render()

    for x,y in zip(circle_xs, circle_ys):
        arcade.draw_circle_outline(x, y, circle_size, arcade.color.BLACK)

    
def main():
    random_locations()

    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT,
                       "Circles")
    arcade.set_background_color(arcade.color.WHITE)

    arcade.schedule(on_draw, 1 / 80)
    arcade.run()

if __name__ == '__main__':
    main()

Quick notes:

  • randint -- Random integers between to specified numbers. You need to import random.
  • global -- In Python, a function can only read from global variables, by default. If you want to change the variables, you have to declare it explicitly. In function on_draw we would like to change the circle size, so we have to declare that. It is not a good practice to use global variables.
  • zip -- zip combines to lists, e.g., zip([1,2,3],['a','b','c']) would return [(1, 'a'), (2, 'b'), (3, 'c')]. We usually use zip when we iterate through two lists in a for loop.
  • Functions from arcade
    • arcade.start_render -- Call this before you want to draw a screen. It will clear the screen. (Some experiment below.)
    • arcade.draw_circle_outline -- Draw a circle
    • arcade.open_window
    • arcade.set_background_color
    • arcade.schedule
    • arcade.run

A moving circle

Drawing a circle

We will start from a very simple program and will move on to more functioning programs. The code below draws a circle at the center of the screen. Take the code and save it as cir1.py.

import arcade

SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600

def on_draw(delta_time):
    arcade.start_render()
    
    x = 300
    y = 300
    arcade.draw_circle_outline(x, y, 20, arcade.color.BLACK)

    
def main():
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT,
                       "Circles")
    arcade.set_background_color(arcade.color.WHITE)

    arcade.schedule(on_draw, 1 / 80)
    arcade.run()

if __name__ == '__main__':
    main()

Try to run it. What do you see?

Experiment: Try to remove the line arcade.start_render(). What do you observe?

Move

We will move the circle by having velocities vx (for the x-axis velocity) and vy (for the y-axis velocity).

Because we would like to keep track of the circle location, we will need global variables x and y to keep the circle co-ordinate. We will use global variables for that and in function on_draw we will use keyword global to explicitly tell Python that we will modify these variables. We will also declare that we will modify vx and vy (because later on we will let the circle bounce at the edges of the screen.)

The updated code for on_draw is shown below. The lines that define x,y,vx,vy assign values to global variables.

vx = 2
vy = 1
x = 300
y = 300

def on_draw(delta_time):
    arcade.start_render()
    
    global x, y, vx, vy

    x += vx
    y += vy
    
    arcade.draw_circle_outline(x, y, 20, arcade.color.BLACK)

Exercise 1: Bounce

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

def on_draw(delta_time):
    arcade.start_render()
    
    global x, y, vx, vy

    x += vx
    y += vy

    # ตรวจสอบตำแหน่ง ถ้าตกขอบให้เปลี่ยนทิศทาง
    # hint: ให้ทำทีละแกน จะง่ายกว่า
    
    arcade.draw_circle_outline(x, y, 20, arcade.color.BLACK)

Many circles

เราจะแก้โปรแกรมให้มีลูกบอลวิ่งไปมาหลาย ๆ ลูก ให้เซฟโปรแกรมที่เขียนในไฟล์ใหม่ชื่อ cir2.py

เราจะสุ่มตำแหน่งและความเร็วของลูกบอลด้วยฟังก์ชัน randint ดังนั้นให้เพิ่มบรรทัด import ดังด้านล่าง

import arcade
from random import randint

เราจะเก็บตำแหน่งและความเร็วในแกนต่าง ๆ ของลูกบอลในลิสต์ xs, ys, vxs, vys และจำนวนลูกบอลในตัวแปร n

vxs = []
vys = []
xs = []
ys = []
n = 10

def initialize():
    for i in range(n):
        xs.append(randint(100, SCREEN_WIDTH-100))
        ys.append(randint(100, SCREEN_HEIGHT-100))
        vxs.append(randint(-5,5))
        vys.append(randint(-5,5))

เราจะเขียนฟังก์ชันสำหรับจัดการลูกบอลลูกที่ i ในรายการดังด้านล่าง จากนั้น on_draw จะเรียกฟังก์ชันนี้ สังเกตว่าเรายังไม่ได้จัดการการชนขอบจอ

def draw_and_move_circle(i):
    xs[i] += vxs[i]
    ys[i] += vys[i]

    arcade.draw_circle_outline(xs[i], ys[i], 20, arcade.color.BLACK)

on_draw วนลูบเรียกฟังก์ชัน draw_and_move_circle

def on_draw(delta_time):
    arcade.start_render()

    for i in range(n):
        draw_and_move_circle(i)

อย่าลืมเรียก initialize ใน main

def main():
    initialize()
    
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT,
                       "Circles")
    arcade.set_background_color(arcade.color.WHITE)

    arcade.schedule(on_draw, 1 / 80)
    arcade.run()

แยกฟังก์ชัน

ฟังก์ชัน draw_and_move_circle เป็นตัวอย่างของฟังก์ชันที่พยายามทำหลายอย่างมากเกินไป เราจะแยกฟังก์ชันออกเป็นสองฟังก์ชัน แล้วเรียกใน on_draw

def move_circle(i):
    # .... อย่าลืมย้ายโค้ดมา

def draw_circle(i):
    # .... อย่าลืมย้ายโค้ดมา

def on_draw(delta_time):
    arcade.start_render()

    for i in range(n):
        move_circle(i)
        draw_circle(i)

แบบฝึกหัด 2: ชนและสะท้อนที่ขอบ

โปรแกรมที่มีลูกบอลหลายลูกที่เราเขียนยังไม่ได้มีการจัดการเรื่องการชนกับขอบ ให้แก้ฟังก์ชัน move_circle ให้ตรวจสอบและปรับทิศทางของลูกบอลให้สะท้อนกลับมาด้วย

The Circle class

สังเกตว่าในโค้ดชุดก่อน เราจะจัดการกับลูกบอลโดยพิจารณาตัวแปรที่เกี่ยวข้องกัน 4 ตัวคือ x, y, vx, และ vy แทบจะตลอดเวลา เราจะ extract ตัวแปรที่เกี่ยวข้องกันนี้ออกมาเป็น object ของคลาส Circle

ให้ย้ายโค้ดจากส่วนที่แล้วมาเขียนใหม่ในไฟล์ cir3.py

คลาสดังกล่าวเขียนบางส่วนได้ดังนี้ อย่าลืมว่าใน python เวลาเขียนเมท็อดจะมีตัวแปร self (ที่เป็นตัวแปรตัวแรก) แทน object ที่ทำงานด้วย และเมท็อด __init__ จะเป็นเมท็อดพิเศษที่ใช้ในการกำหนดค่าเริ่มต้นให้กับ object

class Circle:
    def __init__(self, x, y, vx, vy):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy

    def move(self):
        self.x += self.vx
        self.y += self.vy

        # เพิ่มโค้ดส่วนจัดการการชนขอบจอที่นี่ด้วย

    def draw(self):
        arcade.draw_circle_outline(self.x, self.y,
                                   20, arcade.color.BLACK)

แก้ส่วนสร้าง object

circles = []
n = 10

def initialize():
    for i in range(n):
        circle = Circle(randint(100, SCREEN_WIDTH-100),
                        randint(100, SCREEN_HEIGHT-100),
                        randint(-5,5),
                        randint(-5,5))
        circles.append(circle)

แก้เมท็อด on_draw

def on_draw(delta_time):
    arcade.start_render()

    for c in circles:
        c.move()
        c.draw()

แบบฝึกหัด 3: วงกลมที่มีหลายขนาด

เราจะเพิ่มตัวแปร r ลงใน Circle โดยให้มีค่าเป็น 20 ถ้าไม่มีการระบุตอนสร้าง

class Circle:
    def __init__(self, x, y, vx, vy, r=20):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.r = r
    # ...

ให้แก้โค้ดที่เกี่ยวข้องให้สุ่มมาแล้วมีลูกบอลหลายขนาดเคลื่อนที่ในหน้าจอ

A simple ball game

เราจะเขียนเกมหลบลูกบอล ให้ copy โค้ดจากส่วนก่อน ๆ ลงในไฟล์ชื่อ cir4.py แล้วทำงานต่อที่ไฟล์นี้

เราจะนิยามคลาส Player โดย player จะเป็นวงกลมรัศมี 10 สีน้ำเงิน

class Player:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def draw(self):
        arcade.draw_circle_filled(self.x, self.y,
                                  10, arcade.color.BLUE)

สร้าง object ของคลาส Python ไว้บริเวณใกล้ ๆ กับที่ประกาศ circles (การหาร // คือหารปัดเศษ)

circles = []
player = Player(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)
n = 10

เพิ่มโค้ดให้แสดง player ใน on_draw

def on_draw(delta_time):
    arcade.start_render()

    for c in circles:
        c.move()
        c.draw()

    player.draw()

ขยับ player

ขั้นตอนนี้จะยุ่งยากเล็กน้อย เนื่องจากเราจะใช้การอ่านการกดปุ่มแบบอ่านสถานะ ในการเขียนครั้งถัด ๆ ไปจะสะดวกกว่านี้

เตรียมการอ่าน keys

เราจะใช้ส่วนอ่านการกดปุ่มจากไลบรารี pyglet ดังนั้นต้องไป import ก่อน (ใส่ตรงหัวโปรแกรม)

from pyglet.window import key

เราจะประกาศตัวแปร keys เพื่อใช้อ่านสถานะการกดปุ่ม ให้ใส่ไว้ก่อน on_draw โดยอาจจะประกาศไว้แถว ๆ ที่เราประกาศตัวแปร circles, players, และ n ก็ได้

# ... โค้ดเก่า
circles = []
player = Player(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)
n = 10

# ประกาศ keys
keys = key.KeyStateHandler()

ในฟังก์ชัน main ไปบอกกับหน้าต่างให้อัพเดทสถานะการกดปุ่มผ่านทาง keys โดยเพิ่มบรรทัด push_handlers ให้ใส่ไว้ก่อน arcade.schedule

    arcade.get_window().push_handlers(keys)

    # .. โค้ดเก่า ...    
    arcade.schedule(on_draw, 1 / 80)

ส่งปุ่มให้ player

เพิ่มเมท็อด control ในคลาส Player ที่ตอนนี้ตรวจการกดปุ่มซ้ายอย่างเดียว

class Player:
    # ... ละโค้ดเก่าไว้

    def control(self, keys):
        if keys[key.LEFT]:
            self.x -= 5

จากนั้นเรียก player.control จาก on_draw

def on_draw(delta_time):
    arcade.start_render()

    for c in circles:
        c.move()
        c.draw()

    player.control(keys)
    player.draw()

แบบฝึกหัด 4: เพิ่มการอ่านปุ่มขึ้นลงขวาให้ครบ

แก้เมท็อด Player.control ให้ตรวจสอบการเคลื่อนที่ให้ครบทุกทิศทาง

แบบฝึกหัด 5: โดนชน

เขียนเมท็อด is_hit ที่ตรวจสอบว่า player ชนกับ circle หรือไม่

class Player:
    # ... โค้ดอื่น ๆ ไม่แสดง

    def is_hit(self, circle):
        # ... ตรวจสอบว่า self ชนกับ circle หรือเปล่า 
        # ทำโดยพิจารณาตำแหน่ง self.x, self.y กับ circle.x และ circle.y กับรัศมี circle.r
        # อย่าลืมว่า player มีรัศมี 10
        #
        # method นี้คืนค่า True หรือ False

ใน on_draw ให้เรียก is_hit ถ้าจริงให้จบเลย

def on_draw(delta_time):
    arcade.start_render()

    for c in circles:
        c.move()
        c.draw()
        if player.is_hit(c):
            quit()

    player.draw()
    player.control(keys)

ถ้าเกมยากไป (เปิดมาตายเลย) ให้ลดจำนวนลูกบอล หรือเปลี่ยนการสุ่มให้ไม่อยู่กลางหน้าจอมากไป

แบบฝึกหัด 6: จบแบบเนียนๆ

แทนที่จะ quit เลย ให้แก้โปรแกรมให้จบได้เนียนกว่าเดิม เช่นลูกบอลหยุด หรือ player หายไป หรืออะไรก็ได้

แบบฝึกหัด 100: ชนและเด้ง

ถ้าทำมาถึงจุดนี้ ลองเขียนให้ลูกบอลเด้งเวลาชนกันเองด้วย