
저번 글에 이어 이번에는 장애물을 제작해보자


이제 장애물 역할을 할 액터를 만들기 위해, 상단 메뉴바에 [Tools > New C++ Class..]을 선택한 다음, Actor를 부모 클래스로 상속받아서 'Obstacle' 라는 이름의 클래스를 생성한다

그리고 헤더파일로 가서 오브젝트의 속도를 결정할 ObjectVelocity 변수를 선언해준 다음 초깃값으로는 (0,0,0)을 넣어준다
이 변수에서는 언리얼 에디터에서 수정할 수 있도록 UPROPERTY 매크로와 EditAnywhere 지정자를 넣고, Category = "ObjectSettings" 지정자로 새로운 카테고리를 생성해준다
이제 이 변수들을 사용할 차례인데, 이번 실전 프로젝트에서는 변수들을 그냥 사용하지 않고 함수를 만들어서 사용해보자

헤더파일에 오브젝트를 움직일 함수인 MoveObstacle( )함수를 추가로 선언해준 다음, 움직임에 사용될 매개변수인 DeltaTime도 넣어준다

이제 소스파일로 가서 MoveObstacle( )함수를 구현해준다.
그리고 그 안에 CurrentLocation 변수를 선언해서 현재 액터의 위치를 가져오고, 그 위치에서 부터 Deltatime 매개변수를 사용하여 초당 ObjectVelocity의 값만큼 이동시키도록 만들어준뒤, 이렇게 변한 위치를 SetActorLocation( )함수를 사용하여 현재 위치로 설정해준다
그러면 액터를 배치한 부분을 CurrentLocation 변수에 저장할 것이고, 언리얼 에디터에 수정한 값대로 매 프레임 위치가 변경되어 움직이는 효과를 낼 수 있을 것이다

마지막에는 이렇게 만들어준 MoveObstacle( )함수를 Tick( )함수에서 호출하도록 코드를 작성한다

작업이 완료되면 저장하고 컴파일을 해준다
이제 이렇게 만든 Obstacle C++ 클래스를 월드에 배치하기 위해서 C++ 기반으로 형태를 만들어주자

Obstacle C++ 클래스에 우클릭해서 Create Blueprint class based on Obstacle 항목을 선택하고,

클래스 이름을 BP_Obstacle으로 하고, 생성 경로는 Content 폴더를 선택해서 블루프린트 클래스를 만들어준다

콘텐츠 브라우저에서 [All > Content > EnvironmentPack1 > Meshes > EnvironmentPack_1] 경로에 가면 여러가지 메시들을 찾을 수 있는데, 여기서 원하는 장애물의 형태를 찾고 끌어다 놓으면 메시가 추가되는 모습을 확인할 수 있다
[툴을 이용해서 메시의 크기, 회전도 조절할 수 있고, 메시를 두개 이상 끌어다 놓아서 합치는 것도 가능하다]


테스트를 위해 플레이버튼을 누르면 너무 먼 곳에서 캐릭터가 시작되므로, Outliner에 'Player Content'를 검색하여 플레이어가 시작할 위치를 정해준다


이제 Content폴더에서 C++ 기반으로 만들었던 블루프린트 클래스를 끌어다가 배치해주고, 오브젝트의 속도를 지정해준 다음 플레이 버튼을 눌러준다

그러면 이렇게 밟고 올라갈 수 있는 발판이 하나 완성되었다
하지만 계속 위로 올라가 버리기 때문에, 만약 발판을 놓치면 더 이상 올라갈 수 있는 수단이 사라져 버리게 된다
이 문제를 해결하기 위해서는 발판을 새로 생성하거나, 위 아래로 반복하는 방법 등이 있을 것이다
그 중에서 조금 쉬운 방법으로, if 문을 사용하여 발판이 위 아래로 움직이도록 제작해보자

먼저 헤더파일로 가서, 오브젝트가 움직일 이동거리를 저장해 줄 ObjectMoveDistance 변수를 선언해서 100으로 초기화 해주고, 이동한 거리를 구할 때 사용할 시작위치인 StartLocation 변수도 선언해준다

그리고 if문을 사용하여 발판의 이동 방향을 바꿀 때, 그곳에 사용될 float 타입의 GetMovedDistance( )를 선언해서 발판이 이동한 거리를 저장할 함수를 선언한다
이때 함수에 'const'라는 키워드를 붙였는데, C++을 조금 해 보신 분들은 'const int a = 5;'와 같이 변수나 객체를 상수로 만들어서 그 값을 수정하지 못하도록 해본적이 있을 것이다. 비슷하게 함수에 const를 붙이는 경우에는 해당 함수가 클래스의 어떤 것도 수정할 수 없게 만드는 것을 의미한다.
그렇다면 왜 일반 함수가 아니라 const 키워드를 붙여서 사용해야 할까?
만약 const가 붙지 않은 일반 함수를 호출해서 if문에 사용하게 된다면 원하지 않는 오브젝트의 위치를 변경하게 되거나, 이동거리, 속도 등을 업데이트 하게 될 수 있기 때문이다. 그래서 const 함수를 사용하게 되면 클래스의 상태를 변경하지 않는다는 보장이 생기기 때문에, 함수가 하는 일을 더 명확하게 표현할 수 있으며, 예상치 못한 결과를 방지, 코드의 안정성을 높이고 버그를 줄일 수 있다.
또한 const 함수에는 일반적으로 const화가 된 함수만을 호출할 수 있는데, 예외로 const_cast를 사용하여 const를 강제로 제거하거나, mutable 키워드를 사용하여 const 함수 내에서도 멤버 변수를 변경할 수 있도록 할 수 있지만, 코드가 복잡해지거나 오류를 발생시킬 수도 있기 때문에 추천하지는 않는다.
만약 const 키워드가 붙은 함수 내에서 값을 변경하려고 한다면, 컴파일러는 데이터를 변경할 가능성이 있다고 판단하고 컴파일 오류를 발생시킨다


const함수의 예시는 SetActorLocation( )함수와 GetActorLocation( )함수에서도 찾아볼 수 있다.
각각의 함수에 마우스를 가져다 대면 위치를 임의로 변경시킬 수 있는 SetActorLocation( )함수에서는 const가 붙지 않은 것과는 달리, 단지 위치 정보를 가져오는 GetActorLocation( )함수에서는 const가 붙어있는 모습을 확인할 수 있다.
이렇게 const 함수를 선언하는 방법은 소스파일과 헤더파일의 함수 괄호 뒤에 'const'라는 키워드만 적어주면 된다.

const에 대한 설명은 이정도까지만 하고, 이제 소스파일로 가서 BeginPlay( )함수에서 StartLocation 변수에 현재 위치를 넣어준 다음,[이렇게 작성하면 게임을 플레이 했을 때, 어디에 배치하던지 그 위치에서부터 시작하도록 해준다]

그 밑에 방금 선언했던 GetMovedDistance( )함수를 구현해준다.
여기에서는 언리얼5-14 C++ <액터 이동시키기>에서 다루었던 FVector 구조체의 'Dist( )' 메소드를 사용하여, StartLocation에서부터 현재위치 까지의 거리를 반환하도록 해준다
지금부터는 오브젝트를 위 아래로 반복시킬 차례인데, if문을 사용할 때 '조건부'에 GetMovedDistance( )함수와 방향을 변경할 기준 거리를 비교한 내용을 바로 넣어 코드를 작성해도 되지만, 이것 또한 함수로 만들어보자

다시 헤더파일로 가서 bool 타입으로 IsTurningPointReached( )라는 이름으로 함수를 선언해준다

그리고 소스파일에서 IsTurningPointReached( ) 함수를 구현하고, 오브젝트가 움직인 거리와 설정한 거리를 비교하여 방향전환 지점에 도달했을 때 True 혹은 False를 반환할 수 있도록 작성해준다

이제 if문으로 IsTurningPointReached( )함수를 이용하여 GetMovedDistance(오브젝트가 이동한 총 거리)가 ObjectMoveDistance(100)보다 클 시 ObjectVelocity의 값을 반대로 변경시켜준다
만약 오브젝트의 이동거리가 100을 넘었을 경우, 속도가 음수가 되어 다시 반대로 이동하는 효과가 나타날 것이다
이것을 반복하게 되면서 발판이 왔다갔다 하는 것이다
하지만 이렇게만 설정하면 방향이 바뀌는 위치의 기준이 명확하지 않기 때문에, 반대로 시작점(StartLocation)을 지나 음수 방향으로 같은 거리를 이동하게 될 것이다. 즉, GetMovedDistance( ) 함수(이동한 거리)는 시작점부터의 거리인데 시작점(기준)은 고정되어 있으므로 음수 방향으로도 이동할 수 있는 것이다
우리가 원하는 것은 시작점에서부터 100을 이동하면 그 지점을 다시 시작 지점으로 만들어 100만큼 돌아오게 하는 형식이다

그래서 시작점(StartLocation)을 다시 현재 위치로 설정하여 그 지점부터 조건문이 다시 동작해서 원하는 값을 이동할 수 있도록 해준다
하지만 마지막으로 여기서 알아야 할 것이 있는데, IsTurningPointReached( )함수를 보면 GetMovedDistance( )(오브젝트가 이동한 거리)가 ObjectMoveDistance(설정한 거리) 보다 더 클 때 조건문이 발동한다. 즉, 이동한 거리는 항상 설정한 거리보다 커야하는데, 이렇게 지속된다면 프레임 속도 등의 요소로 인해 오브젝트의 실제 이동거리가 매 프레임마다 약간씩 달라질 수 있다.
예를 들어 한 프레임에서 오브젝트가 100값으로 설정된 ObjectMoveDistance를 약간 초과하여 움직인다면, 시작점은 그 초과분 만큼 더 멀리 설정된다. 그러면 오브젝트는 다음 방향 전환 지점까지 조금 더 짧은 거리를 움직이게 될 것이다. 그래서 일정한 패턴이 깨질 경우 예기치 못한 결과를 초래할 수 있다
이를 해결하기 위한 개념인 '법선'을 알아야 한다.
정말 간단하게 말하자면 속도는 벡터값으로 화살표로 방향과 크기를 나타낼 수 있는데, 우리가 움직이고자 하는 위치를 가리키는 화살표의 방향을 알아야 한다. 이것이 바로 '법선'이다.

법선의 방향은 그 오브젝트의 방향을 따르고, 크기는 기본적으로 1이다. 이 법선의 방향에 거리를 곱하면 시작 위치부터 끝 위치까지의 움직임을 얻을 수 있는 화살표를 얻을 수 있다

이 법선을 사용하기 위해 먼저 조건문 안에 작성했던 코드들을 제거하고,

오브젝트의 속도의 방향을 담을 ObjectMoveDirection 지역 변수를 생성한 다음, 접근연산자(.)로 멤버 함수에 접근하여 ObjectVelocity의 이동 방향을 할당해준다. [GetSafeNormal( )은 해당 벡터의 법선을 가져오는 멤버 함수이다]

그리고 StartLocation에서 부터 시작하여 조금 전 언급했다시피, 법선의 방향에 거리를 곱하여 시작 위치에서 부터 끝 위치까지 이동한 지점을 가리키는 벡터를 구하고 이 값을 StartLocation에 넣어준다 (StartLocation += MoveDirection * ObjectMoveDistance)

이렇게 구한 값을 현재 위치로 설정해서 이 지점 부터 다시 조건문이 발동되도록 하고, 액터의 위치설정이 끝나면 다시 액터가 움직이는 방향이 반대가 되도록 코드를 작성한다

마지막으로 IsTurningPointReached( )함수가 False일 경우(오브젝트가 설정된 거리에 도달하지 못했을 때), 오브젝트를 현재 속도 방향으로 계속 이동하도록 else 구문을 붙여준다

코드를 모두 저장하고 컴파일을 진행해준다

이제 디테일 패널에서 오브젝트의 속도와 이동할 거리를 설정해준 다음 플레이 버튼을 누르면,

발판이 정상적으로 동작하는 모습을 확인할 수 있다 [단축키 Alt + 'S'로 바로 시뮬레이션 하는 것도 가능하다]
다음 글에서는 발판을 이용해서 성 위로 올라갔을 때 생길 수 있는 버그들을 해결해보고, 추가로 회전하는 옵션을 넣어 더욱 다양한 장애물들을 만들어보자
VR게임 개발을 위한 언리얼엔진/C++ 공부한 내용 끄적이기...
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!