clean project

This commit is contained in:
Helar Jaadla
2022-03-07 17:52:41 +02:00
parent a174b45bd2
commit cbeb10ec35
5100 changed files with 837159 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!134 &13400000
PhysicMaterial:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_Name: Bouncey
dynamicFriction: 0.5
staticFriction: 0.6
bounciness: 0.7
frictionCombine: 0
bounceCombine: 3

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b9c664160cb7a724d9777a6ad6222a28
timeCreated: 1474424965
licenseType: Store
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8b67239c8d8693441abb62a2ac48f571
timeCreated: 1474427891
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e0704a47c7ee6de4c9886daac1eadebd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &100100000
Prefab:
m_ObjectHideFlags: 1
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications: []
m_RemovedComponents: []
m_ParentPrefab: {fileID: 0}
m_RootGameObject: {fileID: 1000012825293744}
m_IsPrefabParent: 1
--- !u!1 &1000012825293744
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
serializedVersion: 5
m_Component:
- component: {fileID: 4000014042746218}
- component: {fileID: 33000010642644700}
- component: {fileID: 23000012964101304}
- component: {fileID: 54000011667893522}
- component: {fileID: 135000010694715800}
- component: {fileID: 114000014063321366}
m_Layer: 0
m_Name: Ball
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4000014042746218
Transform:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000012825293744}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 16.53, y: 8.74, z: 6.33}
m_LocalScale: {x: 0.73585784, y: 0.73585784, z: 0.73585784}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!23 &23000012964101304
MeshRenderer:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000012825293744}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_Materials:
- {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_PreserveUVs: 1
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
--- !u!33 &33000010642644700
MeshFilter:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000012825293744}
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
--- !u!54 &54000011667893522
Rigidbody:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000012825293744}
serializedVersion: 2
m_Mass: 15
m_Drag: 0.47
m_AngularDrag: 0
m_UseGravity: 0
m_IsKinematic: 0
m_Interpolate: 0
m_Constraints: 0
m_CollisionDetection: 0
--- !u!114 &114000014063321366
MonoBehaviour:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000012825293744}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a63d0cd5dd6d39a4abd35114563fe347, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!135 &135000010694715800
SphereCollider:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000012825293744}
m_Material: {fileID: 13400000, guid: b9c664160cb7a724d9777a6ad6222a28, type: 2}
m_IsTrigger: 0
m_Enabled: 1
serializedVersion: 2
m_Radius: 0.5
m_Center: {x: 0, y: 0, z: 0}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 73ea9d6c6c3480147ba658fc59d0afa4
timeCreated: 1474514953
licenseType: Store
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,99 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &100100000
Prefab:
m_ObjectHideFlags: 1
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications: []
m_RemovedComponents: []
m_ParentPrefab: {fileID: 0}
m_RootGameObject: {fileID: 1000011449472940}
m_IsPrefabParent: 1
--- !u!1 &1000011449472940
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
serializedVersion: 5
m_Component:
- component: {fileID: 224000013885566140}
- component: {fileID: 222000012566199212}
- component: {fileID: 114000010273600920}
- component: {fileID: 114000010103236586}
m_Layer: 0
m_Name: FlyText
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &114000010103236586
MonoBehaviour:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000011449472940}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dc96f5380d7d743d9ae91f11379eb85b, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &114000010273600920
MonoBehaviour:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000011449472940}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.93003374, g: 0.9852941, b: 0.41295412, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 20
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 2
m_MaxSize: 40
m_Alignment: 4
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: Likes to Win!
--- !u!222 &222000012566199212
CanvasRenderer:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000011449472940}
--- !u!224 &224000013885566140
RectTransform:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000011449472940}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 160, y: 30}
m_Pivot: {x: 0.5, y: 0.5}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bb4775eb616604e329b92c1b4dfbaa5f
timeCreated: 1477081828
licenseType: Store
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,106 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &100100000
Prefab:
m_ObjectHideFlags: 1
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications: []
m_RemovedComponents: []
m_ParentPrefab: {fileID: 0}
m_RootGameObject: {fileID: 1000012172119028}
m_IsPrefabParent: 1
--- !u!1 &1000012172119028
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
serializedVersion: 5
m_Component:
- component: {fileID: 224000013400680178}
- component: {fileID: 222000013258767294}
- component: {fileID: 114000010166173948}
- component: {fileID: 114000010432752000}
m_Layer: 5
m_Name: LeaderboardText
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &114000010166173948
MonoBehaviour:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000012172119028}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.8301217, g: 0.8161765, b: 1, a: 1}
m_RaycastTarget: 0
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 30
m_FontStyle: 0
m_BestFit: 1
m_MinSize: 3
m_MaxSize: 40
m_Alignment: 3
m_AlignByGeometry: 0
m_RichText: 0
m_HorizontalOverflow: 1
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: ' '
--- !u!114 &114000010432752000
MonoBehaviour:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000012172119028}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 1679637790, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 30
m_PreferredWidth: -1
m_PreferredHeight: 30
m_FlexibleWidth: 1
m_FlexibleHeight: -1
--- !u!222 &222000013258767294
CanvasRenderer:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000012172119028}
--- !u!224 &224000013400680178
RectTransform:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1000012172119028}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 09fc1e67c860e68488d20df60c04cb2a
timeCreated: 1476930368
licenseType: Store
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cf3f39c980574304d8f8817cd3dfa941
timeCreated: 1475179794
licenseType: Store
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,71 @@
# Overview
This example uses basic Quickmatch and Peer-to-Peer networking to creating a cross-platform ball shooting game.
Quickmatch is used to find other players for a match and Networking is used to synchronize player
state such as movement of the balls.
# Application Setup
1. Open the Project in Unity 5.4.1p1 or later
2. Import the OculusPlatform Unity package (Main Menu -> Assets -> Import Package -> Custom Package)
## Rift
1. Create your Rift application on the Oculus Developer Dashboard
2. Copy the Application ID into the Project (Main Menu -> Oculus Platform -> Edit Settings -> Oculus Rift App Id)
## GearVR
1. Create the GearVR application on the Oculus Developer Dashboard
2. Move the GearVR application into the Rift application's App Grouping
3. Copy the Application ID into the Project (Main Menu -> Oculus Platform -> Edit Settings -> Gear VR App Id)
4. Copy the OSIG files for the GearVR devices you are testing to Assets\Plugins\Android\Assets
# Configure Matchmaking
1. On the Oculus Dashboard, navigate to the Matchmaking section for your App Grouping
2. Click Create Pool
3. For the Pool Key use: NORMAL_QUICKMATCH, or if you want to use a different Pool Key, update the constant in MatchmakingManager.cs
4. Choose Quickmatch mode
5. Enter 2 for Min Users and 3 for Max Users
6. Choose None for Skill Pool
7. Leave Advanced Quickmatch set to No
8. Leave Should Consider Ping Time? at the default setting of No
9. Don't add anything under Data Settings
10. Click Submit.
# Configure Leaderboards
This sample uses two Leaderboards to track player scores. One leaderboard tracks the player that has
won the most games and another tracks who achieved the highest score in a single game. Setup the leaderboards
using the following steps:
1. Navigate to your App Grouping section on the Developer Dashboard
2. Create a new leadername with the API NAME **MOST_MATCHES_WON** and sort order **Higher is Better**
3. Create a new leadername with the API NAME **HIGHEST_MATCH_SCORE** and sort order **Higher is Better**
# Configure Achievements
The sample updates an achievement that counts the number of times a player has won. Follow these steps to create an
achievement that is unlocked when the player has won 10 matches:
1. Navigate to your App Grouping section on the Developer Dashboard
2. Click on the **Create Achievement** button
3. Set the API Name to **LIKES_TO_WIN**
4. Set an appropriate Title and Description
5. Leave the Write Policy as **CLIENT_AUTHORITATIVE**
6. Leave Is Achievement Secret untoggled
7. Set the Type to **Count**
7. Set the Target to *10*
# Upload your builds
Build executables from Unity and upload them to your Application Dashboard
* Rift
1. Add the executable and data folder to a zip file
2. Upload the zip to the Alpha channel on your Dashboard
3. Set the executable name you chose in the zip file
4. Add Friends you are testing with as Subscribed Users for the Alpha channel
* GearVR
1. Create an android keystore (if you don't have one) so Unity can sign the build. (Player Settings -> Publishing Settings)
2. Upload the apk to the Alpha channel on your Dashboard
3. Each apk you upload needs a new build number (Player Settings -> Other Settings)
4. Add Friends you are testing with as Subscribed Users for the Alpha channel

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a3c974875a2ea724e93b6e5114bca96f
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c2c4feba7e20a244c835a28919f973b1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,25 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using System.Collections;
// An AI Player just shoots a ball forward with some random delay.
public class AIPlayer : Player {
void FixedUpdate ()
{
if (HasBall)
{
// add a little randomness to the shoot rate so the AI's don't look synchronized
if (Random.Range(0f, 1f) < 0.03f)
{
ShootBall();
}
}
else
{
CheckSpawnBall();
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 71702635af79217469ed41ba39db8d5a
timeCreated: 1475264776
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using System.Collections;
using Oculus.Platform;
using Oculus.Platform.Models;
public class AchievementsManager
{
// API NAME defined on the dashboard for the achievement
private const string LIKES_TO_WIN = "LIKES_TO_WIN";
// true if the local user hit the achievement Count setup on the dashboard
private bool m_likesToWinUnlocked;
public bool LikesToWin
{
get { return m_likesToWinUnlocked; }
}
public void CheckForAchievmentUpdates()
{
Achievements.GetProgressByName(new string[]{ LIKES_TO_WIN }).OnComplete(
(Message<AchievementProgressList> msg) =>
{
foreach (var achievement in msg.Data)
{
if (achievement.Name == LIKES_TO_WIN)
{
m_likesToWinUnlocked = achievement.IsUnlocked;
}
}
}
);
}
public void RecordWinForLocalUser()
{
Achievements.AddCount(LIKES_TO_WIN, 1);
CheckForAchievmentUpdates();
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 66d441ddf01234331b50e929139f4780
timeCreated: 1477071923
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using System.Collections;
public class BallEjector : MonoBehaviour {
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b4164fa75939f1e46a3e36dfbdc7f821
timeCreated: 1474514990
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
// Helper class to attach to the MainCamera so it can be moved with the mouse while debugging
// in 2D mode on a PC.
public class Camera2DController : MonoBehaviour
{
void Update ()
{
if (Input.GetButton("Fire2"))
{
var v = Input.GetAxis("Mouse Y");
var h = Input.GetAxis("Mouse X");
transform.rotation *= Quaternion.AngleAxis(h, Vector3.up);
transform.rotation *= Quaternion.AngleAxis(-v, Vector3.right);
Vector3 eulers = transform.eulerAngles;
eulers.z = 0;
transform.eulerAngles = eulers;
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 7ebf96caaf397684b86c4ff4d566798f
timeCreated: 1474514266
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,64 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using UnityEngine.UI;
// Uses two triggers to detect that a basket is made by traveling from top to bottom
// through the hoop.
public class DetectBasket : MonoBehaviour
{
private enum BasketPhase { NONE, TOP, BOTH, BOTTOM }
private BasketPhase m_phase = BasketPhase.NONE;
private Player m_owningPlayer;
public Player Player
{
set { m_owningPlayer = value; }
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.name == "Basket Top" && m_phase == BasketPhase.NONE)
{
m_phase = BasketPhase.TOP;
}
else if (other.gameObject.name == "Basket Bottom" && m_phase == BasketPhase.TOP)
{
m_phase = BasketPhase.BOTH;
}
else
{
m_phase = BasketPhase.NONE;
}
}
void OnTriggerExit(Collider other)
{
if (other.gameObject.name == "Basket Top" && m_phase == BasketPhase.BOTH)
{
m_phase = BasketPhase.BOTTOM;
}
else if (other.gameObject.name == "Basket Bottom" && m_phase == BasketPhase.BOTTOM)
{
m_phase = BasketPhase.NONE;
switch (PlatformManager.CurrentState)
{
case PlatformManager.State.PLAYING_A_LOCAL_MATCH:
case PlatformManager.State.PLAYING_A_NETWORKED_MATCH:
if (m_owningPlayer)
{
m_owningPlayer.Score += 2;
}
break;
}
}
else
{
m_phase = BasketPhase.NONE;
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a63d0cd5dd6d39a4abd35114563fe347
timeCreated: 1475105001
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
// helper script to render fading flytext above an object
public class FlyText : MonoBehaviour
{
// destory the gameobject after this many seconds
private const float LIFESPAN = 3.0f;
// how far to move upwards per frame
private readonly Vector3 m_movePerFrame = 0.5f * Vector3.up;
// actual destruction time
private float m_eol;
void Start()
{
m_eol = Time.time + LIFESPAN;
GetComponent<Text>().CrossFadeColor(Color.black, LIFESPAN * 1.7f, false, true);
}
void Update()
{
if (Time.time < m_eol)
{
transform.localPosition += m_movePerFrame;
}
else
{
Destroy(gameObject);
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: dc96f5380d7d743d9ae91f11379eb85b
timeCreated: 1477078886
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,84 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
// This script moves to goal around in a random direction to add a bit more difficulty
// to the game.
public class GoalMover : MonoBehaviour {
// how far to from the center before changing direction
[SerializeField] private float MAX_OFFSET = 2.0f;
// how fast the backboard will move
[SerializeField] private float m_speed = 0.005f;
// maximum interpolation distance allow to correct per update
private const float MOVE_TOLERANCE = 0.1f;
// the position the goal should be in - only differs if network updates come in
private Vector3 m_expectedPosition;
// the current move vector * m_speed;
private Vector3 m_moveDirection;
// the direction to move when we run into the boundary
private Vector3 m_nextMoveDirection;
public Vector3 ExpectedPosition
{
get { return m_expectedPosition; }
set { m_expectedPosition = value; }
}
public Vector3 MoveDirection
{
get { return m_moveDirection; }
set { m_moveDirection = value; }
}
public Vector3 NextMoveDirection
{
get { return m_nextMoveDirection; }
set { m_nextMoveDirection = value; }
}
void Start ()
{
ExpectedPosition = transform.localPosition;
m_moveDirection.x = Random.Range(-1.0f, 1.0f);
m_moveDirection.y = Random.Range(-1.0f, 1.0f);
m_moveDirection = Vector3.ClampMagnitude(m_moveDirection, m_speed);
m_nextMoveDirection.x = -Mathf.Sign(m_moveDirection.x) * Random.Range(0f, 1.0f);
m_nextMoveDirection.y = -Mathf.Sign(m_moveDirection.y) * Random.Range(0f, 1.0f);
m_nextMoveDirection = Vector3.ClampMagnitude(m_nextMoveDirection, m_speed);
}
void FixedUpdate ()
{
// move a bit along our random direction
transform.localPosition += MoveDirection;
ExpectedPosition += MoveDirection;
// make a slight correction to the position if we're not where we should be
Vector3 correction = ExpectedPosition - transform.localPosition;
correction = Vector3.ClampMagnitude(correction, MOVE_TOLERANCE);
transform.localPosition += correction;
// if we've gone too far from the center point, correct and change direction
if (transform.localPosition.sqrMagnitude > (MAX_OFFSET*MAX_OFFSET))
{
transform.localPosition = Vector3.ClampMagnitude(transform.localPosition, MAX_OFFSET);
ExpectedPosition = transform.localPosition;
MoveDirection = NextMoveDirection;
// select a the next randomish direction to move in
m_nextMoveDirection.x = -Mathf.Sign(m_moveDirection.x) * Random.Range(0f, 1.0f);
m_nextMoveDirection.y = -Mathf.Sign(m_moveDirection.y) * Random.Range(0f, 1.0f);
m_nextMoveDirection = Vector3.ClampMagnitude(m_nextMoveDirection, m_speed);
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fbea2c3cb080a064f84d3bd86c2f3a53
timeCreated: 1475173562
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,199 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using System.Collections.Generic;
using Oculus.Platform;
using Oculus.Platform.Models;
// Coordinates updating leaderboard scores and polling for leaderboard updates.
public class LeaderboardManager
{
// API NAME for the leaderboard where we store how many matches the user has won
private const string MOST_MATCHES_WON = "MOST_MATCHES_WON";
// API NAME for the leaderboard where we store the user's match score
private const string HIGHEST_MATCH_SCORE = "HIGHEST_MATCH_SCORE";
// the top number of entries to query
private const int TOP_N_COUNT = 5;
// how often to poll the service for leaderboard updates
private const float LEADERBOARD_POLL_FREQ = 30.0f;
// the next time to check for leaderboard updates
private float m_nextCheckTime;
// cache to hold most-wins leaderboard entries as they come in
private volatile SortedDictionary<int, LeaderboardEntry> m_mostWins;
// whether we've found the local user's entry yet
private bool m_foundLocalUserMostWinsEntry;
// number of times the local user has won
private long m_numWins;
// callback to deliver the most-wins leaderboard entries
private OnMostWinsLeaderboardUpdated m_mostWinsCallback;
// cache to hold high-score leaderboard entries as they come in
private volatile SortedDictionary<int, LeaderboardEntry> m_highScores;
// whether we've found the local user's entry yet
private bool m_foundLocalUserHighScore;
// callback to deliver the high-scores leaderboard entries
private OnHighScoreLeaderboardUpdated m_highScoreCallback;
public void CheckForUpdates()
{
if (Time.time >= m_nextCheckTime &&
PlatformManager.CurrentState == PlatformManager.State.WAITING_TO_PRACTICE_OR_MATCHMAKE)
{
m_nextCheckTime = Time.time + LEADERBOARD_POLL_FREQ;
QueryMostWinsLeaderboard();
QueryHighScoreLeaderboard();
}
}
#region Most Wins Leaderboard
public delegate void OnMostWinsLeaderboardUpdated(SortedDictionary<int, LeaderboardEntry> entries);
public OnMostWinsLeaderboardUpdated MostWinsLeaderboardUpdatedCallback
{
set { m_mostWinsCallback = value; }
}
void QueryMostWinsLeaderboard()
{
// if a query is already in progress, don't start a new one.
if (m_mostWins != null)
return;
m_mostWins = new SortedDictionary<int, LeaderboardEntry>();
m_foundLocalUserMostWinsEntry = false;
Leaderboards.GetEntries(MOST_MATCHES_WON, TOP_N_COUNT, LeaderboardFilterType.None,
LeaderboardStartAt.Top).OnComplete(MostWinsGetEntriesCallback);
}
void MostWinsGetEntriesCallback(Message<LeaderboardEntryList> msg)
{
if (!msg.IsError)
{
foreach (LeaderboardEntry entry in msg.Data)
{
m_mostWins[entry.Rank] = entry;
if (entry.User.ID == PlatformManager.MyID)
{
m_foundLocalUserMostWinsEntry = true;
m_numWins = entry.Score;
}
}
// results might be paged for large requests
if (msg.Data.HasNextPage)
{
Leaderboards.GetNextEntries(msg.Data).OnComplete(MostWinsGetEntriesCallback);
return;
}
// if local user not in the top, get their position specifically
if (!m_foundLocalUserMostWinsEntry)
{
Leaderboards.GetEntries(MOST_MATCHES_WON, 1, LeaderboardFilterType.None,
LeaderboardStartAt.CenteredOnViewer).OnComplete(MostWinsGetEntriesCallback);
return;
}
}
// else an error is returned if the local player isn't ranked - we can ignore that
if (m_mostWinsCallback != null)
{
m_mostWinsCallback(m_mostWins);
}
m_mostWins = null;
}
#endregion
#region Highest Score Board
public delegate void OnHighScoreLeaderboardUpdated(SortedDictionary<int, LeaderboardEntry> entries);
public OnHighScoreLeaderboardUpdated HighScoreLeaderboardUpdatedCallback
{
set { m_highScoreCallback = value; }
}
void QueryHighScoreLeaderboard()
{
// if a query is already in progress, don't start a new one.
if (m_highScores != null)
return;
m_highScores = new SortedDictionary<int, LeaderboardEntry>();
m_foundLocalUserHighScore = false;
Leaderboards.GetEntries(HIGHEST_MATCH_SCORE, TOP_N_COUNT, LeaderboardFilterType.None,
LeaderboardStartAt.Top).OnComplete(HighestScoreGetEntriesCallback);
}
void HighestScoreGetEntriesCallback(Message<LeaderboardEntryList> msg)
{
if (!msg.IsError)
{
foreach (LeaderboardEntry entry in msg.Data)
{
m_highScores[entry.Rank] = entry;
if (entry.User.ID == PlatformManager.MyID)
{
m_foundLocalUserHighScore = true;
}
}
// results might be paged for large requests
if (msg.Data.HasNextPage)
{
Leaderboards.GetNextEntries(msg.Data).OnComplete(HighestScoreGetEntriesCallback);;
return;
}
// if local user not in the top, get their position specifically
if (!m_foundLocalUserHighScore)
{
Leaderboards.GetEntries(HIGHEST_MATCH_SCORE, 1, LeaderboardFilterType.None,
LeaderboardStartAt.CenteredOnViewer).OnComplete(HighestScoreGetEntriesCallback);
return;
}
}
// else an error is returned if the local player isn't ranked - we can ignore that
if (m_highScoreCallback != null)
{
m_highScoreCallback(m_highScores);
}
m_highScores = null;
}
#endregion
// submit the local player's match score to the leaderboard service
public void SubmitMatchScores(bool wonMatch, uint score)
{
if (wonMatch)
{
m_numWins += 1;
Leaderboards.WriteEntry(MOST_MATCHES_WON, m_numWins);
}
if (score > 0)
{
Leaderboards.WriteEntry(HIGHEST_MATCH_SCORE, score);
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b5525a4fa9d4898438f479b1c25ff8b4
timeCreated: 1476809789
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,48 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using System.Collections;
// This class listens for Input events to shoot a ball, and also notifies the P2PManager when
// ball or scores needs to be synchronized to remote players.
public class LocalPlayer : Player {
public override uint Score
{
set
{
base.Score = value;
if (PlatformManager.CurrentState == PlatformManager.State.PLAYING_A_NETWORKED_MATCH)
{
PlatformManager.P2P.SendScoreUpdate(base.Score);
}
}
}
void Update ()
{
GameObject newball = null;
// if the player is holding a ball
if (HasBall)
{
// check to see if the User is hitting the shoot button
if (Input.GetButton("Fire1") || Input.GetKey(KeyCode.Space))
{
newball = ShootBall();
}
}
// spawn a new held ball if we can
else
{
newball = CheckSpawnBall();
}
if (newball && PlatformManager.CurrentState == PlatformManager.State.PLAYING_A_NETWORKED_MATCH)
{
PlatformManager.P2P.AddNetworkBall(newball);
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ac564d79b10fc01448f93b8dcc74d3d0
timeCreated: 1475264810
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,472 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.UI;
using System.Collections.Generic;
using Oculus.Platform.Models;
// This class coordinates playing matches. It mediates being idle
// and entering a practice or online game match.
public class MatchController : MonoBehaviour
{
// Text to display when the match will start or finish
[SerializeField] private Text m_timerText = null;
// the camera is moved between the idle position and the assigned court position
[SerializeField] private Camera m_camera = null;
// where the camera will be when not in a match
[SerializeField] private Transform m_idleCameraTransform = null;
// button that toggles between matchmaking and cancel
[SerializeField] private Text m_matchmakeButtonText = null;
// this should equal the maximum number of players configured on the Oculus Dashboard
[SerializeField] private PlayerArea[] m_playerAreas = new PlayerArea[3];
// the time to wait between selecting Practice and starting
[SerializeField] private uint PRACTICE_WARMUP_TIME = 5;
// seconds to wait to coordinate P2P setup with other match players before starting
[SerializeField] private uint MATCH_WARMUP_TIME = 30;
// seconds for the match
[SerializeField] private uint MATCH_TIME = 20;
// how long to remain in position after the match to view results
[SerializeField] private uint MATCH_COOLDOWN_TIME = 10;
// panel to add most-wins leaderboard entries to
[SerializeField] private GameObject m_mostWinsLeaderboard = null;
// panel to add high-score leaderboard entries to
[SerializeField] private GameObject m_highestScoresLeaderboard = null;
// leaderboard entry Text prefab
[SerializeField] private GameObject m_leaderboardEntryPrefab = null;
// Text prefab to use for achievements fly-text
[SerializeField] private GameObject m_flytext = null;
// the current state of the match controller
private State m_currentState;
// transition time for states that automatically transition to the next state,
// for example ending the match when the timer expires
private float m_nextStateTransitionTime;
// the court the local player was assigned to
private int m_localSlot;
void Start()
{
PlatformManager.Matchmaking.EnqueueResultCallback = OnMatchFoundCallback;
PlatformManager.Matchmaking.MatchPlayerAddedCallback = MatchPlayerAddedCallback;
PlatformManager.P2P.StartTimeOfferCallback = StartTimeOfferCallback;
PlatformManager.Leaderboards.MostWinsLeaderboardUpdatedCallback = MostWinsLeaderboardCallback;
PlatformManager.Leaderboards.HighScoreLeaderboardUpdatedCallback = HighestScoreLeaderboardCallback;
TransitionToState(State.NONE);
}
void Update()
{
UpdateCheckForNextTimedTransition();
UpdateMatchTimer();
}
public float MatchStartTime
{
get
{
switch(m_currentState)
{
case State.WAITING_TO_START_PRACTICE:
case State.WAITING_TO_SETUP_MATCH:
return m_nextStateTransitionTime;
default: return 0;
}
}
private set { m_nextStateTransitionTime = value; }
}
#region State Management
private enum State
{
UNKNOWN,
// no current match, waiting for the local user to select something
NONE,
// user selected a practice match, waiting for the match timer to start
WAITING_TO_START_PRACTICE,
// playing a Practice match against AI players
PRACTICING,
// post practice match, time to view the scores
VIEWING_RESULTS_PRACTICE,
// selecting Player Online and waiting for the Matchmaking service to find and create a
// match and join the assigned match room
WAITING_FOR_MATCH,
// match room is joined, waiting to coordinate with the other players
WAITING_TO_SETUP_MATCH,
// playing a competative match against other players
PLAYING_MATCH,
// match is complete, viewing the match scores
VIEWING_MATCH_RESULTS,
}
void TransitionToState(State newState)
{
Debug.LogFormat("MatchController State {0} -> {1}", m_currentState, newState);
if (m_currentState != newState)
{
var oldState = m_currentState;
m_currentState = newState;
// state transition logic
switch (newState)
{
case State.NONE:
SetupForIdle();
MoveCameraToIdlePosition();
PlatformManager.TransitionToState(PlatformManager.State.WAITING_TO_PRACTICE_OR_MATCHMAKE);
m_matchmakeButtonText.text = "Play Online";
break;
case State.WAITING_TO_START_PRACTICE:
Assert.AreEqual(oldState, State.NONE);
SetupForPractice();
MoveCameraToMatchPosition();
PlatformManager.TransitionToState(PlatformManager.State.MATCH_TRANSITION);
m_nextStateTransitionTime = Time.time + PRACTICE_WARMUP_TIME;
break;
case State.PRACTICING:
Assert.AreEqual(oldState, State.WAITING_TO_START_PRACTICE);
PlatformManager.TransitionToState(PlatformManager.State.PLAYING_A_LOCAL_MATCH);
m_nextStateTransitionTime = Time.time + MATCH_TIME;
break;
case State.VIEWING_RESULTS_PRACTICE:
Assert.AreEqual(oldState, State.PRACTICING);
PlatformManager.TransitionToState(PlatformManager.State.MATCH_TRANSITION);
m_nextStateTransitionTime = Time.time + MATCH_COOLDOWN_TIME;
m_timerText.text = "0:00.00";
break;
case State.WAITING_FOR_MATCH:
Assert.AreEqual(oldState, State.NONE);
PlatformManager.TransitionToState(PlatformManager.State.MATCH_TRANSITION);
m_matchmakeButtonText.text = "Cancel";
break;
case State.WAITING_TO_SETUP_MATCH:
Assert.AreEqual(oldState, State.WAITING_FOR_MATCH);
m_nextStateTransitionTime = Time.time + MATCH_WARMUP_TIME;
break;
case State.PLAYING_MATCH:
Assert.AreEqual(oldState, State.WAITING_TO_SETUP_MATCH);
PlatformManager.TransitionToState(PlatformManager.State.PLAYING_A_NETWORKED_MATCH);
m_nextStateTransitionTime = Time.time + MATCH_TIME;
break;
case State.VIEWING_MATCH_RESULTS:
Assert.AreEqual(oldState, State.PLAYING_MATCH);
PlatformManager.TransitionToState(PlatformManager.State.MATCH_TRANSITION);
m_nextStateTransitionTime = Time.time + MATCH_COOLDOWN_TIME;
m_timerText.text = "0:00.00";
CalculateMatchResults();
break;
}
}
}
void UpdateCheckForNextTimedTransition()
{
if (m_currentState != State.NONE && Time.time >= m_nextStateTransitionTime)
{
switch (m_currentState)
{
case State.WAITING_TO_START_PRACTICE:
TransitionToState(State.PRACTICING);
break;
case State.PRACTICING:
TransitionToState(State.VIEWING_RESULTS_PRACTICE);
break;
case State.VIEWING_RESULTS_PRACTICE:
TransitionToState(State.NONE);
break;
case State.WAITING_TO_SETUP_MATCH:
TransitionToState(State.PLAYING_MATCH);
break;
case State.PLAYING_MATCH:
TransitionToState(State.VIEWING_MATCH_RESULTS);
break;
case State.VIEWING_MATCH_RESULTS:
PlatformManager.Matchmaking.EndMatch();
TransitionToState(State.NONE);
break;
}
}
}
void UpdateMatchTimer()
{
if (Time.time <= m_nextStateTransitionTime)
{
switch (m_currentState)
{
case State.WAITING_TO_START_PRACTICE:
case State.WAITING_TO_SETUP_MATCH:
m_timerText.text = string.Format("{0:0}", Mathf.Ceil(Time.time - MatchStartTime));
break;
case State.PRACTICING:
case State.PLAYING_MATCH:
var delta = m_nextStateTransitionTime - Time.time;
m_timerText.text = string.Format("{0:#0}:{1:#00}.{2:00}",
Mathf.Floor(delta / 60),
Mathf.Floor(delta) % 60,
Mathf.Floor(delta * 100) % 100);
break;
}
}
}
#endregion
#region Player Setup/Teardown
void SetupForIdle()
{
for (int i = 0; i < m_playerAreas.Length; i++)
{
m_playerAreas[i].SetupForPlayer<AIPlayer>("* AI *");
}
}
void SetupForPractice()
{
// randomly select a position for the local player
m_localSlot = Random.Range(0,m_playerAreas.Length-1);
for (int i=0; i < m_playerAreas.Length; i++)
{
if (i == m_localSlot)
{
m_playerAreas[i].SetupForPlayer<LocalPlayer>(PlatformManager.MyOculusID);
}
else
{
m_playerAreas[i].SetupForPlayer<AIPlayer>("* AI *");
}
}
}
Player MatchPlayerAddedCallback(int slot, User user)
{
Player player = null;
if (m_currentState == State.WAITING_TO_SETUP_MATCH && slot < m_playerAreas.Length)
{
if (user.ID == PlatformManager.MyID)
{
var localPlayer = m_playerAreas[slot].SetupForPlayer<LocalPlayer>(user.OculusID);
MoveCameraToMatchPosition();
player = localPlayer;
m_localSlot = slot;
}
else
{
var remotePlayer = m_playerAreas[slot].SetupForPlayer<RemotePlayer>(user.OculusID);
remotePlayer.User = user;
player = remotePlayer;
}
}
return player;
}
#endregion
#region Main Camera Movement
void MoveCameraToIdlePosition()
{
var ejector = m_camera.gameObject.GetComponentInChildren<BallEjector>();
if (ejector)
{
ejector.transform.SetParent(m_camera.transform.parent, false);
m_camera.transform.SetParent(m_idleCameraTransform, false);
}
}
void MoveCameraToMatchPosition()
{
foreach (var playerArea in m_playerAreas)
{
var player = playerArea.GetComponentInChildren<LocalPlayer>();
if (player)
{
var ejector = player.GetComponentInChildren<BallEjector>();
m_camera.transform.SetParent(player.transform, false);
ejector.transform.SetParent(m_camera.transform, false);
break;
}
}
DisplayAchievementFlytext();
}
#endregion
#region Match Initiation
public void StartPracticeMatch()
{
if (m_currentState == State.NONE)
{
TransitionToState(State.WAITING_TO_START_PRACTICE);
}
}
public void PlayOnlineOrCancel()
{
Debug.Log ("Play online or Cancel");
if (m_currentState == State.NONE)
{
PlatformManager.Matchmaking.QueueForMatch();
TransitionToState (State.WAITING_FOR_MATCH);
}
else if (m_currentState == State.WAITING_FOR_MATCH)
{
PlatformManager.Matchmaking.LeaveQueue();
TransitionToState (State.NONE);
}
}
// notification from the Matchmaking service if we succeeded in finding an online match
void OnMatchFoundCallback(bool success)
{
if (success)
{
TransitionToState(State.WAITING_TO_SETUP_MATCH);
}
else
{
TransitionToState(State.NONE);
}
}
// handle an offer from a remote player for a new match start time
float StartTimeOfferCallback(float remoteTime)
{
if (m_currentState == State.WAITING_TO_SETUP_MATCH)
{
// if the remote start time is later use that, as long as it's not horribly wrong
if (remoteTime > MatchStartTime && (remoteTime - 60) < MatchStartTime)
{
Debug.Log("Moving Start time by " + (remoteTime - MatchStartTime));
MatchStartTime = remoteTime;
}
}
return MatchStartTime;
}
#endregion
#region Leaderboards and Achievements
void MostWinsLeaderboardCallback(SortedDictionary<int, LeaderboardEntry> entries)
{
foreach (Transform entry in m_mostWinsLeaderboard.transform)
{
Destroy(entry.gameObject);
}
foreach (var entry in entries.Values)
{
GameObject label = Instantiate(m_leaderboardEntryPrefab);
label.transform.SetParent(m_mostWinsLeaderboard.transform, false);
label.GetComponent<Text>().text =
string.Format("{0} - {1} - {2}", entry.Rank, entry.User.OculusID, entry.Score);
}
}
void HighestScoreLeaderboardCallback(SortedDictionary<int, LeaderboardEntry> entries)
{
foreach (Transform entry in m_highestScoresLeaderboard.transform)
{
Destroy(entry.gameObject);
}
foreach (var entry in entries.Values)
{
GameObject label = Instantiate(m_leaderboardEntryPrefab);
label.transform.SetParent(m_highestScoresLeaderboard.transform, false);
label.GetComponent<Text>().text =
string.Format("{0} - {1} - {2}", entry.Rank, entry.User.OculusID, entry.Score);
}
}
void CalculateMatchResults()
{
LocalPlayer localPlayer = null;
RemotePlayer remotePlayer = null;
foreach (var court in m_playerAreas)
{
if (court.Player is LocalPlayer)
{
localPlayer = court.Player as LocalPlayer;
}
else if (court.Player is RemotePlayer &&
(remotePlayer == null || court.Player.Score > remotePlayer.Score))
{
remotePlayer = court.Player as RemotePlayer;
}
}
// ignore the match results if the player got into a session without an opponent
if (!localPlayer || !remotePlayer)
{
return;
}
bool wonMatch = localPlayer.Score > remotePlayer.Score;
PlatformManager.Leaderboards.SubmitMatchScores(wonMatch, localPlayer.Score);
if (wonMatch)
{
PlatformManager.Achievements.RecordWinForLocalUser();
}
}
void DisplayAchievementFlytext()
{
if (PlatformManager.Achievements.LikesToWin)
{
GameObject go = Instantiate(m_flytext);
go.GetComponent<Text>().text = "Likes to Win!";
go.transform.position = Vector3.up * 40;
go.transform.SetParent(m_playerAreas[m_localSlot].NameText.transform, false);
}
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 5e31d7fbc2f31b7499684c9e87fc8454
timeCreated: 1475257003
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,145 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using System.Collections.Generic;
using Oculus.Platform;
using Oculus.Platform.Models;
// This class coordinates with the Oculus Platform Matchmaking Service to
// establish a Quickmatch session with one or two other players.
public class MatchmakingManager
{
// the name we setup on the Developer Dashboard for the quickmatch pool
private const string NORMAL_POOL = "NORMAL_QUICKMATCH";
// the ID of the Room the matchmaking service sent to join
private ulong m_matchRoom;
// the list of players that join the match room.
// it may not be all the match players since some might disconnect
// before joining the room, but then again they might disconnect
// midway through a match as well.
private readonly Dictionary<ulong, User> m_remotePlayers;
public MatchmakingManager()
{
m_remotePlayers = new Dictionary<ulong, User>();
Matchmaking.SetMatchFoundNotificationCallback(MatchFoundCallback);
Rooms.SetUpdateNotificationCallback(MatchmakingRoomUpdateCallback);
}
public delegate void OnEnqueueResult(bool successful);
public delegate Player OnMatchPlayerAdded(int slot, User user);
private OnEnqueueResult m_enqueueCallback;
private OnMatchPlayerAdded m_playerCallback;
public OnEnqueueResult EnqueueResultCallback
{
private get { return m_enqueueCallback; }
set { m_enqueueCallback = value; }
}
public OnMatchPlayerAdded MatchPlayerAddedCallback
{
private get { return m_playerCallback; }
set { m_playerCallback = value; }
}
public void QueueForMatch()
{
Matchmaking.Enqueue (NORMAL_POOL).OnComplete(MatchmakingEnqueueCallback);
}
void MatchmakingEnqueueCallback(Message msg)
{
if (msg.IsError)
{
Debug.Log(msg.GetError().Message);
EnqueueResultCallback(false);
return;
}
}
void MatchFoundCallback(Message<Room> msg)
{
m_matchRoom = msg.Data.ID;
Matchmaking.JoinRoom(msg.Data.ID, true).OnComplete(MatchmakingJoinRoomCallback);
}
void MatchmakingJoinRoomCallback(Message<Room> msg)
{
if (msg.IsError)
{
Debug.Log (msg.GetError().Message);
EnqueueResultCallback(false);
return;
}
Debug.Log ("Match found and room joined " + m_matchRoom);
EnqueueResultCallback(true);
// this sample doesn't try to coordinate that all the players see consistent
// positioning to assigned courts, but that would be a great next feature to add
int slot = 0;
if (msg.Data.UsersOptional != null)
{
foreach (var user in msg.Data.UsersOptional)
{
var player = MatchPlayerAddedCallback(slot++, user);
if (PlatformManager.MyID != user.ID)
{
m_remotePlayers[user.ID] = user;
PlatformManager.P2P.AddRemotePlayer (player as RemotePlayer);
}
}
}
}
void MatchmakingRoomUpdateCallback(Message<Room> msg)
{
if (msg.IsError)
{
PlatformManager.TerminateWithError(msg);
return;
}
// check to make sure the room is valid as there are a few odd timing issues (for
// example when leaving a room) that can trigger an uninteresting update
if (msg.Data.ID == m_matchRoom)
{
if (msg.Data.UsersOptional != null)
{
foreach (User user in msg.Data.UsersOptional)
{
if (PlatformManager.MyID != user.ID && !m_remotePlayers.ContainsKey(user.ID))
{
m_remotePlayers[user.ID] = user;
var player = MatchPlayerAddedCallback(m_remotePlayers.Count, user);
PlatformManager.P2P.AddRemotePlayer(player as RemotePlayer);
}
}
}
}
}
public void EndMatch()
{
if (m_matchRoom != 0)
{
Rooms.Leave (m_matchRoom);
m_remotePlayers.Clear ();
PlatformManager.P2P.DisconnectAll ();
m_matchRoom = 0;
}
}
public void LeaveQueue()
{
Matchmaking.Cancel();
EndMatch();
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fa69df7dcb6814fab9439789f1e23e2e
timeCreated: 1475629968
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,579 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using System.Collections.Generic;
using Oculus.Platform;
using Oculus.Platform.Models;
using System;
using UnityEngine.Assertions;
// This helper class coordinates establishing Peer-to-Peer connections between the
// players in the match. It tries to sychronize time between the devices and
// handles position update messages for the backboard and moving balls.
public class P2PManager
{
#region Member variables
// helper class to hold data we need for remote players
private class RemotePlayerData
{
// the last received Net connection state
public PeerConnectionState state;
// the Unity Monobehaviour
public RemotePlayer player;
// offset from my local time to the time of the remote host
public float remoteTimeOffset;
// the last ball update remote time, used to disgard out of order packets
public float lastReceivedBallsTime;
// remote Instance ID -> local MonoBahaviours for balls we're receiving updates on
public readonly Dictionary<int, P2PNetworkBall> activeBalls = new Dictionary<int, P2PNetworkBall>();
}
// authorized users to connect to and associated data
private readonly Dictionary<ulong, RemotePlayerData> m_remotePlayers = new Dictionary<ulong, RemotePlayerData>();
// when to send the next update to remotes on the state on my local balls
private float m_timeForNextBallUpdate;
private const byte TIME_SYNC_MESSAGE = 1;
private const uint TIME_SYNC_MESSAGE_SIZE = 1+4;
private const int TIME_SYNC_MESSAGE_COUNT = 7;
private const byte START_TIME_MESSAGE = 2;
private const uint START_TIME_MESSAGE_SIZE = 1+4;
private const byte BACKBOARD_UPDATE_MESSAGE = 3;
private const uint BACKBOARD_UPDATE_MESSAGE_SIZE = 1+4+12+12+12;
private const byte LOCAL_BALLS_UPDATE_MESSAGE = 4;
private const uint LOCAL_BALLS_UPDATE_MESSATE_SIZE_MAX = 1+4+(2*Player.MAX_BALLS*(1+4+12+12));
private const float LOCAL_BALLS_UPDATE_DELAY = 0.1f;
private const byte SCORE_UPDATE_MESSAGE = 5;
private const uint SCORE_UPDATE_MESSAGE_SIZE = 1 + 4;
// cache of local balls that we are sending updates for
private readonly Dictionary<int, P2PNetworkBall> m_localBalls = new Dictionary<int, P2PNetworkBall>();
// reusable buffer to read network data into
private readonly byte[] readBuffer = new byte[LOCAL_BALLS_UPDATE_MESSATE_SIZE_MAX];
// temporary time-sync cache of the calculated time offsets
private readonly Dictionary<ulong, List<float>> m_remoteSyncTimeCache = new Dictionary<ulong, List<float>>();
// temporary time-sync cache of the last sent message
private readonly Dictionary<ulong, float> m_remoteSentTimeCache = new Dictionary<ulong, float>();
// the delegate to handle start-time coordination
private StartTimeOffer m_startTimeOfferCallback;
#endregion
public P2PManager()
{
Net.SetPeerConnectRequestCallback(PeerConnectRequestCallback);
Net.SetConnectionStateChangedCallback(ConnectionStateChangedCallback);
}
public void UpdateNetwork()
{
if (m_remotePlayers.Count == 0)
return;
// check for new messages
Packet packet;
while ((packet = Net.ReadPacket()) != null)
{
if (!m_remotePlayers.ContainsKey(packet.SenderID))
continue;
packet.ReadBytes(readBuffer);
switch (readBuffer[0])
{
case TIME_SYNC_MESSAGE:
Assert.AreEqual(TIME_SYNC_MESSAGE_SIZE, packet.Size);
ReadTimeSyncMessage(packet.SenderID, readBuffer);
break;
case START_TIME_MESSAGE:
Assert.AreEqual(START_TIME_MESSAGE_SIZE, packet.Size);
ReceiveMatchStartTimeOffer(packet.SenderID, readBuffer);
break;
case BACKBOARD_UPDATE_MESSAGE:
Assert.AreEqual(BACKBOARD_UPDATE_MESSAGE_SIZE, packet.Size);
ReceiveBackboardUpdate(packet.SenderID, readBuffer);
break;
case LOCAL_BALLS_UPDATE_MESSAGE:
ReceiveBallTransforms(packet.SenderID, readBuffer, packet.Size);
break;
case SCORE_UPDATE_MESSAGE:
Assert.AreEqual(SCORE_UPDATE_MESSAGE_SIZE, packet.Size);
ReceiveScoredUpdate(packet.SenderID, readBuffer);
break;
}
}
if (Time.time >= m_timeForNextBallUpdate && m_localBalls.Count > 0)
{
SendLocalBallTransforms();
}
}
#region Connection Management
// adds a remote player to establish a connection to, or accept a connection from
public void AddRemotePlayer(RemotePlayer player)
{
if (!m_remotePlayers.ContainsKey (player.ID))
{
m_remotePlayers[player.ID] = new RemotePlayerData();
m_remotePlayers[player.ID].state = PeerConnectionState.Unknown;
m_remotePlayers [player.ID].player = player;
// ID comparison is used to decide who Connects and who Accepts
if (PlatformManager.MyID < player.ID)
{
Debug.Log ("P2P Try Connect to: " + player.ID);
Net.Connect (player.ID);
}
}
}
public void DisconnectAll()
{
foreach (var id in m_remotePlayers.Keys)
{
Net.Close(id);
}
m_remotePlayers.Clear();
}
void PeerConnectRequestCallback(Message<NetworkingPeer> msg)
{
if (m_remotePlayers.ContainsKey(msg.Data.ID))
{
Debug.LogFormat("P2P Accepting Connection request from {0}", msg.Data.ID);
Net.Accept(msg.Data.ID);
}
else
{
Debug.LogFormat("P2P Ignoring unauthorized Connection request from {0}", msg.Data.ID);
}
}
void ConnectionStateChangedCallback(Message<NetworkingPeer> msg)
{
Debug.LogFormat("P2P {0} Connection state changed to {1}", msg.Data.ID, msg.Data.State);
if (m_remotePlayers.ContainsKey(msg.Data.ID))
{
m_remotePlayers[msg.Data.ID].state = msg.Data.State;
switch (msg.Data.State)
{
case PeerConnectionState.Connected:
if (PlatformManager.MyID < msg.Data.ID)
{
SendTimeSyncMessage(msg.Data.ID);
}
break;
case PeerConnectionState.Timeout:
if (PlatformManager.MyID < msg.Data.ID)
{
Net.Connect(msg.Data.ID);
}
break;
case PeerConnectionState.Closed:
m_remotePlayers.Remove(msg.Data.ID);
break;
}
}
}
#endregion
#region Time Synchronizaiton
// This section implements some basic time synchronization between the players.
// The algorithm is:
// -Send a time-sync message and receive a time-sync message response
// -Estimate time offset
// -Repeat several times
// -Average values discarding any statistical anomalies
// Normally delays would be added in case there is intermittent network congestion
// however the match times are so short we don't do that here. Also, if one client
// pauses their game and Unity stops their simulation, all bets are off for time
// synchronization. Depending on the goals of your app, you could either reinitiate
// time synchronization, or just disconnect that player.
void SendTimeSyncMessage(ulong remoteID)
{
if (!m_remoteSyncTimeCache.ContainsKey(remoteID))
{
m_remoteSyncTimeCache[remoteID] = new List<float>();
}
float time = Time.realtimeSinceStartup;
m_remoteSentTimeCache[remoteID] = time;
byte[] buf = new byte[TIME_SYNC_MESSAGE_SIZE];
buf[0] = TIME_SYNC_MESSAGE;
int offset = 1;
PackFloat(time, buf, ref offset);
Net.SendPacket(remoteID, buf, SendPolicy.Reliable);
}
void ReadTimeSyncMessage(ulong remoteID, byte[] msg)
{
if (!m_remoteSentTimeCache.ContainsKey(remoteID))
{
SendTimeSyncMessage(remoteID);
return;
}
int offset = 1;
float remoteTime = UnpackFloat(msg, ref offset);
float now = Time.realtimeSinceStartup;
float latency = (now - m_remoteSentTimeCache[remoteID]) / 2;
float remoteTimeOffset = now - (remoteTime + latency);
m_remoteSyncTimeCache[remoteID].Add(remoteTimeOffset);
if (m_remoteSyncTimeCache[remoteID].Count < TIME_SYNC_MESSAGE_COUNT)
{
SendTimeSyncMessage(remoteID);
}
else
{
if (PlatformManager.MyID < remoteID)
{
// this client started the sync, need to send one last message to
// the remote so they can finish their sync calculation
SendTimeSyncMessage(remoteID);
}
// sort the times and remember the median
m_remoteSyncTimeCache[remoteID].Sort();
float median = m_remoteSyncTimeCache[remoteID][TIME_SYNC_MESSAGE_COUNT/2];
// calucate the mean and standard deviation
double mean = 0;
foreach (var time in m_remoteSyncTimeCache[remoteID])
{
mean += time;
}
mean /= TIME_SYNC_MESSAGE_COUNT;
double std_dev = 0;
foreach (var time in m_remoteSyncTimeCache[remoteID])
{
std_dev += (mean-time)*(mean-time);
}
std_dev = Math.Sqrt(std_dev)/TIME_SYNC_MESSAGE_COUNT;
// time delta is the mean of the values less than 1 standard deviation from the median
mean = 0;
int meanCount = 0;
foreach (var time in m_remoteSyncTimeCache[remoteID])
{
if (Math.Abs(time-median) < std_dev)
{
mean += time;
meanCount++;
}
}
mean /= meanCount;
Debug.LogFormat("Time offset to {0} is {1}", remoteID, mean);
m_remoteSyncTimeCache.Remove(remoteID);
m_remoteSentTimeCache.Remove(remoteID);
m_remotePlayers[remoteID].remoteTimeOffset = (float)mean;
// now that times are synchronized, lets try to coordinate the
// start time for the match
OfferMatchStartTime();
}
}
float ShiftRemoteTime(ulong remoteID, float remoteTime)
{
if (m_remotePlayers.ContainsKey(remoteID))
{
return remoteTime + m_remotePlayers[remoteID].remoteTimeOffset;
}
else
{
return remoteTime;
}
}
#endregion
#region Match Start Coordination
// Since all the clients will calculate a slightly different start time, this
// message tries to coordinate the match start time to be the lastest of all
// the clients in the match.
// Delegate to coordiate match start times - the return value is our start time
// and the argument is the remote start time, or 0 if that hasn't been given yet.
public delegate float StartTimeOffer(float remoteTime);
public StartTimeOffer StartTimeOfferCallback
{
private get { return m_startTimeOfferCallback; }
set { m_startTimeOfferCallback = value; }
}
void OfferMatchStartTime()
{
byte[] buf = new byte[START_TIME_MESSAGE_SIZE];
buf[0] = START_TIME_MESSAGE;
int offset = 1;
PackFloat(StartTimeOfferCallback(0), buf, ref offset);
foreach (var remoteID in m_remotePlayers.Keys)
{
if (m_remotePlayers [remoteID].state == PeerConnectionState.Connected)
{
Net.SendPacket (remoteID, buf, SendPolicy.Reliable);
}
}
}
void ReceiveMatchStartTimeOffer(ulong remoteID, byte[] msg)
{
int offset = 1;
float remoteTime = UnpackTime(remoteID, msg, ref offset);
StartTimeOfferCallback(remoteTime);
}
#endregion
#region Backboard Transforms
public void SendBackboardUpdate(float time, Vector3 pos, Vector3 moveDir, Vector3 nextMoveDir)
{
byte[] buf = new byte[BACKBOARD_UPDATE_MESSAGE_SIZE];
buf[0] = BACKBOARD_UPDATE_MESSAGE;
int offset = 1;
PackFloat(time, buf, ref offset);
PackVector3(pos, buf, ref offset);
PackVector3(moveDir, buf, ref offset);
PackVector3(nextMoveDir, buf, ref offset);
foreach (KeyValuePair<ulong,RemotePlayerData> player in m_remotePlayers)
{
if (player.Value.state == PeerConnectionState.Connected)
{
Net.SendPacket(player.Key, buf, SendPolicy.Reliable);
}
}
}
void ReceiveBackboardUpdate(ulong remoteID, byte[] msg)
{
int offset = 1;
float remoteTime = UnpackTime(remoteID, msg, ref offset);
Vector3 pos = UnpackVector3(msg, ref offset);
Vector3 moveDir = UnpackVector3(msg, ref offset);
Vector3 nextMoveDir = UnpackVector3(msg, ref offset);
var goal = m_remotePlayers [remoteID].player.Goal;
goal.RemoteBackboardUpdate(remoteTime, pos, moveDir, nextMoveDir);
}
#endregion
#region Ball Tansforms
public void AddNetworkBall(GameObject ball)
{
m_localBalls[ball.GetInstanceID()] = ball.AddComponent<P2PNetworkBall>();
}
public void RemoveNetworkBall(GameObject ball)
{
m_localBalls.Remove(ball.GetInstanceID());
}
void SendLocalBallTransforms()
{
m_timeForNextBallUpdate = Time.time + LOCAL_BALLS_UPDATE_DELAY;
int msgSize = 1 + 4 + (m_localBalls.Count * (1 + 4 + 12 + 12));
byte[] sendBuffer = new byte[msgSize];
sendBuffer[0] = LOCAL_BALLS_UPDATE_MESSAGE;
int offset = 1;
PackFloat(Time.realtimeSinceStartup, sendBuffer, ref offset);
foreach (var ball in m_localBalls.Values)
{
PackBool(ball.IsHeld(), sendBuffer, ref offset);
PackInt32(ball.gameObject.GetInstanceID(), sendBuffer, ref offset);
PackVector3(ball.transform.localPosition, sendBuffer, ref offset);
PackVector3(ball.velocity, sendBuffer, ref offset);
}
foreach (KeyValuePair<ulong, RemotePlayerData> player in m_remotePlayers)
{
if (player.Value.state == PeerConnectionState.Connected)
{
Net.SendPacket(player.Key, sendBuffer, SendPolicy.Unreliable);
}
}
}
void ReceiveBallTransforms(ulong remoteID, byte[] msg, ulong msgLength)
{
int offset = 1;
float remoteTime = UnpackTime(remoteID, msg, ref offset);
// because we're using unreliable networking the packets could come out of order
// and the best thing to do is just ignore old packets because the data isn't
// very useful anyway
if (remoteTime < m_remotePlayers[remoteID].lastReceivedBallsTime)
return;
m_remotePlayers[remoteID].lastReceivedBallsTime = remoteTime;
// loop over all ball updates in the message
while (offset != (int)msgLength)
{
bool isHeld = UnpackBool(msg, ref offset);
int instanceID = UnpackInt32(msg, ref offset);
Vector3 pos = UnpackVector3(msg, ref offset);
Vector3 vel = UnpackVector3(msg, ref offset);
if (!m_remotePlayers[remoteID].activeBalls.ContainsKey(instanceID))
{
var newball = m_remotePlayers[remoteID].player.CreateBall().AddComponent<P2PNetworkBall>();
newball.transform.SetParent(m_remotePlayers[remoteID].player.transform.parent);
m_remotePlayers[remoteID].activeBalls[instanceID] = newball;
}
var ball = m_remotePlayers[remoteID].activeBalls[instanceID];
if (ball)
{
ball.ProcessRemoteUpdate(remoteTime, isHeld, pos, vel);
}
}
}
#endregion
#region Score Updates
public void SendScoreUpdate(uint score)
{
byte[] buf = new byte[SCORE_UPDATE_MESSAGE_SIZE];
buf[0] = SCORE_UPDATE_MESSAGE;
int offset = 1;
PackUint32(score, buf, ref offset);
foreach (KeyValuePair<ulong, RemotePlayerData> player in m_remotePlayers)
{
if (player.Value.state == PeerConnectionState.Connected)
{
Net.SendPacket(player.Key, buf, SendPolicy.Reliable);
}
}
}
void ReceiveScoredUpdate(ulong remoteID, byte[] msg)
{
int offset = 1;
uint score = UnpackUint32(msg, ref offset);
m_remotePlayers[remoteID].player.ReceiveRemoteScore(score);
}
#endregion
#region Serialization
// This region contains basic data serialization logic. This sample doesn't warrant
// much optimization, but the opportunites are ripe those interested in the topic.
void PackVector3(Vector3 vec, byte[] buf, ref int offset)
{
PackFloat(vec.x, buf, ref offset);
PackFloat(vec.y, buf, ref offset);
PackFloat(vec.z, buf, ref offset);
}
Vector3 UnpackVector3(byte[] buf, ref int offset)
{
Vector3 vec;
vec.x = UnpackFloat(buf, ref offset);
vec.y = UnpackFloat(buf, ref offset);
vec.z = UnpackFloat(buf, ref offset);
return vec;
}
void PackQuaternion(Quaternion quat, byte[] buf, ref int offset)
{
PackFloat(quat.x, buf, ref offset);
PackFloat(quat.y, buf, ref offset);
PackFloat(quat.z, buf, ref offset);
PackFloat(quat.w, buf, ref offset);
}
void PackFloat(float value, byte[] buf, ref int offset)
{
Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buf, offset, 4);
offset = offset + 4;
}
float UnpackFloat(byte[] buf, ref int offset)
{
float value = BitConverter.ToSingle(buf, offset);
offset += 4;
return value;
}
float UnpackTime(ulong remoteID, byte[] buf, ref int offset)
{
return ShiftRemoteTime(remoteID, UnpackFloat(buf, ref offset));
}
void PackInt32(int value, byte[] buf, ref int offset)
{
Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buf, offset, 4);
offset = offset + 4;
}
int UnpackInt32(byte[] buf, ref int offset)
{
int value = BitConverter.ToInt32(buf, offset);
offset += 4;
return value;
}
void PackUint32(uint value, byte[] buf, ref int offset)
{
Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buf, offset, 4);
offset = offset + 4;
}
uint UnpackUint32(byte[] buf, ref int offset)
{
uint value = BitConverter.ToUInt32(buf, offset);
offset += 4;
return value;
}
void PackBool(bool value, byte[] buf, ref int offset)
{
buf[offset++] = (byte)(value ? 1 : 0);
}
bool UnpackBool(byte[] buf, ref int offset)
{
return buf[offset++] != 0;;
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f418f541d32d24e7aad85c24970462d1
timeCreated: 1475634295
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,87 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using System.Collections;
// This component handles network coordination for moving balls.
// Synchronizing moving objects that are under the influence of physics
// and other forces is somewhat of an art and this example only scratches
// the surface. Ultimately how you synchronize will depend on the requirements
// of your application and its tolerance for users seeing slightly different
// versions of the simulation.
public class P2PNetworkBall : MonoBehaviour
{
// the last time this ball locally collided with something
private float lastCollisionTime;
// cached reference to the GameObject's Rigidbody component
private Rigidbody rigidBody;
void Awake()
{
rigidBody = gameObject.GetComponent<Rigidbody>();
}
public Vector3 velocity
{
get { return rigidBody.velocity; }
}
public bool IsHeld()
{
return !rigidBody.useGravity;
}
public void ProcessRemoteUpdate(float remoteTime, bool isHeld, Vector3 pos, Vector3 vel)
{
if (isHeld)
{
transform.localPosition = pos;
}
// if we've collided since the update was sent, our state is going to be more accurate so
// it's better to ignore the update
else if (lastCollisionTime < remoteTime)
{
// To correct the position this sample directly moves the ball.
// Another approach would be to gradually lerp the ball there during
// FixedUpdate. However, that approach aggravates any errors that
// come from estimatePosition and estimateVelocity so the lerp
// should be done over few timesteps.
float deltaT = Time.realtimeSinceStartup - remoteTime;
transform.localPosition = estimatePosition(pos, vel, deltaT);
rigidBody.velocity = estimateVelocity(vel, deltaT);
// if the ball is transitioning from held to ballistic, we need to
// update the RigidBody parameters
if (IsHeld())
{
rigidBody.useGravity = true;
rigidBody.detectCollisions = true;
}
}
}
// Estimates the new position assuming simple ballistic motion.
private Vector3 estimatePosition(Vector3 startPosition, Vector3 startVelocty, float time)
{
return startPosition + startVelocty * time + 0.5f * Physics.gravity * time * time;
}
// Estimates the new velocity assuming ballistic motion and drag.
private Vector3 estimateVelocity(Vector3 startVelocity, float time)
{
return startVelocity + Physics.gravity * time * Mathf.Clamp01 (1 - rigidBody.drag * time);
}
void OnCollisionEnter(Collision collision)
{
lastCollisionTime = Time.realtimeSinceStartup;
}
void OnDestroy()
{
PlatformManager.P2P.RemoveNetworkBall(gameObject);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b4360c472d4f2ab498faa9c614ac7c80
timeCreated: 1476304390
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,65 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using System.Collections;
// This component handles network coordination for the moving backboard.
// Although there is randomness in the next direction, the movement is
// otherwise completely predictable, much like a moving platform or door,
// thus we only need to send occasional updates. If the position of the
// backboard is not correct, the GoalMover will gradually nudge it in the
// correct direction until the local and remote motion is synchronized.
public class P2PNetworkGoal : MonoBehaviour
{
// cached reference to the associated GoalMover component
private GoalMover m_goal;
// the last move direction we sent to remote clients
private Vector3 m_lastSentMoveDirection;
private bool m_sendUpdates;
public bool SendUpdates
{
set { m_sendUpdates = value; }
}
void Awake()
{
m_goal = gameObject.GetComponent<GoalMover>();
}
void FixedUpdate ()
{
// since the backboard's movement is deterministic, we don't need to send position
// updates constantly, just when the move direction changes
if (m_sendUpdates && m_goal.MoveDirection != m_lastSentMoveDirection)
{
SendBackboardUpdate();
}
}
public void SendBackboardUpdate()
{
m_lastSentMoveDirection = m_goal.MoveDirection;
float time = Time.realtimeSinceStartup;
PlatformManager.P2P.SendBackboardUpdate(
time, transform.localPosition,
m_goal.MoveDirection, m_goal.NextMoveDirection);
}
// message from the remote player with new transforms
public void RemoteBackboardUpdate(float remoteTime, Vector3 pos, Vector3 moveDir, Vector3 nextMoveDir)
{
// interpolate the position forward since the backboard would have moved over
// the time it took to send the message
float time = Time.realtimeSinceStartup;
float numMissedSteps = (time - remoteTime) / Time.fixedDeltaTime;
m_goal.ExpectedPosition = pos + (Mathf.Round(numMissedSteps) * moveDir);
m_goal.MoveDirection = moveDir;
m_goal.NextMoveDirection = nextMoveDir;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1aef3541237f243e4856d8913668f9b8
timeCreated: 1476575180
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,195 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using Oculus.Platform;
using Oculus.Platform.Models;
public class PlatformManager : MonoBehaviour
{
private static PlatformManager s_instance;
private MatchmakingManager m_matchmaking;
private P2PManager m_p2p;
private LeaderboardManager m_leaderboards;
private AchievementsManager m_achievements;
private State m_currentState;
// my Application-scoped Oculus ID
private ulong m_myID;
// my Oculus user name
private string m_myOculusID;
void Update()
{
m_p2p.UpdateNetwork();
m_leaderboards.CheckForUpdates();
}
#region Initialization and Shutdown
void Awake()
{
// make sure only one instance of this manager ever exists
if (s_instance != null)
{
Destroy(gameObject);
return;
}
s_instance = this;
DontDestroyOnLoad(gameObject);
Core.Initialize();
m_matchmaking = new MatchmakingManager();
m_p2p = new P2PManager();
m_leaderboards = new LeaderboardManager();
m_achievements = new AchievementsManager();
}
void Start()
{
// First thing we should do is perform an entitlement check to make sure
// we successfully connected to the Oculus Platform Service.
Entitlements.IsUserEntitledToApplication().OnComplete(IsEntitledCallback);
}
void IsEntitledCallback(Message msg)
{
if (msg.IsError)
{
TerminateWithError(msg);
return;
}
// Next get the identity of the user that launched the Application.
Users.GetLoggedInUser().OnComplete(GetLoggedInUserCallback);
}
void GetLoggedInUserCallback(Message<User> msg)
{
if (msg.IsError)
{
TerminateWithError(msg);
return;
}
m_myID = msg.Data.ID;
m_myOculusID = msg.Data.OculusID;
TransitionToState(State.WAITING_TO_PRACTICE_OR_MATCHMAKE);
Achievements.CheckForAchievmentUpdates();
}
// In this example, for most errors, we terminate the Application. A full App would do
// something more graceful.
public static void TerminateWithError(Message msg)
{
Debug.Log("Error: " + msg.GetError().Message);
UnityEngine.Application.Quit();
}
public void QuitButtonPressed()
{
UnityEngine.Application.Quit();
}
void OnApplicationQuit()
{
// be a good matchmaking citizen and leave any queue immediately
Matchmaking.LeaveQueue();
}
#endregion
#region Properties
public static MatchmakingManager Matchmaking
{
get { return s_instance.m_matchmaking; }
}
public static P2PManager P2P
{
get { return s_instance.m_p2p; }
}
public static LeaderboardManager Leaderboards
{
get { return s_instance.m_leaderboards; }
}
public static AchievementsManager Achievements
{
get { return s_instance.m_achievements; }
}
public static State CurrentState
{
get { return s_instance.m_currentState; }
}
public static ulong MyID
{
get
{
if (s_instance != null)
{
return s_instance.m_myID;
}
else
{
return 0;
}
}
}
public static string MyOculusID
{
get
{
if (s_instance != null && s_instance.m_myOculusID != null)
{
return s_instance.m_myOculusID;
}
else
{
return string.Empty;
}
}
}
#endregion
#region State Management
public enum State
{
// loading platform library, checking application entitlement,
// getting the local user info
INITIALIZING,
// waiting on the user to join a matchmaking queue or play a practice game
WAITING_TO_PRACTICE_OR_MATCHMAKE,
// waiting for the match to start or viewing results
MATCH_TRANSITION,
// actively playing a practice match
PLAYING_A_LOCAL_MATCH,
// actively playing an online match
PLAYING_A_NETWORKED_MATCH,
};
public static void TransitionToState(State newState)
{
if (s_instance && s_instance.m_currentState != newState)
{
s_instance.m_currentState = newState;
}
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 65808a6a27b5f4917a2b9a171e240753
timeCreated: 1475341418
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,133 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
// The base Player component manages the balls that are in play. Besides spawning new balls,
// old balls are destroyed when too many are around or the Player object itself is destroyed.
public abstract class Player : MonoBehaviour {
// maximum number of balls allowed at a time
public const uint MAX_BALLS = 6;
// the initial force to impart when shooting a ball
private const float INITIAL_FORCE = 870f;
// delay time before a new ball will spawn.
private const float RESPAWN_SECONDS = 2.0f;
// current score for the player
private uint m_score;
// cached reference to the Text component to render the score
private Text m_scoreUI;
// prefab for the GameObject representing a ball
private GameObject m_ballPrefab;
// gameobject for the position and orientation of where the ball will be shot
private BallEjector m_ballEjector;
// queue of active balls for the player to make sure too many arent in play
private Queue<GameObject> m_balls = new Queue<GameObject>();
// reference to a ball that hasn't been shot yet and is tied to the camera
private GameObject m_heldBall;
// when to spawn a new ball
private float m_nextSpawnTime;
#region Properties
public virtual uint Score
{
get { return m_score; }
set
{
m_score = value;
if (m_scoreUI)
{
m_scoreUI.text = m_score.ToString();
}
}
}
public GameObject BallPrefab
{
set { m_ballPrefab = value; }
}
protected bool HasBall
{
get { return m_heldBall != null; }
}
#endregion
void Start()
{
m_ballEjector = transform.GetComponentInChildren<BallEjector>();
m_scoreUI = transform.parent.GetComponentInChildren<Text>();
m_scoreUI.text = "0";
}
public GameObject CreateBall()
{
if (m_balls.Count >= MAX_BALLS)
{
Destroy(m_balls.Dequeue());
}
var ball = Instantiate(m_ballPrefab);
m_balls.Enqueue(ball);
ball.transform.position = m_ballEjector.transform.position;
ball.transform.SetParent(m_ballEjector.transform, true);
ball.GetComponent<Rigidbody>().useGravity = false;
ball.GetComponent<Rigidbody>().detectCollisions = false;
ball.GetComponent<DetectBasket>().Player = this;
return ball;
}
protected GameObject CheckSpawnBall()
{
switch (PlatformManager.CurrentState)
{
case PlatformManager.State.WAITING_TO_PRACTICE_OR_MATCHMAKE:
case PlatformManager.State.PLAYING_A_LOCAL_MATCH:
case PlatformManager.State.PLAYING_A_NETWORKED_MATCH:
if (Time.time >= m_nextSpawnTime && !HasBall)
{
m_heldBall = CreateBall();
return m_heldBall;
}
break;
}
return null;
}
protected GameObject ShootBall()
{
GameObject ball = m_heldBall;
m_heldBall = null;
ball.GetComponent<Rigidbody>().useGravity = true;
ball.GetComponent<Rigidbody>().detectCollisions = true;
ball.GetComponent<Rigidbody>().AddForce(m_ballEjector.transform.forward * INITIAL_FORCE, ForceMode.Acceleration);
ball.transform.SetParent(transform.parent, true);
m_nextSpawnTime = Time.time + RESPAWN_SECONDS;
return ball;
}
void OnDestroy()
{
foreach (var ball in m_balls)
{
Destroy(ball);
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9f31217f54e40894f84c58432a8f7e65
timeCreated: 1475264829
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,64 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using UnityEngine.UI;
using Oculus.Platform.Models;
public class PlayerArea : MonoBehaviour
{
// the prefab for the ball that players will shoot
[SerializeField] private GameObject m_ballPrefab = null;
// cached gameobject that where the player camera will move to
private GameObject m_playerHead;
// cached Text component where we'll render the player's name
private Text m_nameText;
// cached component used to align the backboard movement between devices
private P2PNetworkGoal m_p2pGoal;
public Player Player
{
get { return m_playerHead.GetComponent<Player>(); }
}
public Text NameText
{
get { return m_nameText; }
}
void Awake()
{
m_playerHead = gameObject.transform.Find("Player Head").gameObject;
m_nameText = gameObject.GetComponentsInChildren<Text>()[1];
m_p2pGoal = gameObject.GetComponentInChildren<P2PNetworkGoal> ();
}
public T SetupForPlayer<T>(string name) where T : Player
{
var oldplayer = m_playerHead.GetComponent<Player>();
if (oldplayer) Destroy(oldplayer);
var player = m_playerHead.AddComponent<T>();
player.BallPrefab = m_ballPrefab;
m_nameText.text = name;
if (player is RemotePlayer)
{
(player as RemotePlayer).Goal = m_p2pGoal;
m_p2pGoal.SendUpdates = false;
}
else if (player is LocalPlayer)
{
m_p2pGoal.SendUpdates = true;
}
else
{
m_p2pGoal.SendUpdates = false;
}
return player;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4bf1a3ead6c1aa341873bdfda4366001
timeCreated: 1475265143
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,42 @@
namespace Oculus.Platform.Samples.VrHoops
{
using Oculus.Platform.Models;
public class RemotePlayer : Player
{
private User m_user;
private P2PNetworkGoal m_goal;
public User User
{
set { m_user = value; }
}
public ulong ID
{
get { return m_user.ID; }
}
public P2PNetworkGoal Goal
{
get { return m_goal; }
set { m_goal = value; }
}
public override uint Score
{
set
{
// For now we ignore the score determined from locally scoring backets.
// To get an indication of how close the physics simulations were between devices,
// or whether the remote player was cheating, an estimate of the score could be
// kept and compared against what the remote player was sending us.
}
}
public void ReceiveRemoteScore(uint score)
{
base.Score = score;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 125c22697d9e63447888218eccbc7005
timeCreated: 1475264820
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
namespace Oculus.Platform.Samples.VrHoops
{
using UnityEngine;
using UnityEngine.UI;
// Helper class to attach to the main camera that raycasts where the
// user is looking to select/deselect Buttons.
public class VREyeRaycaster : MonoBehaviour
{
[SerializeField] private UnityEngine.EventSystems.EventSystem m_eventSystem = null;
private Button m_currentButton;
void Update ()
{
RaycastHit hit;
Button button = null;
// do a forward raycast to see if we hit a Button
if (Physics.Raycast(transform.position, transform.forward, out hit, 50f))
{
button = hit.collider.GetComponent<Button>();
}
if (button != null)
{
if (m_currentButton != button)
{
m_currentButton = button;
m_currentButton.Select();
}
}
else if (m_currentButton != null)
{
m_currentButton = null;
if (m_eventSystem != null)
{
m_eventSystem.SetSelectedGameObject(null);
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b02d3b7f6a849724695a1255745c4fa7
timeCreated: 1475533243
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: