Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions examples/farm-day/CollectableResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace FarmDay.Resources
{
/// <summary>
/// Base class for collectable resources like Stone and Wood.
/// Implements spawn protection to prevent automatic collection when resources
/// spawn inside the farmer's collection area.
///
/// The resource will only be collected when:
/// 1. The farmer enters the collection area AFTER the resource spawned, OR
/// 2. The farmer exits and re-enters the collection area
/// </summary>
public class CollectableResource : MonoBehaviour
{
[Header("Resource Settings")]
[SerializeField] protected ResourceType resourceType;
[SerializeField] protected int amount = 1;

[Header("Collection Settings")]
[SerializeField] protected string collectorTag = "Player";
[SerializeField] protected bool useSpawnProtection = true;

// Tracks collectors that were inside the trigger when this resource spawned
private HashSet<int> collectorsInsideOnSpawn = new HashSet<int>();
private bool isInitialized = false;

protected virtual void Start()
{
if (useSpawnProtection)
{
StartCoroutine(InitializeSpawnProtection());
}
else
{
isInitialized = true;
}
}

/// <summary>
/// Detects which collectors are already overlapping when the resource spawns.
/// These collectors will need to exit and re-enter to collect this resource.
/// </summary>
private IEnumerator InitializeSpawnProtection()
{
// Wait for physics to process the new collider
yield return new WaitForFixedUpdate();

// Get our collider
Collider2D myCollider = GetComponent<Collider2D>();
if (myCollider == null)
{
Debug.LogWarning($"CollectableResource on {gameObject.name} has no Collider2D!");
isInitialized = true;
yield break;
}

// Find all overlapping colliders
ContactFilter2D filter = new ContactFilter2D();
filter.useTriggers = true;
filter.SetLayerMask(Physics2D.AllLayers);

List<Collider2D> overlapping = new List<Collider2D>();
myCollider.OverlapCollider(filter, overlapping);

foreach (var col in overlapping)
{
if (col.CompareTag(collectorTag))
{
// Track this collector by instance ID
collectorsInsideOnSpawn.Add(col.gameObject.GetInstanceID());
Debug.Log($"[CollectableResource] {gameObject.name}: Collector {col.name} was inside on spawn, will require exit+re-entry to collect");
}
}

isInitialized = true;
}

protected virtual void OnTriggerEnter2D(Collider2D other)
{
if (!isInitialized) return;
if (!other.CompareTag(collectorTag)) return;

int collectorId = other.gameObject.GetInstanceID();

// If this collector was inside when we spawned, don't collect yet
if (collectorsInsideOnSpawn.Contains(collectorId))
{
Debug.Log($"[CollectableResource] {gameObject.name}: Ignoring enter from {other.name} (was inside on spawn)");
return;
}

// Collector is entering fresh - attempt collection
TryCollect(other);
}

protected virtual void OnTriggerExit2D(Collider2D other)
{
if (!other.CompareTag(collectorTag)) return;

int collectorId = other.gameObject.GetInstanceID();

// When a collector exits, remove them from the spawn protection list
// Next time they enter, they can collect
if (collectorsInsideOnSpawn.Remove(collectorId))
{
Debug.Log($"[CollectableResource] {gameObject.name}: {other.name} exited, spawn protection cleared");
}
}

/// <summary>
/// Attempts to collect this resource. Override in derived classes for custom behavior.
/// </summary>
protected virtual void TryCollect(Collider2D collector)
{
var farmerCollector = collector.GetComponent<IResourceCollector>();
if (farmerCollector == null)
{
Debug.LogWarning($"[CollectableResource] Collector {collector.name} doesn't have IResourceCollector component!");
return;
}

if (farmerCollector.CanCollect(resourceType))
{
farmerCollector.Collect(resourceType, amount);
OnCollected();
}
}

/// <summary>
/// Called when the resource is successfully collected.
/// </summary>
protected virtual void OnCollected()
{
// Play collection effects, sounds, etc.
Debug.Log($"[CollectableResource] {gameObject.name} collected! Type: {resourceType}, Amount: {amount}");

// Destroy the resource
Destroy(gameObject);
}

/// <summary>
/// Force clears spawn protection, allowing immediate collection.
/// Useful for special cases where you want to override the protection.
/// </summary>
public void ClearSpawnProtection()
{
collectorsInsideOnSpawn.Clear();
}
}

/// <summary>
/// Types of resources that can be collected.
/// </summary>
public enum ResourceType
{
Stone,
Wood,
// Add other resource types as needed
}

/// <summary>
/// Interface for objects that can collect resources (e.g., Farmer).
/// </summary>
public interface IResourceCollector
{
bool CanCollect(ResourceType type);
void Collect(ResourceType type, int amount);
}
}
90 changes: 90 additions & 0 deletions examples/farm-day/FarmerCollector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.Collections.Generic;
using UnityEngine;

namespace FarmDay.Resources
{
/// <summary>
/// Component attached to the Farmer that handles resource collection.
/// </summary>
public class FarmerCollector : MonoBehaviour, IResourceCollector
{
[Header("Collection Settings")]
[SerializeField] private int maxStoneCapacity = 100;
[SerializeField] private int maxWoodCapacity = 100;

[Header("Events")]
public System.Action<ResourceType, int> OnResourceCollected;
public System.Action<ResourceType, int> OnResourceChanged;

private Dictionary<ResourceType, int> resources = new Dictionary<ResourceType, int>();

private void Awake()
{
// Initialize resource counts
resources[ResourceType.Stone] = 0;
resources[ResourceType.Wood] = 0;
}

public bool CanCollect(ResourceType type)
{
int current = GetResourceAmount(type);
int max = GetMaxCapacity(type);

return current < max;
}

public void Collect(ResourceType type, int amount)
{
if (!CanCollect(type))
{
Debug.Log($"[FarmerCollector] Cannot collect {type}, inventory full!");
return;
}

int current = GetResourceAmount(type);
int max = GetMaxCapacity(type);
int amountToAdd = Mathf.Min(amount, max - current);

resources[type] = current + amountToAdd;

Debug.Log($"[FarmerCollector] Collected {amountToAdd} {type}. Total: {resources[type]}");

OnResourceCollected?.Invoke(type, amountToAdd);
OnResourceChanged?.Invoke(type, resources[type]);
}

public int GetResourceAmount(ResourceType type)
{
return resources.TryGetValue(type, out int amount) ? amount : 0;
}

public int GetMaxCapacity(ResourceType type)
{
switch (type)
{
case ResourceType.Stone:
return maxStoneCapacity;
case ResourceType.Wood:
return maxWoodCapacity;
default:
return 100;
}
}

/// <summary>
/// Removes resources from inventory (e.g., when using them for crafting).
/// </summary>
public bool UseResource(ResourceType type, int amount)
{
int current = GetResourceAmount(type);
if (current < amount)
{
return false;
}

resources[type] = current - amount;
OnResourceChanged?.Invoke(type, resources[type]);
return true;
}
}
}
57 changes: 57 additions & 0 deletions examples/farm-day/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Farm Day - Exemplo de Coleta Manual de Recursos

Este diretório contém exemplos de código que demonstram como implementar a mecânica de coleta manual de pedra e madeira no Farm Day.

## Problema Resolvido

Quando recursos (pedra/madeira) nascem dentro da área de colisão do farmer, eles eram coletados automaticamente. Com esta solução, o farmer precisa se mover para que o colisor seja ativado corretamente.

## Arquivos

| Arquivo | Descrição |
|---------|-----------|
| `CollectableResource.cs` | Classe base com proteção de spawn |
| `Stone.cs` | Implementação do recurso pedra |
| `Wood.cs` | Implementação do recurso madeira |
| `FarmerCollector.cs` | Componente de coleta do farmer |
| `ResourceSpawner.cs` | Spawner de recursos com integração |

## Como Funciona

1. **Spawn Protection**: Quando um recurso nasce, ele verifica quais coletores já estão dentro da sua área de colisão e os registra.

2. **Entrada Ignorada**: Se um coletor registrado tentar coletar o recurso sem ter saído da área primeiro, a coleta é bloqueada.

3. **Saída Limpa**: Quando o farmer sai da área de colisão, ele é removido da lista de "estava no spawn".

4. **Coleta Liberada**: Na próxima vez que o farmer entrar na área, a coleta acontece normalmente.

## Uso

### Configuração do Prefab de Recurso

1. Adicione o componente `Stone` ou `Wood` ao prefab do recurso
2. Configure um `Collider2D` como trigger
3. Defina as configurações no Inspector

### Configuração do Farmer

1. Adicione o componente `FarmerCollector` ao farmer
2. Configure a tag do farmer como "Player"
3. Certifique-se de que o farmer tem um `Collider2D`

### Configuração do Spawner

1. Adicione o componente `ResourceSpawner` a um GameObject vazio
2. Arraste os prefabs de recursos para o array `resourcePrefabs`
3. Configure o intervalo e raio de spawn

## Testes

Para testar a implementação:

1. Coloque o farmer perto de um ponto de spawn
2. Aguarde um recurso nascer
3. Verifique que o recurso NÃO é coletado automaticamente
4. Mova o farmer para fora e de volta para a área
5. Verifique que o recurso é coletado agora
Loading