432 lines
10 KiB
C#
432 lines
10 KiB
C#
|
|
using Newtonsoft.Json.Linq;
|
|
using System;
|
|
using UdonSharp;
|
|
using UnityEngine;
|
|
using VRC.SDK3.Components.Video;
|
|
using VRC.SDK3.Image;
|
|
using VRC.SDK3.UdonNetworkCalling;
|
|
using VRC.SDK3.Video.Components.Base;
|
|
using VRC.SDKBase;
|
|
using VRC.Udon.Common.Interfaces;
|
|
|
|
|
|
public enum ClueScreenType
|
|
{
|
|
Blank,
|
|
Video,
|
|
Map
|
|
}
|
|
|
|
|
|
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
|
|
public class CaseVideoSyncPlayer : UdonSharpBehaviour
|
|
{
|
|
[SerializeField] private CaseManager _CaseManager;
|
|
|
|
[UdonSynced, FieldChangeCallback(nameof(SubMapIndex))] private int _SubMapIndex = 0;
|
|
[UdonSynced, FieldChangeCallback(nameof(ShowScreen))] private ClueScreenType _ShowScreen = ClueScreenType.Blank;
|
|
[UdonSynced, FieldChangeCallback(nameof(FlashCorrectAnswer))] private bool _FlashCorrectAnswer = false;
|
|
|
|
[UdonSynced, FieldChangeCallback(nameof(VideoIndex))] private int _VideoIndex = -1;
|
|
[UdonSynced, FieldChangeCallback(nameof(TimeAndOffset))] private Vector2 _TimeAndOffset;
|
|
|
|
[UdonSynced, FieldChangeCallback(nameof(PlayVideo))] private bool _VideoIsPlaying;
|
|
|
|
[SerializeField] private BaseVRCVideoPlayer _VideoPlayer;
|
|
[SerializeField] private float _SyncFrequency = 5.0f;
|
|
|
|
[SerializeField] private MeshRenderer _BlankScreenMesh;
|
|
[SerializeField] private MeshRenderer _VideoScreenMesh;
|
|
[SerializeField] private MeshRenderer _MapScreenMesh;
|
|
|
|
[SerializeField] private Material _MapScreenMaterial;
|
|
[SerializeField] private Texture2D _PlaceholderMapTexture;
|
|
|
|
private VRCImageDownloader _MapDownloader;
|
|
private Texture2D[] _MapImages = new Texture2D[0];
|
|
private IUdonEventReceiver _UdonEventReceiverThis;
|
|
private int[] _CachedMapIndices = new int[0];
|
|
private int _MapDownloadIndex = 0;
|
|
private bool _MapDownloadsInProgress = false;
|
|
|
|
private const int IMAGES_PER_MAP_ATLAS = 6;
|
|
|
|
|
|
void Start()
|
|
{
|
|
_MapDownloader = new VRCImageDownloader();
|
|
_UdonEventReceiverThis = (IUdonEventReceiver)this;
|
|
|
|
_MapScreenMesh.sharedMaterial = _MapScreenMaterial;
|
|
|
|
UpdateMap(false);
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
_MapDownloader.Dispose();
|
|
}
|
|
|
|
|
|
[NetworkCallable]
|
|
public void QueueMapDownloads(int[] MapIndices)
|
|
{
|
|
if (_MapDownloadsInProgress)
|
|
{
|
|
// Concatenate both caches into a new bigger cache and await new downloads as usual
|
|
int[] NewMapIndicesCache = new int[_CachedMapIndices.Length + MapIndices.Length];
|
|
Texture2D[] NewMapImages = new Texture2D[_CachedMapIndices.Length + MapIndices.Length];
|
|
for (int i = 0; i < _CachedMapIndices.Length; i++)
|
|
{
|
|
NewMapIndicesCache[i] = _CachedMapIndices[i];
|
|
NewMapImages[i] = _MapImages[i];
|
|
}
|
|
for (int i = 0; i < MapIndices.Length; i++)
|
|
{
|
|
NewMapIndicesCache[i + _CachedMapIndices.Length] = MapIndices[i];
|
|
NewMapImages[i + _CachedMapIndices.Length] = _PlaceholderMapTexture;
|
|
}
|
|
_CachedMapIndices = NewMapIndicesCache;
|
|
_MapImages = NewMapImages;
|
|
}
|
|
else
|
|
{
|
|
_MapDownloadsInProgress = true;
|
|
_CachedMapIndices = MapIndices;
|
|
|
|
_MapImages = new Texture2D[_CachedMapIndices.Length];
|
|
for (int i = 0; i < _MapImages.Length; i++)
|
|
{
|
|
_MapImages[i] = _PlaceholderMapTexture;
|
|
}
|
|
|
|
_SubMapIndex = 0;
|
|
_MapDownloadIndex = 0;
|
|
LoadMapFromIndex(_CachedMapIndices[_MapDownloadIndex]);
|
|
}
|
|
}
|
|
private void LoadMapFromIndex(int MapIndex)
|
|
{
|
|
VRCUrl MapURL = _CaseManager.GetMap(MapIndex);
|
|
|
|
TextureInfo AdditionalTextureInfo = new TextureInfo();
|
|
AdditionalTextureInfo.WrapModeU = TextureWrapMode.Clamp;
|
|
AdditionalTextureInfo.WrapModeV = TextureWrapMode.Clamp;
|
|
AdditionalTextureInfo.GenerateMipMaps = true;
|
|
_MapDownloader.DownloadImage(MapURL, null, _UdonEventReceiverThis, AdditionalTextureInfo);
|
|
}
|
|
public override void OnImageLoadSuccess(IVRCImageDownload Result)
|
|
{
|
|
_MapImages[_MapDownloadIndex] = Result.Result;
|
|
|
|
int MapPage = SubMapIndex / IMAGES_PER_MAP_ATLAS;
|
|
if (MapPage == _MapDownloadIndex)
|
|
{
|
|
_MapScreenMaterial.SetTexture("_EmissionMap", _MapImages[MapPage]);
|
|
}
|
|
|
|
_MapDownloadIndex++;
|
|
if (_MapDownloadIndex >= _CachedMapIndices.Length)
|
|
{
|
|
_MapDownloadsInProgress = false;
|
|
}
|
|
else
|
|
{
|
|
LoadMapFromIndex(_CachedMapIndices[_MapDownloadIndex]);
|
|
}
|
|
|
|
base.OnImageLoadSuccess(Result);
|
|
}
|
|
public override void OnImageLoadError(IVRCImageDownload Result)
|
|
{
|
|
_MapDownloadsInProgress = false;
|
|
|
|
base.OnImageLoadError(Result);
|
|
}
|
|
|
|
[NetworkCallable]
|
|
public void NextCorrectAnswerFrame()
|
|
{
|
|
if (FlashCorrectAnswer)
|
|
{
|
|
SubMapIndex = (SubMapIndex == 4) ? 3 : 4;
|
|
SendCustomEventDelayedSeconds(nameof(NextCorrectAnswerFrame), 0.2f);
|
|
}
|
|
else
|
|
{
|
|
_VideoPlayer.Stop();
|
|
SubMapIndex = 0;
|
|
ShowScreen = ClueScreenType.Blank;
|
|
|
|
RequestSerialization();
|
|
}
|
|
}
|
|
|
|
private void UpdateMap(bool SyncResult = true)
|
|
{
|
|
int MapPage = SubMapIndex / IMAGES_PER_MAP_ATLAS;
|
|
int SubmapIndexWrapped = SubMapIndex % IMAGES_PER_MAP_ATLAS;
|
|
|
|
if (MapPage < _MapImages.Length)
|
|
{
|
|
switch (SubmapIndexWrapped)
|
|
{
|
|
case 0: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.0f, 0.66666666f)); break;
|
|
case 1: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.5f, 0.66666666f)); break;
|
|
case 2: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.0f, 0.33333333f)); break;
|
|
case 3: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.5f, 0.33333333f)); break;
|
|
case 4: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.0f, 0.0f)); break;
|
|
case 5: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.5f, 0.0f)); break;
|
|
}
|
|
|
|
_MapScreenMaterial.SetTexture("_EmissionMap", _MapImages[MapPage]);
|
|
ShowScreen = ClueScreenType.Map;
|
|
|
|
if (SyncResult)
|
|
{
|
|
RequestSerialization();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private void _LoadVideo_Private()
|
|
{
|
|
Debug.LogWarning("[CaseVideoSyncPlayer] Loading video " + VideoIndex + "...");
|
|
|
|
TryLoadURL();
|
|
}
|
|
public void TryLoadURL()
|
|
{
|
|
if (VideoIndex < _CaseManager.GetVideoCount())
|
|
{
|
|
_VideoPlayer.LoadURL(_CaseManager.GetVideo(VideoIndex));
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[CaseVideoSyncPlayer] Index is out of range. Retrying...");
|
|
SendCustomEventDelayedSeconds(nameof(TryLoadURL), 1.0f);
|
|
}
|
|
}
|
|
|
|
private void _PlayVideo_Private()
|
|
{
|
|
Debug.LogWarning("[CaseVideoSyncPlayer] Playing video " + VideoIndex + ".");
|
|
|
|
_VideoPlayer.Play();
|
|
}
|
|
|
|
private void _StopVideo_Private()
|
|
{
|
|
Debug.LogWarning("[CaseVideoSyncPlayer] Stopping video " + VideoIndex + ".");
|
|
|
|
_VideoPlayer.Stop();
|
|
|
|
VideoIndex = -1;
|
|
}
|
|
|
|
public override void OnVideoReady()
|
|
{
|
|
Debug.LogWarning("[CaseVideoSyncPlayer] Video " + VideoIndex + " is ready.");
|
|
|
|
if (_VideoIsPlaying)
|
|
{
|
|
_PlayVideo_Private();
|
|
}
|
|
|
|
base.OnVideoReady();
|
|
}
|
|
|
|
public override void OnVideoError(VideoError VideoError)
|
|
{
|
|
switch(VideoError)
|
|
{
|
|
case VideoError.Unknown:
|
|
Debug.LogError("[CaseVideoSyncPlayer] Unknown playback error.");
|
|
break;
|
|
case VideoError.InvalidURL:
|
|
Debug.LogError("[CaseVideoSyncPlayer] Invalid URL.");
|
|
break;
|
|
case VideoError.AccessDenied:
|
|
Debug.LogError("[CaseVideoSyncPlayer] Access denied.");
|
|
break;
|
|
case VideoError.PlayerError:
|
|
Debug.LogError("[CaseVideoSyncPlayer] Error with video player.");
|
|
break;
|
|
case VideoError.RateLimited:
|
|
Debug.LogError("[CaseVideoSyncPlayer] Rate limited.");
|
|
break;
|
|
}
|
|
|
|
_VideoPlayer.Stop();
|
|
|
|
base.OnVideoError(VideoError);
|
|
}
|
|
|
|
public override void OnVideoStart()
|
|
{
|
|
if (_VideoIsPlaying)
|
|
{
|
|
ShowScreen = ClueScreenType.Video;
|
|
//UpdateTimeAndOffset();
|
|
}
|
|
else
|
|
{
|
|
_VideoPlayer.Stop();
|
|
}
|
|
|
|
base.OnVideoStart();
|
|
}
|
|
|
|
public override void OnVideoEnd()
|
|
{
|
|
PlayVideo = false;
|
|
ShowScreen = ClueScreenType.Blank;
|
|
|
|
RequestSerialization();
|
|
|
|
base.OnVideoEnd();
|
|
}
|
|
|
|
private void UpdateTimeAndOffset()
|
|
{
|
|
if (Networking.IsOwner(gameObject))
|
|
{
|
|
TimeAndOffset = new Vector2(_VideoPlayer.GetTime(), (float)Networking.GetServerTimeInSeconds());
|
|
|
|
if (_SyncFrequency > 0.0f)
|
|
{
|
|
SendCustomEventDelayedSeconds(nameof(UpdateTimeAndOffset), _SyncFrequency);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Resync();
|
|
}
|
|
|
|
RequestSerialization();
|
|
}
|
|
|
|
public void Resync()
|
|
{
|
|
_VideoPlayer.SetTime(TimeAndOffset.x + ((float)Networking.GetServerTimeInSeconds() - TimeAndOffset.y));
|
|
}
|
|
|
|
|
|
[NetworkCallable]
|
|
public void ClearScreen()
|
|
{
|
|
FlashCorrectAnswer = false;
|
|
ShowScreen = ClueScreenType.Blank;
|
|
}
|
|
|
|
|
|
private void SwapToScreen(ClueScreenType Screen)
|
|
{
|
|
if (ShowScreen == Screen) return;
|
|
|
|
switch (Screen)
|
|
{
|
|
case ClueScreenType.Blank:
|
|
{
|
|
_BlankScreenMesh.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f);
|
|
_VideoScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f);
|
|
_MapScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f);
|
|
|
|
PlayVideo = false;
|
|
break;
|
|
}
|
|
case ClueScreenType.Video:
|
|
{
|
|
_BlankScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f);
|
|
_VideoScreenMesh.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f);
|
|
_MapScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f);
|
|
break;
|
|
}
|
|
case ClueScreenType.Map:
|
|
{
|
|
_BlankScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f);
|
|
_VideoScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f);
|
|
_MapScreenMesh.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f);
|
|
|
|
PlayVideo = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
RequestSerialization();
|
|
}
|
|
|
|
|
|
public int SubMapIndex
|
|
{
|
|
set
|
|
{
|
|
_SubMapIndex = value;
|
|
UpdateMap(!FlashCorrectAnswer);
|
|
}
|
|
get => _SubMapIndex;
|
|
}
|
|
|
|
public ClueScreenType ShowScreen
|
|
{
|
|
set
|
|
{
|
|
SwapToScreen(value);
|
|
_ShowScreen = value;
|
|
}
|
|
get => _ShowScreen;
|
|
}
|
|
|
|
public bool FlashCorrectAnswer
|
|
{
|
|
set
|
|
{
|
|
_FlashCorrectAnswer = value;
|
|
SendCustomNetworkEvent(NetworkEventTarget.Self, nameof(NextCorrectAnswerFrame));
|
|
RequestSerialization();
|
|
}
|
|
get => _FlashCorrectAnswer;
|
|
}
|
|
|
|
public int VideoIndex
|
|
{
|
|
set
|
|
{
|
|
_VideoIndex = value;
|
|
if (_VideoIndex >= 0)
|
|
{
|
|
_LoadVideo_Private();
|
|
}
|
|
RequestSerialization();
|
|
}
|
|
get => _VideoIndex;
|
|
}
|
|
|
|
public bool PlayVideo
|
|
{
|
|
set
|
|
{
|
|
_VideoIsPlaying = value;
|
|
if (_VideoIsPlaying) _PlayVideo_Private();
|
|
else _StopVideo_Private();
|
|
RequestSerialization();
|
|
}
|
|
get => _VideoIsPlaying;
|
|
}
|
|
|
|
public Vector2 TimeAndOffset
|
|
{
|
|
set
|
|
{
|
|
_TimeAndOffset = value;
|
|
if (!Networking.IsOwner(gameObject))
|
|
{
|
|
Resync();
|
|
}
|
|
}
|
|
get => _TimeAndOffset;
|
|
}
|
|
}
|