본문 바로가기

비현실 연구소/[2층]샘플 분석실

Collab Viewer 템플릿 분석: 레이저 포인터

 

 


레이저 포인터

 

 

Laser Pointer

 

serviceapi.nmv.naver.com

레이저 포인터는 사물의 특정 부분을 가리키기 위해 만들어진 도구이다.

이번 파트에서는 레이저 포인터 기능에 대해 분석한다.

(쉽게 읽힐 수 있도록 작성해보려고 노력했는데 의도대로 안된 것 같습니다. 기회가 된다면 재작성해서 올려보도록 하겠습니다.)

 


입력 매핑

레이저 포인터는 마우스 좌클릭으로 켠다.

Project Settings 의 Input 카테고리에서 ShootLaser 이벤트에 마우스 좌클릭(Left Mouse Button)이 매핑된 것을 볼 수 있다.

ShootLaser 이벤트는 BP_BasePawn에 구현되어있다.

BP_BasePawn은 이 템플릿에서 사용되는 모든 Pawn의 조상 클래스다.

 

템플릿에서 사용하는 Pawn 클래스들

버튼을 누를때(Pressed)와 뗄 때(Released) 각각 다른 이벤트가 실행된다. 하나씩 알아보자.


Left Mouse Pressed

 

LeftMousePressed 시작

위 이벤트는 네 개의 인자를 받는다. Laser Pointer가 그려질 시작점과 끝점의 위치를 나타내는 Vector, 그리고 VR모드 여부와 레이저 출력 여부에 대한 Bool 타입 인자들이다.

이벤트의 시작에는 먼저 Change Laser Visibility 변수를 세팅한다. 이 Bool 값은 아래 그림과 같이 로직의 마지막 부분에서 레이저를 그릴지 판단하는 근거로 사용된다.

 

LeftMousePressed 마지막 부분

시점 변환중에는 레이저 포인터를 사용하지 못하도록 했다. 좀 더 정확하게는, RMB가 눌려있으면 Is Rotating View가 True가 되어 이벤트 로직이 여기서 끝난다.

Call Set Custom Depth Run on Server

이미 하이라이트 된 액터가 있다면 Custom Depth를 꺼주어(False) 하이라이트 효과를 제거한다. 이 이벤트는 클라이언트의 요청에 의해 서버에서 실행한다.

이것은 RPC(Remote Procedure Calls), 원격 실행 함수이다. 해당 이벤트를 따라가보면,

서버에서는 Multicast 이벤트를 실행하면서 인자를 그대로 전달한다. 이제 모든 클라이언트에서 Set Custom Depth Multicast 이벤트가 호출된다.

각 클라이언트는 Set Custom Depth L 이벤트를 호출된다.

Customdepth 파라미터의 값에 따라 Custom Depth 렌더링을 켜고 끈다.

이 일련의 로직은 멀티플레이 환경에서 한 플레이어의 레이저 포인터가 가리키는 Static Mesh Component를 플레이어 고유의 색으로 하이라이트 시키거나 하이라이트 된 것을 끄는 기능을 한다.

다시 로직으로 돌아가보자.

Branch with Block Selection

Selection 이 막혀있는지를 확인한다.

Block Selection 변수가 세팅되는 위치를 찾아보았다.

BP_TransformComponent > SetupTransformComponent

해당 변수는 BP_TransformComponent의 SetupTransformComponent 이벤트에서 세팅된다.

그리고 이 SetupTransformComponent 이벤트는 BP_DimensionComponent에서 호출한다.

BP_DimensionComponent > SetupTransformComponent

BP_DimensionComponent에 같은 이름의 이벤트가 정의되어있다.

이 이벤트는 네 군데에서 호출된다. 그 중에 Block Selection을 True로 만드는 지점은 한 곳이다.

 

Measurement 기능 사용 중 액터 위에서 좌클릭했을 때, Block Selection = True로 인해 Static Mesh Component의 Custom Depth가 활성화되지 않는 것을 확인할 수 있다.

 

 

Block Selection = True

 

serviceapi.nmv.naver.com

이와 같이 필요한 곳에서 Block Selection 을 True로 만들어 주어 쓸 데 없이 물체가 하이라이팅 되는 것을 방지할 수 있다.

다시 BP_BasePawn으로 돌아가보자.

Call Set Custom Hit

Block Selection = False 이면 Set Custom Hit 이벤트를 수행한다.

Set Custom Hit 이벤트는 호출시 전달받은 인자 + Object Type을 Get Hit 함수에 그대로 전달해준다.

그리고 반환값(Hit 구조체)을 변수에 저장한다.

Hit 판정을 만들어 낼 Object Type은 아래 세개로 한정한다.

 

Object Type to Hit

Get Hit 함수는 내부에서 VR, Touch 디바이스 혹은 PC 여부를 확인해서 각 디바이스에 알맞은 방법으로 히트 판정 결과를 반환한다.

 

위의 과정을 거쳐서 세팅된 Current Hit 구조체 변수는 Set Hit Actor 함수에서 사용된다.

 

Hit 구조체를 받아 온 Set Hit Actor 함수는 내부에서 충돌 판정 위치 및 거리를 변수에 저장한다.

또한 검출된 액터의 레퍼런스를 변수에 저장하고 반환한다.

 

다음으로는 해당 액터가 잠겼는지(Locked)를 확인한다. 이 때, 잠겼다는 것은 다른 플레이어에 의해 선택되었다는 의미이다.

 

그리고 그 판정은 Custom Depth 활성화 여부와 Custom Depth Stencil Value의 비교를 통해 이루어진다.

 

BP_BasePawn > IsLockedActor

 

IsLockedActor 함수의 반환값을 변수에 저장한 뒤 그 값으로 분기를 한다.

 

만약 플레이어가 선택한 액터가 잠겨있다면 Current Hit Actor 변수를 비운다.

그렇지 않다면 Current Hit Actor 변수에 Hit 판정으로 얻어 온 액터를 저장한다.

Get Contextual Widget 함수를 호출해서 위젯을 불러온다.

Contextual Widget의 레퍼런스는 BP_Collab_V_PlayerController(플레이어 컨트롤러)에 있다.

 

BP_BasePawn > Get Contextual Widget

함수명에 오타가 있다. 야근하면서 만들었나보다.

Contextual Widget은 아래와 같이 미리 정의된 요소가 없다. 절차적으로 생성된다는 의미다. 이 부분은 위젯 파트에서 자세히 다룬다.

 

현재 옵션에 따른 처리를 해줘야 한다. 예를들어 마우스 좌클릭을 했을 때, Xray 옵션이 설정돼있다면 Xray 기능을 실행한다.

 

IsVR 노드는 VR 모드일 때는 아무것도 하지 않고, VR 외 모드에서는 옵션에 해당하는 기능을 실행한다.

 

옵션에 따른 실행이 끝나면 Spawn Target 이벤트를 호출한다.

 

Spawn Target 이벤트는 레이저 포인터가 가리키는 지점에 원모양의 타겟을 생성한다.

Spawn Target 이벤트를 확인해보자.

이 때, 현재 옵션이 Transform 의 Move가 아니어야 한다.

버그를 하나 발견했는데 Move 가 아니라 move 로 비교를 해야한다. 애초에 옵션에 할당된 이름이 move이기 때문이다.

 

Move가 아니라 move여야 한다. 아니면 옵션 이름을 바꾸던가.

 

더 좋은 방법은 Equal, Case Insensitive 노드를 사용하는 것이다.

 

노드 형태는 똑같다

타겟 표시는 모든 클라이언트에 그려져야 하기 때문에 마찬가지로 RPC를 이용한다.

우선, Switch Has Authority로 서버/클라이언트를 구분해서 이벤트를 호출한다.

 

서버라면 바로 Multicast 이벤트를 호출하고, 클라이언트라면 우선 서버에 MTC 이벤트를 요청한다.

 

최종적으로 각 클라이언트에서 실행되는 함수는 Spawn Pointer Anim 이다.

 

BP_BasePawn > Spawn Pointer Anim

 

이 함수에서 스폰되는 액터는 BP_LaserPointerTarget이다.

 

BP_LaserPointerTarget > BeginPlay

이 액터는 생성되자마자 두개의 구체 메시를 Timeline에 따라 애니메이션 한다.

그리고 애니메이션이 끝나면 DestroyActor를 호출해서 스스로를 제거한다.

 

 

Spherical Target Animation

 

serviceapi.nmv.naver.com

그리고 SetColor 이벤트를 이용해 플레이어에게 할당된 고유 색을 입혀준다.

 

Call Set Color in Spawn Pointer Anim

 

BP_LaserPointerTarget > Set Color

다시 Left Mouse Pressed 이벤트의 Spawn Target 지점으로 돌아오자.

 

Spawn Target에 이어서 Block Selection에 따른 분기와 타겟 액터에 커스텀 뎁스를 활성화한다.

 

레이저로 선택한 액터를 플레이어 고유의 색으로 물들이는 작업이 여기서 드디어 이루어진다.

다음으로 Is Shooting Laser 변수에 True값을 할당한다.

 

이 변수는 Transform Component 및 Base Pawn / VR Pawn / Orbit Pawn(Desktop & Touch) 등에서 사용된다.

마지막으로 Change Laser Visibility 값에 따라 분기를 실행한다.

해당 변수는 기본값이 True다.

 

Change Laser Visibility가 True일 때, 모든 클라이언트에서 레이저를 눈으로 볼 수 있다.

마우스 좌클릭 이벤트의 마지막은 델리게이트(이벤트 디스패처) 호출로 끝난다.

델리게이트에 이벤트를 할당한 곳은 BP_TransformComponent이다.

 

BP_TransformComponent > Execute After Spawn Pawn

선택된 액터의 Replicate 옵션을 켜주어 플레이어에 의한 액터의 이동을 모든 클라이언트에 동기화하기 위한 장치이다.


Left Mouse Released

마우스 좌클릭 버튼을 떼면 호출되는 이벤트이다.

 

시작하자마자 델리게이트 호출을 한다.

이 델리게이트도 Pressed 이벤트와 마찬가지로 BP_TransformComponent에서 이벤트가 바인딩 되어있다.

 

BP_TransformComponent > Execute After Spawn Pawn

액터의 리플리케이트를 해제하는 옵션이다. 쓸데없는 리플리케이트를 방지하기 위한 장치이다.

델리게이트 호출 후, 세개의 변수에 값을 할당한다.

 

Change Laser Visibility / Actor Offset Location / Is Shooting Laser

BP_BasePawn의 Actor Offset Location 변수는 어디에서도 사용하지 않는 것으로 보인다.

(BP_TransformComponent의 동명의 변수가 있다. 그리고 Transform 모드에서 마우스 휠 동작에 사용된다.)

이후에 Change Laser Visibility의 값에 따라 분기한다.

 

Change Laser Visibility = True 일 때 아래 로직을 수행한다. Pressed 의 그것과 반대로 레이저를 숨긴다.

 

VR폰이 아닐 때, Contextual Option을 불러 Do It on Released 이벤트를 호출한다.

 

Option_Button 위젯에 정의된 해당 이벤트는 Execute when Released 델리게이트를 호출하고

 

이 델리게이트에 이벤트를 할당한 클래스는 DimensionComponent와 AnnotationComponent이다.

각각 마우스 왼쪽버튼을 뗐다가 놓을 때,

측정 지점을 설정하고, Stroke를 완성하는 이벤트를 발생시켜야 하기 때문이다.

필요하다면 해당 델리게이트에 커스텀 이벤트를 추가해서 새로운 기능을 만들 수도 있을 것이다.

 

 


마무리

액터를 가리키고 하이라이트 하는 부분을 포함하여 마우스 좌클릭(Pressed + Released)의 전반적인 작동 방식에 대해 알아보았다. 클릭 한번에 꽤 복잡한 로직이 돌아가는 것을 알 수 있었고, 중간중간 버그나 오타등이 많이 발견됐다. 완성도가 그리 높지 않은 걸 보니 최적화의 기회가 곳곳에 숨어있을 것 같다.