373 lines
10 KiB
C#
373 lines
10 KiB
C#
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.Video;
|
|
|
|
namespace Unheimlich.Features.Hallucination
|
|
{
|
|
/// <summary>
|
|
/// Plays a short hallucination video using an eye blink transition on the HUD canvas.
|
|
/// Movement is handled via LeanTween.
|
|
/// Call Play() to trigger the hallucination.
|
|
/// </summary>
|
|
public class HallucinationFlash : MonoBehaviour
|
|
{
|
|
[Header("Target")]
|
|
[SerializeField] private VideoPlayer _videoPlayer;
|
|
[SerializeField] private RawImage _hallucinationVideo;
|
|
[SerializeField] private Image _topEyelid;
|
|
[SerializeField] private Image _bottomEyelid;
|
|
|
|
[Header("Timings")]
|
|
[SerializeField, Min(0f)] private float _closeDuration = 0.1f;
|
|
[SerializeField, Min(0f)] private float _holdClosed = 0.05f;
|
|
[SerializeField, Min(0f)] private float _openDuration = 0.15f;
|
|
[SerializeField, Min(0f)] private float _visibleHold = 0.5f;
|
|
|
|
[Header("Video Timing")]
|
|
[SerializeField, Range(0f, 1f)] private float _showVideoAtCloseProgress = 0.25f;
|
|
|
|
[Header("Video Playback")]
|
|
[SerializeField, Min(0f)] private float _playbackSpeed = 1f;
|
|
[SerializeField] private bool _skipOnDrop = true;
|
|
[SerializeField] private bool _logVideoDebug = false;
|
|
|
|
[Header("Positions")]
|
|
[SerializeField] private float _openOffset = 600f;
|
|
[SerializeField] private float _closedOffset = 0f;
|
|
|
|
private int _topTweenId = -1;
|
|
private int _bottomTweenId = -1;
|
|
private int _delayTweenId = -1;
|
|
private int _videoTweenId = -1;
|
|
|
|
private bool _isWarmedUp;
|
|
private bool _playWhenReady;
|
|
private bool _videoShown;
|
|
|
|
private void Awake()
|
|
{
|
|
SetHallucinationVisible(false);
|
|
SetEyelidsOpen();
|
|
|
|
ConfigureVideoPlayer();
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
StartCoroutine(WarmUpVideo());
|
|
}
|
|
|
|
public void Play()
|
|
{
|
|
if (!_isWarmedUp)
|
|
{
|
|
_playWhenReady = true;
|
|
return;
|
|
}
|
|
|
|
PlayPreparedSequence();
|
|
}
|
|
|
|
private void ConfigureVideoPlayer()
|
|
{
|
|
if (_videoPlayer == null)
|
|
return;
|
|
|
|
_videoPlayer.playOnAwake = false;
|
|
_videoPlayer.isLooping = false;
|
|
_videoPlayer.waitForFirstFrame = true;
|
|
_videoPlayer.skipOnDrop = _skipOnDrop;
|
|
_videoPlayer.playbackSpeed = _playbackSpeed;
|
|
|
|
if (_logVideoDebug)
|
|
{
|
|
Debug.Log(
|
|
"[HallucinationFlash] ConfigureVideoPlayer"
|
|
+ " | speed=" + _videoPlayer.playbackSpeed
|
|
+ " | canSetSpeed=" + _videoPlayer.canSetPlaybackSpeed
|
|
+ " | skipOnDrop=" + _videoPlayer.skipOnDrop
|
|
+ " | updateMode=" + _videoPlayer.timeUpdateMode
|
|
);
|
|
}
|
|
}
|
|
|
|
private IEnumerator WarmUpVideo()
|
|
{
|
|
if (_videoPlayer == null)
|
|
yield break;
|
|
|
|
SetHallucinationVisible(false);
|
|
|
|
ConfigureVideoPlayer();
|
|
|
|
_videoPlayer.Prepare();
|
|
|
|
while (!_videoPlayer.isPrepared)
|
|
yield return null;
|
|
|
|
ConfigureVideoPlayer();
|
|
|
|
// Play once while hidden to force Unity to decode the first frames.
|
|
_videoPlayer.Play();
|
|
|
|
while (!_videoPlayer.isPlaying)
|
|
yield return null;
|
|
|
|
// Wait a few frames so the RenderTexture actually receives video data.
|
|
yield return null;
|
|
yield return null;
|
|
yield return null;
|
|
|
|
_videoPlayer.Pause();
|
|
_videoPlayer.time = 0.0;
|
|
ConfigureVideoPlayer();
|
|
|
|
// Wait one frame after seeking back to the beginning.
|
|
yield return null;
|
|
|
|
_isWarmedUp = true;
|
|
|
|
if (!_playWhenReady)
|
|
yield break;
|
|
|
|
_playWhenReady = false;
|
|
PlayPreparedSequence();
|
|
}
|
|
|
|
private void PlayPreparedSequence()
|
|
{
|
|
CancelCurrent();
|
|
|
|
ResetVideoToStart();
|
|
SetHallucinationVisible(false);
|
|
SetEyelidsOpen();
|
|
|
|
_videoShown = false;
|
|
|
|
CloseEyes(() =>
|
|
{
|
|
if (!_videoShown)
|
|
ShowVideo();
|
|
|
|
HoldClosed(() =>
|
|
{
|
|
OpenEyes(() =>
|
|
{
|
|
HoldVisible(() =>
|
|
{
|
|
CloseEyes(() =>
|
|
{
|
|
HideVideo();
|
|
|
|
HoldClosed(() =>
|
|
{
|
|
OpenEyes(() =>
|
|
{
|
|
ClearTweenIds();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
ShowVideoDuringClose();
|
|
}
|
|
|
|
private void CloseEyes(System.Action onComplete)
|
|
{
|
|
RectTransform top = _topEyelid.rectTransform;
|
|
RectTransform bottom = _bottomEyelid.rectTransform;
|
|
|
|
_topTweenId = LeanTween.moveY(top, _closedOffset, _closeDuration)
|
|
.setEase(LeanTweenType.easeOutQuad)
|
|
.setOnComplete(() =>
|
|
{
|
|
_topTweenId = -1;
|
|
onComplete?.Invoke();
|
|
})
|
|
.id;
|
|
|
|
_bottomTweenId = LeanTween.moveY(bottom, -_closedOffset, _closeDuration)
|
|
.setEase(LeanTweenType.easeOutQuad)
|
|
.setOnComplete(() =>
|
|
{
|
|
_bottomTweenId = -1;
|
|
})
|
|
.id;
|
|
}
|
|
|
|
private void OpenEyes(System.Action onComplete)
|
|
{
|
|
RectTransform top = _topEyelid.rectTransform;
|
|
RectTransform bottom = _bottomEyelid.rectTransform;
|
|
|
|
_topTweenId = LeanTween.moveY(top, _openOffset, _openDuration)
|
|
.setEase(LeanTweenType.easeInQuad)
|
|
.setOnComplete(() =>
|
|
{
|
|
_topTweenId = -1;
|
|
onComplete?.Invoke();
|
|
})
|
|
.id;
|
|
|
|
_bottomTweenId = LeanTween.moveY(bottom, -_openOffset, _openDuration)
|
|
.setEase(LeanTweenType.easeInQuad)
|
|
.setOnComplete(() =>
|
|
{
|
|
_bottomTweenId = -1;
|
|
})
|
|
.id;
|
|
}
|
|
|
|
private void ShowVideoDuringClose()
|
|
{
|
|
float delay = _closeDuration * _showVideoAtCloseProgress;
|
|
|
|
_videoTweenId = LeanTween.delayedCall(gameObject, delay, () =>
|
|
{
|
|
_videoTweenId = -1;
|
|
ShowVideo();
|
|
}).id;
|
|
}
|
|
|
|
private void ShowVideo()
|
|
{
|
|
if (_videoShown)
|
|
return;
|
|
|
|
_videoShown = true;
|
|
|
|
SetHallucinationVisible(true);
|
|
PlayVideo();
|
|
}
|
|
|
|
private void HideVideo()
|
|
{
|
|
PauseVideoAtStart();
|
|
SetHallucinationVisible(false);
|
|
}
|
|
|
|
private void HoldClosed(System.Action onComplete)
|
|
{
|
|
_delayTweenId = LeanTween.delayedCall(gameObject, _holdClosed, () =>
|
|
{
|
|
_delayTweenId = -1;
|
|
onComplete?.Invoke();
|
|
}).id;
|
|
}
|
|
|
|
private void HoldVisible(System.Action onComplete)
|
|
{
|
|
_delayTweenId = LeanTween.delayedCall(gameObject, _visibleHold, () =>
|
|
{
|
|
_delayTweenId = -1;
|
|
onComplete?.Invoke();
|
|
}).id;
|
|
}
|
|
|
|
private void PlayVideo()
|
|
{
|
|
if (_videoPlayer == null)
|
|
return;
|
|
|
|
ConfigureVideoPlayer();
|
|
|
|
// Do not Stop() here. Stop() can reintroduce decoding latency.
|
|
_videoPlayer.Play();
|
|
|
|
if (_logVideoDebug)
|
|
{
|
|
Debug.Log(
|
|
"[HallucinationFlash] PlayVideo"
|
|
+ " | speed=" + _videoPlayer.playbackSpeed
|
|
+ " | canSetSpeed=" + _videoPlayer.canSetPlaybackSpeed
|
|
+ " | time=" + _videoPlayer.time
|
|
+ " | frame=" + _videoPlayer.frame
|
|
);
|
|
}
|
|
}
|
|
|
|
private void ResetVideoToStart()
|
|
{
|
|
if (_videoPlayer == null)
|
|
return;
|
|
|
|
_videoPlayer.Pause();
|
|
_videoPlayer.time = 0.0;
|
|
|
|
ConfigureVideoPlayer();
|
|
}
|
|
|
|
private void PauseVideoAtStart()
|
|
{
|
|
if (_videoPlayer == null)
|
|
return;
|
|
|
|
_videoPlayer.Pause();
|
|
_videoPlayer.time = 0.0;
|
|
|
|
ConfigureVideoPlayer();
|
|
}
|
|
|
|
private void SetHallucinationVisible(bool visible)
|
|
{
|
|
if (_hallucinationVideo == null)
|
|
return;
|
|
|
|
_hallucinationVideo.enabled = visible;
|
|
}
|
|
|
|
private void SetEyelidsOpen()
|
|
{
|
|
if (_topEyelid == null || _bottomEyelid == null)
|
|
return;
|
|
|
|
RectTransform top = _topEyelid.rectTransform;
|
|
RectTransform bottom = _bottomEyelid.rectTransform;
|
|
|
|
top.anchoredPosition = new Vector2(top.anchoredPosition.x, _openOffset);
|
|
bottom.anchoredPosition = new Vector2(bottom.anchoredPosition.x, -_openOffset);
|
|
}
|
|
|
|
private void CancelCurrent()
|
|
{
|
|
_playWhenReady = false;
|
|
_videoShown = false;
|
|
|
|
if (_topTweenId != -1)
|
|
{
|
|
LeanTween.cancel(_topTweenId);
|
|
_topTweenId = -1;
|
|
}
|
|
|
|
if (_bottomTweenId != -1)
|
|
{
|
|
LeanTween.cancel(_bottomTweenId);
|
|
_bottomTweenId = -1;
|
|
}
|
|
|
|
if (_delayTweenId != -1)
|
|
{
|
|
LeanTween.cancel(_delayTweenId);
|
|
_delayTweenId = -1;
|
|
}
|
|
|
|
if (_videoTweenId != -1)
|
|
{
|
|
LeanTween.cancel(_videoTweenId);
|
|
_videoTweenId = -1;
|
|
}
|
|
}
|
|
|
|
private void ClearTweenIds()
|
|
{
|
|
_topTweenId = -1;
|
|
_bottomTweenId = -1;
|
|
_delayTweenId = -1;
|
|
_videoTweenId = -1;
|
|
}
|
|
}
|
|
} |