1
0
forked from cgvr/DeltaVR

Initial Commit

This commit is contained in:
Toomas Tamm
2020-11-28 16:54:41 +02:00
parent 97292ee26e
commit ea967135f2
4217 changed files with 2945663 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
namespace Oculus.Platform.Samples.VrBoardGame
{
using UnityEngine;
using System.Collections;
// This behaviour is attached to GameObjects whose collision mesh
// describes a specific position on the GameBoard. The collision
// mesh doesn't need to fully cover the board position, but enough
// for eye raycasts to detect that the user is looking there.
public class BoardPosition : MonoBehaviour {
[SerializeField] [Range(0,2)] public int x = 0;
[SerializeField] [Range(0,2)] public int y = 0;
}
}

View File

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

View File

@@ -0,0 +1,99 @@
namespace Oculus.Platform.Samples.VrBoardGame
{
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
// This is a helper class for selecting objects that the user is looking at.
// It will select UI objects that have an attach 3d collision volume and helps
// the GameController locate GamePieces and GamePositions.
public class EyeCamera : MonoBehaviour
{
// the EventSystem used by the UI elements
[SerializeField] private EventSystem m_eventSystem = null;
// the GameController to notify
[SerializeField] private GameController m_gameController = null;
// a tine ball in the distance to debug where the user is looking
[SerializeField] private SphereCollider m_gazeTracker = null;
// the current Button, if any, being looked at
private Button m_currentButton;
// the current GamePiece, if any, being looked at
private GamePiece m_currentPiece;
// the current BoardPosition, if any, being looked at
private BoardPosition m_boardPosition;
void Update()
{
RaycastHit hit;
Button button = null;
GamePiece piece = null;
BoardPosition pos = null;
// do a forward raycast to see if we hit a selectable object
bool hitSomething = Physics.Raycast(transform.position, transform.forward, out hit, 50f);
if (hitSomething) {
button = hit.collider.GetComponent<Button>();
piece = hit.collider.GetComponent<GamePiece>();
pos = hit.collider.GetComponent<BoardPosition>();
}
if (m_currentButton != button)
{
if (m_eventSystem != null)
{
m_eventSystem.SetSelectedGameObject(null);
}
m_currentButton = button;
if (m_currentButton != null)
{
m_currentButton.Select();
}
}
if (m_currentPiece != piece)
{
if (m_currentPiece != null)
{
m_gameController.StoppedLookingAtPiece();
}
m_currentPiece = piece;
if (m_currentPiece != null)
{
m_gameController.StartedLookingAtPiece(m_currentPiece);
}
}
if (m_boardPosition != pos)
{
m_boardPosition = pos;
if (m_boardPosition != null)
{
m_gameController.StartedLookingAtPosition(m_boardPosition);
}
}
// clear the potential move if they gaze off the board
if (hit.collider == m_gazeTracker)
{
m_gameController.ClearProposedMove();
}
// moves the camera with the mouse - very useful for debugging in to 2D mode.
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: 96a29a99957531246921ced0fac365ab
timeCreated: 1480201871
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,188 @@
namespace Oculus.Platform.Samples.VrBoardGame
{
using System;
using UnityEngine;
//
// This script describes the game board along with the game pieces that
// are in play. The rules for the game board are:
// 1) Player can place a normal GamePiece on any empty BoardPosition
// 2) Player can place a power GamePiece on top of a normal piece
// 3) The board is full when all positions have a normal piece
// Player score is calculated as:
// 1) +10 points for each normal piece on the board
// 2) +10 points for each normal piece with 1 square of one of their power pieces
// 3) -10 points for each opponent normal piece within 1 square of their power pieces
//
public class GameBoard : MonoBehaviour
{
public const int LENGTH_X = 3;
public const int LENGTH_Y = 3;
public const int MAX_PLAYERS = 2;
// the placed-piece color for each player
[SerializeField] private Color[] m_playerColors = new Color[MAX_PLAYERS];
// color for pice the player is considering moving to
[SerializeField] private Color m_proposedMoveColor = Color.white;
// the player scores that are recalcuated after a pice is placed
private int[] m_scores = new int[MAX_PLAYERS];
// GameObjects that define each of the allowed piece positions
[SerializeField] private BoardPosition[] m_positions = new BoardPosition[9];
private struct PositionInfo
{
public GameObject piece;
public int pieceOwner;
public int powerPieceOwner;
}
// pieces in play for the current game
private readonly PositionInfo[,] m_pieces = new PositionInfo[LENGTH_X, LENGTH_Y];
// removes all game pieces from the board
public void Reset()
{
for (int x = 0; x < LENGTH_X; x++)
{
for (int y = 0; y < LENGTH_Y; y++)
{
if (m_pieces[x,y].piece != null)
{
Destroy(m_pieces[x,y].piece);
m_pieces[x,y].piece = null;
m_pieces[x,y].pieceOwner = -1;
m_pieces[x,y].powerPieceOwner = -1;
}
}
}
}
#region Board status
// returns true if all the board positions have a piece in them
public bool IsFull()
{
for (int x = 0; x < LENGTH_X; x++)
{
for (int y = 0; y < LENGTH_Y; y++)
{
if (m_pieces[x,y].piece == null)
{
return false;
}
}
}
return true;
}
public bool CanPlayerMoveToPostion(int x, int y)
{
return m_pieces[x,y].piece == null;
}
public bool CanPlayerPowerUpPosition(int x, int y)
{
return m_pieces[x,y].piece != null;
}
#endregion
#region creating game pieces
public void AddPiece(int player, GameObject prefab, int x, int y)
{
var pos = m_positions[x * LENGTH_Y + y];
var piece = Create(prefab, pos.gameObject, pos, Vector3.zero);
piece.GetComponent<Renderer>().material.color = m_playerColors[player];
m_pieces[x,y].piece = piece.gameObject;
m_pieces[x,y].pieceOwner = player;
m_pieces[x,y].powerPieceOwner = -1;
UpdateScores();
}
public GamePiece AddProposedPiece(GameObject prefab, BoardPosition pos)
{
var piece = Create(prefab, pos.gameObject, pos, Vector3.zero);
piece.GetComponent<Renderer>().material.color = m_proposedMoveColor;
return piece;
}
public void AddPowerPiece(int player, GameObject prefab, int x, int y)
{
var piece = Create(prefab, m_pieces[x,y].piece, m_positions[x*LENGTH_Y+y], .2f*Vector3.up);
piece.GetComponent<Renderer>().material.color = m_playerColors[player];
m_pieces[x,y].powerPieceOwner = player;
UpdateScores();
}
public GamePiece AddProposedPowerPiece(GameObject prefab, BoardPosition pos)
{
var piece = Create(prefab, m_pieces[pos.x, pos.y].piece, pos, .2f*Vector3.up);
piece.GetComponent<Renderer>().material.color = m_proposedMoveColor;
return piece;
}
private GamePiece Create(GameObject prefab, GameObject parent, BoardPosition pos, Vector3 off)
{
var go = Instantiate(prefab, parent.transform) as GameObject;
go.transform.position = parent.transform.position + off;
go.GetComponent<GamePiece>().Position = pos;
return go.GetComponent<GamePiece>();
}
#endregion
#region scores
public int GetPlayerScore(int player)
{
return m_scores[player];
}
private void UpdateScores()
{
for (int i = 0; i < MAX_PLAYERS; i++)
{
m_scores[i] = 0;
}
for (int x = 0; x < LENGTH_X; x++)
{
for (int y = 0; y < LENGTH_Y; y++)
{
if (m_pieces[x,y].piece != null)
{
// for each piece on the board, the player gets 10 points
m_scores[m_pieces[x,y].pieceOwner] += 10;
// for each power piece, the player gains or loses 10 points
// based on the ownership of nearby pieces
if (m_pieces[x,y].powerPieceOwner >= 0)
{
for (int px = x-1; px <= x+1; px++)
{
for (int py = y-1; py <= y+1; py++)
{
if (px >= 0 && py >= 0 && px < LENGTH_X && py < LENGTH_Y)
{
var powerup =
m_pieces[x,y].pieceOwner == m_pieces[x,y].powerPieceOwner ?
+10 : -10;
m_scores[m_pieces[x, y].powerPieceOwner] += powerup;
}
}
}
}
}
}
}
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,405 @@
namespace Oculus.Platform.Samples.VrBoardGame
{
using UnityEngine;
using UnityEngine.UI;
// This is the primary class that implements the game logic.
public class GameController : MonoBehaviour
{
// instance of the object interfacing with the matchmaking service
[SerializeField] private MatchmakingManager m_matchmaking = null;
[SerializeField] private GameBoard m_board = null;
[SerializeField] private GamePiece m_pieceA = null;
[SerializeField] private GamePiece m_pieceB = null;
[SerializeField] private GamePiece m_powerPiece = null;
// colors for the various states of the selectable games pieces
[SerializeField] private Color m_unusableColor = Color.white;
[SerializeField] private Color m_unselectedColor = Color.white;
[SerializeField] private Color m_selectedColor = Color.white;
[SerializeField] private Color m_highlightedColor = Color.white;
[SerializeField] private Text m_ballCountText = null;
[SerializeField] private Text m_player0Text = null;
[SerializeField] private Text m_player1Text = null;
private enum GameState {
None,
PracticingMyTurn, PracticingAiTurn,
OnlineMatchMyTurn, OnlineMatchRemoteTurn
}
private GameState m_state;
// the game piece the player is currently looking at
private GamePiece m_interestedPiece;
// the piece the player selected with the Fire button
private GamePiece m_selectedPiece;
// the piece that would be placed if the player pressed the Fire button
private GamePiece m_proposedPiece;
// how many IAP power-balls the user has
private uint m_powerBallcount;
// the name of the current opponent
private string m_opponentName;
void Start()
{
TransitionToState(GameState.None);
UpdateScores();
}
void Update()
{
PerFrameStateUpdate();
}
#region Game State
private void TransitionToState(GameState state)
{
m_state = state;
UpdateGamePieceColors();
}
private void TransitionToNextState()
{
if (!m_board.IsFull())
{
switch (m_state)
{
case GameState.PracticingAiTurn:
TransitionToState(GameState.PracticingMyTurn);
break;
case GameState.PracticingMyTurn:
TransitionToState(GameState.PracticingAiTurn);
break;
case GameState.OnlineMatchRemoteTurn:
TransitionToState(GameState.OnlineMatchMyTurn);
break;
case GameState.OnlineMatchMyTurn:
TransitionToState(GameState.OnlineMatchRemoteTurn);
break;
}
}
else
{
switch (m_state)
{
case GameState.OnlineMatchRemoteTurn:
case GameState.OnlineMatchMyTurn:
m_matchmaking.EndMatch(m_board.GetPlayerScore(0), m_board.GetPlayerScore(1));
break;
}
TransitionToState(GameState.None);
}
}
private void PerFrameStateUpdate()
{
switch (m_state)
{
case GameState.PracticingAiTurn:
// don't move immediately to give the AI time to 'think'
if (Random.Range(1, 100) < 3)
{
MakeAIMove(1);
}
break;
case GameState.PracticingMyTurn:
case GameState.OnlineMatchMyTurn:
if (Input.GetButton("Fire1"))
{
TrySelectPiece();
TryPlacePiece();
}
break;
}
}
#endregion
#region Practicing with an AI Player
public void PracticeButtonPressed()
{
m_opponentName = "* AI *";
switch (m_state)
{
case GameState.OnlineMatchMyTurn:
case GameState.OnlineMatchRemoteTurn:
m_matchmaking.EndMatch(m_board.GetPlayerScore(0), m_board.GetPlayerScore(1));
break;
}
m_board.Reset();
// randomly decised whether the player or AI goes first
if (Random.Range(0, 2) == 1)
{
TransitionToState(GameState.PracticingMyTurn);
}
else
{
TransitionToState(GameState.PracticingAiTurn);
}
UpdateScores();
}
private void MakeAIMove(int player)
{
bool moved = false;
// pick a random search start position
int rx = Random.Range(0, GameBoard.LENGTH_X - 1);
int ry = Random.Range(0, GameBoard.LENGTH_Y - 1);
// from (rx,ry) search of an available move
for (int i = 0; i < GameBoard.LENGTH_X && !moved; i++)
{
for (int j = 0; j < GameBoard.LENGTH_Y && !moved; j++)
{
int x = (rx + i) % GameBoard.LENGTH_X;
int y = (ry + j) % GameBoard.LENGTH_Y;
// first try to place a piece on the current position
if (m_board.CanPlayerMoveToPostion(x, y))
{
GamePiece p = Random.Range(0, 2) == 0 ? m_pieceA : m_pieceB;
m_board.AddPiece(player, p.Prefab, x, y);
moved = true;
}
// a random percentage of the time, try to powerup this position
else if (m_board.CanPlayerPowerUpPosition(x, y) && Random.Range(0, 8) < 2)
{
m_board.AddPowerPiece(player, m_powerPiece.Prefab, x, y);
moved = true;
}
}
}
if (moved)
{
UpdateScores();
TransitionToNextState();
}
}
#endregion
#region Playing Online Match
// called from the MatchmakingManager was a successly online match is made
public void StartOnlineMatch (string opponentName, bool localUserGoesFirst)
{
m_board.Reset();
m_opponentName = opponentName;
if (localUserGoesFirst)
{
TransitionToState(GameState.OnlineMatchMyTurn);
}
else
{
TransitionToState(GameState.OnlineMatchRemoteTurn);
}
UpdateScores();
}
// called from the Matchmaking Manager when the remote users their next move
public void MakeRemoteMove(GamePiece.Piece piece, int x, int y)
{
GameObject prefab = m_pieceA.PrefabFor(piece);
if (piece == GamePiece.Piece.PowerBall)
{
m_board.AddPowerPiece(1, prefab, x, y);
}
else
{
m_board.AddPiece(1, prefab, x, y);
}
UpdateScores();
}
// called from the MatchmakingManager when the local user becomes the room
// owner and thus it's safe for the local user to make their move
public void MarkRemoteTurnComplete()
{
if (m_state == GameState.OnlineMatchRemoteTurn)
{
TransitionToNextState();
}
}
// the match ended from a player leaving before the board was complete
public void RemoteMatchEnded()
{
m_matchmaking.EndMatch(m_board.GetPlayerScore(0), m_board.GetPlayerScore(1));
}
#endregion
#region Selecting and Placing a Game Place
public void StartedLookingAtPiece(GamePiece piece)
{
m_interestedPiece = piece;
UpdateGamePieceColors();
}
public void StoppedLookingAtPiece()
{
m_interestedPiece = null;
UpdateGamePieceColors();
}
// This method is used to display an example piece where the player is looking
// so they know what to expect when they press the Fire button.
public void StartedLookingAtPosition(BoardPosition position)
{
if (m_state != GameState.OnlineMatchMyTurn && m_state != GameState.PracticingMyTurn)
return;
GamePiece newPiece = null;
if ((m_selectedPiece == m_pieceA || m_selectedPiece == m_pieceB) &&
m_board.CanPlayerMoveToPostion(position.x, position.y))
{
newPiece = m_board.AddProposedPiece(m_selectedPiece.Prefab, position);
}
else if (m_selectedPiece == m_powerPiece &&
m_board.CanPlayerPowerUpPosition(position.x, position.y))
{
newPiece = m_board.AddProposedPowerPiece(m_selectedPiece.Prefab, position);
}
if (newPiece != null)
{
if (m_proposedPiece != null)
{
Destroy(m_proposedPiece.gameObject);
}
m_proposedPiece = newPiece;
}
}
public void ClearProposedMove()
{
if (m_proposedPiece != null)
{
Destroy(m_proposedPiece.gameObject);
}
}
public void TrySelectPiece()
{
if (m_interestedPiece == m_pieceA || m_interestedPiece == m_pieceB)
{
m_selectedPiece = m_interestedPiece;
}
else if (m_interestedPiece == m_powerPiece &&
(m_powerBallcount > 0 || m_state == GameState.PracticingMyTurn))
{
m_selectedPiece = m_interestedPiece;
}
UpdateGamePieceColors();
}
public void TryPlacePiece()
{
if (m_proposedPiece == null)
return;
var position = m_proposedPiece.Position;
switch(m_proposedPiece.Type)
{
case GamePiece.Piece.A:
case GamePiece.Piece.B:
m_board.AddPiece(0, m_proposedPiece.Prefab, position.x, position.y);
break;
case GamePiece.Piece.PowerBall:
m_board.AddPowerPiece(0, m_proposedPiece.Prefab, position.x, position.y);
break;
}
Destroy(m_proposedPiece.gameObject);
if (m_state == GameState.OnlineMatchMyTurn)
{
m_matchmaking.SendLocalMove(m_proposedPiece.Type, position.x, position.y);
}
UpdateScores();
TransitionToNextState();
}
#endregion
#region UI
public void QuitButtonPressed()
{
UnityEngine.Application.Quit();
}
public void AddPowerballs(uint count)
{
m_powerBallcount += count;
m_ballCountText.text = "x" + m_powerBallcount.ToString();
}
private void UpdateScores()
{
m_player0Text.text = string.Format("{0}\n\n{1}",
PlatformManager.MyOculusID, m_board.GetPlayerScore(0));
m_player1Text.text = string.Format("{0}\n\n{1}",
m_opponentName, m_board.GetPlayerScore(1));
}
private void UpdateGamePieceColors()
{
switch (m_state)
{
case GameState.None:
case GameState.PracticingAiTurn:
case GameState.OnlineMatchRemoteTurn:
m_pieceA.GetComponent<Renderer>().material.color = m_unusableColor;
m_pieceB.GetComponent<Renderer>().material.color = m_unusableColor;
m_powerPiece.GetComponent<Renderer>().material.color = m_unusableColor;
if (m_proposedPiece != null)
{
Destroy(m_proposedPiece.gameObject);
}
break;
case GameState.PracticingMyTurn:
case GameState.OnlineMatchMyTurn:
m_pieceA.GetComponent<Renderer>().material.color = m_unselectedColor;
m_pieceB.GetComponent<Renderer>().material.color = m_unselectedColor;
m_powerPiece.GetComponent<Renderer>().material.color = m_unselectedColor;
if (m_interestedPiece == m_pieceA || m_interestedPiece == m_pieceB ||
m_interestedPiece == m_powerPiece)
{
m_interestedPiece.GetComponent<Renderer>().material.color = m_highlightedColor;
}
if (m_selectedPiece != null)
{
m_selectedPiece.GetComponent<Renderer>().material.color = m_selectedColor;
}
break;
}
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,54 @@
namespace Oculus.Platform.Samples.VrBoardGame
{
using UnityEngine;
using System.Collections;
public class GamePiece : MonoBehaviour
{
[SerializeField] private Piece m_type = Piece.A;
// Prefab for the game pieces
[SerializeField] private GameObject m_prefabA = null;
[SerializeField] private GameObject m_prefabB = null;
[SerializeField] private GameObject m_prefabPower = null;
public enum Piece { A, B, PowerBall }
private BoardPosition m_position;
public Piece Type
{
get { return m_type; }
}
public BoardPosition Position
{
get { return m_position; }
set { m_position = value; }
}
public GameObject Prefab
{
get
{
switch (m_type)
{
case Piece.A: return m_prefabA;
case Piece.B: return m_prefabB;
default: return m_prefabPower;
}
}
}
public GameObject PrefabFor(Piece p)
{
switch (p)
{
case Piece.A: return m_prefabA;
case Piece.B: return m_prefabB;
default: return m_prefabPower;
}
}
}
}

View File

@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 4c243c46c5f7948488696c53b4fa9786
timeCreated: 1542071393
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences:
- m_prefabA: {instanceID: 0}
- m_prefabB: {instanceID: 0}
- m_prefabPower: {instanceID: 0}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,97 @@
namespace Oculus.Platform.Samples.VrBoardGame
{
using UnityEngine;
using Oculus.Platform;
using Oculus.Platform.Models;
using UnityEngine.UI;
// This class coordinates In-App-Purchases (IAP) for the application. Follow the
// instructions in the Readme for setting up IAP on the Oculus Dashboard. Only
// one consumable IAP item is used is the demo: the Power-Ball!
public class IAPManager : MonoBehaviour
{
// the game controler to notify when the user purchaes more powerballs
[SerializeField] private GameController m_gameController = null;
// where to record to display the current price for the IAP item
[SerializeField] private Text m_priceText = null;
// purchasable IAP products we've configured on the Oculus Dashboard
private const string CONSUMABLE_1 = "PowerballPack1";
void Start()
{
FetchProductPrices();
FetchPurchasedProducts();
}
// get the current price for the configured IAP item
public void FetchProductPrices()
{
string[] skus = { CONSUMABLE_1 };
IAP.GetProductsBySKU(skus).OnComplete(GetProductsBySKUCallback);
}
void GetProductsBySKUCallback(Message<ProductList> msg)
{
if (msg.IsError)
{
PlatformManager.TerminateWithError(msg);
return;
}
foreach (Product p in msg.GetProductList())
{
Debug.LogFormat("Product: sku:{0} name:{1} price:{2}", p.Sku, p.Name, p.FormattedPrice);
if (p.Sku == CONSUMABLE_1)
{
m_priceText.text = p.FormattedPrice;
}
}
}
// fetches the Durable purchased IAP items. should return none unless you are expanding the
// to sample to include them.
public void FetchPurchasedProducts()
{
IAP.GetViewerPurchases().OnComplete(GetViewerPurchasesCallback);
}
void GetViewerPurchasesCallback(Message<PurchaseList> msg)
{
if (msg.IsError)
{
PlatformManager.TerminateWithError(msg);
return;
}
foreach (Purchase p in msg.GetPurchaseList())
{
Debug.LogFormat("Purchased: sku:{0} granttime:{1} id:{2}", p.Sku, p.GrantTime, p.ID);
}
}
public void BuyPowerBallsPressed()
{
#if UNITY_EDITOR
m_gameController.AddPowerballs(1);
#else
IAP.LaunchCheckoutFlow(CONSUMABLE_1).OnComplete(LaunchCheckoutFlowCallback);
#endif
}
private void LaunchCheckoutFlowCallback(Message<Purchase> msg)
{
if (msg.IsError)
{
PlatformManager.TerminateWithError(msg);
return;
}
Purchase p = msg.GetPurchase();
Debug.Log("purchased " + p.Sku);
m_gameController.AddPowerballs(3);
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,400 @@
namespace Oculus.Platform.Samples.VrBoardGame
{
using UnityEngine;
using Oculus.Platform;
using Oculus.Platform.Models;
using UnityEngine.UI;
using System.Collections.Generic;
using System;
using UnityEngine.Assertions;
// This classes uses the Oculus Matchmaking Service to find opponents of a similar
// skill and play a match with them. A skill pool is used with the matchmaking pool
// to coordinate the skill matching. Follow the instructions in the Readme to setup
// the matchmaking pools.
// The Datastore for the Room is used to communicate between the clients. This only
// works for relatively simple games with tolerance for latency. For more complex
// or realtime requirements, you'll want to use the Oculus.Platform.Net API.
public class MatchmakingManager : MonoBehaviour
{
// GameController to notify about match completions or early endings
[SerializeField] private GameController m_gameController = null;
// Text for the button that controls matchmaking
[SerializeField] private Text m_matchButtonText = null;
// Test widget to render matmaking statistics
[SerializeField] private Text m_infoText = null;
// name of the Quckmatch Pool configured on the Oculus Developer Dashboard
// which is expected to have an associated skill pool
private const string POOL = "VR_BOARD_GAME_POOL";
// the ID of the room for the current match
private ulong m_matchRoom;
// opponent User data
private User m_remotePlayer;
// last time we've received a room update
private float m_lastUpdateTime;
// how long to wait before polling for updates
private const float POLL_FREQUENCY = 30.0f;
private enum MatchRoomState { None, Queued, Configuring, MyTurn, RemoteTurn }
private MatchRoomState m_state;
void Start()
{
Matchmaking.SetMatchFoundNotificationCallback(MatchFoundCallback);
Rooms.SetUpdateNotificationCallback(MatchmakingRoomUpdateCallback);
TransitionToState(MatchRoomState.None);
}
void Update()
{
switch (m_state)
{
case MatchRoomState.Configuring:
case MatchRoomState.MyTurn:
case MatchRoomState.RemoteTurn:
// if we're expecting an update form the remote player and we haven't
// heard from them in a while, check the datastore just-in-case
if (POLL_FREQUENCY < (Time.time - m_lastUpdateTime))
{
Debug.Log("Polling Room");
m_lastUpdateTime = Time.time;
Rooms.Get(m_matchRoom).OnComplete(MatchmakingRoomUpdateCallback);
}
break;
}
}
public void MatchButtonPressed()
{
switch (m_state)
{
case MatchRoomState.None:
TransitionToState(MatchRoomState.Queued);
break;
default:
TransitionToState(MatchRoomState.None);
break;
}
}
public void EndMatch(int localScore, int remoteScore)
{
switch (m_state)
{
case MatchRoomState.MyTurn:
case MatchRoomState.RemoteTurn:
var myID = PlatformManager.MyID.ToString();
var remoteID = m_remotePlayer.ID.ToString();
var rankings = new Dictionary<string, int>();
if (localScore > remoteScore)
{
rankings[myID] = 1;
rankings[remoteID] = 2;
}
else if (localScore < remoteScore)
{
rankings[myID] = 2;
rankings[remoteID] = 1;
}
else
{
rankings[myID] = 1;
rankings[remoteID] = 1;
}
// since there is no secure server to simulate the game and report
// verifiable results, each client needs to independently report their
// results for the service to compate for inconsistencies
Matchmaking.ReportResultsInsecure(m_matchRoom, rankings)
.OnComplete(GenericErrorCheckCallback);
break;
}
TransitionToState(MatchRoomState.None);
}
void OnApplicationQuit()
{
// be a good matchmaking citizen and leave any queue immediately
Matchmaking.Cancel();
if (m_matchRoom != 0)
{
Rooms.Leave(m_matchRoom);
}
}
private void TransitionToState(MatchRoomState state)
{
var m_oldState = m_state;
m_state = state;
switch (m_state)
{
case MatchRoomState.None:
m_matchButtonText.text = "Find Match";
// the player can abort from any of the other states to the None state
// so we need to be careful to clean up all state variables
m_remotePlayer = null;
Matchmaking.Cancel();
if (m_matchRoom != 0)
{
Rooms.Leave(m_matchRoom);
m_matchRoom = 0;
}
break;
case MatchRoomState.Queued:
Assert.AreEqual(MatchRoomState.None, m_oldState);
m_matchButtonText.text = "Leave Queue";
Matchmaking.Enqueue2(POOL).OnComplete(MatchmakingEnqueueCallback);
break;
case MatchRoomState.Configuring:
Assert.AreEqual(MatchRoomState.Queued, m_oldState);
m_matchButtonText.text = "Cancel Match";
break;
case MatchRoomState.MyTurn:
case MatchRoomState.RemoteTurn:
Assert.AreNotEqual(MatchRoomState.None, m_oldState);
Assert.AreNotEqual(MatchRoomState.Queued, m_oldState);
m_matchButtonText.text = "Cancel Match";
break;
}
}
void MatchmakingEnqueueCallback(Message untyped_msg)
{
if (untyped_msg.IsError)
{
Debug.Log(untyped_msg.GetError().Message);
TransitionToState(MatchRoomState.None);
return;
}
Message<MatchmakingEnqueueResult> msg = (Message<MatchmakingEnqueueResult>)untyped_msg;
MatchmakingEnqueueResult info = msg.Data;
m_infoText.text = string.Format(
"Avg Wait Time: {0}s\n" +
"Max Expected Wait: {1}s\n" +
"In Last Hour: {2}\n" +
"Recent Percentage: {3}%",
info.AverageWait, info.MaxExpectedWait, info.MatchesInLastHourCount,
info.RecentMatchPercentage);
}
void MatchFoundCallback(Message<Room> msg)
{
if (msg.IsError)
{
Debug.Log(msg.GetError().Message);
TransitionToState(MatchRoomState.None);
return;
}
if (m_state != MatchRoomState.Queued)
{
// ignore callback - user already cancelled
return;
}
// since this example communicates via updates to the datastore, it's vital that
// we subscribe to room updates
Matchmaking.JoinRoom(msg.Data.ID, true /* subscribe to update notifications */)
.OnComplete(MatchmakingJoinRoomCallback);
m_matchRoom = msg.Data.ID;
}
void MatchmakingJoinRoomCallback(Message<Room> msg)
{
if (msg.IsError)
{
Debug.Log(msg.GetError().Message);
TransitionToState(MatchRoomState.None);
return;
}
if (m_state != MatchRoomState.Queued)
{
// ignore callback - user already cancelled
return;
}
int numUsers = (msg.Data.UsersOptional != null) ? msg.Data.UsersOptional.Count : 0;
Debug.Log ("Match room joined: " + m_matchRoom + " count: " + numUsers);
TransitionToState(MatchRoomState.Configuring);
// only process the room data if the other user has already joined
if (msg.Data.UsersOptional != null && msg.Data.UsersOptional.Count == 2)
{
ProcessRoomData(msg.Data);
}
}
// Room Datastore updates are used to send moves between players. So if the MatchRoomState
// is RemoteTurn I'm looking for the other player's move in the Datastore. If the
// MatchRoomState is MyTurn I'm waiting for the room ownership to change so that
// I have authority to write to the datastore.
void MatchmakingRoomUpdateCallback(Message<Room> msg)
{
if (msg.IsError)
{
Debug.Log(msg.GetError().Message);
TransitionToState(MatchRoomState.None);
return;
}
string ownerOculusID = msg.Data.OwnerOptional != null ? msg.Data.OwnerOptional.OculusID : "";
int numUsers = (msg.Data.UsersOptional != null) ? msg.Data.UsersOptional.Count : 0;
Debug.LogFormat(
"Room Update {0}\n" +
" Owner {1}\n" +
" User Count {2}\n" +
" Datastore Count {3}\n",
msg.Data.ID, ownerOculusID, numUsers, msg.Data.DataStore.Count);
// 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)
{
Debug.Log("Unexpected room update from: " + msg.Data.ID);
return;
}
ProcessRoomData(msg.Data);
}
private void ProcessRoomData(Room room)
{
m_lastUpdateTime = Time.time;
if (m_state == MatchRoomState.Configuring)
{
// get the User info for the other player
if (room.UsersOptional != null)
{
foreach (var user in room.UsersOptional)
{
if (PlatformManager.MyID != user.ID)
{
Debug.Log("Found remote user: " + user.OculusID);
m_remotePlayer = user;
break;
}
}
}
if (m_remotePlayer == null)
return;
bool i_go_first = DoesLocalUserGoFirst();
TransitionToState(i_go_first ? MatchRoomState.MyTurn : MatchRoomState.RemoteTurn);
Matchmaking.StartMatch(m_matchRoom).OnComplete(GenericErrorCheckCallback);
m_gameController.StartOnlineMatch(m_remotePlayer.OculusID, i_go_first);
}
// if it's the remote player's turn, look for their move in the datastore
if (m_state == MatchRoomState.RemoteTurn &&
room.DataStore.ContainsKey(m_remotePlayer.OculusID) &&
room.DataStore[m_remotePlayer.OculusID] != "")
{
// process remote move
ProcessRemoteMove(room.DataStore[m_remotePlayer.OculusID]);
TransitionToState(MatchRoomState.MyTurn);
}
// If the room ownership transferred to me, we can mark the remote turn complete.
// We don't do this when the remote move comes in if we aren't yet the owner because
// the local user will not be able to write to the datastore if they aren't the
// owner of the room.
if (m_state == MatchRoomState.MyTurn && room.OwnerOptional != null && room.OwnerOptional.ID == PlatformManager.MyID)
{
m_gameController.MarkRemoteTurnComplete();
}
if (room.UsersOptional == null || (room.UsersOptional != null && room.UsersOptional.Count != 2))
{
Debug.Log("Other user quit the room");
m_gameController.RemoteMatchEnded();
}
}
private void ProcessRemoteMove(string moveString)
{
Debug.Log("Processing remote move string: " + moveString);
string[] tokens = moveString.Split(':');
GamePiece.Piece piece = (GamePiece.Piece)Enum.Parse(typeof(GamePiece.Piece), tokens[0]);
int x = Int32.Parse(tokens[1]);
int y = Int32.Parse(tokens[2]);
// swap the coordinates since each player assumes they are player 0
x = GameBoard.LENGTH_X-1 - x;
y = GameBoard.LENGTH_Y-1 - y;
m_gameController.MakeRemoteMove(piece, x, y);
}
public void SendLocalMove(GamePiece.Piece piece, int boardX, int boardY)
{
string moveString = string.Format("{0}:{1}:{2}", piece.ToString(), boardX, boardY);
Debug.Log("Sending move: " + moveString);
var dict = new Dictionary<string, string>();
dict[PlatformManager.MyOculusID] = moveString;
dict[m_remotePlayer.OculusID] = "";
Rooms.UpdateDataStore(m_matchRoom, dict).OnComplete(UpdateDataStoreCallback);
TransitionToState(MatchRoomState.RemoteTurn);
}
private void UpdateDataStoreCallback(Message<Room> msg)
{
if (m_state != MatchRoomState.RemoteTurn)
{
// ignore calback - user already quit the match
return;
}
// after I've updated the datastore with my move, change ownership so the other
// user can perform their move
Rooms.UpdateOwner(m_matchRoom, m_remotePlayer.ID);
}
// deterministic but somewhat random selection for who goes first
private bool DoesLocalUserGoFirst()
{
// if the room ID is even, the lower ID goes first
if (m_matchRoom % 2 == 0)
{
return PlatformManager.MyID < m_remotePlayer.ID;
}
// otherwise the higher ID goes first
{
return PlatformManager.MyID > m_remotePlayer.ID;
}
}
private void GenericErrorCheckCallback(Message msg)
{
if (msg.IsError)
{
Debug.Log(msg.GetError().Message);
TransitionToState(MatchRoomState.None);
return;
}
}
}
}

View File

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

View File

@@ -0,0 +1,113 @@
namespace Oculus.Platform.Samples.VrBoardGame
{
using UnityEngine;
using Oculus.Platform;
using Oculus.Platform.Models;
// Top level class for initializing the Oculus Platform SDK. It also performs
// and entitlement check and returns information about the logged-in user.
public class PlatformManager : MonoBehaviour
{
private static PlatformManager s_instance;
// my Application-scoped Oculus ID
private ulong m_myID;
// my Oculus user name
private string m_myOculusID;
#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();
}
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;
Debug.Log(" I am " + m_myOculusID);
}
// 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();
}
#endregion
#region Properties
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
}
}

View File

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