Home
Development Environment

Grabbable 오브젝트 만들기

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

Grabbable Module 사용법

VObject에 상호작용을 추가하려면 GrabbableModule을 추가합니다.
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에서 재생할 수 있습니다.

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() 을 호출하면 재생중인 효과음을 중단합니다.