From fdd71edba562a947560c47298f48cfbaf3b64936 Mon Sep 17 00:00:00 2001 From: Alex Stewart Date: Mon, 11 Aug 2025 22:03:32 -0700 Subject: [PATCH] Updated plant behavior to new system. --- .../GameAssets/Scripts/NutrientController.cs | 49 +++--- Assets/GameAssets/Scripts/Plant.cs | 148 ++++++++++-------- Assets/GameAssets/Scripts/PlantController.cs | 59 ++++--- 3 files changed, 139 insertions(+), 117 deletions(-) diff --git a/Assets/GameAssets/Scripts/NutrientController.cs b/Assets/GameAssets/Scripts/NutrientController.cs index 86237e1..321d0b0 100644 --- a/Assets/GameAssets/Scripts/NutrientController.cs +++ b/Assets/GameAssets/Scripts/NutrientController.cs @@ -9,11 +9,13 @@ public class NutrientController : MonoBehaviour private float[,] nutrientMap; [SerializeField, Range(10, 256)] int sizeX; [SerializeField, Range(10, 256)] int sizeY; + [SerializeField, Range(0f, 1f)] float recoveryRate; [SerializeField] MeshRenderer meshRenderer; // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { + nutrientMap = new float[sizeX, sizeY]; for(int r = 0; r < sizeX; r ++) { @@ -38,30 +40,27 @@ public class NutrientController : MonoBehaviour { if (nutrientMap[r,c] < 1) { - nutrientMap[r, c] += (0.1f * dTime); + nutrientMap[r, c] += (recoveryRate/60.0f * dTime); + if(nutrientMap[r, c] > 1) + { + nutrientMap[r, c] = 1; + } } } } } - public float PlantNutrientUse(float x, float y, float radius, float nutrientsNeeded) + public float PlantNutrientUse(float x, float z, float radius, float nutrientsNeeded) { int locX = Mathf.FloorToInt(x * sizeX); - int locY = Mathf.FloorToInt(y * sizeY); + int locZ = Mathf.FloorToInt(z * sizeY); int flooredRadius = Mathf.FloorToInt(radius); - float totalNutrients = nutrientMap[locX, locY]; + float totalNutrients = nutrientMap[locX, locZ]; float[,] nutrientsAvailable; - try - { - nutrientsAvailable = new float[flooredRadius * 2 + 1, flooredRadius * 2 + 1]; - } catch (Exception e) - { - Debug.Log($"x : {x} y : {y} radius : {radius} nutrients needed : {nutrientsNeeded}"); - return 2000000; - } + nutrientsAvailable = new float[flooredRadius * 2 + 1, flooredRadius * 2 + 1]; // Calculate total amount of nutrients available to the plant, at a 1/ (2 * steps from origin) ratio. for (int r = -flooredRadius; r <= flooredRadius; r++) @@ -70,21 +69,22 @@ public class NutrientController : MonoBehaviour for(int c = -flooredRadius; c <= flooredRadius; c++) { - int currentLocY = locY + c; - if (currentLocX < 0 || currentLocX >= nutrientMap.GetLength(0) || currentLocY < 0 || currentLocY >= nutrientMap.GetLength(1)) + int currentLocZ = locZ + c; + // 26 , 255 + if (currentLocX < 0 || currentLocX >= nutrientMap.GetLength(0) || currentLocZ < 0 || currentLocZ >= nutrientMap.GetLength(1)) { - nutrientsAvailable[flooredRadius, flooredRadius] = 0; + nutrientsAvailable[flooredRadius + r, flooredRadius + c] = 0; } else { if (r == 0 && c == 0) { - nutrientsAvailable[flooredRadius, flooredRadius] = nutrientMap[currentLocX, currentLocY]; + nutrientsAvailable[flooredRadius, flooredRadius] = nutrientMap[currentLocX, currentLocZ]; } else { - nutrientsAvailable[flooredRadius + r, flooredRadius + c] = nutrientMap[currentLocX, currentLocY] / ((Mathf.Abs(r) + Mathf.Abs(c)) * 2); - totalNutrients += nutrientMap[currentLocX, currentLocY] / ((Mathf.Abs(r) + Mathf.Abs(c)) * 2); + nutrientsAvailable[flooredRadius + r, flooredRadius + c] = nutrientMap[currentLocX, currentLocZ] / ((Mathf.Abs(r) + Mathf.Abs(c)) * 2); + totalNutrients += nutrientMap[currentLocX, currentLocZ] / ((Mathf.Abs(r) + Mathf.Abs(c)) * 2); } } } @@ -100,7 +100,7 @@ public class NutrientController : MonoBehaviour for (int c = -flooredRadius; c <= flooredRadius; c++) { - int currentLocY = locY + c; + int currentLocY = locZ + c; if (currentLocX >= 0 && currentLocX < nutrientMap.GetLength(0) && currentLocY >= 0 && currentLocY < nutrientMap.GetLength(1)) { nutrientMap[currentLocX, currentLocY] -= (nutrientsAvailable[flooredRadius + r, flooredRadius + c]); @@ -109,14 +109,13 @@ public class NutrientController : MonoBehaviour } return nutrientsNeeded - totalNutrients; } - for (int r = -flooredRadius; r <= flooredRadius; r++) { int currentLocX = locX + r; for (int c = -flooredRadius; c <= flooredRadius; c++) { - int currentLocY = locY + c; + int currentLocY = locZ + c; if (currentLocX >= 0 && currentLocX < nutrientMap.GetLength(0) && currentLocY >= 0 && currentLocY < nutrientMap.GetLength(1)) { nutrientMap[currentLocX, currentLocY] -= (nutrientsAvailable[flooredRadius + r, flooredRadius + c] / totalNutrients) * nutrientsNeeded; @@ -162,14 +161,6 @@ public class NutrientController : MonoBehaviour return totalNutrients; } - public void UseNutrients(float x, float y, float radius, float used) - { - int locX = Mathf.FloorToInt(x * sizeX); - int locY = Mathf.FloorToInt(y * sizeY); - - nutrientMap[locX, locY] -= used; - } - public void AddNutrients(float x, float y, float added, float radius) { int locX = Mathf.FloorToInt(x * sizeX); diff --git a/Assets/GameAssets/Scripts/Plant.cs b/Assets/GameAssets/Scripts/Plant.cs index da03398..3049f28 100644 --- a/Assets/GameAssets/Scripts/Plant.cs +++ b/Assets/GameAssets/Scripts/Plant.cs @@ -1,23 +1,19 @@ using UnityEditor; using UnityEngine; +using UnityEngine.UIElements; public class Plant : MonoBehaviour { - public float healthMax; - public float healthCurrent; - public float nutrientNeed; + private float health; public float growthRate; public float nutrientsConsumed; - public float nutrientsRequiredStep; + public float maxSize; public float size; public float reproductionChance; - public float reproductionDist; - public bool isReproducing; - public float childXPos; - public float childYPos; public bool isAlive; + public bool isReproducing; public GameObject visualPrefab; public GameObject visual; @@ -29,23 +25,20 @@ public class Plant : MonoBehaviour this.visualPrefab = visualPrefab; } - public void SetValues(float healthMax, float nutrientNeed, float growthRate) + public void SetValues(float growthRate, float maxSize, float reproductionChance) { - this.healthMax = healthMax; - this.nutrientNeed = nutrientNeed; + health = 10; this.growthRate = growthRate; - reproductionChance = 1; - reproductionDist = Random.Range(1f, 500f); + this.maxSize = maxSize; + this.reproductionChance = reproductionChance; } // Start is called once before the first execution of Update after the MonoBehaviour is created private void Start() { visual = Instantiate(visualPrefab, transform); - healthCurrent = healthMax; nutrientsConsumed = 0; isAlive = true; - isReproducing = false; size = 1f; } @@ -58,70 +51,86 @@ public class Plant : MonoBehaviour public void Step(float dTime) { Terrain terrain = Core.instance.ground; + float xPosition = transform.position.x / terrain.terrainData.size.x; + float zPosition = transform.position.z / terrain.terrainData.size.z; + NutrientController nutrientCont = Core.instance.nutrientCont; - float xBound = terrain.terrainData.size.x; - float yBound = terrain.terrainData.size.z; - float x = transform.position.x / xBound; - float y = transform.position.z / yBound; - - //availNutrients = nutrientCont.GetAvailNutrients(x, y, size); - - nutrientsRequiredStep = Mathf.Pow(size, 2f) * nutrientNeed * dTime; - - float nutrientDeficit = nutrientCont.PlantNutrientUse(x, y, size, nutrientsRequiredStep); - - if (nutrientDeficit > 0) + if (size >= maxSize) { - healthCurrent -= nutrientDeficit; - nutrientsConsumed += (nutrientsRequiredStep - nutrientDeficit); + TryReproduce(dTime, nutrientCont, xPosition, zPosition); } else { - float adjustedGrowth = (growthRate * 1.5f) / size; - size += adjustedGrowth * dTime; - if(visual != null) + if(size > maxSize * 0.67f && Random.value > 0.5f) { - visual.transform.localScale = new Vector3(size, size, size); + TryReproduce(dTime, nutrientCont, xPosition, zPosition); } - nutrientsConsumed += nutrientsRequiredStep; - } - - if (healthCurrent <= 0) - { - nutrientCont.AddNutrients(x, y, (nutrientsConsumed * 0.9f), size); - isAlive = false; - } - - float reproRoll = Random.Range(0.0f, 1f); - if(reproRoll > 1 - (reproductionChance * dTime)) - { - float direction; - float xOffset; - float yOffset; - int counter = 0; - do + else { - direction = Random.Range(0f, 360f); - xOffset = (reproductionDist + Random.Range(-0.1f * reproductionDist, 0.1f * reproductionDist)) * Mathf.Cos(direction * Mathf.Deg2Rad); - yOffset = (reproductionDist + Random.Range(-0.1f * reproductionDist, 0.1f * reproductionDist)) * Mathf.Sin(direction * Mathf.Deg2Rad); + Grow(dTime, nutrientCont, xPosition, zPosition); + } + } - childXPos = transform.position.x + xOffset; - childYPos = transform.position.y + yOffset; - counter++; - if(counter >= 50) - { - return; - } - } while (childXPos > xBound || childXPos < 0 || childYPos > yBound || childYPos < 0); - isReproducing = true; + if (health <= 0) + { + isAlive = false; + nutrientCont.AddNutrients(xPosition, zPosition, nutrientsConsumed * 0.9f, size); } } - public void childMade() + private void TryReproduce(float dTime, NutrientController nutrientCont, float xPosition, float zPosition) { - childXPos = 0; - childYPos = 0; - isReproducing = false; + if(reproductionChance >= 1) + { + ReproduceSuccess(dTime, nutrientCont, xPosition, zPosition); + } + else + { + float stepChance = reproductionChance * dTime; + + if(Random.value < stepChance) + { + ReproduceSuccess(dTime, nutrientCont, xPosition, zPosition); + } + } + } + + private void ReproduceSuccess(float dTime, NutrientController nutrientCont, float xPosition, float zPosition) + { + float stepGrowthRate = maxSize - Mathf.Pow(size, 2); + stepGrowthRate *= growthRate; + stepGrowthRate *= dTime; + + float stepNutrientCost = (stepGrowthRate / 2) + 0.5f; + float nutrientDeficit = nutrientCont.PlantNutrientUse(xPosition, zPosition, size, stepNutrientCost); + + // Take damage if there aren't enough nutrients to reproduce + if(nutrientDeficit > 0) + { + health -= nutrientDeficit; + } + isReproducing = true; + } + + private void Grow(float dTime, NutrientController nutrientCont, float xPosition, float zPosition) + { + float stepGrowthRate = maxSize - Mathf.Pow(size, 2); + stepGrowthRate *= growthRate; + stepGrowthRate *= dTime; + + float stepNutrientCost = (stepGrowthRate / 2) + 0.5f; + float nutrientDeficit = nutrientCont.PlantNutrientUse(xPosition, zPosition, size, stepNutrientCost); + if (nutrientDeficit > 0) + { + health -= nutrientDeficit * dTime; + nutrientsConsumed += stepNutrientCost - nutrientDeficit; + } + else + { + nutrientsConsumed += stepNutrientCost; + size += stepGrowthRate; + GrowVisual(stepGrowthRate); + } } private void OnDrawGizmos() @@ -134,6 +143,11 @@ public class Plant : MonoBehaviour GUIStyle big = new GUIStyle(); big.fontSize = 50; big.normal.textColor = Color.purple; - Handles.Label(transform.position, $"Health: {healthCurrent}\nSize: {size}\nnRequired: {nutrientsRequiredStep}", big); + Handles.Label(transform.position, $"Health: {health}", big); + } + + void GrowVisual(float growthAmount) + { + visual.transform.localScale += visual.transform.localScale * growthAmount; } } diff --git a/Assets/GameAssets/Scripts/PlantController.cs b/Assets/GameAssets/Scripts/PlantController.cs index 5e8d6fc..9e3046f 100644 --- a/Assets/GameAssets/Scripts/PlantController.cs +++ b/Assets/GameAssets/Scripts/PlantController.cs @@ -7,6 +7,7 @@ public class PlantController : MonoBehaviour [SerializeField] GameObject plantPrefab; [SerializeField] List plantModels; List plants; + List plantQueue; float xBound; float zBound; Terrain terrain; @@ -15,6 +16,7 @@ public class PlantController : MonoBehaviour void Start() { plants = new List(); + plantQueue = new List(); terrain = Core.instance.ground; xBound = terrain.GetComponent().terrainData.size.x; zBound = terrain.GetComponent().terrainData.size.z; @@ -23,7 +25,7 @@ public class PlantController : MonoBehaviour { GameObject newPlant = Instantiate(plantPrefab); newPlant.GetComponent().SetVisual(plantModels[Random.Range(0, plantModels.Count - 1)]); - newPlant.GetComponent().SetValues(Random.Range(10f, 100f), Random.Range(0.5f, 1f), Random.Range(0.1f, 0.25f)); + newPlant.GetComponent().SetValues(Random.Range(0.1f, 0.2f), Random.Range(1.5f, 5), Random.Range(0.25f, 0.5f)); float x = Random.Range(0, xBound); float z = Random.Range(0, zBound); float y = terrain.SampleHeight(new Vector3(x, 0, z)); @@ -39,43 +41,58 @@ public class PlantController : MonoBehaviour // Update is called once per frame void Update() { - for(int i = 0; i < plants.Count; i ++) + float reproAvg = 0; + for(int i = plants.Count - 1; i >= 0;i --) { GameObject currentPlant = plants[i]; Plant plantI = currentPlant.GetComponent(); + reproAvg += plantI.maxSize; if (!plantI.isAlive) { currentPlant.SetActive(false); plants.Remove(currentPlant); Destroy(currentPlant); - i--; } else { - if (plantI.isReproducing) + if(plantI.isReproducing) { - GameObject child = Instantiate(plantPrefab); - Plant childPlant = child.GetComponent(); - childPlant.SetVisual(plantI.visualPrefab); - childPlant.SetValues(plantI.healthMax + Random.Range(-0.5f, 0.5f), plantI.nutrientNeed + Random.Range(-0.01f, 0.01f), plantI.growthRate + Random.Range(-0.01f, 0.01f)); + if(plants.Count < 1000) + { + // Gather data and alter slightly + float childGrowthRate = plantI.growthRate + Random.Range(-0.1f, 0.1f); + float childMaxSize = plantI.maxSize + Random.Range(-0.1f, 0.1f); + float childReproductionChance = plantI.reproductionChance + Random.Range(-0.01f, 0.01f); - float x = plantI.childXPos; - float z = plantI.childYPos; - float y = terrain.SampleHeight(new Vector3(plantI.childXPos, 0, plantI.childYPos)); - Vector3 childPos = new Vector3(x, y, z); + if (childReproductionChance > 1) + { + childReproductionChance = 1; + } - Vector3 normal = terrain.terrainData.GetInterpolatedNormal(plantI.childXPos / xBound, plantI.childYPos / zBound); - child.transform.LookAt(normal); - child.transform.Rotate(new Vector3(90, 0, 0)); - - child.transform.position = childPos; - plants.Add(child); - i++; - - plantI.childMade(); + GameObject childVisualPrefab = plantI.visualPrefab; + GameObject child = Instantiate(plantPrefab); + child.GetComponent().SetVisual(childVisualPrefab); + child.GetComponent().SetValues(childGrowthRate, childMaxSize, childReproductionChance); + float x = Random.Range(0, xBound); + float z = Random.Range(0, zBound); + float y = terrain.SampleHeight(new Vector3(x, 0, z)); + Vector3 normal = terrain.terrainData.GetInterpolatedNormal(x / xBound, z / zBound); + child.transform.LookAt(normal); + child.transform.Rotate(new Vector3(90, 0, 0)); + Vector3 newPlantPos = new Vector3(x, y, z); + child.transform.position = newPlantPos; + plantQueue.Add(child); + } + plantI.isReproducing = false; } } } + Debug.Log($"Max Size Avg = {reproAvg / plants.Count}"); + if(plantQueue.Count > 0) + { + plants.AddRange(plantQueue); + plantQueue.Clear(); + } } public void Step(float dTime)