Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

개발이 취미인 개발자

슈팅게임 알고리즘-편대비행-회전편 본문

슈팅게임 알고리즘(pygame)

슈팅게임 알고리즘-편대비행-회전편

도그풋79 2022. 8. 14. 21:15

 슛팅게임에서 보스전 만큼 중요한 건 바로 현란한 적기의 움직임이라고 생각한다. 인터넷에 있는 슛팅게임 예제를 많이 봐 왔는데 매번 아쉬운 점은 적기의 움직임이 단조롭다는 것이다.

 대부분 위에서 아래로 내려오는 일직선 정도로 구현이 되고 끝이 나 버린다. 그래서 조금 더 다양한 패턴의 움직임을 가진 적기가 등장하는 것을 구현해 봤다. 적기의 움직임도 탄의 종류만큼이나 다양하게 나올 수가 있다. 총알과 다른 점이 있다면 방향이 바뀌면서 움직이는 경우, 더 자연스럽게 움직이도록 처리를 해줘야한다는 점이다.

 총알도 각도가 변하는 경우는 많지만 화면에 노출되는 시간이 길지 않고 크기도 작은 편이라 다소 부자연스러운 움직임을 보이더라도 플레이어가 눈치를 채기가 매우 어렵다. 하지만 적기의 움직임은 그렇지가 않다.

 플레이어의 타겟팅의 대상이 되기 때문에 플레이하는 내내 적기는 주시의 대상이 된다. 그렇기 때문에 가속도 처리 또는 기체 회전 시 감속 처리가 없다면 기체의 자연스러움이 확연히 드러나게 된다.

class EnemyFlight(pygame.sprite.Sprite):
    def __init__(self, sx, sy, enemy_type, rotated=True):
        pygame.sprite.Sprite.__init__(self)
        self.id = None
        self.move_idx = 0
        self.move_data = enemy_movement[enemy_type]
        self.images = [pygame.image.load(img_dir + "/img/enemy_flight.png").convert_alpha()]
        self.image = self.images[0]        
        self.orig_image = self.images[0]
        self.frame = 0
        self.src = pygame.math.Vector2(sx, sy)
        self.trgt_angle = self.move_data[self.move_idx][0]
        self.trgt_angle -= 90
        self.rect = self.image.get_rect(center=self.src)                
        self.vel = pygame.math.Vector2(0, 1)
        self.acceleration = pygame.Vector2(0, 0.1)
        self.angle_speed = 15
        self.angle = 0
        self.max_speed = 7
        self.rotated = rotated
        self.curved_speed = 3
        self.next_frame = self.move_data[self.move_idx][1]

 pygame에는 vector가 없다고 생각해서 그동안 예제를 sin, cos을 이용하여 계산하는 방식으로 처리를 해 왔는데 편대비행 처리를 위해 인터넷을 뒤적거리다가 결국 vector class가 있다는 걸 알게 되었다.

 vector를 이용하면 움직임 처리에서 sin, cos으로 계산하던 각도와 속도부분이 꽤 간단하게 처리가 된다. 무엇보다 방향 전환 시 처리되는 움직임 처리를 훨씬 더 간단한 코드로 처리할 수 있다. 우선 속도 관련된 부분을 보자.

self.vel = pygame.math.Vector2(0, 1)       #속도
self.acceleration = pygame.Vector2(0, 0.1) #가속도
self.angle_speed = 15                      #회전속도
self.angle = 0                             #현재회전상태
self.max_speed = 7                         #최고속도
self.curved_speed = 3                      #회전시속도

EnemyFlight class에 속도와 가속도 처리 부분을 보면 Vector2(0, 1)이라는 건 x축 0, y축은 1만큼 움직이라는 뜻이다.

가속도는 x축은 0, y축은 0.1만큼 증가하라는 뜻이다.

    def update(self, GameMain):
        //...중략...//

        self.vel += self.acceleration

        if self.vel.length() > self.max_speed:
            self.vel.scale_to_length(self.max_speed)

        self.src += self.vel
        self.rect.center = self.src

매 프레임 호출되는 update 코드를 보자

self.vel += self.acceleration

이 코드는 매 프레임마다 원래 속도에 가속도를 더해주고 있다.

그리고 최대 속도를 넘어서는 경우에는 self.vel.scale_to_length 함수는 최대 속도를 유지하도록 해준다.

enemy_movement = [[[90, 100], [190, 9999]],
                  [[90, 100], [-190, 9999]]]

 적 기체의 움직임은 좌표가 아닌 방향(각도)와 시간만으로 처리를 했다.

enemy_movement의 2차원 배열을 선언하고 첫번째 열은 각도, 두번째 열은 시간을 할당했다.

위의 배열의 값을 풀어서 설명하면 기체가 등장하면 처음에는 90도 각도로 100 프레임동안 움직이고

두번째에는 190도를 회전하여 9999프레임동안 움직여라는 뜻이 된다.

 enemy_movement의 배열의 값의 변경과 추가 만으로 다양한 움직임을 가진 기체를 만들 수가 있다. 아래와 같이 배열의 값을 추가한 후에 실행해보자.

enemy_movement = [[[90, 100], [190, 100], [270, 9999]],
                  [[90, 100], [-190, 100], [-270, 9999]]]

 단순히 각도와 시간만 하나 추가만으로 기체가 훨씬 복잡한 움직임을 가지게 된 것을 확인할 수가 있다.

배열값의 각도를 줄때 시계방향인 경우에는 양수, 반시계방향인 경우에는 음수를 주면 된다.  이번에는 회전처리 관련 코드를 보자.

    def rotate(self):
        if self.trgt_angle - self.angle > 0:
            self.angle_speed = abs(self.angle_speed)
        elif self.trgt_angle - self.angle < 0:
            self.angle_speed = abs(self.angle_speed) * -1

        self.angle += self.angle_speed
        self.acceleration.rotate_ip(self.angle_speed)
        if self.vel.length() > self.curved_speed:
            self.vel.scale_to_length(self.curved_speed)
        self.image = pygame.transform.rotate(self.orig_image, -self.angle)
        self.rect = self.image.get_rect(center=self.rect.center)

self.trgt_angle은 바로 배열에서 선언된 값이다. 양수인 경우와 음수인 경우 구분하여 angle_speed 값을 맞춰서 변경해 준다. 

self.acceleration.rotate_ip가 여기서 제일 중요한 부분이다. 기체가 회전하는 시점의 벡터값은 그 회전한 각만큼 변경 되어야 한다. 어떤 물체가 오른쪽으로 움직이다가 왼쪽으로 움직이려고 하면 그 움직이려고 하는 방향에 맞춰서 움직임을 바꿔줘야 하는데 그 역할을 하는 것이 바로 이 코드이다.이 부분을 코드에서 주석 처리하고 실행해 보면 기체가 회전은 하지만 원래 움직임을 그대로 유지한채 그대로 이동하는 것을 확인할 수 있을 것이다.

 

 지금까지와 마찬가지로 예제 소스를 첨부했다. 글을 많이 써보지 못해서 머리에 도는 생각을 글로 온전히 못 전달하는 것 같아 아쉽다는 생각이 든다.

08_enemy_formation.zip
0.02MB