Grabbable 오브젝트 만들기

VObject 제작 을 먼저 읽어주세요
VObject에서 플레이어가 조작할 수 있는 기능을 추가하는 방법을 알아봅시다.

Grabbable Module 사용법

VObject에 상호작용을 추가하려면 GrabbableModule을 추가합니다.
자세한 내용은 Viven Grabbable Module에서 확인하세요
GrabbableModule은 다음과 같은 기능을 제공합니다.
Grab 모드
Place 모드
Grab 모드에서는 물체를 잡고 이동하거나, 기능을 사용할 수 있습니다. Grab 모드에서는 물체가 플레이어의 손을 따라다닙니다.
다음 이벤트를 VivenBehaviour에서 정의해 기능을 설정할 수 있습니다.
onGrabEvent 플레이어가 오브젝트를 잡았을 때 발생하는 이벤트입니다.
onReleaseEvent 플레이어가 Grab 모드를 종료했을 때 발생하는 이벤트입니다.
objectShortClickAction물체를 잡고 있는 채로 짧은 클릭을 하였을 때 발동합니다.
objectLongClickAction물체를 잡고 있는 채로 길게 클릭을 하고 뗐을 때 발동합니다. 길게 클릭의 기준은 1초 입니다.
objectHoldActionStart물체를 잡고 있는 채로 길게 클릭을 유지할 때 발동합니다.
objectHoldActionEnd물체를 잡고 있는 채로 길게 클릭을 유지하다 뗐을 때 발동합니다.
HoldActionClickAction을 같이 사용할 경우 실행 순서를 보장할 수 없습니다. 두 이벤트 종류를 같이 사용하는 것은 권장되지 않습니다.
Place 모드에서는 물체를 배치할 수 있습니다. 다른 물체에 부착하거나 원하는 위치로 이동, 회전시킬 수 있습니다.
Grab 모드의 이벤트들은 VivenLuaBehaviour에서 등록할 수 있습니다.
lua 스크립트의 사용법은 Viven Script 이용하기 를 참고해주세요.
동명의 함수를 lua 에서 정의하면 이벤트와 함께 호출됩니다.
명시적으로 event에 callback 함수를 등록할 수도 있습니다.
function start() -- grabbableModule을 가져옵니다. grabbableModule = self:GetComponent("GrabbableModule") -- 명시적 Callback 함수 추가 grabbableModule.onGrab:AddListener(Foo) end function Foo() Debug.Log("Grabbed") end -- objectShortClickAction 이벤트 함수 -- 물체를 잡고 Action 버튼을 짧게 눌렀다 땠을 때 호출됩니다. function objectShortClickAction() Debug.Log("objectShortClickAction : Shoot") end
Lua
복사
VObject 를 다시 빌드하면 Grab, ShortClick 시 로그가 출력되는 것을 확인할 수 있습니다.

Grab Hand Pose, GrabPoint 설정

GrabbableModule 은 Grab 시 손의 모양과 위치를 설정할 수 있습니다.
GrabbableModule의 Advanced를 클릭합니다.
Grab 시 손의 중심에 GrabPoint가 위치하게 됩니다. VR과 PC에서 별도의 GrabPoints를 설정해 다양한 사용자 경험을 제공할 수 있습니다. GrabPoint를 설정하지 않을 시 물체의 중심이 GrabPoint가 됩니다.
GrabPoint는 GameObject 에 TwozGrabPoint 컴포넌트를 추가해 만들 수 있습니다.
EditorGUI를 통해 Grab 시 손의 회전, 위치를 확인할 수 있습니다. 설정한 GrabPose는 ScriptableObject로 저장해 다른 VObject와 공유할 수 있습니다.
Attach Point는 Place 모드 시 다른 오브젝트를 붙일 수 있는 지점입니다. 조립하거나 쌓을 수 있는 물체(블럭, 벽돌 등)을 구현하려면 Attach Point를 지정해야 합니다. 자세한 내용은 TwozAttachPoint 문서를 참고해주세요.

VivenLuaBehaviour 동기화

RPC를 사용한 동기화

장난감 총을 눌렀을 때 공이 발사되는 기능을 추가하겠습니다.
ToyGun에 RPCComponent를 추가하고, VivenLuaBehaviour의 injection에 공 GameObject를 추가합니다.
VivenLuaBehaviour은 클라이언트에서 실행됩니다. 다른 클라이언트에서도 동시에 함수를 실행하려면 RPC 를 사용해야 합니다.
--- injection -- ball : GameObject (P_Ball_Yellow) --- local rpcComponent -- RPC 컴포넌트 local ballRigidBody = nil function start() -- rpc컴포넌트를 가져옵니다. rpcComponent = self:GetComponent(typeof(RPCComponent)) -- ball의 Rigidbody 컴포넌트를 가져옵니다. ballRigidBody = ball:GetComponent("Rigidbody") -- 공을 비활성화합니다. ball:SetActive(false) end -- objectShortClickAction 이벤트 함수 -- 물체를 잡고 Action 버튼을 짧게 눌렀다 땠을 때 호출됩니다. function objectShortClickAction() -- 플레이 모드가 PC 일 경우 카메라의 중앙으로 발사합니다. if Player.Mine.PlayMode == "PC" then local screenCenterPoint = Input.mousePosition local ray = Ray(self.transform.position, Camera.main:ScreenPointToRay(screenCenterPoint).direction) shootForce = ray.direction * 100 else -- VR 모드일 경우 총의 Forward 방향으로 발사합니다. shootForce = self.transform.forward * 55 end -- 내 클라이언트에서 shoot 함수를 실행합니다. shoot(shootForce.x, shootForce.y, shootForce.z) -- RPC를 호출해 다른 클라이언트에서도 shootSync 함수를 실행시킵니다. rpcComponent:SendRPC("ToyGun", "shootSync", RPCSendOption.Others, shootForce.x, shootForce.y, shootForce.z) end --- 클라이언트용 함수 function shoot(x, y, z) shootForce = Vector3(x, y, z) ballClone = GameObject.Instantiate(self.gameObject, ballSpawnPoint.transform.position, ballSpawnPoint.transform.rotation) ballClone:SetActive(true) ballRigidBody:AddForce(shootForce) end --- RPC 용 함수 function shootSync(x, y, z) shootForce = Vector3(x, y, z) ballClone = GameObject.Instantiate(self.gameObject, ballSpawnPoint.transform.position, ballSpawnPoint.transform.rotation) ballClone:SetActive(true) ballRigidBody:AddForce(shootForce) end
Lua
복사
RPC 를 사용해 다른 장난감 총 발사 기능을 구현했습니다.

SyncTable을 사용한 동기화

문서 추가 예정

사운드 이펙트 추가

Viven 에서는 FMOD를 사용해 사운드를 재생할 수 있습니다. FMOD 라이브러리를 설치해 직접 Sfx를 추가하거나 VivenEventInstance 를 사용해 AudioClip을 FMOD에서 재생할 수 있습니다.
본 문서에서는 VivenEventInstance의 사용법을 설명합니다. FMOD의 자세한 내용은 link iconwww.fmod.com 를 참고해주세요.

Viven Event Instance

Viven Event Instance 컴포넌트를 사용해 손쉽게 AudioClip을 재생할 수 있습니다.

audioClip

재생할 audioClip을 설정합니다. 등록한 audioClip은 VivenBehaviour 에서 재생할 수 있습니다.
audioClip의 볼륨은 mixerGroupType에 따라 제어됩니다.
audioClip은 FMOD의 eventInstance로 변환되어 저장됩니다. 여러 audioClip을 추가하는 것은 메모리 용량을 증가시킬 수 있습니다.

mixerGroupType

Viven 에서는 다음과 같은 카테고리로 소리를 재생할 수 있습니다.
Environment
Sfx
Bgm
Environment는 바람 소리, 새 소리와 같이 VMap에서 기본적으로 재생되는 소리입니다.
Sfx는 오브젝트, 맵과 상호작용하는 과정에서 생기는 소리입니다. 총을 발사하는 소리, 맵의 이벤트에서 발생하는 소리 등 대부분의 소리가 포함됩니다.
Bgm은 맵 전체에 재생되는 배경음악입니다. 맵의 배경음악을 설정하거나 위치에 상관없이 동일한 소리를 재생하는 경우 사용할 수 있습니다.
배경음악 재생을 동기화하는 기능은 현재 미구현 되어있습니다. 재생 시간, 현황은 동기화되지 않을 수 있습니다.
플레이어들은 환경설정에서 각 카테고리 별 크기를 조절할 수 있습니다.

VivenBehaviour 작성

장난감 총에 효과음을 추가하면서 사운드 재생 방법을 알아봅시다.
먼저 ToyGun 프리팹에 Viven EventInstance컴포넌트를 추가합니다.
MixerGroupTypeSfx 로 설정합니다.
AudioClip 은 VivenBehaviour에서 설정하기 위해 비워둡니다.
VivenBehaviour 의 Injections 에 재생할 효과음들을 추가합니다.
ToyGun.lua 에서 사운드를 재생합니다.
--- injection -- ball : GameObject (P_Ball_Yellow) -- shoootSound1 : AudioClip (Pop 1) -- shoootSound2 : AudioClip (Pop 2) -- shoootSound3 : AudioClip (Pop 3) --- -- 효과음 재생 컴포넌트 local eventInstance -- 재생할 audioClip 목록 local shootEffectSounds = {} local rpcComponent -- RPC 컴포넌트 local ballRigidBody = nil function start() -- rpc컴포넌트를 가져옵니다. rpcComponent = self:GetComponent(typeof(RPCComponent)) -- ball의 Rigidbody 컴포넌트를 가져옵니다. ballRigidBody = ball:GetComponent("Rigidbody") -- VivenEventInstance컴포넌트를 가져옵니다. eventInstance = self:GetComponent("VivenEventInstance") -- 테이블에 AudioClip을 추가합니다 table.insert(shootEffectSounds, shootSound1) table.insert(shootEffectSounds, shootSound2) table.insert(shootEffectSounds, shootSound3) -- 공을 비활성화합니다. ball:SetActive(false) end --[[ 중략.... ]]-- --- 클라이언트용 함수 function shoot(x, y, z) shootForce = Vector3(x, y, z) ballClone = GameObject.Instantiate(self.gameObject, ballSpawnPoint.transform.position, ballSpawnPoint.transform.rotation) ballClone:SetActive(true) ballRigidBody:AddForce(shootForce) -- 재생할 audioClip을 설정합니다. eventInstance.audioClip = shootEffectSounds[shootSoundIndex] -- Sfx를 1회 재생합니다. eventInstance:PlayOneShot() end --- RPC 용 함수 function shootSync(x, y, z) shootForce = Vector3(x, y, z) ballClone = GameObject.Instantiate(self.gameObject, ballSpawnPoint.transform.position, ballSpawnPoint.transform.rotation) ballClone:SetActive(true) ballRigidBody:AddForce(shootForce) -- 재생할 audioClip을 설정합니다. eventInstance.audioClip = shootEffectSounds[shootSoundIndex] -- Sfx를 1회 재생합니다. eventInstance:PlayOneShot() end
Lua
복사
효과음을 한번만 재생하려면 VivenEventInstance:PlayOneShot() 을 사용합니다. PlayOneShot은 효과음 인스턴스를 생성합니다. PlayOneShot()을 여러 번 실행하면 효과음이 중첩되어서 생성되며, 기존에 재생중인 효과음은 중단되지 않습니다. 효과음을 한 개만 실행하고 싶다면 VivenEventInstance:Play() 를 사용합니다. Play()는 설정된 AudioClip을 재생하며 Play()가 다시 호출되면 기존에 재생중인 효과음을 중단합니다. Stop() 을 호출하면 재생중인 효과음을 중단합니다.