using System.Collections; using UnityEngine; using UnityEngine.Video; using Newtonsoft.Json.Linq; using UHFPS.Tools; using TMPro; namespace UHFPS.Runtime { public class VCRPlayer : MonoBehaviour, IInventorySelector, ISaveable { public enum DisplayText { Play, Pause, Stop, Eject, FastForwad, Rewind, None } public ItemProperty VHSItem; public Animator animator; public AudioSource audioSource; public InteractableItem VHSTape; public Collider insertCollider; public string tapeMaterialProperty = "_MainTex"; public TMP_Text timeText; public TMP_Text stateText; public GameObject displayParent; public GameObject VHSIcon; public CRTMonitor monitor; public Vector2Int outputTextureSize = new Vector2Int(500, 350); public float rewindSpeed; public float fastForwardSpeed; public float windingStartupSpeed; public float timeBeforeWinding; public string insertTrigger = "Insert"; public string ejectTrigger = "Eject"; public string closeCoverTrigger = "Close"; public string fastForwardSymbol = ">"; public string rewindSymbol = "<"; public string playSymbol = "€"; public string stopSymbol = "€"; public string pauseSymbol = "™"; public string ejectSymbol = "®"; public SoundClip tapeInsert; public SoundClip tapeEject; public SoundClip play; public SoundClip stop; public SoundClip rewind; private RenderTexture outputTexture; private Collider tapeCollider; private string tapeCustomData; private double tapeDuration; private double currentTime; private float windingMod; public bool isStarted; public bool isPoweredOn; public bool IsPlaying => isStarted && !isPaused; private bool canInsert; private bool canEject; private bool isPaused; private bool isEnded; private bool isWinding; private void Awake() { tapeCollider = VHSTape.GetComponent(); canInsert = true; canEject = false; outputTexture = new RenderTexture(outputTextureSize.x, outputTextureSize.y, 16); outputTexture.Create(); } private void Start() { SetPower(isPoweredOn); } private void Update() { if (isStarted && !isWinding) { UpdateClipTime(monitor.videoPlayer.time); if ((tapeDuration - currentTime) <= 0.5f && !isEnded) { monitor.videoPlayer.Pause(); SetDisplayText(DisplayText.Stop); monitor.SetDisplayTexture(DisplayTexture.Stop); audioSource.SetSoundClip(stop, play: true); isEnded = true; } } } private void UpdateClipTime(double time, bool setCurrent = true) { int seconds = ((int)time) % 60; int minutes = Mathf.FloorToInt((int)time / 60f); timeText.text = string.Format("SP {0:D2} : {1:D2}", minutes, seconds); if(setCurrent) currentTime = time; } public void PowerOnOff() { if (isWinding) return; SetPower(!isPoweredOn); } public void SetPower(bool power) { if (isWinding) return; if (isPoweredOn = power) { if (canEject) { SetDisplayText(DisplayText.Stop, true); monitor.SetDisplayTexture(DisplayTexture.Stop); } else { SetDisplayText(DisplayText.None, true); monitor.SetDisplayTexture(DisplayTexture.NoTape); } insertCollider.gameObject.SetActive(canInsert); } else { monitor.SetDisplayTexture(DisplayTexture.NoSignal); monitor.videoPlayer.Pause(); isPaused = isStarted; } displayParent.SetActive(isPoweredOn); } public void SetDisplayText(DisplayText? display, bool resetTimer = false) { stateText.gameObject.SetActive(display.HasValue); stateText.text = display switch { DisplayText.Play => $"PLAY {playSymbol}", DisplayText.Pause => $"PAUSE {pauseSymbol}", DisplayText.Stop => $"STOP {stopSymbol}", DisplayText.Eject => $"EJECT {ejectSymbol}", DisplayText.Rewind => $"REW {rewindSymbol}", DisplayText.FastForwad => $"FF {fastForwardSymbol}", DisplayText.None => "", _ => "" }; if (resetTimer) timeText.text = "-- : --"; } public void StartPausePlayback() { if ((tapeDuration - currentTime) <= 0.5f || !canEject || isEnded) return; if (isWinding) { //TODO: monitor.videoPlayer.time is not setting to currentTime StopAllCoroutines(); SetDisplayText(DisplayText.Stop); monitor.SetDisplayTexture(DisplayTexture.Stop); audioSource.SetSoundClip(stop, play: true); monitor.videoPlayer.time = currentTime; monitor.videoPlayer.Prepare(); isWinding = false; isPaused = true; } else if (!isStarted || isPaused) { SetDisplayText(DisplayText.Play); monitor.SetVideoInput(outputTexture); audioSource.SetSoundClip(play, play: true); monitor.videoPlayer.Play(); isStarted = true; isPaused = false; } else if(!isPaused) { SetDisplayText(DisplayText.Pause); monitor.videoPlayer.Pause(); isPaused = true; } } public void Rewind() { if(isStarted && !isWinding && (tapeDuration - currentTime) < tapeDuration - 0.5f) { monitor.videoPlayer.Pause(); StartCoroutine(OnRewind()); isWinding = true; isEnded = false; } } public void FastForward() { if (isStarted && !isWinding && (tapeDuration - currentTime) > 0.5f) { monitor.videoPlayer.Pause(); StartCoroutine(OnFastForward()); isWinding = true; } } public void InsertVHSTape() { if (isPoweredOn && canInsert) Inventory.Instance.OpenItemSelector(this); } public void EjectVHSTape() { if (isPoweredOn && canEject && !isWinding) { monitor.videoPlayer.Pause(); StartCoroutine(OnEject()); isWinding = true; } } public void OnInventoryItemSelect(Inventory inventory, InventoryItem selectedItem) { if(selectedItem.ItemGuid == VHSItem) { var customData = selectedItem.CustomData.GetJson(); if(customData.TryGetValue("texture", out JToken texture)) { string texturePath = texture.ToString(); Texture2D tapeTexture = Resources.Load(texturePath); MeshRenderer tapeRenderer = VHSTape.GetComponentInChildren(); tapeRenderer.material.SetTexture(tapeMaterialProperty, tapeTexture); } if (customData.TryGetValue("video", out JToken video)) { string videoPath = video.ToString(); VideoClip videoClip = Resources.Load(videoPath); monitor.PrepareVideo(videoClip, outputTexture); tapeDuration = videoClip.length; } VHSTape.gameObject.SetActive(true); insertCollider.gameObject.SetActive(false); tapeCollider.enabled = false; tapeCustomData = selectedItem.CustomData.JsonData; inventory.RemoveItem(selectedItem); canInsert = false; canEject = false; StartCoroutine(OnInsert()); } else { Debug.Log("Selected item is not a VHS Tape!"); } } public void OnTakeVHSTape() { insertCollider.gameObject.SetActive(true); VHSTape.gameObject.SetActive(false); animator.SetTrigger(closeCoverTrigger); canInsert = true; } #region Enumerators IEnumerator OnInsert() { animator.SetTrigger(insertTrigger); audioSource.SetSoundClip(tapeInsert, play: true); yield return new WaitForAnimatorClip(animator, insertTrigger); SetDisplayText(DisplayText.Stop); monitor.SetDisplayTexture(DisplayTexture.Stop); VHSIcon.SetActive(true); VHSTape.gameObject.SetActive(false); canEject = true; } IEnumerator OnEject() { yield return OnRewind(); SetDisplayText(DisplayText.Eject, true); monitor.SetDisplayTexture(DisplayTexture.Stop); yield return new WaitForSeconds(1f); VHSTape.gameObject.SetActive(true); tapeCollider.enabled = false; animator.SetTrigger(ejectTrigger); audioSource.SetSoundClip(tapeEject, play: true); yield return new WaitForAnimatorClip(animator, ejectTrigger); SetDisplayText(null); monitor.SetDisplayTexture(null); monitor.SetDisplayTexture(DisplayTexture.NoTape); VHSIcon.SetActive(false); VHSTape.ItemCustomData.JsonData = tapeCustomData; tapeCustomData = null; tapeCollider.enabled = true; canInsert = true; canEject = false; isStarted = false; isWinding = false; isPaused = false; isEnded = false; } IEnumerator OnRewind() { if (currentTime > 0) { windingMod = 0; monitor.SetDisplayTexture(DisplayTexture.Rewind); SetDisplayText(DisplayText.Rewind); audioSource.SetSoundClip(rewind, play: true); yield return new WaitForSeconds(timeBeforeWinding); while (currentTime > 0) { UpdateClipTime(currentTime, false); windingMod = Mathf.MoveTowards(windingMod, 1, Time.deltaTime * windingStartupSpeed); currentTime -= Time.deltaTime * rewindSpeed * windingMod; yield return null; } audioSource.SetSoundClip(stop, play: true); } monitor.SetDisplayTexture(DisplayTexture.Stop); SetDisplayText(DisplayText.Stop); currentTime = 0; monitor.videoPlayer.time = currentTime; isStarted = false; isWinding = false; isPaused = true; } IEnumerator OnFastForward() { if (currentTime < tapeDuration) { windingMod = 0; monitor.SetDisplayTexture(DisplayTexture.FastForward); SetDisplayText(DisplayText.FastForwad); audioSource.SetSoundClip(rewind, play: true); yield return new WaitForSeconds(timeBeforeWinding); while (currentTime < tapeDuration) { UpdateClipTime(currentTime, false); windingMod = Mathf.MoveTowards(windingMod, 1, Time.deltaTime * windingStartupSpeed); currentTime += Time.deltaTime * fastForwardSpeed * windingMod; yield return null; } } monitor.SetDisplayTexture(DisplayTexture.Stop); SetDisplayText(DisplayText.Stop); audioSource.SetSoundClip(stop, play: true); currentTime = tapeDuration; monitor.videoPlayer.time = currentTime; isWinding = false; isPaused = true; } #endregion public StorableCollection OnSave() { return new StorableCollection() { { nameof(isPoweredOn), isPoweredOn }, { nameof(canInsert), canInsert }, { nameof(canEject), canEject }, { nameof(isEnded), isEnded }, { "playtime", currentTime }, { "customData", !string.IsNullOrEmpty(tapeCustomData) ? JObject.Parse(tapeCustomData) : new JObject() } }; } public void OnLoad(JToken data) { bool isPowered = (bool)data[nameof(isPoweredOn)]; SetPower(isPowered); isPaused = isPowered; canInsert = (bool)data[nameof(canInsert)]; canEject = (bool)data[nameof(canEject)]; isEnded = (bool)data[nameof(isEnded)]; currentTime = (double)data["playtime"]; tapeCustomData = data["customData"].ToString(); if (!string.IsNullOrEmpty(tapeCustomData) && data["customData"]["texture"] != null && data["customData"]["video"] != null) { string texturePath = data["customData"]["texture"].ToString(); string videoPath = data["customData"]["video"].ToString(); Texture2D tapeTexture = Resources.Load(texturePath); VideoClip videoClip = Resources.Load(videoPath); MeshRenderer tapeRenderer = VHSTape.GetComponentInChildren(); if (tapeTexture) tapeRenderer.material.SetTexture(tapeMaterialProperty, tapeTexture); if (videoClip) { tapeDuration = videoClip.length; monitor.PrepareVideo(videoClip, outputTexture); } } UpdateClipTime(currentTime); SetDisplayText(DisplayText.Stop); monitor.SetDisplayTexture(DisplayTexture.Stop); } } }