Preview only show first 10 pages with watermark. For full document please download

Oculus Platform Developer Guide

   EMBED


Share

Transcript

Oculus Platform Developer Guide Version 1.10.0.0 2 | Introduction | Oculus Platform Copyrights and Trademarks © 2017 Oculus VR, LLC. All Rights Reserved. OCULUS VR, OCULUS, and RIFT are trademarks of Oculus VR, LLC. (C) Oculus VR, LLC. All rights reserved. BLUETOOTH is a registered trademark of Bluetooth SIG, Inc. All other trademarks are the property of their respective owners. Certain materials included in this publication are reprinted with the permission of the copyright holder. 2 |  |  Oculus Platform | Contents | 3 Contents Update Your App.................................................................................................4 Adding Oculus Platform Support to Your Project............................................................................................ 4 Making REST Requests...................................................................................................................................... 4 Native Rift and Gear VR Apps.......................................................................................................................... 5 Unity................................................................................................................................................................... 6 Oculus Platform Feature Tutorials....................................................................... 8 Entitlements: App Authorizations...................................................................................................................... 8 Ownership: Additional Authorizations...............................................................................................................9 User and Friends................................................................................................................................................9 Rooms.............................................................................................................................................................. 10 Coordinated App Launch (CAL)...................................................................................................................... 12 Room Invites.................................................................................................................................................... 13 Parties...............................................................................................................................................................15 Basic Matchmaking Walkthrough.................................................................................................................... 17 Advanced Matchmaking.................................................................................................................................. 21 VoIP: Voice over IP..........................................................................................................................................24 P2P................................................................................................................................................................... 26 In-App Purchases............................................................................................................................................. 28 Oculus Keys..................................................................................................................................................... 30 Leaderboards................................................................................................................................................... 31 Achievements................................................................................................................................................... 34 Cloud Storage..................................................................................................................................................36 App Groupings................................................................................................................................................ 39 4 | Update Your App | Oculus Platform Update Your App This section describes how to prepare your app or experience for the Oculus Platform features. Adding Oculus Platform Support to Your Project To add Oculus Platform support to your Visual C++ project: 1. 2. 3. 4. 5. 6. 7. Open the Properties page. Select VC++ Directories. Add the location of the SDK includes folder (InstallFolder\include) to Include Directories. Add the location of the lib file (InstallFolder\Windows) to Library Directories. Expand Linker and select Input. Add the loader library (LibOVRPlatform32_1.lib or LibOVRPlatform64_1.lib) to Additional Dependencies. Using the DLL loader instead of direct linking enables DLL signature verification and graceful detection of the Oculus Runtime. To use the loader, drop the cpp file (InstallFolder/Windows/OVR_PlatformLoader.cpp ) into your project. To bypass the Loader, add this define before including OVR_Platform. #define OVRPL_DISABLED #include Making REST Requests Depending on the type of operation, you might use the Platform UI or API. Although many requests are made in the UI, eventually you will be able to make most of them programmatically. This section describes how to make REST requests. Before you can make a request, make sure you have the credentials for your app. For more information, see Oculus Platform Setup. Many types of requests to the API use the following format: [GET/POST/DELETE] https://graph.oculus.com/app_id/api ?access_token=token|app_id|app_secret &option_1= &option_2= ... where: • • • • app_id—ID of the app. api—name of the API (e.g., "leaderboards"). token—Request token. app_secret—the secret key for the app. Oculus Platform | Update Your App | 5 • option_1, option_2, ...—options specific to the API. The following is an example request that creates a leaderboard: [POST]https://graph.oculus.com/3294569807254367/leaderboards?access_token=OC|3294569807254367| 971bf33c22d4e56438900334be288e43&api_name=game-leaders&sort_order=HIGHER_IS_BETTER> Note: All requests are encrypted; only the endpoint (graph.oculus.com) is passed in the clear. Native Rift and Gear VR Apps This section describes how to set up your app for native C++ development. Initializing in Production To ensure that your game is ready for the Oculus Store, you need to add code that ensures each user has an entitlement for your app. Depending on your development environment, there are different ways to verify entitlements. This section describes how to check entitlements for native C++ apps. Applications that run in a production environment and are launched from the Oculus App use a call, similar to the following, that passes in your AppID: // Initialization call if (ovr_PlatformInitializeWindows(appID) != ovrPlatformInitialize_Success) { // Exit. Initialization failed which means either the oculus service isn’t on the machine or they’ve hacked their DLL } ovr_Entitlement_GetIsViewerEntitled(); Use code similar to the following to poll for a response: // Poll for a response while ((message = ovr_PopMessage()) != nullptr) { switch (ovr_Message_GetType(message)) { case ovrMessage_Entitlement_GetIsViewerEntitled: if (!ovr_Message_IsError(message)) { // User is entitled. Continue with normal game behaviour } else { // User is NOT entitled. Exit } break; default: break; } } Checking Entitlement in Production Make the following call, which will return a bool that indicates whether the user is entitled: ovr_Entitlement_GetIsViewerEntitled(); 6 | Update Your App | Oculus Platform which will result in a message returned with the results. Note: If you are using the legacy test token for standalone development, all entitlement checks will pass. Modifying Your Game Loop To modify your game loop: 1. Look for messages being sent from the platform by modifying your game loop to call ovr_PopMessage() in a loop until it returns null. These can be returned from a call or a notification sent from the platform. 2. When a message is received, call ovr_Message_GetType() to extract the message type. 3. Use ovr_Message_IsError() to verify if the message contains a failure or a success response. Messages include different data, based on the type. There are several helper functions in OVR_Platform.h that parse the data into a convenient struct, which use the ovr_Message_* naming convention. 4. After processing a message, call ovr_FreeMessage() to clean up and prevent memory leaks. Unity This section describes how to set up your app for Unity development. Initializing in Production To ensure that your game is ready for the Oculus Store, you need to add code that ensures each user has an entitlement for your app. Depending on your development environment, there are different ways to verify entitlements. This section describes how to check entitlements for Unity apps. To initialize in Unity, use the Initialize method of the Platform object: Oculus.Platform.Core.Initialize(appID) Checking Entitlement in Production To check for an entitlement: Platform.Entitlement.IsUserEntitledToApplication().OnComplete(callbackMethod); which will call your callback method when returning the results. To process the results, use code similar to the following: void callbackMethod (Message msg) { if (!msg.IsError) { // Entitlement check passed } else { // Entitlement check failed } } } } Oculus Platform | Update Your App | 7 Note: If you are using the legacy test token for standalone development, all entitlement checks will pass. Initializing in the Unity Editor For Unity, you set your test token in the Unity editor. After importing the Oculus Platform wrapper, the Oculus Platform menu item is added to the editor. Select Platform Settings and paste in your Token. Then, initialize normally: Oculus.Platform.Core.Initialize(appID) Note: Make sure to give an entitlement to the user. For more information, see Entitlements: App Authorizations on page 8. Make sure you are logged into the Oculus App while developing in a standalone environment. Modifying the Game Loop Add the following call to your Update() method to make sure your callbacks are processed: Request.RunCallbacks(); 8 | Oculus Platform Feature Tutorials | Oculus Platform Oculus Platform Feature Tutorials This section contains detailed information about each Oculus platform feature. Entitlements: App Authorizations Entitlements prove that a copy of your app or other content is legitimate. You’ll want your app to make an entitlement check to verify the user owns your content. The API for entitlement checks provided by the Oculus Platform SDK will work even if the user isn’t connected to the Internet. However, for an entitlement check to be effective, you'll also need to add code to respond appropriately to a failed check, such as by showing an error message and quitting the app. A failed entitlement check won’t result in any specific action on its own. Entitlements are a core Oculus platform feature, no matter what development method you’re using. You should always include an entitlement check before submitting your app for review by Oculus. You can find step-bystep examples of how to set up entitlements using the Oculus PC SDK or Oculus Mobile SDK for native Rift or Gear VR apps, as well as using the Unity game engine or Unreal Engine in our platform walkthrough examples. Most Unreal developers should use the Verify Entitlements Blueprint under Oculus Platform. However, here's an overview of the required steps: 1. Add the code for the entitlement check to your app (e.g. ovr_Entitlement_GetIsViewerEntitled for a native Rift or Gear VR app, or Entitlements.IsUserEntitledToApplication().OnComplete for Unity). 2. Get the results of the entitlement check (e.g. ovr_Message_GetType returns a ovrMessage_Entitlement_GetIsViewerEntitled message with content ovr_Message_IsError or, for a Unity app, checking if (msg.IsError)). 3. Respond appropriately, for example by showing the user an error message and exiting the app, or by proceeding normally. Note: • Before you can begin using platform features, you’ll need to complete the necessary setup steps described in Oculus Platform Setup. • To verify ownership from your own server, you can also use a cryptographic nonce. See Ownership: Additional Authorizations on page 9. • You can also use entitlements to verify any In-App Purchases you offer in your app. See In-App Purchases on page 28. • Developers and admins automatically get entitlements to all apps within that organization. When an app is added to an organization, all developers and admins will have access to it unless you manually remove them from the Manage Apps page. • For more detail about any function or parameter, see the Oculus Platform Reference Content. FAQ Q: What is necessary for an entitlement check to pass? A: One of the following: • The user is part of the organization that owns the app • The subscribed user is on a release channel for the app • The application appears in your library Oculus Platform | Oculus Platform Feature Tutorials | 9 Q: How often will Oculus refresh the library? A: Every five minutes, if there are no running Oculus applications. To prompt a library refresh within five minutes, close all running Oculus application and wait until the library refreshes. It should then appear in the Oculus Library. Once an applicatoin is visible in the Library, it will pass the entitlement check, even if "Not Valid" is displayed above the application icon. Note: The Unity and Unreal engines are both considered Oculus applications. Q: I still don't see the application, what else can I do? A: Try logging out and logging back into Oculus. Ownership: Additional Authorizations There are other ways to validate copies of your content, as well. To ensure that the user running your application is a legitimate customer, we also provide a way to make a server-server call using a client-provided nonce that can’t be tampered with by the client. Get Cryptographic Nonce This method is a single use; once verified with the server, it is no longer valid. The following function requests a cryptographic nonce that identifies the current user: ovr_UserProof_Generate() It returns the ovrMessage_UserProof_Generate message type. Use ovr_Message_GetUserProof() to parse the message into an ovrUserProof pointer. Your server can validate a Nonce using a secure server-server call using an HTTP POST to the following end point: [POST] https://graph.oculus.com/user_nonce_validate ?access_token= &nonce= &user_id= The request returns verification. For example: {"is_valid":true} Note: Your current App Secret can be found on the developer portal under Platform > API. A usergenerated nonce is single-use only. To test requests, Oculus recommends the Postman Chrome plugin. User and Friends Depending on your needs, you may need to retrieve a list of your user's Oculus friends. Here are some of the functions you can use: 10 | Oculus Platform Feature Tutorials | Oculus Platform Get Logged In User The following function returns all available information for the currently logged-in Oculus user: ovr_User_GetLoggedInUser() It returns the ovrMessage_UserGetLoggedInUser. Use ovr_Message_GetUser to parse the message into an ovrUser pointer. Get Currently Logged In Friends The following function returns all of the currently logged-in user's friends who also own your app: ovr_User_GetLoggedInUserFriends() It returns the ovrMessage_UserGetFriends message type. Use ovr_Message_GetUserArray() to parse the message into an ovrUserArray pointer. Get Information About a User To display information about a specific Oculus user, such as a friend of the currently logged-in user: ovr_User_Get(ovrID userID) It returns the ovrMessage_UserGet message type. Use ovr_Message_GetUser() to parse the message into an ovrUser pointer. Rooms Rooms are virtual places where users can meet to chat or initiate games and experiences. There are three types of rooms: public rooms, private rooms that only friends can join, and private rooms that are invitation only. Rooms can be moderated and initiated by your app or you can allow users to create rooms that they control. Get Information About a Room The following requests information for the provided room ID: ovr_Room_Get() It returns the ovrMessage_RoomGet. Use ovr_Message_GetRoom() to parse message into an ovrRoom pointer. Join an Existing Room To have the logged in user join an existing room: ovr_Room_Join() It returns the ovrMessage_RoomJoin message type. Use ovr_Message_GetRoom() to parse the message into an ovrRoom pointer. Oculus Platform | Oculus Platform Feature Tutorials | 11 Get Information About the Current Room To get information about the current room: ovr_Room_GetCurrent() It returns the ovrMessage_RoomGetCurrent message type. Use ovr_Message_GetRoom() to parse the message into an ovrUser pointer. Leave a Room To have the logged in user leave the room: ovr_Room_Leave() It returns the ovrMessage_RoomLeave message type. Use ovr_Message_GetRoom() to parse the message into an ovrRoom pointer. Create a Room You can create a public or private room and set a maximum room size. To create a room or enable a user to create and join a room: ovr_Room_CreateAndJoinPrivate() It returns the ovrMessage_RoomCreateAndJoinPrivate message type. Use ovr_Message_GetRoom() to parse the message into an ovrRoom pointer. Find Users to Invite To get a list of users that are entitled to the app and can currently log in: ovr_Room_GetInvitableUsers() It returns the ovrMessage_UserGetInvitableUsers message type. Use ovr_Message_GetUserArray() to parse the message into an ovrUserArray pointer. Invite a User to a Room Using the list of invitable users, your app or user can invite one or more users to the room: ovr_Room_InviteUser() It returns the ovrMessage_RoomInviteUser message type. Use ovr_Message_GetRoom() to parse the message into an ovrRoom pointer. Kick a User out of a Room To kick a user out of a room: ovr_Room_KickUser() It returns the ovrMessage_RoomKickUser message type. Use ovr_Message_GetRoom() to parse the message into an ovrRoom pointer. 12 | Oculus Platform Feature Tutorials | Oculus Platform Update Room Metadata You can add up to 2k of key/value pairs to a room. To add metadata: ovr_Room_UpdateDataStore() It returns the ovrMessage_RoomUpdateDataStore message type. Use ovr_Message_GetRoom() to parse the message into an ovrRoom pointer. View Room Updates The following is a platform notification that isn't triggered by a call: ovrMessage_RoomUpdateNotification Use ovr_Message_GetRoom() to parse the message into an ovrRoom pointer. Coordinated App Launch (CAL) Coordinated App Launch (CAL) features your app in the Rooms on page 10 Group App Launcher where users can easily discover, install, and play your social app together. There are two steps to using CAL in your game, integrating the CAL API and configuring CAL on the Oculus Developer Dashboard. Coordinated App Launch is only available for Gear VR apps at this time. Integrate CAL API When users launch a CAL-enabled app from Rooms, your app will receive information about the users in the room, as well as the unique roomID created for the session. This information is sent with the launch intent. You can also retrieve this information from the following method: ovrLaunchDetailsHandle handle = ovr_ApplicationLifecycle_GetLaunchDetails(); if (ovr_LaunchDetails_GetLaunchType(handle) == ovrLaunchType_Coordinated) { ovrID roomID = ovr_LaunchDetails_GetRoomID(handle); ovrUserArrayHandle usersHandle = ovr_LaunchDetails_GetUsers(handle); ... } Or if you're using Unity: using Oculus.Platform; using Oculus.Platform.Models; LaunchDetails launchDetails = LaunchDetails(CAPI.ovr_ApplicationLifecycle_GetLaunchDetails()); if (launchDetails.LaunchType == LaunchType.Coordinated) { UInt64 roomID = launchDetails.RoomID; UserList users = launchDetails.Users; ... } These methods are synchronous and will return results immediately. Note: To test your integration during development, you can simulate a CAL event by using the following command on your phone: adb shell "am start -n com.your.package.name/.YourMainActivity -e intent_cmd \ '"'{"ovr_social_launch":{"type":"COORDINATED","room_id":"123","users" \ Oculus Platform | Oculus Platform Feature Tutorials | 13 :[{"id":"4","alias":"janedoe1234"},{"id":"7","alias":"johndoe5678" \ }]}}'"'" Replacing com.your.package.name/.YourMainActivity with your app's package name and initial activity, and the 'users' array with the IDs and user names of your own test users. You can use this CAL information to place the group in a shared VR session. If you plan to use VoIP: Voice over IP on page 24 in your app, you'll need to determine how you will be handling the transition from Rooms to your app. Please see Parties on page 15 for more information. Configure CAL To configure CAL select your app in the Developer Dashboard and navigate to CAL in the Platform tab. Select 'Supported' to enable CAL and choose the required minimum and maximum users to allow in the launch of an app session. The number of users allowed will depend on the type of app you are creating. For example, a game of Chess should require both a minimum and maximum of 2, while an open-ended multiplayer game could accept groups between 1 and 4. Enter the minimum version code for the first build that supports the CAL API. For the approval process, the build can be in any of the testing or development channels. Once CAL support is enabled and a supported build is uploaded, any account on that release channel will be able to test the feature in Rooms. After the configuration is complete and API integrated, you can submit the app for CAL review. Please send an email requesting review to [email protected]. When the app is approved you will have the option of setting the Public field to 'Yes' enabling public use of the feature. User Experience Recommendations We recommend that your app immediately places users into a social experience. Also, if your app offers multiple game modes or options, we recommend that you set a default mode so users are taken to a social VR experience as soon as possible. If session or game customization is available, present these choices after the users can hear and see each other. Room Invites Room invites are a great way to enable users of your app to initiate a shared experience. You can only use room invites when the inviting user is in a private room or in a moderated room. Matchmaking rooms don’t support room invites. For more on different types of rooms, see Rooms on page 10. Room invites can be received whether the invited person is currently in VR or not. However, people can only be invited to apps or content they’re entitled to, either because they own it, or because they’re a developer of the content. This section describes the required steps to use room invites. Sending Invites You can send invites by constructing your own UI or using the Oculus UI. To build your own: 1. Make sure the user has joined the room for which he or she is sending invites. 14 | Oculus Platform Feature Tutorials | Oculus Platform 2. To get a list of users that can be invited to that room, call ovr_Room_GetInvitableUsers(). You’ll get information for each invitable person that includes the Oculus username (to display) and a user-specific invite token. 3. To actually send the invite, call ovr_Room_InviteUser(ovrID roomID, const char *inviteToken). Note: • Each invite token can only be used once. To re-invite the same user, call ovr_Room_GetInvitableUsers() again. • If you request room updates for the room using the subscribeToUpdates parameter, your app will receive a notification when the user joins. The Oculus SystemUI provides a dialog for room invites. To use the Oculus UI: 1. Make sure the user has joined the room. 2. To open the Oculus UI (and temporarily background your app) call ovr_Room_LaunchInvitableUserFlow(ovrID roomID). 3. The user will see an interface outside of your app, in the SystemUI, that allows users to invite others. Accepting Invites Users can receive and accept room invites in different ways. To accept invites outside of your application: 1. The Oculus app will provide an invite notification to the user for both Rift and Gear VR. 2. If the user accepts this notification then your application will launch. 3. To detect inside your application that the user launched from accepting an invite, you can look for an inviteaccepted message. • For a native app, while polling the message queue, look for the ovrMessage_Notification_Room_InviteAccepted message. For instance: int messageType = ovr_Message_GetType(response); if (messageType == ovrMessage_Notification_Room_InviteAccepted) { const char *roomIDString = ovr_Message_GetString(response); ovrID roomID; ovrID_FromString(&roomID, roomIDString)); // we can now try to join the room } • For a Unity app, use the following callback: Oculus.Platform.Rooms.SetRoomInviteNotificationCallback ( (Oculus.Platform.Message msg) =>{ if (msg.IsError) { // Handle error } else { string roomID = msg.GetString(); // we can now try to join the room } } ); To check for invite notifications from inside your application: 1. 2. 3. 4. 5. Call ovr_Notification_GetRoomInvites() within your app to get the active invites for the user. Parse the returned ovrRoomInviteNotificationArray to get the list of rooms the user is invited to. Call ovr_Room_Get(roomID) on each of the rooms to get more information, such as the room owner. Accumulate and display the invite details as appropriate in your application. The invite can be accepted by calling ovr_Room_Join(roomID) to join the room. Oculus Platform | Oculus Platform Feature Tutorials | 15 Note: • Before you can begin using platform features, you’ll need to complete the necessary setup steps described in Oculus Platform Setup. • For more detail about any function or parameter, see the Oculus Platform Reference Content. Parties Parties are only available for Gear VR apps at this time. Parties allow users to chat with friends and navigate VR together. Users can create Parties, start a voice chat using VoIP: Voice over IP on page 24, invite friends to join them in Rooms on page 10, and even invite their Party to join an app using Coordinated App Launch (CAL) on page 12. Party voice chat persists across apps in VR and users can continue to interact while navigating between apps. You don't have to do anything to allow users to join Parties; however, there are a couple steps if you are planning to use the microphone for commands or chat in your app. • If you're using VoIP: Voice over IP on page 24 - In addition to integrating the VoIP service, you also need to integrate with our SystemVoIP API. Note: Parties introduces a change that removes echo cancellation from our VoIP inputs. We will be adding this back in as an option in the future. In the meantime, you may want to require users to use headphones and mute a user's mic if they don't have headphones plugged in. • If you're using your own VoIP service - You need to integrate with both the Shared Mic API and SystemVoIP API. • If you're using the mic, but not for VoIP - If you're using the mic for something other than VoIP (e.g. voice search or voice commands), you need to integrate with our Shared Mic API. Shared Microphone API The Shared Microphone API allows sharing of mic access between multiple services so app can access the user mic data while in a Party. To integrate with our API, you need to instantiate our mic object and retrieve PCM data from it. // This file was @generated with LibOVRPlatform/codegen/main. Do not modify it! #ifndef OVR_MICROPHONE_H #define OVR_MICROPHONE_H #include "OVR_Platform_Defs.h" #include #include typedef struct ovrMicrophone *ovrMicrophoneHandle; /// Creates an ovrMicrophone object and returns a handle to it. /// The caller owns this memory and is responsible for freeing it. /// Use ovr_Microphone_Destroy to free the object. OVRP_PUBLIC_FUNCTION(ovrMicrophoneHandle) ovr_Microphone_Create(); /// Stops and frees a previously created microphone object. OVRP_PUBLIC_FUNCTION(void) ovr_Microphone_Destroy(ovrMicrophoneHandle obj); /// Gets all available samples of microphone data and copies it into /// outputBuffer. The microphone will generate data at roughly the rate of 480 /// samples per 10ms. The data format is 16 bit fixed point 48khz mono. /// /// This function can be safely called from any thread. OVRP_PUBLIC_FUNCTION(size_t) ovr_Microphone_GetPCM(const ovrMicrophoneHandle obj, int16_t *outputBuffer, size_t outputBufferNumElements); /// Gets all available samples of microphone data and copies it into /// outputBuffer. The microphone will generate data at roughly the rate of 480 16 | Oculus Platform Feature Tutorials | Oculus Platform /// samples per 10ms. The data format is 32 bit floating point 48khz mono. /// /// This function can be safely called from any thread. OVRP_PUBLIC_FUNCTION(size_t) ovr_Microphone_GetPCMFloat(const ovrMicrophoneHandle obj, float *outputBuffer, size_t outputBufferNumElements); /// Starts microphone recording. After this is called pcm data can be extracted /// using ovr_Microphone_GetPCM. /// /// This function can be safely called from any thread. OVRP_PUBLIC_FUNCTION(void) ovr_Microphone_Start(const ovrMicrophoneHandle obj); /// Stops microphone recording. /// /// This function can be safely called from any thread. OVRP_PUBLIC_FUNCTION(void) ovr_Microphone_Stop(const ovrMicrophoneHandle obj); // DEPRECATED - use ovr_Microphone_GetPCMFloat instead OVRP_PUBLIC_FUNCTION(size_t) ovr_Microphone_ReadData(const ovrMicrophoneHandle obj, float *outputBuffer, size_t outputBufferSize); #endif SystemVoIP API The SystemVoIP API ensures that the user doesn't hear both the Party VoIP and the VoIP in your app at the same time. There are two ways to check the state of SystemVoIP (active means the user is using Party VoIP). One way is to check every frame, the other is to listen for a notification. 1. Check Every Frame - To check whether SystemVoIP is active at any time: • In C: ovr_Voip_GetSystemVoipStatus() == ovrSystemVoipStatus_Active • In C#: Voip.GetSystemVoipStatus() == SystemVoipStatus.Active These are synchronous functions, not requests. 2. Listen for a Notification - To get a notification for changes in the VoIP state you can make the following request: • In C: Watch for ovrMessage_Notification_Voip_SystemVoipState, and then use ovr_Message_GetSystemVoipState() and ovr_SystemVoipState_GetStatus() to get the status. • In C# Watch for MessageType.Notification_Voip_SystemVoipState which has a Message from which you can access the Status field, or call SetSystemVoipStateNotificationCallback with a Message callback It's possible for the state to change quickly. The values you extract from notification messages will be the state at the time the notification was added to the message queue, which may be different from the state then the message is processed. You may wish to listen for the notification, and then ignore its values and check the current state using the synchronous functions. If the SystemVoIP is active you can: • Use In-App VoIP - i.e. you want the user to be in your in-app chat instead of in Party VoIP. Suppress SystemVoIP by calling ovr_Voip_SetSystemVoipSuppressed(bool suppressed) and use either the Shared Microphone API or the Platform VoIP API to run your in-app VoIP. • Use SystemVoIP (Party VoIP) - i.e. you want to use Party VoIP instead of in your in-app VoIP. Suppress your in-app VoIP (don't send the user's mic input to other users and don't play other user's audio to the user). SystemVoIP will be unsupressed if the if the app has it suppressed quits. Oculus Platform | Oculus Platform Feature Tutorials | 17 You may also allow your users to toggle between SystemVoIP and your in-app VoIP. This allows users to choose if they want to keep talking to their party (in which case you would need to suppress your in-app VoIP), or if they want to use your in-app VoIP (in which case you would need to suppress the SystemVoIP). Basic Matchmaking Walkthrough Here's how to get started with matchmaking so users can start or join a multiplayer game. Introduction to Matchmaking You can add many different matchmaking options to your game, depending on your specific needs. However, remember your audience for potential matches gets smaller with each additional option you offer. For each option, one key choice you'll need to make is the matchmaking mode. Matchmaking modes determine whether matching will be automatic within your matchmaking pool, or whether players will help choose a match. You can also configure pools more specifically to determine how likely it is players will have a similar skill level, similar network speeds, or fit custom criteria of your own. Matchmaking is supported for native Rift, native Gear VR, Unity, and Unreal Engine apps. Before you begin, you’ll need to complete the necessary setup steps described in Oculus Platform Setup. The API code examples included below are for native Rift and native Gear VR apps. Note: Matchmaking connects to rooms, another Oculus platform feature. For more info on rooms, see: Rooms on page 10. Matchmaking Modes The two supported matchmaking modes are: • Quickmatch: players are automatically matched. In simple quickmatch, destination rooms are automatically created for users. You can determine whether or not players will be matched on skill, network speed, or other factors you choose. In advanced quickmatch, the queue will manage rooms, which allows users to create rooms, and it allows users to be put into rooms that earlier got matches. • Browse: players choose from a list of rooms. Although you can filter which rooms a player might see, the final choice of which to join is entirely theirs. Simple Quickmatch Here’s an overview of the steps required for the simplest possible version of matchmaking. For instance, let's assume you're creating a two-player fighting game. 1. Since quickmatch mode puts players into rooms automatically, we'll use quickmatch mode for this example. 2. Let's also assume that at first you'll just be matching up the next two available players. This means we won't have to define any matchmaking criteria for your pools yet. 3. In the Oculus developer dashboard, take the following actions to create a matchmaking pool: a. b. c. d. e. Select the Platform tab and select Matchmaking from the options on the left. Leave the menu option at the top set to Pools. Click Create Pool. Enter a unique Pool Key for this pool. Only letters, numbers, and underscores are permitted. Choose Quickmatch mode. 18 | Oculus Platform Feature Tutorials | Oculus Platform f. Enter the number 2 for both Min Users and Max Users since your game requires exactly two players. g. Choose None for Skill Pool. We won't be using this feature yet. h. Leave Advanced Quickmatch set to No. i. Leave Should Consider Ping Time? at the default setting of No.. j. Don't add anything under Data Settings. k. Click Submit. 4. Then update your app code as follows: a. Call ovr_Matchmaking_Enqueue. The user will be continually re-enqueued until they join a room or cancel. b. If desired, use functions such as ovr_MatchmakingEnqueueResult_GetMatchesInLastHourCount, ovr_MatchmakingEnqueueResult_GetMaxExpectedWait, ovr_MatchmakingEnqueueResult_GetAverageWait, and ovr_MatchmakingEnqueueResult_GetRecentMatchPercentage to provide information about how likely your users will find a match. c. If you specified true for subscribeToUpdates, you can handle the ovrMessage_RoomUpdateNotification to observe room joins and leaves. d. If matching is successful, handle the ovrMessage_Notification_Matchmaking_MatchFound notification by placing your two players in a room with a call to ovr_Matchmaking_JoinRoom. e. If the user cancels, call ovr_Matchmaking_Cancel2. Simple Quickmatch with Skill Now, let's say you have a two-player game, and your users are willing to wait longer for a match to see if players of closer skill level are available, so that matches are more even. We can compute skill based on wins and losses, as reported by ovr_Matchmaking_ReportResultInsecure, taking into account the quality of the victories and with increasing certainty as players play more games. You'll choose a 'luck factor' for your game; the higher it is, the more permissive we will be in making matches between users of different skills. We'll also need to assign a draw probability even though that's not relevant to this particular situation. Here’s an overview of the required steps: 1. We'll still use quickmatch mode again for this second example. 2. Let's assume you'll select the Medium setting for Luck Factor, because outcomes in your game depend on both skill and chance. 3. We'll have you enter a Draw Probability of zero because your game, like many fighting games, doesn't allow ties. 4. In the Oculus developer dashboard, take the following actions to create a skill pool: a. Select the Platform tab and select Matchmaking on the left. b. Choose Skill Pools from the menu at the top. c. Click Create Skill Pool. d. Enter a unique Skill Pool Key for this pool, for instance TEST_SKILL_POOL_KEY. Only letters, numbers, and underscores are permitted. e. For Luck Factor, select the Medium setting. f. Enter 0 for Draw Probability. g. Click Submit. 5. Now you'll need to join your skill pool to a new matchmaking pool, the same kind of pool you made in the first example: In the Oculus developer dashboard, take the following actions: a. Stay in the Platform tab and in the Matchmaking pane. Oculus Platform | Oculus Platform Feature Tutorials | 19 b. Change the menu option at the top to Pools. c. d. e. f. g. h. i. Click Create Pool. Enter a unique Pool Key for this pool. (Only letters, numbers, and underscores.) Choose Quickmatch mode. Enter the number 2 for both Min Users and Max Users. Choose TEST_SKILL_POOL_KEY for Skill Pool. Leave Advanced Quickmatch set to No. Leave Should Consider Ping Time? at the default setting of No. j. Don't add anything under Data Settings. k. Click Submit. 6. Then update your app code as follows: a. Call ovr_Matchmaking_Enqueue. The user will be continually re-enqueued until they join a room or cancel. b. If you wish, use functions such as ovr_MatchmakingEnqueueResult_GetMatchesInLastHourCount, ovr_MatchmakingEnqueueResult_GetMaxExpectedWait, and ovr_MatchmakingEnqueueResult_GetAverageWait, and ovr_MatchmakingEnqueueResult_GetRecentMatchPercentage to display for your users an idea of their success prospects. c. If matching is successful, handle the ovrMessage_Notification_Matchmaking_MatchFound notification by placing your two players in a room with a call to ovr_Matchmaking_JoinRoom. d. If the user gives up waiting, call ovr_Matchmaking_Cancel2. e. Call ovr_Matchmaking_StartMatch from each user after users have joined and the fight has begun within your game. f. Call ovr_Matchmaking_ReportResultInsecure from each user, including who won and who lost, when the fight has concluded or when one user quits during the fight. For example, your code might look like this: //start the match ovr_Matchmaking_StartMatch(roomID); // when the game is finished, report the result from all users, here marked winner and loser for simplicity { // convert user IDs to strings char winnerID[21]; char loserID[21]; ovrID_ToString(winnerID, sizeof(winnerID), ovr_User_GetID(winnerHandle); ovrID_ToString(loserID, sizeof(loserID), ovr_User_GetID(loserHandle); } // rank the users 1 and 2, respectively ovrKeyValuePair results[] = { ovrKeyValuePair_makeInt(winnerID, 1), ovrKeyValuePair_makeInt(loserID, 2) }; ovr_Matchmaking_ReportResultInsecure(roomID, results, 2); Advanced Quickmatch Mode Some types of games will want to keep track of the active rooms as well as players looking for rooms. For example, maybe you're creating an eight player free-for-all. A room will outlive any particular group of players, and users can customize their rooms with different maps and conditions of contest. 20 | Oculus Platform Feature Tutorials | Oculus Platform 1. For this purpose, you'll want advanced quickmatch. 2. You'll decide whether users can create rooms for others to join, whether the system can create rooms, and whether rooms will stay alive after matches are made for others to join. 3. You must choose how many players can join a room, for instance no more than eight players. 4. In the Oculus developer dashboard, take the following actions to create a matchmaking pool: a. Select the Platform tab and select Matchmaking from the options on the left. b. Leave the menu option at the top set to Pools. c. Click Create Pool. d. e. f. g. h. i. Enter a unique Pool Key for this pool. Only letters, numbers, and underscores are permitted. Choose Quickmatch mode. Enter the number 2 for Min Users and 8 for Max Users. Choose None for Skill Pool. Set Advanced Quickmatch to Yes. This will reveal more options. We'll let users customize and create their own rooms. Set Can people create and enqueue rooms themselves? to Yes. j. You want the system to create rooms if there are currently no hosts with empty slots, so leave Can the system create rooms to match people into? as Yes. k. Your game is casual; you don't want to restrict who can join these rooms, so you leave Can unknown people join matchmaking rooms? as Yes. l. Users can come and go in your game's rooms, so you leave Can rooms be joined after the initial match? set to Yes. m. Leave Should Consider Ping Time? at the default setting of No.. n. Don't add anything under Data Settings. o. Click Submit. 5. Since you're allowing users to create rooms. Since you could use the previous quickmatch-mode examples for users joining rooms, let's just go over how to create rooms. Update your app code as follows for users creating rooms: a. Call ovr_Matchmaking_CreateAndEnqueueRoom. The user will be continually re-enqueued until they join a room or cancel. You can create and enqueue separately if you wish, but for now we'll just show you the simplest option. b. If you wish, use functions such as ovr_MatchmakingEnqueueResult_GetMatchesInLastHourCount, ovr_MatchmakingEnqueueResult_GetMaxExpectedWait, and ovr_MatchmakingEnqueueResult_GetAverageWait, and ovr_MatchmakingEnqueueResult_GetRecentMatchPercentage to display for your users an idea of their success prospects. c. If matching is successful, handle the ovrMessage_Notification_Matchmaking_MatchFound notification by placing matching players in a room with a call to ovr_Matchmaking_JoinRoom. d. If the user gives up waiting, call ovr_Matchmaking_Cancel2. Browse Mode The other matchmaking mode, browse mode, requires players to select a room to join. Use this option if you want players to be able to choose from a list of available servers. Browse mode might be the right choice for you multi-player collaborative world-building game if you want to focus on users getting to choose which rooms they want to check out. 1. In the Oculus developer dashboard, take the following actions to create a matchmaking pool: a. Select the Platform tab and select Matchmaking from the options on the left. Oculus Platform | Oculus Platform Feature Tutorials | 21 b. Leave the menu option at the top set to Pools. c. d. e. f. g. h. i. Click Create Pool. Enter a unique Pool Key for this pool. Only letters, numbers, and underscores are permitted. Choose Browse mode. Enter the number 2 for Min Users and 4 for Max Users. Choose None for Skill Pool. Leave Should Consider Ping Time? at the default setting of No.. Don't add anything under Data Settings. j. Click Submit. 2. Just like with room mode, you'll have a queue of users waiting to join rooms, but now you'll always need to show them a list of rooms to join. Bear in mind you may have to handle conflicts, such as two users attempting to join a room with only one space remaining at the same time. 3. Because you could use the Advanced Quickmatch Mode example above to see how users create rooms, let's just go over how to update your app code so players can browse a list of rooms. Note that even though users are choosing their own room, we still put them in a queue so you can expose to room owners how many people are currently looking for rooms. Here are the required steps: a. To get a current list of rooms available, call ovr_Matchmaking_Browse. The user will be continually reenqueued until they join a room or cancel. b. Handle ovrMessage_Matchmaking_Browse to let the user choose a room from the returned list with a call to ovr_Matchmaking_JoinRoom. c. If the user gives up waiting, call ovr_Matchmaking_Cancel2. Again, this just removes them from the list of users looking for rooms. d. You'll also need to call ovr_Matchmaking_Cancel2 from the room owner's client whenever that room stops accepting new players. You do not need to do this when the room fills up. Advanced Matchmaking Here's how to use all the available matchmaking features. This tutorial assumes you're already familiar with Basic Matchmaking Walkthrough on page 17 and Rooms on page 10. Matchmaking Criteria There are several types of criteria you can use to configure match preferences. You can configure these on a per-pool basis in the Developer Dashboard. • Network Speed: under the Should Consider Ping Time? setting, you can define • Acceptable ping time—determines minimum network speed for match compatibility. • Good ping time—sets target network speed for a match. • Skill Pools: if you're making a two-player game (currently skill matching only supports two-player games) you can set a skill pool that will define other matchmaking pools, with the following parameters: • Luck factor—use to identify how much the outcomes of your matches depends on chance. A pure skill game would get a Very Low luck factor, while a game that relied on chance should get a Very High luck factor. Note that we're more permissive when matching games with a higher luck factor, so you can use a higher luck factor to avoid users not finding a match at all. • Draw Probability—if your game doesn't permit ties, set this to 0. If a game between evenly-matched people always results in a tie, like Tic-Tac-Toe, use a value closer to 1. 22 | Oculus Platform Feature Tutorials | Oculus Platform • Matchmaking Queries: you can configure matches based on conditional statements that you define in the Developer Dashboard. See the Matchmaking Queries section below for details. Each criteria yields a number between 0 and 1. When determining a match between two enqueued users, all the criteria values are multiplied together to get a match value between 0 and 1. 0.5 is considered to be a marginal match, and 0.9 an excellent match. A successful match occurs if the match value is greater than or equal to the match threshold, where the match threshold is 1.0 at enqueue time, and drops to 0.5 over a rampdown period of 30 seconds. A high match value (say, 0.9 or above) would result in a match fairly quickly, whereas a lower match (say, 0.6) would end up waiting longer before a match occurs. A match value less than 0.5 would never result in a successful match. Note that match calculations are asymmetric, meaning that if we are determining whether users A and B can match each other, the match calculation must succeed in the both the A->B direction and the B->A direction in order for them to be matched. Matchmaking Queries Matchmaking Queries allow you to define your own Query Expressions for determining whether enqueued players and rooms can be successfully matched with each other. These expressions compare potential matches' Data Settings against the current user's Data Settings. You define and configure Matchmaking Queries in the Developer Dashboard. At enqueue time, you specify who you are willing to be matched with by providing Data Settings and a Matchmaking Query. Matchmaking Query Expressions A Matchmaking Query is composed of one or more expressions, where each expression defines a conditional statement. The matchmaking service populates each expression with the Data Settings of the enqueuer and the potential match candidate, and then evaluates it to a value between 0 and 1. You will configure an importance with each expression. When an expression passes, it evaluates to a value of 1, and otherwise (failure case) evaluates to the value of the associated importance. Note that the match-on-failure delay times below are calculated based on a rampdown time of 30 seconds. • • • • Required: 0, i.e. never matches on failure High: ~0.55, i.e. matches on failure after 27 seconds Medium: ~0.75, i.e. matches on failure after 15 seconds Low: ~0.9, i.e. matches on failure after 6 seconds See the Matchmaking Criteria section above for more details on how these values are used. In general, the greater the importance, the less likely a match will occur if the expression fails. And in the case of expressions with Required importance, a failure will never result in a match. Matchmaking Query Data Settings Data Settings are the data (key/value pairs) that the enqueuing player or room provides about itself at enqueue time. Data Settings can be used both to determine what a player is looking for, as well as what a player looks like to another player. For example, if a player is enqueued with the following: data settings: "map" = "capture_the_flag" query: their map = my map Then the matchmaking service will apply this query to potential match candidates: their map = "capture_the_flag" Oculus Platform | Oculus Platform Feature Tutorials | 23 Matchmaking Query Code Examples The following examples are for native Rift and native Gear VR apps. Assume that in https://dashboard.oculus.com/application/[YOUR_APP_ID]/matchmaking, I have a pool called "my_pool", with the following Data Settings configured: • player_level (INTEGER) • game_mode (STRING) • map_size (INTEGER_BITSET) I then create a query called "my_query" in "my_pool" with the following query expressions: • Their "player_level" is equal to my "player_level". Importance: Medium • Their "game_mode" is equal to my "game_mode". Importance: Required • Their "map_size" is a bitmask AND of my "map_size". Importance: Required In my game, the "map_size" bitmask has the following bit meanings: • 0x4: large map size • 0x2: medium map size • 0x1: small map size The following codeblock demonstrates a player enqueueing into the matchmaking service and looking to be matched with other players with player_level=10 and game_mode="CaptureTheFlag" and map_size is large or medium or both. ovrMatchmakingOptionsHandle matchmakingOptions = ovr_MatchmakingOptions_Create(); ovr_MatchmakingOptions_SetEnqueueDataSettingsInt(matchmakingOptions, "player_level", 10); ovr_MatchmakingOptions_SetEnqueueDataSettingsString(matchmakingOptions, "game_mode", "CaptureTheFlag"); // I want large or medium map size ovr_MatchmakingOptions_SetEnqueueDataSettingsInt(matchmakingOptions, "map_size", 0x4 & 0x2); // Specify which Matchmaking Query to use with the Data Settings we provided ovr_MatchmakingOptions_SetEnqueueQueryKey(matchmakingOptions, "my_query"); ovr_Matchmaking_Enqueue2("my_pool", matchmakingOptions); ovr_MatchmakingOptions_Destroy(matchmakingOptions); Matchmaking Debugging To debug what's going on with your matchmaking pool, you can get snapshots of the queues using ovr_Matchmaking_GetAdminSnapshot, which returns the state of the queue at whatever time this request is made. This endpoint is not intended to be called in production. // In your code, do a matchmaking enqueue first // We can now inspect the queue to debug it. ovr_Matchmaking_GetAdminSnapshot(); // In your handler case ovrMessage_Matchmaking_GetAdminSnapshot: if (!ovr_Message_IsError(message)) { auto snapshot = ovr_Message_GetMatchmakingAdminSnapshot(message); auto candidates = ovr_MatchmakingAdminSnapshot_GetCandidates(snapshot); auto firstCandidate = ovr_MatchmakingAdminSnapshotCandidateArray_GetElement(candidates, 0); if (ovr_MatchmakingAdminSnapshotCandidate_GetCanMatch(firstCandidate)) { cout << "Yay!" << endl; } } 24 | Oculus Platform Feature Tutorials | Oculus Platform You can also view queue snapshots at enqueue time: ovrMatchmakingOptionsHandle matchmakingOptions = ovr_MatchmakingOptions_Create(); ovr_MatchmakingOptions_SetIsDebug(matchmakingOptions, true); // set other matchmaking options here ... ovr_Matchmaking_Enqueue2("my_pool", matchmakingOptions); ovr_MatchmakingOptions_Destroy(matchmakingOptions); // In your handler case ovrMessage_Matchmaking_Enqueue2: if (!ovr_Message_IsError(message)) { auto enqueueResults = ovr_Message_GetMatchmakingEnqueueResult(message); auto snapshot = ovr_MatchmakingEnqueueResult_GetAdminSnapshot(enqueueResults); auto candidates = ovr_MatchmakingAdminSnapshot_GetCandidates(snapshot); auto firstCandidate = ovr_MatchmakingAdminSnapshotCandidateArray_GetElement(candidates, 0); if (ovr_MatchmakingAdminSnapshotCandidate_GetCanMatch(firstCandidate)) { cout << "Yay!" << endl; } } VoIP: Voice over IP Use VoIP to permit direct voice communication among users of your app. If you're building a Gear VR app, please review Parties on page 15 for information about supporting Party VoIP. Native Development VoIP is supported for native Rift and Gear VR apps, as well as for Unity apps. Here’s an overview of the required VoIP steps for native Rift or Gear VR apps: 1. Call ovr_Voip_Start() to initiate a VoIP connection. For instance: ovr_Voip_Start(remoteUserID); 2. Listen for incoming connection attempts by checking for the ovrMessage_Notification_Voip_ConnectRequest message. For instance, if you wanted to automatically accept all connection attempts, include the following in your message loop along with other platform checks like your entitlement check: case ovrMessage_Notification_Voip_ConnectRequest: { if (!ovr_Message_IsError(message)) { ovrNetworkingPeerHandle netPeer = ovr_Message_GetNetworkingPeer(message); ovr_Voip_Accept(ovr_NetworkingPeer_GetID(netPeer)); } else { const ovrErrorHandle error = ovr_Message_GetError(message); printf("Received voip connect request error: %s\n", ovr_Error_GetMessage(error)); } } break; 3. You’ll need to do your own playback of audio data. Get PCM audio data on a per-connection basis, specific to each user, by calling ovr_Voip_GetPCM(). See Functions for more detail. For instance: int numSamples = ovr_Voip_GetPCM(remotePeerID, buffer, bufferSize); Oculus Platform | Oculus Platform Feature Tutorials | 25 Note: After you retrieve the PCM data, you’ll need to spatialize it and plug it into your app's existing audio code. See our Audio SDK for more detail. 4. You can stop a VoIP session by calling ovr_Voip_Stop(), for instance: ovr_Voip_Stop(remotePeerID); 5. You can monitor the VoIP connection state by listening for the ovrMessage_Notification_Voip_StateChange message. This is optional, but would be helpful for debugging or re-connecting after a failed connection attempt: case ovrMessage_Notification_Voip_StateChange: { ovrNetworkingPeerHandle netPeer = ovr_Message_GetNetworkingPeer(message); ovrPeerConnectionState netState = ovr_NetworkingPeer_GetState(netPeer); printf( "User: %llu, voip state %s\n", ovr_NetworkingPeer_GetID(netPeer), ovrPeerConnectionState_ToString(netState) ); } break; 6. Optional. If you want to filter the voices of your users (e.g., robotic voices, deep voices, squeaky voices, and so on), you can apply VoIP filters to audio captured by the microphone. To use VoIP filters, define a callback function with this signature: MyFilterFunction(int16_t pcmData[], size_t pcmDataLength, int frequency, int numChannels); where pcmData is an array that contains the 16-bit audio samples, pcmDataLength is the length of the elements, frequency specifies the samples per second (default: 48,000), and numChannels specifies whether the data is mono (1) or interleaved stereo (2). Note: Your filter function will be called on an internal audio capture thread. Make sure any data access from it is thread-safe. If your filter function cannot process the audio samples quickly enough, it can cause stuttering and other audio artifacts. Unity Development Here’s an overview of the required VoIP steps for Unity apps: 1. Call Oculus.Platform.Voip.Start, including the user information for the user with whom you'd like to establish a connection. For instance: using Oculus.Platform; Voip.Start(user.ID); 2. You’ll also need to register a notification handler to accept incoming connections. For instance, if you wanted to automatically accept all connection attempts, use: Voip.SetVoipConnectRequestCallback((Message msg) => { Voip.Accept(msg.Data.ID); }); 3. Add our audio playback component. This handles buffering and playback of voice data from a remote peer. You should add this component to a game object that represents a connected user, such as their avatar. For each connected user, you’ll need to set the senderID field of each VoipAudioSourceHiLevel object you create. This lets the system know which user's voice data goes with which playback component. For example: var audioSource = gameObject.AddComponent(); audioSource.senderID = remotePeer.ID; 26 | Oculus Platform Feature Tutorials | Oculus Platform 4. To end a VoIP connection call: Oculus.Platform.Voip.Stop(userID); Voip.Stop(remotePeer.ID); 5. You can monitor the VoIP connection state by registering a callback to listen for VoIP state changes. This is optional, but would be helpful for debugging or re-connecting after a failed connection attempt: Voip.SetVoipStateChangeCallback((Message msg) => { Debug.LogFormat("peer {0} is in state {1}", msg.Data.ID, msg.Data.State); }); 6. You can access the Unity AudioSource object from the VoipAudioSourceHiLevel object to modify playback options. For instance: Var voipAudio = RemoteAvatar.GetComponent(); voipAudio.audioSource.spatialize = true; 7. Optional. If you want to filter the voices of your users (e.g., robotic voices, deep voices, squeaky voices, and so on), you can use VoIP filters. To use VoIP filters, the function should be similar to the following: static CAPI.FilterCallback micFilterDelegate = new CAPI.FilterCallback(MicFilter); static void MicFilter(short[] pcmData, UIntPtr pcmDataLength, int frequency, int numChannels) {//stuff } where pcmData is an array that contains the 16-bit audio samples, pcmDataLength is the length of the elements, frequency specifies the samples per second (default: 48,000), and numChannels specifies whether the data is mono (1) or interleaved stereo (2). Note: Your filter function will be called on an internal audio capture thread. Make sure any data access from it is thread-safe. If your filter function cannot process the audio samples quickly enough, it can cause stuttering and other audio artifacts. For more detail about any function or parameter, see the Oculus Platform Reference Content. P2P Users can send messages to each other through peer-to-peer (P2P) networking. You can choose between reliable messages and unreliable messages. The following is an example of a P2P message exchange session: 1. Your application sends a message from Alice to Bob using ovr_Net_SendPacket(). 2. Bob's application polls for messages and receives ovrMessage_NetworkingPeerConnectRequest. 3. Your application displays a prompt to Bob. Bob agrees to connect. 4. Because Alice included a message, your application parses the message using ovr_Net_ReadPacket() and ovr_Packet_GetBytes , which it then displays to Bob. Your application frees memory used by the message with ovr_Packet_Free(). 5. Alice is requesting donations for her ferret rescue foundation. 6. Bob closes the message and connection. Your application closes the connection using ovr_Net_Close(). Open Connection The following function attempts to open a connection with a user: ovr_Net_Connect(const char* peerID, ovrSendPolicy policy) Oculus Platform | Oculus Platform Feature Tutorials | 27 It sends a ovrMessage_NetworkingPeerConnectRequest to the specified user ID. It returns a ovrMessage_NetworkingPeerConnectionStateChange when the connection is established. Note: ovr_Net_SendPacket() still implicitly connects. However, it does not buffer messages in unreliable mode. ovr_Net_Connect() allows the application to delay sending messages until an actual connection is established. Send Message The following function sends data to the specified user ID: ovr_Net_SendPacket() It sends a ovrMessage_NetworkingPeerConnectRequest to the specified user ID. Open Connection Opens a connection with the specified user ID: ovr_Net_Accept() It sends a ovrMessage_NetworkingPeerConnectRequest to the specified user ID. This should be called after receiving a ovrMessage_NetworkingPeerConnectRequest. Close Connection Closes a connection with the specified user ID: ovr_Net_Close() Read Packet Reads a received packet, if available: ovr_Net_ReadPacket() It returns the ovrMessage_ReadPacket message type. Use o ovr_Packet_GetSenderID() to get the sender ID, o ovr_Packet_GetSize to get the size of the packet, or ovr_Packet_GetBytes to get the contents of the packet. Free Memory After you parse the message, free the memory used by the packet: ovr_Packet_Free() Connection State Change You receive the following message any time a connection state changes: ovrMessage_NetworkingConnectionStateChange Valid states include ovrPeerState_Unknown, ovrPeerState_Connected, and ovrPeerState_Timeout. Use ovr_Message_GetNetworkingPeer() to extract the ID of the sender. 28 | Oculus Platform Feature Tutorials | Oculus Platform Timeout indicates a client is unreachable or has rejected a connection. Connection Request This message is received when a user receives a connection request: ovrMessage_NetworkingPeerConnectRequest Use ovr_Message_GetNetworkingPeer() to extract the ID of the sender. In-App Purchases To create in-app purchases, you create multiple items in a tab-delimited spreadsheet and make them available to your users. Defining Purchases The current format of the in-app purchases file is: SKU Name Description Currency Amount Item Type EXAMPLE1 Example 1 Example IAP item 1 USD 1.79 Consumable EXAMPLE2 Example 2 Example IAP item 2 USD 1.79 Durable To get the latest sample template: 1. 2. 3. 4. 5. Log on to https://dashboard.oculus.com/. On the My Apps page, hover the mouse over your app and then click View Details. Click the Platform tab. Click IAP. Click Download Template. To upload IAPs: 1. 2. 3. 4. 5. Log on to https://dashboard.oculus.com/. On the My Apps page, hover the mouse over your app and then click View Details. Click the Platform tab. Click IAP. Click Upload TSV and follow the on-screen prompts. Initiating a Purchase The following function initiates the Oculus checkout flow for the specified SKU: ovr_IAP_LaunchCheckoutFlow() It returns the ovrMessage_IAPStartCheckout. Use ovr_Message_GetPurchase() to parse the message into an ovrPurchase pointer. Oculus Platform | Oculus Platform Feature Tutorials | 29 Get Products by SKU The following function returns an array of products that match the SKU: ovr_IAP_GetProductsBySku() It returns the ovrMessage_IAPGetProducts message type. Use the GetProductArray() to parse the message into an ovrProductArray pointer. View Purchases The following function returns purchases for the user: ovr_IAP_GetViewerPurchases() It returns the ovrMessage_IAPGetPurchases message type. Use the ovr_Message_GetPurchaseArray() to parse the message into an ovrPurchaseArray pointer. Consume Purchase The following function consumes the specified SKU: ovr_IAP_ConsumePurchase() It returns the ovrMessage_IAPConsumePurchase message type. Use the ovr_Message_GetBoolean() to see if the item was consumed. REST Requests The App access token is a string composed of OC|$APPID|$APPSECRET, where $APPID and $APPSECRET are per-application values that can be found on the Platform tab of the developer console. The user access token is returned by the ovr_AccessToken_Get() SDK call. To verify ownership of an IAP item: [POST] https://graph.oculus.com/$APPID/verify_entitlement ?sku= &access_token= The request returns verification. For example: {"success":true} To consume an IAP item: [POST] https://graph.oculus.com/$APPID/consume_entitlement ?sku= &access_token= The request returns verification. For example: {"success":true} To verify consumption, repeat the previous operation. It should return false. 30 | Oculus Platform Feature Tutorials | Oculus Platform To view all items owned by a user: [GET] https://graph.oculus.com/$APPID/viewer_purchases ?sku= &access_token= &fields==id,item{sku} The request returns an array of items, based on the fields you specified. For example: {"data":[{"id":"963119010431337","item":{"sku":"EXAMPLE1"}}]} Oculus Keys Oculus Keys are codes that let users get a copy of your app. Here are some important things to know about Oculus Keys: • Before you can create keys, you must first submit your app for review. The Oculus Store team approves apps for key distribution. See our Publishing Guide for details. • Oculus Keys don't cost you anything to generate. You can decide to give them away for free, or to sell them. You keep all the revenue if you sell them. • Each key can only be used once. • Oculus Keys can be used whether your app is in the Oculus Store or not. • Only admins for your organization can generate keys. Manage admin roles from the Members tab in the Oculus Developer Dashboard. • You can still use Oculus Platform features if you distribute your app using Oculus Keys. • To create keys:: 1. 2. 3. 4. 5. 6. Log on to https://dashboard.oculus.com. On the My Apps page, hover the mouse of your app and click View Details. Click the Oculus Keys tab. If your app has been approved by the Oculus Store team, you see the Create New Oculus Keys page. Select the number of keys to create, choose a name for this batch of keys, then click Create. If you've generated 250 keys or fewer, you'll see your list of keys displayed. Each key is in 5x5 format, for instance: 3YEBU-GCRKT-SHHHH-PYE0T-KVW7S 7. If you're requesting more than 250 keys, you'll be asked for your public PGP key so we can secure the batch of Oculus Keys during transit. You'll use your private PGP key to unlock the batch after we deliver it to you. Note: • PGP is an industry standard for securing data. • If you already have a PGP key, use it with default settings. • If you have access to a Mac or Linux computer, you can use standard command-line tools to generate the public PGP key. • If you only have access to a Windows computer, you'll need a third-party tool to generate your public PGP key. Oculus Platform | Oculus Platform Feature Tutorials | 31 • For further discussion of common uses for Oculus Keys as well as other alternative methods for controlling and distributing Oculus apps, see Additional App Distribution Options in our Publishing guide. • Before you can begin using platform features, you’ll need to complete the necessary setup steps described in Oculus Platform Setup. • For more detail about any function or parameter, see the Oculus Platform Reference Content. Leaderboards You can create leaderboards, update them, and display results to your users. Although Oculus will manage your leaderboard data, you'll need to handle displaying, reporting, and verifying the data. You can find step-by-step examples of how to set up leaderboards using the Oculus PC SDK or Oculus Mobile SDK for native Rift or Gear VR apps, as well as using the Unity game engine in our platform walkthrough examples. However, here's an overview of the required steps: 1. Display the current leaderboard state before a game begins (e.g. ovr_Leaderboard_GetEntries for a native Rift or Gear VR app, or Platform.Leaderboards.GetEntries for Unity). 2. Write the results of the current game to your leaderboard (e.g. ovr_Leaderboard_WriteEntry or, for a Unity app, Platform.Leaderboards.WriteEntry). Note: • Before you can begin using platform features, you’ll need to complete the necessary setup steps described in Oculus Platform Setup. • For more detail about any function or parameter, see the Oculus Platform Reference Content. REST Requests The App access token is a string composed of OC|$APPID|$APPSECRET, where $APPID and $APPSECRET are per-application values that can be found on the API tab of the developer console. The user access token is returned by the ovr_AccessToken_Get() SDK call. To get the user ID: [GET] https://graph.oculus.com/me ?access_token= The request returns the ID of the user. For example: {"id":"1095130347203668"} To create a new leaderboard programmatically: [POST] https://graph.oculus.com/$APPID/leaderboards ?access_token= &api_name= &sort_order={HIGHER_IS_BETTER | LOWER_IS_BETTER} You can use the following parameters: 32 | Oculus Platform Feature Tutorials | Oculus Platform • api_name: The name used to refer to the leaderboard in this API and in the client SDK. • sort_order: Determines the order of entries in the leaderboard. • HIGHER_IS_BETTER—Descending order. The entry with the highest score is rank 1. • LOWER_IS_BETTER—Ascending order. The entry with the lowest score is rank 1. • entry_write_policy: (optional) Determines who is allowed to write leaderboard entries. • CLIENT_AUTHORITATIVE—(default) Users can write to the leaderboard with the client SDK directly. Entries can still be modified with App Access Tokens. • SERVER_AUTHORITATIVE"—Entries can only be written with App Access Tokens. This is useful in cases where trusted servers are running the game simulation. In those cases, using this option can significantly reduce cheating because only trusted entities can write entries. The leaderboard can still be queried with the client SDK. • earliest_allowed_entry_time: (optional) Writes before this time are rejected. Can be used with latest_allowed_entry_time to create leaderboards that are active only during an event. The default is to allow entries to be posted at any time. If omitted on update, leaves the existing setting. Use 0 to disable the setting. • latest_allowed_entry_time: (optional) Writes after this time are rejected. Works the same as earliest_allowed_entry_time. The request returns the ID of the leaderboard. For example: {"id":"1074233745960170"} To get the ID of a leaderboard, using the leaderboard name: [GET] https://graph.oculus.com/$APPID/leaderboards ?access_token= &api_name= The request returns the ID of the leaderboard. For example: {"id":"1074233745960170"} To query all the entries of a leaderboard: [GET] https://graph.oculus.com/leaderboard_entries ?access_token= &api_name= The request returns a list of leaderboard entries. For example: {"data":[{"id":"1074233745960170", ...} To create or update a leaderboard entry: [GET] https://graph.oculus.com/leaderboard_submit_entry ?access_token= &api_name=&score= You can use the following parameters: • api_name: Name of the leaderboard to post to Oculus Platform | Oculus Platform Feature Tutorials | 33 • score: The integer score being submitted • extra_data_base64: (optional) Extra metadata to store on the row. This can be used to specify information about the score. For instance, in a driving game this might be what car was used. Decoded length can be at most 2048 bytes. • force_update: (optional, default: false) By default, if you already have an entry on the leaderboard and post with a worse score than the existing entry, the existing entry will not be updated. You can use "force_update=true" to force the new entry even if it's worse than the old one. • user_id: When using an App Access Token this must be set to indicate which user you are posting on behalf of. That user must have an entitlement to your app. When using a User Access Token, this field must not be set. The request returns a status. did_update indicates whether the entry was recorded or not. Entries will not be recorded if the user already has an entry on the leaderboard, the new score is worse than the old one, and force_update is false. For example: {"success":true, "did_update":true} To delete a leaderboard entry: [DELETE] https://graph.oculus.com/ ?access_token= The request returns the status. For example: {"success":true} Note: Once deleted, a leaderboard entry cannot be recovered. To remove all entries from a leaderboard: [POST] https://graph.oculus.com/leaderboard_remove_all_entries ?access_token= &api_name= The request returns the status. For example: {"success":true} To delete a leaderboard: [DELETE] https://graph.oculus.com/ ?access_token= The request returns the status. For example: {"success":true} Note: Once deleted, a leaderboard cannot be recovered. 34 | Oculus Platform Feature Tutorials | Oculus Platform Achievements You can create trophies, badges, awards, medals, or challenges in your apps and Oculus will manage it. There are currently three types of achievements: simple, count, and bitfield. Although Oculus will track and manage these achievements, your app is responsible for rendering and displaying achievements to your users. Create Achievements To create an achievement: 1. 2. 3. 4. 5. 6. Log on to https://dashboard.oculus.com/. On the My Apps page, hover the mouse over your app and then click Manage Build. Click the Platform tab. Click Achievements.The Achievements page appears. Click Create Achievement. Select from the following: • Simple—select Simple and enter the name that will be used by the API. • Count—select Count and enter the name and count that will trigger the achievement. • Bitfield—select Bitfield and enter the name, bitfield length, and the number of that must be met to be awarded the achievement. Additionally, enter the title, provide the locked and unlocked descriptions, upload the locked and unlocked icons, choose the write policy, and select whether the achievement is secret. 7. Click Submit. Get Achievement Definition To check the definition of an achievement: ovr_AchievementDefinition_GetByName() It returns the GetAchievementDefinitionArray message type. Use ovrMessage_AchievementDefinitionGetByName() to parse the message into an array of ovrAchievementDefinition pointers. Get Achievement Progress To check the progress of an achievement: ovr_AchievementProgress_GetByName() It returns the ovrMessage_AchievementProgressGetByName message type. Use ovr_Message_GetAchievementProgressArray() to parse the message into an array of ovrAchievementProgress pointers. Unlock an Achievement To unlock an achievement: ovr_AchievementProgress_Unlock() Oculus Platform | Oculus Platform Feature Tutorials | 35 It returns the ovrMessage_AchievementProgressUnlock message type. Use ovr_Message_IsError() to verify success. Increment a Count To increment a count achievement: ovr_AchievementProgress_AddCount() It returns the ovrMessage_AchievementProgressAddCount message type. Use ovr_Message_IsError() to verify success. Add to a Bitfield To add to a bitfield achievement: ovr_AchievementProgress_AddFields() It returns the ovrMessage_AchievementProgressAddFields message type. Use ovr_Message_IsError() to verify success. Create or modify an achievement definition with REST To add or update an achievement with our REST API, use the following: $ curl -F "access_token=$APP_ACCESSTOKEN" -F "api_name=VISIT_3_CONTINENTS" -F "achievement_type=BITFIELD" -F "achievement_write_policy=CLIENT_AUTHORITATIVE" -F "target=3" -F "bitfield_length=7" -F "is_archived=false" -F "title=Achievement Title" -F "description=How to earn me" -F "unlocked_description_override=You did it" -F "is_secret=false" -F "locked_image_file=@/path/to/locked_icon.png;type=image/png" -F "unlocked_image_file=@/path/to/unlocked_icon.png;type=image/png" https://graph.oculus.com/$APPID/achievement_definitions {"id":"1074233745960170"} You can use the following parameters: • api_name: The name used to refer to the achievement in this API and in the client SDK. • achievement_type: Determines the behavior of the achievement. Allowed values are: • SIMPLE—these achievements immediately unlock when the requirements are met. • COUNT—show progress toward a goal, like collecting a number of items, then unlocks when fully completed. For instance, you might have an achievement called WALK_500_MILES that increments a counter after each mile walked, then unlocks when the counter hits 500. • BITFIELD—unlocks based on a target number of bits, used when you want the user to accomplish a certain set of goals. For instance, you might have an achievement called VISIT_3_CONTINENTS, with each continent in the world as one particular bit. This achievement would unlock when any 3 bits in the bitfield are set. • entry_write_policy: Determines who is allowed to write achievement progress. Allowed values are: • CLIENT_AUTHORITATIVE—default setting— users can write achievements with the client SDK directly. Achievements can still be modified with App Access Tokens. • SERVER_AUTHORITATIVE—achievements can only be written with App Access Tokens. This is useful to reduce cheating, in cases where trusted servers are running the game. Achievement progress can still be queried with the client SDK. • target (required for COUNT and BITFIELD achievements): For COUNT achievements, the threshold the progress counter must reach to unlock the achievement. In the above example this would be '500'. For 36 | Oculus Platform Feature Tutorials | Oculus Platform • • • • • • • • BITFIELD achievements, the number of bits in the bitfield that must be set to unlock the achievement. In the above example this would be '3'. bitfield_length (required for BITFIELD achievements): The size of the bitfield for this achievement. Since there are seven continents, in the above example this would be '7'. is_archived (optional): For data recoverability reasons, achievement definitions cannot be deleted. If you accidentally create an achievement, you can set is_archived=true to hide it from all clients. If you accidentally archive an achievement, it can be restored with is_archived=false. title: The name of the achievement that the user sees. description: The description of the achievement that the user sees. For example, you may want to give a short explanation of how to earn the achievement. unlocked_description_override: A replacement description that's only revealed after the user earns the achievement. For instance, if you want to hide spoilers about how the achievement is earned. is_secret (optional): An achievement that the user can't see before it's earned. The achievement's title, description, icon, and progress are all hidden until the achievement unlocks. Default false. unlocked_image_file: The icon shown when the achievement is earned. Must be a 256x256 PNG file. locked_image_file: The icon shown before the achievement is earned. Must be a 256x256 PNG file. We recommend using a locked image that's a grayscale, desaturated, or otherwise obscured version of the unlocked image. Query achievement definitions using our REST API $ curl -G -d "access_token=$APP_ACCESSTOKEN|$USER_ACCESSTOKEN" -d 'api_names=["VISIT_3_CONTINENTS", "WALK_500_MILES"]' -d "include_archived=true" -d 'fields=api_name,achievement_type,achievement_write_policy,target,bitfield_length,is_archived,title,description,un https://graph.oculus.com/$APPID/achievement_definitions {"data":[{"id":"1074233745960170", "api_name":"VISIT_3_CONTINENTS", achievement_type":"BITFIELD", "achievement_write_policy":"CLIENT_AUTHORITATIVE", "target":3, "bitfield_length":7, "is_archived":false, "title":"Achievement Title", "description":"How to earn me", "unlocked_description_override":"You did it", "is_secret":false, "locked_image_uri":"https:// scontent.oculuscdn.com/...", "unlocked_image_uri":"https://scontent.oculuscdn.com/...",}]} The definition of these fields are the same as in the above REST API examples. Note that the images are locked_image_uri and unlocked_image_uri instead of locked_image_file and unlocked_image_file. You can use the following parameters: • api_names (optional): The names of the achievement definitions to fetch. If omitted all achievement definitions are returned. • include_archived (optional): True to include archived achievements. May only be used with APP_ACCESSTOKENs. Cloud Storage Store user data, such as game-save data, in the cloud so the data is reliably accessible from any device. The cloud storage API allows your app to save, synchronize, and load data between devices and different copies of your app. There are three main advantages to using cloud storage: • Users can uninstall and reinstall your app without losing their saved progress. • Sharing progress between devices. • Disaster recovery in case of devices being lost or damaged. Oculus Platform | Oculus Platform Feature Tutorials | 37 The Oculus cloud storage API uses “buckets” (directories) and “blobs” (files), indexed by a “key” (file name), to hold data. We don’t currently support direct file backup. Here’s an overview of the required steps to use cloud storage with a native Rift or Gear VR app: 1. In the Oculus developer dashboard, create at least one bucket within the Platform tab. You can also create multiple buckets if you wish. Use bucket names that conform to Microsoft Windows directory name restrictions. For instance, don’t use slashes, and don’t use capitalization to differentiate bucket names. 2. Store data using the following API method: ovr_CloudStorage_Save(bucket_name, key, data_pointer, data_size, counter, extra_data) Note: This call sends the data blob to the local Oculus process and doesn’t trigger network calls. Synchronization to the cloud only happens after app shutdown. 3. To load a stored data blob, use the following API call: ovr_CloudStorage_Load(bucket_name, key) 4. Poll the message queue for a message type of ovrMessage_CloudStorage_Load. If no error occurs, retrieve the data as follows: ovrCloudStorageDataHandle response = ovr_Message_GetCloudStorageData(message); void* data = ovr_CloudStorageData_GetData(response); uint32 data_size = ovr_CloudStorageData_GetDataSize(response); Note: If there’s a conflict and you configured your bucket for manual conflict resolution (see below), you’ll get an error. 5. To request a list of metadata for all blobs in a bucket, call: ovr_CloudStorage_LoadBucketMetadata(bucket_name) 6. To request metadata on a single key in a bucket, call: ovr_CloudStorage_LoadMetadata(bucket_name, key) 7. Managing Conflicts: Because the cloud storage API supports synchronizing data between multiple devices, you’ll need to decide on a conflict resolution policy. This policy handles situations where multiple devices try to upload blobs to the same key at the same time. You can choose among: • Latest Timestamp: This is the simplest method. The bucket will prioritize the blob with the latest timestamp, as recorded by its local device, at the time of the most recent call to ovr_CloudStorage_Save(). Blobs with older timestamps are discarded. • Highest Counter: The bucket prioritizes blobs with the highest value set in the counter field. For example, use this policy if you always want to preserve the blob with the highest score. Note: If two blobs have the same counter value, either blob may win. • Manual: This policy assigns all responsibility for conflict resolution to your app. If your app saves a new local blob to a specific key with the state ovrCloudStorageDataStatus_InConflict, the blob won’t be uploaded until your app resolves the conflict. Manual conflict resolution can be done at any time but we recommend handling it during app startup and shutdown. Here's how to handle manual conflict resolution: 38 | Oculus Platform Feature Tutorials | Oculus Platform 1. First, see if manual conflict resolution is required by checking the metadata status, or by checking the response from the save message as follows: ovrCloudStorageUpdateResponseHandle response = ovr_Message_GetCloudStorageUpdateResponse(save_message) ovrCloudStorageUpdateStatus status = ovr_CloudStorageUpdateResponse_GetStatus(response)) if (status == ovrCloudStorageUpdateStatus_ManualMergeRequired) { // perform manual merge... } 2. Now, perform the conflict resolution. There are a few ways to do this: • Resolving With Metadata: 1. Load the metadata for the local and remote blobs with the following asynchronous call: ovr_CloudStorage_LoadConflictMetadata(bucket, key) 2. Check the contents of the response message to get the metadata: ovrCloudStorageConflictMetadataHandle response = ovr_Message_GetCloudStorageConflictMetadata(message) ovrCloudStorageMetadataHandle local_metadata = ovr_CloudStorageConflictMetadata_GetLocal(response) ovrCloudStorageMetadataHandle remote_metadata = ovr_CloudStorageConflictMetadata_GetRemote(response) 3. If the metadata for the blob contains enough information to resolve the conflict, then call either: ovr_CloudStorage_ResolveKeepLocal(bucket_name, key, remote_handle) or: ovr_CloudStorage_ResolveKeepRemote(bucket_name, key, remote_handle) Note: The remote handle is included to prevent data loss just in case a new remote blob appears during the process of conflict resolution. If this happens you’ll need to perform conflict resolution again. • Resolving by Comparing Remote and Local Data: If the metadata doesn't have enough information, then you can look at the conflicting data blobs themselves. 1. Compare the remote and local data. Load this data using the handle from the metadata: ovr_CloudStorage_LoadHandle(handle) Note: If the remote blob hasn’t been cached locally, this request will initiate a network call to fetch the data. 2. Your app can decide which blob to keep however you wish, such as asking the user which one to keep. Then call either: ovr_CloudStorage_ResolveKeepLocal(bucket_name, key, remote_handle) or: ovr_CloudStorage_ResolveKeepRemote(bucket_name, key, remote_handle) • Resolving by Merging Remote and Local Data: Oculus Platform | Oculus Platform Feature Tutorials | 39 If your game has a number of variables, you may decide to merge the data. For instance, you might decide to combine the farthest save point from either blob with the best achievement progress from either blob. • If you want your app to merge multiple blobs, then after they’re loaded (as described above), your app can simply save a new local version as follows: ovr_CloudStorage_Save(bucket_name, key, merged_data_pointer, merged_data_size, counter, extra_data) ovr_CloudStorage_ResolveKeepLocal(bucket_name, key, remote_handle) 8. Deleting data: To delete a stored data blob, which includes both the data stored in the cloud and any local copies of that data, call: ovr_CloudStorage_Delete(bucket_name, key); Note: You may write new data to a deleted blob without waiting for synchronization. Note: • Before you can begin using platform features, you’ll need to complete the necessary setup steps described in Oculus Platform Setup. • For more detail about any function or parameter, see the Oculus Platform Reference Content. App Groupings You can group apps together that share settings to simplify management and deploy global changes. Create an App Grouping To create an app grouping: 1. From the Oculus developer dashboard, click App Groupings. 2. Click the Create a New App Grouping link. The Create App Grouping dialog box appears. 3. Enter a name and click Submit. The new group appears in the list. Add Apps to an App Grouping To add apps: 1. From the Oculus developer dashboard, click App Groupings. 2. Hover over an app and select Migrate to another grouping. The Migrate Application dialog box appears. 3. Select a destination group and click Submit. The app is moved to the new group and will inherit its settings. Changing App Grouping Settings To change the settings for apps within an app grouping: 1. 2. 3. 4. Log in to the Oculus developer dashboard. Click App Groupings. Locate the App Grouping and click Manage. The settings page for the app grouping appears. Modify any IAP, DLC, Matchmaking, Achievement, Leaderboard, or Storage settings. The changes will be made for all apps within the grouping.