Updated plant behavior to new system.

This commit is contained in:
2025-08-11 22:03:32 -07:00
parent 6632f02d1c
commit fdd71edba5
3 changed files with 139 additions and 117 deletions
+20 -29
View File
@@ -9,11 +9,13 @@ public class NutrientController : MonoBehaviour
private float[,] nutrientMap; private float[,] nutrientMap;
[SerializeField, Range(10, 256)] int sizeX; [SerializeField, Range(10, 256)] int sizeX;
[SerializeField, Range(10, 256)] int sizeY; [SerializeField, Range(10, 256)] int sizeY;
[SerializeField, Range(0f, 1f)] float recoveryRate;
[SerializeField] MeshRenderer meshRenderer; [SerializeField] MeshRenderer meshRenderer;
// Start is called once before the first execution of Update after the MonoBehaviour is created // Start is called once before the first execution of Update after the MonoBehaviour is created
void Start() void Start()
{ {
nutrientMap = new float[sizeX, sizeY]; nutrientMap = new float[sizeX, sizeY];
for(int r = 0; r < sizeX; r ++) for(int r = 0; r < sizeX; r ++)
{ {
@@ -38,30 +40,27 @@ public class NutrientController : MonoBehaviour
{ {
if (nutrientMap[r,c] < 1) 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 locX = Mathf.FloorToInt(x * sizeX);
int locY = Mathf.FloorToInt(y * sizeY); int locZ = Mathf.FloorToInt(z * sizeY);
int flooredRadius = Mathf.FloorToInt(radius); int flooredRadius = Mathf.FloorToInt(radius);
float totalNutrients = nutrientMap[locX, locY]; float totalNutrients = nutrientMap[locX, locZ];
float[,] nutrientsAvailable; float[,] nutrientsAvailable;
try nutrientsAvailable = new float[flooredRadius * 2 + 1, flooredRadius * 2 + 1];
{
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;
}
// Calculate total amount of nutrients available to the plant, at a 1/ (2 * steps from origin) ratio. // Calculate total amount of nutrients available to the plant, at a 1/ (2 * steps from origin) ratio.
for (int r = -flooredRadius; r <= flooredRadius; r++) for (int r = -flooredRadius; r <= flooredRadius; r++)
@@ -70,21 +69,22 @@ public class NutrientController : MonoBehaviour
for(int c = -flooredRadius; c <= flooredRadius; c++) for(int c = -flooredRadius; c <= flooredRadius; c++)
{ {
int currentLocY = locY + c; int currentLocZ = locZ + c;
if (currentLocX < 0 || currentLocX >= nutrientMap.GetLength(0) || currentLocY < 0 || currentLocY >= nutrientMap.GetLength(1)) // 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 else
{ {
if (r == 0 && c == 0) if (r == 0 && c == 0)
{ {
nutrientsAvailable[flooredRadius, flooredRadius] = nutrientMap[currentLocX, currentLocY]; nutrientsAvailable[flooredRadius, flooredRadius] = nutrientMap[currentLocX, currentLocZ];
} }
else else
{ {
nutrientsAvailable[flooredRadius + r, flooredRadius + c] = 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, currentLocY] / ((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++) 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)) if (currentLocX >= 0 && currentLocX < nutrientMap.GetLength(0) && currentLocY >= 0 && currentLocY < nutrientMap.GetLength(1))
{ {
nutrientMap[currentLocX, currentLocY] -= (nutrientsAvailable[flooredRadius + r, flooredRadius + c]); nutrientMap[currentLocX, currentLocY] -= (nutrientsAvailable[flooredRadius + r, flooredRadius + c]);
@@ -109,14 +109,13 @@ public class NutrientController : MonoBehaviour
} }
return nutrientsNeeded - totalNutrients; return nutrientsNeeded - totalNutrients;
} }
for (int r = -flooredRadius; r <= flooredRadius; r++) for (int r = -flooredRadius; r <= flooredRadius; r++)
{ {
int currentLocX = locX + r; int currentLocX = locX + r;
for (int c = -flooredRadius; c <= flooredRadius; c++) 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)) if (currentLocX >= 0 && currentLocX < nutrientMap.GetLength(0) && currentLocY >= 0 && currentLocY < nutrientMap.GetLength(1))
{ {
nutrientMap[currentLocX, currentLocY] -= (nutrientsAvailable[flooredRadius + r, flooredRadius + c] / totalNutrients) * nutrientsNeeded; nutrientMap[currentLocX, currentLocY] -= (nutrientsAvailable[flooredRadius + r, flooredRadius + c] / totalNutrients) * nutrientsNeeded;
@@ -162,14 +161,6 @@ public class NutrientController : MonoBehaviour
return totalNutrients; 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) public void AddNutrients(float x, float y, float added, float radius)
{ {
int locX = Mathf.FloorToInt(x * sizeX); int locX = Mathf.FloorToInt(x * sizeX);
+81 -67
View File
@@ -1,23 +1,19 @@
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.UIElements;
public class Plant : MonoBehaviour public class Plant : MonoBehaviour
{ {
public float healthMax; private float health;
public float healthCurrent;
public float nutrientNeed;
public float growthRate; public float growthRate;
public float nutrientsConsumed; public float nutrientsConsumed;
public float nutrientsRequiredStep; public float maxSize;
public float size; public float size;
public float reproductionChance; public float reproductionChance;
public float reproductionDist;
public bool isReproducing;
public float childXPos;
public float childYPos;
public bool isAlive; public bool isAlive;
public bool isReproducing;
public GameObject visualPrefab; public GameObject visualPrefab;
public GameObject visual; public GameObject visual;
@@ -29,23 +25,20 @@ public class Plant : MonoBehaviour
this.visualPrefab = visualPrefab; this.visualPrefab = visualPrefab;
} }
public void SetValues(float healthMax, float nutrientNeed, float growthRate) public void SetValues(float growthRate, float maxSize, float reproductionChance)
{ {
this.healthMax = healthMax; health = 10;
this.nutrientNeed = nutrientNeed;
this.growthRate = growthRate; this.growthRate = growthRate;
reproductionChance = 1; this.maxSize = maxSize;
reproductionDist = Random.Range(1f, 500f); this.reproductionChance = reproductionChance;
} }
// Start is called once before the first execution of Update after the MonoBehaviour is created // Start is called once before the first execution of Update after the MonoBehaviour is created
private void Start() private void Start()
{ {
visual = Instantiate(visualPrefab, transform); visual = Instantiate(visualPrefab, transform);
healthCurrent = healthMax;
nutrientsConsumed = 0; nutrientsConsumed = 0;
isAlive = true; isAlive = true;
isReproducing = false;
size = 1f; size = 1f;
} }
@@ -58,70 +51,86 @@ public class Plant : MonoBehaviour
public void Step(float dTime) public void Step(float dTime)
{ {
Terrain terrain = Core.instance.ground; 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; NutrientController nutrientCont = Core.instance.nutrientCont;
float xBound = terrain.terrainData.size.x; if (size >= maxSize)
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)
{ {
healthCurrent -= nutrientDeficit; TryReproduce(dTime, nutrientCont, xPosition, zPosition);
nutrientsConsumed += (nutrientsRequiredStep - nutrientDeficit);
} }
else else
{ {
float adjustedGrowth = (growthRate * 1.5f) / size; if(size > maxSize * 0.67f && Random.value > 0.5f)
size += adjustedGrowth * dTime;
if(visual != null)
{ {
visual.transform.localScale = new Vector3(size, size, size); TryReproduce(dTime, nutrientCont, xPosition, zPosition);
} }
nutrientsConsumed += nutrientsRequiredStep; else
}
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
{ {
direction = Random.Range(0f, 360f); Grow(dTime, nutrientCont, xPosition, zPosition);
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); }
childXPos = transform.position.x + xOffset; if (health <= 0)
childYPos = transform.position.y + yOffset; {
counter++; isAlive = false;
if(counter >= 50) nutrientCont.AddNutrients(xPosition, zPosition, nutrientsConsumed * 0.9f, size);
{
return;
}
} while (childXPos > xBound || childXPos < 0 || childYPos > yBound || childYPos < 0);
isReproducing = true;
} }
} }
public void childMade() private void TryReproduce(float dTime, NutrientController nutrientCont, float xPosition, float zPosition)
{ {
childXPos = 0; if(reproductionChance >= 1)
childYPos = 0; {
isReproducing = false; 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() private void OnDrawGizmos()
@@ -134,6 +143,11 @@ public class Plant : MonoBehaviour
GUIStyle big = new GUIStyle(); GUIStyle big = new GUIStyle();
big.fontSize = 50; big.fontSize = 50;
big.normal.textColor = Color.purple; 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;
} }
} }
+38 -21
View File
@@ -7,6 +7,7 @@ public class PlantController : MonoBehaviour
[SerializeField] GameObject plantPrefab; [SerializeField] GameObject plantPrefab;
[SerializeField] List<GameObject> plantModels; [SerializeField] List<GameObject> plantModels;
List<GameObject> plants; List<GameObject> plants;
List<GameObject> plantQueue;
float xBound; float xBound;
float zBound; float zBound;
Terrain terrain; Terrain terrain;
@@ -15,6 +16,7 @@ public class PlantController : MonoBehaviour
void Start() void Start()
{ {
plants = new List<GameObject>(); plants = new List<GameObject>();
plantQueue = new List<GameObject>();
terrain = Core.instance.ground; terrain = Core.instance.ground;
xBound = terrain.GetComponent<Terrain>().terrainData.size.x; xBound = terrain.GetComponent<Terrain>().terrainData.size.x;
zBound = terrain.GetComponent<Terrain>().terrainData.size.z; zBound = terrain.GetComponent<Terrain>().terrainData.size.z;
@@ -23,7 +25,7 @@ public class PlantController : MonoBehaviour
{ {
GameObject newPlant = Instantiate(plantPrefab); GameObject newPlant = Instantiate(plantPrefab);
newPlant.GetComponent<Plant>().SetVisual(plantModels[Random.Range(0, plantModels.Count - 1)]); newPlant.GetComponent<Plant>().SetVisual(plantModels[Random.Range(0, plantModels.Count - 1)]);
newPlant.GetComponent<Plant>().SetValues(Random.Range(10f, 100f), Random.Range(0.5f, 1f), Random.Range(0.1f, 0.25f)); newPlant.GetComponent<Plant>().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 x = Random.Range(0, xBound);
float z = Random.Range(0, zBound); float z = Random.Range(0, zBound);
float y = terrain.SampleHeight(new Vector3(x, 0, z)); float y = terrain.SampleHeight(new Vector3(x, 0, z));
@@ -39,43 +41,58 @@ public class PlantController : MonoBehaviour
// Update is called once per frame // Update is called once per frame
void Update() 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]; GameObject currentPlant = plants[i];
Plant plantI = currentPlant.GetComponent<Plant>(); Plant plantI = currentPlant.GetComponent<Plant>();
reproAvg += plantI.maxSize;
if (!plantI.isAlive) if (!plantI.isAlive)
{ {
currentPlant.SetActive(false); currentPlant.SetActive(false);
plants.Remove(currentPlant); plants.Remove(currentPlant);
Destroy(currentPlant); Destroy(currentPlant);
i--;
} }
else else
{ {
if (plantI.isReproducing) if(plantI.isReproducing)
{ {
GameObject child = Instantiate(plantPrefab); if(plants.Count < 1000)
Plant childPlant = child.GetComponent<Plant>(); {
childPlant.SetVisual(plantI.visualPrefab); // Gather data and alter slightly
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)); 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; if (childReproductionChance > 1)
float z = plantI.childYPos; {
float y = terrain.SampleHeight(new Vector3(plantI.childXPos, 0, plantI.childYPos)); childReproductionChance = 1;
Vector3 childPos = new Vector3(x, y, z); }
Vector3 normal = terrain.terrainData.GetInterpolatedNormal(plantI.childXPos / xBound, plantI.childYPos / zBound); GameObject childVisualPrefab = plantI.visualPrefab;
child.transform.LookAt(normal); GameObject child = Instantiate(plantPrefab);
child.transform.Rotate(new Vector3(90, 0, 0)); child.GetComponent<Plant>().SetVisual(childVisualPrefab);
child.GetComponent<Plant>().SetValues(childGrowthRate, childMaxSize, childReproductionChance);
child.transform.position = childPos; float x = Random.Range(0, xBound);
plants.Add(child); float z = Random.Range(0, zBound);
i++; float y = terrain.SampleHeight(new Vector3(x, 0, z));
Vector3 normal = terrain.terrainData.GetInterpolatedNormal(x / xBound, z / zBound);
plantI.childMade(); 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) public void Step(float dTime)