386 lines
16 KiB
C#
386 lines
16 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using PixelCrushers;
|
|
using PixelCrushers.DialogueSystem;
|
|
using Sirenix.OdinInspector;
|
|
using Sirenix.Utilities;
|
|
using UnityEngine;
|
|
using UnityEngine.Audio;
|
|
using Random = UnityEngine.Random;
|
|
|
|
namespace Beyond // Ensure this namespace matches your project structure
|
|
{
|
|
// Forward declare Player if it's in a different namespace or assembly,
|
|
// or ensure 'using Beyond.PlayerNamespace;' if applicable.
|
|
// Assuming Player class might be in the same 'Beyond' namespace for simplicity here.
|
|
// If Player class is defined elsewhere, you might need:
|
|
// using YourPlayerNamespace;
|
|
|
|
public class BarkManager : Saver
|
|
{
|
|
// --- Inner Classes (Bark, BarkEntry, SaveData) remain the same ---
|
|
[Serializable]
|
|
public class Bark
|
|
{
|
|
[Tooltip("Conversation to get bark content from.")]
|
|
[ConversationPopup(false)]
|
|
public string barkConversation = string.Empty;
|
|
[Tooltip("Direct text to bark if Conversation is empty.")]
|
|
public string barkText;
|
|
[Tooltip("Audio clip for the bark.")]
|
|
public AudioClip clip;
|
|
}
|
|
|
|
[Serializable]
|
|
public class BarkEntry
|
|
{
|
|
[Tooltip("If true, this entire entry can only be triggered once via PlayBark(int) or PlayBark(string). BarkPlayer ignores this for sequential playback.")]
|
|
public bool playOnce = true;
|
|
[Tooltip("Delay before the bark starts playing (applies to the first bark triggered externally or random barks).")]
|
|
public float delayToStart = 0f;
|
|
[Tooltip("Optional custom audio mixer group for barks in this entry.")]
|
|
public AudioMixerGroup customOutput;
|
|
[Tooltip("The list of barks for this entry.")]
|
|
public Bark[] barks;
|
|
}
|
|
|
|
[ListDrawerSettings(DraggableItems = true, Expanded = false, ShowIndexLabels = true, ShowPaging = false, ShowItemCount = true, HideRemoveButton = true)]
|
|
public BarkEntry[] m_barks;
|
|
|
|
[Serializable]
|
|
public class SaveData
|
|
{
|
|
public bool[] wasEntryPlayed;
|
|
}
|
|
public SaveData m_saveData = new SaveData();
|
|
|
|
private Queue<QueuedBarkRequest> m_barkQueue = new Queue<QueuedBarkRequest>();
|
|
private struct QueuedBarkRequest
|
|
{
|
|
public int entryIndex;
|
|
public Transform target; // Can be null if default (Player) is intended
|
|
}
|
|
|
|
public AudioSource m_audioSource;
|
|
private Dictionary<string, int> m_conversationToEntry = new Dictionary<string, int>();
|
|
|
|
private static BarkManager s_instance;
|
|
public static BarkManager Instance => s_instance;
|
|
|
|
public bool IsPlaying => m_audioSource != null && m_audioSource.isPlaying;
|
|
|
|
// --- Core Methods ---
|
|
|
|
public override void Awake()
|
|
{
|
|
base.Awake();
|
|
if (s_instance != null && s_instance != this)
|
|
{
|
|
Debug.LogError("BarkManager instance already exists! Destroying this duplicate.", this);
|
|
Destroy(gameObject);
|
|
}
|
|
else
|
|
{
|
|
s_instance = this;
|
|
// Optional: DontDestroyOnLoad(gameObject);
|
|
}
|
|
SetupAudioSource();
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
SetupAudioSource();
|
|
InitializeSaveData();
|
|
BuildConversationLookup();
|
|
}
|
|
|
|
private void SetupAudioSource()
|
|
{
|
|
if (m_audioSource == null) m_audioSource = GetComponent<AudioSource>();
|
|
if (m_audioSource == null)
|
|
{
|
|
m_audioSource = gameObject.AddComponent<AudioSource>();
|
|
m_audioSource.playOnAwake = false;
|
|
m_audioSource.spatialBlend = 1.0f;
|
|
}
|
|
}
|
|
|
|
private void InitializeSaveData()
|
|
{
|
|
if (m_saveData.wasEntryPlayed == null || m_saveData.wasEntryPlayed.Length != m_barks.Length)
|
|
{
|
|
m_saveData.wasEntryPlayed = new bool[m_barks.Length];
|
|
}
|
|
}
|
|
|
|
private void BuildConversationLookup()
|
|
{
|
|
m_conversationToEntry.Clear();
|
|
for (int i = 0; i < m_barks.Length; i++)
|
|
{
|
|
var entry = m_barks[i];
|
|
if (entry.barks != null)
|
|
{
|
|
foreach (var bark in entry.barks)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(bark.barkConversation))
|
|
{
|
|
if (!m_conversationToEntry.ContainsKey(bark.barkConversation))
|
|
{
|
|
m_conversationToEntry[bark.barkConversation] = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
public void PlayBark(int num) // <<< METHOD RESTORED HERE
|
|
{
|
|
// This simply queues the request with a null target.
|
|
// The Update loop will handle dequeuing, checking playOnce,
|
|
// resolving the null target to Player.Instance, and playing.
|
|
PlayBarkFromQueue(num, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Plays a bark from the specified entry. Uses Player.Instance.transform for text barks if barkTarget is null.
|
|
/// </summary>
|
|
/// <param name="entryIndex">Index of the BarkEntry.</param>
|
|
/// <param name="barkTarget">Transform for positioning Dialogue Manager barks. If null, attempts to use Player.Instance.transform.</param>
|
|
/// <param name="specificBarkIndex">Index of the specific bark within the entry. -1 for random.</param>
|
|
/// <returns>The AudioClip that will be played (or null if none/delayed).</returns>
|
|
public AudioClip PlayBark(int entryIndex, Transform barkTarget = null, int specificBarkIndex = -1)
|
|
{
|
|
if (entryIndex < 0 || entryIndex >= m_barks.Length)
|
|
{
|
|
Debug.LogError($"BarkManager: Invalid entryIndex {entryIndex}.", this);
|
|
return null;
|
|
}
|
|
|
|
// --- Determine the target for text barks ---
|
|
Transform targetForText = barkTarget;
|
|
if (targetForText == null)
|
|
{
|
|
if (Player.Instance != null) // Check if Player singleton exists
|
|
{
|
|
targetForText = Player.Instance.transform;
|
|
}
|
|
else
|
|
{
|
|
// Player.Instance is null, text bark won't have a specific target unless one was provided.
|
|
// Audio will still play. Log warning maybe?
|
|
// Debug.LogWarning($"BarkManager: barkTarget is null and Player.Instance is null. Text bark for entry {entryIndex} will not be shown.", this);
|
|
}
|
|
}
|
|
// --- End Target Determination ---
|
|
|
|
var barkEntry = m_barks[entryIndex];
|
|
|
|
// Check playOnce only for random barks
|
|
if (specificBarkIndex == -1 && barkEntry.playOnce && m_saveData.wasEntryPlayed != null && m_saveData.wasEntryPlayed[entryIndex])
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (barkEntry.barks == null || barkEntry.barks.Length == 0)
|
|
{
|
|
Debug.LogWarning($"BarkManager: BarkEntry {entryIndex} has no barks defined.", this);
|
|
return null;
|
|
}
|
|
|
|
AudioClip clipToPlay = null;
|
|
if (specificBarkIndex != -1) // Specific bark (e.g., BarkPlayer) - play immediately
|
|
{
|
|
// Pass the determined targetForText
|
|
clipToPlay = PlayBarkImmediately(barkEntry, targetForText, specificBarkIndex);
|
|
}
|
|
else // Random bark
|
|
{
|
|
if (barkEntry.delayToStart > 0f && !IsPlaying)
|
|
{
|
|
// Pass the determined targetForText to the coroutine
|
|
StartCoroutine(PlayBarkWithDelayCoroutine(barkEntry, targetForText, specificBarkIndex));
|
|
}
|
|
else
|
|
{
|
|
// Pass the determined targetForText
|
|
clipToPlay = PlayBarkImmediately(barkEntry, targetForText, specificBarkIndex);
|
|
}
|
|
|
|
// Mark entry as played if playOnce (only for random)
|
|
if (barkEntry.playOnce && m_saveData.wasEntryPlayed != null)
|
|
{
|
|
m_saveData.wasEntryPlayed[entryIndex] = true;
|
|
}
|
|
}
|
|
return clipToPlay;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enqueues a request to play a random bark from the entry associated with the conversation.
|
|
/// Uses Player.Instance.transform for text barks if barkTarget is null.
|
|
/// </summary>
|
|
public void PlayBark(string conversation, Transform barkTarget = null)
|
|
{
|
|
if (m_conversationToEntry.TryGetValue(conversation, out int entryIndex))
|
|
{
|
|
// Determine target *before* queuing, store null if Player isn't available/needed
|
|
Transform targetForQueue = barkTarget;
|
|
if (targetForQueue == null && Player.Instance != null)
|
|
{
|
|
targetForQueue = Player.Instance.transform;
|
|
}
|
|
// Note: If Player.Instance becomes available *later* when the queue processes,
|
|
// the Update loop logic will handle checking for it again.
|
|
// So, we can actually queue null and let Update resolve it.
|
|
m_barkQueue.Enqueue(new QueuedBarkRequest { entryIndex = entryIndex, target = barkTarget }); // Queue the original target (null if default)
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"BarkManager: Conversation '{conversation}' not found in any BarkEntry.", this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enqueues a request to play a random bark from the specified entry index.
|
|
/// Uses Player.Instance.transform for text barks if barkTarget is null.
|
|
/// </summary>
|
|
public void PlayBarkFromQueue(int entryIndex, Transform barkTarget = null)
|
|
{
|
|
// Queue the original target (null if default intended). Update loop will resolve Player.Instance if needed.
|
|
m_barkQueue.Enqueue(new QueuedBarkRequest { entryIndex = entryIndex, target = barkTarget });
|
|
}
|
|
|
|
|
|
private IEnumerator PlayBarkWithDelayCoroutine(BarkEntry barkEntry, Transform targetForText, int specificBarkIndex)
|
|
{
|
|
// Note: Player.Instance could become valid/invalid during this delay.
|
|
// Re-check target just before playing? Or rely on the target passed in?
|
|
// Let's stick with the target determined when the coroutine was started for consistency.
|
|
yield return new WaitForSecondsRealtime(barkEntry.delayToStart);
|
|
PlayBarkImmediately(barkEntry, targetForText, specificBarkIndex);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal method to immediately play audio and show text bark. Uses the provided targetForText.
|
|
/// </summary>
|
|
private AudioClip PlayBarkImmediately(BarkEntry barkEntry, Transform targetForText, int specificBarkIndex = -1)
|
|
{
|
|
if (barkEntry.barks == null || barkEntry.barks.Length == 0) return null;
|
|
|
|
Bark barkToPlay = null;
|
|
int chosenIndex = specificBarkIndex;
|
|
|
|
if (chosenIndex < 0 || chosenIndex >= barkEntry.barks.Length)
|
|
{
|
|
if (barkEntry.barks.Length > 0) chosenIndex = Random.Range(0, barkEntry.barks.Length);
|
|
else return null;
|
|
}
|
|
barkToPlay = barkEntry.barks[chosenIndex];
|
|
|
|
// --- Audio Playback ---
|
|
AudioClip clipToPlay = null;
|
|
if (barkToPlay.clip != null)
|
|
{
|
|
if (m_audioSource == null) Debug.LogError("BarkManager: m_audioSource is null! Cannot play audio.", this);
|
|
else
|
|
{
|
|
m_audioSource.outputAudioMixerGroup = barkEntry.customOutput != null ? barkEntry.customOutput : null;
|
|
m_audioSource.PlayOneShot(barkToPlay.clip);
|
|
clipToPlay = barkToPlay.clip;
|
|
}
|
|
}
|
|
|
|
// --- Text Bark ---
|
|
// Use the resolved targetForText passed into this method
|
|
if (targetForText != null)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(barkToPlay.barkConversation))
|
|
{
|
|
DialogueManager.Bark(barkToPlay.barkConversation, targetForText);
|
|
}
|
|
else if (!string.IsNullOrEmpty(barkToPlay.barkText))
|
|
{
|
|
DialogueManager.BarkString(barkToPlay.barkText, targetForText);
|
|
}
|
|
}
|
|
// If targetForText is null here, no text bark is shown.
|
|
|
|
return clipToPlay;
|
|
}
|
|
|
|
// --- Utility & Save/Load --- (Remain the same)
|
|
|
|
public bool CanBarkBeStillPlayed(int entryIndex)
|
|
{
|
|
if (entryIndex < 0 || entryIndex >= m_barks.Length) return false;
|
|
if (m_saveData.wasEntryPlayed == null || entryIndex >= m_saveData.wasEntryPlayed.Length) return true;
|
|
return !m_barks[entryIndex].playOnce || !m_saveData.wasEntryPlayed[entryIndex];
|
|
}
|
|
|
|
public int GetBarkCountInEntry(int entryIndex)
|
|
{
|
|
if (entryIndex >= 0 && entryIndex < m_barks.Length && m_barks[entryIndex].barks != null)
|
|
{
|
|
return m_barks[entryIndex].barks.Length;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public override string RecordData()
|
|
{
|
|
if (m_saveData.wasEntryPlayed == null || m_saveData.wasEntryPlayed.Length != m_barks.Length)
|
|
{
|
|
bool[] oldData = m_saveData.wasEntryPlayed;
|
|
m_saveData.wasEntryPlayed = new bool[m_barks.Length];
|
|
if (oldData != null) Array.Copy(oldData, m_saveData.wasEntryPlayed, Math.Min(oldData.Length, m_barks.Length));
|
|
}
|
|
return SaveSystem.Serialize(m_saveData);
|
|
}
|
|
|
|
public override void ApplyData(string s)
|
|
{
|
|
if (string.IsNullOrEmpty(s)) return;
|
|
var loadedData = SaveSystem.Deserialize<SaveData>(s);
|
|
if (loadedData != null)
|
|
{
|
|
if (loadedData.wasEntryPlayed != null && loadedData.wasEntryPlayed.Length == m_barks.Length)
|
|
{
|
|
m_saveData = loadedData;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"BarkManager: Loaded 'wasEntryPlayed' data length mismatch. Discarding loaded state.", this);
|
|
InitializeSaveData();
|
|
}
|
|
}
|
|
else Debug.LogError("BarkManager: Failed to deserialize save data.", this);
|
|
}
|
|
|
|
|
|
private void Update()
|
|
{
|
|
// Process the queue for externally triggered RANDOM barks
|
|
if (m_barkQueue.Count > 0 && !IsPlaying)
|
|
{
|
|
QueuedBarkRequest request = m_barkQueue.Dequeue();
|
|
|
|
// --- Resolve target just before playing from queue ---
|
|
Transform targetForText = request.target; // Use the target stored in the queue request first
|
|
if (targetForText == null) // If no specific target was queued...
|
|
{
|
|
if (Player.Instance != null) // Check for Player *now*
|
|
{
|
|
targetForText = Player.Instance.transform;
|
|
}
|
|
// else: targetForText remains null, text bark won't show
|
|
}
|
|
// --- End Target Resolution ---
|
|
|
|
// Call the main PlayBark method, passing the resolved target and -1 for random
|
|
PlayBark(request.entryIndex, targetForText, -1);
|
|
}
|
|
}
|
|
}
|
|
} |