A lot of older work

This commit is contained in:
2026-06-09 17:45:51 +02:00
parent a8b395b1d3
commit cf39fd5dd9
8 changed files with 3760 additions and 1710 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -394,21 +394,47 @@ namespace Marro.PacManUdon
} }
} }
public override void CollectSyncedData(byte[] data, ref int offset, NetworkEventType eventType) public override void CollectSyncedData(byte[] data, ref int index, NetworkEventType eventType)
{ {
if (eventType == NetworkEventType.TimeSequenceSync)
{
data.Append(currentlyInTimeSequence, ref index);
if (currentlyInTimeSequence)
{
data.AppendAsByte((int)currentTimeSequence, ref index);
data.Append(timeSequenceSecondsPassed, ref index);
}
}
//data[offset++] = new byte[] { NetworkManager.Int32ToByte((int)gameState) }; //data[offset++] = new byte[] { NetworkManager.Int32ToByte((int)gameState) };
//data[offset++] = BitConverter.GetBytes(currentlyInTimeSequence); //data[offset++] = BitConverter.GetBytes(currentlyInTimeSequence);
//data[offset++] = new byte[] { NetworkManager.Int32ToByte((int)currentTimeSequence) }; //data[offset++] = new byte[] { NetworkManager.Int32ToByte((int)currentTimeSequence) };
//data[offset++] = BitConverter.GetBytes(timeSequenceSecondsPassed); //data[offset++] = BitConverter.GetBytes(timeSequenceSecondsPassed);
} }
public override bool WriteSyncedData(byte[] data, ref int offset, NetworkEventType eventType) public override bool WriteSyncedData(byte[] data, ref int index, NetworkEventType eventType)
{ {
if (eventType == NetworkEventType.StartGameButtonPressed) if (eventType == NetworkEventType.StartGameButtonPressed)
{ {
StartGameButtonPressed(); StartGameButtonPressed();
} }
if (eventType == NetworkEventType.TimeSequenceSync)
{
var currentlyInTimeSequence = data.ReadBool(ref index);
if (!currentlyInTimeSequence)
{
TimeSequenceTryEndCurrent();
}
else
{
var currentTimeSequence = (PacManTimeSequence)data.ReadByte(ref index);
var timeSequenceSecondsPassed = data.ReadFloat(ref index);
TimeSequenceSyncWithRemote(currentTimeSequence, timeSequenceSecondsPassed);
}
}
//SetGameState((PacManGameState)data[offset++]); //SetGameState((PacManGameState)data[offset++]);
//var currentlyInTimeSequence = BitConverter.ToBoolean(data, offset++); //var currentlyInTimeSequence = BitConverter.ToBoolean(data, offset++);

View File

@@ -193,6 +193,7 @@ namespace Marro.PacManUdon
} }
// Debug.Log($"{gameObject} GhostCaughtQueue with ghost {ghost}"); // Debug.Log($"{gameObject} GhostCaughtQueue with ghost {ghost}");
//networkManager.SendEventSoon(NetworkEventType.TimeSequenceSync);
//networkManager.SendEventSoon(NetworkEventType.GhostUpdate); //networkManager.SendEventSoon(NetworkEventType.GhostUpdate);
ghostScaredQueue.Add(ghost); ghostScaredQueue.Add(ghost);

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ using System.Text;
using TMPro; using TMPro;
using UdonSharp; using UdonSharp;
using UnityEngine; using UnityEngine;
using UnityEngine.Android;
using VRC.SDK3.UdonNetworkCalling; using VRC.SDK3.UdonNetworkCalling;
using VRC.SDKBase; using VRC.SDKBase;
using VRC.Udon.Common; using VRC.Udon.Common;
@@ -17,9 +18,13 @@ namespace Marro.PacManUdon
StartGameButtonPressed = 3, StartGameButtonPressed = 3,
SyncPellets = 4, SyncPellets = 4,
GhostUpdate = 5, GhostUpdate = 5,
TimeSequenceSync = 6,
Pause = 7,
Resume = 8,
Step = 9,
} }
public class NetworkManager : UdonSharpBehaviour public class NetworkManager : SyncedObject
{ {
// The network manager works by serializing event and state data into a byte array, including a timestamp for each event. // The network manager works by serializing event and state data into a byte array, including a timestamp for each event.
// If user is owner, this data is created and stored in a buffer which is cleared upon transmission. // If user is owner, this data is created and stored in a buffer which is cleared upon transmission.
@@ -42,6 +47,11 @@ namespace Marro.PacManUdon
// [7]: (byte) Type of event. 0 = Full Sync, which is used to sync up from an undefinted state. // [7]: (byte) Type of event. 0 = Full Sync, which is used to sync up from an undefinted state.
// [+]: Event-specific data. // [+]: Event-specific data.
#region External settings
[SerializeField] private SyncedObject[] networkEventSubscribersFlat;
[SerializeField] private int[] networkEventSubscribersFlatSegmentLengths;
#endregion
#region Settings #region Settings
/// <summary> /// <summary>
/// The root from which this <see cref="NetworkManager"/> will look for <see cref="SyncedObject"/> to control. /// The root from which this <see cref="NetworkManager"/> will look for <see cref="SyncedObject"/> to control.
@@ -60,9 +70,9 @@ namespace Marro.PacManUdon
/// </summary> /// </summary>
[SerializeField] private float pingDelay = 0.3f; [SerializeField] private float pingDelay = 0.3f;
/// <summary> /// <summary>
/// The rate at which updates occur. /// The time delta at which updates occur.
/// </summary> /// </summary>
[SerializeField] private float updateRate = 0.0166666667f; [SerializeField] private float updateDelta = 0.0166666667f;
#endregion #endregion
#region Constants #region Constants
@@ -99,9 +109,19 @@ namespace Marro.PacManUdon
#region Private attributes #region Private attributes
/// <summary> /// <summary>
/// Objects which are controlled by this <see cref="NetworkManager"/>. /// Whether <see cref="Initialize"/> has been called successfully.
/// </summary> /// </summary>
private SyncedObject[] syncedObjects; private bool initialized = false;
/// <summary>
/// Subscribers to each <see cref="NetworkEventType"/>.
/// </summary>
private SyncedObject[][] networkEventSubscribers;
/// <summary>
/// Subscribers for <see cref="SyncedObject.SyncedUpdate"/>.
/// </summary>
private SyncedObject[] syncedUpdateSubscribers;
/// <summary> /// <summary>
/// Offset from system time to network time, including delay. /// Offset from system time to network time, including delay.
@@ -113,7 +133,17 @@ namespace Marro.PacManUdon
private float internalTime; private float internalTime;
/// <summary> /// <summary>
/// Time at which the latest update occured /// True if time is paused
/// </summary>
private bool paused;
/// <summary>
/// True if a step should happen next update
/// </summary>
private bool stepNext;
/// <summary>
/// Time at which the next update should occur.
/// </summary> /// </summary>
private float nextUpdateTime; private float nextUpdateTime;
/// <summary> /// <summary>
@@ -191,10 +221,23 @@ namespace Marro.PacManUdon
/// </summary> /// </summary>
public bool Ready { get; private set; } = false; public bool Ready { get; private set; } = false;
/// <summary> /// <summary>
/// Whether the current perspective is synced with the owner. (Always true if current perspective is owner.) /// Whether the current perspective is synced with the owner. (Always true if current perspective is owner.)
/// </summary> /// </summary>
public bool Synced { get; private set; } = false; public bool Synced
{
get => synced;
private set
{
synced = value;
if (DebugImageToIndicateSynced != null)
{
DebugImageToIndicateSynced.SetFloat("Color", value ? 1 : 0);
}
}
}
private bool synced = false;
/// <summary> /// <summary>
/// The time since last full sync which is currently being simulated. /// The time since last full sync which is currently being simulated.
@@ -216,16 +259,36 @@ namespace Marro.PacManUdon
/// <summary> /// <summary>
/// Is the local user owner? /// Is the local user owner?
/// </summary> /// </summary>
public bool IsOwner { get; private set; } public bool IsOwner
{
get => isOwner;
private set
{
isOwner = value;
if (DebugImageToIndicateOwner != null)
{
DebugImageToIndicateOwner.SetFloat("Color", value ? 1 : 0);
}
}
}
private bool isOwner = false;
#endregion #endregion
#region General #region General
/// <summary>
/// Initializes the <see cref="NetworkManager"/>. Call <see cref="Reset"/> afterwards to activate networking.
/// </summary>
public void Initialize() public void Initialize()
{ {
if (initialized)
{
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Tried to call {nameof(Initialize)} when already initialized!");
return;
}
if (!BitConverter.IsLittleEndian) if (!BitConverter.IsLittleEndian)
{ {
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Fatal: NetworkManager only supports little endian! Network sync will not be possible."); Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Fatal: NetworkManager only supports little endian! Network sync will not be possible.");
Ready = false;
return; return;
} }
@@ -234,15 +297,75 @@ namespace Marro.PacManUdon
root = transform.parent.gameObject; root = transform.parent.gameObject;
} }
syncedObjects = root.GetComponentsInChildren<SyncedObject>(includeInactive: true); if (!TryGetNetworkEventSubscribers(out networkEventSubscribers))
foreach (var obj in syncedObjects) {
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Fatal: Invalid NetworkEventSubscribers configuration! Network sync will not be possible.");
return;
}
syncedUpdateSubscribers = root.GetComponentsInChildren<SyncedObject>(includeInactive: true);
AssignNetworkManagerReferencesToSubscribers();
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Found {syncedUpdateSubscribers.Length} {nameof(SyncedObject)} in children of {root.name}.");
initialized = true;
}
private bool TryGetNetworkEventSubscribers(out SyncedObject[][] result)
{
result = new SyncedObject[0][];
return true;
var values = networkEventSubscribersFlat;
var lengths = networkEventSubscribersFlatSegmentLengths;
result = new SyncedObject[lengths.Length][];
var index = 0;
foreach (var length in lengths)
{
if (index + length >= values.Length)
{
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) {nameof(TryGetNetworkEventSubscribers)}: Lengths sum is larger than values.");
return false;
}
result[index] = new SyncedObject[length];
Array.Copy(values, index, result, 0, length);
index += length;
}
if (index != values.Length)
{
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) {nameof(TryGetNetworkEventSubscribers)}: Lengths sum is smaller than values.");
}
return true;
}
private void AssignNetworkManagerReferencesToSubscribers()
{
// This results in a lot of duplicated assignments, but the alternative is deduplicating and unfortunately Udon does not have a good method for this.
foreach (var obj in syncedUpdateSubscribers)
{ {
obj.networkManager = this; obj.networkManager = this;
} }
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Found {syncedObjects.Length} {nameof(SyncedObject)} in children of {root.name}."); foreach (var obj in networkEventSubscribersFlat)
{
obj.networkManager = this;
}
}
SetOwner(Networking.IsOwner(gameObject)); public void Reset()
{
if (!initialized)
{
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Tried to call {nameof(Reset)} while not initialized. Call {nameof(Initialize)} first.");
return;
}
IsOwner = Networking.IsOwner(gameObject);
ClearBuffer(); ClearBuffer();
@@ -256,9 +379,14 @@ namespace Marro.PacManUdon
SyncedDeltaTime = Time.fixedDeltaTime; SyncedDeltaTime = Time.fixedDeltaTime;
nextUpdateTime = SyncedTime; nextUpdateTime = SyncedTime;
if (!Synced) // Sync up
if (IsOwner)
{ {
RequestEvent(NetworkEventType.FullSync); // See if we can sync up SendEventSoon(NetworkEventType.FullSyncForced);
}
else
{
RequestEvent(NetworkEventType.FullSync);
} }
Ready = true; Ready = true;
@@ -268,12 +396,12 @@ namespace Marro.PacManUdon
public void Update() public void Update()
{ {
if (!Ready) if (!initialized)
{ {
return; return;
} }
// Fetch the current time // Get our target time
UpdateInternalTime(); UpdateInternalTime();
if (Ready) if (Ready)
@@ -289,27 +417,45 @@ namespace Marro.PacManUdon
} }
} }
// Forwards simulated time at the updateRate pace // Forwards simulated time by updateDelta until we're caught up
while (nextUpdateTime <= internalTime) while (nextUpdateTime <= internalTime)
{ {
ProgressSyncedTime(nextUpdateTime); ProgressSyncedTime(nextUpdateTime);
PerformFixedSyncedUpdate(); PerformFixedSyncedUpdate();
nextUpdateTime = SyncedTime + updateRate; nextUpdateTime = SyncedTime + updateDelta;
} }
} }
private void UpdateInternalTime() private void UpdateInternalTime()
{ {
internalTime = Time.fixedTime - offsetTime; var delta = Time.fixedTime - offsetTime - internalTime;
if (!paused)
{
// Continue time like normal
internalTime += delta;
}
else if (paused && !stepNext)
{
// Since we're paused, increase our offset from Unity's time
offsetTime += delta;
}
else
{
// Step forward by exactly updateDelta, apply the remainder to our offset
offsetTime += delta - updateDelta;
internalTime += updateDelta;
stepNext = false;
}
} }
private void PerformFixedSyncedUpdate() private void PerformFixedSyncedUpdate()
{ {
IsEventUpdate = false; IsEventUpdate = false;
for (int i = 0; i < syncedObjects.Length; i++) for (int i = 0; i < syncedUpdateSubscribers.Length; i++)
{ {
var obj = syncedObjects[i]; var obj = syncedUpdateSubscribers[i];
if (obj.gameObject.activeInHierarchy) if (obj.gameObject.activeInHierarchy)
{ {
@@ -348,15 +494,9 @@ namespace Marro.PacManUdon
} }
} }
private void SetOwner(bool isOwner) private SyncedObject[] GetEventSubscribers(NetworkEventType eventType) =>
{ //networkEventSubscribers[(int)eventType];
IsOwner = isOwner; syncedUpdateSubscribers;
if (DebugImageToIndicateOwner != null)
{
DebugImageToIndicateOwner.SetFloat("Color", isOwner ? 1 : 0);
}
}
#endregion #endregion
#region Sender #region Sender
@@ -412,7 +552,9 @@ namespace Marro.PacManUdon
InitializeEvent(eventType, timestamp, eventId, out byte[] data, out var index); InitializeEvent(eventType, timestamp, eventId, out byte[] data, out var index);
foreach (var obj in syncedObjects) var subscibers = GetEventSubscribers(eventType);
foreach (var obj in subscibers)
{ {
obj.CollectSyncedData(data, ref index, eventType); obj.CollectSyncedData(data, ref index, eventType);
} }
@@ -686,12 +828,14 @@ namespace Marro.PacManUdon
ProgressSyncedTime(timestamp); ProgressSyncedTime(timestamp);
foreach (var obj in syncedObjects) var subscibers = GetEventSubscribers(eventType);
foreach (var obj in subscibers)
{ {
obj.SyncedUpdate(); obj.SyncedUpdate();
} }
foreach (var obj in syncedObjects) foreach (var obj in subscibers)
{ {
var success = obj.WriteSyncedData(@event, ref index, eventType); var success = obj.WriteSyncedData(@event, ref index, eventType);
@@ -869,7 +1013,7 @@ namespace Marro.PacManUdon
} }
bool newOwnerIsLocalPlayer = newOwner == Networking.LocalPlayer; bool newOwnerIsLocalPlayer = newOwner == Networking.LocalPlayer;
SetOwner(newOwnerIsLocalPlayer); IsOwner = newOwnerIsLocalPlayer;
if (newOwnerIsLocalPlayer) if (newOwnerIsLocalPlayer)
{ {
@@ -936,6 +1080,31 @@ namespace Marro.PacManUdon
} }
#endregion #endregion
#region SyncedData
public override void CollectSyncedData(byte[] data, ref int index, NetworkEventType eventType)
{
// Nothing
}
public override bool WriteSyncedData(byte[] data, ref int index, NetworkEventType eventType)
{
switch (eventType)
{
case NetworkEventType.Pause:
Pause();
break;
case NetworkEventType.Resume:
Resume();
break;
case NetworkEventType.Step:
Step();
break;
}
return true;
}
#endregion
#region Debug #region Debug
public void SimulateSyncToTimestamp(float timestamp) public void SimulateSyncToTimestamp(float timestamp)
{ {
@@ -965,6 +1134,11 @@ namespace Marro.PacManUdon
/// </summary> /// </summary>
[SerializeField] private Animator DebugImageToIndicateOwner; [SerializeField] private Animator DebugImageToIndicateOwner;
/// <summary>
/// An animator which visualizes whether the NetworkManager is synced.
/// </summary>
[SerializeField] private Animator DebugImageToIndicateSynced;
public void DoFullSync() public void DoFullSync()
{ {
SendEventSoon(NetworkEventType.FullSync); SendEventSoon(NetworkEventType.FullSync);
@@ -979,6 +1153,47 @@ namespace Marro.PacManUdon
{ {
SendEventSoon(NetworkEventType.GhostUpdate); SendEventSoon(NetworkEventType.GhostUpdate);
} }
public void DoTimeSequenceSync()
{
SendEventSoon(NetworkEventType.TimeSequenceSync);
}
private void Pause()
{
paused = true;
stepNext = false;
}
private void Resume()
{
paused = false;
stepNext = false;
}
private void Step()
{
paused = true;
stepNext = true;
}
public void PauseButtonPressed()
{
Pause();
SendEventSoon(NetworkEventType.Pause);
}
public void ResumeButtonPressed()
{
Resume();
SendEventSoon(NetworkEventType.Resume);
}
public void StepButtonPressed()
{
Step();
SendEventSoon(NetworkEventType.Step);
}
#endregion #endregion
} }
} }

View File

@@ -151,7 +151,11 @@ namespace Marro.PacManUdon
SetDirection(inputDirection + new Vector2(GridMoverTools.PositionToGrid(nextPosition).x - nextPosition.x, 0).normalized); SetDirection(inputDirection + new Vector2(GridMoverTools.PositionToGrid(nextPosition).x - nextPosition.x, 0).normalized);
} }
SetTargetDirection(inputDirection); // This is the direction most logic should assume pacman is moving, the actual direction may be different due to cornering SetTargetDirection(inputDirection); // This is the direction most logic should assume pacman is moving, the actual direction may be different due to cornering
networkManager.SendEventSoon(NetworkEventType.PacManTurn);
if (!followingPredefinedPath)
{
networkManager.SendEventSoon(NetworkEventType.PacManTurn);
}
} }
return nextPosition; return nextPosition;

View File

@@ -20,10 +20,7 @@ namespace Marro.PacManUdon
{ {
Debug.Log($"StartTimeSequence: {timeSequence}"); Debug.Log($"StartTimeSequence: {timeSequence}");
if (currentlyInTimeSequence) TimeSequenceTryEndCurrent();
{
TimeSequenceEndCurrent();
}
TimeSequencePrepareForStart(timeSequence); TimeSequencePrepareForStart(timeSequence);
@@ -35,12 +32,24 @@ namespace Marro.PacManUdon
TimeSequenceProgressToTime(timeSequenceSecondsPassed); TimeSequenceProgressToTime(timeSequenceSecondsPassed);
} }
private void TimeSequenceEndCurrent() private void TimeSequenceTryEndCurrent()
{ {
if (!currentlyInTimeSequence)
{
return;
}
Debug.Log($"{gameObject} TimeSequenceEndCurrent");
jumpingToTimeSequence = true; jumpingToTimeSequence = true;
TimeSequenceProgressToTime(100000f); TimeSequenceProgressToTime(100000f);
Debug.LogWarning($"{gameObject} TimeSequenceEndCurrent");
TryFinalizeTimeSequence();
if (waitingForTimeSequenceFinalize)
{
TimeSequenceExecuteFinalize(currentTimeSequence);
}
jumpingToTimeSequence = false; jumpingToTimeSequence = false;
} }
@@ -79,61 +88,40 @@ namespace Marro.PacManUdon
if (timeSequenceProgress >= timeSequenceKeyframeTimes.Length) if (timeSequenceProgress >= timeSequenceKeyframeTimes.Length)
{ {
currentlyInTimeSequence = false; currentlyInTimeSequence = false;
TimeSequencePrepareForFinish(currentTimeSequence); TimeSequenceFinish(currentTimeSequence);
break; break;
} }
} }
} }
private void TimeSequencePrepareForFinish(PacManTimeSequence timeSequence) private void TimeSequenceFinish(PacManTimeSequence timeSequence)
{ {
//if (networkManager.IsOwner)
//{
Debug.LogWarning($"{gameObject} TimeSequencePrepareForFinish");
TimeSequenceExecuteFinalize(timeSequence); TimeSequenceExecuteFinalize(timeSequence);
networkManager.SendEventSoon(NetworkEventType.TimeSequenceSync);
if (!jumpingToTimeSequence) if (!jumpingToTimeSequence)
{ {
TimeSequenceExecuteFinished(timeSequence); TimeSequenceExecuteFinished(timeSequence);
} }
//}
//else
//{
// waitingForTimeSequenceFinalize = true;
//}
} }
private void TryFinalizeTimeSequence() private void TimeSequenceSyncWithRemote(PacManTimeSequence currentTimeSequence, float timeSequenceSecondsPassed)
{ {
if (!waitingForTimeSequenceFinalize) // If the remote is in a time sequence but we're not, or we're in a different time sequence, or if we're behind,
// jump to the remote's time sequence.
if (!currentlyInTimeSequence
|| currentTimeSequence != this.currentTimeSequence
|| timeSequenceSecondsPassed < this.timeSequenceSecondsPassed)
{ {
return; StartTimeSequence(currentTimeSequence);
} }
TimeSequenceExecuteFinalize(currentTimeSequence); // If we're (now) in a time sequence, jump our progress to match the one on the remote
waitingForTimeSequenceFinalize = false; if (currentlyInTimeSequence)
{
TimeSequenceProgressToTime(timeSequenceSecondsPassed);
}
} }
//private void TimeSequenceSyncWithRemote(bool currentlyInTimeSequence, PacManTimeSequence currentTimeSequence, float timeSequenceSecondsPassed)
//{
// // If the remote is in a time sequence but we're not, or we're in a different time sequence, jump to the remote's time sequence.
// if (currentlyInTimeSequence && (!this.currentlyInTimeSequence || currentTimeSequence != this.currentTimeSequence))
// {
// StartTimeSequence(currentTimeSequence);
// }
// // If we're (now) in a time sequence, jump our progress to match the one on the remote
// if (this.currentlyInTimeSequence)
// {
// TimeSequenceProgressToTime(timeSequenceSecondsPassed);
// }
// // If the remote has finished it's time sequence and we have one waiting to be finalized, we can do so now
// if (!currentlyInTimeSequence)
// {
// TryFinalizeTimeSequence();
// }
//}
#region Events #region Events
public void JumpToTimeSequenceAttractScreenIntroduction() public void JumpToTimeSequenceAttractScreenIntroduction()