# 5.4 나만의 그림판을 만들어요 - 이벤트

벌써 총 7개의 예제 중 4번째 예제를 살펴볼 차례이다. 이 책의 끝이 얼마남지 않았다. 확실히 코드량이 이전보다 많아졌다. 하지만, 걱정할 필요가 없고, 더 많은 것을 배울 수 있는 좋은 기회(?)라고 긍정적이고 도전적으로 여긴다면 우리에게 유익이 있을 것이다.&#x20;

{% code lineNumbers="true" %}
```python
from pgzhelper import *

WIDTH = 960
HEIGHT = 540

drawing = False

pencil = Actor('pencil', (WIDTH / 2, HEIGHT / 2), anchor=('left', 'bottom'))
pencil.scale = 0.3
pencil.brush_init((WIDTH, HEIGHT), 5, 'blue')
eraser = Actor("eraser", (900, 50))
eraser.scale = 0.5

def draw():
    screen.fill('white')
    pencil.brush_draw()
    pencil.draw()
    eraser.draw()
    
    if drawing is False:
        pencil.brush_stop()

def on_mouse_move(pos):
    pencil.left, pencil.bottom = pos

def on_mouse_down(pos):
    global drawing
    drawing = True

    if eraser.collidepoint_pixel(pos):
        pencil.brush_clear()

def on_mouse_up():
    global drawing
    drawing = False
```
{% endcode %}

🔢 제일 먼저는 8번 라인의 연필이라는 배우 객체를 만드는 코드를 살펴보자. 왜 객체의 중심값(anchor값)을 ('left', 'bottom')으로 하는지에 대해서는 엔트리 블록코딩에서 했던 사유와 동일하다. 화면에 그려지는 브러시 스케치가  연필의 몸체에서부터 그려지면 어색하고, 연필 펜촉에서부터 그려져야 자연스럽기 때문이다.

<figure><img src="https://3827255905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fd15BjHzo0klcYpoHnPr6%2Fuploads%2F4c0bItLLTHdmNGdSN8uf%2Fimage.png?alt=media&#x26;token=3d594f23-bbf9-4011-a600-d113ed45edb6" alt=""><figcaption></figcaption></figure>

🔢 6번 라인에서 기존에 라이브러리 내장변수가 아닌 우리의 필요에 의해서 우리가 정의한 변수가 처음으로 등장한다. **drawing** 이란 변수이름에서 그 목적을 어느정도 유추할 수 있듯이(변수를 만들 때는 의도적으로 의미있는 이름을 부여하는게 중요하다는 것을 이전 서들을 통해 이미 여러차례 언급했음), 마우스 버튼을 눌러 _그림을 그리고 있는 상태인지_ 마우스 버튼에서 손가락을 떼서 _그리기를 멈춘 상태인&#xC9C0;_&#xB97C; 구분하기 위한 용도이다. 결국 두 가지 상태만 알면 되기 때문에 해당 변수값으로 참/거짓(True/False) 값만 활용하면 되겠다.

drawing 변수의 목적과 용도는 이해했다. 이번엔 _이_ _변수의 정의(definition)가 코드영역 내 어디에 위치하고 있느냐에 관한 것으로 왜 이 변수는 함수 안쪽 영역(scope)에 존재하지 않고, 함수 바깥쪽 영역에 존재해야 하는가에 대해 알아볼 필요가 있다._ **이는 변수를 어디에 생성하느냐에 대한 코딩문법 규칙에 관한 것으로, 특정 함수 안에서만 사용되는 변수는 해당 함수 안쪽에서 정의하고, 여러 함수에 걸쳐 코드 전체적으로 공통으로 함께 사용해야 하는 변수는 이처럼 함수들 바깥쪽 영역에 정의하게 된다. 이렇게 코드전체적으로 함께 공유하는 변수를 global(전역적) 변수라고 부른다는 것도 함께 기억하자.**

🔢 drawing 값은  26번 라인의 **on\_mouse\_up** 콜백함수(함수이름 시작이 **on\_ 으로 시작 되기때문에, 특별히 이벤트(마우스 버튼 누름 과 같은 액션 등)에 따라** 파이게임제로 라이브러리에서 자동호출 된다는 의미)와 33번 라인의 **on\_mouse\_down** 콜백함수 두 곳에서 함께 사용되고 있다는 것을 알 수 있다. 이렇게 두 함수가 함께 공유해야 하는 변수이기 때문에 해당 변수는 함수들 바깥쪽 영역에 정의되었던 것이다.&#x20;

이제 이 변수를 사용하는 함수에서의 활용을 유심히 살펴보자. **함수 밖에 있는 변수라도 함수 내에서 단지 그 변수값을 읽어서 활용하는데는 기존과의 차이가 없다. 그러나, 만약 이 변수값을 함수 안쪽에서 수정하려면 이 때는 파이썬의 새로운 문법을 알아야 하는데, 27, 34라인에서 보는 것과 같이 해당 변수명 앞에 global 이라는 키워드(keyword)를 붙혀 변수선언 함으로서 해당 변수는 이 함수의 바깥 영역에 존재함을 명확하게 알린 후에야 그 값을 수정할 수 있다는 것이다.**

{% hint style="info" %}
_참고로 여기서 주의할 것이 있는데 **파이썬이 갖는 엄밀히 문법을 따지지 않는 유연성이 장점이면서 동시에 이것이 단점이 되는데**,  global 이라는 키워드를 명확히 쓰지 않고, 실행을 해도 사전에 에러없이 일단은 실행이 잘 되기 때문이다. 그러나, 막상 이 변수값을 수정하려는 그 순간에 에러가 발생하게 된다. 이처럼 왜 원하는데로 동작하지 않는지의 논리적 오류를 사전에 발견하기가 쉽지 않은 상황을 만날 수도 있으니 주의하도록 하자._
{% endhint %}

🔢 15\~18번 라인까지 draw 콜백함수 안에서 배경색을 칠하고, 각각의 등장 오브젝트들을 화면에 그리게 된다. 이때 각 오브젝트의 draw 함수들의 호출순서, 즉 각 오브젝트들이 화면에 그려지는 순서도 고려해야 함을 지난 장에서 언급했었다. 순서를 유의해 연필 위쪽으로 우리의 브러시 스케치가 존재하는 등 다소 어색한 형태가 없도록 하자.

🔢 20-21번 라인은 조건문으로 브러시를 통해 그리는 것을 중단할 시점을 찾기 위해 drawing 변수값을 활용한다. 그 값이 거짓(False)이 되면, brush\_stop 함수(메소드)를 사용해 brush 사용을 중단한다.

🔢 23-24번 라인은 마우스 움직임과 실시간으로 연필 오브젝트의 중심좌표와 일치시키는 작업을 하고 있다. 그래야만 마우스의 움직임에 맞춰 연필 오브젝트가 같이 움직일 수 있기 때문이다. 24라인의 문법이 약간 이상해 보일 수 있는데, **pos라는 값 자체가 한 개의 값처럼 보이지만, 실제로는 (x, y) 좌표값을 갖는 튜플(Tuple)값이다.** 튜플은 아직 배우지 않은 처음 등장하는 데이터 타입(종류)이니 이번 기회에 좀 더 상세히 알아보도록 하자.

#### 튜플 (Tuple) <a href="#tuple" id="tuple"></a>

튜플은 기존에 우리가 알고 있는 [리스트(List) ](https://app.gitbook.com/s/nDUP8xZ7pbezrK2wo5dX/basic_syntax/3.6-list)데이터 타입과 유사한데 어떻게 사용할 수 있는지 먼저 문법을 확인해보자. 정말이지 리스트의 대괄호가 소괄호로 바뀐거 외엔 표면적으로 차이가 없다.

> **튜플명 = (문자, 숫자 등의 여러 값. 단, 값들은 콤마(,)로 구분)**

**그러나, 리스트와 튜플의 근본적인 차이는 값을 추후에 바꿀 수 있느냐/없느냐의 여부이다, 수정가능/읽기전용의 차이이다.** 튜플은 후자인데, 의도적으로 읽기전용(read only)값으로 설정함으로써, 처음 초기값으로 설정한 값을 보호해서 추후에 자신이나 또는 다른 개발자가 개발해 나가면서 실수로 해당 값을 변경하려는 시도를 처음부터 막으려는 의도가 있다. 항상 우리는 함수 저작자가 만들어 놓은 의도성 있는 제약을 따르도록 하자. 우리는 남이 만든 함수나 라이브러리를 가져다가 쓰는 입장에서 크게 불만(?)을 갖긴 어렵고, 함수 저작자가 제안하거나 제약하는 틀 안에서 코딩한다고 생각하면 좀 더 편한다. 참고로 어떤 제약이 있는지의 상세한 확인은  해당 함수에 대한 [라이브러리 메뉴얼](https://pygame-zero.readthedocs.io/en/stable/hooks.html#on_mouse_move)을 통해 확인가능하다.

다시한번 정리하면, 24번 라인의 문법은 pos의 튜플에서 x좌표값을 꺼내 pencil.left 에 저장하고, y좌표값을 꺼내 pensil.bottom 으로 저장시킨다.

🔢 26번에 **on\_mouse\_down** 콜백함수는 이름에서 유추할 수 있듯이, 마우스 버튼을 누를 때 라이브러리를 통해 자동으로 불려지게 된다. 마우스 버튼을 눌렸을 때 그 마우스 커서가 위치한 지점도 pos라는 함수 파라미터값으로 자동으로 넘어오기 때문에 우리는 이 값을 활용해 재미있는 응용을 만들 수 있다. 이제 마지막으로 본 장의 마지막 코드인 30-31라인 코드를 살펴보면 끝이다. 드디어 게임코딩에서 늘상 사용되는 오브젝트들 간의 영역 충돌검사를 하는 코드가 처음으로 등장하였다. 마우스 클릭한 지점의 위치(여기서도 pos라는 튜플값)가 지우개 오브젝트의 영역 안에 있는가를 **collidepoint\_pixel** 함수(메소드)로 확인하여 참(True)가 되면, 즉, 우리가 마우스로 지우개 오브젝트를 클릭했는지를 확인해 만약 눌렸으면 전체 스케치한 내용을 지우는 것이다.

장이 시작할 때는 코드량이 많은 것처럼 보였지만, 막상 그 내용을 이해하고 나면 그렇게 어려운 코딩은 아니라는 것을 알 수 있다. 이처럼 우리는 코드량이 많다는 것으로 미리부터 겁먹을 필요가 전혀 없다. 마지막으로 해당 그림판의 최종 실행결과를 보는 것으로 본 장을 마무리 하겠다.

<figure><img src="https://3827255905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fd15BjHzo0klcYpoHnPr6%2Fuploads%2FXVvoT6Zhmk7ofkK3IKmo%2FKapture%202024-07-25%20at%2016.16.20.gif?alt=media&#x26;token=788bef71-99c6-4970-a0a0-700b29f99bc7" alt=""><figcaption></figcaption></figure>

