# 7.2 배우들의 움직임과 총알 공격 구현하기

이번 절부터는 이전 절에서 전체적인 구조적 윤곽을 잡으면서 구현체 없이 빈 함수로 만들어 두었던 각 함수들의 구현체를 채우도록 하겠다.

제일 첫번째로 **move\_player** 함수다. 기존 게임들에서는 마우스로만 조작했던 게임이라 키보드 조작에 대해서는 처음 등장하고 있다. _파이게임제로의 편리한 점은 키보드 조작에 대해 on\_key\_down 콜백함수를 꼭 쓰지 않더라도, update 콜백함수 안에서 특정 키보드의 키들(예를들어 방향키)에 대해 사전 정의된 변수들(예를 들어, **keyboard. up/down/left/right** 등)을 통해 키의 눌림을 검사할 수 있다는 것이다._ 각 방향키의 눌림에 따라 5픽셀 값 만큼의 이동을 만든다. 주인공 비행기는 전후좌우 사방으로 이동이 가능하나 단, 주어진 화면을 넘어서까지의 이동은 막도록 경계를 두는게 필요하다(10\~17라인). 이제 이정도 수준의 코딩은 이미 이전 게임들에서 많이 해봤기 때문에 이해나 구현에는 큰 어려움이 없을 것이다.

{% code lineNumbers="true" %}
```python
def move_player():
    if keyboard.right:
        player.x += 5
    if keyboard.left:
        player.x -= 5
    if keyboard.up:
        player.y -= 5
    if keyboard.down:
        player.y += 5
    if player.right > WIDTH:
        player.right = WIDTH
    if player.left < 0:
        player.left = 0
    if player.bottom > HEIGHT:
        player.bottom = HEIGHT
    if player.top < 0:
        player.top = 0
```
{% endcode %}

그 다음, 주인공이 총알발사는 부분인 **shoot\_bullets** 함수를 구현하자. 먼저, 기억해야 할 것은 총알도 크기가 작고 사소해 보여도 총알 한개한개가 다 객체라는 것이다. 사용자가 스페이스바를 눌러 총알을 발생할 때 발생할 수 있는 모든 일련의 작업순서를 생각해보면, 먼저는 총알 발사효과음 내고(3번 라인), 총알 객체를 생성하되(4번 라인), 그 총알의 발사 최초위치는 주인공 비행기에서부터 시작되야 하고(5번 라인), 총날이 날아가야 하는 방향의 각도는 상단전방이다(6번 라인). 총알이 15픽셀씩 이동해 날아가되(10라인), 만약, 날아가던 총알이 화면상단 밖으로 넘어가면, 그 총알은 더 이상의 존재의미가 없기 때문에 계속 메모리에 자리를 차지하면서 메모리를 고갈시키지 않도록(참고로 메모리가 full로 다 차면, 프로그램이 갑자기 강제종료될 수 있음) 그런 총알객체는 메모리에서 제거시킨다(11-12번 라인). 메모리에서 제거시키다는 것이 결국 총알객체를 모아놓은 모음인 bullets 리스트 객체 안에서 해당 총알객체를 삭제한다는 것이고, 이를 위해서 리스트 내장메소드 중에 **remove** 메소드를 사용하면 편리하다(12번 라인).

1회 발사되는 총알개수를 MAX\_BULLETS 으로 왜 제약하는지에 궁금할텐데, 그 총알개수 최대값을 검사하는 코드를 제거하고 게임을 실행시켜 보면 왜 그게 필요한지 단번에 이해할게 될 것이다. 우리는 스페이스 바를 아주 잠깐 1회 누른다고 생각하지만, 사실은 update 콜백함수가 초당 60번이나 실행되는(60 FPS) 엄청나게 빠른 속도로 반복해서 불리기 때문에, 스페이스 바가 계속 눌린 상태로 인식되어 생성 총알개수가 순식간에 늘어나 수많은 총알이 한번에 날아가는 것을 볼 수 있다. 이러한 현상은 우리가 적들에게서 지나친 공격적 우위를 점하게 되고, 이는 게임의 난이도를 너무 쉽게 만들어 게임의 재미를 반감시키기 때문에, 게임성을 위해서는 1회 발사되는 총알량을 어느 정도 제한하는 것이 필요하다.

6번 라인에서 등장한 angle 속성값은 우리가 [이전 서](https://app.gitbook.com/s/d15BjHzo0klcYpoHnPr6/start_coding/2-make_flower)에서 이미 익혔던 내용으로, 90도 값을 설정한 것은 화면 상단방향으로 총알이 움직이게 하려는 목적이다. 만약, [이전 서](https://app.gitbook.com/o/VlyovjzwlgPdLGXwHogx/s/ZOoo6AnroO2vGSvpOKw7/)를 거쳐오지 않은 독자가 있을 수 있어 아래에 angle 값의 의미에 대한 추가 설명 이미지를 첨부한다.

<figure><img src="https://949761892-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZOoo6AnroO2vGSvpOKw7%2Fuploads%2FDiMlU6F3CJ2CQFoimJsZ%2Fimage.png?alt=media&#x26;token=8a7dc6d9-f3bb-4056-91dc-e109deb62d84" alt="" width="563"><figcaption></figcaption></figure>

{% code lineNumbers="true" %}
```python
def shoot_bullets():
    if keyboard.space and len(bullets) < MAX_BULLETS:
        sounds.sfx_sounds_interaction25.play()
        bullet = Actor("player_bullet")
        bullet.pos = player.pos
        bullet.angle = 90
        bullets.append(bullet)

    for bullet in bullets:
        bullet.move_forward(15)
        if bullet.y < 0:
            bullets.remove(bullet)
```
{% endcode %}

그 다음으로는 적 비행기들을 출현시켜는 함수는 **create\_enemies** 를 구현해보자. 적 비행기가 출연하고 이후 공격을 위해 총알을 쏘는 모든 일련의 작업순서를 생각해보면, 먼저는 적 비행기는 눈에 보이지 않는 화면 상단에서(6번 라인) 랜덤하게 생성해(7번 라인) 화면 아래방향으로 랜덤하게(8번 라인) 날아가도록 해야할 것이다(12번 라인). 8번 라인에서 [이전 서](https://app.gitbook.com/s/d15BjHzo0klcYpoHnPr6/start_coding/2-make_flower)에서 우리가 이미 익혔던 angle과 거의 유사하나 개념이 조금 다른 **direction** 이라는 속성값이 등장했는데, 엔트리 블록코딩 거쳐온 독자라면 이미 익숙할텐데 엔트리에 방향/이동방향이란 두 가지 값이 존재했었고, 두 값의 차이는 실제 오브젝트 자체를 회전시키느냐와 오브젝트 자체에 대한 회전이 아닌 오브젝트가 움직일 때의 그 움직임의 방향만 바꾸냐의 차이가 존재했다. 여기서 direction은 후자의 경우이다.

파이게임제로에서 Actor객체의 angle 속성값을 설정할 때의 각도와 direction 속성값을 설정할 때의 각도는 동일한 의미를 갖기 때문에 코드에서 random.randint(-100, -80) 의 의미는 화면 정하단방향(-90도) 주변으로 10도씩 약간의 랜덤한 움직임을 주려는 목적인 것이다.

적 비행기가 날아갈 때, 애니메이션을 가미하여(4번, 13번 라인) 게임성을 더했다는 것도 기억해두자. 파이게임제로에서 Actor객체의 애니메이션을 만드는 법에 대해선[ 이전 서](https://app.gitbook.com/s/d15BjHzo0klcYpoHnPr6/start_coding/5-count_boy)에서 이미 학습했기 때문에 자세한 설명은 하지 않겠고, 추가 학습이 필요한 분은 이전 서의 내용을 참조하면 좋겠다. 적 비행기는 가만히 날아가는 것이 아니고, 날아가면서 총알공격을 하기 때문에 이 부분을 구현해야 하며, 쏜 총알이 날아가는 방향은 360도 전방위로 랜덤하게(19번 라인), 5픽셀 속도로 날아가게(23번 라인) 하였다. 그밖에 적 비행기든 총알이든 화면 밖을 빠져나가게 되면, 메모리에서 제거하는 코드는 여전히 항상 필요하다 하겠다(14-15번 라인, 24-25번 라인).

{% code lineNumbers="true" %}
```python
def create_enemies():
    if random.randint(0, 1000) > 980:
        enemy = Actor("enemy1_1")
        enemy.images = ["enemy1_1", "enemy1_2"]
        enemy.fps = 5
        enemy.y = -50
        enemy.x = random.randint(100, WIDTH - 100)
        enemy.direction = random.randint(-100, -80)
        enemies.append(enemy)

    for enemy in enemies:
        enemy.move_in_direction(4)
        enemy.animate()
        if enemy.top > HEIGHT:
            enemies.remove(enemy)
        if random.randint(0, 1000) > 990:
            bullet = Actor("enemy_bullet")
            bullet.pos = enemy.pos
            bullet.angle = random.randint(0, 359)
            enemy_bullets.append(bullet)

    for bullet in enemy_bullets:
        bullet.move_forward(5)
        if bullet.x < 0 or bullet.x > WIDTH or bullet.y < 0 or bullet.y > HEIGHT:
            enemy_bullets.remove(bullet)
```
{% endcode %}

나머지 2개 함수(check\_collision, draw\_text)에 대해서는 다음 절에서 계속 설명을 이어나가도록 하겠다. 만약, 공부하다가 코드에 대한 추가적인 문의가 생긴다면, 함께 [소통할 채널](https://discord.com/channels/1201625272634576936/1201632809618509904)을 준비해 두었으니 언제든 그곳을 통해 의문점을 함께 해결해 나갈 수 있다는 것을 기억하자.
