Working on quest functions and increased map

This commit is contained in:
2026-02-13 17:32:58 +00:00
parent a45873aee0
commit 047d4d8464
24 changed files with 10804 additions and 1043 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,248 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &668200600896096350
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4648213167836588789}
- component: {fileID: 1420193402544156656}
- component: {fileID: 5195458718313043737}
m_Layer: 9
m_Name: SF_Prop_Crops_WheatStack_01
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4648213167836588789
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 668200600896096350}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.5, y: 0.5, z: 0.5}
m_ConstrainProportionsScale: 1
m_Children: []
m_Father: {fileID: 7956216992138273336}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &1420193402544156656
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 668200600896096350}
m_Mesh: {fileID: 4300000, guid: 954735d0ae79dc24db704089807c7144, type: 3}
--- !u!23 &5195458718313043737
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 668200600896096350}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: d726a65f2eaae8847857d20bb327828e, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &3084649488060959498
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7956216992138273336}
m_Layer: 9
m_Name: Model
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7956216992138273336
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3084649488060959498}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4648213167836588789}
m_Father: {fileID: 8734589286718127456}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &3537604211987859112
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8734589286718127456}
- component: {fileID: 6660721327025290466}
- component: {fileID: 555404001010922068}
- component: {fileID: 163142299225453467}
- component: {fileID: 4780405868371040945}
m_Layer: 9
m_Name: PushableObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8734589286718127456
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3537604211987859112}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -6, y: 0, z: -7.5}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 7956216992138273336}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!65 &6660721327025290466
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3537604211987859112}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1.0743799, y: 1.5028269, z: 1.0900393}
m_Center: {x: 0.0058722496, y: 0.74858654, z: 0.0058722496}
--- !u!114 &555404001010922068
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3537604211987859112}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 594cee8a0e8e65f49a379093604ba5b2, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::PushableObject
objectDisplayName: Interactive Object
customInteractionPoint: {fileID: 0}
enableDebugLogs: 0
pushForce: 10
tipRotation: {x: 0, y: 0, z: -75}
tipSpeed: 5
useGravity: 0
mass: 10
--- !u!54 &163142299225453467
Rigidbody:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3537604211987859112}
serializedVersion: 5
m_Mass: 10
m_LinearDamping: 0
m_AngularDamping: 0.05
m_CenterOfMass: {x: 0, y: 0, z: 0}
m_InertiaTensor: {x: 1, y: 1, z: 1}
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_ImplicitCom: 1
m_ImplicitTensor: 1
m_UseGravity: 1
m_IsKinematic: 0
m_Interpolate: 0
m_Constraints: 0
m_CollisionDetection: 0
--- !u!114 &4780405868371040945
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3537604211987859112}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e69611ed4badc844ab02eb8ebbe1adf0, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::QuestTrigger
objectDisplayName: Interactive Object
customInteractionPoint: {fileID: 0}
enableDebugLogs: 0
questId: 3
progressAmount: 1
triggerType: 4
requiredTag:
destroyAfterTrigger: 0
triggerOnce: 1

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 93659a05c2eabfd4a84fd03398457ae3
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ using System.Collections;
/// Example interactive object: A bridge that the player can cross.
/// Can be destroyed, collapsed, or require conditions to cross safely.
/// </summary>
public class Bridge : MonoBehaviour, IInteractiveObject
public class Bridge : InteractiveObjectBase
{
[Header("Bridge Settings")]
public string bridgeName = "Bridge";
@@ -13,38 +13,31 @@ public class Bridge : MonoBehaviour, IInteractiveObject
[SerializeField]
private float crossingDuration = 3f; // Time to cross the bridge
[Header("Interaction Point")]
public Transform interactionPoint; // Where player should stand to interact
private Coroutine crossingRoutine;
public Vector3 GetInteractionPoint()
{
if (interactionPoint != null)
return interactionPoint.position;
return transform.position;
}
public bool CanInteract()
public override bool CanInteract()
{
return isPassable;
}
public void Interact(GameObject player)
public override void Interact(GameObject player)
{
if (!isPassable)
{
Debug.Log("Bridge is not passable!", gameObject);
Log("Bridge is not passable!");
return;
}
Debug.Log($"Player {player.name} is crossing {bridgeName}", gameObject);
Log($"Player {player.name} is crossing {bridgeName}");
// You can add animation, effects, or checks here
// For example: play crossing animation, check integrity, trigger events, etc.
}
public string GetDisplayName() => bridgeName;
void OnValidate()
{
objectDisplayName = bridgeName;
}
/// <summary>
/// Make the bridge impassable (destroyed, collapsed, etc.)
@@ -52,7 +45,7 @@ public class Bridge : MonoBehaviour, IInteractiveObject
public void Collapse()
{
isPassable = false;
Debug.Log($"{bridgeName} has collapsed!", gameObject);
Log($"{bridgeName} has collapsed!");
// Add visual effects, sounds, animations here
}
@@ -62,6 +55,6 @@ public class Bridge : MonoBehaviour, IInteractiveObject
public void Repair()
{
isPassable = true;
Debug.Log($"{bridgeName} has been repaired!", gameObject);
Log($"{bridgeName} has been repaired!");
}
}

View File

@@ -36,6 +36,18 @@ public abstract class Character : MonoBehaviour
[SerializeField]
protected int MaxHp = 10;
[Header("Experience & Leveling")]
[SerializeField]
protected int currentLevel = 1;
[SerializeField]
protected int currentExperience = 0;
[SerializeField]
[Tooltip("Experience points required to reach the next level. Increases per level.")]
protected int experiencePerLevel = 100;
[SerializeField]
[Tooltip("Experience points awarded to the player when this character is defeated.")]
protected int experienceReward = 50;
[Header("Dialogue")]
public bool hasDialogue = false;
[Tooltip("The Dialogue asset for this character. Will be loaded into the central DialogueController.")]
@@ -125,6 +137,10 @@ public abstract class Character : MonoBehaviour
public int GetCurrentHP() => CurHp;
public int GetMaxHP() => MaxHp;
public int GetCurrentLevel() => currentLevel;
public int GetCurrentExperience() => currentExperience;
public int GetExperienceForNextLevel() => experiencePerLevel;
public int GetExperienceReward() => experienceReward;
protected virtual void Awake()
{
@@ -202,9 +218,58 @@ public abstract class Character : MonoBehaviour
public virtual void Die()
{
SetDeathAnimation();
// Reward experience to player if this character has an experience reward
if (experienceReward > 0 && Player.current != null)
{
Player.current.AddExperience(experienceReward);
Log($"Player gained {experienceReward} experience from defeating {gameObject.name}");
}
Destroy(gameObject);
}
public void AddExperience(int amount)
{
if (amount <= 0) return;
currentExperience += amount;
Log($"Gained {amount} experience. Total: {currentExperience}/{experiencePerLevel}");
// Check if level up
while (currentExperience >= experiencePerLevel)
{
LevelUp();
}
}
protected virtual void LevelUp()
{
currentExperience -= experiencePerLevel;
currentLevel++;
// Increase experience requirement for next level (e.g., 10% increase or fixed increment)
experiencePerLevel += 50; // Increase by 50 exp per level
Log($"LEVEL UP! Now level {currentLevel}. Next level requires {experiencePerLevel} exp.");
}
/// <summary>
/// Grant experience to the player from a quest or other source.
/// </summary>
public static void AwardQuestExperience(int amount)
{
if (Player.current != null)
{
Player.current.AddExperience(amount);
Debug.Log($"Player gained {amount} experience from quest completion!");
}
else
{
Debug.LogWarning("Cannot award experience: Player.current is null");
}
}
public void SetTarget(Character t)
{
target = t;

View File

@@ -340,8 +340,8 @@ public class ClickToMoveInputSystem : MonoBehaviour
{
agent.SetDestination(groundHit.point);
}
else if (NavMesh.SamplePosition(Player.current.transform.position, out NavMeshHit hit, 2f, NavMesh.AllAreas) &&
agent.Warp(hit.position) &&
else if (NavMesh.SamplePosition(Player.current.transform.position, out NavMeshHit meshHit, 2f, NavMesh.AllAreas) &&
agent.Warp(meshHit.position) &&
agent.isOnNavMesh)
{
agent.SetDestination(groundHit.point);

View File

@@ -2,7 +2,7 @@ using UnityEngine;
using System.Collections;
using UnityEngine.AI;
public class Gate : MonoBehaviour, IInteractiveObject
public class Gate : InteractiveObjectBase
{
[Header("Gate Settings")]
public bool startsOpen = false;
@@ -17,9 +17,6 @@ public class Gate : MonoBehaviour, IInteractiveObject
public float swingSpeed = 180f; // degrees per second
public bool rotateAroundLocalY = true;
[Header("Interaction")]
public Transform interactionPoint; // Where player should stand to interact (defaults to this transform)
[Header("NavMesh")]
public NavMeshObstacle navMeshObstacle;
@@ -164,7 +161,7 @@ public class Gate : MonoBehaviour, IInteractiveObject
void OnLockedInteract()
{
Debug.Log($"Gate is locked. Requires key: {(string.IsNullOrEmpty(requiredKeyId) ? "None" : requiredKeyId)}", gameObject);
Log($"Gate is locked. Requires key: {(string.IsNullOrEmpty(requiredKeyId) ? "None" : requiredKeyId)}");
}
void OnDestroy()
@@ -176,24 +173,22 @@ public class Gate : MonoBehaviour, IInteractiveObject
}
}
// IInteractiveObject implementation
public Vector3 GetInteractionPoint()
{
if (interactionPoint != null)
return interactionPoint.position;
return transform.position;
}
public bool CanInteract()
// Override base CanInteract
public override bool CanInteract()
{
return !isLocked;
}
public void Interact(GameObject player)
// Override base Interact implementation
public override void Interact(GameObject player)
{
Open();
Debug.Log($"Gate {gateName} opened by {player.name}");
Log($"Gate {gateName} opened by {player.name}");
}
public string GetDisplayName() => gateName;
// Set display name from gateName
void OnValidate()
{
objectDisplayName = gateName;
}
}

View File

@@ -0,0 +1,94 @@
using UnityEngine;
/// <summary>
/// Base class for all interactive objects that implement IInteractiveObject.
/// Consolidates common properties and methods to reduce code duplication.
/// </summary>
public abstract class InteractiveObjectBase : MonoBehaviour, IInteractiveObject
{
[Header("Interactive Object Settings")]
[SerializeField]
protected string objectDisplayName = "Interactive Object";
[SerializeField]
protected Transform customInteractionPoint; // Optional: use instead of collider center
[SerializeField]
protected bool enableDebugLogs = false;
/// <summary>
/// Get the interaction point - uses custom point if assigned, otherwise collider center
/// </summary>
public virtual Vector3 GetInteractionPoint()
{
if (customInteractionPoint != null)
{
Log("GetInteractionPoint: Using custom interaction point");
return customInteractionPoint.position;
}
Collider collider = GetComponent<Collider>();
if (collider != null)
{
Log($"GetInteractionPoint: Using collider center at {collider.bounds.center}");
return collider.bounds.center;
}
Log("GetInteractionPoint: No collider found, using transform position");
return transform.position;
}
/// <summary>
/// Get the display name for this interactive object
/// </summary>
public virtual string GetDisplayName()
{
Log($"GetDisplayName: Returning '{objectDisplayName}'");
return objectDisplayName;
}
/// <summary>
/// Check if this object can be interacted with
/// Override in derived classes to add custom logic
/// </summary>
public virtual bool CanInteract()
{
Log("CanInteract: Default implementation returns true");
return true;
}
/// <summary>
/// Perform the interaction
/// Must be implemented by derived classes
/// </summary>
public abstract void Interact(GameObject player);
/// <summary>
/// Protected logging utility with optional debug toggle
/// </summary>
protected void Log(string message)
{
if (enableDebugLogs)
{
DebugManager.Log(GetType().Name, message, gameObject);
}
}
/// <summary>
/// Set the display name at runtime
/// </summary>
public void SetDisplayName(string newName)
{
objectDisplayName = newName;
Log($"Display name changed to '{newName}'");
}
/// <summary>
/// Set custom interaction point Transform
/// </summary>
public void SetCustomInteractionPoint(Transform point)
{
customInteractionPoint = point;
Log($"Custom interaction point set to {point.name}");
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2020a1589d3df544f93fd540f264786e

View File

@@ -4,7 +4,7 @@ using UnityEngine;
/// Example interactive object: A ledge that the player can jump over.
/// Can have conditions like requiring a minimum jump height or specific equipment.
/// </summary>
public class Ledge : MonoBehaviour, IInteractiveObject
public class Ledge : InteractiveObjectBase
{
[Header("Ledge Settings")]
public string ledgeName = "Ledge";
@@ -17,27 +17,31 @@ public class Ledge : MonoBehaviour, IInteractiveObject
public Transform jumpEndPoint; // Where player lands after jumping
public bool isTraversable = true;
public Vector3 GetInteractionPoint()
/// <summary>
/// Check if the player can safely jump this ledge
/// (Implement with player stats, equipment, etc.)
/// </summary>
public bool CanPlayerJump(Character player)
{
if (jumpStartPoint != null)
return jumpStartPoint.position;
return transform.position;
// Add custom logic here
// Example: check if player has enough stamina, equipment bonuses, etc.
return true;
}
public bool CanInteract()
public override bool CanInteract()
{
return isTraversable;
}
public void Interact(GameObject player)
public override void Interact(GameObject player)
{
if (!isTraversable)
{
Debug.Log("Cannot jump over this ledge!", gameObject);
Log("Cannot jump over this ledge!");
return;
}
Debug.Log($"Player {player.name} is jumping over {ledgeName}", gameObject);
Log($"Player {player.name} is jumping over {ledgeName}");
// You can trigger jump animation, apply force, teleport player, etc.
// Example: Move player to the end point
@@ -55,26 +59,22 @@ public class Ledge : MonoBehaviour, IInteractiveObject
}
}
public string GetDisplayName() => $"{ledgeName} (Height: {height}m)";
/// <summary>
/// Check if the player can safely jump this ledge
/// (Implement with player stats, equipment, etc.)
/// </summary>
public bool CanPlayerJump(Character player)
public override Vector3 GetInteractionPoint()
{
// Add custom logic here
// Example: check if player has enough stamina, equipment bonuses, etc.
return true;
if (jumpStartPoint != null)
return jumpStartPoint.position;
return base.GetInteractionPoint();
}
public override string GetDisplayName() => $"{ledgeName} (Height: {height}m)";
/// <summary>
/// Block the ledge (might be destroyed, blocked by debris, etc.)
/// </summary>
public void Block()
{
isTraversable = false;
Debug.Log($"{ledgeName} is now blocked!", gameObject);
Log($"{ledgeName} is now blocked!");
}
void OnDrawGizmos()

View File

@@ -128,27 +128,49 @@ public class QuestManager : MonoBehaviour
/// </summary>
public bool ProgressQuest(string questId, int amount = 1)
{
Quest quest = GetActiveQuest(questId);
Quest quest = GetQuestById(questId); // Get from available quests, not active
if (quest == null)
{
Debug.LogWarning($"Active quest '{questId}' not found.");
Debug.LogWarning($"Quest '{questId}' not found in available quests.");
return false;
}
return ProgressQuest(quest, amount);
}
return ProgressQuest(quest, amount); // Let the Quest overload handle auto-activation
}
/// <summary>
/// Progress a quest
/// </summary>
public bool ProgressQuest(Quest quest, int amount = 1)
{
if (quest == null || !quest.IsActive())
if (quest == null)
{
Debug.LogWarning("Cannot progress quest - not active.");
Debug.LogWarning("Cannot progress null quest.");
return false;
}
// If quest not active, try to auto-activate it
if (!quest.IsActive())
{
// Check if quest has unmet prerequisites
if (!string.IsNullOrEmpty(quest.prerequisiteQuestId))
{
Quest prerequisite = GetQuestById(quest.prerequisiteQuestId);
if (prerequisite == null || prerequisite.state != QuestState.Completed)
{
Debug.LogWarning($"Cannot progress quest '{quest.questName}' - prerequisite '{quest.prerequisiteQuestId}' not completed.");
return false;
}
}
// Auto-activate the quest if it has no unmet prerequisites
quest.state = QuestState.Active;
quest.currentProgress = 0;
activeQuests.Add(quest);
Debug.Log($"Quest auto-activated: {quest.questName}");
onQuestStarted?.Invoke(quest);
}
// Progress the quest
int previousProgress = quest.currentProgress;
quest.AddProgress(amount);

View File

@@ -9,6 +9,8 @@ public class PlayerCombatController : MonoBehaviour
public int attackDamage = 1;
public float attackRange = 1.5f;
public float attackCooldown = 0.6f;
[Tooltip("Maximum distance to initiate combat (right-click detection range).")]
public float maxTargetAcquisitionRange = 10f;
[Tooltip("Maximum chase distance. Clear target if enemy gets farther than this.")]
public float maxChaseDistance = 20f;
@@ -82,7 +84,16 @@ public class PlayerCombatController : MonoBehaviour
{
if (enemy == null) return;
Log($"Setting target: {enemy.enemyName}");
float distance = Vector3.Distance(transform.position, enemy.transform.position);
// Validate enemy is within acquisition range
if (distance > maxTargetAcquisitionRange)
{
Log($"Enemy {enemy.enemyName} is too far away (distance: {distance:F2}, max: {maxTargetAcquisitionRange:F2}). Cannot target.");
return;
}
Log($"Setting target: {enemy.enemyName} (distance: {distance:F2})");
targetEnemy = enemy;
nextAttackTime = Time.time;

View File

@@ -0,0 +1,122 @@
using UnityEngine;
/// <summary>
/// Script for objects that can be pushed over when interacted with (right-click).
/// Applies torque to tip the object and falls under physics gravity.
/// Integrates with the interaction system.
/// </summary>
public class PushableObject : InteractiveObjectBase
{
[Header("Push Settings")]
[SerializeField]
[Tooltip("Force applied to push the object over.")]
private float pushForce = 10f;
[SerializeField]
[Tooltip("Rotation angle (in degrees) the object tips to when pushed.")]
private Vector3 tipRotation = new Vector3(90f, 0f, 0f);
[SerializeField]
[Tooltip("Speed at which the object rotates to the tip angle.")]
private float tipSpeed = 5f;
[Header("Physics")]
[SerializeField]
[Tooltip("Whether to use gravity for falling.")]
private bool useGravity = true;
[SerializeField]
[Tooltip("Mass of the object (affects how it falls).")]
private float mass = 1f;
private Rigidbody rb;
private bool hasTipped = false;
private Vector3 originalRotation;
private Quaternion targetRotation;
private void Awake()
{
rb = GetComponent<Rigidbody>();
if (rb == null)
{
rb = gameObject.AddComponent<Rigidbody>();
Log("No Rigidbody found. Added one automatically.");
}
// Configure physics
rb.useGravity = useGravity;
rb.mass = mass;
rb.constraints = RigidbodyConstraints.FreezeRotation; // Start frozen until pushed
originalRotation = transform.eulerAngles;
objectDisplayName = "Pushable Object";
Log($"Initialized on layer '{LayerMask.LayerToName(gameObject.layer)}'. Make sure this layer is in ClickToMoveInputSystem's selectionLayers!");
}
public override bool CanInteract()
{
return !hasTipped;
}
public override void Interact(GameObject player)
{
Log("Interact method called");
Push();
}
public void Push()
{
Log("Push method called");
if (hasTipped)
{
Log("Object already tipped, cannot push again.");
return;
}
hasTipped = true;
Log("Object pushed! Tipping over...");
// Unfreeze rotation to allow physics
rb.constraints = RigidbodyConstraints.FreezePosition;
// Apply upward impulse to simulate loss of balance
rb.linearVelocity = Vector3.up * (pushForce * 0.5f);
// Calculate target rotation
targetRotation = Quaternion.Euler(originalRotation + tipRotation);
// Use AnimationUtilities to rotate toward tip angle
float duration = 1f / tipSpeed;
AnimationUtilities.RotateTo(this, transform, targetRotation, duration);
rb.constraints = RigidbodyConstraints.FreezeAll; // Freeze all rotation after tipping to prevent wobbling
TryGetComponent<QuestTrigger>(out var questTrigger);
Debug.Log($"QuestTrigger found: {questTrigger != null}");
if (questTrigger != null)
{
questTrigger.TryTrigger(gameObject);
Log("Triggered quest event on push!");
}
}
/// <summary>
/// Reset the object to its original state
/// </summary>
public void Reset()
{
hasTipped = false;
rb.linearVelocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
rb.constraints = RigidbodyConstraints.FreezeRotation;
transform.eulerAngles = originalRotation;
Log("Object reset to original position.");
}
private void OnDrawGizmosSelected()
{
// Draw push direction indicator
Gizmos.color = Color.green;
Gizmos.DrawRay(transform.position, Vector3.up * 2f);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 594cee8a0e8e65f49a379093604ba5b2

View File

@@ -4,7 +4,7 @@ using UnityEngine;
/// Example script showing how to interact with the Quest system
/// Attach to objects that trigger quest progression (enemies, items, zones, etc.)
/// </summary>
public class QuestTrigger : MonoBehaviour
public class QuestTrigger : InteractiveObjectBase
{
[Header("Quest Trigger Settings")]
[Tooltip("The quest ID this trigger affects")]
@@ -33,6 +33,7 @@ public class QuestTrigger : MonoBehaviour
OnCollisionEnter,
OnItemPickup,
OnDeath,
OnInteract,
Manual
}
@@ -85,45 +86,87 @@ public class QuestTrigger : MonoBehaviour
}
}
void TryTrigger(GameObject triggerer)
/// <summary>
/// IInteractiveObject implementation - called when player interacts with this object
/// </summary>
public override void Interact(GameObject player)
{
if (triggerType == TriggerType.OnInteract)
{
TryTrigger(player);
}
}
/// <summary>
/// IInteractiveObject implementation - check if interaction is allowed
/// </summary>
public override bool CanInteract()
{
// Allow interaction if not already triggered, or if it can trigger multiple times
return !hasTriggered || !triggerOnce;
}
public void TryTrigger(GameObject triggerer)
{
Debug.Log($"[QuestTrigger.TryTrigger] ENTRY - HasTriggered: {hasTriggered}, TriggerOnce: {triggerOnce}");
// Check if already triggered
if (hasTriggered && triggerOnce)
{
Debug.Log($"[QuestTrigger.TryTrigger] EARLY RETURN - Quest already triggered and triggerOnce is enabled.");
return;
}
// Check tag requirement
if (triggerer != null && !string.IsNullOrEmpty(requiredTag))
{
Debug.Log($"[QuestTrigger.TryTrigger] Checking tag requirement. Required: {requiredTag}, Triggerer tag: {triggerer.tag}");
if (!triggerer.CompareTag(requiredTag))
{
Debug.Log($"[QuestTrigger.TryTrigger] EARLY RETURN - Tag mismatch.");
return;
}
}
// Check if QuestManager exists
if (QuestManager.Instance == null)
{
Debug.LogWarning("QuestManager not found in scene.", gameObject);
Debug.LogWarning("[QuestTrigger.TryTrigger] EARLY RETURN - QuestManager not found in scene.");
return;
}
// Check if quest is active
if (!QuestManager.Instance.IsQuestActive(questId))
Debug.Log($"[QuestTrigger.TryTrigger] Getting quest with ID: {questId}");
// Get the quest from the database
Quest quest = QuestManager.Instance.GetQuestById(questId);
if (quest == null)
{
Debug.Log($"Quest '{questId}' is not active. Cannot progress.", gameObject);
Debug.LogWarning($"[QuestTrigger.TryTrigger] EARLY RETURN - Quest '{questId}' not found in quest database.");
return;
}
Debug.Log($"[QuestTrigger.TryTrigger] Quest found: {quest.questName}. Attempting to progress by {progressAmount}");
// Progress the quest
bool progressed = QuestManager.Instance.ProgressQuest(questId, progressAmount);
// Progress the quest (QuestManager handles auto-activation and prerequisite checks)
bool progressed = QuestManager.Instance.ProgressQuest(quest, progressAmount);
Debug.Log($"[QuestTrigger.TryTrigger] ProgressQuest returned: {progressed}");
if (progressed)
{
hasTriggered = true;
Debug.Log($"Quest '{questId}' progressed by {progressAmount}", gameObject);
Debug.Log($"[QuestTrigger.TryTrigger] SUCCESS! Quest '{questId}' progressed by {progressAmount}");
// Destroy if configured
if (destroyAfterTrigger)
{
Debug.Log($"[QuestTrigger.TryTrigger] Destroying object after trigger.");
Destroy(gameObject);
}
}
else
{
Debug.LogWarning($"[QuestTrigger.TryTrigger] FAILED to progress quest '{questId}'");
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 856d1402c92a4d241b1520c6faa70358
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,134 @@
using UnityEngine;
using System.Collections;
/// <summary>
/// Utility class for common animation coroutines and animation helpers.
/// Provides reusable animation patterns used across multiple scripts.
/// </summary>
public static class AnimationUtilities
{
/// <summary>
/// Smoothly rotate an object from its current rotation to a target rotation
/// </summary>
public static Coroutine RotateTo(MonoBehaviour source, Transform target, Quaternion targetRotation, float duration)
{
return source.StartCoroutine(RotateToRoutine(target, targetRotation, duration));
}
private static IEnumerator RotateToRoutine(Transform target, Quaternion targetRotation, float duration)
{
if (duration <= 0) yield break;
float elapsedTime = 0f;
Quaternion startRotation = target.rotation;
while (elapsedTime < duration)
{
elapsedTime += Time.deltaTime;
float t = Mathf.Clamp01(elapsedTime / duration);
target.rotation = Quaternion.Slerp(startRotation, targetRotation, t);
yield return null;
}
target.rotation = targetRotation;
}
/// <summary>
/// Smoothly move an object from its current position to a target position
/// </summary>
public static Coroutine MoveTo(MonoBehaviour source, Transform target, Vector3 targetPosition, float duration)
{
return source.StartCoroutine(MoveToRoutine(target, targetPosition, duration));
}
private static IEnumerator MoveToRoutine(Transform target, Vector3 targetPosition, float duration)
{
if (duration <= 0) yield break;
float elapsedTime = 0f;
Vector3 startPosition = target.position;
while (elapsedTime < duration)
{
elapsedTime += Time.deltaTime;
float t = Mathf.Clamp01(elapsedTime / duration);
target.position = Vector3.Lerp(startPosition, targetPosition, t);
yield return null;
}
target.position = targetPosition;
}
/// <summary>
/// Smoothly rotate an object around a specific axis by a specified angle
/// </summary>
public static Coroutine RotateAround(MonoBehaviour source, Transform target, float targetAngle, Vector3 axis, float duration)
{
return source.StartCoroutine(RotateAroundRoutine(target, targetAngle, axis, duration));
}
private static IEnumerator RotateAroundRoutine(Transform target, float targetAngle, Vector3 axis, float duration)
{
if (duration <= 0) yield break;
float elapsedTime = 0f;
Vector3 eulerAngles = target.localEulerAngles;
float startAngle = 0;
if (axis == Vector3.right) startAngle = eulerAngles.x;
else if (axis == Vector3.up) startAngle = eulerAngles.y;
else if (axis == Vector3.forward) startAngle = eulerAngles.z;
while (elapsedTime < duration)
{
elapsedTime += Time.deltaTime;
float t = Mathf.Clamp01(elapsedTime / duration);
float currentAngle = Mathf.Lerp(startAngle, targetAngle, t);
eulerAngles = target.localEulerAngles;
if (axis == Vector3.right) eulerAngles.x = currentAngle;
else if (axis == Vector3.up) eulerAngles.y = currentAngle;
else if (axis == Vector3.forward) eulerAngles.z = currentAngle;
target.localEulerAngles = eulerAngles;
yield return null;
}
eulerAngles = target.localEulerAngles;
if (axis == Vector3.right) eulerAngles.x = targetAngle;
else if (axis == Vector3.up) eulerAngles.y = targetAngle;
else if (axis == Vector3.forward) eulerAngles.z = targetAngle;
target.localEulerAngles = eulerAngles;
}
/// <summary>
/// Fade in/out by modifying material color alpha
/// </summary>
public static Coroutine Fade(MonoBehaviour source, Renderer renderer, float targetAlpha, float duration)
{
return source.StartCoroutine(FadeRoutine(renderer, targetAlpha, duration));
}
private static IEnumerator FadeRoutine(Renderer renderer, float targetAlpha, float duration)
{
if (renderer == null || duration <= 0) yield break;
float elapsedTime = 0f;
Material material = renderer.material;
Color startColor = material.color;
while (elapsedTime < duration)
{
elapsedTime += Time.deltaTime;
float t = Mathf.Clamp01(elapsedTime / duration);
Color newColor = startColor;
newColor.a = Mathf.Lerp(startColor.a, targetAlpha, t);
material.color = newColor;
yield return null;
}
Color finalColor = startColor;
finalColor.a = targetAlpha;
material.color = finalColor;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ccc2d1cf29c898248bdaa081a1e18bc6

View File

@@ -0,0 +1,63 @@
using UnityEngine;
/// <summary>
/// Centralized debug logging manager for consistent logging across all systems.
/// Provides a global debug toggle and formatted log output.
/// </summary>
public class DebugManager : MonoBehaviour
{
public static DebugManager Instance { get; private set; }
[SerializeField]
private bool enableGlobalDebugLogs = true;
void Awake()
{
if (Instance != null && Instance != this)
{
Debug.LogWarning("Multiple DebugManager instances detected. Destroying duplicate.", gameObject);
Destroy(gameObject);
return;
}
Instance = this;
}
void OnDestroy()
{
if (Instance == this) Instance = null;
}
/// <summary>
/// Log a message if debug logging is enabled
/// </summary>
public static void Log(string category, string message, Object context = null)
{
if (Instance == null || !Instance.enableGlobalDebugLogs) return;
Debug.Log($"[{category}] {message}", context);
}
/// <summary>
/// Log a warning message
/// </summary>
public static void LogWarning(string category, string message, Object context = null)
{
Debug.LogWarning($"[{category}] {message}", context);
}
/// <summary>
/// Log an error message
/// </summary>
public static void LogError(string category, string message, Object context = null)
{
Debug.LogError($"[{category}] {message}", context);
}
/// <summary>
/// Set global debug logging state
/// </summary>
public static void SetDebugEnabled(bool enabled)
{
if (Instance != null)
Instance.enableGlobalDebugLogs = enabled;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 03b75d1f7b86ca241b28fa04bf7a3968

View File

@@ -0,0 +1,69 @@
using UnityEngine;
/// <summary>
/// Utility class for proximity and distance-based calculations.
/// Centralizes common distance checking logic used throughout the game.
/// </summary>
public static class ProximityUtility
{
/// <summary>
/// Check if two objects are within a specified distance
/// </summary>
public static bool IsWithinDistance(Vector3 from, Vector3 to, float distance)
{
return Vector3.Distance(from, to) <= distance;
}
/// <summary>
/// Check if two Transform objects are within a specified distance
/// </summary>
public static bool IsWithinDistance(Transform from, Transform to, float distance)
{
if (from == null || to == null) return false;
return IsWithinDistance(from.position, to.position, distance);
}
/// <summary>
/// Check if GameObject is within a specified distance
/// </summary>
public static bool IsWithinDistance(GameObject from, GameObject to, float distance)
{
if (from == null || to == null) return false;
return IsWithinDistance(from.transform.position, to.transform.position, distance);
}
/// <summary>
/// Get the distance between two positions
/// </summary>
public static float GetDistance(Vector3 from, Vector3 to)
{
return Vector3.Distance(from, to);
}
/// <summary>
/// Get the distance between two Transform objects
/// </summary>
public static float GetDistance(Transform from, Transform to)
{
if (from == null || to == null) return float.MaxValue;
return GetDistance(from.position, to.position);
}
/// <summary>
/// Get the distance between two GameObjects
/// </summary>
public static float GetDistance(GameObject from, GameObject to)
{
if (from == null || to == null) return float.MaxValue;
return GetDistance(from.transform.position, to.transform.position);
}
/// <summary>
/// Check if object is in range and handle both close and far cases
/// </summary>
public static bool CheckRange(Vector3 from, Vector3 to, float minDistance, float maxDistance, out float distance)
{
distance = GetDistance(from, to);
return distance >= minDistance && distance <= maxDistance;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e4d921cf5be3f7d4d89cbc3067f48272