유니티의 이벤트 함수 (유니티 게임 프로그래밍)

시작하기 전에, 프로그래밍 언어에서 함수는 무엇인가?


함수는 하나의 특별한 작업을 수행하기 위해 독립적으로 설계된 코드의 집합입니다. C#에서는 메서드(Method)라고 부릅니다. 공통된 의미로 함수라고 말하기도 합니다. 어떻게 말하든 이해하니까 그러려니 하면 돼요. 해당 블로그 <쯔꾸르 형식 어드벤처 게임 개발> 콘텐츠에서는 C#을 사용하니까 메서드라고 명칭 할게요.

메서드 하나 볼까요? 다른 시간에 더 자세하게 알아볼 테니 지금은 의미만 이해합시다.

위에서 메서드는 코드의 집합이라고 했는데 메서드 이름 아래에 코드가 중괄호( { } )로 묶여 있습니다. 위 코드에서는 print가 메서드 이름이지요. print는 이름에 걸맞게 데이터를 출력해 주는 메서드이고 괄호 안의 데이터를 출력해 줍니다. 메서드를 입력하고 마지막에 ;(세미콜론)을 붙여 메서드 선언이 끝났다는 것을 명시해야 합니다.

정리하자면 print 아래에 데이터를 출력해 주는 기능을 하는 코드 집합이 있는데 print를 입력하는 것만으로 쉽게 실행할 수 있습니다. 그리고 print 메서드를 실행하는 것을 메서드를 호출(call) 한다고 합니다.

메서드(= 함수)에 대해서는 여기까지 알아보고 오늘 주제에 대해 이야기해보도록 하죠.

서론


이번 시간에는 저번 시간에 의문을 남겼던 “컴포넌트가 독립성이 짙다면 우리가 원하는 기능을 외부 입력(유저 입력)을 받아 어떻게 동작시킬까?”라는 문제에 대해 얘기해봅시다. 이 문제는 유니티 게임 엔진의 처리 방식을 알아야 이해할 수 있습니다.

유니티 엔진의 게임 세상 처리 방식


유니티 게임 엔진의 처리 방식은 평소에는 유니티가 제어권을 갖지만 특정 시기가 되면 스크립트(C# 같은 프로그래밍 언어로 입력된 응용 소프트웨어를 제어하는 코드)안에 선언된 특정 함수를 감지하여 제어권을 스크립트에게 넘깁니다. 스크립트는 함수 실행이 완료되면 다시 유니티에게 제어권을 반환합니다.

이런 함수는 게임 플레이 중에 발생하는 이벤트(특정 사건의 발동)에 대응하여 유니티에서 활성화되기 때문에 이벤트 함수라고 합니다. 유니티는 모든 게임 오브젝트를 이벤트 함수 호출 조건이 충족되는지 검토합니다. 특정 시기마다 모든 게임 오브젝트를 검토하여 이벤트 함수 호출 조건이 충족되는 게임 오브젝트가 있다면 해당 오브젝트가 가지고 있는 스크립트안에 선언되어 있는 이벤트 함수를 호출합니다.

예를 들어 캐릭터가 레벨 업하면, “레벨 업”이라는 사건이 발동했으므로 레벨 업 이벤트 함수가 발동되어 멋들어진 이펙트가 실행되는 경우를 생각해 보면 됩니다. 원래 엄청 번쩍거리는데 스크린샷 타이밍을 놓쳤네요 ㅠㅠ

유니티는 다양한 이벤트 함수를 지원하는데요, 게임 개발자는 각각의 게임 오브젝트마다 원하는 컴포넌트 기능을 적절한 사건(이벤트)에 발동해야 합니다. 아래에서 실제로 유니티 이벤트 함수를 사용하여 게임 세상에서 호출되는 원리를 보고 컴포넌트 기능을 호출해 봅시다.

Start 메서드와 이벤트 함수의 이점


void Start()
{
    // 원하는 코드 작성
}

유니티는 다양한 이벤트 함수를 가지고 있는데요, Start 메서드도 유니티의 이벤트 함수 중 하나입니다. 게임 첫 번째 프레임 전에 호출되는 메서드입니다.

간단한 테스트 Scene을 만들게요.

카메라, 광원, Plane, Sphere, Cube를 생성하고 Sphere와 Cube에만 스크립트를 연결하겠습니다. 이벤트 함수에 원하는 기능을 작성하려면 스크립트를 작성해야 하는데요. 이번 시간에는 따라 하지 않아도 됩니다. 그리고 아직 C#에 익숙하지 않으니 “이런 코드가 있구나.” 하고 생각하시면 됩니다. 나중에는 자연스럽게 코드를 이해하게 될 거예요.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TEST1 : MonoBehaviour
{
    void Start()
    {
        print(this.gameObject.name + " 앤글 블로그 입니다.");
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TEST2 : MonoBehaviour
{
    void Start()
    {
        print(this.gameObject.name + " 함께 공부해요.");
    }
}

각 스크립트는 게임이 실행되고 “게임 첫 프레임 전” 이벤트때 Start 메소드가 호출되어 게임 오브젝트 이름과 텍스트를 출력하는 기능을 합니다.

그럼 Play 버튼을 클릭해 게임을 시작해 보겠습니다.

Play 버튼은 트랜스폼 기즈모 토글 버튼 오른쪽에 있습니다. 세모 모양 버튼이 Play 버튼입니다.

게임이 실행되면서 출력되는 값은 오른쪽 아래 Console 창에서 나옵니다. 두 오브젝트에만 스크립트를 연결해 놓았기 때문에 출력되는 값이 2개이군요. 여기서 중요한 것은 게임 오브젝트가 5개(카메라, 광원, Plane, Cube, Sphere)이지만 C# 스크립트를 연결한 Sphere와 Cube만 Start 메서드를 호출한 것입니다.

생각해 보면 “게임 첫 프레임 전”은 모든 게임 오브젝트들이 해당되는 이벤트이니까 모든 게임 오브젝트가 Start 메서드를 호출해야 됩니다. 왜냐하면 게임 세상은 모든 게임 오브젝트가 구성하니까 “게임 첫 프레임 전” 이벤트를 겪겠죠. 실제로 유니티는 이벤트 함수를 호출할 게임 오브젝트가 있는지 모든 게임 오브젝트를 검토합니다. 이벤트 함수 호출 여부는 스크립트에 의해 결정되지요. 위의 Cube와 Sphere처럼 게임 오브젝트에 스크립트가 있고 해당 이벤트 함수가 있다면 이벤트 함수를 호출합니다.

게임 오브젝트들이 이벤트에 해당돼도 스크립트를 가지고 있지 않다면 이벤트 함수가 발동되지 않습니다. 스크립트를 가지고 있더라도 이벤트 함수가 선언되어 있지 않다면 발동되지 않습니다. 그러므로 위 경우처럼 “게임 첫 프레임 전” 이벤트 때 호출할 기능이 없다면 Cube와 Sphere을 제외한 나머지 게임 오브젝트처럼 스크립트를 연결 안 하거나 연결한 스크립트에 Start 메서드를 제외하면 됩니다.

중요한 것은 Start 메서드 같은 이벤트 함수는 호출하는 기능이 무엇인지 모르고 호출하는 겁니다. 위 경우에선 print 함수가 게임 세상에 어떤 영향을 줄지 모르고 실행하죠. 위험한 기능일지라도 따지지 않고 호출합니다. 이런 특징은 적절한 이벤트 함수만 있다면 원하는 순간에 원하는 컴포넌트 기능을 호출할 수 있다는 이점이 있습니다.

“컴포넌트가 독립성이 짙다면 우리가 원하는 기능을 외부 입력(유저 입력)을 받아 어떻게 동작시킬까?”라는 의문점은 “게임 오브젝트에 연결돼있는 스크립트를 통해 원하는 컴포넌트 기능을 원하는 이벤트 때 호출한다.”입니다. 이벤트 함수는 컴포넌트 기능을 묻지도 따지지도 않고 실행시켜 주기 때문에 원하는 컴포넌트를 적절한 순간에 호출할 수 있습니다.

이벤트 함수 사용 예를 하나 더 볼까요?

간단히 보는 OnCollisionEnter 메서드 사용 예


void OnCollisionEnter(Collision collision) {
        // 원하는 코드 입력
}

OnCollisionEnter은 Rigidbody 컴포넌트 또는 Collider 컴포넌트를 가지고 있는 게임 오브젝트가 Collider 컴포넌트를 가지고 있는 다른 게임 오브젝트와 충돌했을 때 호출되는 이벤트 함수입니다.

해당 Scene에서 Rigidbody 컴포넌트를 가지고 있는 게임 오브젝트는 Sphere입니다. 다른 게임 오브젝트는 모두 Collider 컴포넌트를 가지고 있습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Crash : MonoBehaviour
{
    public GameObject gameObject;

    void Start()
    {
        gameObject = GameObject.Find("Capsule");
    }

    private void OnCollisionEnter(Collision collision)
    {
        if(collision.gameObject.name == "Cube")
            gameObject.transform.position = new Vector3(0, 3, 0);
    }
}

Sphere은 위 스크립트가 연결되어 있습니다. 스크립트를 해석하자면 “Sphere가 Cube와 충돌하면 Capsule 오브젝트의 위치를 바꿔버린다!”입니다.

좀 뜬금없죠? Sphere와 Cube가 충돌했는데 왜 Capsule의 위치가 바뀌는 거죠? 하지만 이벤트 함수는 묻지도 따지지도 않고 호출해 줍니다.

정말 아무 상관없는 Capsule이 가지고 있는 Transform 컴포넌트 위치를 바꾸었네요.

중요한 것은 스크립트를 가지고 있는 게임 오브젝트는 Sphere인데, 다른 게임 오브젝트의 컴포넌트에 접근하여 기능을 호출했다는 것입니다. 이처럼 유니티 이벤트 함수의 이점은 “원하는 순간에 원하는 컴포넌트의 기능을 호출한다.”입니다.

간단하게 보는 이벤트 함수의 실행 순서


원하는 순간에 메서드를 호출하고 싶어도 적절한 이벤트 함수가 있어야 가능합니다. 다양한 이벤트 함수가 막무가내로 호출되면 게임 개발자가 원하는 대로 컴포넌트 기능을 호출하지 못하고 게임 세상을 통제하기 힘들 겁니다. 때문에 이벤트 함수는 호출되는 순서가 있는데 밑에서 간략하게 알아보시죠.

간단한 구조처럼 보이지만 매우 간략화 시켰습니다. 사진에 보이는 것보다 이벤트 함수는 더 많이 있어요. 위 그림의 이벤트 함수는 실제로 사용하는 것이 많습니다. 유니티는 이벤트 함수의 순서를 스크립트 라이프사이클이라고 부릅니다. 자세한 수명 주기를 확인하려면 링크를 따라가세요.

다음 시간에는 무엇을 배우나요?


다음 시간에는 스크립트가 이벤트 함수에 접근할 수 있도록 해주는 MonoBehaviour에 대해 알아봅시다.

다음 시간에 만나요~ 제발~

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다