8.2 절차지향형으로 개발하기 2

이번 절에서부터 앞서 언급된 4개의 사용자 함수의 구현체를 구체적으로 채워보는 시간이다. 먼저, reset_ball 함수이다.

이름에서 알 수 있듯이 볼의 초기 위치와 속력의 초기값을 정하는 목적의 함수이다. 오리지널 게임을 유심히 살펴보았다면 알 수 있는데 매 새 게임이 시작할 때마다, 화면 중앙선에서 공이 좌/우 반사판으로 랜덤하게 던져지는 것을 알 수 있다. 우리도 게임이 최종 승패점수(FINAL_SCORE)에 도달하기 전까지는 그렇게 동작하도록 유사 구현을 하였다. 특별히 좌우의 랜덤을 결정하는 것은 13번 라인의 random.choice라는 함수인데 인자값으로 전달된 리스트 안에 존재하는 두 값(-1, 또는 1) 중에 하나를 랜덤하게 골라주는 함수에 의해 동작하게 된다.

def reset_ball():
    global vx, vy

    if b1_score == FINAL_SCORE or b2_score == FINAL_SCORE:
        vx = 0
        vy = 0
    else:
        vx = 5
        vy = 5
        
    # 중심선에서 좌우로 랜덤하게 던지기
    ball.center = (WIDTH/2, random.randint(BALL_RADIUS*2, HEIGHT - BALL_RADIUS*2)
    vx *= random.choice([-1, 1])

다음으로 ball_move_collide 함수이다. 볼의 움직임을 만들고, 움직인 이후에 충돌검사를 하는 함수이다. 만약, 벽(게임화면 테두리)과 충돌지점이 좌/우냐에 따라 어떤 게임유저(bar1 또는 bar2)가 점수(b1_score 또는 b2_score)를 획득하는지 판별하고 승패가 났기 때문에 새 게임을 시작하는 준비를 한다.

def ball_move_collide():
    global vx, vy, b1_score, b2_score

    # vx와 vy만큼 속도로 공을 이동시키기
    ball.move_ip(vx, vy)

     # 공이 위쪽 또는 아래쪽 벽에 부딪힐 때
    if ball.top < 0 or ball.bottom > HEIGHT:
        vy = -vy  # 속도의 y축 방향을 반대로하기
        sounds.wall.play()

    # 공이 왼쪽 벽에 부딪힐 때
    if ball.left < 0:
        b2_score += 1
        reset_ball()
        sounds.die.play()

    # 공이 오른쪽 벽에 부딪힐 때
    if ball.right > WIDTH:
        b1_score += 1
        reset_ball()
        sounds.die.play()

다음으로 gameover_draw 함수이다. 게임 최종승패에 대한 화면에 UI를 표시하는 워낙 간단한 구현체라 특별히 이해에 어려움은 없다.

def gameover_draw():
    if b1_score == FINAL_SCORE:
        winner = 'Player1'
    else:
        winner = 'Player2'
    screen.draw.text(winner + ' Win!!', (WIDTH/3, HEIGHT/2 - 50), color='blue', fontsize=70)
    screen.draw.text('Press Space to play again', (WIDTH/4, HEIGHT/2 + 50), \
        color='skyblue', fontsize=50)

마지막으로 bar_move_collide 함수이다. 이름처럼 반사판의 움직임을 만들고, 반사판에 충돌한 공에 대한 처리를 담당한다. 기존에 블록격파 게임과 유사하여 특별히 코드이해에 어려움은 없고, 새롭게 추가된 부분은 이전 절에 언급되었던 블록격파게임의 신규 추가기능을 제안되었던 게임 랠리가 지속되면서 속도를 점점 증가시키는 알고리즘과 반사판 영역을 두개 상하부로 나눠 상부에 공이 닿을 때와 하부에 공이 닿을 때의 동작을 달리해 게임유저의 반사판 조작에 따른 공격/방어의 자유도를 증가시켜 게임성을 향상시키는 코드를 추가하였다(25~30라인).

def bar_move_collide(bar):
    global vx, vy

    if bar == bar1:
        if keyboard.a:
            if bar.y > 0:
                bar.y -= 7
        elif keyboard.z:
            if bar.y + bar.height < HEIGHT:
                bar.y += 7
    else:  # bar2
        if keyboard.up:
            if bar.y > 0:
                bar.y -= 7
        elif keyboard.down:
            if bar.y + bar.height < HEIGHT:
                bar.y += 7

    if bar.colliderect(ball):
        # 10픽셀 앞으로 먼저 튀어오르기
        if vx < 0:  # bar1 경우
            ball.x += 10
        else:  # bar2 경우
            ball.x -= 10
        vx = -vx  * SPEED_UP # 속도의 x축 방향을 반대로하기
        ''' 공이 윗측 진입하면서 반사판 윗측에 부딪힐 때 또는
            공이 아래측 진입하면서 반사판 아래측에 부딪힐 때는 진입방향 그대로 반사 '''
        if (vy > 0 and ball.centery < bar.centery) or \
            (vy < 0 and ball.centery > bar.centery):
            vy = -vy * SPEED_UP
        sounds.bar.play()

이상으로 4개의 사용자 함수에 대한 구현과 이해가 끝났다. 앞서도 언급했지만, 이 게임은 앞선 블록격파 게임의 특징들에 대한 사진 지식이 있었기 때문에 생각보다 이 게임의 구현이나 이해에 많은 시간이 걸리지 않았던 같다. 마지막으로 모든 전체코드를 공유하는 것으로 절차지향형으로 만들어본 퐁 게임을 마무리 짓고, 다음 절부터는 드디어 사용자 정의 객체를 만드는 방법부터 시작해 그 객체들을 활용해 동일한 게임을 객체지향 패러다임으로 만들어 보기를 시작해보자.

import math
import random

# 게임화면
TITLE = 'pong'
WIDTH = 800
HEIGHT = 600
GAP_FROM_SCR = 20

# 반사판
BAR_H = 100
BAR_W = 15

# 볼
vx = 5
vy = 5
BALL_RADIUS = 10
SPEED_UP = 1.05

# 점수
FINAL_SCORE = 11
b1_score = -1
b2_score = -1


def reset_ball():
    global vx, vy

    if b1_score == FINAL_SCORE or b2_score == FINAL_SCORE:
        vx = 0
        vy = 0
    else:
        vx = 5
        vy = 5

    # 화면 중앙 상부에 위치좌우로 랜덤하게 던지기
    ball.center = (WIDTH/2, random.randint(BALL_RADIUS*2, HEIGHT - BALL_RADIUS*2))
    vx *= random.choice([-1, 1])

def ball_move_collide():
    global vx, vy, b1_score, b2_score

    # vx와 vy만큼 속도로 공을 이동시키기
    ball.move_ip(vx, vy)

     # 공이 위쪽 또는 아래쪽 벽에 부딪힐 때
    if ball.top < 0 or ball.bottom > HEIGHT:
        vy = -vy  # 속도의 y축 방향을 반대로하기
        sounds.wall.play()

    # 공이 왼쪽 벽에 부딪힐 때
    if ball.left < 0:
        b2_score += 1
        reset_ball()
        sounds.die.play()

    # 공이 오른쪽 벽에 부딪힐 때
    if ball.right > WIDTH:
        b1_score += 1
        reset_ball()
        sounds.die.play()

def gameover_draw():
    if b1_score == FINAL_SCORE:
        winner = 'Player1'
    else:
        winner = 'Player2'
    screen.draw.text(winner + ' Win!!', (WIDTH/3, HEIGHT/2 - 50), color='blue', fontsize=70)
    screen.draw.text('Press Space to play again', (WIDTH/4, HEIGHT/2 + 50), \
        color='skyblue', fontsize=50)

def bar_move_collide(bar):
    global vx, vy

    if bar == bar1:
        if keyboard.a:
            if bar.y > 0:
                bar.y -= 7
        elif keyboard.z:
            if bar.y + bar.height < HEIGHT:
                bar.y += 7
    else:  # bar2
        if keyboard.up:
            if bar.y > 0:
                bar.y -= 7
        elif keyboard.down:
            if bar.y + bar.height < HEIGHT:
                bar.y += 7

    if bar.colliderect(ball):
        # 10픽셀 앞으로 먼저 튀어오르기
        if vx < 0:  # bar1 경우
            ball.x += 10
        else:  # bar2 경우
            ball.x -= 10
        vx = -vx  * SPEED_UP # 속도의 x축 방향을 반대로하기
        ''' 공이 윗측 진입하면서 반사판 윗측에 부딪힐 때 또는
            공이 아래측 진입하면서 반사판 아래측에 부딪힐 때는 진입방향 그대로 반사 '''
        if (vy > 0 and ball.centery < bar.centery) or \
            (vy < 0 and ball.centery > bar.centery):
            vy = -vy * SPEED_UP
        sounds.bar.play()


ball = Rect(WIDTH/2, HEIGHT/2, \
        BALL_RADIUS * math.sqrt(2), BALL_RADIUS * math.sqrt(2))
bar1 = Rect(GAP_FROM_SCR, HEIGHT/2 - BAR_H/2, BAR_W, BAR_H)
bar2 = Rect(WIDTH - BAR_W - GAP_FROM_SCR, HEIGHT/2 - BAR_H/2, BAR_W, BAR_H)
bars = [bar1, bar2]


def draw():
    screen.clear()
    # 중심선
    screen.draw.line((WIDTH/2, GAP_FROM_SCR), (WIDTH/2, HEIGHT - GAP_FROM_SCR), color='grey')

    # 반사판
    for bar in bars:
        screen.draw.filled_rect(bar, 'white')

    # 점수
    screen.draw.text(str(b1_score), (WIDTH/4, GAP_FROM_SCR), color='yellow', fontsize=60)
    screen.draw.text(str(b2_score), ((WIDTH/4)*3, GAP_FROM_SCR), color='yellow', fontsize=60)
    if b1_score == FINAL_SCORE or b2_score == FINAL_SCORE:
        gameover_draw()
    else:  # 공
        screen.draw.filled_circle(ball.center, BALL_RADIUS, 'white')

def update():
    global b1_score, b2_score

    # 공
    ball_move_collide()

    # 반사판
    for bar in bars:
        bar_move_collide(bar)

    # 게임시작 조건
    if (b1_score == FINAL_SCORE or b2_score == FINAL_SCORE) or \
        (b1_score == -1 and b2_score == -1):
        if keyboard.space:
            b1_score = 0
            b2_score = 0
            reset_ball()
    else:  # 공
        ball_move_collide()

Last updated

Was this helpful?