diff --git a/Runtime/Engine/Code/Extensions/Vector3Ex.cs b/Runtime/Engine/Code/Extensions/Vector3Ex.cs index 2d7a1a3..2f36045 100755 --- a/Runtime/Engine/Code/Extensions/Vector3Ex.cs +++ b/Runtime/Engine/Code/Extensions/Vector3Ex.cs @@ -23,6 +23,13 @@ namespace RebootKit.Engine.Extensions { } public static class float3Ex { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float3 With(this float3 vec, float? x = null, float? y = null, float? z = null) { + return new float3(x ?? vec.x, + y ?? vec.y, + z ?? vec.z); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsZero(this float3 vec, float epsilon = float.Epsilon) { return math.abs(vec.x) < epsilon && math.abs(vec.y) < epsilon && math.abs(vec.z) < epsilon; diff --git a/Runtime/Engine/Code/Services/Simulation/Actor.cs b/Runtime/Engine/Code/Services/Simulation/Actor.cs index 0aea1df..a1e2151 100755 --- a/Runtime/Engine/Code/Services/Simulation/Actor.cs +++ b/Runtime/Engine/Code/Services/Simulation/Actor.cs @@ -1,32 +1,25 @@ -using System; -using System.Threading; -using Cysharp.Threading.Tasks; -using RebootKit.Engine.Foundation; +using RebootKit.Engine.Foundation; using UnityEngine; namespace RebootKit.Engine.Services.Simulation { - public class Actor : MonoBehaviour { + public abstract class Actor : MonoBehaviour { [field: SerializeField] public SerializableGuid ActorGuid { get; private set; } = SerializableGuid.New(); - public bool IsInitialized { get; private set; } - - bool m_Simulate; - public bool Simulate { - get => m_Simulate; + bool m_IsPlaying = false; + public bool IsPlaying { + get { + return m_IsPlaying; + } set { - if (m_Simulate == value) { + if (m_IsPlaying == value) { return; } - m_Simulate = value; + m_IsPlaying = value; - if (!IsInitialized) { - return; - } - - if (m_Simulate) { + if (m_IsPlaying) { OnBeginPlay(); } else { OnEndPlay(); @@ -34,24 +27,10 @@ namespace RebootKit.Engine.Services.Simulation { } } - async void Start() { - IsInitialized = false; - await InitAsync(destroyCancellationToken); - IsInitialized = true; - - if (Simulate) { - OnBeginPlay(); - } + public virtual void OnSpawned() { } - void Update() { - if (m_Simulate && IsInitialized) { - OnTick(); - } - } - - protected virtual async UniTask InitAsync(CancellationToken cancellationToken) { - await UniTask.Yield(cancellationToken); + public virtual void OnDespawned() { } public virtual void OnBeginPlay() { @@ -60,7 +39,7 @@ namespace RebootKit.Engine.Services.Simulation { public virtual void OnEndPlay() { } - public virtual void OnTick() { + public virtual void OnTick(float deltaTime) { } } } \ No newline at end of file diff --git a/Runtime/Engine/Code/Services/Simulation/WorldService.cs b/Runtime/Engine/Code/Services/Simulation/WorldService.cs index 5dc69f0..7db9646 100755 --- a/Runtime/Engine/Code/Services/Simulation/WorldService.cs +++ b/Runtime/Engine/Code/Services/Simulation/WorldService.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; -using System.Linq; +using System; +using System.Collections.Generic; using System.Threading; using Cysharp.Threading.Tasks; +using R3; using RebootKit.Engine.Foundation; using UnityEngine; using UnityEngine.AddressableAssets; @@ -12,12 +13,11 @@ using UnityEngine.SceneManagement; using Logger = RebootKit.Engine.Foundation.Logger; namespace RebootKit.Engine.Services.Simulation { - public interface IWorldContext { - } - + public interface IWorldContext { } + public class WorldService : IService { static readonly Logger s_Logger = new Logger(nameof(WorldService)); - + enum WorldState { Unloaded, Loading, @@ -28,14 +28,31 @@ namespace RebootKit.Engine.Services.Simulation { WorldConfig m_Config; AsyncOperationHandle m_SceneInstance; + struct ActorData { + public Actor Actor; + public readonly bool ManagedByAddressabled; + + public ActorData(Actor actor, bool managedByAddressabled) { + Actor = actor; + ManagedByAddressabled = managedByAddressabled; + } + } + + readonly List m_Actors = new List(); + readonly IDisposable m_UpdateSubscription; + public IWorldContext Context { get; private set; } - - readonly List m_Actors = new List(); + + public WorldService() { + m_UpdateSubscription = Observable.EveryUpdate() + .Subscribe(_ => { Tick(Time.deltaTime); }); + } public void Dispose() { + m_UpdateSubscription.Dispose(); Unload(); } - + public async UniTask LoadAsync(WorldConfig worldConfig, CancellationToken cancellationToken) { await UniTask.WaitWhile(() => m_WorldState == WorldState.Loading, cancellationToken: cancellationToken); @@ -46,35 +63,24 @@ namespace RebootKit.Engine.Services.Simulation { m_SceneInstance = worldConfig.mainScene.LoadSceneAsync(LoadSceneMode.Additive, false); await m_SceneInstance.ToUniTask(cancellationToken: cancellationToken); - + await m_SceneInstance.Result.ActivateAsync(); SceneManager.SetActiveScene(m_SceneInstance.Result.Scene); foreach (GameObject root in m_SceneInstance.Result.Scene.GetRootGameObjects()) { if (root.TryGetComponent(out IWorldContext worldContext)) { - Assert.IsNull(Context, "WorldContext is already set. There should be only one WorldContext in the scene."); + Assert.IsNull(Context, + "WorldContext is already set. There should be only one WorldContext in the scene."); Context = worldContext; } foreach (Actor actor in root.GetComponentsInChildren()) { - RegisterActor(actor); + m_Actors.Add(new ActorData(actor, false)); } } m_WorldState = WorldState.Loaded; } - - public async UniTask LoadAsync(WorldConfig worldConfig, CancellationToken cancellationToken) where TRequiredActor : Actor { - await LoadAsync(worldConfig, cancellationToken); - - TRequiredActor actor = m_Actors.FirstOrDefault(t => t is TRequiredActor) as TRequiredActor; - if (actor is null) { - s_Logger.Error($"Actor of type {typeof(TRequiredActor)} not found in the scene"); - return null; - } - - return actor; - } public void Unload() { KillAllActors(); @@ -88,7 +94,13 @@ namespace RebootKit.Engine.Services.Simulation { Context = null; } - public async UniTask SpawnActor(AssetReferenceT asset, CancellationToken cancellationToken) where TActor : Actor { + public async UniTask SpawnActor(AssetReferenceT asset, + CancellationToken cancellationToken) where TActor : Actor { + if (m_WorldState != WorldState.Loaded) { + s_Logger.Error("World is not loaded. Cannot spawn actor."); + return null; + } + GameObject gameObject = await Addressables.InstantiateAsync(asset); if (cancellationToken.IsCancellationRequested) { asset.ReleaseInstance(gameObject); @@ -96,6 +108,9 @@ namespace RebootKit.Engine.Services.Simulation { } if (gameObject.TryGetComponent(out TActor actor)) { + actor.OnSpawned(); + actor.IsPlaying = true; + m_Actors.Add(new ActorData(actor, true)); return actor; } @@ -103,20 +118,52 @@ namespace RebootKit.Engine.Services.Simulation { return null; } - public void RegisterActor(Actor actor) { - m_Actors.Add(actor); - } - public void KillActor(Actor actor) { - Addressables.ReleaseInstance(actor.gameObject); + ActorData actorData = default; + bool found = false; + for (int i = m_Actors.Count - 1; i >= 0; i--) { + if (m_Actors[i].Actor == actor) { + found = true; + actorData = m_Actors[i]; + m_Actors.RemoveAt(i); + break; + } + } + Assert.IsTrue(found, $"Actor {actor.name} not found in the world actors list."); + + actor.IsPlaying = false; + actor.OnDespawned(); + + if (actorData.ManagedByAddressabled) { + Addressables.ReleaseInstance(actor.gameObject); + } } public void KillAllActors() { - foreach (Actor actor in m_Actors) { - Addressables.ReleaseInstance(actor.gameObject); + foreach (ActorData actorData in m_Actors) { + actorData.Actor.IsPlaying = false; + actorData.Actor.OnDespawned(); + + if (actorData.ManagedByAddressabled) { + Addressables.ReleaseInstance(actorData.Actor.gameObject); + } else { + UnityEngine.Object.Destroy(actorData.Actor.gameObject); + } } m_Actors.Clear(); } + + void Tick(float deltaTime) { + if (m_WorldState != WorldState.Loaded) { + return; + } + + foreach (ActorData actorData in m_Actors) { + if (actorData.Actor.IsPlaying) { + actorData.Actor.OnTick(deltaTime); + } + } + } } } \ No newline at end of file