started working inventory system

This commit is contained in:
unknown
2025-10-06 15:32:09 +01:00
parent 4d2c0a595d
commit 1ee08ea649
564 changed files with 129649 additions and 355 deletions

View File

@@ -5,11 +5,20 @@ using UnityEngine.Events;
using EasyTalk.Controller;
using EasyTalk.Nodes;
public abstract class Character : MonoBehaviour
[RequireComponent(typeof(CharacterController))]
public abstract class Character : MonoBehaviour, IInteractable
{
public enum Attitude { Friendly, Neutral, Hostile }
public Attitude attitude = Attitude.Neutral;
[Header("Stats")]
public int CurHp;
public int MaxHp;
public int Damage;
public int level = 1;
public float AttackRange = 2.0f;
public float ChaseRange = 5.0f;
public float AttackRate = 1.0f;
public float MoveSpeed = 5.0f;
[Header("Components")]
public CharacterController Controller;
@@ -18,16 +27,19 @@ public abstract class Character : MonoBehaviour
public Dialogue dialogue;
protected DialogueController dialogueController;
public event UnityAction onTakeDamage;
public Inventory Inventory { get { return inventory; } }
private Inventory inventory;
void Awake()
{
dialogueController = FindFirstObjectByType<DialogueController>();
CurHp = MaxHp;
// Get the Inventory component attached to this same GameObject
inventory = GetComponent<Inventory>();
}
public void TakeDamage(int damageToTake)
{
CurHp -= damageToTake;
onTakeDamage?.Invoke();
if (CurHp <= 0)
@@ -43,4 +55,38 @@ public abstract class Character : MonoBehaviour
{
target = t;
}
public virtual string GetInteractPrompt()
{
switch (attitude)
{
case Attitude.Friendly:
return $"Talk to {charName}";
case Attitude.Neutral:
return $"Talk to {charName}";
case Attitude.Hostile:
return $"Attack {charName}";
default:
return $"Interact with {charName}";
}
}
public virtual void OnInteract()
{
switch (attitude)
{
case Attitude.Friendly:
case Attitude.Neutral:
// Start dialogue for friendly or neutral characters
if (dialogue != null && dialogueController != null)
{
dialogueController.ChangeDialogue(dialogue);
dialogueController.PlayDialogue();
}
break;
case Attitude.Hostile:
// Interacting with a hostile character could set them as the combat target
Player.Current.SetTarget(this);
break;
}
}
}

View File

@@ -21,9 +21,16 @@ public class CharacterController : MonoBehaviour
{
if (moveTarget != null && Time.time - lastMoveToUpdate > moveToUpdateRate)
{
animator.ResetTrigger("attack");
lastMoveToUpdate = Time.time;
MoveToPosition(moveTarget.position);
}
if (animator != null)
{
float speed = agent.velocity.magnitude;
float normalisedSpeed = speed / agent.speed;
animator.SetFloat("moveSpeed", normalisedSpeed);
}
}
public void LookTowards (Vector3 direction)
@@ -33,7 +40,6 @@ public class CharacterController : MonoBehaviour
public void MoveToTarget (Transform target)
{
animator.SetBool("isMoving", true);
moveTarget = target;
}
@@ -47,6 +53,5 @@ public class CharacterController : MonoBehaviour
{
agent.isStopped = true;
moveTarget = null;
animator.SetBool("isMoving", false);
}
}

View File

@@ -1,16 +1,26 @@
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
public interface IInteractable
{
string GetInteractPrompt();
void OnInteract();
}
public class ClickController : MonoBehaviour
{
[SerializeField] private LayerMask layerMask;
[SerializeField] private TextMeshProUGUI promptText;
void Update ()
{
Hover();
if (Mouse.current.rightButton.wasPressedThisFrame)
Click();
}
void Click ()
@@ -22,48 +32,53 @@ public class ClickController : MonoBehaviour
// Shoot a raycast from our mouse to what ever we are pointing at.
if (Physics.Raycast(ray, out hit, 1000, layerMask))
{
int hitLayer = hit.collider.gameObject.layer;
// Did we hit the GROUND?
if (hitLayer == LayerMask.NameToLayer("Ground"))
// This single block now handles NPCs, ItemObjects, and more!
if (hit.collider.TryGetComponent(out IInteractable interactable))
{
interactable.OnInteract();
}
else if (hit.collider.TryGetComponent(out Enemy enemy))
{
// Combat targeting is a different action, so it's fine to keep it separate.
Debug.Log("Enemy Clicked");
Player.Current.SetTarget(enemy);
}
else if (hit.collider.gameObject.layer == LayerMask.NameToLayer("Ground"))
{
Debug.Log("Ground Clicked");
Player.Current.SetTarget(null);
Player.Current.Controller.MoveToPosition(hit.point);
}
// Did we hit an ENEMY?
else if (hitLayer == LayerMask.NameToLayer("Enemy"))
}
}
void Hover ()
{
Vector2 mousePos = Mouse.current.position.ReadValue();
Ray ray = Camera.main.ScreenPointToRay(mousePos);
RaycastHit hit;
// Shoot a raycast from our mouse to what ever we are pointing at.
if (Physics.Raycast(ray, out hit, 1000, layerMask))
{
//int hitLayer = hit.collider.gameObject.layer;
//if (hitLayer == LayerMask.NameToLayer("Interactable"))
if(hit.collider.TryGetComponent(out IInteractable interactable))
{
Debug.Log("Enemy Clicked");
Character enemy = hit.collider.GetComponent<Character>();
Player.Current.SetTarget(enemy);
}
else if (hitLayer == LayerMask.NameToLayer("Interactable"))
{
Debug.Log("Interactable Clicked");
if (hit.collider.TryGetComponent(out Enemy enemy))
// Set the position of the prompt to the mouse position
promptText.rectTransform.position = mousePos + new Vector2(15, -15);
promptText.gameObject.SetActive(true);
promptText.text = interactable.GetInteractPrompt();
if (hit.collider.TryGetComponent(out Character character))
{
Debug.Log("Enemy Clicked");
//Character enemy = hit.collider.GetComponent<Character>();
Player.Current.SetTarget(enemy);
}
else if (hit.collider.TryGetComponent(out NPC npc))
{
Debug.Log("NPC Clicked");
Player.Current.SetTarget(npc);
npc.Interact();
return;
HealthBarUI.instance.UpdateInfoPanel(character.charName, character.level, character.attitude);
}
else
{
Debug.Log("Something Else Clicked");
Player.Current.SetTarget(null);
//Debug.Log("Something Else Hovered");
}
}
else
{
Debug.Log("Something Else Clicked");
Player.Current.SetTarget(null);
//Debug.Log("Something Else Hovered");
}
}
}

View File

@@ -13,12 +13,7 @@ public class Enemy : Character
private State curState = State.Idle;
[Header("Ranges")]
[SerializeField] private float chaseRange;
[SerializeField] private float attackRange;
[Header("Attack")]
[SerializeField] private float attackRate;
private float lastAttackTime;
[SerializeField] private GameObject attackPrefab;
@@ -75,30 +70,30 @@ public class Enemy : Character
// Called every frame while in the IDLE state.
void IdleUpdate()
{
if (targetDistance < chaseRange && targetDistance > attackRange)
if (targetDistance < ChaseRange && targetDistance > AttackRange)
SetState(State.Chase);
else if (targetDistance < attackRange)
else if (targetDistance < AttackRange)
SetState(State.Attack);
}
// Called every frame while in the CHASE state.
void ChaseUpdate ()
{
if (targetDistance > chaseRange)
if (targetDistance > ChaseRange)
SetState(State.Idle);
else if (targetDistance < attackRange)
else if (targetDistance < AttackRange)
SetState(State.Attack);
}
// Called every frame while in the ATTACK state.
void AttackUpdate ()
{
if (targetDistance > attackRange)
if (targetDistance > AttackRange)
SetState(State.Chase);
Controller.LookTowards(target.transform.position - transform.position);
if(Time.time - lastAttackTime > attackRate)
if(Time.time - lastAttackTime > AttackRate)
{
lastAttackTime = Time.time;
AttackTarget();

View File

@@ -2,11 +2,19 @@ using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class HealthBarUI : MonoBehaviour
{
public static HealthBarUI instance;
private void Awake() { instance = this; }
[SerializeField] private Image healthFill;
[SerializeField] private Character character;
[SerializeField] private TextMeshProUGUI nameText;
[SerializeField] private TextMeshProUGUI levelText;
[SerializeField] private TextMeshProUGUI attitudeTxt;
[SerializeField] private TextMeshProUGUI goldTxt;
void OnEnable ()
{
@@ -32,4 +40,14 @@ public class HealthBarUI : MonoBehaviour
{
healthFill.fillAmount = (float)character.CurHp / (float)character.MaxHp;
}
public void UpdateInfoPanel(string name, int level, Character.Attitude attitude)
{
nameText.text = "Name: " + name;
levelText.text = "Level: " + level;
attitudeTxt.text = "Attitude " + attitude;
}
public void UpdateGold(int gold)
{
goldTxt.text = "Gold: " + gold;
}
}

View File

@@ -1,2 +1,11 @@
fileFormatVersion: 2
guid: f58975b416465f14fba784246f317ab5
guid: f58975b416465f14fba784246f317ab5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,61 @@
using UnityEngine;
using System.Collections.Generic;
public class Inventory : MonoBehaviour
{
public int maxCapacity = 20;
protected Dictionary<string, int> items = new();
public List<InventoryEntry> startingItems; //List for inspector
private int gold;
public int Gold
{
get { return gold; }
set { gold = Mathf.Max(0, value); }
}
void Start()
{
// Initialize inventory with starting items
foreach (var entry in startingItems)
{
for (int i = 0; i < entry.quantity; i++)
{
AddItem(entry.itemName);
}
}
}
// 'virtual' allows this method to be overridden by subclasses like PlayerInventory
public virtual void AddItem(string itemName)
{
if (items.Count >= maxCapacity && !items.ContainsKey(itemName))
{
Debug.Log("Inventory is full. Cannot add new item: " + itemName);
return;
}
if (items.ContainsKey(itemName))
{
items[itemName]++;
}
else
{
items[itemName] = 1;
}
Debug.Log($"Added {itemName} to an inventory. Total: {items[itemName]}");
}
public void RemoveItem(string itemName)
{
items.Remove(itemName);
}
public int GetItemCount(string itemName)
{
return items.ContainsKey(itemName) ? items[itemName] : 0;
}
}
[System.Serializable]
public class InventoryEntry
{
public string itemName;
public int quantity;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3b32da55f42c25d499e739e39c423d4f

View File

@@ -6,8 +6,6 @@ public class NPC : Character
public enum NPCType { Villager, Merchant, Guard }
public NPCType npcType;
public float speed = 5f;
public string npcName = "NPC";
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
@@ -23,21 +21,20 @@ public class NPC : Character
{
if (npcType == NPCType.Merchant)
{
Debug.Log($"Welcome to my shop, {npcName}!");
Debug.Log($"Welcome to my shop, {charName}!");
}
else if (npcType == NPCType.Villager)
{
//Debug.Log($"Hello there! I'm {npcName}, a humble villager.");
dialogueController.ChangeDialogue(dialogue);
dialogueController.PlayDialogue();
}
else if (npcType == NPCType.Guard)
{
Debug.Log($"Stay out of trouble, citizen. I'm {npcName}, a guard.");
Debug.Log($"Stay out of trouble, citizen. I'm {charName}, a guard.");
}
else
{
Debug.Log($"Greetings from {npcName}!");
Debug.Log($"Greetings from {charName}!");
}
}
}

View File

@@ -31,7 +31,7 @@ public class Player : Character
if (Time.time - lastAttackTime > attackRate && target.GetComponent<Enemy>() != null)
{
//animator.SetTrigger("attack");
animator.SetTrigger("attack");
lastAttackTime = Time.time;
GameObject proj = Instantiate(attackPrefab, target.transform.position + Vector3.up, Quaternion.LookRotation(target.transform.position - transform.position));
proj.GetComponent<Projectile>().Setup(this);

View File

@@ -0,0 +1,28 @@
using UnityEngine;
public class PlayerInventory : Inventory
{
public static PlayerInventory instance;
private void Awake()
{
instance = this;
}
void Start()
{
// We can update the UI here because this is the Player's inventory
HealthBarUI.instance.UpdateGold(Gold);
}
// 'override' modifies the base AddItem method to add UI updates
public override void AddItem(string itemName)
{
// This calls the base AddItem logic first
base.AddItem(itemName);
// Then, it adds the player-specific UI update
Debug.Log("Updating player UI for item: " + itemName);
// You would add your specific UI update logic here, e.g., UpdateInventoryUI();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4b7212ac7e102614f86ce053ab247dcd

View File

@@ -0,0 +1,49 @@
using UnityEngine;
public class QuestManager : MonoBehaviour
{
public static QuestManager instance;
private void Awake() { instance = this; }
public enum QuestState { NotStarted, InProgress, Completed }
public QuestState currentState = QuestState.NotStarted;
public enum QuestPriority { Main, Side }
public QuestPriority questType;
public enum QuestDifficulty { Easy, Medium, Hard }
public QuestDifficulty questDifficulty;
public enum QuestReward { Gold, Item, Experience }
public QuestReward questReward;
public enum QuestType { Fetch, Kill, Escort }
public QuestType questObjectiveType;
public string questName;
public int questID;
public string questDescription;
public int experienceReward;
public int goldReward;
public string itemReward;
public int objectiveAmount;
public int currentAmount;
public string objectiveTarget;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void QuestStart(int questId)
{
currentState = QuestState.InProgress;
Debug.Log("Quest " + questID + " started.");
}
public void QuestComplete(int questId)
{
currentState = QuestState.Completed;
Debug.Log("Quest " + questID + " completed! Rewards: " + experienceReward + " XP, " + goldReward + " Gold, Item: " + itemReward);
// Here you would typically add code to give the player their rewards
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 821ad8b6d539d534986be2f186a56cf6