first commit
This commit is contained in:
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 404d7999c3b78454798028d2efcb9336
|
||||
folderAsset: yes
|
||||
timeCreated: 1529327267
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,501 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="CullArea.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Represents the cull area used for network culling.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents the cull area used for network culling.
|
||||
/// </summary>
|
||||
public class CullArea : MonoBehaviour
|
||||
{
|
||||
private const int MAX_NUMBER_OF_ALLOWED_CELLS = 250;
|
||||
|
||||
public const int MAX_NUMBER_OF_SUBDIVISIONS = 3;
|
||||
|
||||
/// <summary>
|
||||
/// This represents the first ID which is assigned to the first created cell.
|
||||
/// If you already have some interest groups blocking this first ID, fell free to change it.
|
||||
/// However increasing the first group ID decreases the maximum amount of allowed cells.
|
||||
/// Allowed values are in range from 1 to 250.
|
||||
/// </summary>
|
||||
public readonly byte FIRST_GROUP_ID = 1;
|
||||
|
||||
/// <summary>
|
||||
/// This represents the order in which updates are sent.
|
||||
/// The number represents the subdivision of the cell hierarchy:
|
||||
/// - 0: message is sent to all players
|
||||
/// - 1: message is sent to players who are interested in the matching cell of the first subdivision
|
||||
/// If there is only one subdivision we are sending one update to all players
|
||||
/// before sending three consequent updates only to players who are in the same cell
|
||||
/// or interested in updates of the current cell.
|
||||
/// </summary>
|
||||
public readonly int[] SUBDIVISION_FIRST_LEVEL_ORDER = new int[4] { 0, 1, 1, 1 };
|
||||
|
||||
/// <summary>
|
||||
/// This represents the order in which updates are sent.
|
||||
/// The number represents the subdivision of the cell hierarchy:
|
||||
/// - 0: message is sent to all players
|
||||
/// - 1: message is sent to players who are interested in the matching cell of the first subdivision
|
||||
/// - 2: message is sent to players who are interested in the matching cell of the second subdivision
|
||||
/// If there are two subdivisions we are sending every second update only to players
|
||||
/// who are in the same cell or interested in updates of the current cell.
|
||||
/// </summary>
|
||||
public readonly int[] SUBDIVISION_SECOND_LEVEL_ORDER = new int[8] { 0, 2, 1, 2, 0, 2, 1, 2 };
|
||||
|
||||
/// <summary>
|
||||
/// This represents the order in which updates are sent.
|
||||
/// The number represents the subdivision of the cell hierarchy:
|
||||
/// - 0: message is sent to all players
|
||||
/// - 1: message is sent to players who are interested in the matching cell of the first subdivision
|
||||
/// - 2: message is sent to players who are interested in the matching cell of the second subdivision
|
||||
/// - 3: message is sent to players who are interested in the matching cell of the third subdivision
|
||||
/// If there are two subdivisions we are sending every second update only to players
|
||||
/// who are in the same cell or interested in updates of the current cell.
|
||||
/// </summary>
|
||||
public readonly int[] SUBDIVISION_THIRD_LEVEL_ORDER = new int[12] { 0, 3, 2, 3, 1, 3, 2, 3, 1, 3, 2, 3 };
|
||||
|
||||
public Vector2 Center;
|
||||
public Vector2 Size = new Vector2(25.0f, 25.0f);
|
||||
|
||||
public Vector2[] Subdivisions = new Vector2[MAX_NUMBER_OF_SUBDIVISIONS];
|
||||
|
||||
public int NumberOfSubdivisions;
|
||||
|
||||
public int CellCount { get; private set; }
|
||||
|
||||
public CellTree CellTree { get; private set; }
|
||||
|
||||
public Dictionary<int, GameObject> Map { get; private set; }
|
||||
|
||||
public bool YIsUpAxis = false;
|
||||
public bool RecreateCellHierarchy = false;
|
||||
|
||||
private byte idCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the cell hierarchy at runtime.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
this.idCounter = this.FIRST_GROUP_ID;
|
||||
|
||||
this.CreateCellHierarchy();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the cell hierarchy in editor and draws the cell view.
|
||||
/// </summary>
|
||||
public void OnDrawGizmos()
|
||||
{
|
||||
this.idCounter = this.FIRST_GROUP_ID;
|
||||
|
||||
if (this.RecreateCellHierarchy)
|
||||
{
|
||||
this.CreateCellHierarchy();
|
||||
}
|
||||
|
||||
this.DrawCells();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the cell hierarchy.
|
||||
/// </summary>
|
||||
private void CreateCellHierarchy()
|
||||
{
|
||||
if (!this.IsCellCountAllowed())
|
||||
{
|
||||
if (Debug.isDebugBuild)
|
||||
{
|
||||
Debug.LogError("There are too many cells created by your subdivision options. Maximum allowed number of cells is " + (MAX_NUMBER_OF_ALLOWED_CELLS - this.FIRST_GROUP_ID) +
|
||||
". Current number of cells is " + this.CellCount + ".");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Quit();
|
||||
}
|
||||
}
|
||||
|
||||
CellTreeNode rootNode = new CellTreeNode(this.idCounter++, CellTreeNode.ENodeType.Root, null);
|
||||
|
||||
if (this.YIsUpAxis)
|
||||
{
|
||||
this.Center = new Vector2(transform.position.x, transform.position.y);
|
||||
this.Size = new Vector2(transform.localScale.x, transform.localScale.y);
|
||||
|
||||
rootNode.Center = new Vector3(this.Center.x, this.Center.y, 0.0f);
|
||||
rootNode.Size = new Vector3(this.Size.x, this.Size.y, 0.0f);
|
||||
rootNode.TopLeft = new Vector3((this.Center.x - (this.Size.x / 2.0f)), (this.Center.y - (this.Size.y / 2.0f)), 0.0f);
|
||||
rootNode.BottomRight = new Vector3((this.Center.x + (this.Size.x / 2.0f)), (this.Center.y + (this.Size.y / 2.0f)), 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Center = new Vector2(transform.position.x, transform.position.z);
|
||||
this.Size = new Vector2(transform.localScale.x, transform.localScale.z);
|
||||
|
||||
rootNode.Center = new Vector3(this.Center.x, 0.0f, this.Center.y);
|
||||
rootNode.Size = new Vector3(this.Size.x, 0.0f, this.Size.y);
|
||||
rootNode.TopLeft = new Vector3((this.Center.x - (this.Size.x / 2.0f)), 0.0f, (this.Center.y - (this.Size.y / 2.0f)));
|
||||
rootNode.BottomRight = new Vector3((this.Center.x + (this.Size.x / 2.0f)), 0.0f, (this.Center.y + (this.Size.y / 2.0f)));
|
||||
}
|
||||
|
||||
this.CreateChildCells(rootNode, 1);
|
||||
|
||||
this.CellTree = new CellTree(rootNode);
|
||||
|
||||
this.RecreateCellHierarchy = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates all child cells.
|
||||
/// </summary>
|
||||
/// <param name="parent">The current parent node.</param>
|
||||
/// <param name="cellLevelInHierarchy">The cell level within the current hierarchy.</param>
|
||||
private void CreateChildCells(CellTreeNode parent, int cellLevelInHierarchy)
|
||||
{
|
||||
if (cellLevelInHierarchy > this.NumberOfSubdivisions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int rowCount = (int)this.Subdivisions[(cellLevelInHierarchy - 1)].x;
|
||||
int columnCount = (int)this.Subdivisions[(cellLevelInHierarchy - 1)].y;
|
||||
|
||||
float startX = parent.Center.x - (parent.Size.x / 2.0f);
|
||||
float width = parent.Size.x / rowCount;
|
||||
|
||||
for (int row = 0; row < rowCount; ++row)
|
||||
{
|
||||
for (int column = 0; column < columnCount; ++column)
|
||||
{
|
||||
float xPos = startX + (row * width) + (width / 2.0f);
|
||||
|
||||
CellTreeNode node = new CellTreeNode(this.idCounter++, (this.NumberOfSubdivisions == cellLevelInHierarchy) ? CellTreeNode.ENodeType.Leaf : CellTreeNode.ENodeType.Node, parent);
|
||||
|
||||
if (this.YIsUpAxis)
|
||||
{
|
||||
float startY = parent.Center.y - (parent.Size.y / 2.0f);
|
||||
float height = parent.Size.y / columnCount;
|
||||
float yPos = startY + (column * height) + (height / 2.0f);
|
||||
|
||||
node.Center = new Vector3(xPos, yPos, 0.0f);
|
||||
node.Size = new Vector3(width, height, 0.0f);
|
||||
node.TopLeft = new Vector3(xPos - (width / 2.0f), yPos - (height / 2.0f), 0.0f);
|
||||
node.BottomRight = new Vector3(xPos + (width / 2.0f), yPos + (height / 2.0f), 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
float startZ = parent.Center.z - (parent.Size.z / 2.0f);
|
||||
float depth = parent.Size.z / columnCount;
|
||||
float zPos = startZ + (column * depth) + (depth / 2.0f);
|
||||
|
||||
node.Center = new Vector3(xPos, 0.0f, zPos);
|
||||
node.Size = new Vector3(width, 0.0f, depth);
|
||||
node.TopLeft = new Vector3(xPos - (width / 2.0f), 0.0f, zPos - (depth / 2.0f));
|
||||
node.BottomRight = new Vector3(xPos + (width / 2.0f), 0.0f, zPos + (depth / 2.0f));
|
||||
}
|
||||
|
||||
parent.AddChild(node);
|
||||
|
||||
this.CreateChildCells(node, (cellLevelInHierarchy + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the cells.
|
||||
/// </summary>
|
||||
private void DrawCells()
|
||||
{
|
||||
if ((this.CellTree != null) && (this.CellTree.RootNode != null))
|
||||
{
|
||||
this.CellTree.RootNode.Draw();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.RecreateCellHierarchy = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the cell count is allowed.
|
||||
/// </summary>
|
||||
/// <returns>True if the cell count is allowed, false if the cell count is too large.</returns>
|
||||
private bool IsCellCountAllowed()
|
||||
{
|
||||
int horizontalCells = 1;
|
||||
int verticalCells = 1;
|
||||
|
||||
foreach (Vector2 v in this.Subdivisions)
|
||||
{
|
||||
horizontalCells *= (int)v.x;
|
||||
verticalCells *= (int)v.y;
|
||||
}
|
||||
|
||||
this.CellCount = horizontalCells * verticalCells;
|
||||
|
||||
return (this.CellCount <= (MAX_NUMBER_OF_ALLOWED_CELLS - this.FIRST_GROUP_ID));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all cell IDs the player is currently inside or nearby.
|
||||
/// </summary>
|
||||
/// <param name="position">The current position of the player.</param>
|
||||
/// <returns>A list containing all cell IDs the player is currently inside or nearby.</returns>
|
||||
public List<byte> GetActiveCells(Vector3 position)
|
||||
{
|
||||
List<byte> activeCells = new List<byte>(0);
|
||||
this.CellTree.RootNode.GetActiveCells(activeCells, this.YIsUpAxis, position);
|
||||
|
||||
// it makes sense to sort the "nearby" cells. those are in the list in positions after the subdivisions the point is inside. 2 subdivisions result in 3 areas the point is in.
|
||||
int cellsActive = this.NumberOfSubdivisions + 1;
|
||||
int cellsNearby = activeCells.Count - cellsActive;
|
||||
if (cellsNearby > 0)
|
||||
{
|
||||
activeCells.Sort(cellsActive, cellsNearby, new ByteComparer());
|
||||
}
|
||||
return activeCells;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the tree accessible from its root node.
|
||||
/// </summary>
|
||||
public class CellTree
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the root node of the cell tree.
|
||||
/// </summary>
|
||||
public CellTreeNode RootNode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public CellTree()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to define the root node.
|
||||
/// </summary>
|
||||
/// <param name="root">The root node of the tree.</param>
|
||||
public CellTree(CellTreeNode root)
|
||||
{
|
||||
this.RootNode = root;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single node of the tree.
|
||||
/// </summary>
|
||||
public class CellTreeNode
|
||||
{
|
||||
public enum ENodeType : byte
|
||||
{
|
||||
Root = 0,
|
||||
Node = 1,
|
||||
Leaf = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the unique ID of the cell.
|
||||
/// </summary>
|
||||
public byte Id;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the center, top-left or bottom-right position of the cell
|
||||
/// or the size of the cell.
|
||||
/// </summary>
|
||||
public Vector3 Center, Size, TopLeft, BottomRight;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the current node type of the cell tree node.
|
||||
/// </summary>
|
||||
public ENodeType NodeType;
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the parent node.
|
||||
/// </summary>
|
||||
public CellTreeNode Parent;
|
||||
|
||||
/// <summary>
|
||||
/// A list containing all child nodes.
|
||||
/// </summary>
|
||||
public List<CellTreeNode> Childs;
|
||||
|
||||
/// <summary>
|
||||
/// The max distance the player can have to the center of the cell for being 'nearby'.
|
||||
/// This is calculated once at runtime.
|
||||
/// </summary>
|
||||
private float maxDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public CellTreeNode()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to define the ID and the node type as well as setting a parent node.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the cell is used as the interest group.</param>
|
||||
/// <param name="nodeType">The node type of the cell tree node.</param>
|
||||
/// <param name="parent">The parent node of the cell tree node.</param>
|
||||
public CellTreeNode(byte id, ENodeType nodeType, CellTreeNode parent)
|
||||
{
|
||||
this.Id = id;
|
||||
|
||||
this.NodeType = nodeType;
|
||||
|
||||
this.Parent = parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given child to the node.
|
||||
/// </summary>
|
||||
/// <param name="child">The child which is added to the node.</param>
|
||||
public void AddChild(CellTreeNode child)
|
||||
{
|
||||
if (this.Childs == null)
|
||||
{
|
||||
this.Childs = new List<CellTreeNode>(1);
|
||||
}
|
||||
|
||||
this.Childs.Add(child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the cell in the editor.
|
||||
/// </summary>
|
||||
public void Draw()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (this.Childs != null)
|
||||
{
|
||||
foreach (CellTreeNode node in this.Childs)
|
||||
{
|
||||
node.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
Gizmos.color = new Color((this.NodeType == ENodeType.Root) ? 1 : 0, (this.NodeType == ENodeType.Node) ? 1 : 0, (this.NodeType == ENodeType.Leaf) ? 1 : 0);
|
||||
Gizmos.DrawWireCube(this.Center, this.Size);
|
||||
|
||||
byte offset = (byte)this.NodeType;
|
||||
GUIStyle gs = new GUIStyle() { fontStyle = FontStyle.Bold };
|
||||
gs.normal.textColor = Gizmos.color;
|
||||
UnityEditor.Handles.Label(this.Center+(Vector3.forward*offset*1f), this.Id.ToString(), gs);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gathers all cell IDs the player is currently inside or nearby.
|
||||
/// </summary>
|
||||
/// <param name="activeCells">The list to add all cell IDs to the player is currently inside or nearby.</param>
|
||||
/// <param name="yIsUpAxis">Describes if the y-axis is used as up-axis.</param>
|
||||
/// <param name="position">The current position of the player.</param>
|
||||
public void GetActiveCells(List<byte> activeCells, bool yIsUpAxis, Vector3 position)
|
||||
{
|
||||
if (this.NodeType != ENodeType.Leaf)
|
||||
{
|
||||
foreach (CellTreeNode node in this.Childs)
|
||||
{
|
||||
node.GetActiveCells(activeCells, yIsUpAxis, position);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.IsPointNearCell(yIsUpAxis, position))
|
||||
{
|
||||
if (this.IsPointInsideCell(yIsUpAxis, position))
|
||||
{
|
||||
activeCells.Insert(0, this.Id);
|
||||
|
||||
CellTreeNode p = this.Parent;
|
||||
while (p != null)
|
||||
{
|
||||
activeCells.Insert(0, p.Id);
|
||||
|
||||
p = p.Parent;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
activeCells.Add(this.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given point is inside the cell.
|
||||
/// </summary>
|
||||
/// <param name="yIsUpAxis">Describes if the y-axis is used as up-axis.</param>
|
||||
/// <param name="point">The point to check.</param>
|
||||
/// <returns>True if the point is inside the cell, false if the point is not inside the cell.</returns>
|
||||
public bool IsPointInsideCell(bool yIsUpAxis, Vector3 point)
|
||||
{
|
||||
if ((point.x < this.TopLeft.x) || (point.x > this.BottomRight.x))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (yIsUpAxis)
|
||||
{
|
||||
if ((point.y >= this.TopLeft.y) && (point.y <= this.BottomRight.y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((point.z >= this.TopLeft.z) && (point.z <= this.BottomRight.z))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given point is near the cell.
|
||||
/// </summary>
|
||||
/// <param name="yIsUpAxis">Describes if the y-axis is used as up-axis.</param>
|
||||
/// <param name="point">The point to check.</param>
|
||||
/// <returns>True if the point is near the cell, false if the point is too far away.</returns>
|
||||
public bool IsPointNearCell(bool yIsUpAxis, Vector3 point)
|
||||
{
|
||||
if (this.maxDistance == 0.0f)
|
||||
{
|
||||
this.maxDistance = (this.Size.x + this.Size.y + this.Size.z) / 2.0f;
|
||||
}
|
||||
|
||||
return ((point - this.Center).sqrMagnitude <= (this.maxDistance * this.maxDistance));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class ByteComparer : IComparer<byte>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Compare(byte x, byte y)
|
||||
{
|
||||
return x == y ? 0 : x < y ? -1 : 1;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfb1c264fdc576442b2f42c998bed4a2
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,254 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="CullingHandler.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Handles the network culling.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Photon.Pun;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handles the network culling.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(PhotonView))]
|
||||
public class CullingHandler : MonoBehaviour, IPunObservable
|
||||
{
|
||||
#region VARIABLES
|
||||
|
||||
private int orderIndex;
|
||||
|
||||
private CullArea cullArea;
|
||||
|
||||
private List<byte> previousActiveCells, activeCells;
|
||||
|
||||
private PhotonView pView;
|
||||
|
||||
private Vector3 lastPosition, currentPosition;
|
||||
|
||||
|
||||
// used to limit the number of UpdateInterestGroups calls per second (there is no use to change groups more than a few times per second, even if the Culling algorithm makes it look like that)
|
||||
private float timeSinceUpdate;
|
||||
// see timeSinceUpdate
|
||||
private float timeBetweenUpdatesMin = 0.33f;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region UNITY_FUNCTIONS
|
||||
|
||||
/// <summary>
|
||||
/// Gets references to the PhotonView component and the cull area game object.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
if (this.pView == null)
|
||||
{
|
||||
this.pView = GetComponent<PhotonView>();
|
||||
|
||||
if (!this.pView.IsMine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.cullArea == null)
|
||||
{
|
||||
this.cullArea = FindObjectOfType<CullArea>();
|
||||
}
|
||||
|
||||
this.previousActiveCells = new List<byte>(0);
|
||||
this.activeCells = new List<byte>(0);
|
||||
|
||||
this.currentPosition = this.lastPosition = transform.position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the right interest group or prepares the permanent change of the interest Group of the PhotonView component.
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
if (!this.pView.IsMine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (PhotonNetwork.InRoom)
|
||||
{
|
||||
if (this.cullArea.NumberOfSubdivisions == 0)
|
||||
{
|
||||
this.pView.Group = this.cullArea.FIRST_GROUP_ID;
|
||||
|
||||
PhotonNetwork.SetInterestGroups(this.cullArea.FIRST_GROUP_ID, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is used to continuously update the active group.
|
||||
this.pView.ObservedComponents.Add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the player has moved previously and updates the interest groups if necessary.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (!this.pView.IsMine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// we'll limit how often this update may run at all (to avoid too frequent changes and flooding the server with SetInterestGroups calls)
|
||||
this.timeSinceUpdate += Time.deltaTime;
|
||||
if (this.timeSinceUpdate < this.timeBetweenUpdatesMin)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastPosition = this.currentPosition;
|
||||
this.currentPosition = transform.position;
|
||||
|
||||
// This is a simple position comparison of the current and the previous position.
|
||||
// When using Network Culling in a bigger project keep in mind that there might
|
||||
// be more transform-related options, e.g. the rotation, or other options to check.
|
||||
if (this.currentPosition != this.lastPosition)
|
||||
{
|
||||
if (this.HaveActiveCellsChanged())
|
||||
{
|
||||
this.UpdateInterestGroups();
|
||||
this.timeSinceUpdate = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drawing informations.
|
||||
/// </summary>
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!this.pView.IsMine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string subscribedAndActiveCells = "Inside cells:\n";
|
||||
string subscribedCells = "Subscribed cells:\n";
|
||||
|
||||
for (int index = 0; index < this.activeCells.Count; ++index)
|
||||
{
|
||||
if (index <= this.cullArea.NumberOfSubdivisions)
|
||||
{
|
||||
subscribedAndActiveCells += this.activeCells[index] + " | ";
|
||||
}
|
||||
|
||||
subscribedCells += this.activeCells[index] + " | ";
|
||||
}
|
||||
GUI.Label(new Rect(20.0f, Screen.height - 120.0f, 200.0f, 40.0f), "<color=white>PhotonView Group: " + this.pView.Group + "</color>", new GUIStyle() { alignment = TextAnchor.UpperLeft, fontSize = 16 });
|
||||
GUI.Label(new Rect(20.0f, Screen.height - 100.0f, 200.0f, 40.0f), "<color=white>" + subscribedAndActiveCells + "</color>", new GUIStyle() { alignment = TextAnchor.UpperLeft, fontSize = 16 });
|
||||
GUI.Label(new Rect(20.0f, Screen.height - 60.0f, 200.0f, 40.0f), "<color=white>" + subscribedCells + "</color>", new GUIStyle() { alignment = TextAnchor.UpperLeft, fontSize = 16 });
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the previously active cells have changed.
|
||||
/// </summary>
|
||||
/// <returns>True if the previously active cells have changed and false otherwise.</returns>
|
||||
private bool HaveActiveCellsChanged()
|
||||
{
|
||||
if (this.cullArea.NumberOfSubdivisions == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.previousActiveCells = new List<byte>(this.activeCells);
|
||||
this.activeCells = this.cullArea.GetActiveCells(transform.position);
|
||||
|
||||
// If the player leaves the area we insert the whole area itself as an active cell.
|
||||
// This can be removed if it is sure that the player is not able to leave the area.
|
||||
while (this.activeCells.Count <= this.cullArea.NumberOfSubdivisions)
|
||||
{
|
||||
this.activeCells.Add(this.cullArea.FIRST_GROUP_ID);
|
||||
}
|
||||
|
||||
if (this.activeCells.Count != this.previousActiveCells.Count)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.activeCells[this.cullArea.NumberOfSubdivisions] != this.previousActiveCells[this.cullArea.NumberOfSubdivisions])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from old and subscribes to new interest groups.
|
||||
/// </summary>
|
||||
private void UpdateInterestGroups()
|
||||
{
|
||||
List<byte> disable = new List<byte>(0);
|
||||
|
||||
foreach (byte groupId in this.previousActiveCells)
|
||||
{
|
||||
if (!this.activeCells.Contains(groupId))
|
||||
{
|
||||
disable.Add(groupId);
|
||||
}
|
||||
}
|
||||
|
||||
PhotonNetwork.SetInterestGroups(disable.ToArray(), this.activeCells.ToArray());
|
||||
}
|
||||
|
||||
#region IPunObservable implementation
|
||||
|
||||
/// <summary>
|
||||
/// This time OnPhotonSerializeView is not used to send or receive any kind of data.
|
||||
/// It is used to change the currently active group of the PhotonView component, making it work together with PUN more directly.
|
||||
/// Keep in mind that this function is only executed, when there is at least one more player in the room.
|
||||
/// </summary>
|
||||
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
|
||||
{
|
||||
// If the player leaves the area we insert the whole area itself as an active cell.
|
||||
// This can be removed if it is sure that the player is not able to leave the area.
|
||||
while (this.activeCells.Count <= this.cullArea.NumberOfSubdivisions)
|
||||
{
|
||||
this.activeCells.Add(this.cullArea.FIRST_GROUP_ID);
|
||||
}
|
||||
|
||||
if (this.cullArea.NumberOfSubdivisions == 1)
|
||||
{
|
||||
this.orderIndex = (++this.orderIndex % this.cullArea.SUBDIVISION_FIRST_LEVEL_ORDER.Length);
|
||||
this.pView.Group = this.activeCells[this.cullArea.SUBDIVISION_FIRST_LEVEL_ORDER[this.orderIndex]];
|
||||
}
|
||||
else if (this.cullArea.NumberOfSubdivisions == 2)
|
||||
{
|
||||
this.orderIndex = (++this.orderIndex % this.cullArea.SUBDIVISION_SECOND_LEVEL_ORDER.Length);
|
||||
this.pView.Group = this.activeCells[this.cullArea.SUBDIVISION_SECOND_LEVEL_ORDER[this.orderIndex]];
|
||||
}
|
||||
else if (this.cullArea.NumberOfSubdivisions == 3)
|
||||
{
|
||||
this.orderIndex = (++this.orderIndex % this.cullArea.SUBDIVISION_THIRD_LEVEL_ORDER.Length);
|
||||
this.pView.Group = this.activeCells[this.cullArea.SUBDIVISION_THIRD_LEVEL_ORDER[this.orderIndex]];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84db789113d4b01418c1becb128c4561
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcede10f4c76b443483f21320fee20e5
|
||||
folderAsset: yes
|
||||
timeCreated: 1529329408
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,265 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="CullAreaEditor.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Custom inspector for CullArea
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(CullArea))]
|
||||
public class CullAreaEditor : Editor
|
||||
{
|
||||
private bool alignEditorCamera, showHelpEntries;
|
||||
|
||||
private CullArea cullArea;
|
||||
|
||||
private enum UP_AXIS_OPTIONS
|
||||
{
|
||||
SideScrollerMode = 0,
|
||||
TopDownOr3DMode = 1
|
||||
}
|
||||
|
||||
private UP_AXIS_OPTIONS upAxisOptions;
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
cullArea = (CullArea)target;
|
||||
|
||||
// Destroying the newly created cull area if there is already one existing
|
||||
if (FindObjectsOfType<CullArea>().Length > 1)
|
||||
{
|
||||
Debug.LogWarning("Destroying newly created cull area because there is already one existing in the scene.");
|
||||
|
||||
DestroyImmediate(cullArea);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevents the dropdown from resetting
|
||||
if (cullArea != null)
|
||||
{
|
||||
upAxisOptions = cullArea.YIsUpAxis ? UP_AXIS_OPTIONS.SideScrollerMode : UP_AXIS_OPTIONS.TopDownOr3DMode;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
|
||||
if (Application.isEditor && !Application.isPlaying)
|
||||
{
|
||||
OnInspectorGUIEditMode();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnInspectorGUIPlayMode();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the inspector GUI when edit mode is active.
|
||||
/// </summary>
|
||||
private void OnInspectorGUIEditMode()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
#region DEFINE_UP_AXIS
|
||||
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.LabelField("Select game type", EditorStyles.boldLabel);
|
||||
upAxisOptions = (UP_AXIS_OPTIONS)EditorGUILayout.EnumPopup("Game type", upAxisOptions);
|
||||
cullArea.YIsUpAxis = (upAxisOptions == UP_AXIS_OPTIONS.SideScrollerMode);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
#region SUBDIVISION
|
||||
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.LabelField("Set the number of subdivisions", EditorStyles.boldLabel);
|
||||
cullArea.NumberOfSubdivisions = EditorGUILayout.IntSlider("Number of subdivisions", cullArea.NumberOfSubdivisions, 0, CullArea.MAX_NUMBER_OF_SUBDIVISIONS);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (cullArea.NumberOfSubdivisions != 0)
|
||||
{
|
||||
for (int index = 0; index < cullArea.Subdivisions.Length; ++index)
|
||||
{
|
||||
if ((index + 1) <= cullArea.NumberOfSubdivisions)
|
||||
{
|
||||
string countMessage = (index + 1) + ". Subdivision: row / column count";
|
||||
|
||||
EditorGUILayout.BeginVertical();
|
||||
cullArea.Subdivisions[index] = EditorGUILayout.Vector2Field(countMessage, cullArea.Subdivisions[index]);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
else
|
||||
{
|
||||
cullArea.Subdivisions[index] = new UnityEngine.Vector2(1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
#region UPDATING_MAIN_CAMERA
|
||||
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
|
||||
EditorGUILayout.LabelField("View and camera options", EditorStyles.boldLabel);
|
||||
alignEditorCamera = EditorGUILayout.Toggle("Automatically align editor view with grid", alignEditorCamera);
|
||||
|
||||
if (Camera.main != null)
|
||||
{
|
||||
if (GUILayout.Button("Align main camera with grid"))
|
||||
{
|
||||
Undo.RecordObject(Camera.main.transform, "Align main camera with grid.");
|
||||
|
||||
float yCoord = cullArea.YIsUpAxis ? cullArea.Center.y : Mathf.Max(cullArea.Size.x, cullArea.Size.y);
|
||||
float zCoord = cullArea.YIsUpAxis ? -Mathf.Max(cullArea.Size.x, cullArea.Size.y) : cullArea.Center.y;
|
||||
|
||||
Camera.main.transform.position = new Vector3(cullArea.Center.x, yCoord, zCoord);
|
||||
Camera.main.transform.LookAt(cullArea.transform.position);
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Current main camera position is " + Camera.main.transform.position.ToString());
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
cullArea.RecreateCellHierarchy = true;
|
||||
|
||||
AlignEditorView();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
showHelpEntries = EditorGUILayout.Foldout(showHelpEntries, "Need help with this component?");
|
||||
if (showHelpEntries)
|
||||
{
|
||||
EditorGUILayout.HelpBox("To find help you can either follow the tutorial or have a look at the forums by clicking on the buttons below.", MessageType.Info);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Open the tutorial"))
|
||||
{
|
||||
Application.OpenURL("https://doc.photonengine.com/en-us/pun/v2/demos-and-tutorials/package-demos/culling-demo");
|
||||
}
|
||||
if (GUILayout.Button("Take me to the forums"))
|
||||
{
|
||||
Application.OpenURL("https://forum.photonengine.com/categories/unity-networking-plugin-pun");
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the inspector GUI when play mode is active.
|
||||
/// </summary>
|
||||
private void OnInspectorGUIPlayMode()
|
||||
{
|
||||
EditorGUILayout.LabelField("No changes allowed when game is running. Please exit play mode first.", EditorStyles.boldLabel);
|
||||
}
|
||||
|
||||
public void OnSceneGUI()
|
||||
{
|
||||
Handles.BeginGUI();
|
||||
GUILayout.BeginArea(new Rect(Screen.width - 110, Screen.height - 90, 100, 60));
|
||||
|
||||
if (GUILayout.Button("Reset position"))
|
||||
{
|
||||
cullArea.transform.position = Vector3.zero;
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Reset scaling"))
|
||||
{
|
||||
cullArea.transform.localScale = new Vector3(25.0f, 25.0f, 25.0f);
|
||||
}
|
||||
|
||||
GUILayout.EndArea();
|
||||
Handles.EndGUI();
|
||||
|
||||
// Checking for changes of the transform
|
||||
if (cullArea.transform.hasChanged)
|
||||
{
|
||||
// Resetting position
|
||||
float posX = cullArea.transform.position.x;
|
||||
float posY = cullArea.YIsUpAxis ? cullArea.transform.position.y : 0.0f;
|
||||
float posZ = !cullArea.YIsUpAxis ? cullArea.transform.position.z : 0.0f;
|
||||
|
||||
cullArea.transform.position = new Vector3(posX, posY, posZ);
|
||||
|
||||
// Resetting scaling
|
||||
if (cullArea.Size.x < 1.0f || cullArea.Size.y < 1.0f)
|
||||
{
|
||||
float scaleX = (cullArea.transform.localScale.x < 1.0f) ? 1.0f : cullArea.transform.localScale.x;
|
||||
float scaleY = (cullArea.transform.localScale.y < 1.0f) ? 1.0f : cullArea.transform.localScale.y;
|
||||
float scaleZ = (cullArea.transform.localScale.z < 1.0f) ? 1.0f : cullArea.transform.localScale.z;
|
||||
|
||||
cullArea.transform.localScale = new Vector3(scaleX, scaleY, scaleZ);
|
||||
|
||||
Debug.LogWarning("Scaling on a single axis can not be lower than 1. Resetting...");
|
||||
}
|
||||
|
||||
cullArea.RecreateCellHierarchy = true;
|
||||
|
||||
AlignEditorView();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aligns the editor view with the created grid.
|
||||
/// </summary>
|
||||
private void AlignEditorView()
|
||||
{
|
||||
if (!alignEditorCamera)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This creates a temporary game object in order to align the editor view.
|
||||
// The created game object is destroyed afterwards.
|
||||
GameObject tmpGo = new GameObject();
|
||||
|
||||
float yCoord = cullArea.YIsUpAxis ? cullArea.Center.y : Mathf.Max(cullArea.Size.x, cullArea.Size.y);
|
||||
float zCoord = cullArea.YIsUpAxis ? -Mathf.Max(cullArea.Size.x, cullArea.Size.y) : cullArea.Center.y;
|
||||
|
||||
tmpGo.transform.position = new Vector3(cullArea.Center.x, yCoord, zCoord);
|
||||
tmpGo.transform.LookAt(cullArea.transform.position);
|
||||
|
||||
if (SceneView.lastActiveSceneView != null)
|
||||
{
|
||||
SceneView.lastActiveSceneView.AlignViewToObject(tmpGo.transform);
|
||||
}
|
||||
|
||||
DestroyImmediate(tmpGo);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abadaa451a7bff0489078ed9eec61133
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "PhotonUnityNetworking.Utilities.Culling.Editor",
|
||||
"references": [
|
||||
"PhotonUnityNetworking.Utilities"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06dafbbe6b7b3a84f84213f47aabe7f0
|
||||
timeCreated: 1537459565
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 72b80dd954bc647c4a2a924b87762f92
|
||||
folderAsset: yes
|
||||
timeCreated: 1529327208
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,109 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="PhotonLagSimulationGui.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// This MonoBehaviour is a basic GUI for the Photon client's network-simulation feature.
|
||||
// It can modify lag (fixed delay), jitter (random lag) and packet loss.
|
||||
// Part of the [Optional GUI](@ref optionalGui).
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Photon.Pun;
|
||||
using Photon.Realtime;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// This MonoBehaviour is a basic GUI for the Photon client's network-simulation feature.
|
||||
/// It can modify lag (fixed delay), jitter (random lag) and packet loss.
|
||||
/// </summary>
|
||||
/// \ingroup optionalGui
|
||||
public class PhotonLagSimulationGui : MonoBehaviour
|
||||
{
|
||||
/// <summary>Positioning rect for window.</summary>
|
||||
public Rect WindowRect = new Rect(0, 100, 120, 100);
|
||||
|
||||
/// <summary>Unity GUI Window ID (must be unique or will cause issues).</summary>
|
||||
public int WindowId = 101;
|
||||
|
||||
/// <summary>Shows or hides GUI (does not affect settings).</summary>
|
||||
public bool Visible = true;
|
||||
|
||||
/// <summary>The peer currently in use (to set the network simulation).</summary>
|
||||
public PhotonPeer Peer { get; set; }
|
||||
|
||||
public void Start()
|
||||
{
|
||||
this.Peer = PhotonNetwork.NetworkingClient.LoadBalancingPeer;
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
if (!this.Visible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.Peer == null)
|
||||
{
|
||||
this.WindowRect = GUILayout.Window(this.WindowId, this.WindowRect, this.NetSimHasNoPeerWindow, "Netw. Sim.");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.WindowRect = GUILayout.Window(this.WindowId, this.WindowRect, this.NetSimWindow, "Netw. Sim.");
|
||||
}
|
||||
}
|
||||
|
||||
private void NetSimHasNoPeerWindow(int windowId)
|
||||
{
|
||||
GUILayout.Label("No peer to communicate with. ");
|
||||
}
|
||||
|
||||
private void NetSimWindow(int windowId)
|
||||
{
|
||||
GUILayout.Label(string.Format("Rtt:{0,4} +/-{1,3}", this.Peer.RoundTripTime, this.Peer.RoundTripTimeVariance));
|
||||
|
||||
bool simEnabled = this.Peer.IsSimulationEnabled;
|
||||
bool newSimEnabled = GUILayout.Toggle(simEnabled, "Simulate");
|
||||
if (newSimEnabled != simEnabled)
|
||||
{
|
||||
this.Peer.IsSimulationEnabled = newSimEnabled;
|
||||
}
|
||||
|
||||
float inOutLag = this.Peer.NetworkSimulationSettings.IncomingLag;
|
||||
GUILayout.Label("Lag " + inOutLag);
|
||||
inOutLag = GUILayout.HorizontalSlider(inOutLag, 0, 500);
|
||||
|
||||
this.Peer.NetworkSimulationSettings.IncomingLag = (int)inOutLag;
|
||||
this.Peer.NetworkSimulationSettings.OutgoingLag = (int)inOutLag;
|
||||
|
||||
float inOutJitter = this.Peer.NetworkSimulationSettings.IncomingJitter;
|
||||
GUILayout.Label("Jit " + inOutJitter);
|
||||
inOutJitter = GUILayout.HorizontalSlider(inOutJitter, 0, 100);
|
||||
|
||||
this.Peer.NetworkSimulationSettings.IncomingJitter = (int)inOutJitter;
|
||||
this.Peer.NetworkSimulationSettings.OutgoingJitter = (int)inOutJitter;
|
||||
|
||||
float loss = this.Peer.NetworkSimulationSettings.IncomingLossPercentage;
|
||||
GUILayout.Label("Loss " + loss);
|
||||
loss = GUILayout.HorizontalSlider(loss, 0, 10);
|
||||
|
||||
this.Peer.NetworkSimulationSettings.IncomingLossPercentage = (int)loss;
|
||||
this.Peer.NetworkSimulationSettings.OutgoingLossPercentage = (int)loss;
|
||||
|
||||
// if anything was clicked, the height of this window is likely changed. reduce it to be layouted again next frame
|
||||
if (GUI.changed)
|
||||
{
|
||||
this.WindowRect.height = 100;
|
||||
}
|
||||
|
||||
GUI.DragWindow();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5867a53c8db0e6745818285bb6b6e1b9
|
||||
labels:
|
||||
- ExitGames
|
||||
- PUN
|
||||
- Photon
|
||||
- Networking
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,170 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="PhotonStatsGui.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Basic GUI to show traffic and health statistics of the connection to Photon,
|
||||
// toggled by shift+tab.
|
||||
// Part of the [Optional GUI](@ref optionalGui).
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Photon.Pun;
|
||||
using Photon.Realtime;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Basic GUI to show traffic and health statistics of the connection to Photon,
|
||||
/// toggled by shift+tab.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The shown health values can help identify problems with connection losses or performance.
|
||||
/// Example:
|
||||
/// If the time delta between two consecutive SendOutgoingCommands calls is a second or more,
|
||||
/// chances rise for a disconnect being caused by this (because acknowledgements to the server
|
||||
/// need to be sent in due time).
|
||||
/// </remarks>
|
||||
/// \ingroup optionalGui
|
||||
public class PhotonStatsGui : MonoBehaviour
|
||||
{
|
||||
/// <summary>Shows or hides GUI (does not affect if stats are collected).</summary>
|
||||
public bool statsWindowOn = true;
|
||||
|
||||
/// <summary>Option to turn collecting stats on or off (used in Update()).</summary>
|
||||
public bool statsOn = true;
|
||||
|
||||
/// <summary>Shows additional "health" values of connection.</summary>
|
||||
public bool healthStatsVisible;
|
||||
|
||||
/// <summary>Shows additional "lower level" traffic stats.</summary>
|
||||
public bool trafficStatsOn;
|
||||
|
||||
/// <summary>Show buttons to control stats and reset them.</summary>
|
||||
public bool buttonsOn;
|
||||
|
||||
/// <summary>Positioning rect for window.</summary>
|
||||
public Rect statsRect = new Rect(0, 100, 200, 50);
|
||||
|
||||
/// <summary>Unity GUI Window ID (must be unique or will cause issues).</summary>
|
||||
public int WindowId = 100;
|
||||
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (this.statsRect.x <= 0)
|
||||
{
|
||||
this.statsRect.x = Screen.width - this.statsRect.width;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Checks for shift+tab input combination (to toggle statsOn).</summary>
|
||||
public void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Tab) && Input.GetKey(KeyCode.LeftShift))
|
||||
{
|
||||
this.statsWindowOn = !this.statsWindowOn;
|
||||
this.statsOn = true; // enable stats when showing the window
|
||||
}
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
if (PhotonNetwork.NetworkingClient.LoadBalancingPeer.TrafficStatsEnabled != statsOn)
|
||||
{
|
||||
PhotonNetwork.NetworkingClient.LoadBalancingPeer.TrafficStatsEnabled = this.statsOn;
|
||||
}
|
||||
|
||||
if (!this.statsWindowOn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.statsRect = GUILayout.Window(this.WindowId, this.statsRect, this.TrafficStatsWindow, "Messages (shift+tab)");
|
||||
}
|
||||
|
||||
public void TrafficStatsWindow(int windowID)
|
||||
{
|
||||
bool statsToLog = false;
|
||||
TrafficStatsGameLevel gls = PhotonNetwork.NetworkingClient.LoadBalancingPeer.TrafficStatsGameLevel;
|
||||
long elapsedMs = PhotonNetwork.NetworkingClient.LoadBalancingPeer.TrafficStatsElapsedMs / 1000;
|
||||
if (elapsedMs == 0)
|
||||
{
|
||||
elapsedMs = 1;
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
this.buttonsOn = GUILayout.Toggle(this.buttonsOn, "buttons");
|
||||
this.healthStatsVisible = GUILayout.Toggle(this.healthStatsVisible, "health");
|
||||
this.trafficStatsOn = GUILayout.Toggle(this.trafficStatsOn, "traffic");
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
string total = string.Format("Out {0,4} | In {1,4} | Sum {2,4}", gls.TotalOutgoingMessageCount, gls.TotalIncomingMessageCount, gls.TotalMessageCount);
|
||||
string elapsedTime = string.Format("{0}sec average:", elapsedMs);
|
||||
string average = string.Format("Out {0,4} | In {1,4} | Sum {2,4}", gls.TotalOutgoingMessageCount / elapsedMs, gls.TotalIncomingMessageCount / elapsedMs, gls.TotalMessageCount / elapsedMs);
|
||||
GUILayout.Label(total);
|
||||
GUILayout.Label(elapsedTime);
|
||||
GUILayout.Label(average);
|
||||
|
||||
if (this.buttonsOn)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
this.statsOn = GUILayout.Toggle(this.statsOn, "stats on");
|
||||
if (GUILayout.Button("Reset"))
|
||||
{
|
||||
PhotonNetwork.NetworkingClient.LoadBalancingPeer.TrafficStatsReset();
|
||||
PhotonNetwork.NetworkingClient.LoadBalancingPeer.TrafficStatsEnabled = true;
|
||||
}
|
||||
statsToLog = GUILayout.Button("To Log");
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
string trafficStatsIn = string.Empty;
|
||||
string trafficStatsOut = string.Empty;
|
||||
if (this.trafficStatsOn)
|
||||
{
|
||||
GUILayout.Box("Traffic Stats");
|
||||
trafficStatsIn = "Incoming: \n" + PhotonNetwork.NetworkingClient.LoadBalancingPeer.TrafficStatsIncoming.ToString();
|
||||
trafficStatsOut = "Outgoing: \n" + PhotonNetwork.NetworkingClient.LoadBalancingPeer.TrafficStatsOutgoing.ToString();
|
||||
GUILayout.Label(trafficStatsIn);
|
||||
GUILayout.Label(trafficStatsOut);
|
||||
}
|
||||
|
||||
string healthStats = string.Empty;
|
||||
if (this.healthStatsVisible)
|
||||
{
|
||||
GUILayout.Box("Health Stats");
|
||||
healthStats = string.Format(
|
||||
"ping: {6}[+/-{7}]ms resent:{8} \n\nmax ms between\nsend: {0,4} \ndispatch: {1,4} \n\nlongest dispatch for: \nev({3}):{2,3}ms \nop({5}):{4,3}ms",
|
||||
gls.LongestDeltaBetweenSending,
|
||||
gls.LongestDeltaBetweenDispatching,
|
||||
gls.LongestEventCallback,
|
||||
gls.LongestEventCallbackCode,
|
||||
gls.LongestOpResponseCallback,
|
||||
gls.LongestOpResponseCallbackOpCode,
|
||||
PhotonNetwork.NetworkingClient.LoadBalancingPeer.RoundTripTime,
|
||||
PhotonNetwork.NetworkingClient.LoadBalancingPeer.RoundTripTimeVariance,
|
||||
PhotonNetwork.NetworkingClient.LoadBalancingPeer.ResentReliableCommands);
|
||||
GUILayout.Label(healthStats);
|
||||
}
|
||||
|
||||
if (statsToLog)
|
||||
{
|
||||
string complete = string.Format("{0}\n{1}\n{2}\n{3}\n{4}\n{5}", total, elapsedTime, average, trafficStatsIn, trafficStatsOut, healthStats);
|
||||
Debug.Log(complete);
|
||||
}
|
||||
|
||||
// if anything was clicked, the height of this window is likely changed. reduce it to be layouted again next frame
|
||||
if (GUI.changed)
|
||||
{
|
||||
this.statsRect.height = 100;
|
||||
}
|
||||
|
||||
GUI.DragWindow();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d06466c03d263624786afa88b52928b6
|
||||
labels:
|
||||
- ExitGames
|
||||
- PUN
|
||||
- Photon
|
||||
- Networking
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,84 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="PointedAtGameObjectInfo.cs" company="Exit Games GmbH">
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Display ViewId, OwnerActorNr, IsCeneView and IsMine when clicked using the old UI system
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
using Photon.Pun;
|
||||
using Photon.Realtime;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Display ViewId, OwnerActorNr, IsCeneView and IsMine when clicked.
|
||||
/// </summary>
|
||||
public class PointedAtGameObjectInfo : MonoBehaviour
|
||||
{
|
||||
public static PointedAtGameObjectInfo Instance;
|
||||
|
||||
public Text text;
|
||||
|
||||
Transform focus;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
Debug.LogWarning("PointedAtGameObjectInfo is already featured in the scene, gameobject is destroyed");
|
||||
Destroy(this.gameObject);
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public void SetFocus(PhotonView pv)
|
||||
{
|
||||
|
||||
focus = pv != null ? pv.transform : null;
|
||||
|
||||
if (pv != null)
|
||||
{
|
||||
text.text = string.Format("id {0} own: {1} {2}{3}", pv.ViewID, pv.OwnerActorNr, (pv.IsRoomView) ? "scn" : "", (pv.IsMine) ? " mine" : "");
|
||||
//GUI.Label (new Rect (Input.mousePosition.x + 5, Screen.height - Input.mousePosition.y - 15, 300, 30), );
|
||||
}
|
||||
else
|
||||
{
|
||||
text.text = string.Empty;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveFocus(PhotonView pv)
|
||||
{
|
||||
if (pv == null)
|
||||
{
|
||||
text.text = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
if (pv.transform == focus)
|
||||
{
|
||||
text.text = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (focus != null)
|
||||
{
|
||||
this.transform.position = Camera.main.WorldToScreenPoint(focus.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6262dd9a9b078c4e8cbd47495aa6d23
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,212 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="TabViewManager.cs" company="Exit Games GmbH">
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Output detailed information about Pun Current states, using the old Unity UI framework.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
using Photon.Realtime;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Output detailed information about Pun Current states, using the old Unity UI framework.
|
||||
/// </summary>
|
||||
public class StatesGui : MonoBehaviour
|
||||
{
|
||||
public Rect GuiOffset = new Rect(250, 0, 300, 300);
|
||||
public bool DontDestroy = true;
|
||||
public bool ServerTimestamp;
|
||||
public bool DetailedConnection;
|
||||
public bool Server;
|
||||
public bool AppVersion;
|
||||
public bool UserId;
|
||||
public bool Room;
|
||||
public bool RoomProps;
|
||||
public bool EventsIn;
|
||||
public bool LocalPlayer;
|
||||
public bool PlayerProps;
|
||||
public bool Others;
|
||||
public bool Buttons;
|
||||
public bool ExpectedUsers;
|
||||
|
||||
private Rect GuiRect = new Rect();
|
||||
private static StatesGui Instance;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
DestroyImmediate(this.gameObject);
|
||||
return;
|
||||
}
|
||||
if (DontDestroy)
|
||||
{
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(this.gameObject);
|
||||
}
|
||||
|
||||
if (EventsIn)
|
||||
{
|
||||
PhotonNetwork.NetworkingClient.LoadBalancingPeer.TrafficStatsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (DontDestroy && Instance == this)
|
||||
{
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
float native_width = 800;
|
||||
float native_height = 480;
|
||||
void OnGUI()
|
||||
{
|
||||
if (PhotonNetwork.NetworkingClient == null || PhotonNetwork.NetworkingClient.LoadBalancingPeer == null || PhotonNetwork.NetworkingClient.LoadBalancingPeer.TrafficStatsIncoming == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//set up scaling
|
||||
float rx = Screen.width / native_width;
|
||||
float ry = Screen.height / native_height;
|
||||
GUI.matrix = Matrix4x4.TRS (new Vector3(0, 0, 0), Quaternion.identity, new Vector3 (rx, ry, 1));
|
||||
|
||||
Rect GuiOffsetRuntime = new Rect(this.GuiOffset);
|
||||
|
||||
if (GuiOffsetRuntime.x < 0)
|
||||
{
|
||||
GuiOffsetRuntime.x = Screen.width - GuiOffsetRuntime.width;
|
||||
}
|
||||
GuiRect.xMin = GuiOffsetRuntime.x;
|
||||
GuiRect.yMin = GuiOffsetRuntime.y;
|
||||
GuiRect.xMax = GuiOffsetRuntime.x + GuiOffsetRuntime.width;
|
||||
GuiRect.yMax = GuiOffsetRuntime.y + GuiOffsetRuntime.height;
|
||||
GUILayout.BeginArea(GuiRect);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
if (this.ServerTimestamp)
|
||||
{
|
||||
GUILayout.Label((((double)PhotonNetwork.ServerTimestamp) / 1000d).ToString("F3"));
|
||||
}
|
||||
|
||||
if (Server)
|
||||
{
|
||||
GUILayout.Label(PhotonNetwork.ServerAddress + " " + PhotonNetwork.Server);
|
||||
}
|
||||
if (DetailedConnection)
|
||||
{
|
||||
GUILayout.Label(PhotonNetwork.NetworkClientState.ToString());
|
||||
}
|
||||
if (AppVersion)
|
||||
{
|
||||
GUILayout.Label(PhotonNetwork.NetworkingClient.AppVersion);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
if (UserId)
|
||||
{
|
||||
GUILayout.Label("UID: " + ((PhotonNetwork.AuthValues != null) ? PhotonNetwork.AuthValues.UserId : "no UserId"));
|
||||
GUILayout.Label("UserId:" + PhotonNetwork.LocalPlayer.UserId);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (Room)
|
||||
{
|
||||
if (PhotonNetwork.InRoom)
|
||||
{
|
||||
GUILayout.Label(this.RoomProps ? PhotonNetwork.CurrentRoom.ToStringFull() : PhotonNetwork.CurrentRoom.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("not in room");
|
||||
}
|
||||
}
|
||||
|
||||
if (EventsIn)
|
||||
{
|
||||
int fragments = PhotonNetwork.NetworkingClient.LoadBalancingPeer.TrafficStatsIncoming.FragmentCommandCount;
|
||||
GUILayout.Label("Events Received: "+PhotonNetwork.NetworkingClient.LoadBalancingPeer.TrafficStatsGameLevel.EventCount + " Fragments: "+fragments);
|
||||
}
|
||||
|
||||
|
||||
if (this.LocalPlayer)
|
||||
{
|
||||
GUILayout.Label(PlayerToString(PhotonNetwork.LocalPlayer));
|
||||
}
|
||||
if (Others)
|
||||
{
|
||||
foreach (Player player in PhotonNetwork.PlayerListOthers)
|
||||
{
|
||||
GUILayout.Label(PlayerToString(player));
|
||||
}
|
||||
}
|
||||
if (ExpectedUsers)
|
||||
{
|
||||
if (PhotonNetwork.InRoom)
|
||||
{
|
||||
int countExpected = (PhotonNetwork.CurrentRoom.ExpectedUsers != null) ? PhotonNetwork.CurrentRoom.ExpectedUsers.Length : 0;
|
||||
|
||||
GUILayout.Label("Expected: " + countExpected + " " +
|
||||
((PhotonNetwork.CurrentRoom.ExpectedUsers != null) ? string.Join(",", PhotonNetwork.CurrentRoom.ExpectedUsers) : "")
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Buttons)
|
||||
{
|
||||
if (!PhotonNetwork.IsConnected && GUILayout.Button("Connect"))
|
||||
{
|
||||
PhotonNetwork.ConnectUsingSettings();
|
||||
}
|
||||
GUILayout.BeginHorizontal();
|
||||
if (PhotonNetwork.IsConnected && GUILayout.Button("Disconnect"))
|
||||
{
|
||||
PhotonNetwork.Disconnect();
|
||||
}
|
||||
if (PhotonNetwork.IsConnected && GUILayout.Button("Close Socket"))
|
||||
{
|
||||
PhotonNetwork.NetworkingClient.LoadBalancingPeer.StopThread();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
if (PhotonNetwork.IsConnected && PhotonNetwork.InRoom && GUILayout.Button("Leave"))
|
||||
{
|
||||
PhotonNetwork.LeaveRoom();
|
||||
}
|
||||
if (PhotonNetwork.IsConnected && PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom.PlayerTtl>0 && GUILayout.Button("Leave(abandon)"))
|
||||
{
|
||||
PhotonNetwork.LeaveRoom(false);
|
||||
}
|
||||
if (PhotonNetwork.IsConnected && !PhotonNetwork.InRoom && GUILayout.Button("Join Random"))
|
||||
{
|
||||
PhotonNetwork.JoinRandomRoom();
|
||||
}
|
||||
if (PhotonNetwork.IsConnected && !PhotonNetwork.InRoom && GUILayout.Button("Create Room"))
|
||||
{
|
||||
PhotonNetwork.CreateRoom(null);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
private string PlayerToString(Player player)
|
||||
{
|
||||
if (PhotonNetwork.NetworkingClient == null)
|
||||
{
|
||||
Debug.LogError("nwp is null");
|
||||
return "";
|
||||
}
|
||||
return string.Format("#{0:00} '{1}'{5} {4}{2} {3} {6}", player.ActorNumber + "/userId:<" + player.UserId + ">", player.NickName, player.IsMasterClient ? "(master)" : "", this.PlayerProps ? player.CustomProperties.ToStringFull() : "", (PhotonNetwork.LocalPlayer.ActorNumber == player.ActorNumber) ? "(you)" : "", player.UserId, player.IsInactive ? " / Is Inactive" : "");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62880f27e95abf2418fd79e9d9d568b4
|
||||
timeCreated: 1493998271
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94d453efd58794348867b36584d05a1e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1d46a1c486645945a3667d8bbe2d2e7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,246 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="PhotonTeamsManagerEditor.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Custom inspector for PhotonTeamsManager
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Photon.Realtime;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
[CustomEditor(typeof(PhotonTeamsManager))]
|
||||
public class PhotonTeamsManagerEditor : Editor
|
||||
{
|
||||
private Dictionary<byte, bool> foldouts = new Dictionary<byte, bool>();
|
||||
private PhotonTeamsManager photonTeams;
|
||||
private SerializedProperty teamsListSp;
|
||||
private SerializedProperty listFoldIsOpenSp;
|
||||
|
||||
private const string proSkinString =
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAYAAACzzX7wAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACJJREFUeNpi/P//PwM+wHL06FG8KpgYCABGZWVlvCYABBgA7/sHvGw+cz8AAAAASUVORK5CYII=";
|
||||
private const string lightSkinString = "iVBORw0KGgoAAAANSUhEUgAAAAgAAAACCAIAAADq9gq6AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABVJREFUeNpiVFZWZsAGmBhwAIAAAwAURgBt4C03ZwAAAABJRU5ErkJggg==";
|
||||
private const string removeTextureName = "removeButton_generated";
|
||||
private Texture removeTexture;
|
||||
|
||||
private bool isOpen;
|
||||
|
||||
public override bool RequiresConstantRepaint()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
photonTeams = target as PhotonTeamsManager;
|
||||
teamsListSp = serializedObject.FindProperty("teamsList");
|
||||
listFoldIsOpenSp = serializedObject.FindProperty("listFoldIsOpen");
|
||||
isOpen = listFoldIsOpenSp.boolValue;
|
||||
removeTexture = LoadTexture(removeTextureName, proSkinString, lightSkinString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read width and height if PNG file in pixels.
|
||||
/// </summary>
|
||||
/// <param name="imageData">PNG image data.</param>
|
||||
/// <param name="width">Width of image in pixels.</param>
|
||||
/// <param name="height">Height of image in pixels.</param>
|
||||
private static void GetImageSize( byte[] imageData, out int width, out int height )
|
||||
{
|
||||
width = ReadInt( imageData, 3 + 15 );
|
||||
height = ReadInt( imageData, 3 + 15 + 2 + 2 );
|
||||
}
|
||||
|
||||
private static int ReadInt( byte[] imageData, int offset )
|
||||
{
|
||||
return ( imageData[ offset ] << 8 ) | imageData[ offset + 1 ];
|
||||
}
|
||||
|
||||
private Texture LoadTexture(string textureName, string proSkin, string lightSkin)
|
||||
{
|
||||
string skin = EditorGUIUtility.isProSkin ? proSkin : lightSkin;
|
||||
// Get image data (PNG) from base64 encoded strings.
|
||||
byte[] imageData = Convert.FromBase64String( skin );
|
||||
// Gather image size from image data.
|
||||
int texWidth, texHeight;
|
||||
GetImageSize( imageData, out texWidth, out texHeight );
|
||||
// Generate texture asset.
|
||||
var tex = new Texture2D( texWidth, texHeight, TextureFormat.ARGB32, false, true );
|
||||
tex.hideFlags = HideFlags.HideAndDontSave;
|
||||
tex.name = textureName;
|
||||
tex.filterMode = FilterMode.Point;
|
||||
tex.LoadImage( imageData );
|
||||
return tex;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
DrawTeamsList();
|
||||
return;
|
||||
}
|
||||
PhotonTeam[] availableTeams = photonTeams.GetAvailableTeams();
|
||||
if (availableTeams != null)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
foreach (var availableTeam in availableTeams)
|
||||
{
|
||||
if (!foldouts.ContainsKey(availableTeam.Code))
|
||||
{
|
||||
foldouts[availableTeam.Code] = true;
|
||||
}
|
||||
Player[] teamMembers;
|
||||
if (photonTeams.TryGetTeamMembers(availableTeam, out teamMembers) && teamMembers != null)
|
||||
{
|
||||
foldouts[availableTeam.Code] = EditorGUILayout.Foldout(foldouts[availableTeam.Code],
|
||||
string.Format("{0} ({1})", availableTeam.Name, teamMembers.Length));
|
||||
}
|
||||
else
|
||||
{
|
||||
foldouts[availableTeam.Code] = EditorGUILayout.Foldout(foldouts[availableTeam.Code],
|
||||
string.Format("{0} (0)", availableTeam.Name));
|
||||
}
|
||||
if (foldouts[availableTeam.Code] && teamMembers != null)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
foreach (var player in teamMembers)
|
||||
{
|
||||
EditorGUILayout.LabelField(string.Empty, string.Format("{0} {1}", player, player.IsLocal ? " - You -" : string.Empty));
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTeamsList()
|
||||
{
|
||||
GUILayout.Space(5);
|
||||
HashSet<byte> codes = new HashSet<byte>();
|
||||
HashSet<string> names = new HashSet<string>();
|
||||
for (int i = 0; i < teamsListSp.arraySize; i++)
|
||||
{
|
||||
SerializedProperty e = teamsListSp.GetArrayElementAtIndex(i);
|
||||
string name = e.FindPropertyRelative("Name").stringValue;
|
||||
byte code = (byte)e.FindPropertyRelative("Code").intValue;
|
||||
codes.Add(code);
|
||||
names.Add(name);
|
||||
}
|
||||
this.serializedObject.Update();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
isOpen = PhotonGUI.ContainerHeaderFoldout(string.Format("Teams List ({0})", teamsListSp.arraySize), isOpen);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
listFoldIsOpenSp.boolValue = isOpen;
|
||||
}
|
||||
if (isOpen)
|
||||
{
|
||||
const float containerElementHeight = 22;
|
||||
const float propertyHeight = 16;
|
||||
const float paddingRight = 29;
|
||||
const float paddingLeft = 5;
|
||||
const float spacingY = 3;
|
||||
float containerHeight = (teamsListSp.arraySize + 1) * containerElementHeight;
|
||||
Rect containerRect = PhotonGUI.ContainerBody(containerHeight);
|
||||
float propertyWidth = containerRect.width - paddingRight;
|
||||
float codePropertyWidth = propertyWidth / 5;
|
||||
float namePropertyWidth = 4 * propertyWidth / 5;
|
||||
Rect elementRect = new Rect(containerRect.xMin, containerRect.yMin,
|
||||
containerRect.width, containerElementHeight);
|
||||
Rect propertyPosition = new Rect(elementRect.xMin + paddingLeft, elementRect.yMin + spacingY,
|
||||
codePropertyWidth, propertyHeight);
|
||||
EditorGUI.LabelField(propertyPosition, "Code");
|
||||
Rect secondPropertyPosition = new Rect(elementRect.xMin + paddingLeft + codePropertyWidth, elementRect.yMin + spacingY,
|
||||
namePropertyWidth, propertyHeight);
|
||||
EditorGUI.LabelField(secondPropertyPosition, "Name");
|
||||
for (int i = 0; i < teamsListSp.arraySize; ++i)
|
||||
{
|
||||
elementRect = new Rect(containerRect.xMin, containerRect.yMin + containerElementHeight * (i + 1),
|
||||
containerRect.width, containerElementHeight);
|
||||
propertyPosition = new Rect(elementRect.xMin + paddingLeft, elementRect.yMin + spacingY,
|
||||
codePropertyWidth, propertyHeight);
|
||||
SerializedProperty teamElementSp = teamsListSp.GetArrayElementAtIndex(i);
|
||||
SerializedProperty teamNameSp = teamElementSp.FindPropertyRelative("Name");
|
||||
SerializedProperty teamCodeSp = teamElementSp.FindPropertyRelative("Code");
|
||||
string oldName = teamNameSp.stringValue;
|
||||
byte oldCode = (byte)teamCodeSp.intValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.PropertyField(propertyPosition, teamCodeSp, GUIContent.none);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
byte newCode = (byte)teamCodeSp.intValue;
|
||||
if (codes.Contains(newCode))
|
||||
{
|
||||
Debug.LogWarningFormat("Team with the same code {0} already exists", newCode);
|
||||
teamCodeSp.intValue = oldCode;
|
||||
}
|
||||
}
|
||||
secondPropertyPosition = new Rect(elementRect.xMin + paddingLeft + codePropertyWidth, elementRect.yMin + spacingY,
|
||||
namePropertyWidth, propertyHeight);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.PropertyField(secondPropertyPosition, teamNameSp, GUIContent.none);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
string newName = teamNameSp.stringValue;
|
||||
if (string.IsNullOrEmpty(newName))
|
||||
{
|
||||
Debug.LogWarning("Team name cannot be null or empty");
|
||||
teamNameSp.stringValue = oldName;
|
||||
}
|
||||
else if (names.Contains(newName))
|
||||
{
|
||||
Debug.LogWarningFormat("Team with the same name \"{0}\" already exists", newName);
|
||||
teamNameSp.stringValue = oldName;
|
||||
}
|
||||
}
|
||||
Rect removeButtonRect = new Rect(
|
||||
elementRect.xMax - PhotonGUI.DefaultRemoveButtonStyle.fixedWidth,
|
||||
elementRect.yMin + 2,
|
||||
PhotonGUI.DefaultRemoveButtonStyle.fixedWidth,
|
||||
PhotonGUI.DefaultRemoveButtonStyle.fixedHeight);
|
||||
if (GUI.Button(removeButtonRect, new GUIContent(removeTexture), PhotonGUI.DefaultRemoveButtonStyle))
|
||||
{
|
||||
teamsListSp.DeleteArrayElementAtIndex(i);
|
||||
}
|
||||
if (i < teamsListSp.arraySize - 1)
|
||||
{
|
||||
Rect texturePosition = new Rect(elementRect.xMin + 2, elementRect.yMax, elementRect.width - 4,
|
||||
1);
|
||||
PhotonGUI.DrawSplitter(texturePosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (PhotonGUI.AddButton())
|
||||
{
|
||||
byte c = 0;
|
||||
while (codes.Contains(c) && c < byte.MaxValue)
|
||||
{
|
||||
c++;
|
||||
}
|
||||
this.teamsListSp.arraySize++;
|
||||
SerializedProperty teamElementSp = this.teamsListSp.GetArrayElementAtIndex(teamsListSp.arraySize - 1);
|
||||
SerializedProperty teamNameSp = teamElementSp.FindPropertyRelative("Name");
|
||||
SerializedProperty teamCodeSp = teamElementSp.FindPropertyRelative("Code");
|
||||
teamCodeSp.intValue = c;
|
||||
string n = "New Team";
|
||||
int o = 1;
|
||||
while (names.Contains(n))
|
||||
{
|
||||
n = string.Format("New Team {0}", o);
|
||||
o++;
|
||||
}
|
||||
teamNameSp.stringValue = n;
|
||||
}
|
||||
this.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cb74f08e3fc52942a0d8557772bf4dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "PhotonUnityNetworking.Utilities.PhotonPlayer.Editor",
|
||||
"references": [
|
||||
"PhotonRealtime",
|
||||
"PhotonUnityNetworking",
|
||||
"PhotonUnityNetworking.Utilities",
|
||||
"PhotonUnityNetworking.Editor"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7024f760fc566cf45a16d2c838e22b2d
|
||||
timeCreated: 1537459565
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,67 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="PlayerNumberingInspector.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Custom inspector for PlayerNumbering
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
using Photon.Pun;
|
||||
using Photon.Realtime;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
[CustomEditor(typeof(PlayerNumbering))]
|
||||
public class PlayerNumberingInspector : Editor {
|
||||
|
||||
int localPlayerIndex;
|
||||
|
||||
void OnEnable () {
|
||||
PlayerNumbering.OnPlayerNumberingChanged += RefreshData;
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
PlayerNumbering.OnPlayerNumberingChanged -= RefreshData;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
PlayerNumbering.OnPlayerNumberingChanged += RefreshData;
|
||||
|
||||
if (PhotonNetwork.InRoom)
|
||||
{
|
||||
EditorGUILayout.LabelField("Player Index", "Player ID");
|
||||
if (PlayerNumbering.SortedPlayers != null)
|
||||
{
|
||||
foreach(Player punPlayer in PlayerNumbering.SortedPlayers)
|
||||
{
|
||||
GUI.enabled = punPlayer.ActorNumber > 0;
|
||||
EditorGUILayout.LabelField("Player " +punPlayer.GetPlayerNumber() + (punPlayer.IsLocal?" - You -":""), punPlayer.ActorNumber == 0?"n/a":punPlayer.ToStringFull());
|
||||
GUI.enabled = true;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
GUILayout.Label("PlayerNumbering only works when localPlayer is inside a room");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// force repaint fo the inspector, else we would not see the new data in the inspector.
|
||||
/// This is better then doing it in OnInspectorGUI too many times per frame for no need
|
||||
/// </summary>
|
||||
void RefreshData()
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6590f39353bf4efdb3b14691166135f
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,64 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="PunTeamsInspector.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Custom inspector for PunTeams
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
using Photon.Pun;
|
||||
using Photon.Realtime;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
#pragma warning disable 0618
|
||||
[CustomEditor(typeof(PunTeams))]
|
||||
public class PunTeamsInspector : Editor {
|
||||
|
||||
|
||||
Dictionary<PunTeams.Team, bool> _Foldouts ;
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (_Foldouts==null)
|
||||
{
|
||||
_Foldouts = new Dictionary<PunTeams.Team, bool>();
|
||||
}
|
||||
|
||||
if (PunTeams.PlayersPerTeam!=null)
|
||||
{
|
||||
foreach (KeyValuePair<PunTeams.Team,List<Player>> _pair in PunTeams.PlayersPerTeam)
|
||||
{
|
||||
#pragma warning restore 0618
|
||||
if (!_Foldouts.ContainsKey(_pair.Key))
|
||||
{
|
||||
_Foldouts[_pair.Key] = true;
|
||||
}
|
||||
|
||||
_Foldouts[_pair.Key] = EditorGUILayout.Foldout(_Foldouts[_pair.Key],"Team "+_pair.Key +" ("+_pair.Value.Count+")");
|
||||
|
||||
if (_Foldouts[_pair.Key])
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
foreach(Player _player in _pair.Value)
|
||||
{
|
||||
EditorGUILayout.LabelField("",_player.ToString() + (PhotonNetwork.LocalPlayer==_player?" - You -":""));
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7dcadaf22424c4f5d82f4d48c3b8097f
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,628 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="PhotonTeamsManager.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Implements teams in a room/game with help of player properties.
|
||||
// </summary>
|
||||
// <remarks>
|
||||
// Teams are defined by name and code. Change this to get more / different teams.
|
||||
// There are no rules when / if you can join a team. You could add this in JoinTeam or something.
|
||||
// </remarks>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using Photon.Realtime;
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
[Serializable]
|
||||
public class PhotonTeam
|
||||
{
|
||||
public string Name;
|
||||
public byte Code;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} [{1}]", this.Name, this.Code);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements teams in a room/game with help of player properties. Access them by Player.GetTeam extension.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Teams are defined by enum Team. Change this to get more / different teams.
|
||||
/// There are no rules when / if you can join a team. You could add this in JoinTeam or something.
|
||||
/// </remarks>
|
||||
[DisallowMultipleComponent]
|
||||
public class PhotonTeamsManager : MonoBehaviour, IMatchmakingCallbacks, IInRoomCallbacks
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
#pragma warning disable 0414
|
||||
[SerializeField]
|
||||
private bool listFoldIsOpen = true;
|
||||
#pragma warning restore 0414
|
||||
#endif
|
||||
|
||||
[SerializeField]
|
||||
private List<PhotonTeam> teamsList = new List<PhotonTeam>
|
||||
{
|
||||
new PhotonTeam { Name = "Blue", Code = 1 },
|
||||
new PhotonTeam { Name = "Red", Code = 2 }
|
||||
};
|
||||
|
||||
private Dictionary<byte, PhotonTeam> teamsByCode;
|
||||
private Dictionary<string, PhotonTeam> teamsByName;
|
||||
|
||||
/// <summary>The main list of teams with their player-lists. Automatically kept up to date.</summary>
|
||||
private Dictionary<byte, HashSet<Player>> playersPerTeam;
|
||||
|
||||
/// <summary>Defines the player custom property name to use for team affinity of "this" player.</summary>
|
||||
public const string TeamPlayerProp = "_pt";
|
||||
|
||||
public static event Action<Player, PhotonTeam> PlayerJoinedTeam;
|
||||
public static event Action<Player, PhotonTeam> PlayerLeftTeam;
|
||||
|
||||
private static PhotonTeamsManager instance;
|
||||
public static PhotonTeamsManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = FindObjectOfType<PhotonTeamsManager>();
|
||||
if (instance == null)
|
||||
{
|
||||
GameObject obj = new GameObject();
|
||||
obj.name = "PhotonTeamsManager";
|
||||
instance = obj.AddComponent<PhotonTeamsManager>();
|
||||
}
|
||||
instance.Init();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
#region MonoBehaviour
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (instance == null || ReferenceEquals(this, instance))
|
||||
{
|
||||
this.Init();
|
||||
instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
PhotonNetwork.AddCallbackTarget(this);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
PhotonNetwork.RemoveCallbackTarget(this);
|
||||
this.ClearTeams();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
teamsByCode = new Dictionary<byte, PhotonTeam>(teamsList.Count);
|
||||
teamsByName = new Dictionary<string, PhotonTeam>(teamsList.Count);
|
||||
playersPerTeam = new Dictionary<byte, HashSet<Player>>(teamsList.Count);
|
||||
for (int i = 0; i < teamsList.Count; i++)
|
||||
{
|
||||
teamsByCode[teamsList[i].Code] = teamsList[i];
|
||||
teamsByName[teamsList[i].Name] = teamsList[i];
|
||||
playersPerTeam[teamsList[i].Code] = new HashSet<Player>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IMatchmakingCallbacks
|
||||
|
||||
void IMatchmakingCallbacks.OnJoinedRoom()
|
||||
{
|
||||
this.UpdateTeams();
|
||||
}
|
||||
|
||||
void IMatchmakingCallbacks.OnLeftRoom()
|
||||
{
|
||||
this.ClearTeams();
|
||||
}
|
||||
|
||||
void IInRoomCallbacks.OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
|
||||
{
|
||||
object temp;
|
||||
if (changedProps.TryGetValue(TeamPlayerProp, out temp))
|
||||
{
|
||||
if (temp == null)
|
||||
{
|
||||
foreach (byte code in playersPerTeam.Keys)
|
||||
{
|
||||
if (playersPerTeam[code].Remove(targetPlayer))
|
||||
{
|
||||
if (PlayerLeftTeam != null)
|
||||
{
|
||||
PlayerLeftTeam(targetPlayer, teamsByCode[code]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (temp is byte)
|
||||
{
|
||||
byte teamCode = (byte) temp;
|
||||
// check if player switched teams, remove from previous team
|
||||
foreach (byte code in playersPerTeam.Keys)
|
||||
{
|
||||
if (code == teamCode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (playersPerTeam[code].Remove(targetPlayer))
|
||||
{
|
||||
if (PlayerLeftTeam != null)
|
||||
{
|
||||
PlayerLeftTeam(targetPlayer, teamsByCode[code]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
PhotonTeam team = teamsByCode[teamCode];
|
||||
if (!playersPerTeam[teamCode].Add(targetPlayer))
|
||||
{
|
||||
Debug.LogWarningFormat("Unexpected situation while setting team {0} for player {1}, updating teams for all", team, targetPlayer);
|
||||
this.UpdateTeams();
|
||||
}
|
||||
if (PlayerJoinedTeam != null)
|
||||
{
|
||||
PlayerJoinedTeam(targetPlayer, team);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogErrorFormat("Unexpected: custom property key {0} should have of type byte, instead we got {1} of type {2}. Player: {3}",
|
||||
TeamPlayerProp, temp, temp.GetType(), targetPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IInRoomCallbacks.OnPlayerLeftRoom(Player otherPlayer)
|
||||
{
|
||||
if (otherPlayer.IsInactive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
PhotonTeam team = otherPlayer.GetPhotonTeam();
|
||||
if (team != null && !playersPerTeam[team.Code].Remove(otherPlayer))
|
||||
{
|
||||
Debug.LogWarningFormat("Unexpected situation while removing player {0} who left from team {1}, updating teams for all", otherPlayer, team);
|
||||
// revert to 'brute force' in case of unexpected situation
|
||||
this.UpdateTeams();
|
||||
}
|
||||
}
|
||||
|
||||
void IInRoomCallbacks.OnPlayerEnteredRoom(Player newPlayer)
|
||||
{
|
||||
PhotonTeam team = newPlayer.GetPhotonTeam();
|
||||
if (team == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (playersPerTeam[team.Code].Contains(newPlayer))
|
||||
{
|
||||
// player rejoined w/ same team
|
||||
return;
|
||||
}
|
||||
// check if player rejoined w/ different team, remove from previous team
|
||||
foreach (var key in teamsByCode.Keys)
|
||||
{
|
||||
if (playersPerTeam[key].Remove(newPlayer))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!playersPerTeam[team.Code].Add(newPlayer))
|
||||
{
|
||||
Debug.LogWarningFormat("Unexpected situation while adding player {0} who joined to team {1}, updating teams for all", newPlayer, team);
|
||||
// revert to 'brute force' in case of unexpected situation
|
||||
this.UpdateTeams();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private methods
|
||||
|
||||
private void UpdateTeams()
|
||||
{
|
||||
this.ClearTeams();
|
||||
for (int i = 0; i < PhotonNetwork.PlayerList.Length; i++)
|
||||
{
|
||||
Player player = PhotonNetwork.PlayerList[i];
|
||||
PhotonTeam playerTeam = player.GetPhotonTeam();
|
||||
if (playerTeam != null)
|
||||
{
|
||||
playersPerTeam[playerTeam.Code].Add(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearTeams()
|
||||
{
|
||||
foreach (var key in playersPerTeam.Keys)
|
||||
{
|
||||
playersPerTeam[key].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// Find a PhotonTeam using a team code.
|
||||
/// </summary>
|
||||
/// <param name="code">The team code.</param>
|
||||
/// <param name="team">The team to be assigned if found.</param>
|
||||
/// <returns>If successful or not.</returns>
|
||||
public bool TryGetTeamByCode(byte code, out PhotonTeam team)
|
||||
{
|
||||
return teamsByCode.TryGetValue(code, out team);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a PhotonTeam using a team name.
|
||||
/// </summary>
|
||||
/// <param name="teamName">The team name.</param>
|
||||
/// <param name="team">The team to be assigned if found.</param>
|
||||
/// <returns>If successful or not.</returns>
|
||||
public bool TryGetTeamByName(string teamName, out PhotonTeam team)
|
||||
{
|
||||
return teamsByName.TryGetValue(teamName, out team);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all teams available.
|
||||
/// </summary>
|
||||
/// <returns>Returns all teams available.</returns>
|
||||
public PhotonTeam[] GetAvailableTeams()
|
||||
{
|
||||
if (teamsList != null)
|
||||
{
|
||||
return teamsList.ToArray();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all players joined to a team using a team code.
|
||||
/// </summary>
|
||||
/// <param name="code">The code of the team.</param>
|
||||
/// <param name="members">The array of players to be filled.</param>
|
||||
/// <returns>If successful or not.</returns>
|
||||
public bool TryGetTeamMembers(byte code, out Player[] members)
|
||||
{
|
||||
members = null;
|
||||
HashSet<Player> players;
|
||||
if (this.playersPerTeam.TryGetValue(code, out players))
|
||||
{
|
||||
members = new Player[players.Count];
|
||||
int i = 0;
|
||||
foreach (var player in players)
|
||||
{
|
||||
members[i] = player;
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all players joined to a team using a team name.
|
||||
/// </summary>
|
||||
/// <param name="teamName">The name of the team.</param>
|
||||
/// <param name="members">The array of players to be filled.</param>
|
||||
/// <returns>If successful or not.</returns>
|
||||
public bool TryGetTeamMembers(string teamName, out Player[] members)
|
||||
{
|
||||
members = null;
|
||||
PhotonTeam team;
|
||||
if (this.TryGetTeamByName(teamName, out team))
|
||||
{
|
||||
return this.TryGetTeamMembers(team.Code, out members);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all players joined to a team.
|
||||
/// </summary>
|
||||
/// <param name="team">The team which will be used to find players.</param>
|
||||
/// <param name="members">The array of players to be filled.</param>
|
||||
/// <returns>If successful or not.</returns>
|
||||
public bool TryGetTeamMembers(PhotonTeam team, out Player[] members)
|
||||
{
|
||||
members = null;
|
||||
if (team != null)
|
||||
{
|
||||
return this.TryGetTeamMembers(team.Code, out members);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all team mates of a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player whose team mates will be searched.</param>
|
||||
/// <param name="teamMates">The array of players to be filled.</param>
|
||||
/// <returns>If successful or not.</returns>
|
||||
public bool TryGetTeamMatesOfPlayer(Player player, out Player[] teamMates)
|
||||
{
|
||||
teamMates = null;
|
||||
if (player == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
PhotonTeam team = player.GetPhotonTeam();
|
||||
if (team == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
HashSet<Player> players;
|
||||
if (this.playersPerTeam.TryGetValue(team.Code, out players))
|
||||
{
|
||||
if (!players.Contains(player))
|
||||
{
|
||||
Debug.LogWarningFormat("Unexpected situation while getting team mates of player {0} who is joined to team {1}, updating teams for all", player, team);
|
||||
// revert to 'brute force' in case of unexpected situation
|
||||
this.UpdateTeams();
|
||||
}
|
||||
teamMates = new Player[players.Count - 1];
|
||||
int i = 0;
|
||||
foreach (var p in players)
|
||||
{
|
||||
if (p.Equals(player))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
teamMates[i] = p;
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of players in a team by team code.
|
||||
/// </summary>
|
||||
/// <param name="code">Unique code of the team</param>
|
||||
/// <returns>Number of players joined to the team.</returns>
|
||||
public int GetTeamMembersCount(byte code)
|
||||
{
|
||||
PhotonTeam team;
|
||||
if (this.TryGetTeamByCode(code, out team))
|
||||
{
|
||||
return this.GetTeamMembersCount(team);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of players in a team by team name.
|
||||
/// </summary>
|
||||
/// <param name="name">Unique name of the team</param>
|
||||
/// <returns>Number of players joined to the team.</returns>
|
||||
public int GetTeamMembersCount(string name)
|
||||
{
|
||||
PhotonTeam team;
|
||||
if (this.TryGetTeamByName(name, out team))
|
||||
{
|
||||
return this.GetTeamMembersCount(team);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of players in a team.
|
||||
/// </summary>
|
||||
/// <param name="team">The team you want to know the size of</param>
|
||||
/// <returns>Number of players joined to the team.</returns>
|
||||
public int GetTeamMembersCount(PhotonTeam team)
|
||||
{
|
||||
HashSet<Player> players;
|
||||
if (team != null && this.playersPerTeam.TryGetValue(team.Code, out players) && players != null)
|
||||
{
|
||||
return players.Count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unused methods
|
||||
|
||||
void IMatchmakingCallbacks.OnFriendListUpdate(List<FriendInfo> friendList)
|
||||
{
|
||||
}
|
||||
|
||||
void IMatchmakingCallbacks.OnCreatedRoom()
|
||||
{
|
||||
}
|
||||
|
||||
void IMatchmakingCallbacks.OnCreateRoomFailed(short returnCode, string message)
|
||||
{
|
||||
}
|
||||
|
||||
void IMatchmakingCallbacks.OnJoinRoomFailed(short returnCode, string message)
|
||||
{
|
||||
}
|
||||
|
||||
void IMatchmakingCallbacks.OnJoinRandomFailed(short returnCode, string message)
|
||||
{
|
||||
}
|
||||
|
||||
void IInRoomCallbacks.OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
|
||||
{
|
||||
}
|
||||
|
||||
void IInRoomCallbacks.OnMasterClientSwitched(Player newMasterClient)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>Extension methods for the Player class that make use of PhotonTeamsManager.</summary>
|
||||
public static class PhotonTeamExtensions
|
||||
{
|
||||
/// <summary>Gets the team the player is currently joined to. Null if none.</summary>
|
||||
/// <returns>The team the player is currently joined to. Null if none.</returns>
|
||||
public static PhotonTeam GetPhotonTeam(this Player player)
|
||||
{
|
||||
object teamId;
|
||||
PhotonTeam team;
|
||||
if (player.CustomProperties.TryGetValue(PhotonTeamsManager.TeamPlayerProp, out teamId) && PhotonTeamsManager.Instance.TryGetTeamByCode((byte)teamId, out team))
|
||||
{
|
||||
return team;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Join a team.
|
||||
/// </summary>
|
||||
/// <param name="player">The player who will join a team.</param>
|
||||
/// <param name="team">The team to be joined.</param>
|
||||
/// <returns></returns>
|
||||
public static bool JoinTeam(this Player player, PhotonTeam team)
|
||||
{
|
||||
if (team == null)
|
||||
{
|
||||
Debug.LogWarning("JoinTeam failed: PhotonTeam provided is null");
|
||||
return false;
|
||||
}
|
||||
PhotonTeam currentTeam = player.GetPhotonTeam();
|
||||
if (currentTeam != null)
|
||||
{
|
||||
Debug.LogWarningFormat("JoinTeam failed: player ({0}) is already joined to a team ({1}), call SwitchTeam instead", player, team);
|
||||
return false;
|
||||
}
|
||||
return player.SetCustomProperties(new Hashtable { { PhotonTeamsManager.TeamPlayerProp, team.Code } });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Join a team using team code.
|
||||
/// </summary>
|
||||
/// <param name="player">The player who will join the team.</param>
|
||||
/// <param name="teamCode">The code fo the team to be joined.</param>
|
||||
/// <returns></returns>
|
||||
public static bool JoinTeam(this Player player, byte teamCode)
|
||||
{
|
||||
PhotonTeam team;
|
||||
return PhotonTeamsManager.Instance.TryGetTeamByCode(teamCode, out team) && player.JoinTeam(team);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Join a team using team name.
|
||||
/// </summary>
|
||||
/// <param name="player">The player who will join the team.</param>
|
||||
/// <param name="teamName">The name of the team to be joined.</param>
|
||||
/// <returns></returns>
|
||||
public static bool JoinTeam(this Player player, string teamName)
|
||||
{
|
||||
PhotonTeam team;
|
||||
return PhotonTeamsManager.Instance.TryGetTeamByName(teamName, out team) && player.JoinTeam(team);
|
||||
}
|
||||
|
||||
/// <summary>Switch that player's team to the one you assign.</summary>
|
||||
/// <remarks>Internally checks if this player is in that team already or not. Only team switches are actually sent.</remarks>
|
||||
/// <param name="player"></param>
|
||||
/// <param name="team"></param>
|
||||
public static bool SwitchTeam(this Player player, PhotonTeam team)
|
||||
{
|
||||
if (team == null)
|
||||
{
|
||||
Debug.LogWarning("SwitchTeam failed: PhotonTeam provided is null");
|
||||
return false;
|
||||
}
|
||||
PhotonTeam currentTeam = player.GetPhotonTeam();
|
||||
if (currentTeam == null)
|
||||
{
|
||||
Debug.LogWarningFormat("SwitchTeam failed: player ({0}) was not joined to any team, call JoinTeam instead", player);
|
||||
return false;
|
||||
}
|
||||
if (currentTeam.Code == team.Code)
|
||||
{
|
||||
Debug.LogWarningFormat("SwitchTeam failed: player ({0}) is already joined to the same team {1}", player, team);
|
||||
return false;
|
||||
}
|
||||
return player.SetCustomProperties(new Hashtable { { PhotonTeamsManager.TeamPlayerProp, team.Code } },
|
||||
new Hashtable { { PhotonTeamsManager.TeamPlayerProp, currentTeam.Code }});
|
||||
}
|
||||
|
||||
/// <summary>Switch the player's team using a team code.</summary>
|
||||
/// <remarks>Internally checks if this player is in that team already or not.</remarks>
|
||||
/// <param name="player">The player that will switch teams.</param>
|
||||
/// <param name="teamCode">The code of the team to switch to.</param>
|
||||
/// <returns>If the team switch request is queued to be sent to the server or done in case offline or not joined to a room yet.</returns>
|
||||
public static bool SwitchTeam(this Player player, byte teamCode)
|
||||
{
|
||||
PhotonTeam team;
|
||||
return PhotonTeamsManager.Instance.TryGetTeamByCode(teamCode, out team) && player.SwitchTeam(team);
|
||||
}
|
||||
|
||||
/// <summary>Switch the player's team using a team name.</summary>
|
||||
/// <remarks>Internally checks if this player is in that team already or not.</remarks>
|
||||
/// <param name="player">The player that will switch teams.</param>
|
||||
/// <param name="teamName">The name of the team to switch to.</param>
|
||||
/// <returns>If the team switch request is queued to be sent to the server or done in case offline or not joined to a room yet.</returns>
|
||||
public static bool SwitchTeam(this Player player, string teamName)
|
||||
{
|
||||
PhotonTeam team;
|
||||
return PhotonTeamsManager.Instance.TryGetTeamByName(teamName, out team) && player.SwitchTeam(team);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Leave the current team if any.
|
||||
/// </summary>
|
||||
/// <param name="player"></param>
|
||||
/// <returns>If the leaving team request is queued to be sent to the server or done in case offline or not joined to a room yet.</returns>
|
||||
public static bool LeaveCurrentTeam(this Player player)
|
||||
{
|
||||
PhotonTeam currentTeam = player.GetPhotonTeam();
|
||||
if (currentTeam == null)
|
||||
{
|
||||
Debug.LogWarningFormat("LeaveCurrentTeam failed: player ({0}) was not joined to any team", player);
|
||||
return false;
|
||||
}
|
||||
return player.SetCustomProperties(new Hashtable {{PhotonTeamsManager.TeamPlayerProp, null}}, new Hashtable {{PhotonTeamsManager.TeamPlayerProp, currentTeam.Code}});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the team mates.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to get the team mates of.</param>
|
||||
/// <param name="teamMates">The team mates array to fill.</param>
|
||||
/// <returns>If successful or not.</returns>
|
||||
public static bool TryGetTeamMates(this Player player, out Player[] teamMates)
|
||||
{
|
||||
return PhotonTeamsManager.Instance.TryGetTeamMatesOfPlayer(player, out teamMates);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8701526de72d8774fa165e66daf050ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,267 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="PlayerNumbering.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Assign numbers to Players in a room. Uses Room custom Properties
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Photon.Pun;
|
||||
using Photon.Realtime;
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements consistent numbering in a room/game with help of room properties. Access them by Player.GetPlayerNumber() extension.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// indexing ranges from 0 to the maximum number of Players.
|
||||
/// indexing remains for the player while in room.
|
||||
/// If a Player is numbered 2 and player numbered 1 leaves, numbered 1 become vacant and will assigned to the future player joining (the first available vacant number is assigned when joining)
|
||||
/// </remarks>
|
||||
public class PlayerNumbering : MonoBehaviourPunCallbacks
|
||||
{
|
||||
//TODO: Add a "numbers available" bool, to allow easy access to this?!
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The instance. EntryPoint to query about Room Indexing.
|
||||
/// </summary>
|
||||
public static PlayerNumbering instance;
|
||||
|
||||
public static Player[] SortedPlayers;
|
||||
|
||||
/// <summary>
|
||||
/// OnPlayerNumberingChanged delegate. Use
|
||||
/// </summary>
|
||||
public delegate void PlayerNumberingChanged();
|
||||
/// <summary>
|
||||
/// Called everytime the room Indexing was updated. Use this for discrete updates. Always better than brute force calls every frame.
|
||||
/// </summary>
|
||||
public static event PlayerNumberingChanged OnPlayerNumberingChanged;
|
||||
|
||||
|
||||
/// <summary>Defines the room custom property name to use for room player indexing tracking.</summary>
|
||||
public const string RoomPlayerIndexedProp = "pNr";
|
||||
|
||||
/// <summary>
|
||||
/// dont destroy on load flag for this Component's GameObject to survive Level Loading.
|
||||
/// </summary>
|
||||
public bool dontDestroyOnLoad = false;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region MonoBehaviours methods
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
|
||||
if (instance != null && instance != this && instance.gameObject != null)
|
||||
{
|
||||
GameObject.DestroyImmediate(instance.gameObject);
|
||||
}
|
||||
|
||||
instance = this;
|
||||
if (dontDestroyOnLoad)
|
||||
{
|
||||
DontDestroyOnLoad(this.gameObject);
|
||||
}
|
||||
|
||||
this.RefreshData();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region PunBehavior Overrides
|
||||
|
||||
public override void OnJoinedRoom()
|
||||
{
|
||||
this.RefreshData();
|
||||
}
|
||||
|
||||
public override void OnLeftRoom()
|
||||
{
|
||||
PhotonNetwork.LocalPlayer.CustomProperties.Remove(PlayerNumbering.RoomPlayerIndexedProp);
|
||||
}
|
||||
|
||||
public override void OnPlayerEnteredRoom(Player newPlayer)
|
||||
{
|
||||
this.RefreshData();
|
||||
}
|
||||
|
||||
public override void OnPlayerLeftRoom(Player otherPlayer)
|
||||
{
|
||||
this.RefreshData();
|
||||
}
|
||||
|
||||
public override void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
|
||||
{
|
||||
if (changedProps != null && changedProps.ContainsKey(PlayerNumbering.RoomPlayerIndexedProp))
|
||||
{
|
||||
this.RefreshData();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
// each player can select it's own playernumber in a room, if all "older" players already selected theirs
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Internal call Refresh the cached data and call the OnPlayerNumberingChanged delegate.
|
||||
/// </summary>
|
||||
public void RefreshData()
|
||||
{
|
||||
if (PhotonNetwork.CurrentRoom == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (PhotonNetwork.LocalPlayer.GetPlayerNumber() >= 0)
|
||||
{
|
||||
SortedPlayers = PhotonNetwork.CurrentRoom.Players.Values.OrderBy((p) => p.GetPlayerNumber()).ToArray();
|
||||
if (OnPlayerNumberingChanged != null)
|
||||
{
|
||||
OnPlayerNumberingChanged();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
HashSet<int> usedInts = new HashSet<int>();
|
||||
Player[] sorted = PhotonNetwork.PlayerList.OrderBy((p) => p.ActorNumber).ToArray();
|
||||
|
||||
string allPlayers = "all players: ";
|
||||
foreach (Player player in sorted)
|
||||
{
|
||||
allPlayers += player.ActorNumber + "=pNr:"+player.GetPlayerNumber()+", ";
|
||||
|
||||
int number = player.GetPlayerNumber();
|
||||
|
||||
// if it's this user, select a number and break
|
||||
// else:
|
||||
// check if that user has a number
|
||||
// if not, break!
|
||||
// else remember used numbers
|
||||
|
||||
if (player.IsLocal)
|
||||
{
|
||||
Debug.Log ("PhotonNetwork.CurrentRoom.PlayerCount = " + PhotonNetwork.CurrentRoom.PlayerCount);
|
||||
|
||||
// select a number
|
||||
for (int i = 0; i < PhotonNetwork.CurrentRoom.PlayerCount; i++)
|
||||
{
|
||||
if (!usedInts.Contains(i))
|
||||
{
|
||||
player.SetPlayerNumber(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// then break
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (number < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
usedInts.Add(number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Debug.Log(allPlayers);
|
||||
//Debug.Log(PhotonNetwork.LocalPlayer.ToStringFull() + " has PhotonNetwork.player.GetPlayerNumber(): " + PhotonNetwork.LocalPlayer.GetPlayerNumber());
|
||||
|
||||
SortedPlayers = PhotonNetwork.CurrentRoom.Players.Values.OrderBy((p) => p.GetPlayerNumber()).ToArray();
|
||||
if (OnPlayerNumberingChanged != null)
|
||||
{
|
||||
OnPlayerNumberingChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>Extension used for PlayerRoomIndexing and Player class.</summary>
|
||||
public static class PlayerNumberingExtensions
|
||||
{
|
||||
/// <summary>Extension for Player class to wrap up access to the player's custom property.
|
||||
/// Make sure you use the delegate 'OnPlayerNumberingChanged' to knoiw when you can query the PlayerNumber. Numbering can changes over time or not be yet assigned during the initial phase ( when player creates a room for example)
|
||||
/// </summary>
|
||||
/// <returns>persistent index in room. -1 for no indexing</returns>
|
||||
public static int GetPlayerNumber(this Player player)
|
||||
{
|
||||
if (player == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (PhotonNetwork.OfflineMode)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (!PhotonNetwork.IsConnectedAndReady)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
object value;
|
||||
if (player.CustomProperties.TryGetValue (PlayerNumbering.RoomPlayerIndexedProp, out value)) {
|
||||
return (byte)value;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the player number.
|
||||
/// It's not recommanded to manually interfere with the playerNumbering, but possible.
|
||||
/// </summary>
|
||||
/// <param name="player">Player.</param>
|
||||
/// <param name="playerNumber">Player number.</param>
|
||||
public static void SetPlayerNumber(this Player player, int playerNumber)
|
||||
{
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PhotonNetwork.OfflineMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerNumber < 0)
|
||||
{
|
||||
Debug.LogWarning("Setting invalid playerNumber: " + playerNumber + " for: " + player.ToStringFull());
|
||||
}
|
||||
|
||||
if (!PhotonNetwork.IsConnectedAndReady)
|
||||
{
|
||||
Debug.LogWarning("SetPlayerNumber was called in state: " + PhotonNetwork.NetworkClientState + ". Not IsConnectedAndReady.");
|
||||
return;
|
||||
}
|
||||
|
||||
int current = player.GetPlayerNumber();
|
||||
if (current != playerNumber)
|
||||
{
|
||||
Debug.Log("PlayerNumbering: Set number "+playerNumber);
|
||||
player.SetCustomProperties(new Hashtable() { { PlayerNumbering.RoomPlayerIndexedProp, (byte)playerNumber } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b28dd60f6abf16d4094cf0f642a043e2
|
||||
timeCreated: 1512563044
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,62 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="PunPlayerScores.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Scoring system for PhotonPlayer
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Photon.Pun;
|
||||
using Photon.Realtime;
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Scoring system for PhotonPlayer
|
||||
/// </summary>
|
||||
public class PunPlayerScores : MonoBehaviour
|
||||
{
|
||||
public const string PlayerScoreProp = "score";
|
||||
}
|
||||
|
||||
public static class ScoreExtensions
|
||||
{
|
||||
public static void SetScore(this Player player, int newScore)
|
||||
{
|
||||
Hashtable score = new Hashtable(); // using PUN's implementation of Hashtable
|
||||
score[PunPlayerScores.PlayerScoreProp] = newScore;
|
||||
|
||||
player.SetCustomProperties(score); // this locally sets the score and will sync it in-game asap.
|
||||
}
|
||||
|
||||
public static void AddScore(this Player player, int scoreToAddToCurrent)
|
||||
{
|
||||
int current = player.GetScore();
|
||||
current = current + scoreToAddToCurrent;
|
||||
|
||||
Hashtable score = new Hashtable(); // using PUN's implementation of Hashtable
|
||||
score[PunPlayerScores.PlayerScoreProp] = current;
|
||||
|
||||
player.SetCustomProperties(score); // this locally sets the score and will sync it in-game asap.
|
||||
}
|
||||
|
||||
public static int GetScore(this Player player)
|
||||
{
|
||||
object score;
|
||||
if (player.CustomProperties.TryGetValue(PunPlayerScores.PlayerScoreProp, out score))
|
||||
{
|
||||
return (int)score;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b4df3943860f1d45bfe232053a58d80
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,156 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="PunTeams.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Implements teams in a room/game with help of player properties. Access them by Player.GetTeam extension.
|
||||
// </summary>
|
||||
// <remarks>
|
||||
// Teams are defined by enum Team. Change this to get more / different teams.
|
||||
// There are no rules when / if you can join a team. You could add this in JoinTeam or something.
|
||||
// </remarks>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Photon.Pun;
|
||||
using Photon.Realtime;
|
||||
using ExitGames.Client.Photon;
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements teams in a room/game with help of player properties. Access them by Player.GetTeam extension.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Teams are defined by enum Team. Change this to get more / different teams.
|
||||
/// There are no rules when / if you can join a team. You could add this in JoinTeam or something.
|
||||
/// </remarks>
|
||||
[Obsolete("do not use this or add it to the scene. use PhotonTeamsManager instead")]
|
||||
public class PunTeams : MonoBehaviourPunCallbacks
|
||||
{
|
||||
/// <summary>Enum defining the teams available. First team should be neutral (it's the default value any field of this enum gets).</summary>
|
||||
[Obsolete("use custom PhotonTeam instead")]
|
||||
public enum Team : byte { none, red, blue };
|
||||
|
||||
/// <summary>The main list of teams with their player-lists. Automatically kept up to date.</summary>
|
||||
/// <remarks>Note that this is static. Can be accessed by PunTeam.PlayersPerTeam. You should not modify this.</remarks>
|
||||
[Obsolete("use PhotonTeamsManager.Instance.TryGetTeamMembers instead")]
|
||||
public static Dictionary<Team, List<Player>> PlayersPerTeam;
|
||||
|
||||
/// <summary>Defines the player custom property name to use for team affinity of "this" player.</summary>
|
||||
[Obsolete("do not use this. PhotonTeamsManager.TeamPlayerProp is used internally instead.")]
|
||||
public const string TeamPlayerProp = "team";
|
||||
|
||||
|
||||
#region Events by Unity and Photon
|
||||
|
||||
public void Start()
|
||||
{
|
||||
PlayersPerTeam = new Dictionary<Team, List<Player>>();
|
||||
Array enumVals = Enum.GetValues(typeof(Team));
|
||||
foreach (var enumVal in enumVals)
|
||||
{
|
||||
PlayersPerTeam[(Team)enumVal] = new List<Player>();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
this.Start();
|
||||
}
|
||||
|
||||
/// <summary>Needed to update the team lists when joining a room.</summary>
|
||||
/// <remarks>Called by PUN. See enum MonoBehaviourPunCallbacks for an explanation.</remarks>
|
||||
public override void OnJoinedRoom()
|
||||
{
|
||||
|
||||
this.UpdateTeams();
|
||||
}
|
||||
|
||||
public override void OnLeftRoom()
|
||||
{
|
||||
Start();
|
||||
}
|
||||
|
||||
/// <summary>Refreshes the team lists. It could be a non-team related property change, too.</summary>
|
||||
/// <remarks>Called by PUN. See enum MonoBehaviourPunCallbacks for an explanation.</remarks>
|
||||
public override void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
|
||||
{
|
||||
this.UpdateTeams();
|
||||
}
|
||||
|
||||
public override void OnPlayerLeftRoom(Player otherPlayer)
|
||||
{
|
||||
this.UpdateTeams();
|
||||
}
|
||||
|
||||
public override void OnPlayerEnteredRoom(Player newPlayer)
|
||||
{
|
||||
this.UpdateTeams();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[Obsolete("do not call this.")]
|
||||
public void UpdateTeams()
|
||||
{
|
||||
Array enumVals = Enum.GetValues(typeof(Team));
|
||||
foreach (var enumVal in enumVals)
|
||||
{
|
||||
PlayersPerTeam[(Team)enumVal].Clear();
|
||||
}
|
||||
|
||||
for (int i = 0; i < PhotonNetwork.PlayerList.Length; i++)
|
||||
{
|
||||
Player player = PhotonNetwork.PlayerList[i];
|
||||
Team playerTeam = player.GetTeam();
|
||||
PlayersPerTeam[playerTeam].Add(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Extension used for PunTeams and Player class. Wraps access to the player's custom property.</summary>
|
||||
public static class TeamExtensions
|
||||
{
|
||||
/// <summary>Extension for Player class to wrap up access to the player's custom property.</summary>
|
||||
/// <returns>PunTeam.Team.none if no team was found (yet).</returns>
|
||||
[Obsolete("Use player.GetPhotonTeam")]
|
||||
public static PunTeams.Team GetTeam(this Player player)
|
||||
{
|
||||
object teamId;
|
||||
if (player.CustomProperties.TryGetValue(PunTeams.TeamPlayerProp, out teamId))
|
||||
{
|
||||
return (PunTeams.Team)teamId;
|
||||
}
|
||||
|
||||
return PunTeams.Team.none;
|
||||
}
|
||||
|
||||
/// <summary>Switch that player's team to the one you assign.</summary>
|
||||
/// <remarks>Internally checks if this player is in that team already or not. Only team switches are actually sent.</remarks>
|
||||
/// <param name="player"></param>
|
||||
/// <param name="team"></param>
|
||||
[Obsolete("Use player.JoinTeam")]
|
||||
public static void SetTeam(this Player player, PunTeams.Team team)
|
||||
{
|
||||
if (!PhotonNetwork.IsConnectedAndReady)
|
||||
{
|
||||
Debug.LogWarning("JoinTeam was called in state: " + PhotonNetwork.NetworkClientState + ". Not IsConnectedAndReady.");
|
||||
return;
|
||||
}
|
||||
|
||||
PunTeams.Team currentTeam = player.GetTeam();
|
||||
if (currentTeam != team)
|
||||
{
|
||||
player.SetCustomProperties(new Hashtable() { { PunTeams.TeamPlayerProp, (byte)team } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6587c8104d7524f4280d0a68dd779f27
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "PhotonUnityNetworking.Utilities",
|
||||
"references": [
|
||||
"PhotonRealtime",
|
||||
"PhotonUnityNetworking"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a738238285a7a8246af046cbf977522f
|
||||
timeCreated: 1537459565
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 199121930ec5d4aa9bb53d836180b74f
|
||||
folderAsset: yes
|
||||
timeCreated: 1529327582
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,72 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="SmoothSyncMovement.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Smoothed out movement for network gameobjects
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Photon.Pun;
|
||||
using Photon.Realtime;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Smoothed out movement for network gameobjects
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(PhotonView))]
|
||||
public class SmoothSyncMovement : Photon.Pun.MonoBehaviourPun, IPunObservable
|
||||
{
|
||||
public float SmoothingDelay = 5;
|
||||
public void Awake()
|
||||
{
|
||||
bool observed = false;
|
||||
foreach (Component observedComponent in this.photonView.ObservedComponents)
|
||||
{
|
||||
if (observedComponent == this)
|
||||
{
|
||||
observed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!observed)
|
||||
{
|
||||
Debug.LogWarning(this + " is not observed by this object's photonView! OnPhotonSerializeView() in this class won't be used.");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
|
||||
{
|
||||
if (stream.IsWriting)
|
||||
{
|
||||
//We own this player: send the others our data
|
||||
stream.SendNext(transform.position);
|
||||
stream.SendNext(transform.rotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Network player, receive data
|
||||
correctPlayerPos = (Vector3)stream.ReceiveNext();
|
||||
correctPlayerRot = (Quaternion)stream.ReceiveNext();
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 correctPlayerPos = Vector3.zero; //We lerp towards this
|
||||
private Quaternion correctPlayerRot = Quaternion.identity; //We lerp towards this
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!photonView.IsMine)
|
||||
{
|
||||
//Update remote player (smooth this, this looks good, at the cost of some accuracy)
|
||||
transform.position = Vector3.Lerp(transform.position, correctPlayerPos, Time.deltaTime * this.SmoothingDelay);
|
||||
transform.rotation = Quaternion.Lerp(transform.rotation, correctPlayerRot, Time.deltaTime * this.SmoothingDelay);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35144820bcc25444bb8f0fd767d9423e
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e10fe700b1a744ec78575843923fe6d7
|
||||
folderAsset: yes
|
||||
timeCreated: 1529327245
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,136 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="ConnectAndJoinRandom.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Simple component to call ConnectUsingSettings and to get into a PUN room easily.
|
||||
// </summary>
|
||||
// <remarks>
|
||||
// A custom inspector provides a button to connect in PlayMode, should AutoConnect be false.
|
||||
// </remarks>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//#if UNITY_EDITOR
|
||||
//using UnityEditor;
|
||||
//#endif
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
//using Photon.Pun;
|
||||
using Photon.Realtime;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>Simple component to call ConnectUsingSettings and to get into a PUN room easily.</summary>
|
||||
/// <remarks>A custom inspector provides a button to connect in PlayMode, should AutoConnect be false.</remarks>
|
||||
public class ConnectAndJoinRandom : MonoBehaviourPunCallbacks
|
||||
{
|
||||
/// <summary>Connect automatically? If false you can set this to true later on or call ConnectUsingSettings in your own scripts.</summary>
|
||||
public bool AutoConnect = true;
|
||||
|
||||
/// <summary>Used as PhotonNetwork.GameVersion.</summary>
|
||||
public byte Version = 1;
|
||||
|
||||
/// <summary>Max number of players allowed in room. Once full, a new room will be created by the next connection attemping to join.</summary>
|
||||
[Tooltip("The max number of players allowed in room. Once full, a new room will be created by the next connection attemping to join.")]
|
||||
public byte MaxPlayers = 4;
|
||||
|
||||
public int playerTTL = -1;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (this.AutoConnect)
|
||||
{
|
||||
this.ConnectNow();
|
||||
}
|
||||
}
|
||||
|
||||
public void ConnectNow()
|
||||
{
|
||||
Debug.Log("ConnectAndJoinRandom.ConnectNow() will now call: PhotonNetwork.ConnectUsingSettings().");
|
||||
|
||||
|
||||
PhotonNetwork.ConnectUsingSettings();
|
||||
PhotonNetwork.GameVersion = this.Version + "." + SceneManagerHelper.ActiveSceneBuildIndex;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// below, we implement some callbacks of the Photon Realtime API.
|
||||
// Being a MonoBehaviourPunCallbacks means, we can override the few methods which are needed here.
|
||||
|
||||
|
||||
public override void OnConnectedToMaster()
|
||||
{
|
||||
Debug.Log("OnConnectedToMaster() was called by PUN. This client is now connected to Master Server in region [" + PhotonNetwork.CloudRegion +
|
||||
"] and can join a room. Calling: PhotonNetwork.JoinRandomRoom();");
|
||||
PhotonNetwork.JoinRandomRoom();
|
||||
}
|
||||
|
||||
public override void OnJoinedLobby()
|
||||
{
|
||||
Debug.Log("OnJoinedLobby(). This client is now connected to Relay in region [" + PhotonNetwork.CloudRegion + "]. This script now calls: PhotonNetwork.JoinRandomRoom();");
|
||||
PhotonNetwork.JoinRandomRoom();
|
||||
}
|
||||
|
||||
public override void OnJoinRandomFailed(short returnCode, string message)
|
||||
{
|
||||
Debug.Log("OnJoinRandomFailed() was called by PUN. No random room available in region [" + PhotonNetwork.CloudRegion + "], so we create one. Calling: PhotonNetwork.CreateRoom(null, new RoomOptions() {maxPlayers = 4}, null);");
|
||||
|
||||
RoomOptions roomOptions = new RoomOptions() { MaxPlayers = this.MaxPlayers };
|
||||
if (playerTTL >= 0)
|
||||
roomOptions.PlayerTtl = playerTTL;
|
||||
|
||||
PhotonNetwork.CreateRoom(null, roomOptions, null);
|
||||
}
|
||||
|
||||
// the following methods are implemented to give you some context. re-implement them as needed.
|
||||
public override void OnDisconnected(DisconnectCause cause)
|
||||
{
|
||||
Debug.Log("OnDisconnected(" + cause + ")");
|
||||
}
|
||||
|
||||
public override void OnJoinedRoom()
|
||||
{
|
||||
Debug.Log("OnJoinedRoom() called by PUN. Now this client is in a room in region [" + PhotonNetwork.CloudRegion + "]. Game is now running.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//#if UNITY_EDITOR
|
||||
//[CanEditMultipleObjects]
|
||||
//[CustomEditor(typeof(ConnectAndJoinRandom), true)]
|
||||
//public class ConnectAndJoinRandomInspector : Editor
|
||||
//{
|
||||
// void OnEnable() { EditorApplication.update += Update; }
|
||||
// void OnDisable() { EditorApplication.update -= Update; }
|
||||
|
||||
// bool isConnectedCache = false;
|
||||
|
||||
// void Update()
|
||||
// {
|
||||
// if (this.isConnectedCache != PhotonNetwork.IsConnected)
|
||||
// {
|
||||
// this.Repaint();
|
||||
// }
|
||||
// }
|
||||
|
||||
// public override void OnInspectorGUI()
|
||||
// {
|
||||
// this.isConnectedCache = !PhotonNetwork.IsConnected;
|
||||
|
||||
|
||||
// this.DrawDefaultInspector(); // Draw the normal inspector
|
||||
|
||||
// if (Application.isPlaying && !PhotonNetwork.IsConnected)
|
||||
// {
|
||||
// if (GUILayout.Button("Connect"))
|
||||
// {
|
||||
// ((ConnectAndJoinRandom)this.target).ConnectNow();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//#endif
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c1b84a427010e0469ce0df07ab64dbc
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,111 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="OnJoinedInstantiate.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Very basic component to move a GameObject by WASD and Space.
|
||||
// </summary>
|
||||
// <remarks>
|
||||
// Requires a PhotonView.
|
||||
// Disables itself on GameObjects that are not owned on Start.
|
||||
//
|
||||
// Speed affects movement-speed.
|
||||
// JumpForce defines how high the object "jumps".
|
||||
// JumpTimeout defines after how many seconds you can jump again.
|
||||
// </remarks>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Photon.Pun;
|
||||
using Photon.Realtime;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Very basic component to move a GameObject by WASD and Space.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requires a PhotonView.
|
||||
/// Disables itself on GameObjects that are not owned on Start.
|
||||
///
|
||||
/// Speed affects movement-speed.
|
||||
/// JumpForce defines how high the object "jumps".
|
||||
/// JumpTimeout defines after how many seconds you can jump again.
|
||||
/// </remarks>
|
||||
[RequireComponent(typeof(PhotonView))]
|
||||
public class MoveByKeys : Photon.Pun.MonoBehaviourPun
|
||||
{
|
||||
public float Speed = 10f;
|
||||
public float JumpForce = 200f;
|
||||
public float JumpTimeout = 0.5f;
|
||||
|
||||
private bool isSprite;
|
||||
private float jumpingTime;
|
||||
private Rigidbody body;
|
||||
private Rigidbody2D body2d;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
//enabled = photonView.isMine;
|
||||
this.isSprite = (GetComponent<SpriteRenderer>() != null);
|
||||
|
||||
this.body2d = GetComponent<Rigidbody2D>();
|
||||
this.body = GetComponent<Rigidbody>();
|
||||
}
|
||||
|
||||
|
||||
// Update is called once per frame
|
||||
public void FixedUpdate()
|
||||
{
|
||||
if (!photonView.IsMine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((Input.GetAxisRaw("Horizontal") < -0.1f) || (Input.GetAxisRaw("Horizontal") > 0.1f))
|
||||
{
|
||||
transform.position += Vector3.right * (Speed * Time.deltaTime) * Input.GetAxisRaw("Horizontal");
|
||||
}
|
||||
|
||||
// jumping has a simple "cooldown" time but you could also jump in the air
|
||||
if (this.jumpingTime <= 0.0f)
|
||||
{
|
||||
if (this.body != null || this.body2d != null)
|
||||
{
|
||||
// obj has a Rigidbody and can jump (AddForce)
|
||||
if (Input.GetKey(KeyCode.Space))
|
||||
{
|
||||
this.jumpingTime = this.JumpTimeout;
|
||||
|
||||
Vector2 jump = Vector2.up * this.JumpForce;
|
||||
if (this.body2d != null)
|
||||
{
|
||||
this.body2d.AddForce(jump);
|
||||
}
|
||||
else if (this.body != null)
|
||||
{
|
||||
this.body.AddForce(jump);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.jumpingTime -= Time.deltaTime;
|
||||
}
|
||||
|
||||
// 2d objects can't be moved in 3d "forward"
|
||||
if (!this.isSprite)
|
||||
{
|
||||
if ((Input.GetAxisRaw("Vertical") < -0.1f) || (Input.GetAxisRaw("Vertical") > 0.1f))
|
||||
{
|
||||
transform.position += Vector3.forward * (Speed * Time.deltaTime) * Input.GetAxisRaw("Vertical");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99b4daedc5e674a429acdf1e77da6a55
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,72 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="OnClickDestroy.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities
|
||||
// </copyright>
|
||||
// <summary>A compact script for prototyping.</summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the networked GameObject either by PhotonNetwork.Destroy or by sending an RPC which calls Object.Destroy().
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Using an RPC to Destroy a GameObject is typically a bad idea.
|
||||
/// It allows any player to Destroy a GameObject and may cause errors.
|
||||
///
|
||||
/// A client has to clean up the server's event-cache, which contains events for Instantiate and
|
||||
/// buffered RPCs related to the GO.
|
||||
///
|
||||
/// A buffered RPC gets cleaned up when the sending player leaves the room, so players joining later
|
||||
/// won't get those buffered RPCs. This in turn, may mean they don't destroy the GO due to coming later.
|
||||
///
|
||||
/// Vice versa, a GameObject Instantiate might get cleaned up when the creating player leaves a room.
|
||||
/// This way, the GameObject that a RPC targets might become lost.
|
||||
///
|
||||
/// It makes sense to test those cases. Many are not breaking errors and you just have to be aware of them.
|
||||
///
|
||||
///
|
||||
/// Gets OnClick() calls by Unity's IPointerClickHandler. Needs a PhysicsRaycaster on the camera.
|
||||
/// See: https://docs.unity3d.com/ScriptReference/EventSystems.IPointerClickHandler.html
|
||||
/// </remarks>
|
||||
public class OnClickDestroy : MonoBehaviourPun, IPointerClickHandler
|
||||
{
|
||||
public PointerEventData.InputButton Button;
|
||||
public KeyCode ModifierKey;
|
||||
|
||||
public bool DestroyByRpc;
|
||||
|
||||
|
||||
void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
if (!PhotonNetwork.InRoom || (this.ModifierKey != KeyCode.None && !Input.GetKey(this.ModifierKey)) || eventData.button != this.Button )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.DestroyByRpc)
|
||||
{
|
||||
this.photonView.RPC("DestroyRpc", RpcTarget.AllBuffered);
|
||||
}
|
||||
else
|
||||
{
|
||||
PhotonNetwork.Destroy(this.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[PunRPC]
|
||||
public IEnumerator DestroyRpc()
|
||||
{
|
||||
Destroy(this.gameObject);
|
||||
yield return 0; // if you allow 1 frame to pass, the object's OnDestroy() method gets called and cleans up references.
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9dab7328ba500e944a99d62065fba6c0
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,56 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="OnClickInstantiate.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities
|
||||
// </copyright>
|
||||
// <summary>A compact script for prototyping.</summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a networked GameObject on click.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Gets OnClick() calls by Unity's IPointerClickHandler. Needs a PhysicsRaycaster on the camera.
|
||||
/// See: https://docs.unity3d.com/ScriptReference/EventSystems.IPointerClickHandler.html
|
||||
/// </remarks>
|
||||
public class OnClickInstantiate : MonoBehaviour, IPointerClickHandler
|
||||
{
|
||||
public enum InstantiateOption { Mine, Scene }
|
||||
|
||||
|
||||
public PointerEventData.InputButton Button;
|
||||
public KeyCode ModifierKey;
|
||||
|
||||
public GameObject Prefab;
|
||||
|
||||
[SerializeField]
|
||||
private InstantiateOption InstantiateType = InstantiateOption.Mine;
|
||||
|
||||
|
||||
void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
if (!PhotonNetwork.InRoom || (this.ModifierKey != KeyCode.None && !Input.GetKey(this.ModifierKey)) || eventData.button != this.Button)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
switch (this.InstantiateType)
|
||||
{
|
||||
case InstantiateOption.Mine:
|
||||
PhotonNetwork.Instantiate(this.Prefab.name, eventData.pointerCurrentRaycast.worldPosition + new Vector3(0, 0.5f, 0), Quaternion.identity, 0);
|
||||
break;
|
||||
case InstantiateOption.Scene:
|
||||
PhotonNetwork.InstantiateRoomObject(this.Prefab.name, eventData.pointerCurrentRaycast.worldPosition + new Vector3(0, 0.5f, 0), Quaternion.identity, 0, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b79a9b62449de940a073364858c3f9b
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,89 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="OnClickInstantiate.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities
|
||||
// </copyright>
|
||||
// <summary>A compact script for prototyping.</summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
using System.Collections;
|
||||
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This component will instantiate a network GameObject when in a room and the user click on that component's GameObject.
|
||||
/// Uses PhysicsRaycaster for positioning.
|
||||
/// </summary>
|
||||
public class OnClickRpc : MonoBehaviourPun, IPointerClickHandler
|
||||
{
|
||||
public PointerEventData.InputButton Button;
|
||||
public KeyCode ModifierKey;
|
||||
|
||||
public RpcTarget Target;
|
||||
|
||||
void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
if (!PhotonNetwork.InRoom || (this.ModifierKey != KeyCode.None && !Input.GetKey(this.ModifierKey)) || eventData.button != this.Button)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.photonView.RPC("ClickRpc", this.Target);
|
||||
}
|
||||
|
||||
|
||||
#region RPC Implementation
|
||||
|
||||
private Material originalMaterial;
|
||||
private Color originalColor;
|
||||
private bool isFlashing;
|
||||
|
||||
[PunRPC]
|
||||
public void ClickRpc()
|
||||
{
|
||||
//Debug.Log("ClickRpc Called");
|
||||
this.StartCoroutine(this.ClickFlash());
|
||||
}
|
||||
|
||||
public IEnumerator ClickFlash()
|
||||
{
|
||||
if (isFlashing)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
isFlashing = true;
|
||||
|
||||
this.originalMaterial = GetComponent<Renderer>().material;
|
||||
if (!this.originalMaterial.HasProperty("_EmissionColor"))
|
||||
{
|
||||
Debug.LogWarning("Doesn't have emission, can't flash " + gameObject);
|
||||
yield break;
|
||||
}
|
||||
|
||||
bool wasEmissive = this.originalMaterial.IsKeywordEnabled("_EMISSION");
|
||||
this.originalMaterial.EnableKeyword("_EMISSION");
|
||||
|
||||
this.originalColor = this.originalMaterial.GetColor("_EmissionColor");
|
||||
this.originalMaterial.SetColor("_EmissionColor", Color.white);
|
||||
|
||||
for (float f = 0.0f; f <= 1.0f; f += 0.08f)
|
||||
{
|
||||
Color lerped = Color.Lerp(Color.white, this.originalColor, f);
|
||||
this.originalMaterial.SetColor("_EmissionColor", lerped);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
this.originalMaterial.SetColor("_EmissionColor", this.originalColor);
|
||||
if (!wasEmissive) this.originalMaterial.DisableKeyword("_EMISSION");
|
||||
isFlashing = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cd0ed7420882554fa146aab6aeb2e80
|
||||
timeCreated: 1536748184
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,32 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="OnJoinedInstantiate.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// This component will quit the application when escape key is pressed
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// This component will quit the application when escape key is pressed
|
||||
/// </summary>
|
||||
public class OnEscapeQuit : MonoBehaviour
|
||||
{
|
||||
[Conditional("UNITY_ANDROID"), Conditional("UNITY_IOS")]
|
||||
public void Update()
|
||||
{
|
||||
// "back" button of phone equals "Escape". quit app if that's pressed
|
||||
if (Input.GetKeyDown(KeyCode.Escape))
|
||||
{
|
||||
Application.Quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fbabafd914a48f4eb6108d8d8f43d29
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,450 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="OnJoinedInstantiate.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// This component will instantiate a network GameObject when a room is joined
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
using Photon.Realtime;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This component will instantiate a network GameObject when a room is joined
|
||||
/// </summary>
|
||||
public class OnJoinedInstantiate : MonoBehaviour
|
||||
, IMatchmakingCallbacks
|
||||
{
|
||||
public enum SpawnSequence { Connection, Random, RoundRobin }
|
||||
|
||||
#region Inspector Items
|
||||
|
||||
// Old field, only here for backwards compat. Value copies over to SpawnPoints in OnValidate
|
||||
[HideInInspector] private Transform SpawnPosition;
|
||||
|
||||
[HideInInspector] public SpawnSequence Sequence = SpawnSequence.Connection;
|
||||
|
||||
[HideInInspector] public List<Transform> SpawnPoints = new List<Transform>(1) { null };
|
||||
|
||||
[Tooltip("Add a random variance to a spawn point position. GetRandomOffset() can be overridden with your own method for producing offsets.")]
|
||||
[HideInInspector] public bool UseRandomOffset = true;
|
||||
|
||||
[Tooltip("Radius of the RandomOffset.")]
|
||||
[FormerlySerializedAs("PositionOffset")]
|
||||
[HideInInspector] public float RandomOffset = 2.0f;
|
||||
|
||||
[Tooltip("Disables the Y axis of RandomOffset. The Y value of the spawn point will be used.")]
|
||||
[HideInInspector] public bool ClampY = true;
|
||||
|
||||
[HideInInspector] public List<GameObject> PrefabsToInstantiate = new List<GameObject>(1) { null }; // set in inspector
|
||||
|
||||
[FormerlySerializedAs("autoSpawnObjects")]
|
||||
[HideInInspector] public bool AutoSpawnObjects = true;
|
||||
|
||||
#endregion
|
||||
|
||||
// Record of spawned objects, used for Despawn All
|
||||
public Stack<GameObject> SpawnedObjects = new Stack<GameObject>();
|
||||
protected int spawnedAsActorId;
|
||||
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
protected void OnValidate()
|
||||
{
|
||||
/// Check the prefab to make sure it is the actual resource, and not a scene object or other instance.
|
||||
if (PrefabsToInstantiate != null)
|
||||
for (int i = 0; i < PrefabsToInstantiate.Count; ++i)
|
||||
{
|
||||
var prefab = PrefabsToInstantiate[i];
|
||||
if (prefab)
|
||||
PrefabsToInstantiate[i] = ValidatePrefab(prefab);
|
||||
}
|
||||
|
||||
/// Move any values from old SpawnPosition field to new SpawnPoints
|
||||
if (SpawnPosition)
|
||||
{
|
||||
if (SpawnPoints == null)
|
||||
SpawnPoints = new List<Transform>();
|
||||
|
||||
SpawnPoints.Add(SpawnPosition);
|
||||
SpawnPosition = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate, and if valid add this prefab to the first null element of the list, or create a new element. Returns true if the object was added.
|
||||
/// </summary>
|
||||
/// <param name="prefab"></param>
|
||||
public bool AddPrefabToList(GameObject prefab)
|
||||
{
|
||||
var validated = ValidatePrefab(prefab);
|
||||
if (validated)
|
||||
{
|
||||
// Don't add to list if this prefab already is on the list
|
||||
if (PrefabsToInstantiate.Contains(validated))
|
||||
return false;
|
||||
|
||||
// First try to use any null array slots to keep things tidy
|
||||
if (PrefabsToInstantiate.Contains(null))
|
||||
PrefabsToInstantiate[PrefabsToInstantiate.IndexOf(null)] = validated;
|
||||
// Otherwise, just add this prefab.
|
||||
else
|
||||
PrefabsToInstantiate.Add(validated);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the supplied GameObject is an instance of a prefab, or the actual source Asset,
|
||||
/// and returns a best guess at the actual resource the dev intended to use.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected static GameObject ValidatePrefab(GameObject unvalidated)
|
||||
{
|
||||
if (unvalidated == null)
|
||||
return null;
|
||||
|
||||
if (!unvalidated.GetComponent<PhotonView>())
|
||||
return null;
|
||||
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
|
||||
GameObject validated = null;
|
||||
|
||||
if (unvalidated != null)
|
||||
{
|
||||
|
||||
if (PrefabUtility.IsPartOfPrefabAsset(unvalidated))
|
||||
return unvalidated;
|
||||
|
||||
var prefabStatus = PrefabUtility.GetPrefabInstanceStatus(unvalidated);
|
||||
var isValidPrefab = prefabStatus == PrefabInstanceStatus.Connected || prefabStatus == PrefabInstanceStatus.Disconnected;
|
||||
|
||||
if (isValidPrefab)
|
||||
validated = PrefabUtility.GetCorrespondingObjectFromSource(unvalidated) as GameObject;
|
||||
else
|
||||
return null;
|
||||
|
||||
if (!PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(validated).Contains("/Resources"))
|
||||
Debug.LogWarning("Player Prefab needs to be a Prefab in a Resource folder.");
|
||||
}
|
||||
#else
|
||||
GameObject validated = unvalidated;
|
||||
|
||||
if (unvalidated != null && PrefabUtility.GetPrefabType(unvalidated) != PrefabType.Prefab)
|
||||
validated = PrefabUtility.GetPrefabParent(unvalidated) as GameObject;
|
||||
#endif
|
||||
return validated;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
public virtual void OnEnable()
|
||||
{
|
||||
PhotonNetwork.AddCallbackTarget(this);
|
||||
}
|
||||
|
||||
public virtual void OnDisable()
|
||||
{
|
||||
PhotonNetwork.RemoveCallbackTarget(this);
|
||||
}
|
||||
|
||||
|
||||
public virtual void OnJoinedRoom()
|
||||
{
|
||||
// Only AutoSpawn if we are a new ActorId. Rejoining should reproduce the objects by server instantiation.
|
||||
if (AutoSpawnObjects && !PhotonNetwork.LocalPlayer.HasRejoined)
|
||||
{
|
||||
SpawnObjects();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SpawnObjects()
|
||||
{
|
||||
if (this.PrefabsToInstantiate != null)
|
||||
{
|
||||
foreach (GameObject o in this.PrefabsToInstantiate)
|
||||
{
|
||||
if (o == null)
|
||||
continue;
|
||||
#if UNITY_EDITOR
|
||||
Debug.Log("Auto-Instantiating: " + o.name);
|
||||
#endif
|
||||
Vector3 spawnPos; Quaternion spawnRot;
|
||||
GetSpawnPoint(out spawnPos, out spawnRot);
|
||||
|
||||
|
||||
var newobj = PhotonNetwork.Instantiate(o.name, spawnPos, spawnRot, 0);
|
||||
SpawnedObjects.Push(newobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroy all objects that have been spawned by this component for this client.
|
||||
/// </summary>
|
||||
/// <param name="localOnly">Use Object.Destroy rather than PhotonNetwork.Destroy.</param>
|
||||
public virtual void DespawnObjects(bool localOnly)
|
||||
{
|
||||
|
||||
while (SpawnedObjects.Count > 0)
|
||||
{
|
||||
var go = SpawnedObjects.Pop();
|
||||
if (go)
|
||||
{
|
||||
if (localOnly)
|
||||
Object.Destroy(go);
|
||||
else
|
||||
PhotonNetwork.Destroy(go);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnFriendListUpdate(List<FriendInfo> friendList) { }
|
||||
public virtual void OnCreatedRoom() { }
|
||||
public virtual void OnCreateRoomFailed(short returnCode, string message) { }
|
||||
public virtual void OnJoinRoomFailed(short returnCode, string message) { }
|
||||
public virtual void OnJoinRandomFailed(short returnCode, string message) { }
|
||||
public virtual void OnLeftRoom() { }
|
||||
|
||||
protected int lastUsedSpawnPointIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next SpawnPoint from the list using the SpawnSequence, and applies RandomOffset (if used) to the transform matrix.
|
||||
/// Override this method with any custom code for coming up with a spawn location. This method is used by AutoSpawn.
|
||||
/// </summary>
|
||||
public virtual void GetSpawnPoint(out Vector3 spawnPos, out Quaternion spawnRot)
|
||||
{
|
||||
|
||||
// Fetch a point using the Sequence method indicated
|
||||
Transform point = GetSpawnPoint();
|
||||
|
||||
if (point != null)
|
||||
{
|
||||
spawnPos = point.position;
|
||||
spawnRot = point.rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnPos = new Vector3(0, 0, 0);
|
||||
spawnRot = new Quaternion(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
if (UseRandomOffset)
|
||||
{
|
||||
Random.InitState((int)(Time.time * 10000));
|
||||
spawnPos += GetRandomOffset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the transform of the next SpawnPoint from the list, selected using the SpawnSequence setting.
|
||||
/// RandomOffset is not applied, only the transform of the SpawnPoint is returned.
|
||||
/// Override this method to change how Spawn Point transform is selected. Return the transform you want to use as a spawn point.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual Transform GetSpawnPoint()
|
||||
{
|
||||
// Fetch a point using the Sequence method indicated
|
||||
if (SpawnPoints == null || SpawnPoints.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (Sequence)
|
||||
{
|
||||
case SpawnSequence.Connection:
|
||||
{
|
||||
int id = PhotonNetwork.LocalPlayer.ActorNumber;
|
||||
return SpawnPoints[(id == -1) ? 0 : id % SpawnPoints.Count];
|
||||
}
|
||||
|
||||
case SpawnSequence.RoundRobin:
|
||||
{
|
||||
lastUsedSpawnPointIndex++;
|
||||
if (lastUsedSpawnPointIndex >= SpawnPoints.Count)
|
||||
lastUsedSpawnPointIndex = 0;
|
||||
|
||||
/// Use Vector.Zero and Quaternion.Identity if we are dealing with no or a null spawnpoint.
|
||||
return SpawnPoints == null || SpawnPoints.Count == 0 ? null : SpawnPoints[lastUsedSpawnPointIndex];
|
||||
}
|
||||
|
||||
case SpawnSequence.Random:
|
||||
{
|
||||
return SpawnPoints[Random.Range(0, SpawnPoints.Count)];
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When UseRandomeOffset is enabled, this method is called to produce a Vector3 offset. The default implementation clamps the Y value to zero. You may override this with your own implementation.
|
||||
/// </summary>
|
||||
protected virtual Vector3 GetRandomOffset()
|
||||
{
|
||||
Vector3 random = Random.insideUnitSphere;
|
||||
if (ClampY)
|
||||
random.y = 0;
|
||||
return RandomOffset * random.normalized;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
[CustomEditor(typeof(OnJoinedInstantiate), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class OnJoinedInstantiateEditor : Editor
|
||||
{
|
||||
|
||||
SerializedProperty SpawnPoints, PrefabsToInstantiate, UseRandomOffset, ClampY, RandomOffset, Sequence, autoSpawnObjects;
|
||||
GUIStyle fieldBox;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
SpawnPoints = serializedObject.FindProperty("SpawnPoints");
|
||||
PrefabsToInstantiate = serializedObject.FindProperty("PrefabsToInstantiate");
|
||||
UseRandomOffset = serializedObject.FindProperty("UseRandomOffset");
|
||||
ClampY = serializedObject.FindProperty("ClampY");
|
||||
RandomOffset = serializedObject.FindProperty("RandomOffset");
|
||||
Sequence = serializedObject.FindProperty("Sequence");
|
||||
|
||||
autoSpawnObjects = serializedObject.FindProperty("AutoSpawnObjects");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
const int PAD = 6;
|
||||
|
||||
if (fieldBox == null)
|
||||
fieldBox = new GUIStyle("HelpBox") { padding = new RectOffset(PAD, PAD, PAD, PAD) };
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
EditableReferenceList(PrefabsToInstantiate, new GUIContent(PrefabsToInstantiate.displayName, PrefabsToInstantiate.tooltip), fieldBox);
|
||||
|
||||
EditableReferenceList(SpawnPoints, new GUIContent(SpawnPoints.displayName, SpawnPoints.tooltip), fieldBox);
|
||||
|
||||
/// Spawn Pattern
|
||||
EditorGUILayout.BeginVertical(fieldBox);
|
||||
EditorGUILayout.PropertyField(Sequence);
|
||||
EditorGUILayout.PropertyField(UseRandomOffset);
|
||||
if (UseRandomOffset.boolValue)
|
||||
{
|
||||
EditorGUILayout.PropertyField(RandomOffset);
|
||||
EditorGUILayout.PropertyField(ClampY);
|
||||
}
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
/// Auto/Manual Spawn
|
||||
EditorGUILayout.BeginVertical(fieldBox);
|
||||
EditorGUILayout.PropertyField(autoSpawnObjects);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a basic rendered list of objects from a SerializedProperty list or array, with Add/Destroy buttons.
|
||||
/// </summary>
|
||||
/// <param name="list"></param>
|
||||
/// <param name="gc"></param>
|
||||
public void EditableReferenceList(SerializedProperty list, GUIContent gc, GUIStyle style = null)
|
||||
{
|
||||
EditorGUILayout.LabelField(gc);
|
||||
|
||||
if (style == null)
|
||||
style = new GUIStyle("HelpBox") { padding = new RectOffset(6, 6, 6, 6) };
|
||||
|
||||
EditorGUILayout.BeginVertical(style);
|
||||
|
||||
int count = list.arraySize;
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
if (GUI.Button(EditorGUILayout.GetControlRect(GUILayout.MaxWidth(20)), "+", (GUIStyle)"minibutton"))
|
||||
{
|
||||
int newindex = list.arraySize;
|
||||
list.InsertArrayElementAtIndex(0);
|
||||
list.GetArrayElementAtIndex(0).objectReferenceValue = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// List Elements and Delete buttons
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
bool add = (GUI.Button(EditorGUILayout.GetControlRect(GUILayout.MaxWidth(20)), "+", (GUIStyle)"minibutton"));
|
||||
EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i), GUIContent.none);
|
||||
bool remove = (GUI.Button(EditorGUILayout.GetControlRect(GUILayout.MaxWidth(20)), "x", (GUIStyle)"minibutton"));
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (add)
|
||||
{
|
||||
Add(list, i);
|
||||
break;
|
||||
}
|
||||
|
||||
if (remove)
|
||||
{
|
||||
list.DeleteArrayElementAtIndex(i);
|
||||
//EditorGUILayout.EndHorizontal();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.GetControlRect(false, 4);
|
||||
|
||||
if (GUI.Button(EditorGUILayout.GetControlRect(), "Add", (GUIStyle)"minibutton"))
|
||||
Add(list, count);
|
||||
|
||||
}
|
||||
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void Add(SerializedProperty list, int i)
|
||||
{
|
||||
{
|
||||
int newindex = list.arraySize;
|
||||
list.InsertArrayElementAtIndex(i);
|
||||
list.GetArrayElementAtIndex(i).objectReferenceValue = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed3b5dcba7bf9114cb13fc59e0a71f55
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,24 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="OnStartDelete.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// This component will destroy the GameObject it is attached to (in Start()).
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>This component will destroy the GameObject it is attached to (in Start()).</summary>
|
||||
public class OnStartDelete : MonoBehaviour
|
||||
{
|
||||
// Use this for initialization
|
||||
private void Start()
|
||||
{
|
||||
Destroy(this.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8d56a54ae062da4a87516fb994f4e30
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e3b3d6025e4b41fca914dca9dcc718d
|
||||
folderAsset: yes
|
||||
timeCreated: 1529328285
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,176 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="CountdownTimer.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// This is a basic CountdownTimer. In order to start the timer, the MasterClient can add a certain entry to the Custom Room Properties,
|
||||
// which contains the property's name 'StartTime' and the actual start time describing the moment, the timer has been started.
|
||||
// To have a synchronized timer, the best practice is to use PhotonNetwork.Time.
|
||||
// In order to subscribe to the CountdownTimerHasExpired event you can call CountdownTimer.OnCountdownTimerHasExpired += OnCountdownTimerIsExpired;
|
||||
// from Unity's OnEnable function for example. For unsubscribing simply call CountdownTimer.OnCountdownTimerHasExpired -= OnCountdownTimerIsExpired;.
|
||||
// You can do this from Unity's OnDisable function for example.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using ExitGames.Client.Photon;
|
||||
using Photon.Realtime;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>This is a basic, network-synced CountdownTimer based on properties.</summary>
|
||||
/// <remarks>
|
||||
/// In order to start the timer, the MasterClient can call SetStartTime() to set the timestamp for the start.
|
||||
/// The property 'StartTime' then contains the server timestamp when the timer has been started.
|
||||
///
|
||||
/// In order to subscribe to the CountdownTimerHasExpired event you can call CountdownTimer.OnCountdownTimerHasExpired
|
||||
/// += OnCountdownTimerIsExpired;
|
||||
/// from Unity's OnEnable function for example. For unsubscribing simply call CountdownTimer.OnCountdownTimerHasExpired
|
||||
/// -= OnCountdownTimerIsExpired;.
|
||||
///
|
||||
/// You can do this from Unity's OnEnable and OnDisable functions.
|
||||
/// </remarks>
|
||||
public class CountdownTimer : MonoBehaviourPunCallbacks
|
||||
{
|
||||
/// <summary>
|
||||
/// OnCountdownTimerHasExpired delegate.
|
||||
/// </summary>
|
||||
public delegate void CountdownTimerHasExpired();
|
||||
|
||||
public const string CountdownStartTime = "StartTime";
|
||||
|
||||
[Header("Countdown time in seconds")]
|
||||
public float Countdown = 5.0f;
|
||||
|
||||
private bool isTimerRunning;
|
||||
|
||||
private int startTime;
|
||||
|
||||
[Header("Reference to a Text component for visualizing the countdown")]
|
||||
public Text Text;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when the timer has expired.
|
||||
/// </summary>
|
||||
public static event CountdownTimerHasExpired OnCountdownTimerHasExpired;
|
||||
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (this.Text == null) Debug.LogError("Reference to 'Text' is not set. Please set a valid reference.", this);
|
||||
}
|
||||
|
||||
public override void OnEnable()
|
||||
{
|
||||
Debug.Log("OnEnable CountdownTimer");
|
||||
base.OnEnable();
|
||||
|
||||
// the starttime may already be in the props. look it up.
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
Debug.Log("OnDisable CountdownTimer");
|
||||
}
|
||||
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!this.isTimerRunning) return;
|
||||
|
||||
float countdown = TimeRemaining();
|
||||
this.Text.text = string.Format("Game starts in {0} seconds", countdown.ToString("n0"));
|
||||
|
||||
if (countdown > 0.0f) return;
|
||||
|
||||
OnTimerEnds();
|
||||
}
|
||||
|
||||
|
||||
private void OnTimerRuns()
|
||||
{
|
||||
this.isTimerRunning = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
private void OnTimerEnds()
|
||||
{
|
||||
this.isTimerRunning = false;
|
||||
this.enabled = false;
|
||||
|
||||
Debug.Log("Emptying info text.", this.Text);
|
||||
this.Text.text = string.Empty;
|
||||
|
||||
if (OnCountdownTimerHasExpired != null) OnCountdownTimerHasExpired();
|
||||
}
|
||||
|
||||
|
||||
public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
|
||||
{
|
||||
Debug.Log("CountdownTimer.OnRoomPropertiesUpdate " + propertiesThatChanged.ToStringFull());
|
||||
Initialize();
|
||||
}
|
||||
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
int propStartTime;
|
||||
if (TryGetStartTime(out propStartTime))
|
||||
{
|
||||
this.startTime = propStartTime;
|
||||
Debug.Log("Initialize sets StartTime " + this.startTime + " server time now: " + PhotonNetwork.ServerTimestamp + " remain: " + TimeRemaining());
|
||||
|
||||
|
||||
this.isTimerRunning = TimeRemaining() > 0;
|
||||
|
||||
if (this.isTimerRunning)
|
||||
OnTimerRuns();
|
||||
else
|
||||
OnTimerEnds();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private float TimeRemaining()
|
||||
{
|
||||
int timer = PhotonNetwork.ServerTimestamp - this.startTime;
|
||||
return this.Countdown - timer / 1000f;
|
||||
}
|
||||
|
||||
|
||||
public static bool TryGetStartTime(out int startTimestamp)
|
||||
{
|
||||
startTimestamp = PhotonNetwork.ServerTimestamp;
|
||||
|
||||
object startTimeFromProps;
|
||||
if (PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(CountdownStartTime, out startTimeFromProps))
|
||||
{
|
||||
startTimestamp = (int)startTimeFromProps;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static void SetStartTime()
|
||||
{
|
||||
int startTime = 0;
|
||||
bool wasSet = TryGetStartTime(out startTime);
|
||||
|
||||
Hashtable props = new Hashtable
|
||||
{
|
||||
{CountdownTimer.CountdownStartTime, (int)PhotonNetwork.ServerTimestamp}
|
||||
};
|
||||
PhotonNetwork.CurrentRoom.SetCustomProperties(props);
|
||||
|
||||
|
||||
Debug.Log("Set Custom Props for Time: "+ props.ToStringFull() + " wasSet: "+wasSet);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ffc398cf76e6d458caf303b5fceea504
|
||||
timeCreated: 1529327775
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2edc8c63e86d94f6990d3f7e7e90066a
|
||||
folderAsset: yes
|
||||
timeCreated: 1529327659
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,430 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="PunTurnManager.cs" company="Exit Games GmbH">
|
||||
// PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Manager for Turn Based games, using PUN
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Photon.Realtime;
|
||||
|
||||
using ExitGames.Client.Photon;
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Pun turnBased Game manager.
|
||||
/// Provides an Interface (IPunTurnManagerCallbacks) for the typical turn flow and logic, between players
|
||||
/// Provides Extensions for Player, Room and RoomInfo to feature dedicated api for TurnBased Needs
|
||||
/// </summary>
|
||||
public class PunTurnManager : MonoBehaviourPunCallbacks, IOnEventCallback
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// External definition for better garbage collection management, used in ProcessEvent.
|
||||
/// </summary>
|
||||
Player sender;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps accessing the "turn" custom properties of a room.
|
||||
/// </summary>
|
||||
/// <value>The turn index</value>
|
||||
public int Turn
|
||||
{
|
||||
get { return PhotonNetwork.CurrentRoom.GetTurn(); }
|
||||
private set
|
||||
{
|
||||
|
||||
_isOverCallProcessed = false;
|
||||
|
||||
PhotonNetwork.CurrentRoom.SetTurn(value, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the turn in seconds.
|
||||
/// </summary>
|
||||
public float TurnDuration = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the elapsed time in the current turn in seconds
|
||||
/// </summary>
|
||||
/// <value>The elapsed time in the turn.</value>
|
||||
public float ElapsedTimeInTurn
|
||||
{
|
||||
get { return ((float) (PhotonNetwork.ServerTimestamp - PhotonNetwork.CurrentRoom.GetTurnStart())) / 1000.0f; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remaining seconds for the current turn. Ranges from 0 to TurnDuration
|
||||
/// </summary>
|
||||
/// <value>The remaining seconds fo the current turn</value>
|
||||
public float RemainingSecondsInTurn
|
||||
{
|
||||
get { return Mathf.Max(0f, this.TurnDuration - this.ElapsedTimeInTurn); }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the turn is completed by all.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this turn is completed by all; otherwise, <c>false</c>.</value>
|
||||
public bool IsCompletedByAll
|
||||
{
|
||||
get { return PhotonNetwork.CurrentRoom != null && Turn > 0 && this.finishedPlayers.Count == PhotonNetwork.CurrentRoom.PlayerCount; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current turn is finished by me.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if the current turn is finished by me; otherwise, <c>false</c>.</value>
|
||||
public bool IsFinishedByMe
|
||||
{
|
||||
get { return this.finishedPlayers.Contains(PhotonNetwork.LocalPlayer); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current turn is over. That is the ElapsedTimeinTurn is greater or equal to the TurnDuration
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if the current turn is over; otherwise, <c>false</c>.</value>
|
||||
public bool IsOver
|
||||
{
|
||||
get { return this.RemainingSecondsInTurn <= 0f; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The turn manager listener. Set this to your own script instance to catch Callbacks
|
||||
/// </summary>
|
||||
public IPunTurnManagerCallbacks TurnManagerListener;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The finished players.
|
||||
/// </summary>
|
||||
private readonly HashSet<Player> finishedPlayers = new HashSet<Player>();
|
||||
|
||||
/// <summary>
|
||||
/// The turn manager event offset event message byte. Used internaly for defining data in Room Custom Properties
|
||||
/// </summary>
|
||||
public const byte TurnManagerEventOffset = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The Move event message byte. Used internaly for saving data in Room Custom Properties
|
||||
/// </summary>
|
||||
public const byte EvMove = 1 + TurnManagerEventOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The Final Move event message byte. Used internaly for saving data in Room Custom Properties
|
||||
/// </summary>
|
||||
public const byte EvFinalMove = 2 + TurnManagerEventOffset;
|
||||
|
||||
// keep track of message calls
|
||||
private bool _isOverCallProcessed = false;
|
||||
|
||||
#region MonoBehaviour CallBack
|
||||
|
||||
|
||||
void Start(){}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (Turn > 0 && this.IsOver && !_isOverCallProcessed)
|
||||
{
|
||||
_isOverCallProcessed = true;
|
||||
this.TurnManagerListener.OnTurnTimeEnds(this.Turn);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tells the TurnManager to begins a new turn.
|
||||
/// </summary>
|
||||
public void BeginTurn()
|
||||
{
|
||||
Turn = this.Turn + 1; // note: this will set a property in the room, which is available to the other players.
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Call to send an action. Optionally finish the turn, too.
|
||||
/// The move object can be anything. Try to optimize though and only send the strict minimum set of information to define the turn move.
|
||||
/// </summary>
|
||||
/// <param name="move"></param>
|
||||
/// <param name="finished"></param>
|
||||
public void SendMove(object move, bool finished)
|
||||
{
|
||||
if (IsFinishedByMe)
|
||||
{
|
||||
UnityEngine.Debug.LogWarning("Can't SendMove. Turn is finished by this player.");
|
||||
return;
|
||||
}
|
||||
|
||||
// along with the actual move, we have to send which turn this move belongs to
|
||||
Hashtable moveHt = new Hashtable();
|
||||
moveHt.Add("turn", Turn);
|
||||
moveHt.Add("move", move);
|
||||
|
||||
byte evCode = (finished) ? EvFinalMove : EvMove;
|
||||
PhotonNetwork.RaiseEvent(evCode, moveHt, new RaiseEventOptions() {CachingOption = EventCaching.AddToRoomCache}, SendOptions.SendReliable);
|
||||
if (finished)
|
||||
{
|
||||
PhotonNetwork.LocalPlayer.SetFinishedTurn(Turn);
|
||||
}
|
||||
|
||||
// the server won't send the event back to the origin (by default). to get the event, call it locally
|
||||
// (note: the order of events might be mixed up as we do this locally)
|
||||
ProcessOnEvent(evCode, moveHt, PhotonNetwork.LocalPlayer.ActorNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the player finished the current turn.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c>, if player finished the current turn, <c>false</c> otherwise.</returns>
|
||||
/// <param name="player">The Player to check for</param>
|
||||
public bool GetPlayerFinishedTurn(Player player)
|
||||
{
|
||||
if (player != null && this.finishedPlayers != null && this.finishedPlayers.Contains(player))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Callbacks
|
||||
|
||||
// called internally
|
||||
void ProcessOnEvent(byte eventCode, object content, int senderId)
|
||||
{
|
||||
if (senderId == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sender = PhotonNetwork.CurrentRoom.GetPlayer(senderId);
|
||||
|
||||
switch (eventCode)
|
||||
{
|
||||
case EvMove:
|
||||
{
|
||||
Hashtable evTable = content as Hashtable;
|
||||
int turn = (int)evTable["turn"];
|
||||
object move = evTable["move"];
|
||||
this.TurnManagerListener.OnPlayerMove(sender, turn, move);
|
||||
|
||||
break;
|
||||
}
|
||||
case EvFinalMove:
|
||||
{
|
||||
Hashtable evTable = content as Hashtable;
|
||||
int turn = (int)evTable["turn"];
|
||||
object move = evTable["move"];
|
||||
|
||||
if (turn == this.Turn)
|
||||
{
|
||||
this.finishedPlayers.Add(sender);
|
||||
|
||||
this.TurnManagerListener.OnPlayerFinished(sender, turn, move);
|
||||
|
||||
}
|
||||
|
||||
if (IsCompletedByAll)
|
||||
{
|
||||
this.TurnManagerListener.OnTurnCompleted(this.Turn);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by PhotonNetwork.OnEventCall registration
|
||||
/// </summary>
|
||||
/// <param name="photonEvent">Photon event.</param>
|
||||
public void OnEvent(EventData photonEvent)
|
||||
{
|
||||
this.ProcessOnEvent(photonEvent.Code, photonEvent.CustomData, photonEvent.Sender);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by PhotonNetwork
|
||||
/// </summary>
|
||||
/// <param name="propertiesThatChanged">Properties that changed.</param>
|
||||
public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
|
||||
{
|
||||
|
||||
// Debug.Log("OnRoomPropertiesUpdate: "+propertiesThatChanged.ToStringFull());
|
||||
|
||||
if (propertiesThatChanged.ContainsKey("Turn"))
|
||||
{
|
||||
_isOverCallProcessed = false;
|
||||
this.finishedPlayers.Clear();
|
||||
this.TurnManagerListener.OnTurnBegins(this.Turn);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
public interface IPunTurnManagerCallbacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Called the turn begins event.
|
||||
/// </summary>
|
||||
/// <param name="turn">Turn Index</param>
|
||||
void OnTurnBegins(int turn);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a turn is completed (finished by all players)
|
||||
/// </summary>
|
||||
/// <param name="turn">Turn Index</param>
|
||||
void OnTurnCompleted(int turn);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a player moved (but did not finish the turn)
|
||||
/// </summary>
|
||||
/// <param name="player">Player reference</param>
|
||||
/// <param name="turn">Turn Index</param>
|
||||
/// <param name="move">Move Object data</param>
|
||||
void OnPlayerMove(Player player, int turn, object move);
|
||||
|
||||
/// <summary>
|
||||
/// When a player finishes a turn (includes the action/move of that player)
|
||||
/// </summary>
|
||||
/// <param name="player">Player reference</param>
|
||||
/// <param name="turn">Turn index</param>
|
||||
/// <param name="move">Move Object data</param>
|
||||
void OnPlayerFinished(Player player, int turn, object move);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when a turn completes due to a time constraint (timeout for a turn)
|
||||
/// </summary>
|
||||
/// <param name="turn">Turn index</param>
|
||||
void OnTurnTimeEnds(int turn);
|
||||
}
|
||||
|
||||
|
||||
public static class TurnExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// currently ongoing turn number
|
||||
/// </summary>
|
||||
public static readonly string TurnPropKey = "Turn";
|
||||
|
||||
/// <summary>
|
||||
/// start (server) time for currently ongoing turn (used to calculate end)
|
||||
/// </summary>
|
||||
public static readonly string TurnStartPropKey = "TStart";
|
||||
|
||||
/// <summary>
|
||||
/// Finished Turn of Actor (followed by number)
|
||||
/// </summary>
|
||||
public static readonly string FinishedTurnPropKey = "FToA";
|
||||
|
||||
/// <summary>
|
||||
/// Sets the turn.
|
||||
/// </summary>
|
||||
/// <param name="room">Room reference</param>
|
||||
/// <param name="turn">Turn index</param>
|
||||
/// <param name="setStartTime">If set to <c>true</c> set start time.</param>
|
||||
public static void SetTurn(this Room room, int turn, bool setStartTime = false)
|
||||
{
|
||||
if (room == null || room.CustomProperties == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Hashtable turnProps = new Hashtable();
|
||||
turnProps[TurnPropKey] = turn;
|
||||
if (setStartTime)
|
||||
{
|
||||
turnProps[TurnStartPropKey] = PhotonNetwork.ServerTimestamp;
|
||||
}
|
||||
|
||||
room.SetCustomProperties(turnProps);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current turn from a RoomInfo
|
||||
/// </summary>
|
||||
/// <returns>The turn index </returns>
|
||||
/// <param name="room">RoomInfo reference</param>
|
||||
public static int GetTurn(this RoomInfo room)
|
||||
{
|
||||
if (room == null || room.CustomProperties == null || !room.CustomProperties.ContainsKey(TurnPropKey))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) room.CustomProperties[TurnPropKey];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the start time when the turn began. This can be used to calculate how long it's going on.
|
||||
/// </summary>
|
||||
/// <returns>The turn start.</returns>
|
||||
/// <param name="room">Room.</param>
|
||||
public static int GetTurnStart(this RoomInfo room)
|
||||
{
|
||||
if (room == null || room.CustomProperties == null || !room.CustomProperties.ContainsKey(TurnStartPropKey))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) room.CustomProperties[TurnStartPropKey];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// gets the player's finished turn (from the ROOM properties)
|
||||
/// </summary>
|
||||
/// <returns>The finished turn index</returns>
|
||||
/// <param name="player">Player reference</param>
|
||||
public static int GetFinishedTurn(this Player player)
|
||||
{
|
||||
Room room = PhotonNetwork.CurrentRoom;
|
||||
if (room == null || room.CustomProperties == null || !room.CustomProperties.ContainsKey(TurnPropKey))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
string propKey = FinishedTurnPropKey + player.ActorNumber;
|
||||
return (int) room.CustomProperties[propKey];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the player's finished turn (in the ROOM properties)
|
||||
/// </summary>
|
||||
/// <param name="player">Player Reference</param>
|
||||
/// <param name="turn">Turn Index</param>
|
||||
public static void SetFinishedTurn(this Player player, int turn)
|
||||
{
|
||||
Room room = PhotonNetwork.CurrentRoom;
|
||||
if (room == null || room.CustomProperties == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string propKey = FinishedTurnPropKey + player.ActorNumber;
|
||||
Hashtable finishedTurnProp = new Hashtable();
|
||||
finishedTurnProp[propKey] = turn;
|
||||
|
||||
room.SetCustomProperties(finishedTurnProp);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a1b3bda60e9e804f87fd1e5d20a885a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 665e986f9b37c294d96c166a76e618d3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,53 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="ButtonInsideScrollList.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Used on Buttons inside UI lists to prevent scrollRect parent to scroll when down on buttons.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Button inside scroll list will stop scrolling ability of scrollRect container, so that when pressing down on a button and draggin up and down will not affect scrolling.
|
||||
/// this doesn't do anything if no scrollRect component found in Parent Hierarchy.
|
||||
/// </summary>
|
||||
public class ButtonInsideScrollList : MonoBehaviour, IPointerDownHandler, IPointerUpHandler {
|
||||
|
||||
ScrollRect scrollRect;
|
||||
|
||||
// Use this for initialization
|
||||
void Start () {
|
||||
scrollRect = GetComponentInParent<ScrollRect>();
|
||||
}
|
||||
|
||||
#region IPointerDownHandler implementation
|
||||
void IPointerDownHandler.OnPointerDown (PointerEventData eventData)
|
||||
{
|
||||
if (scrollRect !=null)
|
||||
{
|
||||
scrollRect.StopMovement();
|
||||
scrollRect.enabled = false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IPointerUpHandler implementation
|
||||
|
||||
void IPointerUpHandler.OnPointerUp (PointerEventData eventData)
|
||||
{
|
||||
if (scrollRect !=null && !scrollRect.enabled)
|
||||
{
|
||||
scrollRect.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0e8b381f2c05442ca5c01638958156a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,40 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="EventSystemSpawner.cs" company="Exit Games GmbH">
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// For additive Scene Loading context, eventSystem can't be added to each scene and instead should be instantiated only if necessary.
|
||||
// https://answers.unity.com/questions/1403002/multiple-eventsystem-in-scene-this-is-not-supporte.html
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Event system spawner. Will add an EventSystem GameObject with an EventSystem component and a StandaloneInputModule component.
|
||||
/// Use this in additive scene loading context where you would otherwise get a "Multiple EventSystem in scene... this is not supported" error from Unity.
|
||||
/// </summary>
|
||||
public class EventSystemSpawner : MonoBehaviour
|
||||
{
|
||||
void OnEnable()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
|
||||
Debug.LogError("PUN Demos are not compatible with the New Input System, unless you enable \"Both\" in: Edit > Project Settings > Player > Active Input Handling. Pausing App.");
|
||||
Debug.Break();
|
||||
return;
|
||||
#endif
|
||||
|
||||
EventSystem sceneEventSystem = FindObjectOfType<EventSystem>();
|
||||
if (sceneEventSystem == null)
|
||||
{
|
||||
GameObject eventSystem = new GameObject("EventSystem");
|
||||
|
||||
eventSystem.AddComponent<EventSystem>();
|
||||
eventSystem.AddComponent<StandaloneInputModule>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68187d3cf4c8746aaa64930f1a766a38
|
||||
timeCreated: 1529319867
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,64 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="ImageToggleIsOnTransition.cs" company="Exit Games GmbH">
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Use this on Toggle graphics to have some color transition as well without corrupting toggle's behaviour.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this on toggles texts to have some color transition on the text depending on the isOn State.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Graphic))]
|
||||
public class GraphicToggleIsOnTransition : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
|
||||
{
|
||||
public Toggle toggle;
|
||||
|
||||
private Graphic _graphic;
|
||||
|
||||
public Color NormalOnColor = Color.white;
|
||||
public Color NormalOffColor = Color.black;
|
||||
public Color HoverOnColor = Color.black;
|
||||
public Color HoverOffColor = Color.black;
|
||||
|
||||
private bool isHover;
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
this.isHover = true;
|
||||
this._graphic.color = this.toggle.isOn ? this.HoverOnColor : this.HoverOffColor;
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
this.isHover = false;
|
||||
this._graphic.color = this.toggle.isOn ? this.NormalOnColor : this.NormalOffColor;
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
this._graphic = this.GetComponent<Graphic>();
|
||||
|
||||
this.OnValueChanged(this.toggle.isOn);
|
||||
|
||||
this.toggle.onValueChanged.AddListener(this.OnValueChanged);
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
this.toggle.onValueChanged.RemoveListener(this.OnValueChanged);
|
||||
}
|
||||
|
||||
public void OnValueChanged(bool isOn)
|
||||
{
|
||||
this._graphic.color = isOn ? (this.isHover ? this.HoverOnColor : this.HoverOnColor) : (this.isHover ? this.NormalOffColor : this.NormalOffColor);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 195602a009c4b42b6a62e0bdf601b70d
|
||||
timeCreated: 1524059610
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,45 @@
|
||||
// <copyright file="OnPointerOverTooltip.cs" company="Exit Games GmbH">
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Set focus to a given photonView when pointed is over
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Set focus to a given photonView when pointed is over
|
||||
/// </summary>
|
||||
public class OnPointerOverTooltip : MonoBehaviour,IPointerEnterHandler,IPointerExitHandler
|
||||
{
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
PointedAtGameObjectInfo.Instance.RemoveFocus(this.GetComponent<PhotonView>());
|
||||
}
|
||||
|
||||
#region IPointerExitHandler implementation
|
||||
|
||||
void IPointerExitHandler.OnPointerExit (PointerEventData eventData)
|
||||
{
|
||||
PointedAtGameObjectInfo.Instance.RemoveFocus (this.GetComponent<PhotonView>());
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPointerEnterHandler implementation
|
||||
|
||||
void IPointerEnterHandler.OnPointerEnter (PointerEventData eventData)
|
||||
{
|
||||
PointedAtGameObjectInfo.Instance.SetFocus (this.GetComponent<PhotonView>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff93154db96e843fbbc5e816ec0d2b48
|
||||
timeCreated: 1495545560
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,123 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="TabViewManager.cs" company="Exit Games GmbH">
|
||||
// Part of: PunCockpit
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Simple Management for Tabs, it requires a ToggleGroup, and then for each Tab, a Unique Name, the related Toggle and its associated RectTransform View
|
||||
// this manager handles Tab views activation and deactivation, and provides a Unity Event Callback when a tab was selected.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
/// <summary>
|
||||
/// Tab view manager. Handles Tab views activation and deactivation, and provides a Unity Event Callback when a tab was selected.
|
||||
/// </summary>
|
||||
public class TabViewManager : MonoBehaviour
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Tab change event.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class TabChangeEvent : UnityEvent<string> { }
|
||||
|
||||
[Serializable]
|
||||
public class Tab
|
||||
{
|
||||
public string ID = "";
|
||||
public Toggle Toggle;
|
||||
public RectTransform View;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The toggle group component target.
|
||||
/// </summary>
|
||||
public ToggleGroup ToggleGroup;
|
||||
|
||||
/// <summary>
|
||||
/// all the tabs for this group
|
||||
/// </summary>
|
||||
public Tab[] Tabs;
|
||||
|
||||
/// <summary>
|
||||
/// The on tab changed Event.
|
||||
/// </summary>
|
||||
public TabChangeEvent OnTabChanged;
|
||||
|
||||
protected Tab CurrentTab;
|
||||
|
||||
Dictionary<Toggle, Tab> Tab_lut;
|
||||
|
||||
void Start()
|
||||
{
|
||||
|
||||
Tab_lut = new Dictionary<Toggle, Tab>();
|
||||
|
||||
foreach (Tab _tab in this.Tabs)
|
||||
{
|
||||
|
||||
Tab_lut[_tab.Toggle] = _tab;
|
||||
|
||||
_tab.View.gameObject.SetActive(_tab.Toggle.isOn);
|
||||
|
||||
if (_tab.Toggle.isOn)
|
||||
{
|
||||
CurrentTab = _tab;
|
||||
}
|
||||
_tab.Toggle.onValueChanged.AddListener((isSelected) =>
|
||||
{
|
||||
if (!isSelected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
OnTabSelected(_tab);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects a given tab.
|
||||
/// </summary>
|
||||
/// <param name="id">Tab Id</param>
|
||||
public void SelectTab(string id)
|
||||
{
|
||||
foreach (Tab _t in Tabs)
|
||||
{
|
||||
if (_t.ID == id)
|
||||
{
|
||||
_t.Toggle.isOn = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// final method for a tab selection routine
|
||||
/// </summary>
|
||||
/// <param name="tab">Tab.</param>
|
||||
void OnTabSelected(Tab tab)
|
||||
{
|
||||
CurrentTab.View.gameObject.SetActive(false);
|
||||
|
||||
CurrentTab = Tab_lut[ToggleGroup.ActiveToggles().FirstOrDefault()];
|
||||
|
||||
CurrentTab.View.gameObject.SetActive(true);
|
||||
|
||||
OnTabChanged.Invoke(CurrentTab.ID);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c94485733838d40fda441b2c0fbbec10
|
||||
timeCreated: 1521118118
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,70 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="TextButtonTransition.cs" company="Exit Games GmbH">
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Use this on Button texts to have some color transition on the text as well without corrupting button's behaviour.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Use this on Button texts to have some color transition on the text as well without corrupting button's behaviour.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Text))]
|
||||
public class TextButtonTransition : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
|
||||
{
|
||||
|
||||
Text _text;
|
||||
|
||||
/// <summary>
|
||||
/// The selectable Component.
|
||||
/// </summary>
|
||||
public Selectable Selectable;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the normal of the transition state.
|
||||
/// </summary>
|
||||
public Color NormalColor= Color.white;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the hover of the transition state.
|
||||
/// </summary>
|
||||
public Color HoverColor = Color.black;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
_text = GetComponent<Text>();
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
_text.color = NormalColor;
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
_text.color = NormalColor;
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
if (Selectable == null || Selectable.IsInteractable()) {
|
||||
_text.color = HoverColor;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
if (Selectable == null || Selectable.IsInteractable()) {
|
||||
_text.color = NormalColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d234639538a34b8d9e3cc6362a7afd0
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,86 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="TextToggleIsOnTransition.cs" company="Exit Games GmbH">
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Use this on Button texts to have some color transition on the text as well without corrupting button's behaviour.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Use this on toggles texts to have some color transition on the text depending on the isOn State.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Text))]
|
||||
public class TextToggleIsOnTransition : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The toggle Component.
|
||||
/// </summary>
|
||||
public Toggle toggle;
|
||||
|
||||
Text _text;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the normal on transition state.
|
||||
/// </summary>
|
||||
public Color NormalOnColor= Color.white;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the normal off transition state.
|
||||
/// </summary>
|
||||
public Color NormalOffColor = Color.black;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the hover on transition state.
|
||||
/// </summary>
|
||||
public Color HoverOnColor= Color.black;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the hover off transition state.
|
||||
/// </summary>
|
||||
public Color HoverOffColor = Color.black;
|
||||
|
||||
bool isHover;
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
_text = GetComponent<Text>();
|
||||
|
||||
OnValueChanged (toggle.isOn);
|
||||
|
||||
toggle.onValueChanged.AddListener(OnValueChanged);
|
||||
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
toggle.onValueChanged.RemoveListener(OnValueChanged);
|
||||
}
|
||||
|
||||
public void OnValueChanged(bool isOn)
|
||||
{
|
||||
_text.color = isOn? (isHover?HoverOnColor:HoverOnColor) : (isHover?NormalOffColor:NormalOffColor) ;
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
isHover = true;
|
||||
_text.color = toggle.isOn?HoverOnColor:HoverOffColor;
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
isHover = false;
|
||||
_text.color = toggle.isOn?NormalOnColor:NormalOffColor;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec99d371d7c8e44899ce4b834dfd4d6a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
Reference in New Issue
Block a user