11 Commits

Author SHA1 Message Date
be06575f91 feat: BehaviourExtensions.FindBehaviour 2024-02-08 17:58:15 +03:00
ed6975bf24 fix: Null Reference Error on ITransform.SetParent 2024-02-07 14:24:19 +03:00
d9660c08b1 feat: Collider RigidBody Reference Update on Parent Change 2024-02-07 12:33:07 +03:00
3902f1caca feat: Parent Change Propagation to Children 2024-02-07 12:32:55 +03:00
14e3337daa feat: BehaviourControllerExtensions
- TryGetBehaviourInParent
- GetBehaviourInParent
- TryGetBehaviourInChildren
- GetBehaviourInChildren
2024-02-07 11:53:57 +03:00
f729cdc0a8 revert: refactor: ITransformWithGameObject
This reverts commit f96c58cbd4.
2024-02-07 11:45:14 +03:00
c767e1e856 docs(core): Parent & Child Methods 2024-02-06 17:42:24 +03:00
f96c58cbd4 refactor: ITransformWithGameObject 2024-02-06 17:38:11 +03:00
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
6e4c9b0ef8 feat: Transform Hierarchy System 2024-02-06 15:55:07 +03:00
b931abb735 feat: Shape to Vector2D Overlap 2024-02-06 10:56:54 +03:00
8 changed files with 390 additions and 19 deletions

View File

@@ -1,11 +1,12 @@
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.
@@ -23,17 +24,75 @@ public interface ITransform
Action<ITransform>? OnRotationChanged { get; set; } Action<ITransform>? OnRotationChanged { get; set; }
/// <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>
Action<ITransform, ITransform?>? OnParentChanged { get; set; }
/// <summary>
/// Event triggered when a new <see cref="ITransform"/> is added to the <see cref="Children"/>.
/// </summary>
Action<ITransform, ITransform>? OnChildrenAdded { get; set; }
/// <summary>
/// Event triggered when an <see cref="ITransform"/> is removed from the <see cref="Children"/>.
/// </summary>
Action<ITransform, ITransform>? OnChildrenRemoved { get; set; }
/// <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);
} }

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

@@ -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

@@ -1,20 +1,42 @@
using System; 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<IAssignableGameObject>? OnGameObjectAssigned { get; set; } = null;
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<ITransform>? OnPositionChanged { get; set; } = null; public Action<ITransform>? OnPositionChanged { get; set; } = null;
public Action<ITransform>? OnScaleChanged { get; set; } = null; public Action<ITransform>? OnScaleChanged { get; set; } = null;
public Action<ITransform>? OnRotationChanged { get; set; } = null; public Action<ITransform>? OnRotationChanged { get; set; } = null;
public Action<ITransform, ITransform?>? OnParentChanged { get; set; } = null;
public Action<ITransform, ITransform>? OnChildrenAdded { get; set; } = null;
public Action<ITransform, ITransform>? OnChildrenRemoved { get; set; } = 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,6 +46,7 @@ public class Transform : ITransform
return; return;
_position = value; _position = value;
UpdateLocalPosition();
OnPositionChanged?.Invoke(this); OnPositionChanged?.Invoke(this);
} }
} }
@@ -37,6 +60,7 @@ public class Transform : ITransform
return; return;
_scale = value; _scale = value;
UpdateLocalScale();
OnScaleChanged?.Invoke(this); OnScaleChanged?.Invoke(this);
} }
} }
@@ -50,7 +74,210 @@ public class Transform : ITransform
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);
}
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)
{
// 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)
child.OnParentChanged?.Invoke(transform, previousParent);
}
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.Scale(Vector2D.One).Rotate(0f * Math.DegreeToRadian);
else
_position = Parent.Position + LocalPosition.Scale(new(1f / Parent.Scale.X, 1f / 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.Initialized)
return false;
GameObject = gameObject;
OnGameObjectAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (GameObject is not null && GameObject.Initialized)
return false;
GameObject = null!;
OnUnassigned?.Invoke(this);
return true;
}
} }

View File

@@ -37,7 +37,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 +45,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 _1, ITransform? _2)
{
BehaviourController.TryGetBehaviourInParent(out _rigidBody2D);
} }
private void OnBehaviourAddedToController(IBehaviourController _, IBehaviour behaviour) private void OnBehaviourAddedToController(IBehaviourController _, IBehaviour behaviour)

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;
} }