// --------------------------------------------------------------------------------------------------------------------
//
// Part of: Photon Unity Utilities,
//
//
// Handles the network culling.
//
// developer@exitgames.com
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
namespace Photon.Pun.UtilityScripts
{
using ExitGames.Client.Photon;
///
/// Handles the network culling.
///
[RequireComponent(typeof(PhotonView))]
public class CullingHandler : MonoBehaviour, IPunObservable
{
#region VARIABLES
private int orderIndex;
private CullArea cullArea;
private List 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
///
/// Gets references to the PhotonView component and the cull area game object.
///
private void OnEnable()
{
if (this.pView == null)
{
this.pView = GetComponent();
if (!this.pView.IsMine)
{
return;
}
}
if (this.cullArea == null)
{
this.cullArea = FindObjectOfType();
}
this.previousActiveCells = new List(0);
this.activeCells = new List(0);
this.currentPosition = this.lastPosition = transform.position;
}
///
/// Initializes the right interest group or prepares the permanent change of the interest Group of the PhotonView component.
///
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);
}
}
}
///
/// Checks if the player has moved previously and updates the interest groups if necessary.
///
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;
}
}
}
///
/// Drawing informations.
///
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), "PhotonView Group: " + this.pView.Group + "", new GUIStyle() { alignment = TextAnchor.UpperLeft, fontSize = 16 });
GUI.Label(new Rect(20.0f, Screen.height - 100.0f, 200.0f, 40.0f), "" + subscribedAndActiveCells + "", new GUIStyle() { alignment = TextAnchor.UpperLeft, fontSize = 16 });
GUI.Label(new Rect(20.0f, Screen.height - 60.0f, 200.0f, 40.0f), "" + subscribedCells + "", new GUIStyle() { alignment = TextAnchor.UpperLeft, fontSize = 16 });
}
#endregion
///
/// Checks if the previously active cells have changed.
///
/// True if the previously active cells have changed and false otherwise.
private bool HaveActiveCellsChanged()
{
if (this.cullArea.NumberOfSubdivisions == 0)
{
return false;
}
this.previousActiveCells = new List(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;
}
///
/// Unsubscribes from old and subscribes to new interest groups.
///
private void UpdateInterestGroups()
{
List disable = new List(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
///
/// 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.
///
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
}
}