first commit
This commit is contained in:
@ -0,0 +1,501 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="CullArea.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Represents the cull area used for network culling.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents the cull area used for network culling.
|
||||
/// </summary>
|
||||
public class CullArea : MonoBehaviour
|
||||
{
|
||||
private const int MAX_NUMBER_OF_ALLOWED_CELLS = 250;
|
||||
|
||||
public const int MAX_NUMBER_OF_SUBDIVISIONS = 3;
|
||||
|
||||
/// <summary>
|
||||
/// This represents the first ID which is assigned to the first created cell.
|
||||
/// If you already have some interest groups blocking this first ID, fell free to change it.
|
||||
/// However increasing the first group ID decreases the maximum amount of allowed cells.
|
||||
/// Allowed values are in range from 1 to 250.
|
||||
/// </summary>
|
||||
public readonly byte FIRST_GROUP_ID = 1;
|
||||
|
||||
/// <summary>
|
||||
/// This represents the order in which updates are sent.
|
||||
/// The number represents the subdivision of the cell hierarchy:
|
||||
/// - 0: message is sent to all players
|
||||
/// - 1: message is sent to players who are interested in the matching cell of the first subdivision
|
||||
/// If there is only one subdivision we are sending one update to all players
|
||||
/// before sending three consequent updates only to players who are in the same cell
|
||||
/// or interested in updates of the current cell.
|
||||
/// </summary>
|
||||
public readonly int[] SUBDIVISION_FIRST_LEVEL_ORDER = new int[4] { 0, 1, 1, 1 };
|
||||
|
||||
/// <summary>
|
||||
/// This represents the order in which updates are sent.
|
||||
/// The number represents the subdivision of the cell hierarchy:
|
||||
/// - 0: message is sent to all players
|
||||
/// - 1: message is sent to players who are interested in the matching cell of the first subdivision
|
||||
/// - 2: message is sent to players who are interested in the matching cell of the second subdivision
|
||||
/// If there are two subdivisions we are sending every second update only to players
|
||||
/// who are in the same cell or interested in updates of the current cell.
|
||||
/// </summary>
|
||||
public readonly int[] SUBDIVISION_SECOND_LEVEL_ORDER = new int[8] { 0, 2, 1, 2, 0, 2, 1, 2 };
|
||||
|
||||
/// <summary>
|
||||
/// This represents the order in which updates are sent.
|
||||
/// The number represents the subdivision of the cell hierarchy:
|
||||
/// - 0: message is sent to all players
|
||||
/// - 1: message is sent to players who are interested in the matching cell of the first subdivision
|
||||
/// - 2: message is sent to players who are interested in the matching cell of the second subdivision
|
||||
/// - 3: message is sent to players who are interested in the matching cell of the third subdivision
|
||||
/// If there are two subdivisions we are sending every second update only to players
|
||||
/// who are in the same cell or interested in updates of the current cell.
|
||||
/// </summary>
|
||||
public readonly int[] SUBDIVISION_THIRD_LEVEL_ORDER = new int[12] { 0, 3, 2, 3, 1, 3, 2, 3, 1, 3, 2, 3 };
|
||||
|
||||
public Vector2 Center;
|
||||
public Vector2 Size = new Vector2(25.0f, 25.0f);
|
||||
|
||||
public Vector2[] Subdivisions = new Vector2[MAX_NUMBER_OF_SUBDIVISIONS];
|
||||
|
||||
public int NumberOfSubdivisions;
|
||||
|
||||
public int CellCount { get; private set; }
|
||||
|
||||
public CellTree CellTree { get; private set; }
|
||||
|
||||
public Dictionary<int, GameObject> Map { get; private set; }
|
||||
|
||||
public bool YIsUpAxis = false;
|
||||
public bool RecreateCellHierarchy = false;
|
||||
|
||||
private byte idCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the cell hierarchy at runtime.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
this.idCounter = this.FIRST_GROUP_ID;
|
||||
|
||||
this.CreateCellHierarchy();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the cell hierarchy in editor and draws the cell view.
|
||||
/// </summary>
|
||||
public void OnDrawGizmos()
|
||||
{
|
||||
this.idCounter = this.FIRST_GROUP_ID;
|
||||
|
||||
if (this.RecreateCellHierarchy)
|
||||
{
|
||||
this.CreateCellHierarchy();
|
||||
}
|
||||
|
||||
this.DrawCells();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the cell hierarchy.
|
||||
/// </summary>
|
||||
private void CreateCellHierarchy()
|
||||
{
|
||||
if (!this.IsCellCountAllowed())
|
||||
{
|
||||
if (Debug.isDebugBuild)
|
||||
{
|
||||
Debug.LogError("There are too many cells created by your subdivision options. Maximum allowed number of cells is " + (MAX_NUMBER_OF_ALLOWED_CELLS - this.FIRST_GROUP_ID) +
|
||||
". Current number of cells is " + this.CellCount + ".");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Quit();
|
||||
}
|
||||
}
|
||||
|
||||
CellTreeNode rootNode = new CellTreeNode(this.idCounter++, CellTreeNode.ENodeType.Root, null);
|
||||
|
||||
if (this.YIsUpAxis)
|
||||
{
|
||||
this.Center = new Vector2(transform.position.x, transform.position.y);
|
||||
this.Size = new Vector2(transform.localScale.x, transform.localScale.y);
|
||||
|
||||
rootNode.Center = new Vector3(this.Center.x, this.Center.y, 0.0f);
|
||||
rootNode.Size = new Vector3(this.Size.x, this.Size.y, 0.0f);
|
||||
rootNode.TopLeft = new Vector3((this.Center.x - (this.Size.x / 2.0f)), (this.Center.y - (this.Size.y / 2.0f)), 0.0f);
|
||||
rootNode.BottomRight = new Vector3((this.Center.x + (this.Size.x / 2.0f)), (this.Center.y + (this.Size.y / 2.0f)), 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Center = new Vector2(transform.position.x, transform.position.z);
|
||||
this.Size = new Vector2(transform.localScale.x, transform.localScale.z);
|
||||
|
||||
rootNode.Center = new Vector3(this.Center.x, 0.0f, this.Center.y);
|
||||
rootNode.Size = new Vector3(this.Size.x, 0.0f, this.Size.y);
|
||||
rootNode.TopLeft = new Vector3((this.Center.x - (this.Size.x / 2.0f)), 0.0f, (this.Center.y - (this.Size.y / 2.0f)));
|
||||
rootNode.BottomRight = new Vector3((this.Center.x + (this.Size.x / 2.0f)), 0.0f, (this.Center.y + (this.Size.y / 2.0f)));
|
||||
}
|
||||
|
||||
this.CreateChildCells(rootNode, 1);
|
||||
|
||||
this.CellTree = new CellTree(rootNode);
|
||||
|
||||
this.RecreateCellHierarchy = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates all child cells.
|
||||
/// </summary>
|
||||
/// <param name="parent">The current parent node.</param>
|
||||
/// <param name="cellLevelInHierarchy">The cell level within the current hierarchy.</param>
|
||||
private void CreateChildCells(CellTreeNode parent, int cellLevelInHierarchy)
|
||||
{
|
||||
if (cellLevelInHierarchy > this.NumberOfSubdivisions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int rowCount = (int)this.Subdivisions[(cellLevelInHierarchy - 1)].x;
|
||||
int columnCount = (int)this.Subdivisions[(cellLevelInHierarchy - 1)].y;
|
||||
|
||||
float startX = parent.Center.x - (parent.Size.x / 2.0f);
|
||||
float width = parent.Size.x / rowCount;
|
||||
|
||||
for (int row = 0; row < rowCount; ++row)
|
||||
{
|
||||
for (int column = 0; column < columnCount; ++column)
|
||||
{
|
||||
float xPos = startX + (row * width) + (width / 2.0f);
|
||||
|
||||
CellTreeNode node = new CellTreeNode(this.idCounter++, (this.NumberOfSubdivisions == cellLevelInHierarchy) ? CellTreeNode.ENodeType.Leaf : CellTreeNode.ENodeType.Node, parent);
|
||||
|
||||
if (this.YIsUpAxis)
|
||||
{
|
||||
float startY = parent.Center.y - (parent.Size.y / 2.0f);
|
||||
float height = parent.Size.y / columnCount;
|
||||
float yPos = startY + (column * height) + (height / 2.0f);
|
||||
|
||||
node.Center = new Vector3(xPos, yPos, 0.0f);
|
||||
node.Size = new Vector3(width, height, 0.0f);
|
||||
node.TopLeft = new Vector3(xPos - (width / 2.0f), yPos - (height / 2.0f), 0.0f);
|
||||
node.BottomRight = new Vector3(xPos + (width / 2.0f), yPos + (height / 2.0f), 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
float startZ = parent.Center.z - (parent.Size.z / 2.0f);
|
||||
float depth = parent.Size.z / columnCount;
|
||||
float zPos = startZ + (column * depth) + (depth / 2.0f);
|
||||
|
||||
node.Center = new Vector3(xPos, 0.0f, zPos);
|
||||
node.Size = new Vector3(width, 0.0f, depth);
|
||||
node.TopLeft = new Vector3(xPos - (width / 2.0f), 0.0f, zPos - (depth / 2.0f));
|
||||
node.BottomRight = new Vector3(xPos + (width / 2.0f), 0.0f, zPos + (depth / 2.0f));
|
||||
}
|
||||
|
||||
parent.AddChild(node);
|
||||
|
||||
this.CreateChildCells(node, (cellLevelInHierarchy + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the cells.
|
||||
/// </summary>
|
||||
private void DrawCells()
|
||||
{
|
||||
if ((this.CellTree != null) && (this.CellTree.RootNode != null))
|
||||
{
|
||||
this.CellTree.RootNode.Draw();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.RecreateCellHierarchy = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the cell count is allowed.
|
||||
/// </summary>
|
||||
/// <returns>True if the cell count is allowed, false if the cell count is too large.</returns>
|
||||
private bool IsCellCountAllowed()
|
||||
{
|
||||
int horizontalCells = 1;
|
||||
int verticalCells = 1;
|
||||
|
||||
foreach (Vector2 v in this.Subdivisions)
|
||||
{
|
||||
horizontalCells *= (int)v.x;
|
||||
verticalCells *= (int)v.y;
|
||||
}
|
||||
|
||||
this.CellCount = horizontalCells * verticalCells;
|
||||
|
||||
return (this.CellCount <= (MAX_NUMBER_OF_ALLOWED_CELLS - this.FIRST_GROUP_ID));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all cell IDs the player is currently inside or nearby.
|
||||
/// </summary>
|
||||
/// <param name="position">The current position of the player.</param>
|
||||
/// <returns>A list containing all cell IDs the player is currently inside or nearby.</returns>
|
||||
public List<byte> GetActiveCells(Vector3 position)
|
||||
{
|
||||
List<byte> activeCells = new List<byte>(0);
|
||||
this.CellTree.RootNode.GetActiveCells(activeCells, this.YIsUpAxis, position);
|
||||
|
||||
// it makes sense to sort the "nearby" cells. those are in the list in positions after the subdivisions the point is inside. 2 subdivisions result in 3 areas the point is in.
|
||||
int cellsActive = this.NumberOfSubdivisions + 1;
|
||||
int cellsNearby = activeCells.Count - cellsActive;
|
||||
if (cellsNearby > 0)
|
||||
{
|
||||
activeCells.Sort(cellsActive, cellsNearby, new ByteComparer());
|
||||
}
|
||||
return activeCells;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the tree accessible from its root node.
|
||||
/// </summary>
|
||||
public class CellTree
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the root node of the cell tree.
|
||||
/// </summary>
|
||||
public CellTreeNode RootNode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public CellTree()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to define the root node.
|
||||
/// </summary>
|
||||
/// <param name="root">The root node of the tree.</param>
|
||||
public CellTree(CellTreeNode root)
|
||||
{
|
||||
this.RootNode = root;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single node of the tree.
|
||||
/// </summary>
|
||||
public class CellTreeNode
|
||||
{
|
||||
public enum ENodeType : byte
|
||||
{
|
||||
Root = 0,
|
||||
Node = 1,
|
||||
Leaf = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the unique ID of the cell.
|
||||
/// </summary>
|
||||
public byte Id;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the center, top-left or bottom-right position of the cell
|
||||
/// or the size of the cell.
|
||||
/// </summary>
|
||||
public Vector3 Center, Size, TopLeft, BottomRight;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the current node type of the cell tree node.
|
||||
/// </summary>
|
||||
public ENodeType NodeType;
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the parent node.
|
||||
/// </summary>
|
||||
public CellTreeNode Parent;
|
||||
|
||||
/// <summary>
|
||||
/// A list containing all child nodes.
|
||||
/// </summary>
|
||||
public List<CellTreeNode> Childs;
|
||||
|
||||
/// <summary>
|
||||
/// The max distance the player can have to the center of the cell for being 'nearby'.
|
||||
/// This is calculated once at runtime.
|
||||
/// </summary>
|
||||
private float maxDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public CellTreeNode()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to define the ID and the node type as well as setting a parent node.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the cell is used as the interest group.</param>
|
||||
/// <param name="nodeType">The node type of the cell tree node.</param>
|
||||
/// <param name="parent">The parent node of the cell tree node.</param>
|
||||
public CellTreeNode(byte id, ENodeType nodeType, CellTreeNode parent)
|
||||
{
|
||||
this.Id = id;
|
||||
|
||||
this.NodeType = nodeType;
|
||||
|
||||
this.Parent = parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given child to the node.
|
||||
/// </summary>
|
||||
/// <param name="child">The child which is added to the node.</param>
|
||||
public void AddChild(CellTreeNode child)
|
||||
{
|
||||
if (this.Childs == null)
|
||||
{
|
||||
this.Childs = new List<CellTreeNode>(1);
|
||||
}
|
||||
|
||||
this.Childs.Add(child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the cell in the editor.
|
||||
/// </summary>
|
||||
public void Draw()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (this.Childs != null)
|
||||
{
|
||||
foreach (CellTreeNode node in this.Childs)
|
||||
{
|
||||
node.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
Gizmos.color = new Color((this.NodeType == ENodeType.Root) ? 1 : 0, (this.NodeType == ENodeType.Node) ? 1 : 0, (this.NodeType == ENodeType.Leaf) ? 1 : 0);
|
||||
Gizmos.DrawWireCube(this.Center, this.Size);
|
||||
|
||||
byte offset = (byte)this.NodeType;
|
||||
GUIStyle gs = new GUIStyle() { fontStyle = FontStyle.Bold };
|
||||
gs.normal.textColor = Gizmos.color;
|
||||
UnityEditor.Handles.Label(this.Center+(Vector3.forward*offset*1f), this.Id.ToString(), gs);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gathers all cell IDs the player is currently inside or nearby.
|
||||
/// </summary>
|
||||
/// <param name="activeCells">The list to add all cell IDs to the player is currently inside or nearby.</param>
|
||||
/// <param name="yIsUpAxis">Describes if the y-axis is used as up-axis.</param>
|
||||
/// <param name="position">The current position of the player.</param>
|
||||
public void GetActiveCells(List<byte> activeCells, bool yIsUpAxis, Vector3 position)
|
||||
{
|
||||
if (this.NodeType != ENodeType.Leaf)
|
||||
{
|
||||
foreach (CellTreeNode node in this.Childs)
|
||||
{
|
||||
node.GetActiveCells(activeCells, yIsUpAxis, position);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.IsPointNearCell(yIsUpAxis, position))
|
||||
{
|
||||
if (this.IsPointInsideCell(yIsUpAxis, position))
|
||||
{
|
||||
activeCells.Insert(0, this.Id);
|
||||
|
||||
CellTreeNode p = this.Parent;
|
||||
while (p != null)
|
||||
{
|
||||
activeCells.Insert(0, p.Id);
|
||||
|
||||
p = p.Parent;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
activeCells.Add(this.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given point is inside the cell.
|
||||
/// </summary>
|
||||
/// <param name="yIsUpAxis">Describes if the y-axis is used as up-axis.</param>
|
||||
/// <param name="point">The point to check.</param>
|
||||
/// <returns>True if the point is inside the cell, false if the point is not inside the cell.</returns>
|
||||
public bool IsPointInsideCell(bool yIsUpAxis, Vector3 point)
|
||||
{
|
||||
if ((point.x < this.TopLeft.x) || (point.x > this.BottomRight.x))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (yIsUpAxis)
|
||||
{
|
||||
if ((point.y >= this.TopLeft.y) && (point.y <= this.BottomRight.y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((point.z >= this.TopLeft.z) && (point.z <= this.BottomRight.z))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given point is near the cell.
|
||||
/// </summary>
|
||||
/// <param name="yIsUpAxis">Describes if the y-axis is used as up-axis.</param>
|
||||
/// <param name="point">The point to check.</param>
|
||||
/// <returns>True if the point is near the cell, false if the point is too far away.</returns>
|
||||
public bool IsPointNearCell(bool yIsUpAxis, Vector3 point)
|
||||
{
|
||||
if (this.maxDistance == 0.0f)
|
||||
{
|
||||
this.maxDistance = (this.Size.x + this.Size.y + this.Size.z) / 2.0f;
|
||||
}
|
||||
|
||||
return ((point - this.Center).sqrMagnitude <= (this.maxDistance * this.maxDistance));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class ByteComparer : IComparer<byte>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Compare(byte x, byte y)
|
||||
{
|
||||
return x == y ? 0 : x < y ? -1 : 1;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfb1c264fdc576442b2f42c998bed4a2
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,254 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="CullingHandler.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Handles the network culling.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Photon.Pun;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handles the network culling.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(PhotonView))]
|
||||
public class CullingHandler : MonoBehaviour, IPunObservable
|
||||
{
|
||||
#region VARIABLES
|
||||
|
||||
private int orderIndex;
|
||||
|
||||
private CullArea cullArea;
|
||||
|
||||
private List<byte> previousActiveCells, activeCells;
|
||||
|
||||
private PhotonView pView;
|
||||
|
||||
private Vector3 lastPosition, currentPosition;
|
||||
|
||||
|
||||
// used to limit the number of UpdateInterestGroups calls per second (there is no use to change groups more than a few times per second, even if the Culling algorithm makes it look like that)
|
||||
private float timeSinceUpdate;
|
||||
// see timeSinceUpdate
|
||||
private float timeBetweenUpdatesMin = 0.33f;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region UNITY_FUNCTIONS
|
||||
|
||||
/// <summary>
|
||||
/// Gets references to the PhotonView component and the cull area game object.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
if (this.pView == null)
|
||||
{
|
||||
this.pView = GetComponent<PhotonView>();
|
||||
|
||||
if (!this.pView.IsMine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.cullArea == null)
|
||||
{
|
||||
this.cullArea = FindObjectOfType<CullArea>();
|
||||
}
|
||||
|
||||
this.previousActiveCells = new List<byte>(0);
|
||||
this.activeCells = new List<byte>(0);
|
||||
|
||||
this.currentPosition = this.lastPosition = transform.position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the right interest group or prepares the permanent change of the interest Group of the PhotonView component.
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
if (!this.pView.IsMine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (PhotonNetwork.InRoom)
|
||||
{
|
||||
if (this.cullArea.NumberOfSubdivisions == 0)
|
||||
{
|
||||
this.pView.Group = this.cullArea.FIRST_GROUP_ID;
|
||||
|
||||
PhotonNetwork.SetInterestGroups(this.cullArea.FIRST_GROUP_ID, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is used to continuously update the active group.
|
||||
this.pView.ObservedComponents.Add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the player has moved previously and updates the interest groups if necessary.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (!this.pView.IsMine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// we'll limit how often this update may run at all (to avoid too frequent changes and flooding the server with SetInterestGroups calls)
|
||||
this.timeSinceUpdate += Time.deltaTime;
|
||||
if (this.timeSinceUpdate < this.timeBetweenUpdatesMin)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastPosition = this.currentPosition;
|
||||
this.currentPosition = transform.position;
|
||||
|
||||
// This is a simple position comparison of the current and the previous position.
|
||||
// When using Network Culling in a bigger project keep in mind that there might
|
||||
// be more transform-related options, e.g. the rotation, or other options to check.
|
||||
if (this.currentPosition != this.lastPosition)
|
||||
{
|
||||
if (this.HaveActiveCellsChanged())
|
||||
{
|
||||
this.UpdateInterestGroups();
|
||||
this.timeSinceUpdate = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drawing informations.
|
||||
/// </summary>
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!this.pView.IsMine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string subscribedAndActiveCells = "Inside cells:\n";
|
||||
string subscribedCells = "Subscribed cells:\n";
|
||||
|
||||
for (int index = 0; index < this.activeCells.Count; ++index)
|
||||
{
|
||||
if (index <= this.cullArea.NumberOfSubdivisions)
|
||||
{
|
||||
subscribedAndActiveCells += this.activeCells[index] + " | ";
|
||||
}
|
||||
|
||||
subscribedCells += this.activeCells[index] + " | ";
|
||||
}
|
||||
GUI.Label(new Rect(20.0f, Screen.height - 120.0f, 200.0f, 40.0f), "<color=white>PhotonView Group: " + this.pView.Group + "</color>", new GUIStyle() { alignment = TextAnchor.UpperLeft, fontSize = 16 });
|
||||
GUI.Label(new Rect(20.0f, Screen.height - 100.0f, 200.0f, 40.0f), "<color=white>" + subscribedAndActiveCells + "</color>", new GUIStyle() { alignment = TextAnchor.UpperLeft, fontSize = 16 });
|
||||
GUI.Label(new Rect(20.0f, Screen.height - 60.0f, 200.0f, 40.0f), "<color=white>" + subscribedCells + "</color>", new GUIStyle() { alignment = TextAnchor.UpperLeft, fontSize = 16 });
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the previously active cells have changed.
|
||||
/// </summary>
|
||||
/// <returns>True if the previously active cells have changed and false otherwise.</returns>
|
||||
private bool HaveActiveCellsChanged()
|
||||
{
|
||||
if (this.cullArea.NumberOfSubdivisions == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.previousActiveCells = new List<byte>(this.activeCells);
|
||||
this.activeCells = this.cullArea.GetActiveCells(transform.position);
|
||||
|
||||
// If the player leaves the area we insert the whole area itself as an active cell.
|
||||
// This can be removed if it is sure that the player is not able to leave the area.
|
||||
while (this.activeCells.Count <= this.cullArea.NumberOfSubdivisions)
|
||||
{
|
||||
this.activeCells.Add(this.cullArea.FIRST_GROUP_ID);
|
||||
}
|
||||
|
||||
if (this.activeCells.Count != this.previousActiveCells.Count)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.activeCells[this.cullArea.NumberOfSubdivisions] != this.previousActiveCells[this.cullArea.NumberOfSubdivisions])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from old and subscribes to new interest groups.
|
||||
/// </summary>
|
||||
private void UpdateInterestGroups()
|
||||
{
|
||||
List<byte> disable = new List<byte>(0);
|
||||
|
||||
foreach (byte groupId in this.previousActiveCells)
|
||||
{
|
||||
if (!this.activeCells.Contains(groupId))
|
||||
{
|
||||
disable.Add(groupId);
|
||||
}
|
||||
}
|
||||
|
||||
PhotonNetwork.SetInterestGroups(disable.ToArray(), this.activeCells.ToArray());
|
||||
}
|
||||
|
||||
#region IPunObservable implementation
|
||||
|
||||
/// <summary>
|
||||
/// This time OnPhotonSerializeView is not used to send or receive any kind of data.
|
||||
/// It is used to change the currently active group of the PhotonView component, making it work together with PUN more directly.
|
||||
/// Keep in mind that this function is only executed, when there is at least one more player in the room.
|
||||
/// </summary>
|
||||
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
|
||||
{
|
||||
// If the player leaves the area we insert the whole area itself as an active cell.
|
||||
// This can be removed if it is sure that the player is not able to leave the area.
|
||||
while (this.activeCells.Count <= this.cullArea.NumberOfSubdivisions)
|
||||
{
|
||||
this.activeCells.Add(this.cullArea.FIRST_GROUP_ID);
|
||||
}
|
||||
|
||||
if (this.cullArea.NumberOfSubdivisions == 1)
|
||||
{
|
||||
this.orderIndex = (++this.orderIndex % this.cullArea.SUBDIVISION_FIRST_LEVEL_ORDER.Length);
|
||||
this.pView.Group = this.activeCells[this.cullArea.SUBDIVISION_FIRST_LEVEL_ORDER[this.orderIndex]];
|
||||
}
|
||||
else if (this.cullArea.NumberOfSubdivisions == 2)
|
||||
{
|
||||
this.orderIndex = (++this.orderIndex % this.cullArea.SUBDIVISION_SECOND_LEVEL_ORDER.Length);
|
||||
this.pView.Group = this.activeCells[this.cullArea.SUBDIVISION_SECOND_LEVEL_ORDER[this.orderIndex]];
|
||||
}
|
||||
else if (this.cullArea.NumberOfSubdivisions == 3)
|
||||
{
|
||||
this.orderIndex = (++this.orderIndex % this.cullArea.SUBDIVISION_THIRD_LEVEL_ORDER.Length);
|
||||
this.pView.Group = this.activeCells[this.cullArea.SUBDIVISION_THIRD_LEVEL_ORDER[this.orderIndex]];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84db789113d4b01418c1becb128c4561
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcede10f4c76b443483f21320fee20e5
|
||||
folderAsset: yes
|
||||
timeCreated: 1529329408
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,265 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="CullAreaEditor.cs" company="Exit Games GmbH">
|
||||
// Part of: Photon Unity Utilities,
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Custom inspector for CullArea
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Photon.Pun.UtilityScripts
|
||||
{
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(CullArea))]
|
||||
public class CullAreaEditor : Editor
|
||||
{
|
||||
private bool alignEditorCamera, showHelpEntries;
|
||||
|
||||
private CullArea cullArea;
|
||||
|
||||
private enum UP_AXIS_OPTIONS
|
||||
{
|
||||
SideScrollerMode = 0,
|
||||
TopDownOr3DMode = 1
|
||||
}
|
||||
|
||||
private UP_AXIS_OPTIONS upAxisOptions;
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
cullArea = (CullArea)target;
|
||||
|
||||
// Destroying the newly created cull area if there is already one existing
|
||||
if (FindObjectsOfType<CullArea>().Length > 1)
|
||||
{
|
||||
Debug.LogWarning("Destroying newly created cull area because there is already one existing in the scene.");
|
||||
|
||||
DestroyImmediate(cullArea);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevents the dropdown from resetting
|
||||
if (cullArea != null)
|
||||
{
|
||||
upAxisOptions = cullArea.YIsUpAxis ? UP_AXIS_OPTIONS.SideScrollerMode : UP_AXIS_OPTIONS.TopDownOr3DMode;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
|
||||
if (Application.isEditor && !Application.isPlaying)
|
||||
{
|
||||
OnInspectorGUIEditMode();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnInspectorGUIPlayMode();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the inspector GUI when edit mode is active.
|
||||
/// </summary>
|
||||
private void OnInspectorGUIEditMode()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
#region DEFINE_UP_AXIS
|
||||
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.LabelField("Select game type", EditorStyles.boldLabel);
|
||||
upAxisOptions = (UP_AXIS_OPTIONS)EditorGUILayout.EnumPopup("Game type", upAxisOptions);
|
||||
cullArea.YIsUpAxis = (upAxisOptions == UP_AXIS_OPTIONS.SideScrollerMode);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
#region SUBDIVISION
|
||||
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.LabelField("Set the number of subdivisions", EditorStyles.boldLabel);
|
||||
cullArea.NumberOfSubdivisions = EditorGUILayout.IntSlider("Number of subdivisions", cullArea.NumberOfSubdivisions, 0, CullArea.MAX_NUMBER_OF_SUBDIVISIONS);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (cullArea.NumberOfSubdivisions != 0)
|
||||
{
|
||||
for (int index = 0; index < cullArea.Subdivisions.Length; ++index)
|
||||
{
|
||||
if ((index + 1) <= cullArea.NumberOfSubdivisions)
|
||||
{
|
||||
string countMessage = (index + 1) + ". Subdivision: row / column count";
|
||||
|
||||
EditorGUILayout.BeginVertical();
|
||||
cullArea.Subdivisions[index] = EditorGUILayout.Vector2Field(countMessage, cullArea.Subdivisions[index]);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
else
|
||||
{
|
||||
cullArea.Subdivisions[index] = new UnityEngine.Vector2(1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
#region UPDATING_MAIN_CAMERA
|
||||
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
|
||||
EditorGUILayout.LabelField("View and camera options", EditorStyles.boldLabel);
|
||||
alignEditorCamera = EditorGUILayout.Toggle("Automatically align editor view with grid", alignEditorCamera);
|
||||
|
||||
if (Camera.main != null)
|
||||
{
|
||||
if (GUILayout.Button("Align main camera with grid"))
|
||||
{
|
||||
Undo.RecordObject(Camera.main.transform, "Align main camera with grid.");
|
||||
|
||||
float yCoord = cullArea.YIsUpAxis ? cullArea.Center.y : Mathf.Max(cullArea.Size.x, cullArea.Size.y);
|
||||
float zCoord = cullArea.YIsUpAxis ? -Mathf.Max(cullArea.Size.x, cullArea.Size.y) : cullArea.Center.y;
|
||||
|
||||
Camera.main.transform.position = new Vector3(cullArea.Center.x, yCoord, zCoord);
|
||||
Camera.main.transform.LookAt(cullArea.transform.position);
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Current main camera position is " + Camera.main.transform.position.ToString());
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
cullArea.RecreateCellHierarchy = true;
|
||||
|
||||
AlignEditorView();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
showHelpEntries = EditorGUILayout.Foldout(showHelpEntries, "Need help with this component?");
|
||||
if (showHelpEntries)
|
||||
{
|
||||
EditorGUILayout.HelpBox("To find help you can either follow the tutorial or have a look at the forums by clicking on the buttons below.", MessageType.Info);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Open the tutorial"))
|
||||
{
|
||||
Application.OpenURL("https://doc.photonengine.com/en-us/pun/v2/demos-and-tutorials/package-demos/culling-demo");
|
||||
}
|
||||
if (GUILayout.Button("Take me to the forums"))
|
||||
{
|
||||
Application.OpenURL("https://forum.photonengine.com/categories/unity-networking-plugin-pun");
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the inspector GUI when play mode is active.
|
||||
/// </summary>
|
||||
private void OnInspectorGUIPlayMode()
|
||||
{
|
||||
EditorGUILayout.LabelField("No changes allowed when game is running. Please exit play mode first.", EditorStyles.boldLabel);
|
||||
}
|
||||
|
||||
public void OnSceneGUI()
|
||||
{
|
||||
Handles.BeginGUI();
|
||||
GUILayout.BeginArea(new Rect(Screen.width - 110, Screen.height - 90, 100, 60));
|
||||
|
||||
if (GUILayout.Button("Reset position"))
|
||||
{
|
||||
cullArea.transform.position = Vector3.zero;
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Reset scaling"))
|
||||
{
|
||||
cullArea.transform.localScale = new Vector3(25.0f, 25.0f, 25.0f);
|
||||
}
|
||||
|
||||
GUILayout.EndArea();
|
||||
Handles.EndGUI();
|
||||
|
||||
// Checking for changes of the transform
|
||||
if (cullArea.transform.hasChanged)
|
||||
{
|
||||
// Resetting position
|
||||
float posX = cullArea.transform.position.x;
|
||||
float posY = cullArea.YIsUpAxis ? cullArea.transform.position.y : 0.0f;
|
||||
float posZ = !cullArea.YIsUpAxis ? cullArea.transform.position.z : 0.0f;
|
||||
|
||||
cullArea.transform.position = new Vector3(posX, posY, posZ);
|
||||
|
||||
// Resetting scaling
|
||||
if (cullArea.Size.x < 1.0f || cullArea.Size.y < 1.0f)
|
||||
{
|
||||
float scaleX = (cullArea.transform.localScale.x < 1.0f) ? 1.0f : cullArea.transform.localScale.x;
|
||||
float scaleY = (cullArea.transform.localScale.y < 1.0f) ? 1.0f : cullArea.transform.localScale.y;
|
||||
float scaleZ = (cullArea.transform.localScale.z < 1.0f) ? 1.0f : cullArea.transform.localScale.z;
|
||||
|
||||
cullArea.transform.localScale = new Vector3(scaleX, scaleY, scaleZ);
|
||||
|
||||
Debug.LogWarning("Scaling on a single axis can not be lower than 1. Resetting...");
|
||||
}
|
||||
|
||||
cullArea.RecreateCellHierarchy = true;
|
||||
|
||||
AlignEditorView();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aligns the editor view with the created grid.
|
||||
/// </summary>
|
||||
private void AlignEditorView()
|
||||
{
|
||||
if (!alignEditorCamera)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This creates a temporary game object in order to align the editor view.
|
||||
// The created game object is destroyed afterwards.
|
||||
GameObject tmpGo = new GameObject();
|
||||
|
||||
float yCoord = cullArea.YIsUpAxis ? cullArea.Center.y : Mathf.Max(cullArea.Size.x, cullArea.Size.y);
|
||||
float zCoord = cullArea.YIsUpAxis ? -Mathf.Max(cullArea.Size.x, cullArea.Size.y) : cullArea.Center.y;
|
||||
|
||||
tmpGo.transform.position = new Vector3(cullArea.Center.x, yCoord, zCoord);
|
||||
tmpGo.transform.LookAt(cullArea.transform.position);
|
||||
|
||||
if (SceneView.lastActiveSceneView != null)
|
||||
{
|
||||
SceneView.lastActiveSceneView.AlignViewToObject(tmpGo.transform);
|
||||
}
|
||||
|
||||
DestroyImmediate(tmpGo);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abadaa451a7bff0489078ed9eec61133
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "PhotonUnityNetworking.Utilities.Culling.Editor",
|
||||
"references": [
|
||||
"PhotonUnityNetworking.Utilities"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06dafbbe6b7b3a84f84213f47aabe7f0
|
||||
timeCreated: 1537459565
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user