322 lines
14 KiB
C#
322 lines
14 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using PixelCrushers; // For Saver
|
|
using UnityEngine;
|
|
using Random = UnityEngine.Random;
|
|
|
|
namespace Beyond
|
|
{
|
|
[RequireComponent(typeof(Collider))]
|
|
public class BarkPlayer : Saver
|
|
{
|
|
[Header("Bark Configuration")]
|
|
[Tooltip("The index of the BarkEntry in BarkManager's m_barks list.")]
|
|
public int barkManagerEntryIndex = 0;
|
|
|
|
[Tooltip("Delay in seconds after one bark's audio finishes before starting the next.")]
|
|
public float delayBetweenBarks = 0.5f;
|
|
|
|
[Tooltip("Play barks within the entry in a random order (shuffled once on start/load).")]
|
|
public bool shuffleOrder = false;
|
|
|
|
[Tooltip("Keep playing barks (looping the sequence if necessary) as long as an object on a triggering layer stays inside.")]
|
|
public bool loopWhileInside = true;
|
|
|
|
[Header("Trigger Settings")]
|
|
[Tooltip("Layers that can activate this bark trigger.")]
|
|
public LayerMask triggeringLayers; // User configures this in Inspector
|
|
|
|
[Header("State (Read Only)")]
|
|
[SerializeField, ReadOnly(true)]
|
|
private int nextBarkSequenceIndex = 0;
|
|
[SerializeField, ReadOnly(true)]
|
|
private bool isTriggeringObjectInside = false; // Renamed for clarity
|
|
|
|
// --- Internal Vars ---
|
|
// Removed PLAYER_LAYER_NAME constant
|
|
private List<int> playbackOrder;
|
|
private bool isInitialized = false;
|
|
private Collider triggerCollider;
|
|
private BarkManager barkManager;
|
|
private Coroutine currentPlaybackCoroutine = null;
|
|
private Transform currentTriggererTransform = null; // Store the transform of the object inside
|
|
|
|
|
|
// --- Save Data --- (Remains the same)
|
|
[System.Serializable]
|
|
public class BarkPlayerData { /* ... same as before ... */
|
|
public int savedNextBarkSequenceIndex;
|
|
public List<int> savedPlaybackOrder;
|
|
public bool wasShuffled;
|
|
}
|
|
private BarkPlayerData m_saveData = new BarkPlayerData();
|
|
|
|
// --- Methods ---
|
|
|
|
public override void Awake()
|
|
{
|
|
base.Awake();
|
|
|
|
triggerCollider = GetComponent<Collider>();
|
|
if (!triggerCollider.isTrigger)
|
|
{
|
|
Debug.LogWarning($"BarkPlayer on {gameObject.name}: Collider is not set to 'Is Trigger'. Forcing it.", this);
|
|
triggerCollider.isTrigger = true;
|
|
}
|
|
|
|
// --- Set Default LayerMask if Unassigned ---
|
|
if (triggeringLayers.value == 0) // LayerMask is empty/unassigned in inspector
|
|
{
|
|
int playerLayer = LayerMask.NameToLayer("Player");
|
|
if (playerLayer != -1)
|
|
{
|
|
triggeringLayers = LayerMask.GetMask("Player"); // Default to Player layer
|
|
Debug.LogWarning($"BarkPlayer on {gameObject.name}: Triggering Layers not set in inspector, defaulting to 'Player' layer.", this);
|
|
}
|
|
else
|
|
{
|
|
// Layer "Player" doesn't exist, and no layers were assigned. Log an error.
|
|
Debug.LogError($"BarkPlayer on {gameObject.name}: Triggering Layers is not set in the inspector, and the default 'Player' layer was not found. This trigger will likely not activate.", this);
|
|
// Leave triggeringLayers as 0 (Nothing)
|
|
}
|
|
}
|
|
// --- End LayerMask Default ---
|
|
|
|
|
|
// Initialize save data defaults
|
|
m_saveData.savedNextBarkSequenceIndex = 0;
|
|
m_saveData.savedPlaybackOrder = null;
|
|
m_saveData.wasShuffled = shuffleOrder;
|
|
nextBarkSequenceIndex = 0;
|
|
isTriggeringObjectInside = false;
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
barkManager = BarkManager.Instance;
|
|
if (barkManager == null)
|
|
{
|
|
Debug.LogError($"BarkPlayer on {gameObject.name}: Could not find BarkManager instance!", this);
|
|
enabled = false;
|
|
return;
|
|
}
|
|
|
|
InitializePlaybackOrder();
|
|
isInitialized = true;
|
|
}
|
|
|
|
// InitializePlaybackOrder() remains the same as before
|
|
|
|
private void InitializePlaybackOrder()
|
|
{
|
|
if (barkManager == null) return;
|
|
|
|
int barkCount = barkManager.GetBarkCountInEntry(barkManagerEntryIndex);
|
|
if (barkCount <= 0)
|
|
{
|
|
Debug.LogWarning($"BarkPlayer on {gameObject.name}: BarkEntry {barkManagerEntryIndex} has no barks. Disabling.", this);
|
|
playbackOrder = new List<int>();
|
|
enabled = false;
|
|
return;
|
|
}
|
|
|
|
bool useLoadedOrder = m_saveData.savedPlaybackOrder != null &&
|
|
m_saveData.wasShuffled == shuffleOrder &&
|
|
m_saveData.savedPlaybackOrder.Count == barkCount;
|
|
|
|
if (useLoadedOrder)
|
|
{
|
|
playbackOrder = new List<int>(m_saveData.savedPlaybackOrder);
|
|
}
|
|
else
|
|
{
|
|
playbackOrder = Enumerable.Range(0, barkCount).ToList();
|
|
if (shuffleOrder)
|
|
{
|
|
for (int i = playbackOrder.Count - 1; i > 0; i--) {
|
|
int j = Random.Range(0, i + 1);
|
|
(playbackOrder[i], playbackOrder[j]) = (playbackOrder[j], playbackOrder[i]);
|
|
}
|
|
m_saveData.wasShuffled = true;
|
|
} else {
|
|
m_saveData.wasShuffled = false;
|
|
}
|
|
m_saveData.savedPlaybackOrder = new List<int>(playbackOrder);
|
|
}
|
|
|
|
nextBarkSequenceIndex = Mathf.Clamp(m_saveData.savedNextBarkSequenceIndex, 0, playbackOrder.Count);
|
|
|
|
if (nextBarkSequenceIndex >= playbackOrder.Count && !loopWhileInside)
|
|
{
|
|
Debug.Log($"BarkPlayer on {gameObject.name}: Sequence for entry {barkManagerEntryIndex} already completed (and not looping).", this);
|
|
}
|
|
}
|
|
|
|
|
|
private void OnTriggerEnter(Collider other)
|
|
{
|
|
if (!isInitialized) return;
|
|
|
|
// --- Check Layer Mask ---
|
|
int otherLayer = other.gameObject.layer;
|
|
// Check if the entering object's layer is in our mask
|
|
if ((triggeringLayers.value & (1 << otherLayer)) == 0)
|
|
{
|
|
// Layer not in mask, ignore this trigger event
|
|
return;
|
|
}
|
|
// --- End Layer Check ---
|
|
|
|
// Object on a triggering layer entered
|
|
Debug.Log($"BarkPlayer ({gameObject.name}): Triggering object '{other.name}' entered.", this);
|
|
isTriggeringObjectInside = true;
|
|
currentTriggererTransform = other.transform; // Store the transform for barking
|
|
|
|
// Start playback if not already running
|
|
if (currentPlaybackCoroutine == null)
|
|
{
|
|
currentPlaybackCoroutine = StartCoroutine(ContinuousPlaybackLoop());
|
|
}
|
|
}
|
|
|
|
private void OnTriggerExit(Collider other)
|
|
{
|
|
if (!isInitialized) return;
|
|
|
|
// --- Check Layer Mask ---
|
|
int otherLayer = other.gameObject.layer;
|
|
if ((triggeringLayers.value & (1 << otherLayer)) == 0)
|
|
{
|
|
// Ignore exit events from non-triggering layers
|
|
return;
|
|
}
|
|
// --- End Layer Check ---
|
|
|
|
// Object on a triggering layer exited
|
|
// Important: Only react if the exiting object is the *same one* we stored.
|
|
// This handles cases where multiple triggering objects might enter/exit.
|
|
if (other.transform == currentTriggererTransform)
|
|
{
|
|
Debug.Log($"BarkPlayer ({gameObject.name}): Triggering object '{other.name}' exited.", this);
|
|
isTriggeringObjectInside = false;
|
|
currentTriggererTransform = null; // Clear the stored transform
|
|
|
|
// Stop the playback coroutine
|
|
if (currentPlaybackCoroutine != null)
|
|
{
|
|
StopCoroutine(currentPlaybackCoroutine);
|
|
currentPlaybackCoroutine = null;
|
|
Debug.Log($"BarkPlayer ({gameObject.name}): Stopping playback loop due to exit.", this);
|
|
// Optional: Stop BarkManager audio immediately
|
|
// if (barkManager != null && barkManager.IsPlaying) barkManager.m_audioSource.Stop();
|
|
}
|
|
}
|
|
// Else: Some other object on a triggering layer exited, but the primary one is still inside. Do nothing.
|
|
|
|
}
|
|
|
|
// ContinuousPlaybackLoop now uses the stored currentTriggererTransform
|
|
private IEnumerator ContinuousPlaybackLoop()
|
|
{
|
|
Debug.Log($"BarkPlayer ({gameObject.name}): Starting playback loop.", this);
|
|
|
|
// Ensure we have a target transform before proceeding
|
|
while (currentTriggererTransform == null && isTriggeringObjectInside)
|
|
{
|
|
Debug.LogWarning($"BarkPlayer ({gameObject.name}): Waiting for valid triggerer transform.", this);
|
|
yield return null; // Wait a frame if trigger happened but transform wasn't stored yet (unlikely but safe)
|
|
}
|
|
|
|
// Main loop runs while a triggering object is inside AND we have its transform
|
|
while (isTriggeringObjectInside && currentTriggererTransform != null)
|
|
{
|
|
if (playbackOrder == null || playbackOrder.Count == 0)
|
|
{
|
|
Debug.LogWarning($"BarkPlayer ({gameObject.name}): No barks to play in entry {barkManagerEntryIndex}.", this);
|
|
yield break;
|
|
}
|
|
|
|
bool sequenceFinished = nextBarkSequenceIndex >= playbackOrder.Count;
|
|
|
|
if (sequenceFinished)
|
|
{
|
|
if (loopWhileInside)
|
|
{
|
|
Debug.Log($"BarkPlayer ({gameObject.name}): Sequence finished, looping.", this);
|
|
nextBarkSequenceIndex = 0;
|
|
if (shuffleOrder) {
|
|
InitializePlaybackOrder();
|
|
nextBarkSequenceIndex = 0;
|
|
if (playbackOrder.Count == 0) yield break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"BarkPlayer ({gameObject.name}): Sequence finished, not looping. Waiting for exit.", this);
|
|
// Wait until trigger object leaves
|
|
while(isTriggeringObjectInside && currentTriggererTransform != null) { yield return null; }
|
|
yield break; // Exit coroutine
|
|
}
|
|
}
|
|
|
|
// Wait for BarkManager's AudioSource if necessary
|
|
if (barkManager.IsPlaying)
|
|
{
|
|
yield return null;
|
|
continue;
|
|
}
|
|
|
|
// Play the bark using the stored transform
|
|
int barkIndexToPlay = playbackOrder[nextBarkSequenceIndex];
|
|
Debug.Log($"BarkPlayer ({gameObject.name}): Playing bark {nextBarkSequenceIndex} (actual index: {barkIndexToPlay}) targeting '{currentTriggererTransform.name}'.", this);
|
|
|
|
// Pass the stored transform
|
|
AudioClip playedClip = barkManager.PlayBark(barkManagerEntryIndex, currentTriggererTransform, barkIndexToPlay);
|
|
|
|
nextBarkSequenceIndex++;
|
|
|
|
float waitTime = delayBetweenBarks;
|
|
if (playedClip != null) { waitTime += Mathf.Max(0f, playedClip.length); }
|
|
|
|
if (waitTime > 0) { yield return new WaitForSeconds(waitTime); }
|
|
else { yield return null; }
|
|
}
|
|
|
|
Debug.Log($"BarkPlayer ({gameObject.name}): Playback loop finished (Triggering object left or transform lost).", this);
|
|
// Reset coroutine reference *if* it was this instance that finished it
|
|
// (OnTriggerExit might have already cleared it)
|
|
if (currentPlaybackCoroutine != null && !isTriggeringObjectInside) {
|
|
currentPlaybackCoroutine = null;
|
|
}
|
|
}
|
|
|
|
|
|
// --- Save System Integration --- (Remains the same)
|
|
public override string RecordData() { /* ... same as before ... */
|
|
m_saveData.savedNextBarkSequenceIndex = this.nextBarkSequenceIndex;
|
|
m_saveData.savedPlaybackOrder = new List<int>(this.playbackOrder ?? new List<int>());
|
|
m_saveData.wasShuffled = this.shuffleOrder;
|
|
return SaveSystem.Serialize(m_saveData);
|
|
}
|
|
public override void ApplyData(string s) { /* ... same as before ... */
|
|
if (string.IsNullOrEmpty(s)) return;
|
|
var loadedData = SaveSystem.Deserialize<BarkPlayerData>(s);
|
|
if (loadedData != null) { m_saveData = loadedData; }
|
|
else Debug.LogError($"BarkPlayer ({gameObject.name}): Failed to deserialize save data.", this);
|
|
}
|
|
|
|
// --- Gizmos --- (Remain the same, uses isTriggeringObjectInside now)
|
|
private void OnDrawGizmos() {
|
|
Collider col = GetComponent<Collider>();
|
|
if (col != null) {
|
|
Gizmos.color = isTriggeringObjectInside ? new Color(1f, 0.5f, 0.5f, 0.4f) : new Color(0.5f, 1f, 0.5f, 0.3f);
|
|
if (col is BoxCollider box) Gizmos.DrawCube(transform.TransformPoint(box.center), Vector3.Scale(box.size, transform.lossyScale));
|
|
else if (col is SphereCollider sphere) Gizmos.DrawSphere(transform.TransformPoint(sphere.center), sphere.radius * MaxComponent(transform.lossyScale));
|
|
}
|
|
}
|
|
private float MaxComponent(Vector3 v) => Mathf.Max(Mathf.Max(v.x, v.y), v.z);
|
|
}
|
|
} |