I’m working on GearVR/Daydream compatibility in my current project right now.
My goal is to have one project, that I can build for each target with minimal effort to set the build mode. I refuse to fork.
I want to just do this:
- Set one VR platform in Unity VR list. (GearVR, Daydream or None)
- Build.
- Done.
Here’s some points I’ve discovered so far.
EDIT: best to just jump to this future post: Cross-platform VR development: GearVR and Daydream
Daydream: you can have a regular camera in scene and daydream “hooks” into that when Daydream is added to VR list. Just add GVREditorEmulator and GVRControllerInput to some scene object and you’re done, you can proceed to call all GVR static functions.
GearVR: you must place OVRCameraRig prefab into scene, there can be no regular cam in scene. I have yet to work out if this can be swapped out dynamically, I think not, I think OVRCameraRig has to be there at engine init?
Small fly in the ointment: When building daydream target: you should remove (maybe rename) the Assets/OVR/Plugins folder if you have one, if you fail to do this then the daydream game will cause any samsung phone with GearVR service running to popup “place phone in GearVR”, even if “GearVR” is not in unity VR list (and thus not referenced in manifest). Many samsung users are getting this issue when trying to play daydream games right now, their workaround is to disable oculus services, but that’s hardly conducive to a nice end user experience.
You can leave your Assets/Plugins/Android/assets/oculussig_xxxxx file where it is for daydream builds.
For daydream samsung S8/S8+ (and maybe other targets?) single pass rendering yields black at the moment, I set to multi pass for now.
I am using ETC2 texture compression for now, ASTC for final production build.
CAMERA:
I’m doing runtime VR mode detection once upon game startup like this, I wait for v.bInitialised to go true until the game then proceeds to load/build further scenery:
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// INIT CAMERA FOR VR SYSTEM TYPE
private void InitSystemType()
{
// WAIT FOR MAIN CAM TO COME UP
v.oCameraActive = Camera.main;
if (!v.oCameraActive)
{
Invoke("InitSystemType", 0.1f);
LOGW("NO Camera.main");
return;
}
main.g.cLibMesh.ChildObjectToParent(v.oCameraActive.gameObject, this.gameObject);
LOGM("FOUND Camera.main");
// WHAT CAM PREFAB
string sCameraPrefab = "";
// GET VR SYSTEM NAMES INTO A LIST
string[] asDevices = VRSettings.supportedDevices;
List<string> lsDevices = new List<string>();
for (int iDevice = 0; iDevice < asDevices.Length; iDevice++)
{
lsDevices.Add(asDevices[iDevice].ToUpper());
}
// GEARVR
if (lsDevices.Contains(K_S_VR_DEVICE_LIST_NAME_GEARVR))
{
v.eSystemType = E_SYSTEM_TYPE.GEARVR;
sCameraPrefab = "vr_camera_gearvr";//OVRCameraRig and OVRManager in this prefab. And 2 OVRGearVRControllers are in the OVRCameraRig heirachy.
if (v.oCameraActive)
{
DestroyImmediate(v.oCameraActive.gameObject);
}
#if(UNITY_EDITOR)
v.bSynthesizeHMDRotation = true;
#else
v.bSynthesizeHMDRotation = false;
#endif
}
// DAYDREAM
else if (lsDevices.Contains(K_S_VR_DEVICE_LIST_NAME_DAYDREAM))
{
v.eSystemType = E_SYSTEM_TYPE.DAYDREAM;
sCameraPrefab = "vr_camera_daydream";//GVREditorEmulator and GVRControllerInput in this prefab.
}
// NONE
else
{
sCameraPrefab = "vr_camera_none";//just a dummy transform in this prefab.
v.eSystemType = E_SYSTEM_TYPE.NONE;
v.bSynthesizeHMDRotation = true;
}
// LOAD RELEVANT PREFAB
GameObject oCameraPrefab = main.g.cLibMesh.CreateObject("VR/Prefabs/" + sCameraPrefab);
if (!oCameraPrefab)
{
LOGE("CANT LOAD CAMERA PREFAB " + sCameraPrefab);
return;
}
v.tCameraPrefab = oCameraPrefab.transform;
main.g.cLibMesh.ChildObjectToParent(v.tCameraPrefab, this.transform);
v.tCameraPrefab.name = sCameraPrefab.ToUpper();
// SET CAM
v.oCameraActive = Camera.main;
main.g.cLibMesh.ChildObjectToParent(v.oCameraActive.gameObject, this.gameObject);
v.oCameraActive.nearClipPlane = K_F_CAMERA_PLANE_NEAR;
v.oCameraActive.farClipPlane = K_F_CAMERA_PLANE_FAR;
v.oCameraActive.backgroundColor = Color.black;
// DONE
v.bInitilised = true;
LOGM("SYSTEM TYPE SET TO: " + v.eSystemType);
}
My only issue is getting GearVR to init properly at runtime, I get 2 log warnings per frame:
ovrp_GetAppHasInputFocus return Failure_NotInitialized
ovrp_GetAppHasSystemOverlayPresent return Failure_NotInitialized
Searching google for either of these warnings gives zero results, so I’m obviously way out in the weeds here.
I’m not able to find docs on how to programattically instantiate Oculus in unity script, maybe the prefabs have to be in scene at start?
INPUT:
For input I am including both APIs and switching when polling like this. Then the relevant control state is stored in a superset abstraction class I made.
public class C_BUTTON
{
public bool bDown;
public bool bJustUp;
public bool bJustDown;
// UPDATE
public void Update(bool bDownNow)
{
bJustDown = (bDownNow && !bDown);
bJustUp = (!bDownNow && bDown);
bDown = bDownNow;
}
}
public class C_BUTTONS
{
public bool bFresh;
public C_BUTTON cBtn_App = new C_BUTTON();
public C_BUTTON cBtn_PadClick = new C_BUTTON();
public C_BUTTON cBtn_PadTouch = new C_BUTTON();
public C_BUTTON cBtn_Trigger = new C_BUTTON();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// LATE UPDATE
private void LateUpdate()
{
if (!v.bActive)
{
return;
}
// DATA NOT FRESH FOR NEXT FRAME
v.cButtons.bFresh = false;
v.cTouchPad.bFresh = false;
v.bBatterState_Fresh = false;
v.bControllerRotation_Fresh = false;
}
....
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// GET STATE OF ALL BUTTONS
public C_BUTTONS Controller_GetButtons()
{
// NEED TO GET FRESH DATA?
if (!v.cButtons.bFresh)
{
//---------------------------------------------------------------------------------------------------------------------
// DAY DREAM
if (v.eSystemType == E_SYSTEM_TYPE.DAYDREAM)
{
v.cButtons.cBtn_App.Update(GvrControllerInput.AppButton);
v.cButtons.cBtn_PadClick.Update(GvrControllerInput.ClickButton);
v.cButtons.cBtn_PadTouch.Update(GvrControllerInput.IsTouching);
//v.cButtons.cBtn_Trigger.Update(false); NO TRIGGER ON DAYDREAM
}
//---------------------------------------------------------------------------------------------------------------------
// GEAR VR
else if (v.eSystemType == E_SYSTEM_TYPE.GEARVR)
{
v.cButtons.cBtn_App.Update(OVRInput.Get(OVRInput.Button.Back));
v.cButtons.cBtn_PadClick.Update(OVRInput.Get(OVRInput.Button.PrimaryTouchpad));
v.cButtons.cBtn_PadTouch.Update(OVRInput.Get(OVRInput.Touch.PrimaryTouchpad));
v.cButtons.cBtn_Trigger.Update(OVRInput.Get(OVRInput.Button.PrimaryIndexTrigger));
}
//---------------------------------------------------------------------------------------------------------------------
// NONE
else if (v.eSystemType == E_SYSTEM_TYPE.NONE)
{
v.cButtons.cBtn_App.Update(Input.GetKey(KeyCode.X));
v.cButtons.cBtn_PadClick.Update(Input.GetKey(KeyCode.Space) || Input.GetMouseButton(0));
v.cButtons.cBtn_PadTouch.Update(v.cNone.vTouchpadPos_Raw.magnitude > 0.0f);
v.cButtons.cBtn_Trigger.Update(Input.GetKey(KeyCode.RightAlt));
}
// NOW FRESH
v.cButtons.bFresh = true;
}
// DONE
return v.cButtons;
}....
Above is basically what unity will have to do if it wants to “unite” all VR APIs.
Unity will have to employ some even more general abstraction under the hood, because it will have to handle extra buttons/pads/sticks/orientations found on any potential future input devices. It should allow the unity user to query any systems control state through the same UnityEngine.VR API. A wide problem for sure.