Compare commits

...

49 Commits

Author SHA1 Message Date
Syntriax 1545291942 fix: Issues With Behaviours Entering / Leaving the Hierarchy Callbacks Not Firing 2024-11-10 20:21:53 +03:00
Syntriax 81625abd25 refactor: Improved & Fixed Issues with Transforms with Parents 2024-11-10 19:18:44 +03:00
Syntriax ea94bed00d fix: Transform Rotation Miscalculation 2024-11-09 17:15:57 +03:00
Syntriax 12cb144688 feat: Math.Sin & Math.Cos 2024-11-06 23:00:10 +03:00
Syntriax 85f0555c59 fix: Duplicate Values on Behaviour Collector 2024-11-03 20:31:51 +03:00
Syntriax 55ed8b84f6 Refactor: CollisionDetectionInformation Names 2024-11-03 20:25:28 +03:00
Syntriax 4856800f5f feat: Improved Entering & Exiting of Hierarchy Objects 2024-11-03 20:13:25 +03:00
Syntriax cc44e1ea69 fix: OnEnterHierarchy Not Being Called Correctly 2024-11-03 19:58:57 +03:00
Syntriax cb60c71184 feat: ICoroutineYield for Delaying Coroutines 2024-11-02 22:27:04 +03:00
Syntriax eb445604e8 fix: Math Method Mistakenly Declared as an Extension 2024-11-02 13:53:58 +03:00
Syntriax 8eb34a0a6d fix: Transform Local Fields Being Miscalculated 2024-11-02 13:47:08 +03:00
Syntriax 9f522bdb66 fix: Physics Engine Not Resolving Static Objects Correctly 2024-11-02 09:39:53 +03:00
Syntriax d7f0b76485 fix: Circle Transformation Miscalculation 2024-11-02 09:38:01 +03:00
Syntriax 90370a2b43 feat: Physics Coroutine Manager 2024-10-29 23:14:08 +03:00
Syntriax 0f3f1594d0 refactor: Wrong Accessibility Level On Field 2024-10-29 22:45:08 +03:00
Syntriax 24b50eba12 fix: IGameObjects Not Being Initialized Properly 2024-10-26 22:45:24 +03:00
Syntriax 6bc9043a86 feat: Basic CoroutineManager 2024-10-26 13:47:39 +03:00
Syntriax 43f1749b04 refactor: IHierarchy Integration with Overall Code Base 2024-10-26 13:46:04 +03:00
Syntriax 62e50aefc1 feat: IPhysicsEngine2D Events 2024-10-25 23:10:32 +03:00
Syntriax 9c2b098821 feat: IGameManager Events 2024-10-25 23:10:20 +03:00
Syntriax 91aa26e15a BREAKING CHANGE: Added IHierarchObject with Hierarchy Enter & Exit 2024-10-22 22:05:22 +03:00
Syntriax cf8a5de580 fix: Behaviour Collector Not Recognizing Registered GameObjects After Initial Setup 2024-10-22 21:46:26 +03:00
Syntriax 1f8fa78b76 BREAKING CHANGE: Renamed IInitialize.Initialized to IsInitialized 2024-10-22 20:57:12 +03:00
Syntriax fdc38fc800 feat: Engine.Core.MathExtensions 2024-10-20 15:12:20 +03:00
Syntriax fc34a60f30 chore: Another Code Style Mistake 2024-10-20 15:11:51 +03:00
Syntriax eca23c5b89 feat: Math.Lerp 2024-10-20 14:47:42 +03:00
Syntriax f08f721f52 chore: Fixed Indentation Mistake 2024-10-20 14:40:58 +03:00
Syntriax fb402acc30 fix: BehaviourCollector Skipping Unregistered GameObjects 2024-10-05 23:08:44 +03:00
Syntriax 923b25e26e feat: Missing Line Extension Methods 2024-10-05 22:59:50 +03:00
Syntriax 2bcd1c5a89 fix: BehaviourController Manual AddBehaviour Assign Exceptions 2024-09-30 19:55:14 +03:00
Syntriax 40d1ce7c68 chore: Removed Unnecessary Method from IButtonInputs 2024-09-30 19:24:14 +03:00
Syntriax e7c80871fe refactor: Renamed Behaviour to BehaviourBase & BehaviourOverride to Behaviour 2024-09-30 12:18:51 +03:00
Syntriax c51eda49bf fix: GameManager Instantiation/Deletion during Update causing Array Changed Exception 2024-09-27 18:38:24 +03:00
Syntriax 0f8a7db567 fix: EngineTime DeltaTimeFrame 2024-09-26 22:59:25 +03:00
Syntriax ffa0128813 feat: IPhysicsUpdate 2024-09-26 18:55:15 +03:00
Syntriax 15984bcc06 refactor: IButtonInputs from Actions to Delegate 2024-09-25 14:36:57 +03:00
Syntriax ef21cdf213 refactor: Actions to Delegates 2024-07-15 01:13:39 +03:00
Syntriax 2cf6135063 refactor: Renamed BehaviourCacher to BehaviourCollector 2024-02-09 09:43:15 +03:00
Syntriax be06575f91 feat: BehaviourExtensions.FindBehaviour 2024-02-08 17:58:15 +03:00
Syntriax ed6975bf24 fix: Null Reference Error on ITransform.SetParent 2024-02-07 14:24:19 +03:00
Syntriax d9660c08b1 feat: Collider RigidBody Reference Update on Parent Change 2024-02-07 12:33:07 +03:00
Syntriax 3902f1caca feat: Parent Change Propagation to Children 2024-02-07 12:32:55 +03:00
Syntriax 14e3337daa feat: BehaviourControllerExtensions
- TryGetBehaviourInParent
- GetBehaviourInParent
- TryGetBehaviourInChildren
- GetBehaviourInChildren
2024-02-07 11:53:57 +03:00
Syntriax f729cdc0a8 revert: refactor: ITransformWithGameObject
This reverts commit f96c58cbd4.
2024-02-07 11:45:14 +03:00
Syntriax c767e1e856 docs(core): Parent & Child Methods 2024-02-06 17:42:24 +03:00
Syntriax f96c58cbd4 refactor: ITransformWithGameObject 2024-02-06 17:38:11 +03:00
Syntriax fed288859f feat: IAssignableGameObject to ITransform
I originally didn't want ITransform to have a reference to any IGameObject, since it didn't seem necessary to couple these two, and to make ITransform more flexible and reusable but without it we can't get a reference to the IGameObject(s) that's using that ITransform without doing some very stupid workarounds. I'll try to find a better way for this.
2024-02-06 17:32:39 +03:00
Syntriax 6e4c9b0ef8 feat: Transform Hierarchy System 2024-02-06 15:55:07 +03:00
Syntriax b931abb735 feat: Shape to Vector2D Overlap 2024-02-06 10:56:54 +03:00
54 changed files with 1542 additions and 413 deletions

View File

@ -1,5 +1,3 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
@ -10,7 +8,7 @@ public interface IAssignable
/// <summary> /// <summary>
/// Event triggered when the <see cref="IAssignable"/>'s fields are unassigned and completely ready to recycle. /// Event triggered when the <see cref="IAssignable"/>'s fields are unassigned and completely ready to recycle.
/// </summary> /// </summary>
Action<IAssignable>? OnUnassigned { get; set; } event OnUnassignedDelegate? OnUnassigned;
/// <summary> /// <summary>
/// Unassign <see cref="IAssignable"/>'s all fields and make it ready to recycle. /// Unassign <see cref="IAssignable"/>'s all fields and make it ready to recycle.
@ -19,4 +17,6 @@ public interface IAssignable
/// <see cref="true"/>, if the fields are unsigned successfully, <see cref="false"/> if not. /// <see cref="true"/>, if the fields are unsigned successfully, <see cref="false"/> if not.
/// </returns> /// </returns>
bool Unassign(); bool Unassign();
delegate void OnUnassignedDelegate(IAssignable sender);
} }

View File

@ -1,5 +1,3 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
@ -10,7 +8,7 @@ public interface IAssignableBehaviourController : IAssignable
/// <summary> /// <summary>
/// Event triggered when the <see cref="IBehaviourController"/> value has has been assigned a new value. /// Event triggered when the <see cref="IBehaviourController"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } event OnBehaviourControllerAssignedDelegate? OnBehaviourControllerAssigned;
/// <inheritdoc cref="IBehaviourController" /> /// <inheritdoc cref="IBehaviourController" />
IBehaviourController BehaviourController { get; } IBehaviourController BehaviourController { get; }
@ -23,4 +21,6 @@ public interface IAssignableBehaviourController : IAssignable
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not. /// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns> /// </returns>
bool Assign(IBehaviourController behaviourController); bool Assign(IBehaviourController behaviourController);
delegate void OnBehaviourControllerAssignedDelegate(IAssignableBehaviourController sender);
} }

View File

@ -1,5 +1,3 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
@ -10,7 +8,7 @@ public interface IAssignableEntity : IAssignable
/// <summary> /// <summary>
/// Event triggered when the <see cref="IEntity"/> value has has been assigned a new value. /// Event triggered when the <see cref="IEntity"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableEntity>? OnEntityAssigned { get; set; } event OnEntityAssignedDelegate? OnEntityAssigned;
/// <inheritdoc cref="IEntity" /> /// <inheritdoc cref="IEntity" />
IEntity Entity { get; } IEntity Entity { get; }
@ -23,4 +21,6 @@ public interface IAssignableEntity : IAssignable
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not. /// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns> /// </returns>
bool Assign(IEntity entity); bool Assign(IEntity entity);
delegate void OnEntityAssignedDelegate(IAssignableEntity sender);
} }

View File

@ -1,5 +1,3 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
@ -10,7 +8,7 @@ public interface IAssignableGameManager : IAssignable
/// <summary> /// <summary>
/// Event triggered when the <see cref="IGameManager"/> value has has been assigned a new value. /// Event triggered when the <see cref="IGameManager"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableGameManager>? OnGameManagerAssigned { get; set; } event OnGameManagerAssignedDelegate? OnGameManagerAssigned;
/// <inheritdoc cref="IGameManager" /> /// <inheritdoc cref="IGameManager" />
IGameManager GameManager { get; } IGameManager GameManager { get; }
@ -23,4 +21,6 @@ public interface IAssignableGameManager : IAssignable
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not. /// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns> /// </returns>
bool Assign(IGameManager gameManager); bool Assign(IGameManager gameManager);
delegate void OnGameManagerAssignedDelegate(IAssignableGameManager sender);
} }

View File

@ -1,5 +1,3 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
@ -10,7 +8,7 @@ public interface IAssignableGameObject : IAssignable
/// <summary> /// <summary>
/// Event triggered when the <see cref="IGameObject"/> value has has been assigned a new value. /// Event triggered when the <see cref="IGameObject"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableGameObject>? OnGameObjectAssigned { get; set; } event OnGameObjectAssignedDelegate? OnGameObjectAssigned;
/// <inheritdoc cref="IGameObject" /> /// <inheritdoc cref="IGameObject" />
IGameObject GameObject { get; } IGameObject GameObject { get; }
@ -23,4 +21,6 @@ public interface IAssignableGameObject : IAssignable
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not. /// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns> /// </returns>
bool Assign(IGameObject gameObject); bool Assign(IGameObject gameObject);
delegate void OnGameObjectAssignedDelegate(IAssignableGameObject sender);
} }

View File

@ -1,5 +1,3 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
@ -10,7 +8,7 @@ public interface IAssignableStateEnable : IAssignable
/// <summary> /// <summary>
/// Event triggered when the <see cref="IStateEnable"/> value has has been assigned a new value. /// Event triggered when the <see cref="IStateEnable"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } event OnStateEnableAssignedDelegate? OnStateEnableAssigned;
/// <inheritdoc cref="IStateEnable" /> /// <inheritdoc cref="IStateEnable" />
IStateEnable StateEnable { get; } IStateEnable StateEnable { get; }
@ -23,4 +21,6 @@ public interface IAssignableStateEnable : IAssignable
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not. /// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns> /// </returns>
bool Assign(IStateEnable stateEnable); bool Assign(IStateEnable stateEnable);
delegate void OnStateEnableAssignedDelegate(IAssignableStateEnable sender);
} }

View File

@ -1,5 +1,3 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
@ -10,7 +8,7 @@ public interface IAssignableTransform : IAssignable
/// <summary> /// <summary>
/// Event triggered when the <see cref="ITransform"/> value has has been assigned a new value. /// Event triggered when the <see cref="ITransform"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableTransform>? OnTransformAssigned { get; set; } event OnTransformAssignedDelegate? OnTransformAssigned;
/// <inheritdoc cref="ITransform" /> /// <inheritdoc cref="ITransform" />
ITransform Transform { get; } ITransform Transform { get; }
@ -23,4 +21,6 @@ public interface IAssignableTransform : IAssignable
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not. /// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns> /// </returns>
bool Assign(ITransform transform); bool Assign(ITransform transform);
delegate void OnTransformAssignedDelegate(IAssignableTransform sender);
} }

View File

@ -0,0 +1,64 @@
namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents an <see cref="IEntity"/> that can enter and exit a hierarchy within the <see cref="IGameManager"/> system.
/// This interface allows for tracking the object's presence in the hierarchy and provides events
/// for notifying when the see enters or exits the hierarchy.
/// </summary>
public interface IHierarchyObject : IEntity, INameable
{
/// <summary>
/// Event triggered when the <see cref="IEntity"/> enters the hierarchy.
/// </summary>
event OnEnteredHierarchyDelegate? OnEnteredHierarchy;
/// <summary>
/// Event triggered when the <see cref="IEntity"/> exits the hierarchy.
/// </summary>
event OnExitedHierarchyDelegate? OnExitedHierarchy;
/// <summary>
/// Gets the <see cref="IGameManager"/> associated with this <see cref="IEntity"/> , if any.
/// </summary>
IGameManager? GameManager { get; }
/// <summary>
/// Indicates whether the <see cref="IEntity"/> is currently in the hierarchy.
/// </summary>
bool IsInHierarchy { get; }
/// <summary>
/// Internal method to handle entering the hierarchy.
/// This should be called by the system to properly manage hierarchy states.
/// </summary>
/// <param name="gameManager">The game manager that is managing this hierarchy.</param>
/// <returns>
/// <see cref="true"/> if the <see cref="IEntity"/> successfully entered the hierarchy;
/// <see cref="false"/> if it failed to do so.
/// </returns>
internal bool EnterHierarchy(IGameManager gameManager);
/// <summary>
/// Internal method to handle exiting the hierarchy.
/// This should be called by the system to properly manage hierarchy states.
/// </summary>
/// <returns>
/// <see cref="true"/> if the <see cref="IEntity"/> successfully exited the hierarchy;
/// <see cref="false"/> if it failed to do so.
/// </returns>
internal bool ExitHierarchy();
/// <summary>
/// Delegate type for the event triggered when the <see cref="IEntity"/> enters the hierarchy of a <see cref="IGameManager">.
/// </summary>
/// <param name="sender">The <see cref="IEntity"/> that entered the hierarchy.</param>
/// <param name="gameManager">The <see cref="IGameManager"/> that the <see cref="IEntity"/> has entered it's hierarchy.</param>
delegate void OnEnteredHierarchyDelegate(IHierarchyObject sender, IGameManager gameManager);
/// <summary>
/// Delegate type for the event triggered when the <see cref="IEntity"/> exits the hierarchy of a <see cref="IGameManager">.
/// </summary>
/// <param name="sender">The <see cref="IEntity"/> that exited the hierarchy.</param>
/// <param name="gameManager">The <see cref="IGameManager"/> that the <see cref="IEntity"/> has exited it's hierarchy.</param>
delegate void OnExitedHierarchyDelegate(IHierarchyObject sender, IGameManager gameManager);
}

View File

@ -1,16 +1,18 @@
using System; using System;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
public abstract class BaseEntity : IEntity public abstract class BaseEntity : IEntity
{ {
public Action<IEntity, string>? OnIdChanged { get; set; } = null; public event IEntity.OnIdChangedDelegate? OnIdChanged = null;
public Action<IAssignable>? OnUnassigned { get; set; } = null; public event IInitialize.OnInitializedDelegate? OnInitialized = null;
public Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } = null; public event IInitialize.OnFinalizedDelegate? OnFinalized = null;
public Action<IInitialize>? OnInitialized { get; set; } = null; public event IAssignableStateEnable.OnStateEnableAssignedDelegate? OnStateEnableAssigned = null;
public Action<IInitialize>? OnFinalized { get; set; } = null; public event IAssignable.OnUnassignedDelegate? OnUnassigned = null;
private IStateEnable _stateEnable = null!; private IStateEnable _stateEnable = null!;
@ -37,7 +39,7 @@ public abstract class BaseEntity : IEntity
} }
} }
public bool Initialized public bool IsInitialized
{ {
get => _initialized; get => _initialized;
private set private set
@ -55,7 +57,7 @@ public abstract class BaseEntity : IEntity
public bool Assign(IStateEnable stateEnable) public bool Assign(IStateEnable stateEnable)
{ {
if (Initialized) if (IsInitialized)
return false; return false;
_stateEnable = stateEnable; _stateEnable = stateEnable;
@ -67,11 +69,13 @@ public abstract class BaseEntity : IEntity
protected virtual void UnassignInternal() { } protected virtual void UnassignInternal() { }
public bool Unassign() public bool Unassign()
{ {
if (Initialized) if (IsInitialized)
return false; return false;
UnassignInternal(); UnassignInternal();
_stateEnable = null!;
_stateEnable.Unassign();
OnUnassigned?.Invoke(this); OnUnassigned?.Invoke(this);
return true; return true;
} }
@ -79,24 +83,26 @@ public abstract class BaseEntity : IEntity
protected virtual void InitializeInternal() { } protected virtual void InitializeInternal() { }
public bool Initialize() public bool Initialize()
{ {
if (Initialized) if (IsInitialized)
return false; return false;
NotAssignedException.Check(this, _stateEnable);
InitializeInternal(); InitializeInternal();
Initialized = true; IsInitialized = true;
return true; return true;
} }
protected virtual void FinalizeInternal() { } protected virtual void FinalizeInternal() { }
public bool Finalize() public bool Finalize()
{ {
if (!Initialized) if (!IsInitialized)
return false; return false;
FinalizeInternal(); FinalizeInternal();
Initialized = false; IsInitialized = false;
return true; return true;
} }

View File

@ -1,5 +1,3 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
@ -10,7 +8,7 @@ public interface IBehaviour : IEntity, IAssignableBehaviourController, IAssignab
/// <summary> /// <summary>
/// Event triggered when the priority of the <see cref="IBehaviour"/> changes. /// Event triggered when the priority of the <see cref="IBehaviour"/> changes.
/// </summary> /// </summary>
Action<IBehaviour>? OnPriorityChanged { get; set; } event OnPriorityChangedDelegate? OnPriorityChanged;
/// <summary> /// <summary>
/// The priority of the <see cref="IBehaviour"/>. /// The priority of the <see cref="IBehaviour"/>.
@ -21,4 +19,6 @@ public interface IBehaviour : IEntity, IAssignableBehaviourController, IAssignab
/// The value indicating whether the <see cref="IBehaviour"/> is active. /// The value indicating whether the <see cref="IBehaviour"/> is active.
/// </summary> /// </summary>
bool IsActive { get; } bool IsActive { get; }
delegate void OnPriorityChangedDelegate(IBehaviour sender, int previousPriority);
} }

View File

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@ -7,32 +6,32 @@ namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
/// Represents a controller for managing <see cref="IBehaviour"/>s and notify them accordingly about the engine's updates. Connected to an <see cref="IGameObject"/>. /// Represents a controller for managing <see cref="IBehaviour"/>s and notify them accordingly about the engine's updates. Connected to an <see cref="IGameObject"/>.
/// </summary> /// </summary>
public interface IBehaviourController : IAssignableGameObject, IEnumerable<IBehaviour> public interface IBehaviourController : IInitialize, IAssignableGameObject, IEnumerable<IBehaviour>
{ {
/// <summary> /// <summary>
/// Event triggered before the update of <see cref="IBehaviour"/>s. /// Event triggered before the update of <see cref="IBehaviour"/>s.
/// </summary> /// </summary>
Action<IBehaviourController>? OnPreUpdate { get; set; } event OnPreUpdateDelegate? OnPreUpdate;
/// <summary> /// <summary>
/// Event triggered during the update of <see cref="IBehaviour"/>s. /// Event triggered during the update of <see cref="IBehaviour"/>s.
/// </summary> /// </summary>
Action<IBehaviourController>? OnUpdate { get; set; } event OnUpdateDelegate? OnUpdate;
/// <summary> /// <summary>
/// Event triggered before the drawing phase. /// Event triggered before the drawing phase.
/// </summary> /// </summary>
Action<IBehaviourController>? OnPreDraw { get; set; } event OnPreDrawDelegate? OnPreDraw;
/// <summary> /// <summary>
/// Event triggered when a <see cref="IBehaviour"/> is added to the <see cref="IBehaviourController"/>. /// Event triggered when a <see cref="IBehaviour"/> is added to the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
Action<IBehaviourController, IBehaviour>? OnBehaviourAdded { get; set; } event OnBehaviourAddedDelegate? OnBehaviourAdded;
/// <summary> /// <summary>
/// Event triggered when a <see cref="IBehaviour"/> is removed from the <see cref="IBehaviourController"/>. /// Event triggered when a <see cref="IBehaviour"/> is removed from the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
Action<IBehaviourController, IBehaviour>? OnBehaviourRemoved { get; set; } event OnBehaviourRemovedDelegate? OnBehaviourRemoved;
/// <summary> /// <summary>
/// Adds a <see cref="IBehaviour"/> to the <see cref="IBehaviourController"/>. /// Adds a <see cref="IBehaviour"/> to the <see cref="IBehaviourController"/>.
@ -102,4 +101,11 @@ public interface IBehaviourController : IAssignableGameObject, IEnumerable<IBeha
/// Performs pre-draw operations. /// Performs pre-draw operations.
/// </summary> /// </summary>
void UpdatePreDraw(); void UpdatePreDraw();
delegate void OnPreUpdateDelegate(IBehaviourController sender);
delegate void OnUpdateDelegate(IBehaviourController sender);
delegate void OnPreDrawDelegate(IBehaviourController sender);
delegate void OnBehaviourAddedDelegate(IBehaviourController sender, IBehaviour behaviourAdded);
delegate void OnBehaviourRemovedDelegate(IBehaviourController sender, IBehaviour behaviourRemoved);
} }

View File

@ -0,0 +1,8 @@
using System.Collections;
namespace Syntriax.Engine.Core.Abstract;
public interface ICoroutineYield
{
bool Yield();
}

View File

@ -1,5 +1,3 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
@ -11,10 +9,12 @@ public interface IEntity : IInitialize, IAssignableStateEnable
/// Event triggered when the <see cref="Id"/> of the <see cref="IEntity"/> changes. /// Event triggered when the <see cref="Id"/> of the <see cref="IEntity"/> changes.
/// The string action parameter is the previous <see cref="Id"/> of the <see cref="IEntity"/>. /// The string action parameter is the previous <see cref="Id"/> of the <see cref="IEntity"/>.
/// </summary> /// </summary>
Action<IEntity, string>? OnIdChanged { get; set; } event OnIdChangedDelegate? OnIdChanged;
/// <summary> /// <summary>
/// The ID of the <see cref="IEntity"/>. /// The ID of the <see cref="IEntity"/>.
/// </summary> /// </summary>
string Id { get; set; } string Id { get; set; }
delegate void OnIdChangedDelegate(IEntity sender, string previousId);
} }

View File

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
@ -8,15 +7,35 @@ namespace Syntriax.Engine.Core.Abstract;
/// </summary> /// </summary>
public interface IGameManager : IEntity, IEnumerable<IGameObject> public interface IGameManager : IEntity, IEnumerable<IGameObject>
{ {
/// <summary>
/// Event triggered when <see cref="Update(EngineTime)"/> is called on the <see cref="IGameManager"/>.
/// </summary>
event OnUpdateDelegate? OnUpdate;
/// <summary>
/// Event triggered when <see cref="PreDraw"/> is called on the <see cref="IGameManager"/>.
/// </summary>
event OnPreDawDelegate? OnPreDraw;
/// <summary> /// <summary>
/// Event triggered when a <see cref="IGameObject"/> is registered to the <see cref="IGameManager"/>. /// Event triggered when a <see cref="IGameObject"/> is registered to the <see cref="IGameManager"/>.
/// </summary> /// </summary>
Action<IGameManager, IGameObject>? OnGameObjectRegistered { get; set; } event OnGameObjectRegisteredDelegate? OnGameObjectRegistered;
/// <summary> /// <summary>
/// Event triggered when a <see cref="IGameObject"/> is unregistered from the <see cref="IGameManager"/>. /// Event triggered when a <see cref="IGameObject"/> is unregistered from the <see cref="IGameManager"/>.
/// </summary> /// </summary>
Action<IGameManager, IGameObject>? OnGameObjectUnRegistered { get; set; } event OnGameObjectUnRegisteredDelegate? OnGameObjectUnRegistered;
/// <summary>
/// Event triggered when a <see cref="IHierarchyObject"/> is registered to the <see cref="IGameManager"/>.
/// </summary>
event OnHierarchyObjectRegisteredDelegate? OnHierarchyObjectRegistered;
/// <summary>
/// Event triggered when a <see cref="IHierarchyObject"/> is unregistered from the <see cref="IGameManager"/>.
/// </summary>
event OnHierarchyObjectUnRegisteredDelegate? OnHierarchyObjectUnRegistered;
/// <summary> /// <summary>
/// Gets a read-only list of <see cref="IGameObject"/>s managed by the <see cref="IGameManager"/>. /// Gets a read-only list of <see cref="IGameObject"/>s managed by the <see cref="IGameManager"/>.
@ -24,10 +43,15 @@ public interface IGameManager : IEntity, IEnumerable<IGameObject>
IReadOnlyList<IGameObject> GameObjects { get; } IReadOnlyList<IGameObject> GameObjects { get; }
/// <summary> /// <summary>
/// Registers a <see cref="IGameObject"/> to the <see cref="IGameManager"/>. /// Gets a read-only list of <see cref="IHierarchyObject"/>s managed by the <see cref="IGameManager"/>.
/// </summary> /// </summary>
/// <param name="gameObject">The <see cref="IGameObject"/> to register.</param> IReadOnlyList<IHierarchyObject> HierarchyObjects { get; }
void RegisterGameObject(IGameObject gameObject);
/// <summary>
/// Registers an <see cref="IHierarchyObject"/> to the <see cref="IGameManager"/>.
/// </summary>
/// <param name="hierarchyObject">The <see cref="IHierarchyObject"/> to register.</param>
void Register(IHierarchyObject hierarchyObject);
/// <summary> /// <summary>
/// Instantiates a <see cref="IGameObject"/> of type T with the given arguments and registers it to the <see cref="IGameManager"/>. /// Instantiates a <see cref="IGameObject"/> of type T with the given arguments and registers it to the <see cref="IGameManager"/>.
@ -38,11 +62,10 @@ public interface IGameManager : IEntity, IEnumerable<IGameObject>
T InstantiateGameObject<T>(params object?[]? args) where T : class, IGameObject; T InstantiateGameObject<T>(params object?[]? args) where T : class, IGameObject;
/// <summary> /// <summary>
/// Removes a <see cref="IGameObject"/> from the <see cref="IGameManager"/>. /// Removes an <see cref="IHierarchyObject"/> from the <see cref="IGameManager"/>.
/// </summary> /// </summary>
/// <param name="gameObject">The <see cref="IGameObject"/> to remove.</param> /// <param name="hierarchyObject">The <see cref="IHierarchyObject"/> to remove.</param>
/// <returns>The removed <see cref="IGameObject"/>.</returns> void Remove(IHierarchyObject hierarchyObject);
IGameObject RemoveGameObject(IGameObject gameObject);
/// <summary> /// <summary>
/// Updates the <see cref="IGameManager"/> with the given engine time data. /// Updates the <see cref="IGameManager"/> with the given engine time data.
@ -54,4 +77,12 @@ public interface IGameManager : IEntity, IEnumerable<IGameObject>
/// Performs operations that should be done before the draw calls. /// Performs operations that should be done before the draw calls.
/// </summary> /// </summary>
void PreDraw(); void PreDraw();
delegate void OnUpdateDelegate(IGameManager sender, EngineTime time);
delegate void OnPreDawDelegate(IGameManager sender);
delegate void OnGameObjectRegisteredDelegate(IGameManager sender, IGameObject gameObjectRegistered);
delegate void OnGameObjectUnRegisteredDelegate(IGameManager sender, IGameObject gameObjectUnregistered);
delegate void OnHierarchyObjectRegisteredDelegate(IGameManager sender, IHierarchyObject hierarchyObjectRegistered);
delegate void OnHierarchyObjectUnRegisteredDelegate(IGameManager sender, IHierarchyObject hierarchyObjectUnregistered);
} }

View File

@ -1,19 +1,19 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
/// Represents a game object with various properties and functionalities. /// Represents a game object with various properties and functionalities.
/// </summary> /// </summary>
public interface IGameObject : IEntity, IAssignableGameManager, IAssignableTransform, IAssignableBehaviourController, INameable, IInitialize public interface IGameObject : IEntity, IHierarchyObject, IAssignableTransform, IAssignableBehaviourController, INameable, IInitialize
{ {
/// <summary> /// <summary>
/// Event triggered when the <see cref="Update"/> method is called. /// Event triggered when the <see cref="Update"/> method is called.
/// </summary> /// </summary>
Action<IGameObject>? OnUpdated { get; set; } event OnUpdatedDelegate? OnUpdated;
/// <summary> /// <summary>
/// Updates the game object. /// Updates the game object.
/// </summary> /// </summary>
void Update(); void Update();
delegate void OnUpdatedDelegate(IGameObject sender);
} }

View File

@ -1,5 +1,3 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
@ -10,17 +8,17 @@ public interface IInitialize
/// <summary> /// <summary>
/// Event triggered when the <see cref="Initialize"/> method is called successfully. /// Event triggered when the <see cref="Initialize"/> method is called successfully.
/// </summary> /// </summary>
Action<IInitialize>? OnInitialized { get; set; } event OnInitializedDelegate? OnInitialized;
/// <summary> /// <summary>
/// Event triggered when the <see cref="Finalize"/> method is called successfully. /// Event triggered when the <see cref="Finalize"/> method is called successfully.
/// </summary> /// </summary>
Action<IInitialize>? OnFinalized { get; set; } event OnFinalizedDelegate? OnFinalized;
/// <summary> /// <summary>
/// The value indicating whether the entity has been initialized. /// The value indicating whether the entity has been initialized.
/// </summary> /// </summary>
bool Initialized { get; } bool IsInitialized { get; }
/// <summary> /// <summary>
/// Initializes the entity. /// Initializes the entity.
@ -33,4 +31,7 @@ public interface IInitialize
/// </summary> /// </summary>
/// <returns><see cref="true"/> if finalization is successful, otherwise <see cref="false"/>.</returns> /// <returns><see cref="true"/> if finalization is successful, otherwise <see cref="false"/>.</returns>
bool Finalize(); bool Finalize();
delegate void OnInitializedDelegate(IInitialize sender);
delegate void OnFinalizedDelegate(IInitialize sender);
} }

View File

@ -1,5 +1,3 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
@ -10,10 +8,12 @@ public interface INameable
/// <summary> /// <summary>
/// Event triggered when the name of the entity changes. /// Event triggered when the name of the entity changes.
/// </summary> /// </summary>
Action<IEntity>? OnNameChanged { get; set; } event OnNameChangedDelegate? OnNameChanged;
/// <summary> /// <summary>
/// The name of the entity. /// The name of the entity.
/// </summary> /// </summary>
string Name { get; set; } string Name { get; set; }
delegate void OnNameChangedDelegate(INameable sender, string previousName);
} }

View File

@ -1,5 +1,3 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
@ -10,10 +8,12 @@ public interface IStateEnable : IAssignableEntity
/// <summary> /// <summary>
/// Event triggered when the <see cref="Enabled"/> state of the <see cref="IStateEnable"/> changes. /// Event triggered when the <see cref="Enabled"/> state of the <see cref="IStateEnable"/> changes.
/// </summary> /// </summary>
Action<IStateEnable>? OnEnabledChanged { get; set; } event OnNameChangedDelegate? OnEnabledChanged;
/// <summary> /// <summary>
/// The value indicating whether the <see cref="IStateEnable"/> is enabled. /// The value indicating whether the <see cref="IStateEnable"/> is enabled.
/// </summary> /// </summary>
bool Enabled { get; set; } bool Enabled { get; set; }
delegate void OnNameChangedDelegate(IStateEnable sender, bool previousState);
} }

View File

@ -1,39 +1,105 @@
using System; using System;
using System.Collections.Generic;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
/// Represents the transformation properties of an object such as position, scale, and rotation. /// Represents the transformation properties of an object such as position, scale, and rotation.
/// </summary> /// </summary>
public interface ITransform public interface ITransform : IAssignableGameObject, IEnumerable<ITransform>
{ {
/// <summary> /// <summary>
/// Event triggered when the <see cref="Position"/> of the <see cref="ITransform"/> changes. /// Event triggered when the <see cref="Position"/> of the <see cref="ITransform"/> changes.
/// </summary> /// </summary>
Action<ITransform>? OnPositionChanged { get; set; } event OnPositionChangedDelegate? OnPositionChanged;
/// <summary> /// <summary>
/// Event triggered when the <see cref="Scale"/> of the <see cref="ITransform"/> changes. /// Event triggered when the <see cref="Scale"/> of the <see cref="ITransform"/> changes.
/// </summary> /// </summary>
Action<ITransform>? OnScaleChanged { get; set; } event OnScaleChangedDelegate? OnScaleChanged;
/// <summary> /// <summary>
/// Event triggered when the <see cref="Rotation"/> of the <see cref="ITransform"/> changes. /// Event triggered when the <see cref="Rotation"/> of the <see cref="ITransform"/> changes.
/// </summary> /// </summary>
Action<ITransform>? OnRotationChanged { get; set; } event OnRotationChangedDelegate? OnRotationChanged;
/// <summary> /// <summary>
/// The position of the <see cref="ITransform"/> in 2D space. /// Event triggered when the <see cref="Parent"/> of the <see cref="ITransform"/> changes. The second parameter is the old <see cref="ITransform"/>.
/// </summary>
event OnParentChangedDelegate? OnParentChanged;
/// <summary>
/// Event triggered when a new <see cref="ITransform"/> is added to the <see cref="Children"/>.
/// </summary>
event OnChildrenAddedDelegate? OnChildrenAdded;
/// <summary>
/// Event triggered when an <see cref="ITransform"/> is removed from the <see cref="Children"/>.
/// </summary>
event OnChildrenRemovedDelegate? OnChildrenRemoved;
/// <summary>
/// The world position of the <see cref="ITransform"/> in 2D space.
/// </summary> /// </summary>
Vector2D Position { get; set; } Vector2D Position { get; set; }
/// <summary> /// <summary>
/// The scale of the <see cref="ITransform"/>. /// The world scale of the <see cref="ITransform"/>.
/// </summary> /// </summary>
Vector2D Scale { get; set; } Vector2D Scale { get; set; }
/// <summary> /// <summary>
/// The rotation of the <see cref="ITransform"/> in degrees. /// The world rotation of the <see cref="ITransform"/> in degrees.
/// </summary> /// </summary>
float Rotation { get; set; } float Rotation { get; set; }
/// <summary>
/// The local position of the <see cref="ITransform"/> in 2D space.
/// </summary>
Vector2D LocalPosition { get; set; }
/// <summary>
/// The local scale of the <see cref="ITransform"/>.
/// </summary>
Vector2D LocalScale { get; set; }
/// <summary>
/// The local rotation of the <see cref="ITransform"/> in degrees.
/// </summary>
float LocalRotation { get; set; }
/// <summary>
/// The parent <see cref="ITransform"/> of the <see cref="ITransform"/>.
/// </summary>
ITransform? Parent { get; }
/// <summary>
/// The <see cref="ITransform"/>s that have this <see cref="ITransform"/> as their <see cref="Parent"/>.
/// </summary>
IReadOnlyList<ITransform> Children { get; }
/// <summary>
/// Sets the parent <see cref="ITransform"/> of this <see cref="ITransform"/>.
/// </summary>
/// <param name="transform">The parent <see cref="ITransform"/> to set.</param>
void SetParent(ITransform? transform);
/// <summary>
/// Adds a child <see cref="ITransform"/> to this <see cref="ITransform"/>.
/// </summary>
/// <param name="transform">The child <see cref="ITransform"/> to add.</param>
void AddChild(ITransform transform);
/// <summary>
/// Removes a child <see cref="ITransform"/> from this <see cref="ITransform"/>.
/// </summary>
/// <param name="transform">The child <see cref="ITransform"/> to remove.</param>
void RemoveChild(ITransform transform);
delegate void OnPositionChangedDelegate(ITransform sender);
delegate void OnScaleChangedDelegate(ITransform sender);
delegate void OnRotationChangedDelegate(ITransform sender);
delegate void OnParentChangedDelegate(ITransform sender, ITransform? previousParent, ITransform? newParent);
delegate void OnChildrenAddedDelegate(ITransform sender, ITransform childrenAdded);
delegate void OnChildrenRemovedDelegate(ITransform sender, ITransform childrenRemoved);
} }

View File

@ -1,59 +1,105 @@
using System;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("{GetType().Name, nq}, Priority: {Priority}, Initialized: {Initialized}")] public abstract class Behaviour : BehaviourBase
public abstract class Behaviour : BaseEntity, IBehaviour
{ {
public Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } = null; private bool isInitializedThisFrame = false;
public Action<IBehaviour>? OnPriorityChanged { get; set; } = null; protected IGameObject GameObject => BehaviourController.GameObject;
protected ITransform Transform => BehaviourController.GameObject.Transform;
public Behaviour()
private IBehaviourController _behaviourController = null!;
private int _priority = 0;
public IBehaviourController BehaviourController => _behaviourController;
public override bool IsActive => base.IsActive && BehaviourController.GameObject.StateEnable.Enabled;
public int Priority
{ {
get => _priority; OnInitialized += OnInitialize;
set OnFinalized += OnFinalize;
{ OnUnassigned += OnUnassign;
if (value == _priority)
return;
_priority = value;
OnPriorityChanged?.Invoke(this);
}
} }
public bool Assign(IBehaviourController behaviourController) protected virtual void OnUnassign() { }
{ private void OnUnassign(IAssignable assignable) => OnUnassign();
if (Initialized)
return false;
_behaviourController = behaviourController; protected virtual void OnInitialize() { }
OnBehaviourControllerAssigned?.Invoke(this); private void OnInitialize(IInitialize _)
return true; {
isInitializedThisFrame = true;
BehaviourController.OnPreUpdate += PreUpdate;
BehaviourController.OnPreDraw += PreDraw;
BehaviourController.OnUpdate += Update;
BehaviourController.GameObject.OnEnteredHierarchy += EnteredHierarchy;
BehaviourController.GameObject.OnExitedHierarchy += ExitedHierarchy;
OnInitialize();
if (GameObject.IsInHierarchy)
EnteredHierarchy(GameObject, GameObject.GameManager ?? throw new System.Exception("Unexpected Error"));
} }
protected override void UnassignInternal() protected virtual void OnFinalize() { }
private void OnFinalize(IInitialize _)
{ {
base.UnassignInternal(); BehaviourController.OnPreUpdate -= PreUpdate;
_behaviourController = null!; BehaviourController.OnPreDraw -= PreDraw;
BehaviourController.OnUpdate -= Update;
BehaviourController.GameObject.OnEnteredHierarchy -= EnteredHierarchy;
BehaviourController.GameObject.OnExitedHierarchy -= ExitedHierarchy;
OnFinalize();
if (GameObject.IsInHierarchy)
ExitedHierarchy(GameObject, GameObject.GameManager ?? throw new System.Exception("Unexpected Error"));
} }
protected override void InitializeInternal() protected virtual void OnPreUpdatePreActiveCheck() { }
protected virtual void OnPreUpdate() { }
private void PreUpdate(IBehaviourController _)
{ {
base.InitializeInternal(); OnPreUpdatePreActiveCheck();
NotAssignedException.Check(this, _behaviourController);
NotAssignedException.Check(this, StateEnable); if (!IsActive)
return;
if (isInitializedThisFrame)
FirstActiveFrame();
OnPreUpdate();
} }
protected virtual void OnFirstActiveFrame() { }
private void FirstActiveFrame()
{
OnFirstActiveFrame();
isInitializedThisFrame = false;
}
protected virtual void OnUpdatePreActiveCheck() { }
protected virtual void OnUpdate() { }
private void Update(IBehaviourController _)
{
OnUpdatePreActiveCheck();
if (!IsActive)
return;
OnUpdate();
}
protected virtual void OnPreDrawPreActiveCheck() { }
protected virtual void OnPreDraw() { }
private void PreDraw(IBehaviourController _)
{
OnPreDrawPreActiveCheck();
if (!StateEnable.Enabled)
return;
OnPreDraw();
}
protected virtual void OnEnteredHierarchy(IGameManager gameManager) { }
private void EnteredHierarchy(IHierarchyObject sender, IGameManager gameManager) => OnEnteredHierarchy(gameManager);
protected virtual void OnExitedHierarchy(IGameManager gameManager) { }
private void ExitedHierarchy(IHierarchyObject sender, IGameManager gameManager) => OnExitedHierarchy(gameManager);
} }

View File

@ -0,0 +1,59 @@
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("{GetType().Name, nq}, Priority: {Priority}, Initialized: {Initialized}")]
public abstract class BehaviourBase : BaseEntity, IBehaviour
{
public event IAssignableBehaviourController.OnBehaviourControllerAssignedDelegate? OnBehaviourControllerAssigned = null;
public event IBehaviour.OnPriorityChangedDelegate? OnPriorityChanged = null;
private IBehaviourController _behaviourController = null!;
private int _priority = 0;
public IBehaviourController BehaviourController => _behaviourController;
public override bool IsActive => base.IsActive && BehaviourController.GameObject.StateEnable.Enabled;
public int Priority
{
get => _priority;
set
{
if (value == _priority)
return;
int previousPriority = _priority;
_priority = value;
OnPriorityChanged?.Invoke(this, previousPriority);
}
}
public bool Assign(IBehaviourController behaviourController)
{
if (IsInitialized)
return false;
_behaviourController = behaviourController;
OnBehaviourControllerAssigned?.Invoke(this);
return true;
}
protected override void UnassignInternal()
{
base.UnassignInternal();
_behaviourController = null!;
}
protected override void InitializeInternal()
{
base.InitializeInternal();
NotAssignedException.Check(this, _behaviourController);
NotAssignedException.Check(this, StateEnable);
}
}

View File

@ -6,13 +6,13 @@ using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
public class BehaviourCacher<T> : IAssignableGameManager, IEnumerable<T> public class BehaviourCollector<T> : IAssignableGameManager, IEnumerable<T>
{ {
public Action<IAssignable>? OnUnassigned { get; set; } = null; public event IAssignable.OnUnassignedDelegate? OnUnassigned = null;
public Action<IAssignableGameManager>? OnGameManagerAssigned { get; set; } = null; public event IAssignableGameManager.OnGameManagerAssignedDelegate? OnGameManagerAssigned = null;
public Action<BehaviourCacher<T>, T>? OnCached { get; set; } = null; public event OnCollectedDelegate? OnCollected = null;
public Action<BehaviourCacher<T>, T>? OnUncached { get; set; } = null; public event OnRemovedDelegate? OnRemoved = null;
private readonly List<T> _behaviours = new(32); private readonly List<T> _behaviours = new(32);
@ -21,19 +21,25 @@ public class BehaviourCacher<T> : IAssignableGameManager, IEnumerable<T>
public T this[Index index] => _behaviours[index]; public T this[Index index] => _behaviours[index];
public BehaviourCacher() { } public BehaviourCollector() { }
public BehaviourCacher(IGameManager gameManager) => Assign(gameManager); public BehaviourCollector(IGameManager gameManager) => Assign(gameManager);
private void OnGameObjectRegistered(IGameManager manager, IGameObject gameObject) private void OnGameObjectRegistered(IGameManager manager, IGameObject gameObject)
{ {
gameObject.BehaviourController.OnBehaviourAdded += OnBehaviourAdded; gameObject.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
gameObject.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved; gameObject.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;
foreach (IBehaviour item in gameObject.BehaviourController)
OnBehaviourAdded(gameObject.BehaviourController, item);
} }
private void OnGameObjectUnregistered(IGameManager manager, IGameObject gameObject) private void OnGameObjectUnregistered(IGameManager manager, IGameObject gameObject)
{ {
gameObject.BehaviourController.OnBehaviourAdded -= OnBehaviourAdded; gameObject.BehaviourController.OnBehaviourAdded -= OnBehaviourAdded;
gameObject.BehaviourController.OnBehaviourRemoved -= OnBehaviourRemoved; gameObject.BehaviourController.OnBehaviourRemoved -= OnBehaviourRemoved;
foreach (IBehaviour item in gameObject.BehaviourController)
OnBehaviourRemoved(gameObject.BehaviourController, item);
} }
private void OnBehaviourAdded(IBehaviourController controller, IBehaviour behaviour) private void OnBehaviourAdded(IBehaviourController controller, IBehaviour behaviour)
@ -42,7 +48,7 @@ public class BehaviourCacher<T> : IAssignableGameManager, IEnumerable<T>
return; return;
_behaviours.Add(tBehaviour); _behaviours.Add(tBehaviour);
OnCached?.Invoke(this, tBehaviour); OnCollected?.Invoke(this, tBehaviour);
} }
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviour behaviour) private void OnBehaviourRemoved(IBehaviourController controller, IBehaviour behaviour)
@ -53,7 +59,7 @@ public class BehaviourCacher<T> : IAssignableGameManager, IEnumerable<T>
if (!_behaviours.Remove(tBehaviour)) if (!_behaviours.Remove(tBehaviour))
return; return;
OnUncached?.Invoke(this, tBehaviour); OnRemoved?.Invoke(this, tBehaviour);
} }
public bool Assign(IGameManager gameManager) public bool Assign(IGameManager gameManager)
@ -62,11 +68,7 @@ public class BehaviourCacher<T> : IAssignableGameManager, IEnumerable<T>
return false; return false;
foreach (IGameObject gameObject in gameManager) foreach (IGameObject gameObject in gameManager)
{
OnGameObjectRegistered(gameManager, gameObject); OnGameObjectRegistered(gameManager, gameObject);
foreach (IBehaviour behaviour in gameObject.BehaviourController)
OnBehaviourAdded(gameObject.BehaviourController, behaviour);
}
gameManager.OnGameObjectRegistered += OnGameObjectRegistered; gameManager.OnGameObjectRegistered += OnGameObjectRegistered;
gameManager.OnGameObjectUnRegistered += OnGameObjectUnregistered; gameManager.OnGameObjectUnRegistered += OnGameObjectUnregistered;
@ -83,11 +85,7 @@ public class BehaviourCacher<T> : IAssignableGameManager, IEnumerable<T>
return false; return false;
foreach (IGameObject gameObject in GameManager) foreach (IGameObject gameObject in GameManager)
{
OnGameObjectUnregistered(GameManager, gameObject); OnGameObjectUnregistered(GameManager, gameObject);
foreach (IBehaviour behaviour in gameObject.BehaviourController)
OnBehaviourRemoved(gameObject.BehaviourController, behaviour);
}
GameManager.OnGameObjectRegistered -= OnGameObjectRegistered; GameManager.OnGameObjectRegistered -= OnGameObjectRegistered;
GameManager.OnGameObjectUnRegistered -= OnGameObjectUnregistered; GameManager.OnGameObjectUnRegistered -= OnGameObjectUnregistered;
@ -99,4 +97,8 @@ public class BehaviourCacher<T> : IAssignableGameManager, IEnumerable<T>
public IEnumerator<T> GetEnumerator() => _behaviours.GetEnumerator(); public IEnumerator<T> GetEnumerator() => _behaviours.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _behaviours.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _behaviours.GetEnumerator();
public delegate void OnCollectedDelegate(BehaviourCollector<T> sender, T behaviourCollected);
public delegate void OnRemovedDelegate(BehaviourCollector<T> sender, T behaviourRemoved);
} }

View File

@ -5,33 +5,57 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("Behaviour Count: {behaviours.Count}")] [System.Diagnostics.DebuggerDisplay("Behaviour Count: {behaviours.Count}")]
public class BehaviourController : IBehaviourController public class BehaviourController : IBehaviourController
{ {
public Action<IBehaviourController>? OnPreUpdate { get; set; } public event IBehaviourController.OnPreUpdateDelegate? OnPreUpdate = null;
public Action<IBehaviourController>? OnUpdate { get; set; } = null; public event IBehaviourController.OnUpdateDelegate? OnUpdate = null;
public Action<IBehaviourController>? OnPreDraw { get; set; } = null; public event IBehaviourController.OnPreDrawDelegate? OnPreDraw = null;
public Action<IBehaviourController, IBehaviour>? OnBehaviourAdded { get; set; } = null; public event IBehaviourController.OnBehaviourAddedDelegate? OnBehaviourAdded = null;
public Action<IBehaviourController, IBehaviour>? OnBehaviourRemoved { get; set; } = null; public event IBehaviourController.OnBehaviourRemovedDelegate? OnBehaviourRemoved = null;
public Action<IAssignable>? OnUnassigned { get; set; } = null; public event IAssignableGameObject.OnGameObjectAssignedDelegate? OnGameObjectAssigned = null;
public Action<IAssignableGameObject>? OnGameObjectAssigned { get; set; } = null;
public event IInitialize.OnInitializedDelegate? OnInitialized = null;
public event IInitialize.OnFinalizedDelegate? OnFinalized = null;
public event IAssignable.OnUnassignedDelegate? OnUnassigned = null;
private readonly IList<IBehaviour> behaviours = new List<IBehaviour>(Constants.BEHAVIOURS_SIZE_INITIAL); private readonly IList<IBehaviour> behaviours = new List<IBehaviour>(Constants.BEHAVIOURS_SIZE_INITIAL);
private IGameObject _gameObject = null!; private IGameObject _gameObject = null!;
private bool _initialized = false;
public IGameObject GameObject => _gameObject; public IGameObject GameObject => _gameObject;
public bool IsInitialized
{
get => _initialized;
private set
{
if (value == _initialized)
return;
_initialized = value;
if (value)
OnInitialized?.Invoke(this);
else
OnFinalized?.Invoke(this);
}
}
public T AddBehaviour<T>(T behaviour) where T : class, IBehaviour public T AddBehaviour<T>(T behaviour) where T : class, IBehaviour
{ {
InsertBehaviourByPriority(behaviour); InsertBehaviourByPriority(behaviour);
behaviour.Assign(this);
behaviour.Assign(GameObject.StateEnable);
behaviour.Initialize(); behaviour.Initialize();
behaviour.OnPriorityChanged += OnPriorityChange; behaviour.OnPriorityChanged += OnPriorityChange;
OnBehaviourAdded?.Invoke(this, behaviour); OnBehaviourAdded?.Invoke(this, behaviour);
@ -110,7 +134,7 @@ public class BehaviourController : IBehaviourController
public bool Assign(IGameObject gameObject) public bool Assign(IGameObject gameObject)
{ {
if (GameObject is not null && GameObject.Initialized) if (GameObject is not null && GameObject.IsInitialized)
return false; return false;
_gameObject = gameObject; _gameObject = gameObject;
@ -118,9 +142,36 @@ public class BehaviourController : IBehaviourController
return true; return true;
} }
public bool Initialize()
{
if (IsInitialized)
return false;
NotAssignedException.Check(this, _gameObject);
foreach (IBehaviour behaviour in behaviours)
behaviour.Initialize();
IsInitialized = true;
return true;
}
public bool Finalize()
{
if (!IsInitialized)
return false;
foreach (IBehaviour behaviour in behaviours)
behaviour.Finalize();
IsInitialized = false;
return true;
}
public bool Unassign() public bool Unassign()
{ {
if (GameObject is not null && GameObject.Initialized) if (IsInitialized)
return false; return false;
_gameObject = null!; _gameObject = null!;
@ -165,10 +216,11 @@ public class BehaviourController : IBehaviourController
behaviours.Add(behaviour); behaviours.Add(behaviour);
} }
private void OnPriorityChange(IBehaviour behaviour)
private void OnPriorityChange(IBehaviour sender, int previousPriority)
{ {
behaviours.Remove(behaviour); behaviours.Remove(sender);
InsertBehaviourByPriority(behaviour); InsertBehaviourByPriority(sender);
} }
public IEnumerator<IBehaviour> GetEnumerator() => behaviours.GetEnumerator(); public IEnumerator<IBehaviour> GetEnumerator() => behaviours.GetEnumerator();

View File

@ -1,87 +0,0 @@
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public abstract class BehaviourOverride : Behaviour
{
private bool isInitializedThisFrame = false;
protected IGameObject GameObject => BehaviourController.GameObject;
protected ITransform Transform => BehaviourController.GameObject.Transform;
public BehaviourOverride()
{
OnInitialized += OnInitialize;
OnFinalized += OnFinalize;
OnUnassigned += OnUnassign;
}
protected virtual void OnUnassign() { }
private void OnUnassign(IAssignable assignable) => OnUnassign();
protected virtual void OnInitialize() { }
private void OnInitialize(IInitialize _)
{
isInitializedThisFrame = true;
BehaviourController.OnPreUpdate += PreUpdate;
BehaviourController.OnPreDraw += PreDraw;
BehaviourController.OnUpdate += Update;
OnInitialize();
}
protected virtual void OnFinalize() { }
private void OnFinalize(IInitialize _)
{
BehaviourController.OnPreUpdate -= PreUpdate;
BehaviourController.OnPreDraw -= PreDraw;
BehaviourController.OnUpdate -= Update;
OnFinalize();
}
protected virtual void OnPreUpdatePreActiveCheck() { }
protected virtual void OnPreUpdate() { }
private void PreUpdate(IBehaviourController _)
{
OnPreUpdatePreActiveCheck();
if (!IsActive)
return;
if (isInitializedThisFrame)
FirstActiveFrame();
OnPreUpdate();
}
protected virtual void OnFirstActiveFrame() { }
private void FirstActiveFrame()
{
OnFirstActiveFrame();
isInitializedThisFrame = false;
}
protected virtual void OnUpdatePreActiveCheck() { }
protected virtual void OnUpdate() { }
private void Update(IBehaviourController _)
{
OnUpdatePreActiveCheck();
if (!IsActive)
return;
OnUpdate();
}
protected virtual void OnPreDrawPreActiveCheck() { }
protected virtual void OnPreDraw() { }
private void PreDraw(IBehaviourController _)
{
OnPreDrawPreActiveCheck();
if (!StateEnable.Enabled)
return;
OnPreDraw();
}
}

View File

@ -0,0 +1,44 @@
using System.Collections;
using System.Collections.Generic;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public class CoroutineManager : HierarchyObjectBase
{
private readonly List<IEnumerator> enumerators = [];
public IEnumerator CreateCoroutine(IEnumerator enumerator)
{
enumerators.Add(enumerator);
return enumerator;
}
public void StopCoroutine(IEnumerator enumerator)
{
enumerators.Remove(enumerator);
}
protected override void OnEnteringHierarchy(IGameManager gameManager)
{
gameManager.OnUpdate += OnUpdate;
}
protected override void OnExitingHierarchy(IGameManager gameManager)
{
gameManager.OnUpdate -= OnUpdate;
}
private void OnUpdate(IGameManager sender, EngineTime time)
{
for (int i = enumerators.Count - 1; i >= 0; i--)
{
if (enumerators[i].Current is ICoroutineYield coroutineYield && coroutineYield.Yield())
continue;
if (!enumerators[i].MoveNext())
enumerators.RemoveAt(i);
}
}
}

View File

@ -0,0 +1,12 @@
using System;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public class CoroutineYield(Func<bool> condition) : ICoroutineYield
{
private readonly Func<bool> condition = condition;
public bool Yield() => condition.Invoke();
}

View File

@ -7,5 +7,5 @@ public readonly struct EngineTime(TimeSpan Total, TimeSpan Elapsed)
public readonly TimeSpan Total = Total; public readonly TimeSpan Total = Total;
public readonly TimeSpan Elapsed = Elapsed; public readonly TimeSpan Elapsed = Elapsed;
public readonly float DeltaTimeFrame = (float)Elapsed.TotalMilliseconds; public readonly float DeltaTimeFrame = (float)Elapsed.TotalMilliseconds * .001f;
} }

View File

@ -0,0 +1,47 @@
using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public static class BehaviourControllerExtensions
{
public static bool TryGetBehaviourInParent<T>(this IBehaviourController behaviourController, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
{
behaviour = GetBehaviourInParent<T>(behaviourController);
return behaviour is not null;
}
public static T? GetBehaviourInParent<T>(this IBehaviourController behaviourController) where T : class
{
IBehaviourController? controller = behaviourController;
while (controller is not null)
{
if (behaviourController.GetBehaviour<T>() is T behaviour)
return behaviour;
controller = controller.GameObject.Transform.Parent?.GameObject.BehaviourController;
}
return default;
}
public static bool TryGetBehaviourInChildren<T>(this IBehaviourController behaviourController, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
{
behaviour = GetBehaviourInChildren<T>(behaviourController);
return behaviour is not null;
}
public static T? GetBehaviourInChildren<T>(this IBehaviourController behaviourController) where T : class
{
if (behaviourController.GetBehaviour<T>() is T localBehaviour)
return localBehaviour;
foreach (ITransform transform in behaviourController.GameObject.Transform)
if (GetBehaviourInChildren<T>(transform.GameObject.BehaviourController) is T behaviour)
return behaviour;
return default;
}
}

View File

@ -7,18 +7,22 @@ namespace Syntriax.Engine.Core;
public static class BehaviourExtensions public static class BehaviourExtensions
{ {
public static bool TryFindBehaviour<T>(this IEnumerable<IGameObject> gameObjects, [NotNullWhen(returnValue: true)] out T? behaviour) public static T? FindBehaviour<T>(this IEnumerable<IGameObject> gameObjects) where T : class
{ {
behaviour = default;
foreach (IGameObject gameObject in gameObjects) foreach (IGameObject gameObject in gameObjects)
if (gameObject.BehaviourController.TryGetBehaviour(out behaviour)) if (gameObject.BehaviourController.TryGetBehaviour(out T? behaviour))
return true; return behaviour;
return false; return default;
} }
public static void FindBehaviours<T>(this IEnumerable<IGameObject> gameObjects, List<T> behaviours) public static bool TryFindBehaviour<T>(this IEnumerable<IGameObject> gameObjects, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
{
behaviour = FindBehaviour<T>(gameObjects);
return behaviour is not null;
}
public static void FindBehaviours<T>(this IEnumerable<IGameObject> gameObjects, List<T> behaviours) where T : class
{ {
behaviours.Clear(); behaviours.Clear();
List<T> cache = []; List<T> cache = [];

View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public static class HierarchyObjectExtensions
{
public static T? FindObject<T>(this IEnumerable<IHierarchyObject> hierarchyObjects) where T : class
{
foreach (IHierarchyObject hierarchyObject in hierarchyObjects)
if (hierarchyObject is T @object)
return @object;
return default;
}
public static bool TryFindObject<T>(this IEnumerable<IHierarchyObject> hierarchyObjects, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
{
behaviour = FindObject<T>(hierarchyObjects);
return behaviour is not null;
}
public static void FindObjects<T>(this IEnumerable<IHierarchyObject> hierarchyObjects, List<T> behaviours) where T : class
{
behaviours.Clear();
foreach (IHierarchyObject hierarchyObject in hierarchyObjects)
if (hierarchyObject is T @object)
behaviours.Add(@object);
}
}

View File

@ -4,11 +4,16 @@ namespace Syntriax.Engine.Core;
public static class TransformExtensions public static class TransformExtensions
{ {
public static ITransform SetTransform(this ITransform transform, Vector2D? position = null, float? rotation = null, Vector2D? scale = null) public static ITransform SetTransform(this ITransform transform,
Vector2D? position = null, float? rotation = null, Vector2D? scale = null,
Vector2D? localPosition = null, float? localRotation = null, Vector2D? localScale = null)
{ {
if (position.HasValue) transform.Position = position.Value; if (position.HasValue) transform.Position = position.Value;
if (rotation.HasValue) transform.Rotation = rotation.Value; if (rotation.HasValue) transform.Rotation = rotation.Value;
if (scale.HasValue) transform.Scale = scale.Value; if (scale.HasValue) transform.Scale = scale.Value;
if (localPosition.HasValue) transform.LocalPosition = localPosition.Value;
if (localRotation.HasValue) transform.LocalRotation = localRotation.Value;
if (localScale.HasValue) transform.LocalScale = localScale.Value;
return transform; return transform;
} }
} }

View File

@ -22,6 +22,9 @@ public class GameObjectFactory
behaviourController ??= TypeFactory.Get<BehaviourController>(); behaviourController ??= TypeFactory.Get<BehaviourController>();
stateEnable ??= TypeFactory.Get<StateEnable>(); stateEnable ??= TypeFactory.Get<StateEnable>();
if (!transform.Assign(gameObject))
throw AssignException.From(transform, gameObject);
if (!behaviourController.Assign(gameObject)) if (!behaviourController.Assign(gameObject))
throw AssignException.From(behaviourController, gameObject); throw AssignException.From(behaviourController, gameObject);
if (!stateEnable.Assign(gameObject)) if (!stateEnable.Assign(gameObject))

View File

@ -11,11 +11,16 @@ namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("GameObject Count: {_gameObjects.Count}")] [System.Diagnostics.DebuggerDisplay("GameObject Count: {_gameObjects.Count}")]
public class GameManager : BaseEntity, IGameManager public class GameManager : BaseEntity, IGameManager
{ {
public Action<IGameManager, IGameObject>? OnGameObjectRegistered { get; set; } = null; public event IGameManager.OnUpdateDelegate? OnUpdate = null;
public Action<IGameManager, IGameObject>? OnGameObjectUnRegistered { get; set; } = null; public event IGameManager.OnPreDawDelegate? OnPreDraw = null;
public event IGameManager.OnGameObjectRegisteredDelegate? OnGameObjectRegistered = null;
public event IGameManager.OnGameObjectUnRegisteredDelegate? OnGameObjectUnRegistered = null;
public event IGameManager.OnHierarchyObjectRegisteredDelegate? OnHierarchyObjectRegistered = null;
public event IGameManager.OnHierarchyObjectUnRegisteredDelegate? OnHierarchyObjectUnRegistered = null;
private readonly List<IGameObject> _gameObjects = new(Constants.GAME_OBJECTS_SIZE_INITIAL); private readonly List<IGameObject> _gameObjects = new(Constants.GAME_OBJECTS_SIZE_INITIAL);
private readonly List<IHierarchyObject> _hierarchyObjects = new(Constants.GAME_OBJECTS_SIZE_INITIAL);
private GameObjectFactory _gameObjectFactory = null!; private GameObjectFactory _gameObjectFactory = null!;
@ -30,6 +35,7 @@ public class GameManager : BaseEntity, IGameManager
} }
public IReadOnlyList<IGameObject> GameObjects => _gameObjects; public IReadOnlyList<IGameObject> GameObjects => _gameObjects;
public IReadOnlyList<IHierarchyObject> HierarchyObjects => _hierarchyObjects;
public override IStateEnable StateEnable public override IStateEnable StateEnable
{ {
@ -46,12 +52,23 @@ public class GameManager : BaseEntity, IGameManager
} }
} }
public void RegisterGameObject(IGameObject gameObject) public void Register(IHierarchyObject hierarchyObject)
{ {
if (_gameObjects.Contains(gameObject)) if (_hierarchyObjects.Contains(hierarchyObject))
throw new Exception($"{nameof(IGameObject)} named {gameObject.Name} is already registered to the {nameof(GameManager)}."); throw new Exception($"{nameof(IHierarchyObject)} named {hierarchyObject.Name} is already registered to the {nameof(GameManager)}.");
Register(gameObject); if (hierarchyObject is IGameObject gameObject)
Register(gameObject);
else
{
if (!hierarchyObject.Initialize())
throw new Exception($"{nameof(hierarchyObject)} can't be initialized");
_hierarchyObjects.Add(hierarchyObject);
hierarchyObject.EnterHierarchy(this);
OnHierarchyObjectRegistered?.Invoke(this, hierarchyObject);
}
} }
public T InstantiateGameObject<T>(params object?[]? args) where T : class, IGameObject public T InstantiateGameObject<T>(params object?[]? args) where T : class, IGameObject
@ -61,13 +78,23 @@ public class GameManager : BaseEntity, IGameManager
return gameObject; return gameObject;
} }
public IGameObject RemoveGameObject(IGameObject gameObject) public void Remove(IHierarchyObject hierarchyObject)
{ {
if (!_gameObjects.Contains(gameObject)) if (!_hierarchyObjects.Contains(hierarchyObject))
throw new Exception($"{nameof(IGameObject)} named {gameObject.Name} is not registered to the {nameof(GameManager)}."); throw new Exception($"{nameof(IHierarchyObject)} named {hierarchyObject.Name} is not registered to the {nameof(GameManager)}.");
Unregister(gameObject); if (hierarchyObject is IGameObject gameObject)
return gameObject; Unregister(gameObject);
else
{
_hierarchyObjects.Remove(hierarchyObject);
hierarchyObject.ExitHierarchy();
if (!hierarchyObject.Finalize())
throw new Exception($"{nameof(hierarchyObject)} can't be finalized");
OnHierarchyObjectUnRegistered?.Invoke(this, hierarchyObject);
}
} }
protected override void InitializeInternal() protected override void InitializeInternal()
@ -89,33 +116,67 @@ public class GameManager : BaseEntity, IGameManager
public void Update(EngineTime time) public void Update(EngineTime time)
{ {
Time.SetTime(time); Time.SetTime(time);
foreach (var gameObject in GameObjects) for (int i = 0; i < GameObjects.Count; i++)
gameObject.BehaviourController.Update(); GameObjects[i].BehaviourController.Update();
OnUpdate?.Invoke(this, time);
} }
public void PreDraw() public void PreDraw()
{ {
foreach (var gameObject in GameObjects) for (int i = 0; i < GameObjects.Count; i++)
gameObject.BehaviourController.UpdatePreDraw(); GameObjects[i].BehaviourController.UpdatePreDraw();
OnPreDraw?.Invoke(this);
} }
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
private void Register(IGameObject gameObject) private void Register(IGameObject gameObject)
{ {
gameObject.Assign(this); if (_gameObjects.Contains(gameObject))
throw new Exception($"{nameof(IGameObject)} named {gameObject.Name} is already registered to the {nameof(GameManager)}.");
gameObject.OnFinalized += OnGameObjectFinalize; gameObject.OnFinalized += OnGameObjectFinalize;
gameObject.OnExitedHierarchy += OnGameObjectExitedHierarchy;
if (!gameObject.Initialize())
throw new Exception($"{nameof(gameObject)} can't be initialized");
foreach (ITransform child in gameObject.Transform.Children)
Register(child.GameObject);
_gameObjects.Add(gameObject); _gameObjects.Add(gameObject);
_hierarchyObjects.Add(gameObject);
if (!gameObject.EnterHierarchy(this))
throw new Exception($"{nameof(gameObject)} can't enter the hierarchy");
OnHierarchyObjectRegistered?.Invoke(this, gameObject);
OnGameObjectRegistered?.Invoke(this, gameObject); OnGameObjectRegistered?.Invoke(this, gameObject);
} }
private void Unregister(IGameObject gameObject) private void Unregister(IGameObject gameObject)
{ {
if (!_gameObjects.Contains(gameObject))
throw new Exception($"{nameof(IGameObject)} named {gameObject.Name} is not registered to the {nameof(GameManager)}.");
gameObject.OnFinalized -= OnGameObjectFinalize; gameObject.OnFinalized -= OnGameObjectFinalize;
gameObject.OnExitedHierarchy -= OnGameObjectExitedHierarchy;
foreach (ITransform child in gameObject.Transform.Children)
Unregister(child.GameObject);
_gameObjects.Remove(gameObject); _gameObjects.Remove(gameObject);
_hierarchyObjects.Remove(gameObject);
if (!gameObject.ExitHierarchy())
throw new Exception($"{nameof(gameObject)} can't exit the hierarchy");
if (!gameObject.Finalize())
throw new Exception($"{nameof(gameObject)} can't be finalized");
OnHierarchyObjectUnRegistered?.Invoke(this, gameObject);
OnGameObjectUnRegistered?.Invoke(this, gameObject); OnGameObjectUnRegistered?.Invoke(this, gameObject);
} }
@ -125,6 +186,12 @@ public class GameManager : BaseEntity, IGameManager
Unregister(gameObject); Unregister(gameObject);
} }
private void OnGameObjectExitedHierarchy(IHierarchyObject sender, IGameManager gameManager)
{
if (sender is IGameObject gameObject)
Unregister(gameObject);
}
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
public IEnumerator<IGameObject> GetEnumerator() => _gameObjects.GetEnumerator(); public IEnumerator<IGameObject> GetEnumerator() => _gameObjects.GetEnumerator();

View File

@ -1,5 +1,3 @@
using System;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions; using Syntriax.Engine.Core.Exceptions;
@ -8,25 +6,26 @@ namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("Name: {Name}, Initialized: {Initialized}")] [System.Diagnostics.DebuggerDisplay("Name: {Name}, Initialized: {Initialized}")]
public class GameObject : BaseEntity, IGameObject public class GameObject : BaseEntity, IGameObject
{ {
public Action<IAssignableTransform>? OnTransformAssigned { get; set; } = null; public event IHierarchyObject.OnEnteredHierarchyDelegate? OnEnteredHierarchy = null;
public Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } = null; public event IHierarchyObject.OnExitedHierarchyDelegate? OnExitedHierarchy = null;
public Action<IAssignableGameManager>? OnGameManagerAssigned { get; set; } = null;
public Action<IEntity>? OnNameChanged { get; set; } = null; public event IAssignableTransform.OnTransformAssignedDelegate? OnTransformAssigned = null;
public event IAssignableBehaviourController.OnBehaviourControllerAssignedDelegate? OnBehaviourControllerAssigned = null;
public Action<IGameObject>? OnUpdated { get; set; } = null; public event INameable.OnNameChangedDelegate? OnNameChanged = null;
public event IGameObject.OnUpdatedDelegate? OnUpdated = null;
private ITransform _transform = null!; private ITransform _transform = null!;
private IBehaviourController _behaviourController = null!; private IBehaviourController _behaviourController = null!;
private IStateEnable _stateEnable = null!; private IGameManager? _gameManager = null;
private IGameManager _gameManager = null!;
private string _name = nameof(GameObject); private string _name = nameof(GameObject);
public ITransform Transform => _transform; public ITransform Transform => _transform;
public IBehaviourController BehaviourController => _behaviourController; public IBehaviourController BehaviourController => _behaviourController;
public IGameManager GameManager => _gameManager; public IGameManager? GameManager => _gameManager;
public bool IsInHierarchy => GameManager is not null;
public string Name public string Name
{ {
@ -35,8 +34,9 @@ public class GameObject : BaseEntity, IGameObject
{ {
if (value == _name) return; if (value == _name) return;
string previousName = _name;
_name = value; _name = value;
OnNameChanged?.Invoke(this); OnNameChanged?.Invoke(this, previousName);
} }
} }
@ -46,13 +46,14 @@ public class GameObject : BaseEntity, IGameObject
NotAssignedException.Check(this, _transform); NotAssignedException.Check(this, _transform);
NotAssignedException.Check(this, _behaviourController); NotAssignedException.Check(this, _behaviourController);
NotAssignedException.Check(this, _stateEnable);
NotAssignedException.Check(this, _gameManager); if (!_behaviourController.Initialize())
throw new System.Exception($"Failed to Initialize {BehaviourController.GetType().Name} on {Transform.GameObject.Name}");
} }
public void Update() public void Update()
{ {
if (!_stateEnable.Enabled) if (!StateEnable.Enabled)
return; return;
OnUpdated?.Invoke(this); OnUpdated?.Invoke(this);
@ -62,23 +63,25 @@ public class GameObject : BaseEntity, IGameObject
{ {
base.FinalizeInternal(); base.FinalizeInternal();
foreach (IBehaviour behaviour in _behaviourController.GetBehaviours<IBehaviour>()) if (!_behaviourController.Finalize())
behaviour.Finalize(); throw new System.Exception($"Failed to Finalize {BehaviourController.GetType().Name} on {Transform.GameObject.Name}");
} }
public bool Assign(ITransform transform) public bool Assign(ITransform transform)
{ {
if (Initialized) if (IsInitialized)
return false; return false;
_transform = transform; _transform = transform;
OnTransformAssigned?.Invoke(this); OnTransformAssigned?.Invoke(this);
transform.OnParentChanged += OnParentChangedInternal;
OnParentChangedInternal(transform, null, transform.Parent);
return true; return true;
} }
public bool Assign(IBehaviourController behaviourController) public bool Assign(IBehaviourController behaviourController)
{ {
if (Initialized) if (IsInitialized)
return false; return false;
_behaviourController = behaviourController; _behaviourController = behaviourController;
@ -86,32 +89,63 @@ public class GameObject : BaseEntity, IGameObject
return true; return true;
} }
public bool Assign(IGameManager gameManager)
{
if (Initialized)
return false;
_gameManager = gameManager;
OnGameManagerAssigned?.Invoke(this);
return true;
}
protected override void UnassignInternal() protected override void UnassignInternal()
{ {
base.UnassignInternal(); base.UnassignInternal();
_stateEnable = null!; _transform.OnParentChanged -= OnParentChangedInternal;
OnParentChangedInternal(_transform, _transform.Parent, null);
_transform = null!; _transform = null!;
_behaviourController = null!; _behaviourController = null!;
_gameManager = null!; _gameManager = null!;
} }
public GameObject() { OnBehaviourControllerAssigned += ConnectBehaviourController; } public GameObject() { OnBehaviourControllerAssigned += ConnectBehaviourController; }
private void ConnectBehaviourController(IAssignableBehaviourController controller) private void ConnectBehaviourController(IAssignableBehaviourController controller)
{ {
controller.BehaviourController.OnBehaviourAdded += OnBehaviourAdded; controller.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
controller.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved; controller.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;
} }
private void OnBehaviourRemoved(IBehaviourController _, IBehaviour behaviour) { if (Initialized) behaviour.Initialize(); } private void OnBehaviourRemoved(IBehaviourController _, IBehaviour behaviour) { if (IsInitialized) behaviour.Finalize(); }
private void OnBehaviourAdded(IBehaviourController _, IBehaviour behaviour) { if (Initialized) behaviour.Finalize(); } private void OnBehaviourAdded(IBehaviourController _, IBehaviour behaviour) { if (!IsInitialized) behaviour.Initialize(); }
private void OnParentChangedInternal(ITransform sender, ITransform? previousParent, ITransform? newParent)
{
if (previousParent is not null)
{
previousParent.OnParentChanged -= OnParentChangedInternal;
previousParent.GameObject.OnEnteredHierarchy -= EnterHierarchyInternal;
previousParent.GameObject.OnExitedHierarchy -= ExitHierarchyInternal;
}
if (newParent is not null)
{
newParent.OnParentChanged += OnParentChangedInternal;
newParent.GameObject.OnEnteredHierarchy += EnterHierarchyInternal;
newParent.GameObject.OnExitedHierarchy += ExitHierarchyInternal;
}
}
private void EnterHierarchyInternal(IHierarchyObject sender, IGameManager gameManager) => ((IHierarchyObject)this).EnterHierarchy(gameManager);
private void ExitHierarchyInternal(IHierarchyObject sender, IGameManager gameManager) => ((IHierarchyObject)this).ExitHierarchy();
bool IHierarchyObject.EnterHierarchy(IGameManager gameManager)
{
if (IsInHierarchy)
return false;
_gameManager = gameManager;
OnEnteredHierarchy?.Invoke(this, gameManager);
return true;
}
bool IHierarchyObject.ExitHierarchy()
{
if (!IsInHierarchy || _gameManager is not IGameManager gameManager)
return false;
_gameManager = null;
OnExitedHierarchy?.Invoke(this, gameManager);
return true;
}
} }

View File

@ -0,0 +1,142 @@
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public abstract class HierarchyObjectBase : IHierarchyObject
{
public event IHierarchyObject.OnEnteredHierarchyDelegate? OnEnteredHierarchy = null;
public event IHierarchyObject.OnExitedHierarchyDelegate? OnExitedHierarchy = null;
public event IEntity.OnIdChangedDelegate? OnIdChanged = null;
public event IInitialize.OnInitializedDelegate? OnInitialized = null;
public event IInitialize.OnFinalizedDelegate? OnFinalized = null;
public event IAssignableStateEnable.OnStateEnableAssignedDelegate? OnStateEnableAssigned = null;
public event IAssignable.OnUnassignedDelegate? OnUnassigned = null;
public event INameable.OnNameChangedDelegate? OnNameChanged = null;
private string _id = string.Empty;
private string _name = nameof(HierarchyObjectBase);
private bool _initialized = false;
private IStateEnable _stateEnable = null!;
private IGameManager? _gameManager = null;
public IGameManager? GameManager => _gameManager;
public bool IsInHierarchy => _gameManager is not null;
public bool IsInitialized
{
get => _initialized;
private set
{
if (value == _initialized)
return;
_initialized = value;
if (value)
OnInitialized?.Invoke(this);
else
OnFinalized?.Invoke(this);
}
}
public virtual IStateEnable StateEnable => _stateEnable;
public string Name
{
get => _name;
set
{
if (value == _name) return;
string previousName = _name;
_name = value;
OnNameChanged?.Invoke(this, previousName);
}
}
public string Id
{
get => _id;
set
{
if (value == _id)
return;
string previousId = _id;
_id = value;
OnIdChanged?.Invoke(this, previousId);
}
}
public bool Assign(IStateEnable stateEnable)
{
if (IsInitialized)
return false;
_stateEnable = stateEnable;
_stateEnable.Assign(this);
OnStateEnableAssigned?.Invoke(this);
return true;
}
protected virtual void UnassignInternal() { }
public bool Unassign()
{
if (IsInitialized)
return false;
UnassignInternal();
OnUnassigned?.Invoke(this);
return true;
}
protected virtual void InitializeInternal() { }
public bool Initialize()
{
if (IsInitialized)
return false;
InitializeInternal();
IsInitialized = true;
return true;
}
protected virtual void FinalizeInternal() { }
public bool Finalize()
{
if (!IsInitialized)
return false;
FinalizeInternal();
IsInitialized = false;
return true;
}
protected virtual void OnEnteringHierarchy(IGameManager gameManager) { }
bool IHierarchyObject.EnterHierarchy(IGameManager gameManager)
{
if (IsInHierarchy)
return false;
_gameManager = gameManager;
OnEnteringHierarchy(gameManager);
OnEnteredHierarchy?.Invoke(this, gameManager);
return true;
}
protected virtual void OnExitingHierarchy(IGameManager gameManager) { }
bool IHierarchyObject.ExitHierarchy()
{
if (!IsInHierarchy || _gameManager is not IGameManager gameManager)
return false;
_gameManager = null;
OnExitingHierarchy(gameManager);
OnExitedHierarchy?.Invoke(this, gameManager);
return true;
}
}

View File

@ -4,9 +4,10 @@ using System.Numerics;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
public static class Math public static class Math
{/// <summary> {
/// The value of Pi (π), a mathematical constant approximately equal to 3.14159. /// <summary>
/// </summary> /// The value of Pi (π), a mathematical constant approximately equal to 3.14159.
/// </summary>
public const float PI = 3.1415926535897932f; public const float PI = 3.1415926535897932f;
/// <summary> /// <summary>
@ -37,6 +38,20 @@ public static class Math
/// <returns>The absolute value of <paramref name="x"/>.</returns> /// <returns>The absolute value of <paramref name="x"/>.</returns>
public static T Abs<T>(T x) where T : INumber<T> => x > T.Zero ? x : -x; public static T Abs<T>(T x) where T : INumber<T> => x > T.Zero ? x : -x;
/// <summary>
/// Returns the cosine of a number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The cosine of <paramref name="x"/>.</returns>
public static float Cos(float x) => MathF.Cos(x);
/// <summary>
/// Returns the sine of a number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The sine of <paramref name="x"/>.</returns>
public static float Sin(float x) => MathF.Sin(x);
/// <summary> /// <summary>
/// Returns the arccosine of a number. /// Returns the arccosine of a number.
/// </summary> /// </summary>
@ -74,7 +89,7 @@ public static class Math
/// <param name="min">The minimum value.</param> /// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param> /// <param name="max">The maximum value.</param>
/// <returns>The clamped value.</returns> /// <returns>The clamped value.</returns>
public static T Clamp<T>(this T x, T min, T max) where T : INumber<T> => (x < min) ? min : (x > max) ? max : x; public static T Clamp<T>(T x, T min, T max) where T : INumber<T> => (x < min) ? min : (x > max) ? max : x;
/// <summary> /// <summary>
/// Returns the smallest integral value that is greater than or equal to the specified number. /// Returns the smallest integral value that is greater than or equal to the specified number.
@ -156,6 +171,16 @@ public static class Math
/// <returns>The number <paramref name="x"/> raised to the power <paramref name="y"/>.</returns> /// <returns>The number <paramref name="x"/> raised to the power <paramref name="y"/>.</returns>
public static float Pow(float x, float y) => MathF.Pow(x, y); public static float Pow(float x, float y) => MathF.Pow(x, y);
/// <summary>
/// Performs linear interpolation between two specified values.
/// </summary>
/// <typeparam name="T">The type of the values, which must implement <see cref="IFloatingPoint{T}"/>.</typeparam>
/// <param name="x">The starting value of the interpolation.</param>
/// <param name="y">The ending value of the interpolation.</param>
/// <param name="t">The interpolation factor, typically in the range [0, 1].</param>
/// <returns>A value that represents the linear interpolation between <paramref name="x"/> and <paramref name="y"/>.</returns>
public static T Lerp<T>(T x, T y, T t) where T : IFloatingPoint<T> => x + (y - x) * t;
/// <summary> /// <summary>
/// Rounds a number to a specified number of fractional digits. /// Rounds a number to a specified number of fractional digits.
/// </summary> /// </summary>
@ -186,5 +211,4 @@ public static class Math
/// <param name="x">The number.</param> /// <param name="x">The number.</param>
/// <returns>The integral part of <paramref name="x"/>.</returns> /// <returns>The integral part of <paramref name="x"/>.</returns>
public static float Truncate(float x) => MathF.Truncate(x); public static float Truncate(float x) => MathF.Truncate(x);
} }

View File

@ -0,0 +1,76 @@
using System;
using System.Numerics;
namespace Syntriax.Engine.Core;
public static class MathExtensions
{
/// <inheritdoc cref="Math.Abs{T}(T)" />
public static T Abs<T>(this T x) where T : INumber<T> => Math.Abs(x);
/// <inheritdoc cref="Math.Cos(float)" />
public static float Cos(this float x) => Math.Cos(x);
/// <inheritdoc cref="Math.Sin(float)" />
public static float Sin(this float x) => Math.Sin(x);
/// <inheritdoc cref="Math.Acos(float)" />
public static float Acos(this float x) => Math.Acos(x);
/// <inheritdoc cref="Math.Asin(float)" />
public static float Asin(this float x) => Math.Asin(x);
/// <inheritdoc cref="Math.Atan2(float, float)" />
public static float Atan2(this float y, float x) => Math.Atan2(y, x);
/// <inheritdoc cref="Math.Atanh(float)" />
public static float Atanh(this float x) => Math.Atanh(x);
/// <inheritdoc cref="Math.Clamp{T}(T, T, T)" />
public static T Clamp<T>(this T x, T min, T max) where T : INumber<T> => Math.Clamp(x, min, max);
/// <inheritdoc cref="Math.Ceiling(float)" />
public static float Ceiling(this float x) => Math.Ceiling(x);
/// <inheritdoc cref="Math.CopySign(float, float)" />
public static float CopySign(this float x, float y) => Math.CopySign(x, y);
/// <inheritdoc cref="Math.Floor(float)" />
public static float Floor(this float x) => Math.Floor(x);
/// <inheritdoc cref="Math.IEEERemainder(float, float)" />
public static float IEEERemainder(this float x, float y) => Math.IEEERemainder(x, y);
/// <inheritdoc cref="Math.Log(float, float)" />
public static float Log(this float x, float y) => Math.Log(x, y);
/// <inheritdoc cref="Math.Max{T}(T, T)" />
public static T Max<T>(this T x, T y) where T : INumber<T> => Math.Max(x, y);
/// <inheritdoc cref="Math.AbsMax{T}(T, T)" />
public static T AbsMax<T>(this T x, T y) where T : INumber<T> => Math.AbsMax(x, y);
/// <inheritdoc cref="Math.Min{T}(T, T)" />
public static T Min<T>(this T x, T y) where T : INumber<T> => Math.Min(x, y);
/// <inheritdoc cref="Math.AbsMin{T}(T, T)" />
public static T AbsMin<T>(this T x, T y) where T : INumber<T> => Math.AbsMin(x, y);
/// <inheritdoc cref="Math.Pow(float, float)" />
public static float Pow(this float x, float y) => Math.Pow(x, y);
/// <inheritdoc cref="Math.Lerp{T}(T, T, T)" />
public static T Lerp<T>(this T x, T y, T t) where T : IFloatingPoint<T> => Math.Lerp(x, y, t);
/// <inheritdoc cref="Math.Round(float, int, MidpointRounding)" />
public static float Round(this float x, int digits, MidpointRounding mode) => Math.Round(x, digits, mode);
/// <inheritdoc cref="Math.Sqr{T}(T)" />
public static T Sqr<T>(this T x) where T : INumber<T> => Math.Sqr(x);
/// <inheritdoc cref="Math.Sqrt(float)" />
public static float Sqrt(this float x) => Math.Sqrt(x);
/// <inheritdoc cref="Math.Truncate(float)" />
public static float Truncate(this float x) => Math.Truncate(x);
}

View File

@ -1,14 +1,12 @@
using System;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
public class StateEnable : IStateEnable public class StateEnable : IStateEnable
{ {
public Action<IAssignable>? OnUnassigned { get; set; } = null; public event IAssignable.OnUnassignedDelegate? OnUnassigned = null;
public Action<IAssignableEntity>? OnEntityAssigned { get; set; } = null; public event IAssignableEntity.OnEntityAssignedDelegate? OnEntityAssigned = null;
public Action<IStateEnable>? OnEnabledChanged { get; set; } = null; public event IStateEnable.OnNameChangedDelegate? OnEnabledChanged = null;
private bool _enabled = true; private bool _enabled = true;
private IEntity _entity = null!; private IEntity _entity = null!;
@ -23,14 +21,15 @@ public class StateEnable : IStateEnable
if (value == _enabled) if (value == _enabled)
return; return;
bool previousState = _enabled;
_enabled = value; _enabled = value;
OnEnabledChanged?.Invoke(this); OnEnabledChanged?.Invoke(this, previousState);
} }
} }
public bool Assign(IEntity entity) public bool Assign(IEntity entity)
{ {
if (_entity is not null && _entity.Initialized) if (_entity is not null && _entity.IsInitialized)
return false; return false;
_entity = entity; _entity = entity;

View File

@ -1,20 +1,41 @@
using System; using System.Collections;
using System.Collections.Generic;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("Position: {Position.ToString(), nq}, Scale: {Scale.ToString(), nq}, Rotation: {Rotation}")] [System.Diagnostics.DebuggerDisplay("Name: {GameObject.Name, nq} Position: {Position.ToString(), nq}, Scale: {Scale.ToString(), nq}, Rotation: {Rotation}")]
public class Transform : ITransform public class Transform : ITransform
{ {
public Action<ITransform>? OnPositionChanged { get; set; } = null; public event IAssignableGameObject.OnGameObjectAssignedDelegate? OnGameObjectAssigned = null;
public Action<ITransform>? OnScaleChanged { get; set; } = null;
public Action<ITransform>? OnRotationChanged { get; set; } = null; public event IAssignable.OnUnassignedDelegate? OnUnassigned = null;
public event ITransform.OnPositionChangedDelegate? OnPositionChanged = null;
public event ITransform.OnScaleChangedDelegate? OnScaleChanged = null;
public event ITransform.OnRotationChangedDelegate? OnRotationChanged = null;
public event ITransform.OnParentChangedDelegate? OnParentChanged = null;
public event ITransform.OnChildrenAddedDelegate? OnChildrenAdded = null;
public event ITransform.OnChildrenRemovedDelegate? OnChildrenRemoved = null;
private Vector2D _position = Vector2D.Zero; private Vector2D _position = Vector2D.Zero;
private Vector2D _scale = Vector2D.One; private Vector2D _scale = Vector2D.One;
private float _rotation = 0f; private float _rotation = 0f;
private Vector2D _localPosition = Vector2D.Zero;
private Vector2D _localScale = Vector2D.One;
private float _localRotation = 0f;
private readonly List<ITransform> _children = [];
public IGameObject GameObject { get; private set; } = null!;
public ITransform? Parent { get; private set; } = null;
public IReadOnlyList<ITransform> Children => _children;
public Vector2D Position public Vector2D Position
{ {
get => _position; get => _position;
@ -24,33 +45,238 @@ public class Transform : ITransform
return; return;
_position = value; _position = value;
UpdateLocalPosition();
OnPositionChanged?.Invoke(this); OnPositionChanged?.Invoke(this);
} }
} }
public Vector2D Scale public Vector2D Scale
{ {
get => _scale; get => _localScale.Scale(Parent?.Scale ?? Vector2D.One);
set set
{ {
if (value == _scale) if (value == _scale)
return; return;
_scale = value; _scale = value;
UpdateLocalScale();
OnScaleChanged?.Invoke(this); OnScaleChanged?.Invoke(this);
} }
} }
public float Rotation public float Rotation
{ {
get => _rotation; get => _localRotation + (Parent?.Rotation ?? 0f);
set set
{ {
if (value == _rotation) if (value == _rotation)
return; return;
_rotation = value; _rotation = value;
UpdateLocalPosition();
OnRotationChanged?.Invoke(this); OnRotationChanged?.Invoke(this);
} }
} }
public Vector2D LocalPosition
{
get => _localPosition;
set
{
if (value == _localPosition)
return;
_localPosition = value;
UpdatePosition();
OnPositionChanged?.Invoke(this);
}
}
public Vector2D LocalScale
{
get => _localScale;
set
{
if (value == _localScale)
return;
_localScale = value;
UpdateScale();
OnScaleChanged?.Invoke(this);
}
}
public float LocalRotation
{
get => _localRotation;
set
{
if (value == _localRotation)
return;
_localRotation = value;
UpdateRotation();
OnRotationChanged?.Invoke(this);
}
}
public void SetParent(ITransform? transform)
{
if (transform == this || Parent == transform)
return;
ITransform? previousParent = Parent;
if (previousParent is not null)
{
previousParent.RemoveChild(this);
previousParent.OnPositionChanged -= RecalculatePosition;
previousParent.OnScaleChanged -= RecalculateScale;
previousParent.OnRotationChanged -= RecalculateRotation;
previousParent.OnParentChanged -= NotifyChildrenOnParentChange;
}
Parent = transform;
if (transform is not null)
{
transform.AddChild(this);
transform.OnPositionChanged += RecalculatePosition;
transform.OnScaleChanged += RecalculateScale;
transform.OnRotationChanged += RecalculateRotation;
transform.OnParentChanged += NotifyChildrenOnParentChange;
}
UpdateLocalPosition();
UpdateLocalScale();
UpdateLocalRotation();
OnParentChanged?.Invoke(this, previousParent, transform);
}
public void AddChild(ITransform transform)
{
if (_children.Contains(transform))
return;
_children.Add(transform);
transform.SetParent(this);
OnChildrenAdded?.Invoke(this, transform);
}
public void RemoveChild(ITransform transform)
{
if (!_children.Remove(transform))
return;
transform.SetParent(null);
OnChildrenRemoved?.Invoke(this, transform);
}
public IEnumerator<ITransform> GetEnumerator() => _children.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _children.GetEnumerator();
private void NotifyChildrenOnParentChange(ITransform transform, ITransform? previousParent, ITransform? newParent)
{
// TODO No idea how logical this is to propagate this to the children the way I'm doing right now.
// I was originally gonna just call `child.OnParentChanged?.Invoke(child, child.Parent);` but seems an unnecessary call too?
foreach (var child in Children) // TODO CHECK ERRORS
child.SetParent(this);
}
private void RecalculatePosition(ITransform _)
{
if (Parent is null)
return;
UpdatePosition();
OnPositionChanged?.Invoke(this);
}
private void RecalculateScale(ITransform _)
{
if (Parent is null)
return;
UpdateScale();
OnScaleChanged?.Invoke(this);
}
private void RecalculateRotation(ITransform _)
{
if (Parent is null)
return;
UpdatePosition();
UpdateRotation();
OnPositionChanged?.Invoke(this);
OnRotationChanged?.Invoke(this);
}
private void UpdateLocalPosition()
{
if (Parent is null)
_localPosition = Position;
else
_localPosition = Parent.Position.FromTo(Position).Scale(Parent.Scale);
}
private void UpdateLocalScale()
{
if (Parent is null)
_localScale = Scale;
else
_localScale = Scale.Scale(new(1f / Parent.Scale.X, 1f / Parent.Scale.Y));
}
private void UpdateLocalRotation()
{
if (Parent is null)
_localRotation = Rotation;
else
_localRotation = Rotation - Parent.Rotation;
}
private void UpdatePosition()
{
if (Parent is null)
_position = LocalPosition.Rotate(0f * Math.DegreeToRadian);
else
_position = Parent.Position + LocalPosition.Scale(new(Parent.Scale.X, Parent.Scale.Y)).Rotate(Parent.Rotation * Math.DegreeToRadian);
}
private void UpdateScale()
{
if (Parent is null)
_scale = LocalScale;
else
_scale = Vector2D.Scale(Parent.Scale, LocalScale);
}
private void UpdateRotation()
{
if (Parent is null)
_rotation = LocalRotation;
else
_rotation = Parent.Rotation + LocalRotation;
}
public bool Assign(IGameObject gameObject)
{
if (GameObject is not null && GameObject.IsInitialized)
return false;
GameObject = gameObject;
OnGameObjectAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (GameObject is not null && GameObject.IsInitialized)
return false;
GameObject = null!;
OnUnassigned?.Invoke(this);
return true;
}
} }

View File

@ -4,11 +4,12 @@ namespace Syntriax.Engine.Input;
public interface IButtonInputs<T> : IAssignableStateEnable public interface IButtonInputs<T> : IAssignableStateEnable
{ {
void RegisterOnPress(T button, Action<IButtonInputs<T>, T> callback); void RegisterOnPress(T button, ButtonCallbackDelegate callback);
void UnregisterOnPress(T button, Action<IButtonInputs<T>, T> callback); void UnregisterOnPress(T button, ButtonCallbackDelegate callback);
void RegisterOnRelease(T button, Action<IButtonInputs<T>, T> callback); void RegisterOnRelease(T button, ButtonCallbackDelegate callback);
void UnregisterOnRelease(T button, Action<IButtonInputs<T>, T> callback); void UnregisterOnRelease(T button, ButtonCallbackDelegate callback);
bool IsPressed(T button); bool IsPressed(T button);
bool WasPressed(T button);
delegate void ButtonCallbackDelegate(IButtonInputs<T> buttonInputs, T button);
} }

View File

@ -1,5 +1,3 @@
using System;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Abstract; namespace Syntriax.Engine.Physics2D.Abstract;
@ -12,17 +10,17 @@ public interface ICollider2D : IBehaviour, IAssignableTransform
/// <summary> /// <summary>
/// Event triggered when a collision is detected. /// Event triggered when a collision is detected.
/// </summary> /// </summary>
Action<ICollider2D, CollisionDetectionInformation>? OnCollisionDetected { get; set; } event OnCollisionDetectedDelegate? OnCollisionDetected;
/// <summary> /// <summary>
/// Event triggered when a collision is resolved. /// Event triggered when a collision is resolved.
/// </summary> /// </summary>
Action<ICollider2D, CollisionDetectionInformation>? OnCollisionResolved { get; set; } event OnCollisionResolvedDelegate? OnCollisionResolved;
/// <summary> /// <summary>
/// Event triggered when another <see cref="ICollider2D"/> triggers this <see cref="ICollider2D"/>. /// Event triggered when another <see cref="ICollider2D"/> triggers this <see cref="ICollider2D"/>.
/// </summary> /// </summary>
Action<ICollider2D, ICollider2D>? OnTriggered { get; set; } event OnTriggeredDelegate? OnTriggered;
/// <summary> /// <summary>
/// The <see cref="IRigidBody2D"/> associated with the <see cref="ICollider2D"/>. /// The <see cref="IRigidBody2D"/> associated with the <see cref="ICollider2D"/>.
@ -38,4 +36,12 @@ public interface ICollider2D : IBehaviour, IAssignableTransform
/// Recalculates <see cref="ICollider2D"/> properties. /// Recalculates <see cref="ICollider2D"/> properties.
/// </summary> /// </summary>
void Recalculate(); void Recalculate();
void Detect(CollisionDetectionInformation collisionDetectionInformation);
void Resolve(CollisionDetectionInformation collisionDetectionInformation);
void Trigger(ICollider2D initiator);
delegate void OnCollisionDetectedDelegate(ICollider2D sender, CollisionDetectionInformation collisionDetectionInformation);
delegate void OnCollisionResolvedDelegate(ICollider2D sender, CollisionDetectionInformation collisionDetectionInformation);
delegate void OnTriggeredDelegate(ICollider2D sender, ICollider2D initiatorCollider);
} }

View File

@ -5,6 +5,16 @@ namespace Syntriax.Engine.Physics2D.Abstract;
/// </summary> /// </summary>
public interface IPhysicsEngine2D public interface IPhysicsEngine2D
{ {
/// <summary>
/// Event triggered when the <see cref="IPhysicsEngine2D"/> has done a single physics iteration.
/// </summary>
event OnPhysicsIterationDelegate? OnPhysicsIteration;
/// <summary>
/// Event triggered when the <see cref="IPhysicsEngine2D"/> has done a full physics step/>.
/// </summary>
event OnPhysicsStepDelegate? OnPhysicsStep;
/// <summary> /// <summary>
/// The number of iterations the <see cref="IPhysicsEngine2D"/> performs per step. /// The number of iterations the <see cref="IPhysicsEngine2D"/> performs per step.
/// </summary> /// </summary>
@ -15,4 +25,7 @@ public interface IPhysicsEngine2D
/// </summary> /// </summary>
/// <param name="deltaTime">The time step.</param> /// <param name="deltaTime">The time step.</param>
void Step(float deltaTime); void Step(float deltaTime);
delegate void OnPhysicsIterationDelegate(IPhysicsEngine2D sender, float iterationDeltaTime);
delegate void OnPhysicsStepDelegate(IPhysicsEngine2D sender, float stepDeltaTime);
} }

View File

@ -0,0 +1,15 @@
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a <see cref="IBehaviour"/> that listens to physics simulation update phase.
/// </summary>
public interface IPhysicsUpdate : IBehaviour
{
/// <summary>
/// Updates the physics state of the object based on the elapsed time since the last update.
/// </summary>
/// <param name="delta">The time elapsed since the last physics update, typically in seconds.</param>
void PhysicsUpdate(float delta);
}

View File

@ -1,16 +1,14 @@
using System;
using Syntriax.Engine.Core; using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Abstract; using Syntriax.Engine.Physics2D.Abstract;
namespace Syntriax.Engine.Physics2D; namespace Syntriax.Engine.Physics2D;
public abstract class Collider2DBehaviourBase : BehaviourOverride, ICollider2D public abstract class Collider2DBehaviourBase : Behaviour, ICollider2D
{ {
public Action<ICollider2D, CollisionDetectionInformation>? OnCollisionDetected { get; set; } = null; public event ICollider2D.OnCollisionDetectedDelegate? OnCollisionDetected = null;
public Action<ICollider2D, CollisionDetectionInformation>? OnCollisionResolved { get; set; } = null; public event ICollider2D.OnCollisionResolvedDelegate? OnCollisionResolved = null;
public Action<ICollider2D, ICollider2D>? OnTriggered { get; set; } = null; public event ICollider2D.OnTriggeredDelegate? OnTriggered = null;
protected bool NeedsRecalculation { get; private set; } = true; protected bool NeedsRecalculation { get; private set; } = true;
@ -20,7 +18,7 @@ public abstract class Collider2DBehaviourBase : BehaviourOverride, ICollider2D
public bool IsTrigger { get; set; } = false; public bool IsTrigger { get; set; } = false;
ITransform IAssignableTransform.Transform => Transform; ITransform IAssignableTransform.Transform => Transform;
Action<IAssignableTransform>? IAssignableTransform.OnTransformAssigned { get => GameObject.OnTransformAssigned; set => GameObject.OnTransformAssigned = value; } public event IAssignableTransform.OnTransformAssignedDelegate? OnTransformAssigned { add => GameObject.OnTransformAssigned += value; remove => GameObject.OnTransformAssigned -= value; }
bool IAssignableTransform.Assign(ITransform transform) => GameObject.Assign(transform); bool IAssignableTransform.Assign(ITransform transform) => GameObject.Assign(transform);
@ -37,7 +35,7 @@ public abstract class Collider2DBehaviourBase : BehaviourOverride, ICollider2D
protected override void OnInitialize() protected override void OnInitialize()
{ {
BehaviourController.TryGetBehaviour(out _rigidBody2D); BehaviourController.TryGetBehaviourInParent(out _rigidBody2D);
BehaviourController.OnBehaviourAdded += OnBehaviourAddedToController; BehaviourController.OnBehaviourAdded += OnBehaviourAddedToController;
BehaviourController.OnBehaviourRemoved += OnBehaviourRemovedFromController; BehaviourController.OnBehaviourRemoved += OnBehaviourRemovedFromController;
@ -45,6 +43,12 @@ public abstract class Collider2DBehaviourBase : BehaviourOverride, ICollider2D
Transform.OnPositionChanged += SetNeedsRecalculation; Transform.OnPositionChanged += SetNeedsRecalculation;
Transform.OnRotationChanged += SetNeedsRecalculation; Transform.OnRotationChanged += SetNeedsRecalculation;
Transform.OnScaleChanged += SetNeedsRecalculation; Transform.OnScaleChanged += SetNeedsRecalculation;
Transform.OnParentChanged += UpdateRigidBody2D;
}
private void UpdateRigidBody2D(ITransform sender, ITransform? previousParent, ITransform? newParent)
{
BehaviourController.TryGetBehaviourInParent(out _rigidBody2D);
} }
private void OnBehaviourAddedToController(IBehaviourController _, IBehaviour behaviour) private void OnBehaviourAddedToController(IBehaviourController _, IBehaviour behaviour)
@ -70,4 +74,8 @@ public abstract class Collider2DBehaviourBase : BehaviourOverride, ICollider2D
Transform.OnPositionChanged -= SetNeedsRecalculation; Transform.OnPositionChanged -= SetNeedsRecalculation;
Transform.OnRotationChanged -= SetNeedsRecalculation; Transform.OnRotationChanged -= SetNeedsRecalculation;
} }
public void Detect(CollisionDetectionInformation collisionDetectionInformation) => OnCollisionDetected?.Invoke(this, collisionDetectionInformation);
public void Resolve(CollisionDetectionInformation collisionDetectionInformation) => OnCollisionResolved?.Invoke(this, collisionDetectionInformation);
public void Trigger(ICollider2D initiator) => OnTriggered?.Invoke(this, initiator);
} }

View File

@ -6,14 +6,16 @@ namespace Syntriax.Engine.Physics2D;
[System.Diagnostics.DebuggerDisplay("Normal: {Normal.ToString(), nq}, Penetration: {Penetration}")] [System.Diagnostics.DebuggerDisplay("Normal: {Normal.ToString(), nq}, Penetration: {Penetration}")]
public readonly struct CollisionDetectionInformation public readonly struct CollisionDetectionInformation
( (
ICollider2D Left, ICollider2D Detector,
ICollider2D Right, ICollider2D Detected,
Vector2D Normal, Vector2D Normal,
float Penetration float Penetration
) )
{ {
public ICollider2D Left { get; init; } = Left; public ICollider2D Detector { get; init; } = Detector;
public ICollider2D Right { get; init; } = Right; public ICollider2D Detected { get; init; } = Detected;
public Vector2D Normal { get; init; } = Normal; public Vector2D Normal { get; init; } = Normal;
public float Penetration { get; init; } = Penetration; public float Penetration { get; init; } = Penetration;
public CollisionDetectionInformation Reverse() => new(Detected, Detector, -Normal, Penetration);
} }

View File

@ -55,7 +55,7 @@ public class CollisionDetector2D : ICollisionDetector2D
if (!leftProjection.Overlaps(rightProjection, out float depth)) if (!leftProjection.Overlaps(rightProjection, out float depth))
return false; return false;
if (collisionInformation.Left is null || Math.Abs(collisionInformation.Penetration) > Math.Abs(depth)) if (collisionInformation.Detector is null || Math.Abs(collisionInformation.Penetration) > Math.Abs(depth))
collisionInformation = new(left, right, projectionVector, depth); collisionInformation = new(left, right, projectionVector, depth);
} }
@ -79,7 +79,7 @@ public class CollisionDetector2D : ICollisionDetector2D
if (!shapeProjection.Overlaps(circleProjection, out float depth)) if (!shapeProjection.Overlaps(circleProjection, out float depth))
return false; return false;
if (collisionInformation.Left is null || Math.Abs(collisionInformation.Penetration) > Math.Abs(depth)) if (collisionInformation.Detector is null || Math.Abs(collisionInformation.Penetration) > Math.Abs(depth))
collisionInformation = new(shapeCollider, circleCollider, projectionVector, depth); collisionInformation = new(shapeCollider, circleCollider, projectionVector, depth);
} }
@ -92,7 +92,7 @@ public class CollisionDetector2D : ICollisionDetector2D
if (!shapeProjection.Overlaps(circleProjection, out float depth)) if (!shapeProjection.Overlaps(circleProjection, out float depth))
return false; return false;
if (collisionInformation.Left is null || Math.Abs(collisionInformation.Penetration) > Math.Abs(depth)) if (collisionInformation.Detector is null || Math.Abs(collisionInformation.Penetration) > Math.Abs(depth))
collisionInformation = new(shapeCollider, circleCollider, shapeToCircleProjectionVector, depth); collisionInformation = new(shapeCollider, circleCollider, shapeToCircleProjectionVector, depth);
} }

View File

@ -9,8 +9,8 @@ public class CollisionResolver2D : ICollisionResolver2D
{ {
Vector2D displacementVector = collisionInformation.Normal * collisionInformation.Penetration; Vector2D displacementVector = collisionInformation.Normal * collisionInformation.Penetration;
ICollider2D left = collisionInformation.Left; ICollider2D left = collisionInformation.Detector;
ICollider2D right = collisionInformation.Right; ICollider2D right = collisionInformation.Detected;
bool isLeftStatic = left.RigidBody2D?.IsStatic ?? true; bool isLeftStatic = left.RigidBody2D?.IsStatic ?? true;
bool isRightStatic = right.RigidBody2D?.IsStatic ?? true; bool isRightStatic = right.RigidBody2D?.IsStatic ?? true;
@ -38,7 +38,7 @@ public class CollisionResolver2D : ICollisionResolver2D
left.Recalculate(); left.Recalculate();
right.Recalculate(); right.Recalculate();
left.OnCollisionResolved?.Invoke(collisionInformation.Left, collisionInformation); left.Resolve(collisionInformation);
right.OnCollisionResolved?.Invoke(right, collisionInformation); right.Resolve(collisionInformation);
} }
} }

View File

@ -1,13 +1,33 @@
using System;
using Syntriax.Engine.Core; using Syntriax.Engine.Core;
using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Physics2D.Primitives; using Syntriax.Engine.Physics2D.Primitives;
namespace Engine.Physics2D; namespace Engine.Physics2D;
public static partial class Physics2D public static partial class Physics2D
{ {
public static bool Overlaps(this Shape shape, Vector2D point) => Overlaps(shape, point, out var _);
public static bool Overlaps(this Shape shape, Vector2D point, out float depth)
{
depth = float.MaxValue;
var vertices = shape.Vertices;
int count = vertices.Count;
for (int indexProjection = 0; indexProjection < count; indexProjection++)
{
Vector2D projectionVector = vertices[indexProjection].FromTo(vertices[(indexProjection + 1) % count]).Perpendicular().Normalized;
Projection shapeProjection = shape.ToProjection(projectionVector);
float projectedPoint = point.Dot(projectionVector);
if (shapeProjection.Max < projectedPoint || shapeProjection.Min > projectedPoint)
return false;
depth = Math.Min(depth, Math.Abs(Math.AbsMin(shapeProjection.Max - projectedPoint, shapeProjection.Min - projectedPoint)));
}
return true;
}
public static bool Overlaps(this Circle left, Circle right) public static bool Overlaps(this Circle left, Circle right)
{ {
float distanceSquared = left.Center.FromTo(right.Center).LengthSquared(); float distanceSquared = left.Center.FromTo(right.Center).LengthSquared();
@ -27,7 +47,7 @@ public static partial class Physics2D
normal = distanceVector.Normalized; normal = distanceVector.Normalized;
if (isOverlapping) if (isOverlapping)
depth = MathF.Sqrt(radiusSumSquared - distanceSquared); depth = Math.Sqrt(radiusSumSquared - distanceSquared);
return isOverlapping; return isOverlapping;
} }
@ -44,7 +64,7 @@ public static partial class Physics2D
normal = distanceVector.Normalized; normal = distanceVector.Normalized;
if (isOverlapping) if (isOverlapping)
depth = MathF.Sqrt(radiusSquared - distanceSquared); depth = Math.Sqrt(radiusSquared - distanceSquared);
return isOverlapping; return isOverlapping;
} }

View File

@ -0,0 +1,67 @@
using System.Collections;
using System.Collections.Generic;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Abstract;
namespace Engine.Physics2D;
public class PhysicsCoroutineManager : HierarchyObjectBase
{
private readonly List<IEnumerator> enumerators = [];
private IPhysicsEngine2D? physicsEngine = null;
public IEnumerator CreateCoroutine(IEnumerator enumerator)
{
enumerators.Add(enumerator);
return enumerator;
}
public void StopCoroutine(IEnumerator enumerator)
{
enumerators.Remove(enumerator);
}
protected override void OnEnteringHierarchy(IGameManager gameManager)
{
physicsEngine = gameManager.HierarchyObjects.FindObject<IPhysicsEngine2D>();
if (physicsEngine is IPhysicsEngine2D foundPhysicsEngine)
foundPhysicsEngine.OnPhysicsStep += OnPhysicsStep;
else
gameManager.OnUpdate += OnUpdate;
}
private void OnPhysicsStep(IPhysicsEngine2D sender, float stepDeltaTime)
{
for (int i = enumerators.Count - 1; i >= 0; i--)
{
if (enumerators[i].Current is ICoroutineYield coroutineYield && coroutineYield.Yield())
continue;
if (!enumerators[i].MoveNext())
enumerators.RemoveAt(i);
}
}
protected override void OnExitingHierarchy(IGameManager gameManager)
{
if (physicsEngine is IPhysicsEngine2D existingPhysicsEngine)
existingPhysicsEngine.OnPhysicsStep -= OnPhysicsStep;
gameManager.OnUpdate -= OnUpdate;
}
private void OnUpdate(IGameManager sender, EngineTime time)
{
if (GameManager is not IGameManager gameManager)
return;
physicsEngine = gameManager.HierarchyObjects.FindObject<IPhysicsEngine2D>();
if (physicsEngine is IPhysicsEngine2D foundPhysicsEngine)
{
foundPhysicsEngine.OnPhysicsStep += OnPhysicsStep;
gameManager.OnUpdate -= OnUpdate;
}
}
}

View File

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Abstract; using Syntriax.Engine.Physics2D.Abstract;
@ -8,6 +7,9 @@ namespace Syntriax.Engine.Physics2D;
public class PhysicsEngine2D : IPhysicsEngine2D public class PhysicsEngine2D : IPhysicsEngine2D
{ {
public event IPhysicsEngine2D.OnPhysicsIterationDelegate? OnPhysicsIteration = null;
public event IPhysicsEngine2D.OnPhysicsStepDelegate? OnPhysicsStep = null;
private readonly List<IRigidBody2D> rigidBodies = new(32); private readonly List<IRigidBody2D> rigidBodies = new(32);
private readonly List<ICollider2D> colliders = new(64); private readonly List<ICollider2D> colliders = new(64);
@ -72,7 +74,7 @@ public class PhysicsEngine2D : IPhysicsEngine2D
if (bothCollidersAreTriggers) if (bothCollidersAreTriggers)
continue; continue;
bool bothCollidersAreStatic = colliderX.RigidBody2D?.IsStatic ?? true && colliderX.RigidBody2D?.IsStatic == colliderY.RigidBody2D?.IsStatic; bool bothCollidersAreStatic = (colliderX.RigidBody2D?.IsStatic ?? true) && (colliderY.RigidBody2D?.IsStatic ?? true);
if (bothCollidersAreStatic) if (bothCollidersAreStatic)
continue; continue;
@ -80,23 +82,33 @@ public class PhysicsEngine2D : IPhysicsEngine2D
{ {
if (colliderX.IsTrigger) if (colliderX.IsTrigger)
{ {
colliderX.OnTriggered?.Invoke(colliderX, colliderY); colliderX.Trigger(colliderY);
continue; continue;
} }
else if (colliderY.IsTrigger) else if (colliderY.IsTrigger)
{ {
colliderY.OnTriggered?.Invoke(colliderY, colliderY); colliderY.Trigger(colliderY);
continue; continue;
} }
colliderX.OnCollisionDetected?.Invoke(colliderX, information); if (information.Detector == colliderX)
colliderY.OnCollisionDetected?.Invoke(colliderY, information); {
colliderX.Detect(information);
colliderY.Detect(information.Reverse());
}
else
{
colliderX.Detect(information.Reverse());
colliderY.Detect(information);
}
collisionResolver?.Resolve(information); collisionResolver?.Resolve(information);
} }
} }
} }
OnPhysicsIteration?.Invoke(this, intervalDeltaTime);
} }
OnPhysicsStep?.Invoke(this, deltaTime);
} }
private static void StepRigidBody(IRigidBody2D rigidBody, float intervalDeltaTime) private static void StepRigidBody(IRigidBody2D rigidBody, float intervalDeltaTime)

View File

@ -1,28 +1,25 @@
using System;
using Syntriax.Engine.Core; using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Abstract; using Syntriax.Engine.Physics2D.Abstract;
namespace Syntriax.Engine.Physics2D; namespace Syntriax.Engine.Physics2D;
public class PhysicsEngine2DCacher : IPhysicsEngine2D, IAssignableGameManager public class PhysicsEngine2DCollector : HierarchyObjectBase, IPhysicsEngine2D
{ {
public Action<IAssignable>? OnUnassigned { get; set; } = null; public event IPhysicsEngine2D.OnPhysicsIterationDelegate? OnPhysicsIteration = null;
public Action<IAssignableGameManager>? OnGameManagerAssigned { get; set; } = null; public event IPhysicsEngine2D.OnPhysicsStepDelegate? OnPhysicsStep = null;
private int _iterationPerStep = 1; private int _iterationPerStep = 1;
protected readonly ICollisionDetector2D collisionDetector = null!; protected readonly ICollisionDetector2D collisionDetector = null!;
protected readonly ICollisionResolver2D collisionResolver = null!; protected readonly ICollisionResolver2D collisionResolver = null!;
protected BehaviourCacher<IRigidBody2D> rigidBodyCacher = new(); protected BehaviourCollector<IRigidBody2D> rigidBodyCollector = new();
protected BehaviourCacher<ICollider2D> colliderCacher = new(); protected BehaviourCollector<ICollider2D> colliderCollector = new();
protected BehaviourCollector<IPhysicsUpdate> physicsUpdateCollector = new();
public int IterationPerStep { get => _iterationPerStep; set => _iterationPerStep = value < 1 ? 1 : value; } public int IterationPerStep { get => _iterationPerStep; set => _iterationPerStep = value < 1 ? 1 : value; }
public IGameManager GameManager { get; private set; } = null!;
public void Step(float deltaTime) public void Step(float deltaTime)
{ {
@ -31,23 +28,23 @@ public class PhysicsEngine2DCacher : IPhysicsEngine2D, IAssignableGameManager
for (int iterationIndex = 0; iterationIndex < IterationPerStep; iterationIndex++) for (int iterationIndex = 0; iterationIndex < IterationPerStep; iterationIndex++)
{ {
// Can Parallel // Can Parallel
foreach (var rigidBody in rigidBodyCacher) foreach (var rigidBody in rigidBodyCollector)
StepRigidBody(rigidBody, intervalDeltaTime); StepRigidBody(rigidBody, intervalDeltaTime);
// Can Parallel // Can Parallel
foreach (var collider in colliderCacher) foreach (var collider in colliderCollector)
collider.Recalculate(); collider.Recalculate();
// Can Parallel // Can Parallel
for (int x = 0; x < colliderCacher.Behaviours.Count; x++) for (int x = 0; x < colliderCollector.Behaviours.Count; x++)
{ {
ICollider2D? colliderX = colliderCacher.Behaviours[x]; ICollider2D? colliderX = colliderCollector.Behaviours[x];
if (!colliderX.IsActive) if (!colliderX.IsActive)
return; return;
for (int y = x + 1; y < colliderCacher.Behaviours.Count; y++) for (int y = x + 1; y < colliderCollector.Behaviours.Count; y++)
{ {
ICollider2D? colliderY = colliderCacher.Behaviours[y]; ICollider2D? colliderY = colliderCollector.Behaviours[y];
if (!colliderY.IsActive) if (!colliderY.IsActive)
return; return;
@ -59,7 +56,7 @@ public class PhysicsEngine2DCacher : IPhysicsEngine2D, IAssignableGameManager
if (bothCollidersAreTriggers) if (bothCollidersAreTriggers)
continue; continue;
bool bothCollidersAreStatic = colliderX.RigidBody2D?.IsStatic ?? true && colliderX.RigidBody2D?.IsStatic == colliderY.RigidBody2D?.IsStatic; bool bothCollidersAreStatic = (colliderX.RigidBody2D?.IsStatic ?? true) && (colliderY.RigidBody2D?.IsStatic ?? true);
if (bothCollidersAreStatic) if (bothCollidersAreStatic)
continue; continue;
@ -67,23 +64,38 @@ public class PhysicsEngine2DCacher : IPhysicsEngine2D, IAssignableGameManager
{ {
if (colliderX.IsTrigger) if (colliderX.IsTrigger)
{ {
colliderX.OnTriggered?.Invoke(colliderX, colliderY); colliderX.Trigger(colliderY);
continue; continue;
} }
else if (colliderY.IsTrigger) else if (colliderY.IsTrigger)
{ {
colliderY.OnTriggered?.Invoke(colliderY, colliderY); colliderY.Trigger(colliderY);
continue; continue;
} }
colliderX.OnCollisionDetected?.Invoke(colliderX, information); if (information.Detector == colliderX)
colliderY.OnCollisionDetected?.Invoke(colliderY, information); {
colliderX.Detect(information);
colliderY.Detect(information.Reverse());
}
else
{
colliderX.Detect(information.Reverse());
colliderY.Detect(information);
}
collisionResolver?.Resolve(information); collisionResolver?.Resolve(information);
} }
} }
} }
OnPhysicsIteration?.Invoke(this, intervalDeltaTime);
} }
foreach (IPhysicsUpdate physicsUpdate in physicsUpdateCollector)
physicsUpdate.PhysicsUpdate(deltaTime);
OnPhysicsStep?.Invoke(this, deltaTime);
} }
private static void StepRigidBody(IRigidBody2D rigidBody, float intervalDeltaTime) private static void StepRigidBody(IRigidBody2D rigidBody, float intervalDeltaTime)
@ -94,55 +106,27 @@ public class PhysicsEngine2DCacher : IPhysicsEngine2D, IAssignableGameManager
rigidBody.Transform.Position += rigidBody.Velocity * intervalDeltaTime; rigidBody.Transform.Position += rigidBody.Velocity * intervalDeltaTime;
rigidBody.Transform.Rotation += rigidBody.AngularVelocity * intervalDeltaTime; rigidBody.Transform.Rotation += rigidBody.AngularVelocity * intervalDeltaTime;
} }
protected override void OnEnteringHierarchy(IGameManager gameManager)
public bool Assign(IGameManager gameManager)
{ {
if (GameManager is not null) physicsUpdateCollector.Assign(gameManager);
return false; colliderCollector.Assign(gameManager);
rigidBodyCollector.Assign(gameManager);
colliderCacher.Assign(gameManager);
rigidBodyCacher.Assign(gameManager);
GameManager = gameManager;
OnGameManagerAssigned?.Invoke(this);
return true;
} }
public bool Unassign() protected override void OnExitingHierarchy(IGameManager gameManager)
{ {
if (GameManager is null) physicsUpdateCollector.Unassign();
return false; colliderCollector.Unassign();
rigidBodyCollector.Unassign();
colliderCacher.Unassign();
rigidBodyCacher.Unassign();
GameManager = null!;
OnUnassigned?.Invoke(this);
return true;
} }
public PhysicsEngine2DCacher() public PhysicsEngine2DCollector()
{ {
collisionDetector = new CollisionDetector2D(); collisionDetector = new CollisionDetector2D();
collisionResolver = new CollisionResolver2D(); collisionResolver = new CollisionResolver2D();
} }
public PhysicsEngine2DCacher(IGameManager gameManager) public PhysicsEngine2DCollector(ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver)
{
Assign(gameManager);
collisionDetector = new CollisionDetector2D();
collisionResolver = new CollisionResolver2D();
}
public PhysicsEngine2DCacher(IGameManager gameManager, ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver)
{
Assign(gameManager);
this.collisionDetector = collisionDetector;
this.collisionResolver = collisionResolver;
}
public PhysicsEngine2DCacher(ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver)
{ {
this.collisionDetector = collisionDetector; this.collisionDetector = collisionDetector;
this.collisionResolver = collisionResolver; this.collisionResolver = collisionResolver;

View File

@ -69,7 +69,7 @@ public readonly struct Circle(Vector2D center, float radius)
/// Transforms the <see cref="Circle"/> by the specified <see cref="ITransform"/>. /// Transforms the <see cref="Circle"/> by the specified <see cref="ITransform"/>.
/// </summary> /// </summary>
public static Circle TransformCircle(ITransform transform, Circle circle) public static Circle TransformCircle(ITransform transform, Circle circle)
=> new(transform.TransformVector2D(circle.Center), circle.Radius * transform.Scale.Magnitude); => new(transform.TransformVector2D(circle.Center), circle.Radius * (transform.Scale.Magnitude / Vector2D.One.Magnitude));
/// <summary> /// <summary>
/// Checks if two <see cref="Circle"/>s are approximately equal. /// Checks if two <see cref="Circle"/>s are approximately equal.

View File

@ -202,6 +202,40 @@ public readonly struct Line(Vector2D from, Vector2D to)
/// </summary> /// </summary>
public static class LineExtensions public static class LineExtensions
{ {
/// <summary>
/// Linearly interpolates between the two endpoints of the <see cref="Line"/> segment using parameter 't'.
/// </summary>
public static Vector2D Lerp(this Line line, float t) => Line.Lerp(line, t);
/// <summary>
/// The equation of the <see cref="Line"/> defined by this <see cref="Line"/> segment.
/// </summary>
public static LineEquation ToLineEquation(this Line line) => Line.GetLineEquation(line);
/// <summary>
/// Determines whether the specified <see cref="Vector2D"/> lies on the <see cref="Line"/>.
/// </summary>
public static bool Intersects(this Line line, Vector2D point) => Line.Intersects(line, point);
/// <summary>
/// Calculates the parameter 't' representing the point's position on the <see cref="Line"/> segment.
/// </summary>
public static float GetT(this Line line, Vector2D point) => Line.GetT(line, point);
/// <summary>
/// Checks if the <see cref="Line"/> segment intersects with another <see cref="Line"/> segment.
/// </summary>
public static bool Intersects(this Line left, Line right) => Line.Intersects(left, right);
/// <summary>
/// Determines whether two <see cref="Line"/> segments intersect.
/// </summary>
public static bool Intersects(this Line left, Line right, [NotNullWhen(returnValue: true)] out Vector2D? point) => Line.Intersects(left, right, out point);
/// <summary> /// <summary>
/// Checks if two <see cref="Line"/>s are approximately equal. /// Checks if two <see cref="Line"/>s are approximately equal.
/// </summary> /// </summary>

View File

@ -1,14 +1,12 @@
using System;
using Syntriax.Engine.Core; using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Abstract; using Syntriax.Engine.Physics2D.Abstract;
namespace Syntriax.Engine.Physics2D; namespace Syntriax.Engine.Physics2D;
public class RigidBody2D : BehaviourOverride, IRigidBody2D public class RigidBody2D : Behaviour, IRigidBody2D
{ {
public Action<IAssignableTransform>? OnTransformAssigned { get => GameObject.OnTransformAssigned; set => GameObject.OnTransformAssigned = value; } event IAssignableTransform.OnTransformAssignedDelegate? IAssignableTransform.OnTransformAssigned { add => GameObject.OnTransformAssigned += value; remove => GameObject.OnTransformAssigned -= value; }
private const float LOWEST_ALLOWED_MASS = 0.00001f; private const float LOWEST_ALLOWED_MASS = 0.00001f;
private float _mass = 1f; private float _mass = 1f;