using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Hashtable = ExitGames.Client.Photon.Hashtable;
namespace Photon.Pun.Demo.Procedural
{
///
/// Describes the Size of the World.
/// A 'Tiny' world for example will be created with 16 x 16 Blocks.
///
public enum WorldSize
{
Tiny = 16,
Small = 32,
Medium = 64,
Large = 128
}
///
/// Describes the type of the generated world.
/// This basically influences the maximum height of a Block.
///
public enum WorldType
{
Flat = 4,
Standard = 8,
Mountain = 16
}
///
/// Describes how many Blocks are part of one CLuster.
/// Having a 'Small' ClusterSize increases the amount of Clusters being created,
/// whereat generating a world with a 'Large' ClusterSize doesn't create that many Clusters.
///
public enum ClusterSize
{
Small = 4,
Medium = 16,
Large = 64
}
///
/// The World Generator creates a world based on four options the current MasterClient can set.
/// These options are available on the Ingame Control Panel. If those options are confirmed by the current MasterClient,
/// they will be stored in the Custom Room Properties to make them available on all clients.
/// These options are:
/// 1) a numerical Seed to make sure that each client generates the same world and to avoid Random functions and 'network-instantiate' everything
/// 2) the World Size to describe how large the generated world should be
/// 3) the Cluster Size to describe how many Blocks are inside each Cluster
/// 4) the World Type to make the generated world appear in different 'looks'.
///
public class WorldGenerator : MonoBehaviour
{
public readonly string SeedPropertiesKey = "Seed";
public readonly string WorldSizePropertiesKey = "WorldSize";
public readonly string ClusterSizePropertiesKey = "ClusterSize";
public readonly string WorldTypePropertiesKey = "WorldType";
private static WorldGenerator instance;
public static WorldGenerator Instance
{
get
{
if (instance == null)
{
#if UNITY_6000_0_OR_NEWER
instance = FindFirstObjectByType();
#else
instance = FindObjectOfType();
#endif
}
return instance;
}
}
public int Seed { get; set; }
public WorldSize WorldSize { get; set; }
public ClusterSize ClusterSize { get; set; }
public WorldType WorldType { get; set; }
private Dictionary clusterList;
public Material[] WorldMaterials;
#region UNITY
public void Awake()
{
clusterList = new Dictionary();
WorldSize = WorldSize.Tiny;
ClusterSize = ClusterSize.Small;
WorldType = WorldType.Standard;
}
#endregion
#region CLASS FUNCTIONS
///
/// Called whenever a client receives a Custom Room Properties update containing all necessary information for creating a world.
/// If there is currently a world generation process running, it will be stopped automatically.
/// Also if there is a world already existing, it will be destroyed before the new one gets created.
///
public void CreateWorld()
{
StopAllCoroutines();
DestroyWorld();
StartCoroutine(GenerateWorld());
}
///
/// Destroys each Block from each Cluster before actually destroying the Cluster itself.
///
private void DestroyWorld()
{
Hashtable clusterPropertyUpdates = new Hashtable();
foreach (GameObject cluster in clusterList.Values)
{
Cluster clusterComponent = cluster.GetComponent();
clusterPropertyUpdates.Add(clusterComponent.ClusterPropKey, null); // collect all room property changes in one place, send once
clusterComponent.DestroyCluster(removeClusterFromRoomProperties: false);
Destroy(cluster);
}
PhotonNetwork.CurrentRoom.SetCustomProperties(clusterPropertyUpdates); // all cluster changes done in one SetProps call
clusterList.Clear();
}
///
/// Whenever the 'Confirm' button on the Control Panel is clicked by the MasterClient,
/// the Room Properties will be updated with the settings he defined.
///
public void ConfirmAndUpdateProperties()
{
if (!PhotonNetwork.IsMasterClient)
{
return;
}
Hashtable properties = new Hashtable
{
{SeedPropertiesKey, Seed},
{WorldSizePropertiesKey, (int) WorldSize},
{ClusterSizePropertiesKey, (int) ClusterSize},
{WorldTypePropertiesKey, (int) WorldType}
};
PhotonNetwork.CurrentRoom.SetCustomProperties(properties);
}
///
/// Decreases the height of a certain Block from a certain Cluster.
///
public void DecreaseBlockHeight(int clusterId, int blockId)
{
Cluster c = clusterList[clusterId].GetComponent();
if (c != null)
{
c.DecreaseBlockHeight(blockId);
}
}
///
/// Increases the height of a certain Block from a certain Cluster.
///
public void IncreaseBlockHeight(int clusterId, int blockId)
{
Cluster c = clusterList[clusterId].GetComponent();
if (c != null)
{
c.IncreaseBlockHeight(blockId);
}
}
#endregion
#region COROUTINES
///
/// Generates a new world based on the settings made either by the MasterClient on the
/// Ingame Control Panel or after receiving the new settings from the Custom Room Properties update.
///
private IEnumerator GenerateWorld()
{
Debug.Log(string.Format("Procedural Demo: Creating world using Seed: {0}, World Size: {1}, Cluster Size: {2} and World Type: {3}", Seed, WorldSize, ClusterSize, WorldType));
Simplex.Noise.Seed = Seed;
int clusterId = 0;
// Instantiating all necessary clusters at their target position
for (int x = 0; x < (int) WorldSize; x += (int) Mathf.Sqrt((int) ClusterSize))
{
for (int z = 0; z < (int) WorldSize; z += (int) Mathf.Sqrt((int) ClusterSize))
{
GameObject cluster = new GameObject();
cluster.name = "Cluster " + clusterId;
cluster.transform.SetParent(transform);
cluster.transform.position = new Vector3(x, 0.0f, z);
Cluster clusterComponent = cluster.AddComponent();
clusterComponent.ClusterId = clusterId;
clusterList.Add(clusterId++, cluster);
}
}
yield return new WaitForEndOfFrame();
// Instantiating all necessary blocks as a child of a certain cluster
foreach (GameObject cluster in clusterList.Values)
{
Vector3 clusterPosition = cluster.transform.position;
int blockId = 0;
for (int x = 0; x < (int) Mathf.Sqrt((int) ClusterSize); ++x)
{
for (int z = 0; z < (int) Mathf.Sqrt((int) ClusterSize); ++z)
{
float noiseValue = Simplex.Noise.CalcPixel2D((int) clusterPosition.x + x, (int) clusterPosition.z + z, 0.02f);
int height = (int) noiseValue / (int) (256.0f / (float) WorldType);
int materialIndex = (int) noiseValue / (int) (256.0f / WorldMaterials.Length);
GameObject block = GameObject.CreatePrimitive(PrimitiveType.Cube);
block.name = "Block " + blockId;
block.transform.SetParent(cluster.transform);
block.transform.localScale = new Vector3(1.0f, height, 1.0f);
block.transform.position = new Vector3(clusterPosition.x + x, height / 2.0f, clusterPosition.z + z);
block.GetComponent().material = WorldMaterials[materialIndex];
Block blockComponent = block.AddComponent();
blockComponent.BlockId = blockId;
blockComponent.ClusterId = cluster.GetComponent().ClusterId;
cluster.GetComponent().AddBlock(blockId++, block);
}
}
yield return new WaitForEndOfFrame();
}
// Applying modifications made to the world when joining the room later or while it is created
foreach (DictionaryEntry entry in PhotonNetwork.CurrentRoom.CustomProperties)
{
if (entry.Value == null)
{
continue;
}
string key = entry.Key.ToString();
if ((key == SeedPropertiesKey) || (key == WorldSizePropertiesKey) || (key == ClusterSizePropertiesKey) || (key == WorldTypePropertiesKey))
{
continue;
}
int indexOfBlank = key.IndexOf(' ');
key = key.Substring(indexOfBlank + 1, (key.Length - (indexOfBlank + 1)));
int.TryParse(key, out clusterId);
GameObject cluster;
if (clusterList.TryGetValue(clusterId, out cluster))
{
Cluster c = cluster.GetComponent();
if (c != null)
{
Dictionary clusterModifications = (Dictionary) entry.Value;
foreach (KeyValuePair pair in clusterModifications)
{
c.SetBlockHeightRemote(pair.Key, pair.Value);
}
}
}
}
}
#endregion
}
}