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. 28. 22:54

 원래는 이번에는 스테이지 처리에 대해 구현하려고 했는데 스테이지 처리 전에 먼저 소개할 부분이 있다는 생각이 들었다. 그것은 바로 메인 캐릭터와 총알과의 충돌 처리 부분이다.

 이제까지 많은 예제를 만들면서 충돌 처리와 같은 필수적인 부분을 추가하지 않았는데 지금이라도 설명하고 넘어가는 것이 앞으로 남은 강좌에 꽤나 필수적이라는 생각이 들었다. 충돌 처리는 게임 개발하는 과정에서 꽤 난이도가 높은 편이다. 특히 적기의 모양이 복잡할 수록 많은 연산과 로직이 들어가야 한다. 그래서 과거 하드웨어 성능이 떨어지던 시절에는 충돌 영역을 단순하게 박스 모양으로 설정해서 영역 안에 총알이 들어간 경우, 충돌로 판정하도록 처리했다고 한다. 하드웨어의 연산 속도가 빠르지 않았기 때문에 게임이 충돌 처리에 많은 자원을 쓸 수가 없기 때문이었다.

 그러나 이제는 하드웨어 성능이 엄청나게 발전 했기 때문에 다양한 형태의 충돌 연산도 순식간에 처리가 가능하다. 하지만 이제는 복잡한 도형과의 충돌 시, 그것을 어떻게 수학적으로 계산하고 판단할 지는 개발자의 몫으로 남게 되었다. 그러나  너무 걱정할 것은 없다. pygame은 간단한 게임 엔진이지만 복잡한 로직의 개발 없이 간단한 한 줄의 코드로 복잡한 도형 간의 충돌을 쉽게 처리를 할 수가 있다.

 우선 충돌 시 효과를 나타내는 폭발 스프라이트를 추가할 것이다. 폭발 스프라이트는 두 가지 형태로 나눌 것이다. 

하나는 메인 캐릭터가 발사한 총알에 맞았을 때 발사한 총알이 폭발하는 스프라이트이다. 아래 그림과 같이 작고 간단하게  폭팔하는 모양을 처리할 것이다.

다른 하나는 메인 캐럭터의 발사한 총알에 기체가 폭발하는 스파라이트이다. 아래와 같이 총알 보다는 좀 더 크고 여러 개의 이미지를 묶어 효과를 줄 생각이다.

 폭팔하는 클래스의 소스 코드는 다음과 같다.

class Explosion(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.idx = 0        

    def update(self):
        if self.idx + 1 >= self.interval:
            self.kill()
        else:
            self.image = self.images[self.idx//self.imagecnt]
            self.idx += 1

class ExplosionLarge(Explosion):
    def __init__(self, x, y):
        super().__init__()
        self.images = [pygame.image.load(img_dir + "/img/explosion_l1.png")
                      ,pygame.image.load(img_dir + "/img/explosion_l2.png")
                      ,pygame.image.load(img_dir + "/img/explosion_l3.png")
                      ,pygame.image.load(img_dir + "/img/explosion_l4.png")
                      ,pygame.image.load(img_dir + "/img/explosion_l5.png")
                      ,pygame.image.load(img_dir + "/img/explosion_l6.png")
                      ,pygame.image.load(img_dir + "/img/explosion_l7.png")
                      ]
        self.image = self.images[self.idx]
        self.rect = self.image.get_rect()
        self.rect.center = [x, y]
        self.interval = 49
        self.imagecnt = 7

class ExplosionMiddle(Explosion):
    def __init__(self, x, y):
        super().__init__()
        self.images = [pygame.image.load(img_dir + "/img/explosion_m1.png")
                      ,pygame.image.load(img_dir + "/img/explosion_m2.png")
                      ,pygame.image.load(img_dir + "/img/explosion_m3.png")
                      ,pygame.image.load(img_dir + "/img/explosion_m4.png")]
        self.image = self.images[self.idx]
        self.rect = self.image.get_rect()
        self.rect.center = [x, y]
        self.interval = 12
        self.imagecnt = 4

 Explosion 이라는 클래스를 선언한 후, ExplosionMiddle 과 ExplosionLarge가 상속받도록 처리했다. 각각 폭발하는 스프라이트의 갯수에 맞춰서 interval을 주고 update 영역에서  self.image = self.images[self.idx//self.imagecnt] 순서에 따라 교체되도록 처리를 했다.

 메인 캐릭터가 발사하는 총알 코드는 아래와 같다.

class Flight(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        ... (중략) ...
        self.delay_time = 200
        self.last_fire_time = pygame.time.get_ticks()        
        self.name = "flight"

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

        if key[pygame.K_SPACE] and pygame.time.get_ticks() - self.last_fire_time > self.delay_time:
            self.last_fire_time = pygame.time.get_ticks()
            flight_bullet = FlightBullet(self.rect.x + self.rect.w/2 , self.rect.y)
            GameMain.flight_group.add(flight_bullet)

        ... (중략) ..

기본적으로 스페이스바를 누르면 총알이 생성되도록 처리했다. 그리고 마지막 발사 시간과 현재 시간을 체크하여 delay_time 보다 작은 경우에는 스페이스바가 누르더라도 총알은 발사되지 않도록 했다. 이유는 이런 시간 차를 두지 않으면 한번 키를 눌렀을 때 총알이 너무 많이 나가기 때문이다. and pygame.time.get_ticks() 영역을 소스 코드에서 지우거나 self.delay_time을 0으로 만든 다음에 첨부한 소스 코드를 실행해 보면 그 이유를 알게 될 것이다.

 나중에 게임을 좀 더 업그레이드 해서 특정한 아이템을 먹었을 때 delay_time을 조금씩 줄여주는 효과를 주는 것을 구현해 보자. 

 아래 코드는 스페이스바를 눌렀을 때 생성되는 FlightBullet 클래스의 소스코드이다.

class FlightBullet(pygame.sprite.Sprite):
    def __init__(self, sx, sy):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(img_dir + "/img/flight_bullet_01.png")
        self.rect = self.image.get_rect()
        self.rect.center = [sx ,sy]
        self.speed = 20
        self.name = "flight_bullet"
    
    def update(self, GameMain):
        self.rect.y -= self.speed
        
        if self.rect.y < -50:
            self.kill()

    def destroy(self, GameMain):
        explosion_small = ExplosionMiddle(self.rect.x, self.rect.y + 15)
        GameMain.explosion_group.add(explosion_small)
        self.kill()

self.name 이라는 멤버 변수를 하나 더 생성했는데 이것은 아래에서 충돌 판정 시 어떤 것과 충돌 했는지 감지하기 위해 추가했다.

destroy 라는 메서드는 바로 총알이 적 기체와 충돌했을 때 호출되는 메서드로 explosion 스프라이트 그룹에 폭발하는 스프라이트를 추가한 후 self.kill()을 통해 생성된 FlightBullet 은 삭제된다.

update 영역에서는 rect.y < -50 보다 작아지는 경우, 즉 화면에서 사라져서 화면 넘어로 완전히 넘어간 경우에도 self.kill()을 통해 FlightBullet을 삭제하도록 처리했다. 이 부분이 없게 되면 총알은 사라지지 않고 계속 연산 처리가 되어 게임이 오래 진행되면 연산할 객체들이 많아서 게임의 성능이 떨어지게 된다.

 그리고 마지막 game_enemy 소스이다. 여기서는 적 기체의 충돌 판정이 처리된다.

class EnemyFlight(pygame.sprite.Sprite):
    def __init__(self, sx, sy, enemy_type, move_type):
        pygame.sprite.Sprite.__init__(self)
        ...(중략)...
        self.energy = 2      
        

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

        ...(중략)...   
    
    def damaged(self, GameMain, damage):
        self.energy -= damage
        if self.energy < 0:
            explosion_large = ExplosionLarge(self.rect.x, self.rect.y)
            GameMain.explosion_group.add(explosion_large)
            self.kill()

    def check_collider(self, GameMain):
        flight = pygame.sprite.spritecollide(self\
                                           , GameMain.flight_group\
                                           , False\
                                           , pygame.sprite.collide_mask)
        if flight:
            if flight[0].name == "flight_bullet":
                self.damaged(GameMain, 1)
                flight[0].destroy(GameMain)

충돌 처리 함수 pygame.sprite.spritecollide는 아래와 같은 함수로 동작이 된다.

pygame.sprite.spritecollide(sprite, group, dokill, collided = None)

 

첫번째 변수 sprite는 충돌 처리를 감지할 sprite가 되겠다. 여기서는 self (enemy) 이다.

두번째 변수 group는 첫번째 변수와 충돌 처리를 감지할 sprite_group이다. 총알과 메인 캐릭터는 flight_group에서 동작하고 있기 때문에 여기서는 flight_group을 넣어준다.

세번째 변수 dokill은 충돌인 경우 kill(), 즉 객체를 바로 삭제할지 여부를 나타낸다. 여기서는 적 기체가 enegy를 가지고 있어 그 enegy가 0보다 작아질 때 kill() 되도록 처리했다. 이 부분을 True로 두면 한방에 적 기체는 사라지게 된다.

네번째는 collider 감지 형태인데 여기서는 collide_mask로 처리했다. 복잡한 형태의 sprite간의 충돌은 collide_mask로 처리하는 것이 좋을 것이다. 아래 링크를 타고 들어가면 collide_mask외에 rect, circle과 같은 다른 형태의 충돌 타입에 대해 설명이 되어 있다. 추후 슈팅 게임 이외에 다른 형태의 게임을 개발할 때 참고하면 될 것이다.

 

pygame.sprite.spritecollide를 디버그 해보면 list 형태의 값으로 리턴되고 만약 충돌이 없는 경우는 None이 리턴된다.

따라서 if flight: 코드는 True가 되는 경우는 충돌이 있는 경우이다.

아까 FlightBullet 클래스에 name이라는 멤버 변수를 선언했는데 그 이유가 여기에 있다.

만약 충돌한 객체가 총알인 경우에는 우선 적 기체의 enegy를 1만큼 차감하고 flight[0].destroy를 호출하여 FlightBullet 객체를 지워버리는 것이다.

여기에서는 Flight, 메인 캐릭터(플레이어)와 충돌 부분은 처리를 하지 않았는데 만약 코드를 추가한다면 아래와 같을 것이다. 메인 캐럭터가 어떤 타입의 기체로 개발하느냐에 따라 에너지를 차감하던지 바로 폭발시키면 될 것이다.

    def check_collider(self, GameMain):
        flight = pygame.sprite.spritecollide(self\
                                           , GameMain.flight_group\
                                           , False\
                                           , pygame.sprite.collide_mask)
        if flight:
            if flight[0].name == "flight_bullet":
                self.damaged(GameMain, 1)
                flight[0].destroy(GameMain)
            elif flight[0].name == "flight":
                //메인 캐릭터(플레이어)와의 충돌시 처리
                pass

 

 

 

pygame.sprite — pygame v2.1.1 documentation

The first point of collision between the masks is returned. The collision point is offset from sprite1's mask's topleft corner (which is always (0, 0)). The collision point is a position within the mask and is not related to the actual screen position of s

www.pygame.org

 

 

전체 소스 코드를 첨부한다. 게임 개발이나 공부하는데 혹시나 도움이 되었으면 하는 바램으로 올려본다.

10_enemy_formation_collider.zip
0.05MB