Semi-working timelines integration
This commit is contained in:
committed by
Michal Pikulski
parent
c46036dce6
commit
fd611ac27f
241
Assets/Scripts/Interactions/InteractionTimelineAction.cs
Normal file
241
Assets/Scripts/Interactions/InteractionTimelineAction.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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>();
|
||||
|
||||
// Play the timeline
|
||||
playableDirector.Play();
|
||||
|
||||
// Start a timeout coroutine for safety using the mapping's timeout
|
||||
StartCoroutine(TimeoutCoroutine(mapping.timeoutSeconds));
|
||||
|
||||
// Wait for the timeline to complete (or timeout)
|
||||
bool result = await _currentPlaybackTCS.Task;
|
||||
_currentPlaybackTCS = null;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnPlayableDirectorStopped(PlayableDirector director)
|
||||
{
|
||||
if (director == playableDirector && _currentPlaybackTCS != null)
|
||||
{
|
||||
_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user