first commit

This commit is contained in:
2022-07-08 09:14:55 +08:00
commit 4d6bd72555
1123 changed files with 456307 additions and 0 deletions

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 404d7999c3b78454798028d2efcb9336
folderAsset: yes
timeCreated: 1529327267
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dfb1c264fdc576442b2f42c998bed4a2
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -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
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 84db789113d4b01418c1becb128c4561
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: fcede10f4c76b443483f21320fee20e5
folderAsset: yes
timeCreated: 1529329408
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: abadaa451a7bff0489078ed9eec61133
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,12 @@
{
"name": "PhotonUnityNetworking.Utilities.Culling.Editor",
"references": [
"PhotonUnityNetworking.Utilities"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 06dafbbe6b7b3a84f84213f47aabe7f0
timeCreated: 1537459565
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 72b80dd954bc647c4a2a924b87762f92
folderAsset: yes
timeCreated: 1529327208
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 5867a53c8db0e6745818285bb6b6e1b9
labels:
- ExitGames
- PUN
- Photon
- Networking
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -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();
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d06466c03d263624786afa88b52928b6
labels:
- ExitGames
- PUN
- Photon
- Networking
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e6262dd9a9b078c4e8cbd47495aa6d23
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -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" : "");
}
}
}

View File

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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 94d453efd58794348867b36584d05a1e
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f1d46a1c486645945a3667d8bbe2d2e7
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8cb74f08e3fc52942a0d8557772bf4dc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,15 @@
{
"name": "PhotonUnityNetworking.Utilities.PhotonPlayer.Editor",
"references": [
"PhotonRealtime",
"PhotonUnityNetworking",
"PhotonUnityNetworking.Utilities",
"PhotonUnityNetworking.Editor"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7024f760fc566cf45a16d2c838e22b2d
timeCreated: 1537459565
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d6590f39353bf4efdb3b14691166135f
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -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--;
}
}
}
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7dcadaf22424c4f5d82f4d48c3b8097f
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8701526de72d8774fa165e66daf050ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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 } });
}
}
}
}

View File

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

View File

@ -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;
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6b4df3943860f1d45bfe232053a58d80
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -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 } });
}
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6587c8104d7524f4280d0a68dd779f27
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,11 @@
{
"name": "PhotonUnityNetworking.Utilities",
"references": [
"PhotonRealtime",
"PhotonUnityNetworking"
],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false
}

View File

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

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 199121930ec5d4aa9bb53d836180b74f
folderAsset: yes
timeCreated: 1529327582
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 35144820bcc25444bb8f0fd767d9423e
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: e10fe700b1a744ec78575843923fe6d7
folderAsset: yes
timeCreated: 1529327245
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5c1b84a427010e0469ce0df07ab64dbc
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -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");
}
}
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 99b4daedc5e674a429acdf1e77da6a55
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -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.
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9dab7328ba500e944a99d62065fba6c0
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9b79a9b62449de940a073364858c3f9b
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -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
}
}

View File

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

View File

@ -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();
}
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1fbabafd914a48f4eb6108d8d8f43d29
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -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
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ed3b5dcba7bf9114cb13fc59e0a71f55
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f8d56a54ae062da4a87516fb994f4e30
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 7e3b3d6025e4b41fca914dca9dcc718d
folderAsset: yes
timeCreated: 1529328285
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 2edc8c63e86d94f6990d3f7e7e90066a
folderAsset: yes
timeCreated: 1529327659
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1a1b3bda60e9e804f87fd1e5d20a885a
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 665e986f9b37c294d96c166a76e618d3
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e0e8b381f2c05442ca5c01638958156a
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -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>();
}
}
}
}

View File

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

View File

@ -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);
}
}
}

View File

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

View File

@ -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
}
}

View File

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

View File

@ -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);
}
}
}

View File

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

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9d234639538a34b8d9e3cc6362a7afd0
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ec99d371d7c8e44899ce4b834dfd4d6a
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: