259 lines
10 KiB
C#
259 lines
10 KiB
C#
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.Playables;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Input;
|
|
|
|
namespace Interactions
|
|
{
|
|
/// <summary>
|
|
/// Component that plays timeline animations in response to interaction events
|
|
/// </summary>
|
|
[RequireComponent(typeof(PlayableDirector))]
|
|
public class InteractionTimelineAction : InteractionActionBase
|
|
{
|
|
[System.Serializable]
|
|
public class TimelineEventMapping
|
|
{
|
|
public InteractionEventType eventType;
|
|
public PlayableAsset[] timelines;
|
|
|
|
[Tooltip("Whether to bind the player character to the track named 'Player'")]
|
|
public bool bindPlayerCharacter = false;
|
|
|
|
[Tooltip("Whether to bind the follower character to the track named 'Pulver'")]
|
|
public bool bindPulverCharacter = false;
|
|
|
|
[Tooltip("Custom track name for player character binding")]
|
|
public string playerTrackName = "Player";
|
|
|
|
[Tooltip("Custom track name for follower character binding")]
|
|
public string pulverTrackName = "Pulver";
|
|
|
|
[Tooltip("Time in seconds before the timeline is automatically completed (safety feature)")]
|
|
public float timeoutSeconds = 30f;
|
|
|
|
[Tooltip("Whether to loop the last timeline in the sequence")]
|
|
public bool loopLast = false;
|
|
|
|
[Tooltip("Whether to loop through all timelines in the sequence")]
|
|
public bool loopAll = false;
|
|
|
|
// Helper property to check if we have valid timelines
|
|
public bool HasValidTimelines => timelines != null && timelines.Length > 0 && timelines[0] != null;
|
|
}
|
|
|
|
[Header("Timeline Configuration")] [SerializeField]
|
|
private PlayableDirector playableDirector;
|
|
|
|
[SerializeField] private TimelineEventMapping[] timelineMappings;
|
|
|
|
private TaskCompletionSource<bool> _currentPlaybackTCS;
|
|
private int _currentTimelineIndex = 0;
|
|
private TimelineEventMapping _currentMapping = null;
|
|
|
|
protected override void Awake()
|
|
{
|
|
base.Awake();
|
|
|
|
if (playableDirector == null)
|
|
{
|
|
playableDirector = GetComponent<PlayableDirector>();
|
|
}
|
|
|
|
if (playableDirector == null)
|
|
{
|
|
Debug.LogError("[InteractionTimelineAction] PlayableDirector component is missing!");
|
|
enabled = false;
|
|
return;
|
|
}
|
|
|
|
// Subscribe to the director's stopped event
|
|
playableDirector.stopped += OnPlayableDirectorStopped;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (playableDirector != null)
|
|
{
|
|
playableDirector.stopped -= OnPlayableDirectorStopped;
|
|
}
|
|
}
|
|
|
|
protected override async Task<bool> ExecuteAsync(InteractionEventType eventType, PlayerTouchController player,
|
|
FollowerController follower)
|
|
{
|
|
// Find the timeline for this event type
|
|
TimelineEventMapping mapping = Array.Find(timelineMappings, m => m.eventType == eventType);
|
|
|
|
if (mapping == null || !mapping.HasValidTimelines)
|
|
{
|
|
// No timeline configured for this event
|
|
return true;
|
|
}
|
|
|
|
_currentMapping = mapping;
|
|
// _currentTimelineIndex = 0;
|
|
|
|
return await PlayTimelineSequence(player, follower);
|
|
}
|
|
|
|
private async Task<bool> PlayTimelineSequence(PlayerTouchController player, FollowerController follower)
|
|
{
|
|
if (_currentMapping == null || !_currentMapping.HasValidTimelines)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
follower.DropHeldItemAt(follower.transform.position);
|
|
|
|
// Play the current timeline in the sequence
|
|
bool result = await PlaySingleTimeline(_currentMapping.timelines[_currentTimelineIndex], _currentMapping, player, follower);
|
|
|
|
// Return false if the playback failed
|
|
if (!result)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Increment the timeline index for next playback
|
|
_currentTimelineIndex++;
|
|
|
|
// Check if we've reached the end of the sequence
|
|
if (_currentTimelineIndex >= _currentMapping.timelines.Length)
|
|
{
|
|
// If loop all is enabled, start over
|
|
if (_currentMapping.loopAll)
|
|
{
|
|
_currentTimelineIndex = 0;
|
|
// Don't continue automatically, wait for next interaction
|
|
return true;
|
|
}
|
|
// If loop last is enabled, replay the last timeline
|
|
else if (_currentMapping.loopLast)
|
|
{
|
|
_currentTimelineIndex = _currentMapping.timelines.Length - 1;
|
|
// Don't continue automatically, wait for next interaction
|
|
return true;
|
|
}
|
|
// Otherwise, we're done with the sequence
|
|
else
|
|
{
|
|
_currentTimelineIndex = 0;
|
|
_currentMapping = null;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// If we have more timelines in the sequence, we're done for now
|
|
// Next interaction will pick up where we left off
|
|
return true;
|
|
}
|
|
|
|
private async Task<bool> PlaySingleTimeline(PlayableAsset timelineAsset, TimelineEventMapping mapping,
|
|
PlayerTouchController player, FollowerController follower)
|
|
{
|
|
if (timelineAsset == null)
|
|
{
|
|
Debug.LogWarning("[InteractionTimelineAction] Timeline asset is null");
|
|
return true; // Return true to continue the interaction flow
|
|
}
|
|
|
|
// Set the timeline asset
|
|
playableDirector.playableAsset = timelineAsset;
|
|
|
|
// Bind characters if needed
|
|
if (mapping.bindPlayerCharacter && player != null)
|
|
{
|
|
try
|
|
{
|
|
var trackOutput = playableDirector.playableAsset.outputs.FirstOrDefault(o => o.streamName == mapping.playerTrackName);
|
|
if (trackOutput.sourceObject != null)
|
|
{
|
|
playableDirector.SetGenericBinding(trackOutput.sourceObject, player.gameObject);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"[InteractionTimelineAction] Could not find track named '{mapping.playerTrackName}' for player binding");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[InteractionTimelineAction] Error binding player to timeline: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
if (mapping.bindPulverCharacter && follower != null)
|
|
{
|
|
try
|
|
{
|
|
var trackOutput = playableDirector.playableAsset.outputs.FirstOrDefault(o => o.streamName == mapping.pulverTrackName);
|
|
if (trackOutput.sourceObject != null)
|
|
{
|
|
playableDirector.SetGenericBinding(trackOutput.sourceObject, follower.gameObject);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"[InteractionTimelineAction] Could not find track named '{mapping.pulverTrackName}' for follower binding");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[InteractionTimelineAction] Error binding follower to timeline: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// Create a task completion source to await the timeline completion
|
|
_currentPlaybackTCS = new TaskCompletionSource<bool>();
|
|
|
|
// Register for the stopped event if not already registered
|
|
playableDirector.stopped -= OnPlayableDirectorStopped;
|
|
playableDirector.stopped += OnPlayableDirectorStopped;
|
|
|
|
// Log the timeline playback
|
|
Debug.Log($"[InteractionTimelineAction] Playing timeline {timelineAsset.name} for event {mapping.eventType}");
|
|
|
|
// Play the timeline
|
|
playableDirector.Play();
|
|
|
|
// Start a timeout coroutine for safety using the mapping's timeout
|
|
StartCoroutine(TimeoutCoroutine(mapping.timeoutSeconds));
|
|
|
|
// Await the timeline completion (will be signaled by the OnPlayableDirectorStopped callback)
|
|
bool result = await _currentPlaybackTCS.Task;
|
|
|
|
// Log completion
|
|
Debug.Log($"[InteractionTimelineAction] Timeline {timelineAsset.name} playback completed with result: {result}");
|
|
|
|
// Clear the task completion source
|
|
_currentPlaybackTCS = null;
|
|
|
|
return result;
|
|
}
|
|
|
|
private void OnPlayableDirectorStopped(PlayableDirector director)
|
|
{
|
|
if (director != playableDirector || _currentPlaybackTCS == null)
|
|
return;
|
|
|
|
Debug.Log($"[InteractionTimelineAction] PlayableDirector stopped. Signaling completion.");
|
|
|
|
// Signal completion when the director stops
|
|
_currentPlaybackTCS.TrySetResult(true);
|
|
}
|
|
|
|
private System.Collections.IEnumerator TimeoutCoroutine(float timeoutDuration)
|
|
{
|
|
yield return new WaitForSeconds(timeoutDuration);
|
|
|
|
// If the TCS still exists after timeout, complete it with failure
|
|
if (_currentPlaybackTCS != null)
|
|
{
|
|
Debug.LogWarning($"[InteractionTimelineAction] Timeline playback timed out after {timeoutDuration} seconds");
|
|
_currentPlaybackTCS.TrySetResult(false);
|
|
}
|
|
}
|
|
}
|
|
}
|