Merge branch origin/codex/add-player-interactions-and-critter-feeding into main
This commit is contained in:
24
Assets/Scripts/Interaction/Interactable.cs
Normal file
24
Assets/Scripts/Interaction/Interactable.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace BlackRoad.Worldbuilder.Interaction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base component for objects that can be interacted with by the player.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Interactable : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Tooltip("Display name shown in UI when player looks at this.")]
|
||||||
|
[SerializeField] private string displayName = "Object";
|
||||||
|
|
||||||
|
[Tooltip("Optional interaction verb (e.g., 'Feed', 'Open', 'Talk').")]
|
||||||
|
[SerializeField] private string verb = "Use";
|
||||||
|
|
||||||
|
public string DisplayName => displayName;
|
||||||
|
public string Verb => verb;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the player attempts to interact with this object.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Interact(GameObject interactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
Assets/Scripts/Interaction/PlayerInteractor.cs
Normal file
71
Assets/Scripts/Interaction/PlayerInteractor.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace BlackRoad.Worldbuilder.Interaction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Raycasts from the center of the player's view to find Interactable objects.
|
||||||
|
/// Shows a simple on-screen prompt and calls Interact() on key press.
|
||||||
|
/// </summary>
|
||||||
|
public class PlayerInteractor : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("Refs")]
|
||||||
|
[SerializeField] private Camera playerCamera;
|
||||||
|
|
||||||
|
[Header("Settings")]
|
||||||
|
[SerializeField] private float interactDistance = 4f;
|
||||||
|
[SerializeField] private KeyCode interactKey = KeyCode.E;
|
||||||
|
|
||||||
|
[Header("UI")]
|
||||||
|
[SerializeField] private bool drawPrompt = true;
|
||||||
|
|
||||||
|
private Interactable _current;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (playerCamera == null)
|
||||||
|
playerCamera = Camera.main;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
UpdateCurrentTarget();
|
||||||
|
|
||||||
|
if (_current != null && Input.GetKeyDown(interactKey))
|
||||||
|
{
|
||||||
|
_current.Interact(gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCurrentTarget()
|
||||||
|
{
|
||||||
|
_current = null;
|
||||||
|
if (playerCamera == null) return;
|
||||||
|
|
||||||
|
Ray ray = playerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
|
||||||
|
if (Physics.Raycast(ray, out RaycastHit hit, interactDistance))
|
||||||
|
{
|
||||||
|
_current = hit.collider.GetComponentInParent<Interactable>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGUI()
|
||||||
|
{
|
||||||
|
if (!drawPrompt || _current == null) return;
|
||||||
|
|
||||||
|
string label = $"{_current.Verb} {_current.DisplayName} [{interactKey}]";
|
||||||
|
|
||||||
|
GUIStyle style = GUI.skin.label;
|
||||||
|
style.alignment = TextAnchor.LowerCenter;
|
||||||
|
style.fontSize = 16;
|
||||||
|
|
||||||
|
Rect rect = new Rect(
|
||||||
|
0,
|
||||||
|
Screen.height - 40,
|
||||||
|
Screen.width,
|
||||||
|
30
|
||||||
|
);
|
||||||
|
|
||||||
|
GUI.Label(rect, label, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
99
Assets/Scripts/Life/CritterInteraction.cs
Normal file
99
Assets/Scripts/Life/CritterInteraction.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using BlackRoad.Worldbuilder.Interaction;
|
||||||
|
|
||||||
|
namespace BlackRoad.Worldbuilder.Life
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Allows the player to feed and "pet" critters.
|
||||||
|
/// Feeding reduces hunger via CritterNeeds.
|
||||||
|
/// Petting increases a simple trust value.
|
||||||
|
/// </summary>
|
||||||
|
public class CritterInteraction : Interactable
|
||||||
|
{
|
||||||
|
[Header("Needs & Behaviour")]
|
||||||
|
[SerializeField] private CritterNeeds needs;
|
||||||
|
[SerializeField] private CritterAgent agent;
|
||||||
|
|
||||||
|
[Header("Interaction Effects")]
|
||||||
|
[Tooltip("How much hunger to reduce per feeding (0..1).")]
|
||||||
|
[SerializeField] private float feedAmount = 0.25f;
|
||||||
|
[Tooltip("How much trust is gained per pet.")]
|
||||||
|
[SerializeField] private float trustIncrease = 0.1f;
|
||||||
|
|
||||||
|
[Header("Trust")]
|
||||||
|
[Range(0f, 1f)]
|
||||||
|
[SerializeField] private float trust = 0f;
|
||||||
|
|
||||||
|
public float Trust => trust;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (needs == null)
|
||||||
|
needs = GetComponent<CritterNeeds>();
|
||||||
|
if (agent == null)
|
||||||
|
agent = GetComponent<CritterAgent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Interact(GameObject interactor)
|
||||||
|
{
|
||||||
|
// Simple rule:
|
||||||
|
// - If critter is hungry -> feeding action
|
||||||
|
// - Else -> pet action
|
||||||
|
if (needs != null && needs.IsHungry)
|
||||||
|
{
|
||||||
|
Feed();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Pet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Feed()
|
||||||
|
{
|
||||||
|
if (needs == null) return;
|
||||||
|
|
||||||
|
// Feed: lower hunger instantly by some amount.
|
||||||
|
needs.SetHunger(Mathf.Clamp01(needs.Hunger - feedAmount));
|
||||||
|
trust = Mathf.Clamp01(trust + trustIncrease * 0.5f);
|
||||||
|
|
||||||
|
// Small "happy" animation cue (bob up slightly).
|
||||||
|
StartCoroutine(BobAnimation(Color.green));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Pet()
|
||||||
|
{
|
||||||
|
trust = Mathf.Clamp01(trust + trustIncrease);
|
||||||
|
|
||||||
|
// Slight bob to show reaction.
|
||||||
|
StartCoroutine(BobAnimation(Color.cyan));
|
||||||
|
}
|
||||||
|
|
||||||
|
private System.Collections.IEnumerator BobAnimation(Color color)
|
||||||
|
{
|
||||||
|
Renderer rend = GetComponentInChildren<Renderer>();
|
||||||
|
Color original = rend != null ? rend.material.color : Color.white;
|
||||||
|
|
||||||
|
float t = 0f;
|
||||||
|
Vector3 basePos = transform.position;
|
||||||
|
|
||||||
|
while (t < 0.4f)
|
||||||
|
{
|
||||||
|
t += Time.deltaTime;
|
||||||
|
float bob = Mathf.Sin(t * Mathf.PI * 4f) * 0.05f;
|
||||||
|
transform.position = basePos + Vector3.up * bob;
|
||||||
|
|
||||||
|
if (rend != null)
|
||||||
|
{
|
||||||
|
rend.material.color = Color.Lerp(original, color, t / 0.4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform.position = basePos;
|
||||||
|
if (rend != null)
|
||||||
|
rend.material.color = original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user