
함수: 일정한 동작을 하는 코드들을 묶어놓은 것
클래스에 함수를 만들기 위해서 두 단계의 과정을 거쳐야 한다 -> 함수의 선언과 함수의 구현
함수 원형 선언은 이 클래스에 이런 함수가 있음을 알리는 것으로 함수의 반환형, 이름, 매개변수를 적고 세미콜론을 찍어주면 된다
-> 반환형 함수이름(매개변수);
반환형: 함수의 동작이 끝나고 나서 돌려줄 결괏값의 종류 [ex) 동작의 성공 or 실패(bool), 남아있는 아이템의 개수(int) 등]
만약 반환형이 int로 되어있다면 함수가 안에 있는 코드들의 모든 동작을 끝내고 나서 돌려줄 결괏값의 타입이 정수형이라는 의미이다
(함수가 동작을 끝내고 나서 결괏값을 돌려줄 필요가 없다면 void라는 타입으로 결괏값을 돌려주지 않을 것을 알릴 수 있다)
함수이름은 이 함수를 호출하는 데 사용되는 것으로, 개발자가 원하는 대로 이름을 지어주면 된다
[다만 함수 이름을 지을 때 이 함수가 어떤 작업을 하는 함수인지 이름만 보고도 알 수 있게 하는 것이 좋다]
매개변수(파라미터): 괄호로 묶여있는 부분을 통해서 함수가 동작하는데 필요한 변수를 함수 외부에서 받아와서 함수 내부에서 사용할
수 있게 한다. [매개변수 괄호가 비어있을 때는 함수에 필요한 외부 변수가 없다는 뜻이고, 여러 개의 매개변수가 필요하면 콤마를 이용하여 매개변수의 타입과 이름을 나열하면 된다]
함수 몸체: 함수가 실제로 처리해야 할 일을 코드로 작성
프로그래밍에서 함수를 어떠한 작업을 하나의 묶음으로 만들어서 필요할 때마다 재사용할 수 있는데, 작업 효율을 위해서는 필요한 기능 별로 함수를 만들어서 사용해야 한다
그러면 한번 함수를 다루어보고, 함수를 이용해서 액터의 위치를 이동시키는 작업을 진행해 보자
언리얼엔진을 실행하고 프로젝트 타입을 C++로 변경한 다음, 프로젝트를 생성한다
프로젝트가 생성되면 상단 메뉴바에서 [Tools > New C++ Class..]를 선택하고 Actor 클래스를 상속받아서 MyActor C++ 클래스를 하나 생성한다
그럼 한번 함수를 직접 만들어서 사용해보자
함수를 구현할 때는 소스파일에서 진행이 되는데,
반환형 클래스이름::함수이름(매개변수)]
{
//함수의 내용
}
의 형태로 작성된다
다시 한번 알아보자
1. 헤더파일(.h) -> 함수의 선언, 객체 생성 (반드시 선언만을 담아야 하는 것은 아님)
2. 소스파일(.cpp) -> 함수의 내용, 객체 기본값 지정
그 전에 먼저 함수에 사용할 변수들을 선언해보자
먼저 헤더파일로 이동하여 이동거리값인 DistanceValue, 속도값인 VelocityValue, 시간값인 TimeValue를 선언하고, UPROPERTY 매크로를 사용한 다음 지정자를 넣어준다
그리고 그 밑에 초당 이동거리를 구하기 위한 MetersPerSecond( ) 함수를 선언한다
[아직 함수의 구현이 되지 않았거나 함수 내용에 문제가 생긴 경우 초록색 밑줄이 생긴다]
변수와 함수의 선언이 끝났으니, 소스파일로 이동해서 구현해 줄 차례이다
먼저 생성자 함수에서 DistanceValue와 TimeValue프로퍼티의 기본값을 설정해주고,
아래로 가서 이동거리를 시간으로 나누어 속력을 구하는 MetersPerSecond( )함수를 구현해 준다
이때, 앞에서 말했듯이 함수의 선언부와 구현부가 나누어져 있을 경우, AMyActor::을 사용하여 이 함수가 AMyActor 클래스에 포함되어 있다는 것을 명시적으로 알려야 한다
인터넷에서 복사/붙여 넣기 시 작은 확률로 컴파일러가 인식할 수 없는 특수문자가 포함되서 컴파일 오류를 발생시킬 수 있으므로 직접 작성하는 것을 추천한다
[만약 복사/붙여 넣기로 컴파일 오류가 발생했을 시 오류 로그를 따라가 Error가 사라질 때까지 코드를 지웠다가 다시 작성해보자]
그리고 이 함수를 게임을 플레이 하자마자 불러올 수 있도록 BeginPlay( )함수에 MetersPerSecond( )를 호출해준다
이렇게 ValocityValue를 이용한 MetersPerSecond( ) 함수가 하나 완성이 되었다
하지만 이 함수가 특정한 시점(DistanceValue나 TimeValue가 초기화되거나 변경될 때)에 자동으로 호출이 되어야 한다
이를 위해 언리얼엔진에서 제공하는 콜백함수인 PostInitProperies( ) 함수와 PostEditChangeProperty( ) 함수를 사용해 보자
다시 헤더파일로 가서 PostInitProperties( )와 PostEditChangeProperty( ) 함수를 선언해준다
PostInitProperties( ) 함수: 오브젝트의 변수가 초기화될 때 호출하는 역할
PostEditChangeProperty( ) 함수: 오브젝트의 변수가 수정될 때 호출하는 역할
FPropertyChangedEvent& PropertyChangedEvent: 이 매개변수는 프로퍼티 변경 이벤트를 나타내는 객체이다
[FPropertyChangedEvent 클래스는 프로퍼티가 변경될 때 어떤 변경 사항이 발생했는지에 대한 정보를 얻을 수 있다]
여기서 함수를 선언할 때 사용한 virtual과 override 키워드에 대해 궁금할 수도 있다
그 전에 알아야 할 것은 원래 자식클래스인 AMyActor에서 상속받는 함수들과는 달리, 사실 두 함수는 부모클래스인 AActor에서 상속받는 함수이다
그래서 헤더파일에 이 함수들을 선언할 때 부모클래스 AActor를 자식클래스 AMyActor로 덮어씌운다는 의미로 virtual키워드와 override키워드를 사용해야 한다
이렇게 PostInitProperties( ) 함수와 PostEditChangeProperty( ) 함수를 선언하고 나면 소스파일에서 구현을 해야 하는데, 아까 CalculateDPS( ) 함수처럼 직접 소스파일로 가서 하나씩 구현할 수도 있지만,
함수 이름에 커서를 두고 [Ctrl +.]을 누른 후 Create Definition of 'PostInitProperties' in MyActor.cpp (정의 만들기)를 선택하면 빈 함수의 틀을 스크립트 에디터가 자동으로 생성해 준다
그리고 PostInitProperties( ) 함수에 Super::PostInitProperties 함수를 호출한 다음 MetersPerSecond( )함수를 호출하게 만들어 준다
여기서 Super키워드는 부모클래스에 있는 원본 프로퍼티나 함수를 가져오는 데 사용되는 키워드이다
AMyActor에서 override로 선언한 PostInitProperties함수는 AActor의 PostInitProperties를 덮어씌워서 만든 것이기 때문에,
AMyActor에서 만든 PostInitProperties함수에서 AActor의 PostInitProperties를 다시 호출해주지 않으면 PostInitProperties에서 실제로 처리하는 작업이 실행되지 않아서 문제가 발생할 수 있다
정리하자면
- PostInitProperties함수는 원래 AActor(부모클래스)에서 상속받는것 -> 이것을 AMyActor(자식클래스)에서 사용하려고 함
- AMyActor(자식클래스)에서 사용하기 위해 헤더파일에서 virtual과 override키워드를 사용하여 AMyActor에 덮어씀
- 소스파일에서 Super키워드를 사용하여 (AMyActor)PostInitproperties에서 (AActor)PostInitProperties를 호출해줌
- 이 작업(Super)을 하지 않으면 오류가 발생할 수 있음
그래서 부모 클래스의 함수를 덮어씌워서 만드는 경우에는 Super 키워드를 이용해서 부모 클래스의 원본 함수를 한번 실행시켜 주는 것이 좋다
PostInitProperties 함수를 모드 작성하고 나면 같은 방식으로 PostEditChangeProperty함수도 작성해 준다
다만 PostEditChangeProperty함수에서는 원본 PostEditChangeProperty함수보다 MetersPerSecond( )함수를 먼저 호출해 주도록 작성한다
이번에는 C++에서 만든 MetersPerSecond( ) 함수를 블루프린트에서 호출하고 사용할 수 있도록 만들어 보자
스크립트 에디터로 돌아가서 AMyActor 클래스의 헤더 파일을 연다
프로퍼티를 언리얼 에디터에서 볼 수 있게 하기 위해서 UPROPERTY 매크로를 사용한 것처럼, 비슷하게 함수에서도 UFUNCTION 매크로를 사용하고 지정자를 넣어서 블루프린트에서 사용할 수 있게 설정하는 등의 기능을 사용할 수 있다
함수 위에 UFUNCTION 매크로를 붙이고, 지정자는 BlueprintCallable과 Category = "Velocity"를 입력한다
UFUNCTION 매크로에서 BlueprintCallable 지정자를 넣어주면 해당 함수를 블루프린트에서 사용할 수 있게 된다
[주의: 블루프린트에서 사용할 모든 함수에는 카테고리를 할당해 줘야 블루프린트에서 정상적으로 동작한다]
UFUNCTION 매크로를 모두 작성하면 저장하고 언리얼 에디터로 돌아가서 컴파일을 해준다
그다음에는 MyActor 클래스를 기반으로 블루프린트 클래스를 만들어보자
C++ 클래스가 코드를 타이핑하여 제작된 것이라면, 이 블루프린트 클래스는 언리얼엔진의 시각적 스크립팅 시스템을 사용해서 액터의 형태를 한눈에 볼 수 있고, 노드 기반의 인터페이스를 사용하여 C++로 작성한 코드에서 추가로 로직을 구성할 수 있다.
블루프린트 클래스가 C++ 클래스를 기반으로 만들어지기 때문에 C++ 클래스의 속성을 그대로 본떠오고, 한개의 C++ 클래스로도 다른 목적을 가진 여러 개의 블루프린트 클래스를 생성할 수 있다
콘텐츠 브라우저에서 MyActor C++ 클래스 에셋을 우클릭하고 Create Blueprint class based on My Actor(MyActor 기반 블루프린트 생성)을 선택하면 블루프린트 클래스 추가 창이 뜬다
블루프린트 클래스의 이름을 BP_MyActor로 입력한 다음 Content 폴더로 지정 후 블루프린트 클래스 생성 버튼을 누른다
블루프린트가 생성되고 나면 생성된 블루프린트 클래스를 대상으로 블루프린트 에디터가 열린다
그리고 오른쪽에 디테일 패널을 보면 각 프로퍼티의 값이 MyActor C++ 클래스를 기반으로 초기화되어 있는 것을 볼 수 있다
이 블루프린트에서 코드를 작성하기 위해, 상단의 이벤트 그래프 탭을 선택해서 연다
이벤트 그래프 패널을 보면 C++ 클래스에서 본 것과 같이 BeginPlay, Tick과 같은 언리얼 이벤트 함수의 노드들이 비활성화된 채로 배치되어 있다
여기서 우리가 C++에서 블루프린트에서 사용가능하게 만들었던 Meters Per Second( ) 함수를 사용하기 위해서는
이벤트 그래프의 빈 공간에 우클릭해서 콘텍스트 메뉴를 열고 Meters Per Second를 검색해서 함수 노드를 가져와주고,
다시 빈 공간에 우클릭한 다음 Set Distance Value를 검색해서 프로퍼티의 값을 설정하는 노드를 추가한 뒤, Distance Value값을 300 정도로 넣어준다
그리고 BeginPlay 노드 핀을 끌어 순서대로 Meters Per Second( )함수 노드, Set Distance Value 노드를 이어준다
블루프린트 코드를 모두 작성한 뒤에는 C++ 코드를 작성한 뒤에 컴파일했던 것과 같이 블루프린트 에디터의 컴파일 버튼을 눌러서 블루프린트 클래스를 컴파일해 주면 된다
테스트를 위해서 BP_MyActor를 월드에 배치하고 게임을 플레이시켜 보면 월드에 배치된 BP_MyActor의 DistanceValue(500) 값이 블루프린트의 BeginPlay 이벤트에서 설정한 대로 300으로 바뀌고, 그에 따라 VelocityValue값도 60으로 변경된 것을 확인할 수 있다
이런 방식으로 프로그래머가 블루프린트에서 호출할 수 있게 핵심 기능을 가진 함수를 만들면 디자이너는 블루프린트에서 함수를 조합해서 게임 기능을 만들면 된다
이제 마지막으로 언리얼엔진에서 기본으로 제공하는 함수들을 이용해서 액터의 위치를 이동시켜보자
그 전에 언리얼에서 제공하는 함수들은 정말 굉장히 많은데
여기서 함수를 구현한 내용을 보면 BeginPlay( )함수, Tick( )함수는 물론이고,
- OnOverlapBegin( )/OnOverlapEnd( ) 함수: 액터의 컴포넌트가 다른 컴포넌트와 겹치기 시작하거나 겹침이 끝났을 때 호출되는 함수. 이 함수들은 주로 충돌 검사를 위해 사용되며, 트리거 이벤트를 처리할 때 유용하다
- SpawnActor( ) 함수: 게임 월드에 새로운 액터를 생성하고 배치하는 데 사용된다
- GetActorLocation( )/SetActorLocation( ) 함수: 액터의 현재 위치를 가져오거나 새로운 위치로 설정하는 함수
- GetActorRotation( )/SetActorRotation( ) 함수: 액터의 현재 회전값을 가져오거나 새로운 회전값으로 설정하는 함수
- PlaySound2D( )/PlaySoundAtLocation( )함수: 2D 사운드를 재생하거나 특정 위치에서 사운드를 재생하는 함수
- AddForce( )함수: 물리 액터에 힘을 추가하여 이동시키는 데 사용하는 함수
- Destroy( )함수: 액터를 게임 월드에서 제거하는 함수
대표적으로 몇몇 함수들 외에도 정말 많은 함수가 존재한다
그중에서 우리는 액터의 위치를 이동시키고 싶기 때문에 SetActorLocation( )함수를 이용할 것이다
소스파일의 BeginPlay( )함수로 가서 SetActorLocation( )함수를 적어주고 인수로는 FVector(100, 200, 300)값을 넣어준다
이렇게 하면 게임이 시작될 때, 이 MyActor C++ 클래스를 3D 월드 공간에서 X좌표 100, Y좌표 200, Z좌표 300인 지점으로 액터를 이동시키게 될 것이다
코드를 모두 작성하면 저장하고 컴파일 해준다
이제 레벨에 배치된 BP_MyActor 블루프린트 클래스를 그대로 사용해도 되지만, 그 대신에 C++ 클래스 액터를 사용해보자
여기서 BP_MyActor 블루프린트 클래스는 DefaultSceneRoot 컴포넌트가 존재하기 때문에, 이미 Transform 값을 가지고 있지만 MyActor C++ 클래스는 컴포넌트가 없기 때문에 Transform값이 없다. 즉, 위치를 지정해 줄 수가 없는 것이다
이를 해결하기 위해 MyActor C++ 클래스를 선택하고 오른쪽에 [+Add] 버튼을 눌러 큐브 형태의 메시 컴포넌트를 넣어준다
그러면 이렇게 큐브 형태를 갖추게 되고 Transform이 생기며, 위치는 (0,0,0)으로 설정이 된 것을 볼 수 있다
그리고 게임을 플레이 하면 (0,0,0) 위치에서 액터가 옮겨지면서 (100, 200, 300) 위치로 이동하는 모습을 볼 수 있다
[F8 단축키를 이용하면 플레이 상태를 유지하면서 마우스가 활성화되고 인게임 오브젝트들을 조작할 수 있다]
VR게임 개발을 위한 언리얼엔진/C++ 공부한 내용 끄적이기...
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!