From d33c6201f4589925310b987b83736b49d512fea0 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Fri, 14 Jun 2024 22:37:48 -0300 Subject: [PATCH 01/20] feat: Add base particle system component. --- .../Common/Components/BaseParticleSystem.cs | 117 ++++++++++++++++++ Basalt/Types/Particle.cs | 20 +++ 2 files changed, 137 insertions(+) create mode 100644 Basalt/Common/Components/BaseParticleSystem.cs create mode 100644 Basalt/Types/Particle.cs diff --git a/Basalt/Common/Components/BaseParticleSystem.cs b/Basalt/Common/Components/BaseParticleSystem.cs new file mode 100644 index 0000000..aebdb3e --- /dev/null +++ b/Basalt/Common/Components/BaseParticleSystem.cs @@ -0,0 +1,117 @@ +using Basalt.Common.Entities; +using Basalt.Types; +using System.Numerics; + +namespace Basalt.Common.Components +{ + public abstract class BaseParticleSystem : Component + { + public Vector3 StartVelocity { get; set; } + + protected Particle[] _particles; + private int _length; + private float _emissionRate = 5, _particleLifetime = 5; + public float EmissionRate + { + get => _emissionRate; + set + { + _emissionRate = value; + ResizePool(); + } + } + + public float ParticleLifetime + { + get => _particleLifetime; + set + { + _particleLifetime = value; + ResizePool(); + } + } + + public delegate void ParticleUpdateDelegate(ref Particle particle); + private ParticleUpdateDelegate _particleUpdate; + private ParticleUpdateDelegate _onParticleReset; + protected BaseParticleSystem(Entity entity) : base(entity) + { + ResizePool(); + for (int i = 0; i < _particles.Length; i++) + { + _particles[i].Lifetime = ParticleLifetime / _length * i; + } + } + + public abstract void RenderParticles(); + public sealed override void OnRender() + { + RenderParticles(); + } + public override void OnUpdate() + { + var dt = Time.DeltaTime; + for (int i = 0; i < _particles.Length; i++) + { + _particles[i].Lifetime += dt; + _particles[i].Position += _particles[i].Velocity * dt; + _particleUpdate?.Invoke(ref _particles[i]); + if (_particles[i].Lifetime > ParticleLifetime) + { + _particles[i].Position = Entity.Transform.Position; + _particles[i].Rotation = Quaternion.Identity; + _particles[i].Velocity = Vector3.One; + _particles[i].Lifetime = 0; + _onParticleReset?.Invoke(ref _particles[i]); + } + } + } + + private void ResizePool() + { + int oldLength = _length; + _length = (int)(EmissionRate * ParticleLifetime); + var newPool = new Particle[_length]; + if(_length < oldLength) + { + for (int i = 0; i < _length; i++) + { + newPool[i] = _particles[i]; + } + } + else + { + for (int i = 0; i < oldLength; i++) + { + newPool[i] = _particles[i]; + } + for (int i = oldLength; i < _length; i++) + { + newPool[i] = new Particle(Entity.Transform.Position, Quaternion.Identity, Vector3.One, 0); + } + } + _particles = newPool; + } + + public void SubscribeUpdate(ParticleUpdateDelegate update) + { + _particleUpdate += update; + } + + public void UnsubscribeUpdate(ParticleUpdateDelegate update) + { + _particleUpdate -= update; + } + + public void SubscribeOnParticleReset(ParticleUpdateDelegate update) + { + _onParticleReset += update; + } + + public void UnsubscribeOnParticleReset(ParticleUpdateDelegate update) + { + _onParticleReset -= update; + } + + } +} diff --git a/Basalt/Types/Particle.cs b/Basalt/Types/Particle.cs new file mode 100644 index 0000000..df9540c --- /dev/null +++ b/Basalt/Types/Particle.cs @@ -0,0 +1,20 @@ +using System.Numerics; + +namespace Basalt.Types +{ + public struct Particle + { + public Vector3 Position; + public Quaternion Rotation; + public Vector3 Velocity; + public float Lifetime; + + public Particle(Vector3 position, Quaternion rotation, Vector3 velocity, float lifetime) + { + Position = position; + Rotation = rotation; + Velocity = velocity; + Lifetime = lifetime; + } + } +} From 3615e2ab304d60357448e15267bbf9b32eb02db9 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Fri, 14 Jun 2024 22:37:55 -0300 Subject: [PATCH 02/20] feat: Add RaylibParticleSystem component --- .../Components/RaylibParticleSystem.cs | 21 +++++++++++++++++++ Basalt.TestField/Basalt.TestField.csproj | 1 + Basalt.TestField/Program.cs | 19 ++++++++++------- 3 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 Basalt.Raylib/Components/RaylibParticleSystem.cs diff --git a/Basalt.Raylib/Components/RaylibParticleSystem.cs b/Basalt.Raylib/Components/RaylibParticleSystem.cs new file mode 100644 index 0000000..2ddfcc1 --- /dev/null +++ b/Basalt.Raylib/Components/RaylibParticleSystem.cs @@ -0,0 +1,21 @@ +using Basalt.Common.Components; +using Basalt.Common.Entities; +using Raylib_cs; + +namespace Basalt.Raylib.Components +{ + public class RaylibParticleSystem : BaseParticleSystem + { + public RaylibParticleSystem(Entity entity) : base(entity) + { + } + + public override void RenderParticles() + { + foreach(var particle in _particles) + { + Raylib_cs.Raylib.DrawSphere(particle.Position, 1f, Color.Red); + } + } + } +} diff --git a/Basalt.TestField/Basalt.TestField.csproj b/Basalt.TestField/Basalt.TestField.csproj index e4135ed..306d5ed 100644 --- a/Basalt.TestField/Basalt.TestField.csproj +++ b/Basalt.TestField/Basalt.TestField.csproj @@ -5,6 +5,7 @@ net8.0 enable enable + true diff --git a/Basalt.TestField/Program.cs b/Basalt.TestField/Program.cs index 3d2e90f..7a9e25d 100644 --- a/Basalt.TestField/Program.cs +++ b/Basalt.TestField/Program.cs @@ -66,15 +66,18 @@ Engine.CreateEntity(player); -var trigger = new Entity(); -trigger.Id = "entity.trigger"; -trigger.Transform.Position = new Vector3(10, 2.5f, 0); -trigger.AddComponent(new BoxCollider(trigger) { Size = new Vector3(5), IsTrigger = true }); -trigger.AddComponent(new ModelRenderer(trigger) { ModelCacheKey = "cube", Size = new Vector3(5), ColorTint = Color.Blue }); -trigger.AddComponent(new Rigidbody(trigger) { IsKinematic = true }); -trigger.AddComponent(new TestTrigger(trigger)); -Engine.CreateEntity(trigger); +var emitter = new Entity(); +emitter.Transform.Position = new Vector3(0, 10, 0); +emitter.AddComponent(new RaylibParticleSystem(emitter)); +Engine.CreateEntity(emitter); +var ps = emitter.GetComponent()!; + +ps.SubscribeOnParticleReset((ref Particle p) => +{ + p.Velocity = new(Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5); + +}); TestingUtils.SetupTestingScene(250); TestingUtils.SetupDebugInfo(); \ No newline at end of file From 4298aa71e2297b4c629cea5516eb9f16147c4c38 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Sat, 15 Jun 2024 13:17:21 -0300 Subject: [PATCH 03/20] Add Particle.Scale and Particle.Color prroperties --- Basalt/Types/Particle.cs | 46 +++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/Basalt/Types/Particle.cs b/Basalt/Types/Particle.cs index df9540c..978dd07 100644 --- a/Basalt/Types/Particle.cs +++ b/Basalt/Types/Particle.cs @@ -1,14 +1,32 @@ -using System.Numerics; +using System.Drawing; +using System.Numerics; namespace Basalt.Types { public struct Particle { - public Vector3 Position; - public Quaternion Rotation; - public Vector3 Velocity; - public float Lifetime; + public Vector3 Position = Vector3.Zero; + public Quaternion Rotation = Quaternion.Identity; + public Vector3 Velocity = Vector3.Zero; + public float Lifetime = 0; + public Vector3 Scale { get; set; } = Vector3.One; + public Color Color { get; set; } = Color.HotPink; + public Particle() + { + + } + public Particle(Vector3 position, Quaternion rotation) + { + Position = position; + Rotation = rotation; + } + public Particle(Vector3 position, Quaternion rotation, Vector3 velocity) + { + Position = position; + Rotation = rotation; + Velocity = velocity; + } public Particle(Vector3 position, Quaternion rotation, Vector3 velocity, float lifetime) { Position = position; @@ -16,5 +34,23 @@ public Particle(Vector3 position, Quaternion rotation, Vector3 velocity, float l Velocity = velocity; Lifetime = lifetime; } + public Particle(Vector3 position, Quaternion rotation, Vector3 velocity, float lifetime, Vector3 scale) + { + Position = position; + Rotation = rotation; + Velocity = velocity; + Lifetime = lifetime; + Scale = scale; + } + public Particle(Vector3 position, Quaternion rotation, Vector3 velocity, float lifetime, Vector3 scale, Color color) + { + Position = position; + Rotation = rotation; + Velocity = velocity; + Lifetime = lifetime; + Scale = scale; + Color = color; + } + } } From 3ffb29ab46d873e21bfa3cc4883c40486f338dda Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Sat, 15 Jun 2024 13:17:43 -0300 Subject: [PATCH 04/20] Modify particle reset to use custom default --- Basalt/Common/Components/BaseParticleSystem.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Basalt/Common/Components/BaseParticleSystem.cs b/Basalt/Common/Components/BaseParticleSystem.cs index aebdb3e..0e36bb9 100644 --- a/Basalt/Common/Components/BaseParticleSystem.cs +++ b/Basalt/Common/Components/BaseParticleSystem.cs @@ -6,9 +6,10 @@ namespace Basalt.Common.Components { public abstract class BaseParticleSystem : Component { - public Vector3 StartVelocity { get; set; } protected Particle[] _particles; + + private Particle defaults; private int _length; private float _emissionRate = 5, _particleLifetime = 5; public float EmissionRate @@ -32,8 +33,8 @@ public float ParticleLifetime } public delegate void ParticleUpdateDelegate(ref Particle particle); - private ParticleUpdateDelegate _particleUpdate; - private ParticleUpdateDelegate _onParticleReset; + private ParticleUpdateDelegate? _particleUpdate; + private ParticleUpdateDelegate? _onParticleReset; protected BaseParticleSystem(Entity entity) : base(entity) { ResizePool(); @@ -41,6 +42,7 @@ protected BaseParticleSystem(Entity entity) : base(entity) { _particles[i].Lifetime = ParticleLifetime / _length * i; } + defaults = new(Entity.Transform.Position, Quaternion.Identity, Vector3.Zero, 0); } public abstract void RenderParticles(); @@ -58,10 +60,7 @@ public override void OnUpdate() _particleUpdate?.Invoke(ref _particles[i]); if (_particles[i].Lifetime > ParticleLifetime) { - _particles[i].Position = Entity.Transform.Position; - _particles[i].Rotation = Quaternion.Identity; - _particles[i].Velocity = Vector3.One; - _particles[i].Lifetime = 0; + _particles[i] = defaults; _onParticleReset?.Invoke(ref _particles[i]); } } @@ -113,5 +112,10 @@ public void UnsubscribeOnParticleReset(ParticleUpdateDelegate update) _onParticleReset -= update; } + public void UpdateDefaults(Particle particle) + { + defaults = particle; + } + } } From 30320886fa55002160669a82b208074afff3921e Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Sat, 15 Jun 2024 13:18:04 -0300 Subject: [PATCH 05/20] Modify system to use models instead of sphere --- .../Components/RaylibParticleSystem.cs | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/Basalt.Raylib/Components/RaylibParticleSystem.cs b/Basalt.Raylib/Components/RaylibParticleSystem.cs index 2ddfcc1..dd2ea5c 100644 --- a/Basalt.Raylib/Components/RaylibParticleSystem.cs +++ b/Basalt.Raylib/Components/RaylibParticleSystem.cs @@ -1,20 +1,49 @@ using Basalt.Common.Components; using Basalt.Common.Entities; +using Basalt.Common.Exceptions; +using Basalt.Common.Utils; +using Basalt.Raylib.Graphics; +using Basalt.Types; using Raylib_cs; +using System.Numerics; namespace Basalt.Raylib.Components { public class RaylibParticleSystem : BaseParticleSystem { + private string _modelCacheKey; + public string ModelCacheKey + { + get => _modelCacheKey; + set + { + _modelCacheKey = value; + init = false; + } + } + bool init = false; + Model model; public RaylibParticleSystem(Entity entity) : base(entity) { } public override void RenderParticles() { - foreach(var particle in _particles) + if(!init) + { + init = true; + var m = ResourceCache.Instance.GetModel(ModelCacheKey); + if(m == null) + { + throw new InvalidResourceKeyException($"{nameof(ModelCacheKey)}:{ModelCacheKey}"); + } + model = m.Value; + + } + foreach (var particle in _particles) { - Raylib_cs.Raylib.DrawSphere(particle.Position, 1f, Color.Red); + model.Transform = Raymath.MatrixRotateXYZ(Raymath.QuaternionToEuler(particle.Rotation)); + Raylib_cs.Raylib.DrawModelEx(model, particle.Position, Vector3.UnitY, 0, particle.Scale, particle.Color.ToRaylibColor()); } } } From 7acdadee93a1f9ee79fec03577b3565df120ee86 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Sat, 15 Jun 2024 13:18:26 -0300 Subject: [PATCH 06/20] feat: Add System.Drawing.Color.ToRaylibColor() extension method --- Basalt.Raylib/ExtensionMethods.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Basalt.Raylib/ExtensionMethods.cs b/Basalt.Raylib/ExtensionMethods.cs index 2f0d8bd..78f2c47 100644 --- a/Basalt.Raylib/ExtensionMethods.cs +++ b/Basalt.Raylib/ExtensionMethods.cs @@ -8,6 +8,7 @@ using Basalt.Raylib.Input; using Basalt.Raylib.Sound; using Basalt.Types; +using Raylib_cs; namespace Basalt.Raylib { @@ -65,5 +66,7 @@ public static EngineBuilder UseRaylibPreset(this EngineBuilder builder, WindowIn builder.AddComponent(); return builder; } + + public static Raylib_cs.Color ToRaylibColor(this System.Drawing.Color color) => new(color.R, color.G, color.B, color.A); } } From 0c06bcad3c46d00045e8de8880e4fbb4bc9b88eb Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Sat, 15 Jun 2024 13:30:54 -0300 Subject: [PATCH 07/20] docs: Add documentation for changes so far --- .../Components/RaylibParticleSystem.cs | 10 ++- Basalt.Raylib/ExtensionMethods.cs | 6 ++ Basalt.TestField/Program.cs | 4 +- .../Common/Components/BaseParticleSystem.cs | 59 +++++++++++-- Basalt/Types/Particle.cs | 85 +++++++++++++++++-- 5 files changed, 148 insertions(+), 16 deletions(-) diff --git a/Basalt.Raylib/Components/RaylibParticleSystem.cs b/Basalt.Raylib/Components/RaylibParticleSystem.cs index dd2ea5c..287a1ce 100644 --- a/Basalt.Raylib/Components/RaylibParticleSystem.cs +++ b/Basalt.Raylib/Components/RaylibParticleSystem.cs @@ -9,9 +9,15 @@ namespace Basalt.Raylib.Components { + /// + /// A particle system that renders particles using Raylib. + /// public class RaylibParticleSystem : BaseParticleSystem { - private string _modelCacheKey; + private string _modelCacheKey = "sphere"; + /// + /// The cache key for the model to use for rendering particles. + /// public string ModelCacheKey { get => _modelCacheKey; @@ -27,7 +33,7 @@ public RaylibParticleSystem(Entity entity) : base(entity) { } - public override void RenderParticles() + protected override void RenderParticles() { if(!init) { diff --git a/Basalt.Raylib/ExtensionMethods.cs b/Basalt.Raylib/ExtensionMethods.cs index 78f2c47..8dfbf82 100644 --- a/Basalt.Raylib/ExtensionMethods.cs +++ b/Basalt.Raylib/ExtensionMethods.cs @@ -67,6 +67,12 @@ public static EngineBuilder UseRaylibPreset(this EngineBuilder builder, WindowIn return builder; } + + /// + /// Converts a to a . + /// + /// The color to be converted to a raylib color type + /// The original color as a public static Raylib_cs.Color ToRaylibColor(this System.Drawing.Color color) => new(color.R, color.G, color.B, color.A); } } diff --git a/Basalt.TestField/Program.cs b/Basalt.TestField/Program.cs index 7a9e25d..7a5abdc 100644 --- a/Basalt.TestField/Program.cs +++ b/Basalt.TestField/Program.cs @@ -68,7 +68,7 @@ var emitter = new Entity(); emitter.Transform.Position = new Vector3(0, 10, 0); -emitter.AddComponent(new RaylibParticleSystem(emitter)); +emitter.AddComponent(new RaylibParticleSystem(emitter) { ModelCacheKey = "cube"}); Engine.CreateEntity(emitter); var ps = emitter.GetComponent()!; @@ -76,6 +76,8 @@ ps.SubscribeOnParticleReset((ref Particle p) => { p.Velocity = new(Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5); + // Apply random rotation + p.Rotation = Quaternion.CreateFromYawPitchRoll(Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2); }); diff --git a/Basalt/Common/Components/BaseParticleSystem.cs b/Basalt/Common/Components/BaseParticleSystem.cs index 0e36bb9..ddc7f34 100644 --- a/Basalt/Common/Components/BaseParticleSystem.cs +++ b/Basalt/Common/Components/BaseParticleSystem.cs @@ -4,14 +4,31 @@ namespace Basalt.Common.Components { + /// + /// A base class for particle system components. + /// public abstract class BaseParticleSystem : Component { - + /// + /// The particle pool used by the particle system. + /// protected Particle[] _particles; - private Particle defaults; + /// + /// The default particle values to be set on reset. + /// + protected Particle defaults; + private int _length; private float _emissionRate = 5, _particleLifetime = 5; + + + /// + /// Gets or sets the emission rate of the particle system. + /// + /// + /// Modifying this value will resize the particle pool. + /// public float EmissionRate { get => _emissionRate; @@ -22,6 +39,12 @@ public float EmissionRate } } + /// + /// Gets or sets the lifetime of the particles in the system. + /// + /// + /// Modifying this value will resize the particle pool. + /// public float ParticleLifetime { get => _particleLifetime; @@ -31,7 +54,10 @@ public float ParticleLifetime ResizePool(); } } - + /// + /// A delegate for updating particles. + /// + /// The reference to the particle being updated public delegate void ParticleUpdateDelegate(ref Particle particle); private ParticleUpdateDelegate? _particleUpdate; private ParticleUpdateDelegate? _onParticleReset; @@ -45,7 +71,10 @@ protected BaseParticleSystem(Entity entity) : base(entity) defaults = new(Entity.Transform.Position, Quaternion.Identity, Vector3.Zero, 0); } - public abstract void RenderParticles(); + /// + /// Renders the particles in the system. + /// + protected abstract void RenderParticles(); public sealed override void OnRender() { RenderParticles(); @@ -86,32 +115,52 @@ private void ResizePool() } for (int i = oldLength; i < _length; i++) { - newPool[i] = new Particle(Entity.Transform.Position, Quaternion.Identity, Vector3.One, 0); + newPool[i] = defaults; } } _particles = newPool; } + /// + /// Subscribes a delegate to be called when a particle is updated every frame. + /// + /// The target delegate public void SubscribeUpdate(ParticleUpdateDelegate update) { _particleUpdate += update; } + /// + /// Unsubscribes a delegate from being called when a particle is updated every frame. + /// + /// The target delegate public void UnsubscribeUpdate(ParticleUpdateDelegate update) { _particleUpdate -= update; } + /// + /// Subscribes a delegate to be called when a particle is reset. + /// + /// The target delegate public void SubscribeOnParticleReset(ParticleUpdateDelegate update) { _onParticleReset += update; } + /// + /// Unsubscribes a delegate from being called when a particle is reset. + /// + /// The target delegate public void UnsubscribeOnParticleReset(ParticleUpdateDelegate update) { _onParticleReset -= update; } + /// + /// Changes the default values of the particles in the system. + /// + /// The new default particle value public void UpdateDefaults(Particle particle) { defaults = particle; diff --git a/Basalt/Types/Particle.cs b/Basalt/Types/Particle.cs index 978dd07..2acbbe1 100644 --- a/Basalt/Types/Particle.cs +++ b/Basalt/Types/Particle.cs @@ -1,32 +1,83 @@ -using System.Drawing; +using Basalt.Common.Components; +using System.Drawing; using System.Numerics; namespace Basalt.Types { + /// + /// A struct representing a particle used by derived types. + /// public struct Particle { + /// + /// The current position of the particle. + /// public Vector3 Position = Vector3.Zero; - public Quaternion Rotation = Quaternion.Identity; + + /// + /// The rotation of the particle. + /// + public Quaternion Rotation = Quaternion.Identity; + + /// + /// The velocity of the particle. + /// public Vector3 Velocity = Vector3.Zero; + + /// + /// The lifetime of the particle. + /// public float Lifetime = 0; + + /// + /// The scale of the particle. + /// public Vector3 Scale { get; set; } = Vector3.One; + + /// + /// The color of the particle. + /// public Color Color { get; set; } = Color.HotPink; - public Particle() - { - - } - public Particle(Vector3 position, Quaternion rotation) + /// + /// Initializes a new instance of the struct. + /// + public Particle() + { + + } + + /// + /// Initializes a new instance of the struct with the specified position and rotation. + /// + /// The position of the particle. + /// The rotation of the particle. + public Particle(Vector3 position, Quaternion rotation) { Position = position; Rotation = rotation; } + + /// + /// Initializes a new instance of the struct with the specified position, rotation, and velocity. + /// + /// The position of the particle. + /// The rotation of the particle. + /// The velocity of the particle. public Particle(Vector3 position, Quaternion rotation, Vector3 velocity) { Position = position; Rotation = rotation; Velocity = velocity; } + + /// + /// Initializes a new instance of the struct with the specified position, rotation, velocity, and lifetime. + /// + /// The position of the particle. + /// The rotation of the particle. + /// The velocity of the particle. + /// The lifetime of the particle. public Particle(Vector3 position, Quaternion rotation, Vector3 velocity, float lifetime) { Position = position; @@ -34,6 +85,15 @@ public Particle(Vector3 position, Quaternion rotation, Vector3 velocity, float l Velocity = velocity; Lifetime = lifetime; } + + /// + /// Initializes a new instance of the struct with the specified position, rotation, velocity, lifetime, and scale. + /// + /// The position of the particle. + /// The rotation of the particle. + /// The velocity of the particle. + /// The lifetime of the particle. + /// The scale of the particle. public Particle(Vector3 position, Quaternion rotation, Vector3 velocity, float lifetime, Vector3 scale) { Position = position; @@ -42,6 +102,16 @@ public Particle(Vector3 position, Quaternion rotation, Vector3 velocity, float l Lifetime = lifetime; Scale = scale; } + + /// + /// Initializes a new instance of the struct with the specified position, rotation, velocity, lifetime, scale, and color. + /// + /// The position of the particle. + /// The rotation of the particle. + /// The velocity of the particle. + /// The lifetime of the particle. + /// The scale of the particle. + /// The color of the particle. public Particle(Vector3 position, Quaternion rotation, Vector3 velocity, float lifetime, Vector3 scale, Color color) { Position = position; @@ -51,6 +121,5 @@ public Particle(Vector3 position, Quaternion rotation, Vector3 velocity, float l Scale = scale; Color = color; } - } } From 0099ea85347604e6749e0a2e58fdd1aec30b1ba1 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Sat, 15 Jun 2024 13:57:55 -0300 Subject: [PATCH 08/20] feat: Add CameraControllerBase generic class --- .../Common/Components/CameraControllerBase.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Basalt/Common/Components/CameraControllerBase.cs diff --git a/Basalt/Common/Components/CameraControllerBase.cs b/Basalt/Common/Components/CameraControllerBase.cs new file mode 100644 index 0000000..0e722ff --- /dev/null +++ b/Basalt/Common/Components/CameraControllerBase.cs @@ -0,0 +1,17 @@ +using Basalt.Common.Entities; + +namespace Basalt.Common.Components +{ + public abstract class CameraControllerBase : Component + { + public abstract T Camera { get; set; } + public CameraControllerBase(Entity entity) : base(entity) + { + } + + public virtual void SetCamera(T camera) + { + Camera = camera; + } + } +} From 2568b609786fbee19a3c445a74bcec0b044d4411 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Sat, 15 Jun 2024 13:58:29 -0300 Subject: [PATCH 09/20] feat: Add base Raylib camera controller class --- .../Components/RayCameraController.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Basalt.Raylib/Components/RayCameraController.cs diff --git a/Basalt.Raylib/Components/RayCameraController.cs b/Basalt.Raylib/Components/RayCameraController.cs new file mode 100644 index 0000000..12bdd1d --- /dev/null +++ b/Basalt.Raylib/Components/RayCameraController.cs @@ -0,0 +1,27 @@ +using Basalt.Common.Components; +using Basalt.Common.Entities; +using Raylib_cs; +using System.Numerics; + +namespace Basalt.Raylib.Components +{ + public class RayCameraController : CameraControllerBase + { + public RayCameraController(Entity entity) : base(entity) + { + Camera = new Camera3D + { + Position = Entity.Transform.Position, + Target = Entity.Transform.Position + Entity.Transform.Forward, + Up = new Vector3(0f, 1f, 0f), + FovY = 60f, + Projection = CameraProjection.Perspective + }; + + // Set the camera as the active camera in raylib + Raylib_cs.Raylib.UpdateCamera(ref camera, CameraMode.FirstPerson); + } + protected Camera3D camera; + public override Camera3D Camera { get => camera; set => camera = value; } + } +} From 57c6f74f4d9ca29fc8802633b56bba3624df3a39 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Sat, 15 Jun 2024 13:58:39 -0300 Subject: [PATCH 10/20] feat: Add First Person camera controller class --- .../Components/FirstPersonCameraController.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Basalt.Raylib/Components/FirstPersonCameraController.cs diff --git a/Basalt.Raylib/Components/FirstPersonCameraController.cs b/Basalt.Raylib/Components/FirstPersonCameraController.cs new file mode 100644 index 0000000..4d470d0 --- /dev/null +++ b/Basalt.Raylib/Components/FirstPersonCameraController.cs @@ -0,0 +1,33 @@ +using Basalt.Common.Entities; +using Basalt.Math; +using System.Numerics; +using static Raylib_cs.Raylib; + +namespace Basalt.Raylib.Components +{ + public class FirstPersonCameraController : RayCameraController + { + public float Sensitivity = 0.1f; + public FirstPersonCameraController(Entity entity) : base(entity) + { + } + public override void OnUpdate() + { + if (!Enabled) + return; + + Vector3 rotation = new(GetMouseDelta().X * Sensitivity, // Rotation: yaw + GetMouseDelta().Y * Sensitivity, // Rotation: pitch + 0.0f); // Rotation: roll + + // Update the camera in raylib + + camera.Position = Entity.Transform.Position; + camera.Target = camera.Position + Entity.Transform.Forward; + + UpdateCameraPro(ref camera, Vector3.Zero, rotation, 0); + + Entity.Transform.Rotation = BasaltMath.LookAtRotation(camera.Position, camera.Target, camera.Up); + } + } +} From 7ec532351f8efac7ffa4aa26f2409ea9ec4eacd4 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Sat, 15 Jun 2024 13:59:37 -0300 Subject: [PATCH 11/20] feat: RaylibGraphicsEngine updated to support new camera system. --- .../Graphics/RaylibGraphicsEngine.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Basalt.Raylib/Graphics/RaylibGraphicsEngine.cs b/Basalt.Raylib/Graphics/RaylibGraphicsEngine.cs index 28ec454..234f566 100644 --- a/Basalt.Raylib/Graphics/RaylibGraphicsEngine.cs +++ b/Basalt.Raylib/Graphics/RaylibGraphicsEngine.cs @@ -107,14 +107,20 @@ public unsafe void Initialize() public unsafe void Render() { Camera3D camera = new(); - var control = entityManager.GetEntities().FirstOrDefault(e => e.GetComponent() != null)?.GetComponent() ?? null; + var control = Engine.Instance.EntityManager.GetEntities().FirstOrDefault(e => e.GetComponents().FirstOrDefault(c => c.GetType().IsSubclassOf(typeof(RayCameraController))) is not null); + RayCameraController controller; if (control == null) { - throw new NullReferenceException("No camera controller found in the scene."); + Engine.Instance.Logger?.LogError("No camera controller found. Using default controller instead."); + var entity = new Entity(); + entity.AddComponent(new FirstPersonCameraController(entity)); + Engine.CreateEntity(entity); + controller = entity.GetComponent()!; + control = entity; } - - camera = control!.camera; - control.OnStart(); + else + controller = control!.GetComponents().FirstOrDefault(c => c.GetType().IsSubclassOf(typeof(RayCameraController))) as RayCameraController; + camera = controller!.Camera; RenderTexture2D target = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); @@ -140,7 +146,6 @@ public unsafe void Render() // Main game loop while (ShouldRun) { - control.OnUpdate(); Time.DeltaTime = GetFrameTime(); @@ -173,7 +178,7 @@ public unsafe void Render() foreach (var source in sources) Rlights.UpdateLightValues(LightShader, source.Source); - SetShaderValue(LightShader, LightShader.Locs[(int)ShaderLocationIndex.VectorView], control.Entity.Transform.Position, ShaderUniformDataType.Vec3); + SetShaderValue(LightShader, LightShader.Locs[(int)ShaderLocationIndex.VectorView], controller.Entity.Transform.Position, ShaderUniformDataType.Vec3); } //---------------------------------------------------------------------------------- @@ -184,7 +189,7 @@ public unsafe void Render() BeginTextureMode(target); ClearBackground(Color.Black); - BeginMode3D(control.camera); + BeginMode3D(controller.Camera); eventBus?.TriggerEvent(BasaltConstants.RenderEventKey); From 0cd2cab1cc853e496f08492679fabebd651e2a5f Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Sat, 15 Jun 2024 14:01:58 -0300 Subject: [PATCH 12/20] feat: CameraController can now be changed for RaylibGraphicsEngine --- .../Graphics/RaylibGraphicsEngine.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Basalt.Raylib/Graphics/RaylibGraphicsEngine.cs b/Basalt.Raylib/Graphics/RaylibGraphicsEngine.cs index 234f566..5776ee6 100644 --- a/Basalt.Raylib/Graphics/RaylibGraphicsEngine.cs +++ b/Basalt.Raylib/Graphics/RaylibGraphicsEngine.cs @@ -28,6 +28,7 @@ public class RaylibGraphicsEngine : IGraphicsEngine private EntityManager entityManager; private ISoundSystem? soundSystem; private IEventBus eventBus; + private RayCameraController? cameraController; Shader PostProcessShader, LightShader; bool ShouldRun = true; @@ -108,19 +109,18 @@ public unsafe void Render() { Camera3D camera = new(); var control = Engine.Instance.EntityManager.GetEntities().FirstOrDefault(e => e.GetComponents().FirstOrDefault(c => c.GetType().IsSubclassOf(typeof(RayCameraController))) is not null); - RayCameraController controller; if (control == null) { Engine.Instance.Logger?.LogError("No camera controller found. Using default controller instead."); var entity = new Entity(); entity.AddComponent(new FirstPersonCameraController(entity)); Engine.CreateEntity(entity); - controller = entity.GetComponent()!; + cameraController = entity.GetComponent()!; control = entity; } else - controller = control!.GetComponents().FirstOrDefault(c => c.GetType().IsSubclassOf(typeof(RayCameraController))) as RayCameraController; - camera = controller!.Camera; + cameraController = control!.GetComponents().FirstOrDefault(c => c.GetType().IsSubclassOf(typeof(RayCameraController))) as RayCameraController; + camera = cameraController!.Camera; RenderTexture2D target = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); @@ -178,7 +178,7 @@ public unsafe void Render() foreach (var source in sources) Rlights.UpdateLightValues(LightShader, source.Source); - SetShaderValue(LightShader, LightShader.Locs[(int)ShaderLocationIndex.VectorView], controller.Entity.Transform.Position, ShaderUniformDataType.Vec3); + SetShaderValue(LightShader, LightShader.Locs[(int)ShaderLocationIndex.VectorView], cameraController.Entity.Transform.Position, ShaderUniformDataType.Vec3); } //---------------------------------------------------------------------------------- @@ -189,7 +189,7 @@ public unsafe void Render() BeginTextureMode(target); ClearBackground(Color.Black); - BeginMode3D(controller.Camera); + BeginMode3D(cameraController.Camera); eventBus?.TriggerEvent(BasaltConstants.RenderEventKey); @@ -264,13 +264,16 @@ public static T InvokeOnThread(Func delegateFunc) private T invoke(Func delegateFunc) => delegateFunc(); - - public void Shutdown() { ShouldRun = false; ResourceCache.Instance.UnloadRaylib(); logger?.LogWarning("Shutting down graphics engine..."); } + + public void SetCameraController(RayCameraController newController) + { + cameraController = newController; + } } } From 5e5db23546ef5d7162356fd559bef9ef949300ac Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Sat, 15 Jun 2024 15:53:57 -0300 Subject: [PATCH 13/20] docs: Add XML doc comments chore: Run Code Cleanup --- .../Components/FirstPersonCameraController.cs | 14 ++++++++++++- Basalt.Raylib/Components/Image.cs | 2 +- .../Components/RayCameraController.cs | 12 +++++++++++ .../Components/RaylibParticleSystem.cs | 5 ++--- Basalt.Raylib/ExtensionMethods.cs | 1 - Basalt.Raylib/Sound/RaylibSoundSystem.cs | 4 ++-- Basalt.TestField/Components/DebugInfo.cs | 2 -- Basalt.TestField/Components/TestTrigger.cs | 2 +- Basalt.TestField/Program.cs | 7 +++---- Basalt.Tests/CircularBufferTests.cs | 2 -- Basalt.Tests/StateMachineTests.cs | 3 +-- .../ComponentDependentOnAttribute.cs | 4 +--- .../Common/Components/BaseParticleSystem.cs | 4 ++-- Basalt/Common/Components/BoxCollider.cs | 3 +-- .../Common/Components/CameraControllerBase.cs | 21 ++++++++++++------- Basalt/Common/Components/Transform.cs | 5 ++--- Basalt/Common/Entities/Entity.cs | 2 +- Basalt/Common/Entities/EntityManager.cs | 1 - Basalt/Common/Physics/CollisionHandler.cs | 2 +- Basalt/Common/Physics/PhysicsEngine.cs | 5 ++--- Basalt/Common/Utils/ResourceCache.cs | 2 +- Basalt/Engine.cs | 8 +++---- Basalt/Utility/CircularBuffer.cs | 2 +- 23 files changed, 65 insertions(+), 48 deletions(-) diff --git a/Basalt.Raylib/Components/FirstPersonCameraController.cs b/Basalt.Raylib/Components/FirstPersonCameraController.cs index 4d470d0..8cf10ea 100644 --- a/Basalt.Raylib/Components/FirstPersonCameraController.cs +++ b/Basalt.Raylib/Components/FirstPersonCameraController.cs @@ -5,12 +5,24 @@ namespace Basalt.Raylib.Components { + /// + /// Represents a first-person camera controller for Raylib. + /// public class FirstPersonCameraController : RayCameraController { + /// + /// The sensitivity of the camera controller. + /// public float Sensitivity = 0.1f; + + /// + /// Initializes a new instance of the class. + /// + /// The entity associated with the camera controller. public FirstPersonCameraController(Entity entity) : base(entity) { } + public override void OnUpdate() { if (!Enabled) @@ -18,7 +30,7 @@ public override void OnUpdate() Vector3 rotation = new(GetMouseDelta().X * Sensitivity, // Rotation: yaw GetMouseDelta().Y * Sensitivity, // Rotation: pitch - 0.0f); // Rotation: roll + 0.0f); // Rotation: roll // Update the camera in raylib diff --git a/Basalt.Raylib/Components/Image.cs b/Basalt.Raylib/Components/Image.cs index 4578538..34adec4 100644 --- a/Basalt.Raylib/Components/Image.cs +++ b/Basalt.Raylib/Components/Image.cs @@ -3,8 +3,8 @@ using Basalt.Common.Utils; using Basalt.Raylib.Graphics; using Raylib_cs; -using static Raylib_cs.Raylib; using System.Numerics; +using static Raylib_cs.Raylib; namespace Basalt.Raylib.Components { diff --git a/Basalt.Raylib/Components/RayCameraController.cs b/Basalt.Raylib/Components/RayCameraController.cs index 12bdd1d..6593e35 100644 --- a/Basalt.Raylib/Components/RayCameraController.cs +++ b/Basalt.Raylib/Components/RayCameraController.cs @@ -5,8 +5,15 @@ namespace Basalt.Raylib.Components { + /// + /// Represents a camera controller for Raylib using Camera3D. + /// public class RayCameraController : CameraControllerBase { + /// + /// Initializes a new instance of the class. + /// + /// The entity associated with the camera controller. public RayCameraController(Entity entity) : base(entity) { Camera = new Camera3D @@ -21,7 +28,12 @@ public RayCameraController(Entity entity) : base(entity) // Set the camera as the active camera in raylib Raylib_cs.Raylib.UpdateCamera(ref camera, CameraMode.FirstPerson); } + protected Camera3D camera; + + /// + /// Gets or sets the camera. + /// public override Camera3D Camera { get => camera; set => camera = value; } } } diff --git a/Basalt.Raylib/Components/RaylibParticleSystem.cs b/Basalt.Raylib/Components/RaylibParticleSystem.cs index 287a1ce..39adb00 100644 --- a/Basalt.Raylib/Components/RaylibParticleSystem.cs +++ b/Basalt.Raylib/Components/RaylibParticleSystem.cs @@ -3,7 +3,6 @@ using Basalt.Common.Exceptions; using Basalt.Common.Utils; using Basalt.Raylib.Graphics; -using Basalt.Types; using Raylib_cs; using System.Numerics; @@ -35,11 +34,11 @@ public RaylibParticleSystem(Entity entity) : base(entity) protected override void RenderParticles() { - if(!init) + if (!init) { init = true; var m = ResourceCache.Instance.GetModel(ModelCacheKey); - if(m == null) + if (m == null) { throw new InvalidResourceKeyException($"{nameof(ModelCacheKey)}:{ModelCacheKey}"); } diff --git a/Basalt.Raylib/ExtensionMethods.cs b/Basalt.Raylib/ExtensionMethods.cs index 8dfbf82..c461f37 100644 --- a/Basalt.Raylib/ExtensionMethods.cs +++ b/Basalt.Raylib/ExtensionMethods.cs @@ -8,7 +8,6 @@ using Basalt.Raylib.Input; using Basalt.Raylib.Sound; using Basalt.Types; -using Raylib_cs; namespace Basalt.Raylib { diff --git a/Basalt.Raylib/Sound/RaylibSoundSystem.cs b/Basalt.Raylib/Sound/RaylibSoundSystem.cs index 6261c39..aedc15c 100644 --- a/Basalt.Raylib/Sound/RaylibSoundSystem.cs +++ b/Basalt.Raylib/Sound/RaylibSoundSystem.cs @@ -28,7 +28,7 @@ public void Initialize() private void UpdateStream(object? sender, EventArgs args) { - if(MusicPlaying is not null) + if (MusicPlaying is not null) { UpdateMusicStream(MusicPlaying.Value); } @@ -49,7 +49,7 @@ public void Shutdown() /// /// The logger to use for logging. public RaylibSoundSystem() - { + { } /// diff --git a/Basalt.TestField/Components/DebugInfo.cs b/Basalt.TestField/Components/DebugInfo.cs index d3ba5f2..770307b 100644 --- a/Basalt.TestField/Components/DebugInfo.cs +++ b/Basalt.TestField/Components/DebugInfo.cs @@ -1,8 +1,6 @@ using Basalt.Common; using Basalt.Common.Components; using Basalt.Common.Entities; -using Raylib_cs; -using System.Diagnostics; using static Raylib_cs.Raylib; namespace Basalt.TestField.Components diff --git a/Basalt.TestField/Components/TestTrigger.cs b/Basalt.TestField/Components/TestTrigger.cs index f6c3eaa..0e8e351 100644 --- a/Basalt.TestField/Components/TestTrigger.cs +++ b/Basalt.TestField/Components/TestTrigger.cs @@ -12,7 +12,7 @@ public TestTrigger(Entity entity) : base(entity) public override void OnCollision(Collider other) { - if(other.Entity.Id == "entity.player") + if (other.Entity.Id == "entity.player") { Console.WriteLine($"Trigger collided with {other.Entity.Id}"); Entity.GetComponent().ModelCacheKey = "sphere"; diff --git a/Basalt.TestField/Program.cs b/Basalt.TestField/Program.cs index 7a5abdc..0650bb7 100644 --- a/Basalt.TestField/Program.cs +++ b/Basalt.TestField/Program.cs @@ -15,7 +15,6 @@ using Basalt.Raylib.Sound; using Basalt.Raylib.Utils; using Basalt.TestField; -using Basalt.TestField.Components; using Basalt.Types; using Raylib_cs; using System.Numerics; @@ -54,7 +53,7 @@ var player = new Entity(); -player.AddComponent(new CameraController(player)); +player.AddComponent(new FirstPersonCameraController(player)); player.Id = "entity.player"; Vector3 offset = Vector3.UnitY * -1; player.Transform.Position = new Vector3(0, 5, 0); @@ -68,7 +67,7 @@ var emitter = new Entity(); emitter.Transform.Position = new Vector3(0, 10, 0); -emitter.AddComponent(new RaylibParticleSystem(emitter) { ModelCacheKey = "cube"}); +emitter.AddComponent(new RaylibParticleSystem(emitter) { ModelCacheKey = "cube" }); Engine.CreateEntity(emitter); var ps = emitter.GetComponent()!; @@ -78,7 +77,7 @@ p.Velocity = new(Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5); // Apply random rotation p.Rotation = Quaternion.CreateFromYawPitchRoll(Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2); - + }); TestingUtils.SetupTestingScene(250); diff --git a/Basalt.Tests/CircularBufferTests.cs b/Basalt.Tests/CircularBufferTests.cs index b912d5f..4602fa2 100644 --- a/Basalt.Tests/CircularBufferTests.cs +++ b/Basalt.Tests/CircularBufferTests.cs @@ -1,6 +1,4 @@ using Basalt.Utility; -using NUnit.Framework; -using System; namespace Basalt.Tests { diff --git a/Basalt.Tests/StateMachineTests.cs b/Basalt.Tests/StateMachineTests.cs index 257a3a7..46b1496 100644 --- a/Basalt.Tests/StateMachineTests.cs +++ b/Basalt.Tests/StateMachineTests.cs @@ -1,6 +1,5 @@ -using NUnit.Framework; -using Moq; using Basalt.Utility; +using Moq; namespace Basalt.Tests { diff --git a/Basalt/Common/Attributes/ComponentDependentOnAttribute.cs b/Basalt/Common/Attributes/ComponentDependentOnAttribute.cs index 358bca6..a95067a 100644 --- a/Basalt/Common/Attributes/ComponentDependentOnAttribute.cs +++ b/Basalt/Common/Attributes/ComponentDependentOnAttribute.cs @@ -1,6 +1,4 @@ -using Basalt.Common.Components; - -namespace Basalt.Common.Attributes +namespace Basalt.Common.Attributes { [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] diff --git a/Basalt/Common/Components/BaseParticleSystem.cs b/Basalt/Common/Components/BaseParticleSystem.cs index ddc7f34..6d9c3b8 100644 --- a/Basalt/Common/Components/BaseParticleSystem.cs +++ b/Basalt/Common/Components/BaseParticleSystem.cs @@ -33,7 +33,7 @@ public float EmissionRate { get => _emissionRate; set - { + { _emissionRate = value; ResizePool(); } @@ -100,7 +100,7 @@ private void ResizePool() int oldLength = _length; _length = (int)(EmissionRate * ParticleLifetime); var newPool = new Particle[_length]; - if(_length < oldLength) + if (_length < oldLength) { for (int i = 0; i < _length; i++) { diff --git a/Basalt/Common/Components/BoxCollider.cs b/Basalt/Common/Components/BoxCollider.cs index f2f22c9..c2e749f 100644 --- a/Basalt/Common/Components/BoxCollider.cs +++ b/Basalt/Common/Components/BoxCollider.cs @@ -1,5 +1,4 @@ -using Basalt.Common.Attributes; -using Basalt.Common.Entities; +using Basalt.Common.Entities; using Newtonsoft.Json; using System.Numerics; diff --git a/Basalt/Common/Components/CameraControllerBase.cs b/Basalt/Common/Components/CameraControllerBase.cs index 0e722ff..0e89490 100644 --- a/Basalt/Common/Components/CameraControllerBase.cs +++ b/Basalt/Common/Components/CameraControllerBase.cs @@ -2,16 +2,23 @@ namespace Basalt.Common.Components { - public abstract class CameraControllerBase : Component + /// + /// Base class for camera controllers. + /// + /// The type that represents a camera. + public abstract class CameraControllerBase : Component { - public abstract T Camera { get; set; } - public CameraControllerBase(Entity entity) : base(entity) - { - } + /// + /// Gets or sets the camera. + /// + public abstract T Camera { get; set; } - public virtual void SetCamera(T camera) + /// + /// Initializes a new instance of the class. + /// + /// The entity. + public CameraControllerBase(Entity entity) : base(entity) { - Camera = camera; } } } diff --git a/Basalt/Common/Components/Transform.cs b/Basalt/Common/Components/Transform.cs index eb9dc20..afed5db 100644 --- a/Basalt/Common/Components/Transform.cs +++ b/Basalt/Common/Components/Transform.cs @@ -1,5 +1,4 @@ using Basalt.Common.Entities; -using Basalt.Common.Physics; using Basalt.Core.Common.Attributes; using Basalt.Math; using Newtonsoft.Json; @@ -28,9 +27,9 @@ public Vector3 Position return; } - if(Engine.Instance.EntityManager != null) + if (Engine.Instance.EntityManager != null) Engine.Instance.EntityManager.ChunkingMechanism.MarkForUpdate(Entity); - + var offset = value - position; position = value; diff --git a/Basalt/Common/Entities/Entity.cs b/Basalt/Common/Entities/Entity.cs index 4521506..6a5646a 100644 --- a/Basalt/Common/Entities/Entity.cs +++ b/Basalt/Common/Entities/Entity.cs @@ -366,7 +366,7 @@ internal void CallStart() if (dependencyAttribute != null) { var missing = dependencyAttribute.Dependencies.Where(d => !HasComponent(d)); - if(missing.Any()) + if (missing.Any()) { Engine.Instance.Logger?.LogError($"Component \"{component.GetType().Name}\" is missing component dependencies: {string.Join(", ", missing.Select(m => $"\"{m.Name}\""))}"); } diff --git a/Basalt/Common/Entities/EntityManager.cs b/Basalt/Common/Entities/EntityManager.cs index dcce452..d8e4288 100644 --- a/Basalt/Common/Entities/EntityManager.cs +++ b/Basalt/Common/Entities/EntityManager.cs @@ -1,5 +1,4 @@ using Basalt.Common.Physics; -using Basalt.Core.Common.Abstractions.Engine; namespace Basalt.Common.Entities { diff --git a/Basalt/Common/Physics/CollisionHandler.cs b/Basalt/Common/Physics/CollisionHandler.cs index cbb48aa..3187239 100644 --- a/Basalt/Common/Physics/CollisionHandler.cs +++ b/Basalt/Common/Physics/CollisionHandler.cs @@ -95,7 +95,7 @@ private static void BoxBoxCollision(Collider col1, Collider col2) return; // Handle collisions and separate them from here - + // Calculate the direction of least penetration Vector3 separationDirection = Vector3.Zero; float minOverlap = Min(overlapX, Min(overlapY, overlapZ)); diff --git a/Basalt/Common/Physics/PhysicsEngine.cs b/Basalt/Common/Physics/PhysicsEngine.cs index 08ec3e6..b0f7c4e 100644 --- a/Basalt/Common/Physics/PhysicsEngine.cs +++ b/Basalt/Common/Physics/PhysicsEngine.cs @@ -1,5 +1,4 @@ -using Basalt.Common.Components; -using Basalt.Common.Entities; +using Basalt.Common.Entities; using Basalt.Common.Utils; using Basalt.Core.Common.Abstractions.Engine; @@ -33,7 +32,7 @@ public class PhysicsEngine : IPhysicsEngine public PhysicsEngine() #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. { - + } /// diff --git a/Basalt/Common/Utils/ResourceCache.cs b/Basalt/Common/Utils/ResourceCache.cs index 62c4953..1dc6347 100644 --- a/Basalt/Common/Utils/ResourceCache.cs +++ b/Basalt/Common/Utils/ResourceCache.cs @@ -84,7 +84,7 @@ public static bool TryGetResource(string resourceName, out T resource) if (Instance.resourceCache.ContainsKey(resourceName)) { var r = Instance.resourceCache[resourceName]; - if(r is T t) + if (r is T t) { resource = t; return true; diff --git a/Basalt/Engine.cs b/Basalt/Engine.cs index 3e04a2a..99628fa 100644 --- a/Basalt/Engine.cs +++ b/Basalt/Engine.cs @@ -56,13 +56,13 @@ public bool Running /// public EntityManager EntityManager { - get - { - if(_entityManager == null) + get + { + if (_entityManager == null) { _entityManager = new(); } - return _entityManager; + return _entityManager; } private set { _entityManager = value; } } diff --git a/Basalt/Utility/CircularBuffer.cs b/Basalt/Utility/CircularBuffer.cs index afc4314..e44226a 100644 --- a/Basalt/Utility/CircularBuffer.cs +++ b/Basalt/Utility/CircularBuffer.cs @@ -79,7 +79,7 @@ public CircularBuffer(int size, Func generator) } _index = 0; } - + #endregion /// From 6b1d15a55c7f2ccdb5424c470677ac6171b9c246 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Sat, 15 Jun 2024 16:54:12 -0300 Subject: [PATCH 14/20] feat: Add Entity.Clone() and Entity.Create() --- Basalt.TestField/Program.cs | 174 ++++++++++-------- Basalt.Tests/Common/TestComponent.cs | 11 +- .../Integration/EntityIntegrationTests.cs | 76 ++++++++ Basalt/Common/Entities/Entity.cs | 94 +++++++++- 4 files changed, 268 insertions(+), 87 deletions(-) diff --git a/Basalt.TestField/Program.cs b/Basalt.TestField/Program.cs index 0650bb7..42c66e6 100644 --- a/Basalt.TestField/Program.cs +++ b/Basalt.TestField/Program.cs @@ -1,84 +1,104 @@ -using Basalt; -using Basalt.Common; -using Basalt.Common.Components; +//using Basalt; +//using Basalt.Common; +//using Basalt.Common.Components; +//using Basalt.Common.Entities; +//using Basalt.Common.Events; +//using Basalt.Common.Logging; +//using Basalt.Common.Physics; +//using Basalt.Common.Utils; +//using Basalt.Core.Common.Abstractions.Engine; +//using Basalt.Core.Common.Abstractions.Input; +//using Basalt.Core.Common.Abstractions.Sound; +//using Basalt.Raylib.Components; +//using Basalt.Raylib.Graphics; +//using Basalt.Raylib.Input; +//using Basalt.Raylib.Sound; +//using Basalt.Raylib.Utils; +//using Basalt.TestField; +//using Basalt.Types; +//using Raylib_cs; +//using System.Numerics; + + +//var initParams = new WindowInitParams +//{ +// Title = "Basalt Test Field", +// Width = 1920, +// Height = 1080, +// TargetFps = 120, +// Fullscreen = false, +// Borderless = true, +// MSAA4X = true, +// PostProcessing = false, +//}; +//ResourceCache.Instance.LoadShader("lighting", @"resources/shaders/lighting.fs", @"resources/shaders/lighting.vs"); +//ResourceCache.Instance.LoadModel("robot", @"resources/robot.glb", "lighting"); +//ResourceCache.Instance.LoadTexture("logo", @"resources/logo.png"); +//ResourceCache.Instance.LoadAudio("music", @"resources/country.mp3", ResourceCacheExtensions.AudioLoadRequest.Type.Music); +//ResourceCache.Instance.LoadAudio("sfx", @"resources/boom.wav", ResourceCacheExtensions.AudioLoadRequest.Type.Sound); +//var builder = new EngineBuilder(); + +//builder.AddComponent(() => new RaylibGraphicsEngine(initParams) { LightingShaderCacheKey = "lighting" }, true); +//builder.AddComponent(true); +//builder.AddComponent(); +//builder.AddComponent(); +//builder.AddComponent(); + +//builder.AddLogger(new ConsoleLogger(Basalt.Core.Common.Types.LogLevel.Info)); + +//var engine = builder.Build(); + +//engine.Initialize(); + + + +//var player = new Entity(); +//player.AddComponent(new FirstPersonCameraController(player)); +//player.Id = "entity.player"; +//Vector3 offset = Vector3.UnitY * -1; +//player.Transform.Position = new Vector3(0, 5, 0); +//player.AddComponent(new SphereRenderer(player) { Size = new Vector3(1f), Color = Color.Red, Offset = offset }); +//player.AddComponent(new BoxCollider(player) { Size = new Vector3(1, 2, 1), Offset = offset }); +//player.AddComponent(new Rigidbody(player) { IsKinematic = false, Mass = 25 }); +//player.AddComponent(new Basalt.TestField.Components.PlayerController(player)); +//player.AddComponent(new LightSource(player, "lighting") { Color = Color.Red, Type = LightType.Point }); + +//Engine.CreateEntity(player); + +//var emitter = new Entity(); +//emitter.Transform.Position = new Vector3(0, 10, 0); +//emitter.AddComponent(new RaylibParticleSystem(emitter) { ModelCacheKey = "cube" }); +//Engine.CreateEntity(emitter); + +//var ps = emitter.GetComponent()!; + +//ps.SubscribeOnParticleReset((ref Particle p) => +//{ +// p.Velocity = new(Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5); +// // Apply random rotation +// p.Rotation = Quaternion.CreateFromYawPitchRoll(Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2); + +//}); + +//TestingUtils.SetupTestingScene(250); +//TestingUtils.SetupDebugInfo(); + using Basalt.Common.Entities; -using Basalt.Common.Events; -using Basalt.Common.Logging; -using Basalt.Common.Physics; -using Basalt.Common.Utils; -using Basalt.Core.Common.Abstractions.Engine; -using Basalt.Core.Common.Abstractions.Input; -using Basalt.Core.Common.Abstractions.Sound; -using Basalt.Raylib.Components; -using Basalt.Raylib.Graphics; -using Basalt.Raylib.Input; -using Basalt.Raylib.Sound; -using Basalt.Raylib.Utils; -using Basalt.TestField; -using Basalt.Types; -using Raylib_cs; using System.Numerics; +var entity = new Entity(); +entity.Transform.Position = new(0, 1, 0); +entity.Transform.Rotation = Quaternion.CreateFromYawPitchRoll(1, 1, 0); +var clone = entity.Clone(); -var initParams = new WindowInitParams -{ - Title = "Basalt Test Field", - Width = 1920, - Height = 1080, - TargetFps = 120, - Fullscreen = false, - Borderless = true, - MSAA4X = true, - PostProcessing = false, -}; -ResourceCache.Instance.LoadShader("lighting", @"resources/shaders/lighting.fs", @"resources/shaders/lighting.vs"); -ResourceCache.Instance.LoadModel("robot", @"resources/robot.glb", "lighting"); -ResourceCache.Instance.LoadTexture("logo", @"resources/logo.png"); -ResourceCache.Instance.LoadAudio("music", @"resources/country.mp3", ResourceCacheExtensions.AudioLoadRequest.Type.Music); -ResourceCache.Instance.LoadAudio("sfx", @"resources/boom.wav", ResourceCacheExtensions.AudioLoadRequest.Type.Sound); -var builder = new EngineBuilder(); - -builder.AddComponent(() => new RaylibGraphicsEngine(initParams) { LightingShaderCacheKey = "lighting" }, true); -builder.AddComponent(true); -builder.AddComponent(); -builder.AddComponent(); -builder.AddComponent(); - -builder.AddLogger(new ConsoleLogger(Basalt.Core.Common.Types.LogLevel.Info)); - -var engine = builder.Build(); - -engine.Initialize(); - - - -var player = new Entity(); -player.AddComponent(new FirstPersonCameraController(player)); -player.Id = "entity.player"; -Vector3 offset = Vector3.UnitY * -1; -player.Transform.Position = new Vector3(0, 5, 0); -player.AddComponent(new SphereRenderer(player) { Size = new Vector3(1f), Color = Color.Red, Offset = offset }); -player.AddComponent(new BoxCollider(player) { Size = new Vector3(1, 2, 1), Offset = offset }); -player.AddComponent(new Rigidbody(player) { IsKinematic = false, Mass = 25 }); -player.AddComponent(new Basalt.TestField.Components.PlayerController(player)); -player.AddComponent(new LightSource(player, "lighting") { Color = Color.Red, Type = LightType.Point }); - -Engine.CreateEntity(player); - -var emitter = new Entity(); -emitter.Transform.Position = new Vector3(0, 10, 0); -emitter.AddComponent(new RaylibParticleSystem(emitter) { ModelCacheKey = "cube" }); -Engine.CreateEntity(emitter); - -var ps = emitter.GetComponent()!; - -ps.SubscribeOnParticleReset((ref Particle p) => +foreach(var prop in entity.Transform.GetType().GetProperties()) { - p.Velocity = new(Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5); - // Apply random rotation - p.Rotation = Quaternion.CreateFromYawPitchRoll(Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2); + Console.WriteLine($"{prop.Name}: {prop.GetValue(entity.Transform)}"); -}); +} + +foreach (var prop in clone.Transform.GetType().GetProperties()) +{ + Console.WriteLine($"Cloned {prop.Name}: {prop.GetValue(clone.Transform)}"); +} -TestingUtils.SetupTestingScene(250); -TestingUtils.SetupDebugInfo(); \ No newline at end of file diff --git a/Basalt.Tests/Common/TestComponent.cs b/Basalt.Tests/Common/TestComponent.cs index b96f108..b606ad5 100644 --- a/Basalt.Tests/Common/TestComponent.cs +++ b/Basalt.Tests/Common/TestComponent.cs @@ -29,8 +29,12 @@ public Entity? Target } } } - public bool HasStarted; - public int OnStartCount = 0, OnUpdateCount = 0, OnRenderCount = 0, OnPhysicsUpdateCount = 0; + public bool HasStarted {get; set;} + public int OnStartCount { get; set; } = 0; + public int OnUpdateCount { get; set; } = 0; + public int OnRenderCount {get; set;} = 0; + public int OnPhysicsUpdateCount { get; set;} = 0; + public int Foo { get; private set; } = 0; public override void OnStart() { HasStarted = true; @@ -57,5 +61,8 @@ public override void OnPhysicsUpdate() OnPhysicsUpdateCount++; } + public int Get() => Foo; + public void Set(int value) => Foo = value; + } } diff --git a/Basalt.Tests/Integration/EntityIntegrationTests.cs b/Basalt.Tests/Integration/EntityIntegrationTests.cs index 86af1f9..b4dd029 100644 --- a/Basalt.Tests/Integration/EntityIntegrationTests.cs +++ b/Basalt.Tests/Integration/EntityIntegrationTests.cs @@ -316,5 +316,81 @@ public void EntityEnabled_WhenDisabled_ShouldNotCallEvents() Assert.That(entity.GetComponent()!.OnRenderCount, Is.EqualTo(0), "Render was called"); Assert.That(entity.GetComponent()!.OnPhysicsUpdateCount, Is.EqualTo(0), "Physics Update was called"); } + + [Test] + public void CloneEntity_ShouldReturnNewInstances() + { + // Arrange + var entity = new Entity(); + entity.AddComponent(new TestComponent(entity)); + entity.AddComponent(new Rigidbody(entity)); + var tc = entity.GetComponent()!; + var rb = entity.GetComponent()!; + + // Act + Engine.Instance.Initialize(); + Engine.CreateEntity(entity); + var clone = entity.Clone(); + var ctc = clone.GetComponent()!; + var crb = clone.GetComponent()!; + + // Assert + Assert.That(clone, Is.Not.Null); + Assert.That(clone, Is.Not.EqualTo(entity)); + Assert.That(ctc, Is.Not.EqualTo(tc)); + Assert.That(crb, Is.Not.EqualTo(rb)); + Assert.That(ctc.Entity, Is.EqualTo(clone)); + Assert.That(crb.Entity, Is.EqualTo(clone)); + } + + [Test] + public void CloneEntity_WhenCloning_ShouldCloneAllComponents() + { + // Arrange + var entity = new Entity(); + entity.AddComponent(new TestComponent(entity)); + entity.AddComponent(new Rigidbody(entity)); + + // Act + Engine.Instance.Initialize(); + Engine.CreateEntity(entity); + var clone = entity.Clone(); + + // Assert + Assert.IsNotNull(clone); + Assert.IsNotNull(clone.GetComponent()); + Assert.IsNotNull(clone.GetComponent()); + } + + + [Test] + public void CloneEntity_WhenCloning_ShouldCopyProperties() + { + // Arrange + var entity = new Entity(); + entity.Transform.Position = Vector3.One; + entity.AddComponent(new TestComponent(entity) { OnRenderCount = 42 }); + entity.AddComponent(new Rigidbody(entity) { Mass = 10, IsKinematic = true, Drag = 1.1f}); + + var rb = entity.GetComponent()!; + var tc = entity.GetComponent()!; + + tc.Set(69); + + // Act + Engine.Instance.Initialize(); + Engine.CreateEntity(entity); + var clone = entity.Clone(); + var crb = clone.GetComponent()!; + var ctc = clone.GetComponent()!; + + // Assert + Assert.That(clone.Transform.Position, Is.EqualTo(entity.Transform.Position).Using((IEqualityComparer) new Vector3EqualityComparer())); + Assert.That(clone.GetComponent()!.OnRenderCount, Is.EqualTo(entity.GetComponent()!.OnRenderCount)); + Assert.That(crb.IsKinematic, Is.EqualTo(rb.IsKinematic)); + Assert.That(crb.Mass, Is.EqualTo(rb.Mass)); + Assert.That(crb.Drag, Is.EqualTo(rb.Drag)); + Assert.That(ctc.Foo, Is.Not.EqualTo(tc.Foo)); + } } } diff --git a/Basalt/Common/Entities/Entity.cs b/Basalt/Common/Entities/Entity.cs index 6a5646a..f94dd3d 100644 --- a/Basalt/Common/Entities/Entity.cs +++ b/Basalt/Common/Entities/Entity.cs @@ -173,11 +173,17 @@ public static Entity DeserializeFromJson(string json) /// Adds a component to the entity. /// /// The component to add - public void AddComponent(Component component) + public void AddComponent(Component component, bool overwrite = false) { // Check for singleton attribute if (components.Any(c => c.GetType() == component.GetType()) && component.GetType().GetCustomAttribute() != null) - return; + { + if(!overwrite) + return; + + // Replace the existing component + components.Remove(components.First(c => c.GetType() == component.GetType())); + } components.Add(component); switch (component) @@ -201,6 +207,10 @@ public void AddComponent(Component component) } + /// + /// Works identical to AddComponent, however forces changes to singleton components by overwritting them. + /// + /// private void ForceAddComponent(Component component) { @@ -213,14 +223,23 @@ private void ForceAddComponent(Component component) components.Add(component); - if (Rigidbody == null && component is Rigidbody rb) + switch (component) { - Rigidbody = rb; - } + case Rigidbody rb: + Rigidbody = rb; + break; - else if (component is Transform t) - { - Transform = t; + case Transform t: + Transform = t; + break; + + case Collider c: + Collider = c; + break; + + default: + // Handle other cases if necessary + break; } } @@ -332,6 +351,17 @@ public void Destroy() } } + /// + /// Adds the entity to the engine by calling and recursively adds all children. + /// + public void Create() + { + Engine.CreateEntity(this); + foreach (var child in Children) + { + child.Create(); + } + } internal void CallOnCollision(Collider other) { @@ -375,5 +405,53 @@ internal void CallStart() } } } + + /// + /// Creates a deep copy of the entity and all of its children and components. + /// + /// + /// The clone entity will not be added to the engine automatically. Use or to add it to the engine.
+ /// The result will have a randomly generated Id, and all components will be cloned, with a few limitations: + /// + /// Will only copy public fields; + /// Will only copy properties if the setter is public; + /// Will not copy static fields or properties; + /// Will not copy init-only fields or properties; + /// Reference-type fields and properties will have their references copied; + /// + ///
+ /// A deep copy of the entity + public Entity Clone() + { + var result = new Entity(); + result.Id = Id + Guid.NewGuid().ToString(); + foreach (var component in components) + { + var c = Activator.CreateInstance(component.GetType(), result) as Component; + foreach (var prop in c.GetType().GetProperties()) + { + // Only copy values if the property has a setter, is not static and is public + + if (prop.SetMethod != null && prop.CanWrite && !prop.SetMethod.IsStatic && prop.SetMethod.IsPublic) + prop.SetValue(c, prop.GetValue(component)); + } + + foreach(var field in c.GetType().GetFields()) + { + if(field.IsPublic && !field.IsStatic && !field.IsInitOnly && !field.IsLiteral) + field.SetValue(c, field.GetValue(component)); + } + + c.Entity = result; + c.started = false; + + result.ForceAddComponent(c); + } + foreach(var child in Children) + { + result.AddChildren(child.Clone()); + } + return result; + } } } From 0aa34fb04739f155e495895d83f935fca4231899 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Mon, 17 Jun 2024 13:58:47 -0300 Subject: [PATCH 15/20] fix: OnCollision no longer called when entity is destroyed. --- Basalt/Common/Entities/Entity.cs | 8 +++++++- Basalt/Common/Physics/CollisionHandler.cs | 2 +- Basalt/Common/Physics/PhysicsEngine.cs | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Basalt/Common/Entities/Entity.cs b/Basalt/Common/Entities/Entity.cs index f94dd3d..b24192e 100644 --- a/Basalt/Common/Entities/Entity.cs +++ b/Basalt/Common/Entities/Entity.cs @@ -63,6 +63,9 @@ public bool Enabled } } + [JsonIgnore] + public bool Destroyed { get; private set; } = false; + public Entity() { Transform = new Transform(this); @@ -339,6 +342,7 @@ public void RemoveChildren(Entity child) ///
public void Destroy() { + destroyed = true; Engine.RemoveEntity(this); foreach (var child in Children) { @@ -363,8 +367,10 @@ public void Create() } } - internal void CallOnCollision(Collider other) + public void CallOnCollision(Collider other) { + if (destroyed) + return; foreach (var component in components) component.OnCollision(other); } diff --git a/Basalt/Common/Physics/CollisionHandler.cs b/Basalt/Common/Physics/CollisionHandler.cs index 3187239..389148a 100644 --- a/Basalt/Common/Physics/CollisionHandler.cs +++ b/Basalt/Common/Physics/CollisionHandler.cs @@ -49,7 +49,7 @@ public static void Handle(Collider col1, Collider col2) /// The second box collider. private static void BoxBoxCollision(Collider col1, Collider col2) { - if (!col1.Enabled || !col2.Enabled) + if (!col1.Enabled || !col2.Enabled || col1.Entity.Destroyed || col2.Entity.Destroyed) return; BoxCollider box1 = (BoxCollider)col1; diff --git a/Basalt/Common/Physics/PhysicsEngine.cs b/Basalt/Common/Physics/PhysicsEngine.cs index b0f7c4e..a1c62bf 100644 --- a/Basalt/Common/Physics/PhysicsEngine.cs +++ b/Basalt/Common/Physics/PhysicsEngine.cs @@ -19,7 +19,6 @@ public class PhysicsEngine : IPhysicsEngine private IEventBus eventBus; private ILogger? logger; private bool ShouldRun = true; - private List entities; /// /// Gets or sets the gravity value for the physics engine. /// From b2cd357fdb311c21ae2f62c820f3373fca38f34e Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Mon, 17 Jun 2024 13:59:29 -0300 Subject: [PATCH 16/20] remove misspelling --- Basalt/Common/Entities/Entity.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Basalt/Common/Entities/Entity.cs b/Basalt/Common/Entities/Entity.cs index b24192e..4f3b401 100644 --- a/Basalt/Common/Entities/Entity.cs +++ b/Basalt/Common/Entities/Entity.cs @@ -342,7 +342,7 @@ public void RemoveChildren(Entity child) /// public void Destroy() { - destroyed = true; + Destroyed = true; Engine.RemoveEntity(this); foreach (var child in Children) { @@ -369,7 +369,7 @@ public void Create() public void CallOnCollision(Collider other) { - if (destroyed) + if (Destroyed) return; foreach (var component in components) component.OnCollision(other); From 32de89577af550199992a5b8413546a7149123bc Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Tue, 18 Jun 2024 13:05:12 -0300 Subject: [PATCH 17/20] fix: InvalidResourceKeyException now contains the param name and value that caused the exception --- Basalt.Raylib/Components/ModelRenderer.cs | 2 +- Basalt.Raylib/Components/RaylibParticleSystem.cs | 2 +- Basalt/Common/Exceptions/InvalidResourceKeyException.cs | 3 ++- Basalt/Common/Physics/PhysicsEngine.cs | 3 +-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Basalt.Raylib/Components/ModelRenderer.cs b/Basalt.Raylib/Components/ModelRenderer.cs index 78af068..5b140f5 100644 --- a/Basalt.Raylib/Components/ModelRenderer.cs +++ b/Basalt.Raylib/Components/ModelRenderer.cs @@ -72,7 +72,7 @@ public override unsafe void OnRender() cube = ResourceCache.Instance.GetModel(ModelCacheKey)!.Value; else { - throw new InvalidResourceKeyException(nameof(ModelCacheKey)); + throw new InvalidResourceKeyException(nameof(ModelCacheKey), ModelCacheKey); } init = true; } diff --git a/Basalt.Raylib/Components/RaylibParticleSystem.cs b/Basalt.Raylib/Components/RaylibParticleSystem.cs index 39adb00..7256c71 100644 --- a/Basalt.Raylib/Components/RaylibParticleSystem.cs +++ b/Basalt.Raylib/Components/RaylibParticleSystem.cs @@ -40,7 +40,7 @@ protected override void RenderParticles() var m = ResourceCache.Instance.GetModel(ModelCacheKey); if (m == null) { - throw new InvalidResourceKeyException($"{nameof(ModelCacheKey)}:{ModelCacheKey}"); + throw new InvalidResourceKeyException(nameof(ModelCacheKey), ModelCacheKey); } model = m.Value; diff --git a/Basalt/Common/Exceptions/InvalidResourceKeyException.cs b/Basalt/Common/Exceptions/InvalidResourceKeyException.cs index 5140aa2..a1d5d76 100644 --- a/Basalt/Common/Exceptions/InvalidResourceKeyException.cs +++ b/Basalt/Common/Exceptions/InvalidResourceKeyException.cs @@ -2,7 +2,8 @@ { public class InvalidResourceKeyException : Exception { - public InvalidResourceKeyException(string key) : base($"The resource key '{key}' is invalid or not found. Make sure the key is correctly typed or whether the resource was loaded or not.") + public InvalidResourceKeyException(string paramName, string paramValue) + : base($"The resource key '{paramValue}' for '{paramName}' is invalid or not found. Make sure the key is correctly typed or whether the resource was loaded or not.") { } } diff --git a/Basalt/Common/Physics/PhysicsEngine.cs b/Basalt/Common/Physics/PhysicsEngine.cs index a1c62bf..1b46d65 100644 --- a/Basalt/Common/Physics/PhysicsEngine.cs +++ b/Basalt/Common/Physics/PhysicsEngine.cs @@ -74,11 +74,10 @@ public void Simulate() eventBus?.TriggerEvent(BasaltConstants.PhysicsUpdateEventKey); - Engine.Instance.EntityManager.ChunkingMechanism.Update(); - // Check for collisions DetectCollisions(Engine.Instance.EntityManager.ChunkingMechanism.GetEntitiesChunked()); + Engine.Instance.EntityManager.ChunkingMechanism.Update(); elapsedTime = DateTimeOffset.Now.ToUnixTimeMilliseconds() - startTime; From 00bc197b6db7c4cbdf60b260fd2f0226059b91f6 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Tue, 18 Jun 2024 13:09:36 -0300 Subject: [PATCH 18/20] feat: Particle Systems can be looping or have limited emission duration. --- Basalt.TestField/Program.cs | 174 ++++++++---------- .../Common/Components/BaseParticleSystem.cs | 38 +++- 2 files changed, 114 insertions(+), 98 deletions(-) diff --git a/Basalt.TestField/Program.cs b/Basalt.TestField/Program.cs index 42c66e6..0650bb7 100644 --- a/Basalt.TestField/Program.cs +++ b/Basalt.TestField/Program.cs @@ -1,104 +1,84 @@ -//using Basalt; -//using Basalt.Common; -//using Basalt.Common.Components; -//using Basalt.Common.Entities; -//using Basalt.Common.Events; -//using Basalt.Common.Logging; -//using Basalt.Common.Physics; -//using Basalt.Common.Utils; -//using Basalt.Core.Common.Abstractions.Engine; -//using Basalt.Core.Common.Abstractions.Input; -//using Basalt.Core.Common.Abstractions.Sound; -//using Basalt.Raylib.Components; -//using Basalt.Raylib.Graphics; -//using Basalt.Raylib.Input; -//using Basalt.Raylib.Sound; -//using Basalt.Raylib.Utils; -//using Basalt.TestField; -//using Basalt.Types; -//using Raylib_cs; -//using System.Numerics; - - -//var initParams = new WindowInitParams -//{ -// Title = "Basalt Test Field", -// Width = 1920, -// Height = 1080, -// TargetFps = 120, -// Fullscreen = false, -// Borderless = true, -// MSAA4X = true, -// PostProcessing = false, -//}; -//ResourceCache.Instance.LoadShader("lighting", @"resources/shaders/lighting.fs", @"resources/shaders/lighting.vs"); -//ResourceCache.Instance.LoadModel("robot", @"resources/robot.glb", "lighting"); -//ResourceCache.Instance.LoadTexture("logo", @"resources/logo.png"); -//ResourceCache.Instance.LoadAudio("music", @"resources/country.mp3", ResourceCacheExtensions.AudioLoadRequest.Type.Music); -//ResourceCache.Instance.LoadAudio("sfx", @"resources/boom.wav", ResourceCacheExtensions.AudioLoadRequest.Type.Sound); -//var builder = new EngineBuilder(); - -//builder.AddComponent(() => new RaylibGraphicsEngine(initParams) { LightingShaderCacheKey = "lighting" }, true); -//builder.AddComponent(true); -//builder.AddComponent(); -//builder.AddComponent(); -//builder.AddComponent(); - -//builder.AddLogger(new ConsoleLogger(Basalt.Core.Common.Types.LogLevel.Info)); - -//var engine = builder.Build(); - -//engine.Initialize(); - - - -//var player = new Entity(); -//player.AddComponent(new FirstPersonCameraController(player)); -//player.Id = "entity.player"; -//Vector3 offset = Vector3.UnitY * -1; -//player.Transform.Position = new Vector3(0, 5, 0); -//player.AddComponent(new SphereRenderer(player) { Size = new Vector3(1f), Color = Color.Red, Offset = offset }); -//player.AddComponent(new BoxCollider(player) { Size = new Vector3(1, 2, 1), Offset = offset }); -//player.AddComponent(new Rigidbody(player) { IsKinematic = false, Mass = 25 }); -//player.AddComponent(new Basalt.TestField.Components.PlayerController(player)); -//player.AddComponent(new LightSource(player, "lighting") { Color = Color.Red, Type = LightType.Point }); - -//Engine.CreateEntity(player); - -//var emitter = new Entity(); -//emitter.Transform.Position = new Vector3(0, 10, 0); -//emitter.AddComponent(new RaylibParticleSystem(emitter) { ModelCacheKey = "cube" }); -//Engine.CreateEntity(emitter); - -//var ps = emitter.GetComponent()!; - -//ps.SubscribeOnParticleReset((ref Particle p) => -//{ -// p.Velocity = new(Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5); -// // Apply random rotation -// p.Rotation = Quaternion.CreateFromYawPitchRoll(Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2); - -//}); - -//TestingUtils.SetupTestingScene(250); -//TestingUtils.SetupDebugInfo(); - +using Basalt; +using Basalt.Common; +using Basalt.Common.Components; using Basalt.Common.Entities; +using Basalt.Common.Events; +using Basalt.Common.Logging; +using Basalt.Common.Physics; +using Basalt.Common.Utils; +using Basalt.Core.Common.Abstractions.Engine; +using Basalt.Core.Common.Abstractions.Input; +using Basalt.Core.Common.Abstractions.Sound; +using Basalt.Raylib.Components; +using Basalt.Raylib.Graphics; +using Basalt.Raylib.Input; +using Basalt.Raylib.Sound; +using Basalt.Raylib.Utils; +using Basalt.TestField; +using Basalt.Types; +using Raylib_cs; using System.Numerics; -var entity = new Entity(); -entity.Transform.Position = new(0, 1, 0); -entity.Transform.Rotation = Quaternion.CreateFromYawPitchRoll(1, 1, 0); -var clone = entity.Clone(); -foreach(var prop in entity.Transform.GetType().GetProperties()) +var initParams = new WindowInitParams { - Console.WriteLine($"{prop.Name}: {prop.GetValue(entity.Transform)}"); - -} - -foreach (var prop in clone.Transform.GetType().GetProperties()) + Title = "Basalt Test Field", + Width = 1920, + Height = 1080, + TargetFps = 120, + Fullscreen = false, + Borderless = true, + MSAA4X = true, + PostProcessing = false, +}; +ResourceCache.Instance.LoadShader("lighting", @"resources/shaders/lighting.fs", @"resources/shaders/lighting.vs"); +ResourceCache.Instance.LoadModel("robot", @"resources/robot.glb", "lighting"); +ResourceCache.Instance.LoadTexture("logo", @"resources/logo.png"); +ResourceCache.Instance.LoadAudio("music", @"resources/country.mp3", ResourceCacheExtensions.AudioLoadRequest.Type.Music); +ResourceCache.Instance.LoadAudio("sfx", @"resources/boom.wav", ResourceCacheExtensions.AudioLoadRequest.Type.Sound); +var builder = new EngineBuilder(); + +builder.AddComponent(() => new RaylibGraphicsEngine(initParams) { LightingShaderCacheKey = "lighting" }, true); +builder.AddComponent(true); +builder.AddComponent(); +builder.AddComponent(); +builder.AddComponent(); + +builder.AddLogger(new ConsoleLogger(Basalt.Core.Common.Types.LogLevel.Info)); + +var engine = builder.Build(); + +engine.Initialize(); + + + +var player = new Entity(); +player.AddComponent(new FirstPersonCameraController(player)); +player.Id = "entity.player"; +Vector3 offset = Vector3.UnitY * -1; +player.Transform.Position = new Vector3(0, 5, 0); +player.AddComponent(new SphereRenderer(player) { Size = new Vector3(1f), Color = Color.Red, Offset = offset }); +player.AddComponent(new BoxCollider(player) { Size = new Vector3(1, 2, 1), Offset = offset }); +player.AddComponent(new Rigidbody(player) { IsKinematic = false, Mass = 25 }); +player.AddComponent(new Basalt.TestField.Components.PlayerController(player)); +player.AddComponent(new LightSource(player, "lighting") { Color = Color.Red, Type = LightType.Point }); + +Engine.CreateEntity(player); + +var emitter = new Entity(); +emitter.Transform.Position = new Vector3(0, 10, 0); +emitter.AddComponent(new RaylibParticleSystem(emitter) { ModelCacheKey = "cube" }); +Engine.CreateEntity(emitter); + +var ps = emitter.GetComponent()!; + +ps.SubscribeOnParticleReset((ref Particle p) => { - Console.WriteLine($"Cloned {prop.Name}: {prop.GetValue(clone.Transform)}"); -} + p.Velocity = new(Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5); + // Apply random rotation + p.Rotation = Quaternion.CreateFromYawPitchRoll(Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2); + +}); +TestingUtils.SetupTestingScene(250); +TestingUtils.SetupDebugInfo(); \ No newline at end of file diff --git a/Basalt/Common/Components/BaseParticleSystem.cs b/Basalt/Common/Components/BaseParticleSystem.cs index 6d9c3b8..37b1e61 100644 --- a/Basalt/Common/Components/BaseParticleSystem.cs +++ b/Basalt/Common/Components/BaseParticleSystem.cs @@ -20,7 +20,7 @@ public abstract class BaseParticleSystem : Component protected Particle defaults; private int _length; - private float _emissionRate = 5, _particleLifetime = 5; + private float _emissionRate = 5, _particleLifetime = 5, _systemLifetime = 0; /// @@ -54,6 +54,17 @@ public float ParticleLifetime ResizePool(); } } + + /// + /// Gets or sets the duration of the particle system. The system will reset and stop after this duration. + /// + public float SystemDuration { get; set; } = 5f; + + /// + /// Gets or sets whether the particle system should loop. + /// + public bool Looping { get; set; } = false; + /// /// A delegate for updating particles. /// @@ -82,6 +93,10 @@ public sealed override void OnRender() public override void OnUpdate() { var dt = Time.DeltaTime; + _systemLifetime += dt; + if (!Looping && _systemLifetime > SystemDuration) + return; + for (int i = 0; i < _particles.Length; i++) { _particles[i].Lifetime += dt; @@ -166,5 +181,26 @@ public void UpdateDefaults(Particle particle) defaults = particle; } + /// + /// Resets the particle system to it's initial state. + /// + public void Reset() + { + _systemLifetime = 0; + for (int i = 0; i < _particles.Length; i++) + { + _particles[i] = defaults; + } + } + + + /// + /// Stop the particle system from emitting new particles. Already existing particles will continue to update until end of lifetime. + /// + public void Stop() + { + _systemLifetime = SystemDuration; + } + } } From 7c4d8546aaad4bb46c7bacf25efb5f52288b6547 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Tue, 18 Jun 2024 13:13:18 -0300 Subject: [PATCH 19/20] chore: Clean up --- Basalt/Common/Components/BaseParticleSystem.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Basalt/Common/Components/BaseParticleSystem.cs b/Basalt/Common/Components/BaseParticleSystem.cs index 37b1e61..5b02ffa 100644 --- a/Basalt/Common/Components/BaseParticleSystem.cs +++ b/Basalt/Common/Components/BaseParticleSystem.cs @@ -1,5 +1,6 @@ using Basalt.Common.Entities; using Basalt.Types; +using System.Drawing; using System.Numerics; namespace Basalt.Common.Components @@ -94,7 +95,8 @@ public override void OnUpdate() { var dt = Time.DeltaTime; _systemLifetime += dt; - if (!Looping && _systemLifetime > SystemDuration) + + if (!Looping && _systemLifetime > SystemDuration + ParticleLifetime) return; for (int i = 0; i < _particles.Length; i++) @@ -104,6 +106,12 @@ public override void OnUpdate() _particleUpdate?.Invoke(ref _particles[i]); if (_particles[i].Lifetime > ParticleLifetime) { + if (!Looping && _systemLifetime > SystemDuration) + { + _particles[i] = defaults; + _particles[i].Color = Color.FromArgb(0x00000000); + continue; + } _particles[i] = defaults; _onParticleReset?.Invoke(ref _particles[i]); } From 8189013c148d2ed841930ffc34c76cdf6f509712 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Tue, 18 Jun 2024 13:13:47 -0300 Subject: [PATCH 20/20] chore(release): 1.9.0 --- Basalt.Core/Basalt.Core.csproj | 2 +- Basalt.Raylib/Basalt.Raylib.csproj | 2 +- Basalt/Basalt.csproj | 2 +- CHANGELOG.md | 23 +++++++++++++++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Basalt.Core/Basalt.Core.csproj b/Basalt.Core/Basalt.Core.csproj index eefeb29..a031b73 100644 --- a/Basalt.Core/Basalt.Core.csproj +++ b/Basalt.Core/Basalt.Core.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 1.8.1 + 1.9.0 True BasaltLogoBg.png README.md diff --git a/Basalt.Raylib/Basalt.Raylib.csproj b/Basalt.Raylib/Basalt.Raylib.csproj index 16a629c..952ccd7 100644 --- a/Basalt.Raylib/Basalt.Raylib.csproj +++ b/Basalt.Raylib/Basalt.Raylib.csproj @@ -5,7 +5,7 @@ enable enable true - 1.8.1 + 1.9.0 True BasaltLogoBg.png README.md diff --git a/Basalt/Basalt.csproj b/Basalt/Basalt.csproj index bf9ed54..9a957ea 100644 --- a/Basalt/Basalt.csproj +++ b/Basalt/Basalt.csproj @@ -5,7 +5,7 @@ enable enable true - 1.8.1 + 1.9.0 True Basalt BasaltLogoBg.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 69bb867..52f11c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ All notable changes to this project will be documented in this file. See [versionize](https://github.com/versionize/versionize) for commit guidelines. + +## [1.9.0](https://www.github.com/thiagomvas/Basalt/releases/tag/v1.9.0) (2024-06-18) + +### Features + +* Add base particle system component. ([d33c620](https://www.github.com/thiagomvas/Basalt/commit/d33c6201f4589925310b987b83736b49d512fea0)) +* Add base Raylib camera controller class ([2568b60](https://www.github.com/thiagomvas/Basalt/commit/2568b609786fbee19a3c445a74bcec0b044d4411)) +* Add CameraControllerBase generic class ([0099ea8](https://www.github.com/thiagomvas/Basalt/commit/0099ea85347604e6749e0a2e58fdd1aec30b1ba1)) +* Add Entity.Clone() and Entity.Create() ([6b1d15a](https://www.github.com/thiagomvas/Basalt/commit/6b1d15a55c7f2ccdb5424c470677ac6171b9c246)) +* Add First Person camera controller class ([57c6f74](https://www.github.com/thiagomvas/Basalt/commit/57c6f74f4d9ca29fc8802633b56bba3624df3a39)) +* Add RaylibParticleSystem component ([3615e2a](https://www.github.com/thiagomvas/Basalt/commit/3615e2ab304d60357448e15267bbf9b32eb02db9)) +* Add System.Drawing.Color.ToRaylibColor() extension method ([7acdade](https://www.github.com/thiagomvas/Basalt/commit/7acdadee93a1f9ee79fec03577b3565df120ee86)) +* CameraController can now be changed for RaylibGraphicsEngine ([0cd2cab](https://www.github.com/thiagomvas/Basalt/commit/0cd2cab1cc853e496f08492679fabebd651e2a5f)) +* Particle Systems can be looping or have limited emission duration. ([00bc197](https://www.github.com/thiagomvas/Basalt/commit/00bc197b6db7c4cbdf60b260fd2f0226059b91f6)) +* RaylibGraphicsEngine updated to support new camera system. ([7ec5323](https://www.github.com/thiagomvas/Basalt/commit/7ec532351f8efac7ffa4aa26f2409ea9ec4eacd4)) + +### Bug Fixes + +* InvalidResourceKeyException now contains the param name and value that caused the exception ([32de895](https://www.github.com/thiagomvas/Basalt/commit/32de89577af550199992a5b8413546a7149123bc)) +* OnCollision no longer called when entity is destroyed. ([0aa34fb](https://www.github.com/thiagomvas/Basalt/commit/0aa34fb04739f155e495895d83f935fca4231899)) +* Possible null reference exception when setting transform position ([35f7d32](https://www.github.com/thiagomvas/Basalt/commit/35f7d320bd374805363cab3c16930da9a6ce3217)) +* Properly reference new chunking ([be6bcf9](https://www.github.com/thiagomvas/Basalt/commit/be6bcf98f370b693589fe802344d613515efcf6e)) + ## [1.8.1](https://www.github.com/thiagomvas/Basalt/releases/tag/v1.8.1) (2024-06-10)