Skip to content
Open
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
47 changes: 47 additions & 0 deletions Assets/Scripts/DoorTeleporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using UnityEngine;

[RequireComponent(typeof(Collider2D))]
public class DoorTeleporter : MonoBehaviour
{
[SerializeField] private Transform destinationPoint;
[SerializeField] private KeyCode interactKey = KeyCode.E;

private bool playerInside;
private Transform player;

private void Reset()
{
var col = GetComponent<Collider2D>();
col.isTrigger = true;
}

private void Update()
{
if (playerInside && destinationPoint != null && Input.GetKeyDown(interactKey))
{
player.position = destinationPoint.position;
}
}

private void OnTriggerEnter2D(Collider2D other)
{
if (!other.CompareTag("Player"))
{
return;
}

playerInside = true;
player = other.transform;
}

private void OnTriggerExit2D(Collider2D other)
{
if (!other.CompareTag("Player"))
{
return;
}

playerInside = false;
player = null;
}
}
35 changes: 35 additions & 0 deletions Assets/Scripts/Inventory2D.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Collections.Generic;
using UnityEngine;

public class Inventory2D : MonoBehaviour
{
[SerializeField] private int maxItems = 10;

private readonly List<ItemType> items = new List<ItemType>();

public bool AddItem(ItemType item)
{
if (item == ItemType.None || items.Count >= maxItems)
{
return false;
}

items.Add(item);
return true;
}

public bool HasItem(ItemType item)
{
return items.Contains(item);
}

public bool RemoveItem(ItemType item)
{
return items.Remove(item);
}

public IReadOnlyList<ItemType> Items
{
get { return items; }
}
}
32 changes: 32 additions & 0 deletions Assets/Scripts/ItemPickup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using UnityEngine;

[RequireComponent(typeof(Collider2D))]
public class ItemPickup : MonoBehaviour
{
[SerializeField] private ItemType itemType;

public void Configure(ItemType type)
{
itemType = type;
}

private void Reset()
{
var col = GetComponent<Collider2D>();
col.isTrigger = true;
}

private void OnTriggerEnter2D(Collider2D other)
{
var inventory = other.GetComponent<Inventory2D>();
if (inventory == null)
{
return;
}

if (inventory.AddItem(itemType))
{
Destroy(gameObject);
}
}
}
10 changes: 10 additions & 0 deletions Assets/Scripts/ItemType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
public enum ItemType
{
None = 0,
Key,
Book,
Candle,
Potion,
Gem,
Scroll
}
61 changes: 61 additions & 0 deletions Assets/Scripts/MansionQuestManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class MansionQuestManager : MonoBehaviour
{
[SerializeField] private List<NPCRequester> npcs = new List<NPCRequester>();
[SerializeField] private List<ItemType> possibleRequests = new List<ItemType>();
[SerializeField] private bool uniqueRequests = true;
[SerializeField] private UnityEvent onAllNPCsSatisfied;

private void Start()
{
AssignRequests();
}

public void AssignRequests()
{
if (npcs.Count == 0 || possibleRequests.Count == 0)
{
return;
}

List<ItemType> pool = new List<ItemType>(possibleRequests);

for (int i = 0; i < npcs.Count; i++)
{
if (npcs[i] == null)
{
continue;
}

if (pool.Count == 0)
{
pool = new List<ItemType>(possibleRequests);
}

int index = Random.Range(0, pool.Count);
ItemType selected = pool[index];
npcs[i].SetRequest(selected);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Initialize NPC quest-manager links during assignment

Quest completion is only checked through questManager?.CheckQuestState() in NPCRequester, but MansionQuestManager.AssignRequests() currently configures only the requested item and never injects itself into the NPCs. In scenes where designers only populate the manager's NPC list (as the quick setup suggests), each NPC's questManager stays null and onAllNPCsSatisfied will never be evaluated after deliveries.

Useful? React with 👍 / 👎.


if (uniqueRequests)
{
pool.RemoveAt(index);
}
}
}

public void CheckQuestState()
{
for (int i = 0; i < npcs.Count; i++)
{
if (npcs[i] != null && !npcs[i].IsSatisfied)
{
return;
}
}

onAllNPCsSatisfied?.Invoke();
}
}
64 changes: 64 additions & 0 deletions Assets/Scripts/NPCRequester.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using UnityEngine;
using UnityEngine.Events;

[RequireComponent(typeof(Collider2D))]
public class NPCRequester : MonoBehaviour
{
[Header("Pedido")]
[SerializeField] private ItemType requestedItem = ItemType.None;
[SerializeField] private bool consumeItemOnDelivery = true;
[SerializeField] private MansionQuestManager questManager;

[Header("Eventos")]
[SerializeField] private UnityEvent onCorrectItemDelivered;
[SerializeField] private UnityEvent onWrongItemOrMissing;

public ItemType RequestedItem => requestedItem;
public bool IsSatisfied { get; private set; }

private void Reset()
{
var col = GetComponent<Collider2D>();
col.isTrigger = true;
}

public void SetRequest(ItemType item)
{
requestedItem = item;
IsSatisfied = false;
}

private void OnTriggerStay2D(Collider2D other)
{
if (IsSatisfied)
{
return;
}

if (!other.CompareTag("Player") || !Input.GetKeyDown(KeyCode.E))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move interaction key check out of physics callback

The NPC hand-in gate uses Input.GetKeyDown(KeyCode.E) inside OnTriggerStay2D, but GetKeyDown is frame-based while OnTriggerStay2D runs on physics ticks, so key presses can be missed when render and fixed-update timing diverge. In play this causes intermittent failures to deliver items even when the player is inside the NPC trigger and pressing E correctly.

Useful? React with 👍 / 👎.

{
return;
}

var inventory = other.GetComponent<Inventory2D>();
if (inventory == null)
{
return;
}

if (requestedItem != ItemType.None && inventory.HasItem(requestedItem))
{
if (consumeItemOnDelivery)
{
inventory.RemoveItem(requestedItem);
}

IsSatisfied = true;
onCorrectItemDelivered?.Invoke();
questManager?.CheckQuestState();
return;
}

onWrongItemOrMissing?.Invoke();
}
}
80 changes: 80 additions & 0 deletions Assets/Scripts/RandomItemSpawner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System.Collections.Generic;
using UnityEngine;

public class RandomItemSpawner : MonoBehaviour
{
[System.Serializable]
public struct SpawnEntry
{
public ItemType itemType;
public GameObject prefab;
}

[SerializeField] private List<SpawnEntry> itemPrefabs = new List<SpawnEntry>();
[SerializeField] private List<Transform> spawnPoints = new List<Transform>();
[SerializeField] private int spawnCount = 8;

private readonly List<GameObject> spawnedItems = new List<GameObject>();

private void Start()
{
SpawnItems();
}

public void SpawnItems()
{
ClearItems();

if (itemPrefabs.Count == 0 || spawnPoints.Count == 0)
{
return;
}

int max = Mathf.Min(spawnCount, spawnPoints.Count);
List<Transform> shuffledPoints = new List<Transform>(spawnPoints);
Shuffle(shuffledPoints);

for (int i = 0; i < max; i++)
{
SpawnEntry entry = itemPrefabs[Random.Range(0, itemPrefabs.Count)];
if (entry.prefab == null)
{
continue;
}

GameObject instance = Instantiate(entry.prefab, shuffledPoints[i].position, Quaternion.identity);
ItemPickup pickup = instance.GetComponent<ItemPickup>();
if (pickup == null)
{
pickup = instance.AddComponent<ItemPickup>();
}

pickup.Configure(entry.itemType);
spawnedItems.Add(instance);
}
}

public void ClearItems()
{
for (int i = 0; i < spawnedItems.Count; i++)
{
if (spawnedItems[i] != null)
{
Destroy(spawnedItems[i]);
}
}

spawnedItems.Clear();
}

private static void Shuffle<T>(List<T> list)
{
for (int i = 0; i < list.Count; i++)
{
int randomIndex = Random.Range(i, list.Count);
T temp = list[i];
list[i] = list[randomIndex];
list[randomIndex] = temp;
}
}
}
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Sistema de missão para mansão 2D (Unity)

Este repositório inclui scripts base para o cenário pedido:

- 3 NPCs pedindo itens específicos.
- NPC aceita **somente** o item correto.
- Itens com spawn aleatório nas salas da mansão.
- Portas para transitar entre salas.

## Scripts

- `ItemType.cs`: enum com os tipos de item.
- `Inventory2D.cs`: inventário simples do jogador.
- `ItemPickup.cs`: coleta itens ao encostar no jogador.
- `NPCRequester.cs`: valida entrega de item no NPC ao pressionar `E`.
- `RandomItemSpawner.cs`: sorteia e instancia itens em pontos de spawn.
- `DoorTeleporter.cs`: teleporta jogador para outra sala ao interagir com porta.
- `MansionQuestManager.cs`: distribui pedidos aos NPCs e detecta conclusão.

## Configuração rápida

1. Adicione `Inventory2D` no objeto Player (com tag `Player`).
2. Crie prefabs de item com `Collider2D` trigger + `ItemPickup`.
3. Em cada NPC, adicione `Collider2D` trigger + `NPCRequester`.
4. Crie um objeto `GameManager` com `MansionQuestManager` e referencie os 3 NPCs.
5. Configure `possibleRequests` com os tipos de item permitidos.
6. Crie um objeto `RandomItemSpawner`, informe `spawnPoints` e os prefabs dos itens.
7. Em cada porta, adicione `DoorTeleporter` e informe o `destinationPoint` da próxima sala.

## Fluxo de jogo

- Jogador explora salas da mansão.
- Coleta itens sorteados nos pontos de spawn.
- Entra em portas para acessar novas salas.
- Entrega cada item no NPC correto usando `E`.
- Quando os 3 NPCs estiverem satisfeitos, `onAllNPCsSatisfied` é disparado.