반응형

이번엔 벽돌깨기 게임에서 공의 이동방법에 대해 적어 보겠습니다.

그 전에...혹시나 이 글을 보실 분들에게 말씀 드리면 이 방법은 정답이 아닙니다. 그냥 제가 쓴 방법이므로 참고만 하시길 바랍니다. 더 좋고 쉬운 방법이 얼마든지 있을 수 있습니다.

공의 이동은 삼각함수를 이용합니다. 삼각함수의 sin 함수와 cos 함수를 이용하면 아주 쉽게 구할 수 있습니다.


위 그림에서 우리는 공을 시작위치에서 이동위치 까지 이동시키려고 합니다. c 는 이동거리 입니다. 이동 방향은 각도(x)로 표시합니다. 공을 이동시키려면 우리는 a 값과 b 값을 알아내어 시작위치의 좌표에 더해 주면 이동위치의 좌표을 얻게 됩니다. 

sin x = b/c 이므로 
b = sin x * c 가 됩니다. 같은 이유로...

cos x = a/c 이므로 
a = cos x * c 가 됩니다. 

이것을 파이썬으로 구현해보면 다음과 같습니다.

b = math.sin(math.radians(self.angle)) * c
a = math.cos(math.radians(self.angle)) * c

삼각함수를 사용하려면 먼저 import math 를 사용해야 합니다. 

angle 이 각도인데 여기서의 각도는 편의상 일반적으로 우리가 사용하는 이해하기 쉬운 360도 각도 입니다. 그런데 컴퓨터에서 각도는 라디안(Radian)으로 표시합니다. 따라서 우리가 일반적으로 쓰는 각도를 라디안 값으로 변환해서 삼각함수를 사용해야 하고요. 우리가 쓰는 360도 각도를 라디안으로 바꿔주는 함수가 math.radians 입니다. 

결과적으로 실제 게임에 쓰인 소스는 아래와 같습니다.

xd = math.sin(math.radians(self.angle)) * self.dt *self.dirx
yd = math.cos(math.radians(self.angle)) * self.dt *self.diry
        
self.x += xd
self.y += yd

일단 저는 위와 같이 프로그램을 작성했습니다. 기존 x, y 좌표에 삼각함수를 이용해 구해진 이동거리 xd 와 yd 를 더해주고 있습니다. 이때 우리가 의도한 방향으로 공이 움직이게 되는 각도는 90 ~ 270 도의 각도 입니다.(sin 값과 cos 값이 양수냐 음수냐에 따른 것) 다르게 할 방법도 있을텐데 이렇게 생각하는게 간단한거 같아서 그렇게 했습니다. 처음 공이 발사될 각도의 초기값을 180 ~ 270 도 사이로 주면 공은 왼쪽 위로, 90 ~ 180 도 사이로 주면 오른쪽 위로 공은 날아갑니다. 

위의 소스는.....
dt 만큼의 거리를 angle 각도로 이동할때 x축 방향으로 xd 만큼 이동
dt 만큼의 거리를 angle 각도로 이동할때 y축 방향으로 yd 만큼 이동

한다는 겁니다. 
따라서 원래의 x 와 y 좌표에 xd 와 yd 를 더해주면 공이 이동하게 됩니다.

dirx 와 diry 는 이동의 방향을 설정해 줍니다. 공의 특정 방향으로의 이동을 0~360도의 각도로 정해 줄 수도 있지만 이렇게 계산하면 좀 복잡해 집니다. 따라서 간단하게 벽이나 벽돌에 맞고 튕기는 것을 구현할때 튕기는 방향에 -1 을 곱해서 부호만 바꿔주는 겁니다. 
예를 들어 120도의 각도로 위로 올라가던 공이 천정에 맞고 튕기면 diry 값에 -1 을 곱해서 yd 값을 마이너스에서 플러스로 바꿔주면 공이 같은 반사각으로 튕겨 내려오는 것을 쉽게 구현할 수 있습니다. 
dirx, diry 는 이러한 방향을 바꿔주는 역활을 하는 변수 입니다. 초기값은 1 이며 공이 튕겨야 할때 마다 -1 을 곱해 줌으로서 공의 방향을 바꿔주는 역활을 합니다. 

변수 dt 는 공이 한번에(?) 이동할 거리입니다. 이동거리를 높게 주면 공이 빠르게 움직이기 때문에 공의 속도와 관련되어 있기도 합니다. 

>

import pygame
import math    #삼각함수를 쓰기 위해 필요

class Ball(pygame.sprite.Sprite):
    #img 이미지
    def __init__(self,img,x,y):
        super().__init__()
        self.image = img
        self.rect = img.get_rect()
        self.rect.center = (x,y)
        self.mask = pygame.mask.from_surface(img) #충돌체크용 마스크 생성
        self.go = False #공을 움직일 거면 True, 기본값은 당연히 False
        self.dt = 2 #공의 이동 거리, 속도
        self.x = x  #공의 현재 x 좌표 
        self.y = y  #공의 현재 y 좌표 
        self.dirx = 1   
        self.diry = 1

    #공이 움직일 공간
    def boundRect(self,rect):
        self.brect = rect
    
    #주어진 각도로 공을 움직임    
    def start(self, angle):
        self.angle = angle
        self.go = True
    
    #공과 막대의 충돌시 공의 방향을 바꿈    
    def collideBar(self):
        self.diry *=-1

    def move(self,surface):        
        if not self.go : return #게임이 시작되지 않았으면 아래 내용을 실행하지 않고 리턴시킴
        
        #공의 이동을 계산 
        xd = math.sin(math.radians(self.angle)) * self.dt *self.dirx
        yd = math.cos(math.radians(self.angle)) * self.dt *self.diry
        #구해진 이동거리를 원래 좌표에 더해서 이동함
        self.x += xd
        self.y += yd
        
        #공이 벽에 맞고 튕기는 부분. brect 는 공이 이동 가능한 공간, rect는 공의 사각범위임
        # dirx 와 diry 에 -1 을 곱해 방향을 바꿔준다.
        if self.x - self.rect.width/2 < self.brect.x : 
            self.x = self.brect.x + self.rect.width/2
            self.dirx *= -1
        if self.y - self.rect.width/2 < self.brect.y :
            self.y = self.brect.y + self.rect.width/2
            self.diry *= -1
        if self.x + self.rect.width/2 > self.brect.x + self.brect.width :
            self.x = self.brect.x + self.brect.width - self.rect.width/2
            self.dirx *= -1
        if self.y + self.rect.width/2  > self.brect.y + self.brect.height:
            self.y = self.brect.y + self.brect.height - self.rect.width/2
            self.diry *= -1
        
        #공의 새로운 위치를 입력해주고
        self.rect.center = (self.x,self.y)
        #공을 그린다.
        surface.blit(self.image,self.rect)

공을 다루는 Ball 클래스 입니다. Sprite 를 상속받았고요. 아직 완성된건 아닙니다만...현재는 대강 움직이면서 벽돌을 제거하는 건 가능합니다. 막대로 공을 튕겨낼 수도 있고요.

다만 현재는 작업중이라 공을 막대로 튕겨내지 못해도 공이 아웃되지 않고 그냥 아래쪽 벽에 맞고 계속 튕기게 되어 있습니다.

Ball 클래스는 객체를 생성할때 초기 공의 위치를 입력받습니다. 그리고 start(220) 이런식으로 이동할 각도를 인자로 넣어 실행하면 공은 그 각도로 움직이기 시작합니다.

삼각함수만 이해하면 쉬운부분이니 간단히 이정도만 이야기 하고 넘어 가겠습니다. ^^;

일단 기본적인 공의 이동과 충돌체크 부분은 대충 되었네요.

그런데....막대로 공을 튕겨내는 부분은 좀 생각을 해야 될게...스프라이트의 충돌체크로 했더니 버그가 좀 있습니다. 공이 막대의 상단 부분이 아닌 측면쪽에 부딪혔을때 마우스를 이동시켜 공이 막대의 내부로 들어오게 되면, 충돌 체크 함수에 의해 공의 방향이 바뀌게 되긴 하지만 공은 막대의 내부에 있으므로 계속해서 스프라이트 충돌함수가 실행되어 결과적으론 공이 막대 내부의 부분을 타고 흐르는(?) 현상이 생기게 됩니다.(말로 하자니 좀 어렵네요)

막대가 마우스의 x 좌표에 따라 그대로 이동하기 때문에 생기는 증상인데 어떻게 해결하는게 가장 괜찮을지 생각을 좀 해 봐야 겠습니다.

+ Recent posts