/// <summary> /// main method that should be called to populate biomes /// </summary> public void start(int level) { // create biomes int numBiomes = (int)Mathf.Max(WIDTH, HEIGHT) / 30; Debug.Log("Creating biomes"); // ocean for (int i = 0; i < numBiomes; i++) { switch (level) { case 1: case 2: default: // Ocean createBiome(Terrain.TerrainTypes.Ocean); // Desert createBiome(Terrain.TerrainTypes.DesertHill); // GrassHill createBiome(Terrain.TerrainTypes.GrassHill); // Mountain createBiome(Terrain.TerrainTypes.Grass); break; case 3: // Desert createBiome(Terrain.TerrainTypes.DesertHill); createBiome(Terrain.TerrainTypes.DesertHill); createBiome(Terrain.TerrainTypes.DesertHill); createBiome(Terrain.TerrainTypes.DesertHill); break; } } // Populate none biom areas with grass Debug.Log("Creating non biomes"); for (int i = 0; i < WIDTH; i++) { for (int j = 0; j < HEIGHT; j++) { MapTile tile = ScriptableObject.CreateInstance <MapTile>(); // A vector used for hex position Vector3Int vector = new Vector3Int(-i + WIDTH / 2, -j + HEIGHT / 2, 0); int[] pos = new int[2]; pos[0] = i; pos[1] = j; if (getTileTerrain(pos).Equals(Terrain.TerrainTypes.NotSet)) { if (nextToOcean(pos)) { tile.Terrain = new Terrain(Terrain.TerrainTypes.Beach); } else if (surroundedByDessert(pos) || level == 3) { tile.Terrain = new Terrain(Terrain.TerrainTypes.Desert); } else { tile.Terrain = new Terrain(Terrain.TerrainTypes.Grass); } SetTileTo(vector, tile); // Refresh the tile whenever its sprite changes. tile.SpriteChange += () => map.RefreshTile(vector); } } } if (level != 3) { // creates rivers until a sufficient length has been found. // max 4 attempts. for (int i = 0; i < 4 || riverLength >= Mathf.Max(WIDTH, HEIGHT) / 2; i++) { createRiver(); riverLength = 0; } } NormalizeWaterTiles(); }
/// <summary> /// grows a biome /// </summary> private void growBoime(int[] anchor, int biomHalfLength, Terrain.TerrainTypes terrain) { // constants that will be used further down the line float k = Mathf.Sqrt(Mathf.Pow(biomHalfLength, 2) * 2); float a = (float)2.0f / Mathf.Log10(Mathf.Pow(k, 2) - 2.0f); // growing biom for (int i = 0; i < biomHalfLength * 3; i++) { for (int j = 0; j < biomHalfLength * 3; j++) { // current position array where index 0 is X and index 1 is Y coordinate int[] curPos = new int[2]; curPos[0] = anchor[0] - biomHalfLength + i; curPos[1] = anchor[1] - biomHalfLength + j; // check if X and Y values are within the map if (curPos[0] < WIDTH && curPos[0] >= 0 && curPos[1] < HEIGHT && curPos[1] >= 0) { MapTile tile = ScriptableObject.CreateInstance <MapTile>(); // A vector used for hex position Vector3Int vector = new Vector3Int(-curPos[0] + WIDTH / 2, -curPos[1] + HEIGHT / 2, 0); // Find the real position (the position on the screen) Vector3 mappedVector = map.CellToWorld(vector); tile.Canvas = parent; tile.ScreenPosition = mappedVector; // check if terrain is vacant if (getTileTerrain(curPos).Equals(Terrain.TerrainTypes.NotSet)) { // Debug.Log("Cur: X: " + curX + ", Y: " + curY); // weighted random terrain allocation depending on distance from anchor int value = random.Next(0, 100); // weighting is done in such a that: // P = (k^2 - d^2)^a, // where: // P = probability of the biome tile being set // k = a constant (calculated earlier before the nested for loops) // d = absolute distance between the anchor and any potential biome tile // a = a constant (calculated earlier before the nested for loops) // // the formula satisfies the two equations below: // 100 = (k^2 - 2)^a, this ensures that the biome is atleast 3x3 // 0 = (k^2 - d^2)^a, where d is a distance for an arbitrary square just outside the biome half length // // so if biome half length is 7 then k = 11.3 and a = 0.484 float dist = Mathf.Sqrt(Mathf.Pow(anchor[0] - curPos[0], 2) + Mathf.Pow(anchor[1] - curPos[1], 2)); double prob = Mathf.Pow(Mathf.Pow(k, 2) - Mathf.Pow(dist, 2), a); if (value < prob) { occupiedBiomSpots[curPos] = terrain; tile.Terrain = new Terrain(terrain); SetTileTo(vector, tile); // add mountains if the biome is just grass if (terrain.Equals(Terrain.TerrainTypes.Grass)) { StructureFactory mountainFactory = new MountainFactory(); if (mountainFactory.CanBuildOnto(tile, out _)) { mountainFactory.BuildOnto(tile); } } // Refresh the tile whenever its sprite changes. tile.SpriteChange += () => map.RefreshTile(vector); } } } } } }
/// <summary> /// main iteration for the river /// should be called twice at same anchor /// </summary> private void extendRiverBiome(int[] anchor, float riverGradient) { // 5a) initialise variables int[] curPos = new int[2]; curPos[0] = anchor[0]; curPos[1] = anchor[1]; // this is to make the river a bit more wavy float riverGradientBuffer = 5f; int counter = 0; Boolean negativeGradientBuffer = true; float overallRiverGradient = riverGradient - riverGradientBuffer; Debug.Log("river iteration"); // main iteration // continue as long as river tiles are within map while (curPos[0] < WIDTH && curPos[0] > 0 && curPos[1] < HEIGHT && curPos[1] > 0 && counter < 300) { Debug.Log("curPos: X: " + curPos[0] + ", Y: " + curPos[1]); // get neighbouring tiles to current and iterate through them int[,] adjPos = getNeighbouringTiles(curPos); float gradientDifference = WIDTH * HEIGHT * 10000; int[] prevPos = new int[2]; prevPos[0] = curPos[0]; prevPos[1] = curPos[1]; // iterate through the neighbouring tiles for (int i = 0; i < adjPos.GetLength(0); i++) { int[] temp = new int[2]; temp[0] = adjPos[i, 0]; temp[1] = adjPos[i, 1]; // give river gradient a bit of buffer variance if (counter % 15 == 0) { anchor[0] = curPos[0]; anchor[1] = curPos[1]; if (negativeGradientBuffer) { negativeGradientBuffer = false; overallRiverGradient += riverGradientBuffer; } else { negativeGradientBuffer = true; overallRiverGradient -= riverGradientBuffer; } } // if the neighbouring tile is within the map // set the tile to river if (temp[0] < WIDTH && temp[0] >= 0 && temp[1] < HEIGHT && temp[1] >= 0 && getTileTerrain(temp).Equals(Terrain.TerrainTypes.NotSet)) { float gradient; // calculate gradient of the adjPos to anchor try { gradient = (anchor[1] - temp[1]) / (anchor[0] - temp[0]); } catch (DivideByZeroException) { gradient = WIDTH * HEIGHT * 10000; // some large number of signify inifinity } // get tile with smallest gradient change to be next current position if (Mathf.Abs(overallRiverGradient - gradient) <= gradientDifference) { gradientDifference = Mathf.Abs(overallRiverGradient - gradient); curPos[0] = temp[0]; curPos[1] = temp[1]; // make sure the river does not get too close to other biomes besides oceans int biomeLengthValue = (int)(Mathf.Max(WIDTH, HEIGHT) / 5); // int biomeHalfLength = random.Next(biomeLengthValue - 2, biomeLengthValue + 2); if (!riverTileTooCloseToBiome(temp, biomeLengthValue)) { float gradientToClosestBiome = getGradientOfClosestBiomeTile(curPos, biomeLengthValue); int[] closestBiometile = getClosestBiomeAnchorTile(curPos); float prevDist = Mathf.Sqrt(Mathf.Pow(prevPos[0] - closestBiometile[0], 2) + Mathf.Pow(prevPos[1] - closestBiometile[1], 2)); float curDist = Mathf.Sqrt(Mathf.Pow(curPos[0] - closestBiometile[0], 2) + Mathf.Pow(curPos[1] - closestBiometile[1], 2)); Debug.Log("prevDist: " + prevDist); Debug.Log("curDist: " + curDist); // check if the river is heading towards a biome and if is change gradient of the river if (curDist < prevDist) { overallRiverGradient = -1f / gradientToClosestBiome; } } } // set tile to River MapTile tile = ScriptableObject.CreateInstance <MapTile>(); // A vector used for hex position Vector3Int vector = new Vector3Int(-temp[0] + WIDTH / 2, -temp[1] + HEIGHT / 2, 0); // Find the real position (the position on the screen) Vector3 mappedVector = map.CellToWorld(vector); tile.Canvas = parent; tile.ScreenPosition = mappedVector; tile.Terrain = new Terrain(Terrain.TerrainTypes.River); occupiedBiomSpots[temp] = Terrain.TerrainTypes.River; SetTileTo(vector, tile); // Refresh the tile whenever its sprite changes. tile.SpriteChange += () => map.RefreshTile(vector); } } if (getTileTerrain(curPos).Equals(Terrain.TerrainTypes.Ocean)) { // reached ocean break; } if (prevPos[0].Equals(curPos[0]) && prevPos[1].Equals(curPos[1])) { // current position did not change so stop loop. break; } counter++; riverLength++; } }
/// <summary> /// creates a river /// 1) first the centre is determined /// 2) radius of the circle around origin related values are calculated, ie the base value which is proportional to map dimensions, /// a random nonce that dictates the possible range of the radius /// 3) anchor on the circumference of the circle is randomly made /// 4) gradient of the river is calculated, it will be tangential to the circle, ie gradient of river = -1/(gradient of line origin to anchor) /// 5) get neighbouring tiles of current position and set tile with gradient of river as water, repeat until next tile is water or out of map /// <summary> private void createRiver() { Debug.Log("creating river"); // 1) get coordinate of origin int[] centre = new int[2]; centre[0] = WIDTH / 2; centre[1] = HEIGHT / 2; Debug.Log("centre: X: " + centre[0] + ", Y: " + centre[1]); // 2) calculate radius related value of circle around origin int radiusBaseValue = Mathf.Max(WIDTH, HEIGHT) / 4; int radiusNonce = random.Next(0, Mathf.Max(WIDTH, HEIGHT) / 30); Debug.Log("radiusBaseValue: " + radiusBaseValue); Debug.Log("radiusNonce: " + radiusNonce); // 3) anchor of the river int[] anchor = new int[2]; anchor[0] = random.Next(centre[0] - (radiusBaseValue + radiusNonce), centre[0] + (radiusBaseValue + radiusNonce)); anchor[1] = random.Next(centre[1] - (radiusBaseValue + radiusNonce), centre[1] + (radiusBaseValue + radiusNonce)); Debug.Log("anchor: X: " + anchor[0] + ", Y: " + anchor[1]); // adding anchor to screen MapTile anchorTile = ScriptableObject.CreateInstance <MapTile>(); Vector3Int anchorVector = new Vector3Int(-anchor[0] + WIDTH / 2, -anchor[1] + HEIGHT / 2, 0); Vector3 anchorMappedVector = map.CellToWorld(anchorVector); Debug.Log("anchor Vector: X: " + (-anchor[0] + WIDTH / 2) + ", Y: " + (-anchor[1] + HEIGHT / 2)); anchorTile.Canvas = parent; anchorTile.ScreenPosition = anchorMappedVector; anchorTile.Terrain = new Terrain(Terrain.TerrainTypes.River); biomeAnchors[anchor] = Terrain.TerrainTypes.River; // 4) calculate gradient of the river float riverGradient; try { riverGradient = -(anchor[0] - centre[0]) / (anchor[1] - centre[1]); } catch (DivideByZeroException) { riverGradient = 1000; // some large number to signify infinity } Debug.Log("river gradient: " + riverGradient); // 5) expand river in bidirectionally extendRiverBiome(anchor, riverGradient); //while (riverLength < Mathf.Max(WIDTH, HEIGHT) / 2) //{ int[,] adjPos = getNeighbouringTiles(anchor); int value = random.Next(0, 6); int[] anchor2 = new int[2]; anchor2[0] = adjPos[value, 0]; anchor2[1] = adjPos[value, 1]; extendRiverBiome(anchor2, riverGradient); //} // add anchor to occupiedBiomeSpots so that it won't get overwritten when non biomes are made occupiedBiomSpots[anchor] = Terrain.TerrainTypes.River; }
/// <summary> /// Generates the particle effect when a demolision occurs /// </summary> /// <param name="tile"></param> /// <returns></returns> IEnumerator GenerateDestructionParticles(MapTile tile) { GameObject copyOfGameObject = GameObject.Instantiate(tile.Structure.GameObject); copyOfGameObject.name = "CopyStructures"; copyOfGameObject.transform.SetParent(tile.Structure.GameObject.transform.parent.gameObject.transform); GameObject customParticleSystem = new GameObject("CustomDemolishParticle"); customParticleSystem.transform.SetParent(copyOfGameObject.transform.parent, false); customParticleSystem.transform.position = copyOfGameObject.transform.position; ParticleSystem particles = customParticleSystem.AddComponent <ParticleSystem>(); Particles.InitParticleSystem(particles); particles.Stop(); copyOfGameObject.AddComponent <ShakerBehaviour>(); ParticleSystem.MainModule mainModule = particles.main; mainModule.startColor = Color.white; mainModule.startLifetime = 1; mainModule.startSize = new ParticleSystem.MinMaxCurve(0.3f, 0.5f); mainModule.startSpeed = 0.6f; mainModule.maxParticles = 100; mainModule.loop = false; mainModule.duration = 1; ParticleSystem.ShapeModule shapeModule = particles.shape; shapeModule.shapeType = ParticleSystemShapeType.Sphere; shapeModule.angle = 25; shapeModule.radius = 0.8f; shapeModule.scale = new Vector3(0.3f, 0.1f, 1); shapeModule.position = new Vector3(0, -0.4f, 0); var emission = particles.emission; emission.rateOverTime = 30; emission.enabled = true; var colorOverLifetime = particles.colorOverLifetime; Gradient gradientLifetime = new Gradient(); GradientColorKey[] colorKeys = new GradientColorKey[2]; colorKeys[0].color = Color.white; colorKeys[0].time = 0.0f; colorKeys[1].color = Color.white; colorKeys[1].time = 1.0f; GradientAlphaKey[] alphaKeys = new GradientAlphaKey[2]; alphaKeys[0].alpha = 1; alphaKeys[0].time = 0.5f; alphaKeys[1].alpha = 0; alphaKeys[1].time = 1.0f; gradientLifetime.SetKeys(colorKeys, alphaKeys); colorOverLifetime.color = gradientLifetime; colorOverLifetime.enabled = true; var sizeOverLifetime = particles.sizeOverLifetime; sizeOverLifetime.size = new ParticleSystem.MinMaxCurve(1, new AnimationCurve(new Keyframe(0.0f, 0.0f), new Keyframe(0.5f, 1.0f, 0, 0))); sizeOverLifetime.enabled = true; var limit = particles.limitVelocityOverLifetime; limit.separateAxes = true; limit.limitX = 1; limit.limitY = 0; limit.limitZ = 0; limit.dampen = 1; limit.enabled = true; ParticleSystem.TextureSheetAnimationModule textureSheetAnimationModule = particles.textureSheetAnimation; textureSheetAnimationModule.enabled = true; textureSheetAnimationModule.mode = ParticleSystemAnimationMode.Sprites; textureSheetAnimationModule.SetSprite(0, Resources.Load <Sprite>("Textures/CloudParticle")); Renderer renderer = particles.GetComponent <Renderer>(); renderer.sortingLayerName = "Structure"; renderer.sortingOrder = 100; particles.Play(); var existingSmoker = tile.Structure.GameObject.GetComponentInChildren <ParticleSystem>(); if (existingSmoker != null) { var position = existingSmoker.transform.localPosition; var scale = existingSmoker.transform.localScale; // Wait until structure does its unrendering. yield return(new WaitForEndOfFrame()); // Copy over existing smoker and make them stay at their place relative to the world even when the building is falling. existingSmoker.transform.SetParent(copyOfGameObject.transform); existingSmoker.transform.localScale = scale; existingSmoker.transform.localPosition = position; // Stop any existing smokers. Let their trail stay though. foreach (var smoker in copyOfGameObject.GetComponentsInChildren <ParticleSystem>()) { smoker.Stop(); } } yield return(new WaitForSeconds(10)); GameObject.Destroy(copyOfGameObject); GameObject.Destroy(customParticleSystem); }
private void GenerateWindEffect(MapTile tile) { GameObject air = new GameObject(); air.transform.SetParent(tile.Structure.GameObject.transform); air.transform.localPosition = new Vector3(1f, -0.5f, -4.5f); air.transform.localScale = new Vector3(0.5f, 0.5f, 1); ParticleSystem particles = air.AddComponent <ParticleSystem>(); Particles.InitParticleSystem(particles); ParticleSystem.MainModule mainParticle = particles.main; mainParticle.startLifetime = 1.5f; mainParticle.startSpeed = 0; mainParticle.startSize = 0.1f; mainParticle.maxParticles = 5; ParticleSystem.TrailModule trailMode = particles.trails; trailMode.enabled = true; trailMode.lifetime = new ParticleSystem.MinMaxCurve(0.8f); trailMode.dieWithParticles = false; AnimationCurve trailCurve = new AnimationCurve(); trailCurve.AddKey(0, 0); trailCurve.AddKey(0.5f, 1); trailCurve.AddKey(1, 0); trailMode.widthOverTrail = new ParticleSystem.MinMaxCurve(1, trailCurve); ParticleSystem.EmissionModule emissionModule = particles.emission; emissionModule.rateOverTime = 3; ParticleSystemRenderer particleRenderer = particles.GetComponent <ParticleSystemRenderer>(); particleRenderer.trailMaterial = Resources.Load <Material>("Wind"); ParticleSystem.ShapeModule shapeModule = particles.shape; shapeModule.shapeType = ParticleSystemShapeType.Box; shapeModule.rotation = new Vector3(0, 0, 180); shapeModule.scale = new Vector3(0.1f, 1, 1); ParticleSystem.VelocityOverLifetimeModule velocityOverLifetimeModule = particles.velocityOverLifetime; velocityOverLifetimeModule.enabled = true; AnimationCurve curveX = new AnimationCurve(); curveX.AddKey(0, 1); curveX.AddKey(1, 1); AnimationCurve curveY = new AnimationCurve(); curveY.AddKey(0, 0); curveY.AddKey(0.33f, 1); curveY.AddKey(0.66f, -1); curveY.AddKey(1, 0); AnimationCurve curveZ = new AnimationCurve(); curveZ.AddKey(0, 0); curveZ.AddKey(1.0f, 0); //velocityOverLifetimeModule.space = ParticleSystemSimulationSpace.Local; velocityOverLifetimeModule.x = new ParticleSystem.MinMaxCurve(1, curveX); velocityOverLifetimeModule.y = new ParticleSystem.MinMaxCurve(1, curveY); velocityOverLifetimeModule.z = new ParticleSystem.MinMaxCurve(0, curveZ); ParticleSystem.ColorOverLifetimeModule colorOverLifetimeModule = particles.colorOverLifetime; colorOverLifetimeModule.enabled = true; Gradient gradient = new Gradient(); gradient.SetKeys(new [] { new GradientColorKey(new Color32(167, 232, 242, 255), 0.0f), new GradientColorKey(Color.white, 1.0f) }, new [] { new GradientAlphaKey(1.0f, 0.0f), new GradientAlphaKey(0.0f, 1.0f) }); colorOverLifetimeModule.color = gradient; }
public TileClickArgs(MapTile tile) { this.Tile = tile; }