본문 바로가기

코딩/파이썬 kivy

[kivy-006] Pong Game 튜토리얼

pong game 튜토리얼

Pong Game 튜토리얼

kivy의 getting started에서 소개하는 pong game 튜토리얼을 통해 kivy 기반의 애플리케이션을 개발하는 방법에 대하여 이해할 수 있다.

 

참고로 kivy_examples을 설치하게 되면 다음의 경로에서 pong game 코드를 확인할 수 있다.

../share/kivy-examples/tutorials/pong

단계 1

Pong game 튜토리얼의 단계 1은 빈 화면의 애플리케이션을 만드는 것이다.

 

단계 1

 

from kivy.app import App
from kivy.uix.widget import Widget

class PongGame(Widget):
    pass

class PongApp(App):
    def build(self):
        return PongGame()

if __name__ == '__main__':
    PongApp().run()

 

별도의 설정 없이 생성된 애플리케이션은 화면 최소화나 최대화 등 기본적인 애플리케이션 크기 조절이 가능하고, 애플리케이션의 타이틀 "Pong"은 App 클래스를 상속하는 PongApp 클래스의 "Pong"을 따른다.

단계 2

Pong game 튜토리얼의 단계 2는 화면 중앙에 흰색 바와 흰색 바 왼쪽/오른쪽에 숫자 0을 그리는 것이다.

 

단계 2

 

kivy는 kv 언어 기반의 *.kv 파일을 이용해 애플리케이션의 화면 디자인을 알고리즘 개발로 부터 분리할 수 있도록 한다.

따라서 화면 중앙에 표시되는 흰색 바와 흰색 바 왼쪽/오른쪽의 숫자 0은 파이썬 코딩이 아닌 kv 파일 생성으로 구현될 수 있다.

 

단계 1에서 생성한 파이썬 파일(예. kivy_start.py)과 같은 경로에 pong.kv란 이름 및 확장자의 텍스트 파일을 생성한다.

그리고 다음의 내용을 입력한 후 kivy_start.py 파일을 실행하면 위의 이미지와 같은 애플리케이션이 나타난다.

 

#:kivy 2.0.0
<PongGame>:
    canvas:
        Rectangle:
            pos: self.center_x - 5, 0
            size: 10, self.height
            
    Label:
        font_size: 70
        center_x: root.width / 4
        top: root.top - 50
        text: "0"
        
    Label:
        font_size: 70
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: "0"

 

위의 pong.kv 파일에서 볼 수 있듯이 kv 언어는 파이썬과 같이 내용 작성 시 들여쓰기 규칙이 적용된다.

단계 3

Pong game 튜토리얼의 단계 3은 화면 중앙에 흰색 공 하나를 그리고 이 공과 관련된 코드를 작성하는 것이다.

 

단계 3

 

공과 관련된 클래스 PongBall 코드를 추가하고,

 

from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty
from kivy.vector import Vector

class PongBall(Widget):
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)
    velocity = ReferenceListProperty(velocity_x, velocity_y)

    def move(self):
        self.pos = Vector(*self.velocity) + self.pos

 

pong.kv 파일을 수정한 후 파이썬 코드를 실행하면 위의 이미지와 같은 화면이 나타난다.

 

#:kivy 2.0.0
<PongBall>:
    size: 50, 50
    canvas:
        Ellipse:
            pos: self.pos
            size: self.size

<PongGame>:
    canvas:
        Rectangle:
            pos: self.center_x - 5, 0
            size: 10, self.height
    
    Label:
        font_size: 70
        center_x: root.width / 4
        top: root.top - 50
        text: "0"
    
    Label:
        font_size: 70
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: "0"
    
    PongBall:
        center: self.parent.center

단계 4

Pong game 튜토리얼의 단계 4는 흰색 공을 움직이게 하는 것이다.

 

단계 4

 

이를 위해선 kivy.clock 모듈 등을 추가하고 PongGame 및 PongApp 클래스를 수정해야 한다.

 

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty
from kivy.vector import Vector
from kivy.clock import Clock
from random import randint

class PongBall(Widget):
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)
    velocity = ReferenceListProperty(velocity_x, velocity_y)

    def move(self):
        self.pos = Vector(*self.velocity) + self.pos

class PongGame(Widget):
    ball = ObjectProperty(None)

    def serve_ball(self):
        self.ball.center = self.center
        self.ball.velocity = Vector(4, 0).rotate(randint(0, 360))

    def update(self, dt):
        self.ball.move()

        if self.ball.y < 0 or self.ball.top > self.height:
            self.ball.velocity_y *= -1

        if self.ball.x < 0 or self.ball.right > self.width:
            self.ball.velocity_x *= -1

        print(self.ball.top, self.height)

class PongApp(App):
    def build(self):
        game = PongGame()
        game.serve_ball()
        Clock.schedule_interval(game.update, 1./60.)
        return game

if __name__ == '__main__':
    PongApp().run()

 

또한 pong.kv 파일도 파이썬 파일에서 PongBall 클래스의 객체를 참조할 수 있도록 수정한다.

 

#:kivy 2.0.0
<PongBall>:
    size: 50, 50
    canvas:
        Ellipse:
            pos: self.pos
            size: self.size

<PongGame>:
    ball: pong_ball

    canvas:
        Rectangle:
            pos: self.center_x - 5, 0
            size: 10, self.height
    
    Label:
        font_size: 70
        center_x: root.width / 4
        top: root.top - 50
        text: "0"
    
    Label:
        font_size: 70
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: "0"
    
    PongBall:
        id: pong_ball
        center: self.parent.center

단계 5

Pong game 튜토리얼의 단계 5는 사용자의 입력을 받아 공을 받아치는 기능을 구현하는 것이다.

 

이를 위한 파이썬 코드 및 kv 파일은 아래와 같다.

 

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty
from kivy.vector import Vector
from kivy.clock import Clock

class PongPaddle(Widget):
    score = NumericProperty(0)

    def bounce_ball(self, ball):
        if self.collide_widget(ball):
            vx, vy = ball.velocity
            offset = (ball.center_y - self.center_y) / (self.height / 2)
            bounced = Vector(-1 * vx, vy)
            vel = bounced * 1.1
            ball.velocity = vel.x, vel.y + offset

class PongBall(Widget):
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)
    velocity = ReferenceListProperty(velocity_x, velocity_y)

    def move(self):
        self.pos = Vector(*self.velocity) + self.pos

class PongGame(Widget):
    ball = ObjectProperty(None)
    player1 = ObjectProperty(None)
    player2 = ObjectProperty(None)

    def serve_ball(self, vel=(4, 0)):
        self.ball.center = self.center
        self.ball.velocity = vel

    def update(self, dt):
        self.ball.move()

        self.player1.bounce_ball(self.ball)
        self.player2.bounce_ball(self.ball)

        if (self.ball.y < self.y) or (self.ball.top > self.top):
            self.ball.velocity_y *= -1

        if self.ball.x < self.x:
            self.player2.score += 1
            self.serve_ball(vel=(4, 0))

        if self.ball.x > self.width:
            self.player1.score += 1
            self.serve_ball(vel=(-4, 0))

    def on_touch_move(self, touch):
        if touch.x < self.width / 3:
            self.player1.center_y = touch.y
        if touch.x > self.width - self.width / 3:
            self.player2.center_y = touch.y

class PongApp(App):
    def build(self):
        game = PongGame()
        game.serve_ball()
        Clock.schedule_interval(game.update, 1. / 60.)
        return game

if __name__ == '__main__':
    PongApp().run()

 

#:kivy 2.0.0
<PongBall>:
    size: 50, 50

    canvas:
        Ellipse:
            pos: self.pos
            size: self.size

<PongPaddle>:
    size: 25, 200

    canvas:
        Rectangle:
            pos: self.pos
            size: self.size

<PongGame>:
    ball: pong_ball
    player1: player_left
    player2: player_right

    canvas:
        Rectangle:
            pos: self.center_x - 5, 0
            size: 10, self.height

    Label:
        font_size: 70
        center_x: root.width / 4
        top: root.top - 50
        text: str(root.player1.score)

    Label:
        font_size: 70
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: str(root.player2.score)

    PongBall:
        id: pong_ball
        center: self.parent.center

    PongPaddle:
        id: player_left
        x: root.x
        center_y: root.center_y

    PongPaddle:
        id: player_right
        x: root.width - self.width
        center_y: root.center_y