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