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. 5. 1. 21:31

 조준탄이나 n-way탄보다 슈팅게임의 난이도를 높이는 방법은 유도탄을 게임에 추가하는 것이다. 플레이어가 피했다고 생각한 순간 각도를 틀어서 따라오는 탄은 플레이어의 움직임의 제한하여 실수를 유도하게 된다. 이로 인해 플레이어의 게임에 대한 긴장감과 집중도를 높일 수가 있다. 

 유도탄 알고리즘은 조준탄의 연장선에 있다. 조준탄은 발사되는 순간에 플레이어의 위치를 파악한 후 발사된다. 그에 비해 유도탄은 발사된 후에도 계속 플레이어 위치를 추적한다. 조준탄이 최초 한번하는 조준을 유도탄을 매 순간하도록 코드를 작성하면 된다. 물론 끝까지 플레이어를 조준하고 방향을 틀게 되면 플레이어가 영원히 피할 수 없는 유도탄이 되기 때문에 적절한 시점에 추적하는 것을 멈춰줘야 한다.

 

 여기서 구현한 것은 유도탄이 아니라 유도레이저이다. 탄과 레이저는 큰 차이가 없다. 단지 움직이는 모습을 관찰하기에는 탄 보다는 레이저가 좋을 것 같아 레이저로 개발해 보았다. 

강의의 레이저가 직선 움직임만 있었다. 하지만 이번에 구현한 유도레이저는 직선이 아닌 곡선 움직임이 필요하다. 

 처음에 발사되는 레이저는 header는 움직이면서 동시에 tail을 만든다. tail로 생성된 레이저는 위의 그림처럼 점점 그 크기가 작아지도록 애니메이션 처리를 한다. 필자가 그린 레이저는 총 5장 정도로 간단하게 구현을 했다. 만약 크기 뿐만 아니라 alpha값 처리까지 좀 더 세심하게 처리한다면 위의 예시보다 훨씬 그럴듯한 레이저를 만들 수가 있다.

class HomingLaserHeader(pygame.sprite.Sprite):
    def __init__(self, sx, sy, tx, ty, rad, speed):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(img_dir + "/img/homing_laser_01.png").convert_alpha()
        self.speed = 1
        self.max_speed = 8
        self.angle = math.atan2(ty - sy, tx - sx)
        self.rect = self.image.get_rect()
        self.rect.center = [sx, sy]
        self.dx = math.cos(self.angle) * self.speed
        self.dy = math.sin(self.angle) * self.speed
        self.x = sx
        self.y = sy
        self.tracking = True

    def update(self, GameMain):
        if self.tracking:            
            tx = GameMain.flight.rect.x
            ty = GameMain.flight.rect.y
            sx = self.rect.x
            sy = self.rect.y
            self.angle = math.atan2(ty - sy, tx - sx)
            self.dx = math.cos(self.angle) * self.speed
            self.dy = math.sin(self.angle) * self.speed     
            
        # 플레이어의 기체 x,y ±60 내로 접근 시, 추적을 멈춘다.
        if (self.rect.y > GameMain.flight.rect.y - 60 and \
           self.rect.y < GameMain.flight.rect.y + 60) and \
            (self.rect.x > GameMain.flight.rect.x - 60 and \
            self.rect.x < GameMain.flight.rect.x + 60):            
            self.dx = math.cos(self.angle) * self.max_speed
            self.dy = math.sin(self.angle) * self.max_speed
            self.tracking = False
        
        self.x += self.dx
        self.y += self.dy
        self.rect.centerx = int(self.x)
        self.rect.centery = int(self.y)

        if self.max_speed > self.speed:
            self.speed += 0.3
        
        homingLaserShadow = HomingLaserTail(self.x, self.y)
        GameMain.bullet_group.add(homingLaserShadow)

 self.tracking 클래스변수는 일정 시점부터 플레이어의 기체를 추적하는 것을 멈추는 역할을 한다. 여기서는 플레이어 기체의 상하좌우로 60픽셀 이내로 접근 시 self.tracking 값을 False로 처리하여 유도탄으로서의 기능을 상실하고 마지막 각도를 유지한채 날아가도록 처리한다.

class HomingLaserTail(pygame.sprite.Sprite):
    def __init__(self, sx, sy):
        pygame.sprite.Sprite.__init__(self)
        self.images = [pygame.image.load(img_dir + "/img/homing_laser_01.png").convert_alpha()
                     , pygame.image.load(img_dir + "/img/homing_laser_02.png").convert_alpha()
                     , pygame.image.load(img_dir + "/img/homing_laser_03.png").convert_alpha()
                     , pygame.image.load(img_dir + "/img/homing_laser_04.png").convert_alpha()
                     , pygame.image.load(img_dir + "/img/homing_laser_05.png").convert_alpha()
                      ]
        self.image = self.images[0]
        self.rect = self.image.get_rect()
        self.rect.center = [sx, sy]
        self.count = 0

    def update(self, GameMain):
        self.count += 1        
        if self.count <= 5:
            self.image = self.images[0]
        elif self.count <= 10:
            self.image = self.images[1]
        elif self.count <= 15:
            self.image = self.images[2]
        elif self.count <= 20:
            self.image = self.images[3]
        elif self.count <= 25:
            self.image = self.images[4]
        else:
            self.kill()

 위의 설명한 Tail 부분의 코드이다. 시간이 증가함에 따라 크기와 색상이 연해지도록 처리하고 최종적으로는 self.kill()로 Tail 객체를 삭제하도록 처리했다. 현란한 움직임에 비해 구현하는 부분은 오히려 간단한 편이다. 만약 이 유도레이저 대신 유도탄을 구현하고 싶다면 Header를 미사일로 변경하고 Tail 부분을 미사일이 발사된 후 뒤 따라가는 연기를 그려주면 될 것이다. 시간이 허락한다면 나중에 유도탄도 예제로 한번 만들어 보도록 하겠다.

 

 유도레이저를 처리하기 위해 지금까지 사용한 터렛과는 좀 다른 터렛을 하나 만들어 보았다. 지하벙커에 숨어 있다가 문을 열면서 유도 레이저를 발사하는 터렛으로 만들어 봤다.

 위와 같이 총 6개의 이미지를 사용했는데 열리고 닫히는 애니메이션 처리는 순서만 역순으로 바꿔주면서 처리하였다. 그리고 눈을 떴다가 감는 듯한 연출을 하기 위해 뜬 상태에서 레이저가 나가고 한동안 눈을 떠 있도록 처리했다. 이러한 연출을 하기 위한 구현 방법은 개발자마다 다를 것이라고 생각된다. 필자가 구현한 방법이 최선은 아니니 훨씬 더 효율적인 괜찮은 방법이 있다면 그것을 사용하는 것이 좋을 것이다.

 

내 경우에는 TURRET의 총 상태를 5개로 나누고 5 frame 간격으로 다음 상태로 변경하도록 처리했다. 아래 코드를 참고하기 바란다.

# 0:애니메이션 멈춤, 1: 문을 여는 애니메이션, 2: 레이저 발사
# 3:발사 후 대기, 4:포열을 닫는 애니메이션
TURRET_STOP = 0
TURRET_OPEN = 1
TURRET_FIRE = 2
TURRET_WAIT = 3
TURRET_CLOSE = 4

class Enemy(pygame.sprite.Sprite):    
    
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.images = \
            [pygame.image.load(img_dir + "/img/laser_turret_01.png")
            ,pygame.image.load(img_dir + "/img/laser_turret_02.png")
            ,pygame.image.load(img_dir + "/img/laser_turret_03.png")
            ,pygame.image.load(img_dir + "/img/laser_turret_04.png")
            ,pygame.image.load(img_dir + "/img/laser_turret_05.png")
            ,pygame.image.load(img_dir + "/img/laser_turret_06.png")
            ]
        self.animation_frame = 0
        self.animation_interval = 5
        self.fire_frame = 0
        self.fire_delay_frame = random.randint(200, 300)
        #...이하생략

    def update(self, GameMain):
        self.fire_frame += 1        
        self.animation_frame += 1
        ty = GameMain.flight.rect.centery
        sy = self.rect.centery
        tx = GameMain.flight.rect.centerx
        sx = self.rect.centerx
        self.rect = self.image.get_rect()
        self.rect.center = [self.x, self.y]

        if self.turret_status == TURRET_OPEN:
            inc = 1
        elif self.turret_status == TURRET_CLOSE:
            inc = -1
        else:
            inc = 0
        
        if self.fire_frame >= self.fire_delay_frame:
            if self.animation_frame >= self.animation_interval:
                self.image_idx += inc
                self.animation_frame = 0
                if self.turret_status == TURRET_STOP:
                    self.turret_status = 1
                elif self.turret_status == TURRET_FIRE:
                    self.createBullet(GameMain.bullet_group\
                                    , sx, sy, tx, ty, 15, self.speed)
                    self.turret_status = TURRET_WAIT
                    self.animation_frame = -30
                elif self.turret_status == TURRET_WAIT:
                    self.turret_status = TURRET_CLOSE

                if self.image_idx > 5:
                    self.image_idx = 5
                    self.turret_status = TURRET_FIRE
                elif self.image_idx < 0:
                    self.image_idx = 0
                    self.turret_status = TURRET_STOP
                    self.fire_frame = 0
        
        self.image = self.images[self.image_idx]

전체 실행 소스와 이미지를 파일로 첨부한다.

04_homing_laser.zip
0.03MB