389 lines
13 KiB
C#
389 lines
13 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using DG.Tweening;
|
|
|
|
namespace CryingSnow.FarmingIsland
|
|
{
|
|
/// <summary>
|
|
/// Manages a farm, including soil tiles, planting, watering, and harvesting.
|
|
/// </summary>
|
|
public class Farm : MonoBehaviour, IProp
|
|
{
|
|
[SerializeField, Range(2, 6)]
|
|
[Tooltip("Length of the farm grid (number of tiles in the X direction)")]
|
|
private int length = 6;
|
|
|
|
[SerializeField, Range(2, 6)]
|
|
[Tooltip("Width of the farm grid (number of tiles in the Z direction)")]
|
|
private int width = 5;
|
|
|
|
[SerializeField] private Soil soilPrefab;
|
|
[SerializeField] private CropData cropData;
|
|
|
|
#if UNITY_EDITOR
|
|
[SerializeField]
|
|
[Tooltip("Toggle to draw farm tiles in the editor (for debugging)")]
|
|
private bool drawFarmTiles = true;
|
|
#endif
|
|
|
|
[Header("Plant Settings")]
|
|
|
|
[Tooltip("Distance at which planting can occur")]
|
|
[SerializeField] private float plantingDistance = 2f;
|
|
|
|
[Tooltip("Radius from the center of the area (farmer's forward direction + planting distance) within which planting occurs")]
|
|
[SerializeField] private float plantingRadius = 1.2f;
|
|
|
|
[Tooltip("Distance at which watering can occur")]
|
|
[SerializeField] private float wateringDistance = 1.8f;
|
|
|
|
[Tooltip("Radius from the center of the area (farmer's forward direction + watering distance) within which watering occurs")]
|
|
[SerializeField] private float wateringRadius = 1f;
|
|
|
|
[Tooltip("Distance at which harvesting can occur")]
|
|
[SerializeField] private float harvestingDistance = 1.5f;
|
|
|
|
[Tooltip("Radius from the center of the area (farmer's forward direction + harvesting distance) within which harvesting occurs")]
|
|
[SerializeField] private float harvestingRadius = 0.9f;
|
|
|
|
|
|
[Header("Soil Settings")]
|
|
|
|
[Tooltip("Color for dry soil (first variant)")]
|
|
[SerializeField] private Color soilDryColor1 = new Color(0.7725f, 0.5647f, 0.3176f, 1.0f);
|
|
|
|
[Tooltip("Color for wet soil (first variant)")]
|
|
[SerializeField] private Color soilWetColor1 = new Color(0.6031f, 0.4109f, 0.2737f, 1.0f);
|
|
|
|
[Tooltip("Color for dry soil (second variant)")]
|
|
[SerializeField] private Color soilDryColor2 = new Color(0.7300f, 0.5465f, 0.3285f, 1.0f);
|
|
|
|
[Tooltip("Color for wet soil (second variant)")]
|
|
[SerializeField] private Color soilWetColor2 = new Color(0.5600f, 0.3934f, 0.2744f, 1.0f);
|
|
|
|
[Tooltip("Time duration for soil to remain wet")]
|
|
[SerializeField] private float soilWetTime = 1f;
|
|
|
|
[Tooltip("Time duration for soil to dry")]
|
|
[SerializeField] private float soilDryTime = 0.5f;
|
|
|
|
|
|
[Header("Growth Times")]
|
|
|
|
[Tooltip("Time required for a sprout to grow")]
|
|
[SerializeField] private float sproutGrowTime = 1f;
|
|
|
|
[Tooltip("Time required for a plant to grow")]
|
|
[SerializeField] private float plantGrowTime = 1f;
|
|
|
|
[Tooltip("Time required for a crop to grow")]
|
|
[SerializeField] private float cropGrowTime = 0.5f;
|
|
|
|
#region Public Getters
|
|
public CropData CropData => cropData;
|
|
|
|
public float PlantingDistance => plantingDistance;
|
|
public float PlantingRadius => plantingRadius;
|
|
public float WateringDistance => wateringDistance;
|
|
public float WateringRadius => wateringRadius;
|
|
public float HarvestingDistance => harvestingDistance;
|
|
public float HarvestingRadius => harvestingRadius;
|
|
|
|
public Color SoilDryColor(int index) => (index == 0) ? soilDryColor1 : soilDryColor2;
|
|
public Color SoilWetColor(int index) => (index == 0) ? soilWetColor1 : soilWetColor2;
|
|
public float SoilWetTime => soilWetTime;
|
|
public float SoilDryTime => soilDryTime;
|
|
|
|
public float SproutGrowTime => sproutGrowTime;
|
|
public float PlantGrowTime => plantGrowTime;
|
|
public float CropGrowTime => cropGrowTime;
|
|
|
|
public State GetState() => state;
|
|
|
|
public bool IsReady => farmTrigger.enabled;
|
|
|
|
public Location Location => Location.Farm;
|
|
#endregion
|
|
|
|
public enum State { None, Planted, Grown } // Different states the farm can be in
|
|
private State state; // Current state of the farm
|
|
|
|
private BoxCollider farmTrigger; // Collider for farm interaction
|
|
|
|
private int soilLayer; // Layer mask for soil
|
|
|
|
private List<Soil> soils = new List<Soil>(); // List of soil tiles in the farm
|
|
|
|
private PlayerController player; // Reference to the player or farmer interacting with the farm
|
|
|
|
void Awake()
|
|
{
|
|
// Initialize soil layer and create farm trigger and soil tiles
|
|
soilLayer = 1 << LayerMask.NameToLayer("Soil");
|
|
CreateTrigger();
|
|
CreateSoils();
|
|
}
|
|
|
|
void CreateTrigger()
|
|
{
|
|
// Create and configure the collider for farm interaction
|
|
farmTrigger = gameObject.AddComponent<BoxCollider>();
|
|
farmTrigger.isTrigger = true;
|
|
farmTrigger.size = new Vector3(length, 2f, width);
|
|
farmTrigger.center = (farmTrigger.size / 2f);
|
|
farmTrigger.enabled = false;
|
|
}
|
|
|
|
void CreateSoils()
|
|
{
|
|
// Create and initialize soil tiles in the farm grid
|
|
for (int x = 0; x < length; x++)
|
|
{
|
|
for (int z = 0; z < width; z++)
|
|
{
|
|
Vector3 position = new Vector3(x + 0.5f, 0f, z + 0.5f);
|
|
var soil = Instantiate(soilPrefab, transform);
|
|
soil.transform.localPosition = position;
|
|
soil.Initialize(this);
|
|
soil.OnStateChanged += UpdateFarmState;
|
|
soils.Add(soil);
|
|
soil.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IProp.Animate(bool instant, System.Action onFinished)
|
|
{
|
|
if (instant)
|
|
{
|
|
soils.ForEach(soil =>
|
|
{
|
|
soil.gameObject.SetActive(true);
|
|
soil.DisableShadowCasting();
|
|
});
|
|
|
|
farmTrigger.enabled = true;
|
|
}
|
|
else
|
|
{
|
|
StartCoroutine(AnimateSoils());
|
|
}
|
|
}
|
|
|
|
IEnumerator AnimateSoils()
|
|
{
|
|
// Animate soil tiles with a tweening effect
|
|
foreach (var soil in soils)
|
|
{
|
|
soil.gameObject.SetActive(true);
|
|
|
|
soil.transform.localScale = Vector3.zero;
|
|
soil.transform.localPosition += Vector3.up * 2f;
|
|
|
|
var sequence = DOTween.Sequence();
|
|
float duration = 0.5f;
|
|
sequence.Append(soil.transform.DOScale(Vector3.one, duration));
|
|
sequence.Join(soil.transform.DOLocalMoveY(0f, duration));
|
|
sequence.OnComplete(() => soil.DisableShadowCasting());
|
|
|
|
yield return new WaitForSeconds(0.05f);
|
|
}
|
|
|
|
farmTrigger.enabled = true;
|
|
}
|
|
|
|
void UpdateFarmState()
|
|
{
|
|
// Update the state of the farm based on the states of the soil tiles
|
|
switch (state)
|
|
{
|
|
case State.None:
|
|
HandleStateTransition(
|
|
() => soils.All(soil => soil.GetSoilState() != Soil.State.None),
|
|
() => soils.All(soil => soil.GetSoilState() == Soil.State.Sprout),
|
|
State.Planted
|
|
);
|
|
break;
|
|
|
|
case State.Planted:
|
|
HandleStateTransition(
|
|
() => soils.All(soil => soil.GetSoilState() != Soil.State.Sprout),
|
|
() => soils.All(soil => soil.GetSoilState() == Soil.State.Harvest),
|
|
State.Grown
|
|
);
|
|
break;
|
|
|
|
case State.Grown:
|
|
HandleStateTransition(
|
|
() => soils.All(soil => soil.GetSoilState() != Soil.State.Harvest),
|
|
() => soils.All(soil => soil.GetSoilState() == Soil.State.None),
|
|
State.None
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void HandleStateTransition(System.Func<bool> stopWorkingCondition, System.Func<bool> transitionCondition, State nextFarmState)
|
|
{
|
|
// Handle the transition between farm states and update UI accordingly
|
|
if (stopWorkingCondition())
|
|
{
|
|
// Disable farming button if player was not initially inside the trigger zone
|
|
if (player != null)
|
|
UIManager.Instance.DisableFarmingButton();
|
|
|
|
player?.SetWorking(null);
|
|
}
|
|
|
|
if (transitionCondition())
|
|
{
|
|
state = nextFarmState;
|
|
|
|
if (player != null && player.IsControllable)
|
|
UIManager.Instance.ToggleFarmingButton(state, () => player?.SetWorking(this));
|
|
}
|
|
}
|
|
|
|
void OnTriggerEnter(Collider other)
|
|
{
|
|
// Handle player entering the farm trigger zone
|
|
if (other.CompareTag("Player"))
|
|
{
|
|
player = other.GetComponent<PlayerController>();
|
|
|
|
UIManager.Instance.ToggleFarmingButton(state, () => player?.SetWorking(this));
|
|
|
|
// Ensure the Farming Button is up-to-date with the farm state upon player entry
|
|
UpdateFarmState();
|
|
}
|
|
}
|
|
|
|
void OnTriggerExit(Collider other)
|
|
{
|
|
// Handle player exiting the farm trigger zone
|
|
if (other.CompareTag("Player"))
|
|
{
|
|
UIManager.Instance.DisableFarmingButton();
|
|
player.SetWorking(null);
|
|
player = null;
|
|
}
|
|
}
|
|
|
|
public void PlantSeeds(Transform farmer)
|
|
{
|
|
WorkOnNearbySoils(
|
|
farmer,
|
|
Soil.State.None,
|
|
Soil.State.Seed,
|
|
plantingDistance,
|
|
plantingRadius
|
|
);
|
|
}
|
|
|
|
public void WaterPlants(Transform farmer)
|
|
{
|
|
WorkOnNearbySoils(
|
|
farmer,
|
|
Soil.State.Sprout,
|
|
Soil.State.Ripe,
|
|
wateringDistance,
|
|
wateringRadius
|
|
);
|
|
}
|
|
|
|
public int HarvestPlants(Transform farmer)
|
|
{
|
|
return WorkOnNearbySoils(
|
|
farmer,
|
|
Soil.State.Harvest,
|
|
Soil.State.Dead,
|
|
harvestingDistance,
|
|
harvestingRadius
|
|
);
|
|
}
|
|
|
|
private int WorkOnNearbySoils(Transform farmer, Soil.State currentState, Soil.State newState, float workDistance, float workRadius)
|
|
{
|
|
// Work on nearby soil tiles based on their state and update their state
|
|
Vector3 workPosition = farmer.TransformPoint(Vector3.forward * workDistance);
|
|
|
|
var nearbySoils = Physics.OverlapSphere(
|
|
workPosition,
|
|
workRadius,
|
|
soilLayer,
|
|
QueryTriggerInteraction.Collide
|
|
);
|
|
|
|
int validSoils = 0;
|
|
foreach (var nearbySoil in nearbySoils)
|
|
{
|
|
var soil = nearbySoil.GetComponent<Soil>();
|
|
if (soil.GetSoilState() == currentState)
|
|
{
|
|
soil.SetSoilState(newState, farmer);
|
|
validSoils++;
|
|
}
|
|
}
|
|
|
|
return validSoils;
|
|
}
|
|
|
|
public Vector3 GetTargetSoilPosition()
|
|
{
|
|
// Get a random position of a soil tile based on the current farm state
|
|
Soil targetSoil = soils[Random.Range(0, soils.Count)];
|
|
Vector3 defaultPos = targetSoil.transform.position;
|
|
|
|
Soil.State targetSoilState;
|
|
switch (state)
|
|
{
|
|
case State.None:
|
|
targetSoilState = Soil.State.None;
|
|
break;
|
|
case State.Planted:
|
|
targetSoilState = Soil.State.Sprout;
|
|
break;
|
|
case State.Grown:
|
|
targetSoilState = Soil.State.Harvest;
|
|
break;
|
|
default:
|
|
targetSoilState = Soil.State.None;
|
|
break;
|
|
}
|
|
|
|
targetSoil = soils.Where(s => s.GetSoilState() == targetSoilState)
|
|
.OrderBy(s => Random.value)
|
|
.FirstOrDefault();
|
|
|
|
return targetSoil != null ? targetSoil.transform.position : defaultPos;
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
// Unsubscribe from soil state change events
|
|
soils.ForEach(soil => soil.OnStateChanged -= UpdateFarmState);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
void OnDrawGizmos()
|
|
{
|
|
// Draw gizmos for farm tiles in the editor for debugging
|
|
if (!drawFarmTiles) return;
|
|
|
|
Gizmos.color = Color.yellow;
|
|
|
|
for (int x = 0; x < length; x++)
|
|
{
|
|
for (int z = 0; z < width; z++)
|
|
{
|
|
Vector3 position = transform.position + new Vector3(x + 0.5f, 0f, z + 0.5f);
|
|
Gizmos.DrawWireCube(position, new Vector3(1f, 0.2f, 1f));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|