From ac6e6f470832bdd36e9dbf51ccfe3af76425b687 Mon Sep 17 00:00:00 2001 From: Ne1gh Date: Tue, 20 May 2025 23:58:30 +0300 Subject: [PATCH 1/3] Shake implementation and demo. --- Monofoxe/Monofoxe.Engine/GameMgr.cs | 2 + Monofoxe/Monofoxe.Engine/Shake/BounceShake.cs | 136 +++++++++++++ Monofoxe/Monofoxe.Engine/Shake/IShake.cs | 33 ++++ Monofoxe/Monofoxe.Engine/Shake/PerlinShake.cs | 145 ++++++++++++++ .../Shake/Presets/DirectionalShakePreset.cs | 43 ++++ .../Shake/Presets/ExplosionShakePreset.cs | 43 ++++ .../Shake/Presets/ShakePreset.cs | 33 ++++ .../Shake/Presets/ShortShakePreset.cs | 39 ++++ Monofoxe/Monofoxe.Engine/Shake/Shaker.cs | 73 +++++++ .../Shake/Utils/Attenuation.cs | 47 +++++ .../Shake/Utils/Displacement.cs | 67 +++++++ .../Monofoxe.Engine/Shake/Utils/Envelope.cs | 184 ++++++++++++++++++ Monofoxe/Monofoxe.Engine/Shake/Utils/Power.cs | 30 +++ Monofoxe/Monofoxe.Engine/Utils/PerlinNoise.cs | 17 +- .../Graphics/Default/AutismCatDish.json | 6 + .../Graphics/Default/AutismCatDish.png | Bin 0 -> 1635 bytes Samples/Monofoxe.Samples/Demos/ShakeDemo.cs | 115 +++++++++++ Samples/Monofoxe.Samples/GameController.cs | 3 + Samples/Monofoxe.Samples/SceneSwitcher.cs | 1 + 19 files changed, 1009 insertions(+), 8 deletions(-) create mode 100644 Monofoxe/Monofoxe.Engine/Shake/BounceShake.cs create mode 100644 Monofoxe/Monofoxe.Engine/Shake/IShake.cs create mode 100644 Monofoxe/Monofoxe.Engine/Shake/PerlinShake.cs create mode 100644 Monofoxe/Monofoxe.Engine/Shake/Presets/DirectionalShakePreset.cs create mode 100644 Monofoxe/Monofoxe.Engine/Shake/Presets/ExplosionShakePreset.cs create mode 100644 Monofoxe/Monofoxe.Engine/Shake/Presets/ShakePreset.cs create mode 100644 Monofoxe/Monofoxe.Engine/Shake/Presets/ShortShakePreset.cs create mode 100644 Monofoxe/Monofoxe.Engine/Shake/Shaker.cs create mode 100644 Monofoxe/Monofoxe.Engine/Shake/Utils/Attenuation.cs create mode 100644 Monofoxe/Monofoxe.Engine/Shake/Utils/Displacement.cs create mode 100644 Monofoxe/Monofoxe.Engine/Shake/Utils/Envelope.cs create mode 100644 Monofoxe/Monofoxe.Engine/Shake/Utils/Power.cs create mode 100644 Samples/Monofoxe.Samples.Content/Content/Graphics/Default/AutismCatDish.json create mode 100644 Samples/Monofoxe.Samples.Content/Content/Graphics/Default/AutismCatDish.png create mode 100644 Samples/Monofoxe.Samples/Demos/ShakeDemo.cs diff --git a/Monofoxe/Monofoxe.Engine/GameMgr.cs b/Monofoxe/Monofoxe.Engine/GameMgr.cs index 7c1bed2..e9929e3 100644 --- a/Monofoxe/Monofoxe.Engine/GameMgr.cs +++ b/Monofoxe/Monofoxe.Engine/GameMgr.cs @@ -1,6 +1,7 @@ using Microsoft.Xna.Framework; using Monofoxe.Engine.Drawing; using Monofoxe.Engine.SceneSystem; +using Monofoxe.Engine.Utils; using System; using System.Collections.Generic; using System.Reflection; @@ -99,6 +100,7 @@ public static void Init(Game game) var keyboardBind = StuffResolver.GetStuff(); keyboardBind?.Init(); + /*PerlinNoise.Seed = RandomExt.Global.Next*/ Input.MaxGamepadCount = 2; diff --git a/Monofoxe/Monofoxe.Engine/Shake/BounceShake.cs b/Monofoxe/Monofoxe.Engine/Shake/BounceShake.cs new file mode 100644 index 0000000..2ed6722 --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Shake/BounceShake.cs @@ -0,0 +1,136 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +/// Original Unity version made by Ivan Pensionerov https://github.com/gasgiant/Camera-Shake +/// Ported and improved by Minkberry. +///////////////////////////////////////////////////////////////////////////////////////////// +using Monofoxe.Engine.Shake.Utils; +using Microsoft.Xna.Framework; +using Monofoxe.Engine.Utils; + +namespace Monofoxe.Engine.Shake +{ + public class BounceShake : IShake + { + private readonly Params _pars; + private readonly Easing _moveCurve = Easing.EaseInOutCubic; + private readonly Vector2? _sourcePosition = null; + + private float _attenuation = 1; + private Displacement _direction; + private Displacement _previousWaypoint; + private Displacement _currentWaypoint; + private int _bounceIndex; + private float _t; + + + /// + /// Creates an instance of BounceShake. + /// + /// Parameters of the shake. + /// World position of the source of the shake. + public BounceShake(Params parameters, Vector2? sourcePosition = null) + { + _sourcePosition = sourcePosition; + _pars = parameters; + Displacement rnd = Displacement.InsideUnitSpheres(); + _direction = Displacement.Scale(rnd, _pars.AxesMultiplier).Normalized; + } + + + /// + /// Creates an instance of BounceShake. + /// + /// Parameters of the shake. + /// Initial direction of the shake motion. + /// World position of the source of the shake. + public BounceShake(Params parameters, Displacement initialDirection, Vector2? sourcePosition = null) + { + _sourcePosition = sourcePosition; + _pars = parameters; + _direction = Displacement.Scale(initialDirection, _pars.AxesMultiplier).Normalized; + } + + + public Displacement CurrentDisplacement { get; private set; } + public bool IsFinished { get; private set; } + + + public void Initialize(Vector2 cameraPosition, float cameraRotation) + { + _attenuation = _sourcePosition == null ? + 1 : _pars.Attenuation.Attenuate(_sourcePosition.Value, cameraPosition); + _currentWaypoint = _attenuation * _direction.ScaledBy(_pars.PositionStrength, _pars.RotationStrength); + } + + + public void Update(TimeKeeper time, Vector2 cameraPosition, float cameraRotation) + { + if (_t < 1) + { + _t += (float)time.Time() * _pars.Frequence; + if (_pars.Frequence == 0) _t = 1; + + CurrentDisplacement = Displacement.Lerp(_previousWaypoint, _currentWaypoint, + _moveCurve.GetEasing(_t)); + } + else + { + _t = 0; + CurrentDisplacement = _currentWaypoint; + _previousWaypoint = _currentWaypoint; + _bounceIndex++; + if (_bounceIndex > _pars.NumBounces) + { + IsFinished = true; + return; + } + + Displacement rnd = Displacement.InsideUnitSpheres(); + _direction = -_direction + + _pars.Randomness * Displacement.Scale(rnd, _pars.AxesMultiplier).Normalized; + _direction = _direction.Normalized; + float decayValue = 1 - (float)_bounceIndex / _pars.NumBounces; + _currentWaypoint = decayValue * decayValue * _attenuation + * _direction.ScaledBy(_pars.PositionStrength, _pars.RotationStrength); + } + } + + + public class Params + { + /// + /// Strength of the shake for positional axes. + /// + public float PositionStrength = 0.05f; + + /// + /// Strength of the shake for rotational axes. + /// + public float RotationStrength = 0.1f; + + /// + /// Preferred direction of shaking. + /// + public Displacement AxesMultiplier = new Displacement(Vector2.One, 0); + + /// + /// Frequency of shaking. + /// + public float Frequence = 25; + + /// + /// Number of vibrations before stop. + /// + public int NumBounces = 5; + + /// + /// Randomness of motion. + /// + public float Randomness = 0.5f; + + /// + /// How strength falls with distance from the shake source. + /// + public Attenuation Attenuation = new Attenuation(); + } + } +} diff --git a/Monofoxe/Monofoxe.Engine/Shake/IShake.cs b/Monofoxe/Monofoxe.Engine/Shake/IShake.cs new file mode 100644 index 0000000..5687933 --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Shake/IShake.cs @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +/// Original Unity version made by Ivan Pensionerov https://github.com/gasgiant/Camera-Shake +/// Ported and improved by Minkberry. +///////////////////////////////////////////////////////////////////////////////////////////// +using Monofoxe.Engine.Shake.Utils; +using Microsoft.Xna.Framework; +using Monofoxe.Engine.Utils; + +namespace Monofoxe.Engine.Shake +{ + public interface IShake + { + /// + /// Represents current position and rotation of the camera according to the shake. + /// + Displacement CurrentDisplacement { get; } + + /// + /// Shake system will dispose the shake on the first frame when this is true. + /// + bool IsFinished { get; } + + /// + /// Shaker calls this when the shake is added to the list of active shakes. + /// + void Initialize(Vector2 cameraPosition, float cameraRotation); + + /// + /// Shaker calls this every frame on active shakes. + /// + void Update(TimeKeeper time, Vector2 cameraPosition, float cameraRotation); + } +} diff --git a/Monofoxe/Monofoxe.Engine/Shake/PerlinShake.cs b/Monofoxe/Monofoxe.Engine/Shake/PerlinShake.cs new file mode 100644 index 0000000..18ae4c2 --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Shake/PerlinShake.cs @@ -0,0 +1,145 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +/// Original Unity version made by Ivan Pensionerov https://github.com/gasgiant/Camera-Shake +/// Ported and improved by Minkberry. +///////////////////////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using Monofoxe.Engine.Utils; +using Monofoxe.Engine.Shake.Utils; + +namespace Monofoxe.Engine.Shake +{ + public class PerlinShake : IShake + { + private readonly Params _pars; + private readonly Envelope _envelope; + + public IAmplitudeController AmplitudeController; + + private Vector2[] _seeds; + private float _t; + private Vector2? _sourcePosition; + private float _norm; + + /// + /// Creates an instance of PerlinShake. + /// + /// Parameters of the shake. + /// Maximum amplitude of the shake. + /// World position of the source of the shake. + /// Pass true if you want to control amplitude manually. + public PerlinShake( + Params parameters, + float maxAmplitude = 1, + Vector2? sourcePosition = null, + bool manualStrengthControl = false + ) + { + _pars = parameters; + _envelope = new Envelope(_pars.Envelope, maxAmplitude, + manualStrengthControl ? + Envelope.EnvelopeControlMode.Manual : Envelope.EnvelopeControlMode.Auto); + AmplitudeController = _envelope; + _sourcePosition = sourcePosition; + } + + + public Displacement CurrentDisplacement { get; private set; } + public bool IsFinished { get; private set; } + + + public void Initialize(Vector2 cameraPosition, float cameraRotation) + { + _seeds = new Vector2[_pars.NoiseModes.Length]; + _norm = 0; + for (int i = 0; i < _seeds.Length; i++) + { + _seeds[i] = RandomExt.Global.NextInsideUnitCircle() * 20; + _norm += _pars.NoiseModes[i].Amplitude; + } + } + + + public void Update(TimeKeeper time, Vector2 cameraPosition, float cameraRotation) + { + if (_envelope.IsFinished) + { + IsFinished = true; + return; + } + _t += (float)time.Time(); + _envelope.Update((float)time.Time()); + + Displacement disp = Displacement.Zero; + for (int i = 0; i < _pars.NoiseModes.Length; i++) + { + disp += _pars.NoiseModes[i].Amplitude / _norm + * SampleNoise(_seeds[i], _pars.NoiseModes[i].Frequence); + } + + CurrentDisplacement = _envelope.Intensity * Displacement.Scale(disp, _pars.Strength); + if (_sourcePosition != null) + { + CurrentDisplacement *= _pars.Attenuation.Attenuate(_sourcePosition.Value, cameraPosition); + } + } + + + private Displacement SampleNoise(Vector2 seed, float freq) + { + var position = new Vector2( + PerlinNoise.CarmodyNoise(seed.X + _t * freq, seed.Y), + PerlinNoise.CarmodyNoise(seed.X, seed.Y + _t * freq) + ); + position -= Vector2.One * 0.5f; + + var rotation = PerlinNoise.CarmodyNoise(-seed.X - _t * freq, -seed.Y - _t * freq); + rotation -= 0.5f; + + return new Displacement(position, rotation); + } + + + public class Params + { + /// + /// Strength of the shake for each axis. + /// + public Displacement Strength = new Displacement(Vector2.Zero, 0.8f); + + /// + /// Layers of perlin noise with different frequencies. + /// + public NoiseMode[] NoiseModes = { new NoiseMode(12, 1) }; + + /// + /// Strength over time. + /// + public Envelope.EnvelopeParams Envelope; + + /// + /// How strength falls with distance from the shake source. + /// + public Attenuation Attenuation = new Attenuation(); + } + + + public struct NoiseMode + { + public NoiseMode(float freq, float amplitude) + { + Frequence = freq; + Amplitude = amplitude; + } + + /// + /// Frequency multiplier for the noise. + /// + public float Frequence; + + /// + /// Amplitude of the mode. Ranges from 0 to 1. + /// + public float Amplitude; + } + } +} diff --git a/Monofoxe/Monofoxe.Engine/Shake/Presets/DirectionalShakePreset.cs b/Monofoxe/Monofoxe.Engine/Shake/Presets/DirectionalShakePreset.cs new file mode 100644 index 0000000..92782e2 --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Shake/Presets/DirectionalShakePreset.cs @@ -0,0 +1,43 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +/// Original Unity version made by Ivan Pensionerov https://github.com/gasgiant/Camera-Shake +/// Ported and improved by Minkberry. +///////////////////////////////////////////////////////////////////////////////////////////// +using Monofoxe.Engine.Shake.Utils; +using Microsoft.Xna.Framework; + +namespace Monofoxe.Engine.Shake.Presets +{ + /// + /// Preset for bounce shake. + /// Suitable for short and snappy shakes. Moves camera in X and Y axes and rotates it in Z axis. + /// + public class DirectionalShakePreset : ShakePreset + { + public float PositionStrength = 10f; + public float RotationStrength = 0.02f; + + public float Frequency = 25; + + /// + /// Number of vibrations before stop. + /// + public int BounceCount = 5; + + public Vector2 Direction = Vector2.UnitX; + + + public override IShake CreateShake() + { + var pars = new BounceShake.Params + { + PositionStrength = PositionStrength, + RotationStrength = RotationStrength, + Frequence = Frequency, + NumBounces = BounceCount, + Attenuation = Attenuation + }; + + return new BounceShake(pars, new Displacement(Direction), UsesAttenuation ? (Vector2?)SourcePosition : null); + } + } +} diff --git a/Monofoxe/Monofoxe.Engine/Shake/Presets/ExplosionShakePreset.cs b/Monofoxe/Monofoxe.Engine/Shake/Presets/ExplosionShakePreset.cs new file mode 100644 index 0000000..64de6d3 --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Shake/Presets/ExplosionShakePreset.cs @@ -0,0 +1,43 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +/// Original Unity version made by Ivan Pensionerov https://github.com/gasgiant/Camera-Shake +/// Ported and improved by Minkberry. +///////////////////////////////////////////////////////////////////////////////////////////// +using Monofoxe.Engine.Shake.Utils; +using Microsoft.Xna.Framework; + +namespace Monofoxe.Engine.Shake.Presets +{ + /// + /// Preset for perlin shake. + /// Suitable for longer and stronger shakes. Moves camera in X and Y axes and rotates it in Z axis. + /// + public class ExplosionShakePreset : ShakePreset + { + public float PositionStrength = 10f; + public float RotationStrength = 0.02f; + public float Duration = 0.5f; + + public PerlinShake.NoiseMode[] NoiseModes = + { + new PerlinShake.NoiseMode(8, 1), + new PerlinShake.NoiseMode(20, 0.3f) + }; + + + public override IShake CreateShake() + { + var envelopePars = new Envelope.EnvelopeParams(); + envelopePars.Decay = Duration <= 0 ? 1 : 1 / Duration; + + var pars = new PerlinShake.Params() + { + Strength = new Displacement(Vector2.One * PositionStrength, RotationStrength), + NoiseModes = NoiseModes, + Envelope = envelopePars, + Attenuation = Attenuation, + }; + + return new PerlinShake(pars, 1, UsesAttenuation ? (Vector2?)SourcePosition : null); + } + } +} diff --git a/Monofoxe/Monofoxe.Engine/Shake/Presets/ShakePreset.cs b/Monofoxe/Monofoxe.Engine/Shake/Presets/ShakePreset.cs new file mode 100644 index 0000000..1ac8e2e --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Shake/Presets/ShakePreset.cs @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// +/// Original Unity version made by Ivan Pensionerov https://github.com/gasgiant/Camera-Shake +/// Ported and improved by Minkberry. +///////////////////////////////////////////////////////////////////////////////////////////// + +using Monofoxe.Engine.Shake.Utils; +using Microsoft.Xna.Framework; + +namespace Monofoxe.Engine.Shake.Presets +{ + public abstract class ShakePreset + { + /// + /// If true, the amount of shake will depend on distance from camera. + /// + public bool UsesAttenuation = false; + + /// + /// Source position of the shake relative to camera. + /// NOTE: This only takes effect if UsesAttenuation is true. + /// + public Vector2 SourcePosition = Vector2.Zero; + + /// + /// Attenuation settings. Govern how ths shake will behave relative to camera position. + /// NOTE: This only takes effect if UsesAttenuation is true. + /// + public Attenuation Attenuation = new Attenuation(100); + + public abstract IShake CreateShake(); + } +} diff --git a/Monofoxe/Monofoxe.Engine/Shake/Presets/ShortShakePreset.cs b/Monofoxe/Monofoxe.Engine/Shake/Presets/ShortShakePreset.cs new file mode 100644 index 0000000..2ae236b --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Shake/Presets/ShortShakePreset.cs @@ -0,0 +1,39 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +/// Original Unity version made by Ivan Pensionerov https://github.com/gasgiant/Camera-Shake +/// Ported and improved by Minkberry. +///////////////////////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; + +namespace Monofoxe.Engine.Shake.Presets +{ + /// + /// Preset for bounce shake. + /// Suitable for short and snappy shakes. Moves camera in X and Y axes and rotates it in Z axis. + /// + public class ShortShakePreset : ShakePreset + { + public float PositionStrength = 10f; + public float RotationStrength = 0.02f; + + public float Frequency = 25; + + /// + /// Number of vibrations before stop. + /// + public int BounceCount = 5; + + public override IShake CreateShake() + { + var pars = new BounceShake.Params + { + PositionStrength = PositionStrength, + RotationStrength = RotationStrength, + Frequence = Frequency, + NumBounces = BounceCount, + Attenuation = Attenuation, + }; + + return new BounceShake(pars, UsesAttenuation ? (Vector2?)SourcePosition : null); + } + } +} diff --git a/Monofoxe/Monofoxe.Engine/Shake/Shaker.cs b/Monofoxe/Monofoxe.Engine/Shake/Shaker.cs new file mode 100644 index 0000000..4727cbc --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Shake/Shaker.cs @@ -0,0 +1,73 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +/// Original Unity version made by Ivan Pensionerov https://github.com/gasgiant/Camera-Shake +/// Ported and improved by Minkberry. +///////////////////////////////////////////////////////////////////////////////////////////// +using System.Collections.Generic; +using Monofoxe.Engine.Shake.Presets; +using Monofoxe.Engine.Shake.Utils; +using Microsoft.Xna.Framework; +using Monofoxe.Engine.EC; +using Monofoxe.Engine.SceneSystem; +using Monofoxe.Engine.Utils; + +namespace Monofoxe.Engine.Shake +{ + /// + /// Camera shaker component registeres new shakes, holds a list of active shakes, and applies them to the camera additively. + /// + public class Shaker : Entity + { + private readonly List _activeShakes = new List(); + + public Vector2 ShakePosition { get; private set; } + public Angle ShakeRotation { get; private set; } + + + public float StrengthMultiplier = 1f; + + public Shaker(Layer layer) : base(layer) + { + } + + + /// + /// Creates a shake from preset. + /// + public IShake Shake(ShakePreset preset) + { + var shake = preset.CreateShake(); + Shake(shake); + return shake; + } + + + /// + /// Adds a shake to the list of active shakes. + /// + public void Shake(IShake shake) + { + shake.Initialize(ShakePosition, Angle.Right.RadiansF); + _activeShakes.Add(shake); + } + + + public override void Update() + { + Displacement cameraDisplacement = Displacement.Zero; + for (int i = _activeShakes.Count - 1; i >= 0; i--) + { + if (_activeShakes[i].IsFinished) + { + _activeShakes.RemoveAt(i); + } + else + { + _activeShakes[i].Update(TimeKeeper.Global, ShakePosition, Angle.Right.RadiansF); + cameraDisplacement += _activeShakes[i].CurrentDisplacement; + } + } + ShakePosition = StrengthMultiplier * cameraDisplacement.Position; + ShakeRotation = Angle.FromRadians(StrengthMultiplier * cameraDisplacement.Angle); + } + } +} diff --git a/Monofoxe/Monofoxe.Engine/Shake/Utils/Attenuation.cs b/Monofoxe/Monofoxe.Engine/Shake/Utils/Attenuation.cs new file mode 100644 index 0000000..f7b0275 --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Shake/Utils/Attenuation.cs @@ -0,0 +1,47 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +/// Original Unity version made by Ivan Pensionerov https://github.com/gasgiant/Camera-Shake +/// Ported and improved by Minkberry. +///////////////////////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; + +namespace Monofoxe.Engine.Shake.Utils +{ + public struct Attenuation + { + /// + /// Radius in which shake doesn't lose strength. + /// + public float ClippingDistance; + + /// + /// Defines how fast strength falls with distance. + /// + public float FalloffScale; + + /// + /// Power of the falloff function. + /// + public Degree FalloffDegree; + + + public Attenuation(float clippingDistance = 100, float falloffScale = 128, Degree falloffDegree = Degree.Quadratic) + { + ClippingDistance = clippingDistance; + FalloffScale = falloffScale; + FalloffDegree = falloffDegree; + } + + + /// + /// Returns multiplier for the strength of a shake, based on source and camera positions. + /// + public float Attenuate(Vector2 sourcePosition, Vector2 cameraPosition) + { + Vector2 vec = cameraPosition - sourcePosition; + float distance = vec.Length(); + float strength = MathHelper.Clamp(1 - (distance - ClippingDistance) / FalloffScale, 0, 1); + + return Power.Evaluate(strength, FalloffDegree); + } + } +} diff --git a/Monofoxe/Monofoxe.Engine/Shake/Utils/Displacement.cs b/Monofoxe/Monofoxe.Engine/Shake/Utils/Displacement.cs new file mode 100644 index 0000000..0ae2b70 --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Shake/Utils/Displacement.cs @@ -0,0 +1,67 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +/// Original Unity version made by Ivan Pensionerov https://github.com/gasgiant/Camera-Shake +/// Ported and improved by Minkberry. +///////////////////////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using Monofoxe.Engine; +using Monofoxe.Engine.Utils; + +namespace Monofoxe.Engine.Shake.Utils +{ + /// + /// Representation of translation and rotation. + /// + public struct Displacement + { + public Vector2 Position; + public float Angle; + + public Displacement(Vector2 position, float angle) + { + Position = position; + Angle = angle; + } + + public Displacement(Vector2 position) + { + Position = position; + Angle = 0; + } + + public static Displacement Zero => + new Displacement(Vector2.Zero, 0); + + public Displacement Normalized => + new Displacement(Position.SafeNormalize(), Angle); + + public static Displacement operator +(Displacement a, Displacement b) => + new Displacement(a.Position + b.Position, b.Angle + a.Angle); + + public static Displacement operator -(Displacement a, Displacement b) => + new Displacement(a.Position - b.Position, b.Angle - a.Angle); + + public static Displacement operator -(Displacement disp) => + new Displacement(-disp.Position, -disp.Angle); + + public static Displacement operator *(Displacement coords, float number) => + new Displacement(coords.Position * number, coords.Angle * number); + + public static Displacement operator *(float number, Displacement coords) => + coords * number; + + public static Displacement operator /(Displacement coords, float number) => + new Displacement(coords.Position / number, coords.Angle / number); + + public static Displacement Scale(Displacement a, Displacement b) => + new Displacement(a.Position * b.Position, b.Angle * a.Angle); + + public static Displacement Lerp(Displacement a, Displacement b, float t) => + new Displacement(Vector2.Lerp(a.Position, b.Position, t), MathHelper.Lerp(a.Angle, b.Angle, t)); + + public Displacement ScaledBy(float posScale, float rotScale) => + new Displacement(Position * posScale, Angle * rotScale); + + public static Displacement InsideUnitSpheres() => + new Displacement(RandomExt.Global.NextInsideUnitCircle(), 0); + } +} diff --git a/Monofoxe/Monofoxe.Engine/Shake/Utils/Envelope.cs b/Monofoxe/Monofoxe.Engine/Shake/Utils/Envelope.cs new file mode 100644 index 0000000..0b936b2 --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Shake/Utils/Envelope.cs @@ -0,0 +1,184 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +/// Original Unity version made by Ivan Pensionerov https://github.com/gasgiant/Camera-Shake +/// Ported and improved by Minkberry. +///////////////////////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using Monofoxe.Engine; + +namespace Monofoxe.Engine.Shake.Utils +{ + /// + /// Controls strength of the shake over time. + /// + public class Envelope : IAmplitudeController + { + private readonly EnvelopeParams _pars; + private readonly EnvelopeControlMode _controlMode; + + private float _amplitude; + private float _targetAmplitude; + private float _sustainEndTime; + private bool _finishWhenAmplitudeZero; + private bool _finishImmediately; + private EnvelopeState _state; + + /// + /// Creates an Envelope instance. + /// + /// Envelope parameters. + /// Pass Auto for a single shake, or Manual for controlling strength manually. + public Envelope(EnvelopeParams pars, float initialTargetAmplitude, EnvelopeControlMode controlMode) + { + _pars = pars; + _controlMode = controlMode; + SetTarget(initialTargetAmplitude); + } + + /// + /// The value by which you want to multiply shake displacement. + /// + public float Intensity { get; private set; } + + + public bool IsFinished + { + get + { + if (_finishImmediately) return true; + return (_finishWhenAmplitudeZero || _controlMode == EnvelopeControlMode.Auto) + && _amplitude <= 0 && _targetAmplitude <= 0; + } + } + + + public void Finish() + { + _finishWhenAmplitudeZero = true; + SetTarget(0); + } + + + public void FinishImmediately() + { + _finishImmediately = true; + } + + + /// + /// Update is called every frame by the shake. + /// + public void Update(float deltaTime) + { + if (IsFinished) return; + + if (_state == EnvelopeState.Increase) + { + if (_pars.Attack > 0) + _amplitude += deltaTime * _pars.Attack; + if (_amplitude > _targetAmplitude || _pars.Attack <= 0) + { + _amplitude = _targetAmplitude; + _state = EnvelopeState.Sustain; + if (_controlMode == EnvelopeControlMode.Auto) + _sustainEndTime = (float)GameMgr.ElapsedTimeTotal + _pars.Sustain; + } + } + else + { + if (_state == EnvelopeState.Decrease) + { + + if (_pars.Decay > 0) + _amplitude -= deltaTime * _pars.Decay; + if (_amplitude < _targetAmplitude || _pars.Decay <= 0) + { + _amplitude = _targetAmplitude; + _state = EnvelopeState.Sustain; + } + } + else + { + if (_controlMode == EnvelopeControlMode.Auto && (float)GameMgr.ElapsedTimeTotal > _sustainEndTime) + { + SetTarget(0); + } + } + } + + _amplitude = MathHelper.Clamp(_amplitude, 0, 1); + Intensity = Power.Evaluate(_amplitude, _pars.Degree); + } + + + public void SetTargetAmplitude(float value) + { + if (_controlMode == EnvelopeControlMode.Manual && !_finishWhenAmplitudeZero) + { + SetTarget(value); + } + } + + + private void SetTarget(float value) + { + _targetAmplitude = MathHelper.Clamp(value, 0, 1); + _state = _targetAmplitude > _amplitude ? EnvelopeState.Increase : EnvelopeState.Decrease; + } + + + public class EnvelopeParams + { + /// + /// How fast the amplitude rises. + /// + public float Attack = 10; + + /// + /// How long in seconds the amplitude holds a maximum value. + /// + public float Sustain = 0; + + /// + /// How fast the amplitude falls. + /// + public float Decay = 1f; + + /// + /// Power in which the amplitude is raised to get intensity. + /// + public Degree Degree = Degree.Cubic; + } + + public enum EnvelopeControlMode + { + Auto, + Manual + } + + public enum EnvelopeState + { + Sustain, + Increase, + Decrease + } + } + + + public interface IAmplitudeController + { + /// + /// Sets value to which amplitude will move over time. + /// + void SetTargetAmplitude(float value); + + /// + /// Sets amplitude to zero and finishes the shake when zero is reached. + /// + void Finish(); + + /// + /// Immediately finishes the shake. + /// + void FinishImmediately(); + } +} diff --git a/Monofoxe/Monofoxe.Engine/Shake/Utils/Power.cs b/Monofoxe/Monofoxe.Engine/Shake/Utils/Power.cs new file mode 100644 index 0000000..71a27bf --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Shake/Utils/Power.cs @@ -0,0 +1,30 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +/// Original Unity version made by Ivan Pensionerov https://github.com/gasgiant/Camera-Shake +/// Ported and improved by Minkberry. +///////////////////////////////////////////////////////////////////////////////////////////// + + +namespace Monofoxe.Engine.Shake.Utils +{ + public static class Power + { + public static float Evaluate(float value, Degree degree) + { + switch (degree) + { + case Degree.Linear: + return value; + case Degree.Quadratic: + return value * value; + case Degree.Cubic: + return value * value * value; + case Degree.Quadric: + return value * value * value * value; + default: + return value; + } + } + } + + public enum Degree { Linear, Quadratic, Cubic, Quadric } +} diff --git a/Monofoxe/Monofoxe.Engine/Utils/PerlinNoise.cs b/Monofoxe/Monofoxe.Engine/Utils/PerlinNoise.cs index 8a542a8..f7beaeb 100644 --- a/Monofoxe/Monofoxe.Engine/Utils/PerlinNoise.cs +++ b/Monofoxe/Monofoxe.Engine/Utils/PerlinNoise.cs @@ -12,17 +12,18 @@ public enum NoiseType public static class PerlinNoise { - // Random seed - private static int _seed = new Random((int)DateTime.Now.Ticks).Next(); + private static int _seed = 0; public static int Seed { get => _seed; - set - { - _seed = value; - _reseed(); // Carmody - _recalculatePermutations(); // Gustavson - } + } + + public static void SetSeed(int seed) + { + _seed = seed; + + _reseed(); // Carmody + _recalculatePermutations(); // Gustavson } diff --git a/Samples/Monofoxe.Samples.Content/Content/Graphics/Default/AutismCatDish.json b/Samples/Monofoxe.Samples.Content/Content/Graphics/Default/AutismCatDish.json new file mode 100644 index 0000000..86af8ec --- /dev/null +++ b/Samples/Monofoxe.Samples.Content/Content/Graphics/Default/AutismCatDish.json @@ -0,0 +1,6 @@ +{ + "v": 1, + "h": 1, + "originX": "center", + "originY": "center" +} diff --git a/Samples/Monofoxe.Samples.Content/Content/Graphics/Default/AutismCatDish.png b/Samples/Monofoxe.Samples.Content/Content/Graphics/Default/AutismCatDish.png new file mode 100644 index 0000000000000000000000000000000000000000..fb2b3f6a7851cbb421dc36e5e006d392d3ba2d0a GIT binary patch literal 1635 zcmV-p2AuhcP)Px*9Z5t%RCt{2TRTr1Srq;ztOyMdF9jv9c(sBCG`}E`76jX%R9mcEMS~)(>~ zIrlv7_&Vq_D!*02t#?}v094A$VL$rV+WF|neY($G1ZM%L@B4(l2~;UBhxqmeGLX>CS1Upv{3e+P}%%5`_{&zSTq zile^WV!_Up2`(}(;;kDwdV3V+F?b@rcCrn(=mG%z4=Y9s-re6w?Nd}704)9RyOmrd zn+c|wV2kf;AK(z4Hu%!oOiZ?0?2w&RiDFw_2&({qTW1r1d6v;Nh72LlJNee6C&6a$ z?i}DB`0N=|+So_!qWeB$e+)SZLE|-B;ex_?BHxPiC_7O};tbwD@KM_69NW$qB1gQB z+>#9EX9wYXeIhj$RsjS(#fQ?`40aE%y0SGJ1w7&0R4FfqayrNzJP;A?K6fL(%s}mA z8)IR$tw>kfzU;z8$b^rzXPUQq41lk2@gqZaQ#OTPs->qj7Fl`sIsd~7JsbTV0fGjA z5dfI!48HL<0CmO$XTCzT7+|C0&UM+c{R1D~x|cSzgz@lPq>M^=IpkQpwNFnPJSQMV z!mh+^$uov#g-9H=+pU~Vn?$&O;4A&nId8ohAA9SbQQ@}nK?aBT#EUvBR|oGVrg|n& zwh)}QjQ7xtkVywRE`vbI3eJM)*|x&S#R0(4+oLYmlGGkSo<+J$NN4p)#AnW4YA4$W z&Vo3=0rsgLkX)hqhbzYC$c4oaT$s0d9FrZs4-uRO9a(=YAUIXWVzO{ue7JQs;T8+1 zU3|vU{6rjBxiD(OAJFcX?`Lm zo|CU9At^-*&ax>h4~Q;BN47~Goqv}znkVu_Tj7ZOq+-Qe_q0_k6+l^_E1=w>i?OhZ zv9OBL#=f>sS#BV5ezuB_lu}Efazzz^?$(PT<5%O@nr2!fNX@z0*HY{j{Y*ZB|N2tHcJjBh<$pi z$8mi?hI8v|B51sfKWT*5FK^-xoEuz!ioTOTJZpVAhj3AlIGYiO@8Fl*N_d-mn$suA zD<=jbWXrB!-e9`$6w`&L0D!5^K0ue+Sy`MYePs(g{g|_X^v5T3I%*kWD7J z*^1o8^!Hx@s3e;}RLGHry}w~S_)ck0B|Df))C$mRxzg3`U`;S+ZFec7vVs* zWdRv^DJ~CLL^N9w$tI#jHzOn0H%I+I-eg(BVC5p&JA3CSzgl27$$E(~M1-bcK~zV$ z$;yEt>8YSB1yHSsmBouk<@E92h#^!pB~K*{pU zuNF{mBnR0c=?pYKeZ|z|Xq>Ug5Cdtw(Y+L)!_d7XU~zUFQ#L$q#k zCuxKOLrZ62adsT_%Nty`z5wAkrk_QG|N3Gfu2=y*;kt@6H_}b-2=gqq?h=#&He0}7 z|DNNgm4(>yxs@motu*$u3}Evz_A!u8ZiVAQlHMS(wxx)K^|CwSF%X{w?mvDLUt;tF zkCFH^LzLforrt0 hZ~i_gpdSS5@IN}}xBuP9HU|Iz002ovPDHLkV1oW-7}fv) literal 0 HcmV?d00001 diff --git a/Samples/Monofoxe.Samples/Demos/ShakeDemo.cs b/Samples/Monofoxe.Samples/Demos/ShakeDemo.cs new file mode 100644 index 0000000..6901ec5 --- /dev/null +++ b/Samples/Monofoxe.Samples/Demos/ShakeDemo.cs @@ -0,0 +1,115 @@ +using System; +using Microsoft.Xna.Framework; +using Monofoxe.Engine; +using Monofoxe.Engine.Drawing; +using Monofoxe.Engine.EC; +using Monofoxe.Engine.Resources; +using Monofoxe.Engine.SceneSystem; +using Monofoxe.Engine.Shake; +using Monofoxe.Engine.Shake.Presets; +using Monofoxe.Engine.Utils; + +namespace Monofoxe.Samples.Demos +{ + public class ShakeDemo : Entity + { + public static readonly string Description = "A to doubt." + + Environment.NewLine + + "S to eat." + + Environment.NewLine + + "D to steal his dainty."; + + public const Buttons DoubtButton = Buttons.A; + public const Buttons EatButton = Buttons.S; + public const Buttons StealButton = Buttons.D; + + + private Shaker _shaker; + + private IShake _currentShake; + + + private Sprite _cat; + private Vector2 _catPosition; + + private Sprite _dainty; + private Vector2 _daintyPosition; + + private bool _stoleDainty; + + public ShakeDemo(Layer layer) : base(layer) + { + _shaker = new Shaker(layer); + + _cat = ResourceHub.GetResource("DefaultSprites", "AutismCat"); + _catPosition = GameController.MainCamera.Size / 2 - _cat.Size / 2; + + _dainty = ResourceHub.GetResource("DefaultSprites", "AutismCatDish"); + _daintyPosition = GameController.MainCamera.Size / 2 + Vector2.UnitY * 100; + } + + public override void Update() + { + if (Input.CheckButton(DoubtButton)) + { + _currentShake = _shaker.Shake( + new ShortShakePreset() + { + PositionStrength = 0.7f, + BounceCount = 30 + } + ); + + _stoleDainty = false; + } + else if (Input.CheckButton(EatButton)) + { + _currentShake = (BounceShake)_shaker.Shake + ( + new DirectionalShakePreset() + { + Direction = Vector2.UnitY, + Frequency = 8, + PositionStrength = 10 + } + ); + + _stoleDainty = false; + } + else if (Input.CheckButton(StealButton)) + { + _currentShake = (PerlinShake)_shaker.Shake + ( + new ExplosionShakePreset() + { + Duration = 2, + PositionStrength = 500 + } + ); + + _stoleDainty = true; + } + + + Vector2 displacement = default; + + if (_currentShake != null) + { + displacement = _currentShake.CurrentDisplacement.Position; + } + + _catPosition = GameController.MainCamera.Size / 2 - _cat.Size - Vector2.UnitY * 70 + + displacement; + } + + public override void Draw() + { + _cat.Draw(_catPosition, 0, Vector2.One * 2, Angle.Right, Color.White); + + if (!_stoleDainty) + { + _dainty.Draw(_daintyPosition, 0, Vector2.One * 2, Angle.Right, Color.White); + } + } + } +} diff --git a/Samples/Monofoxe.Samples/GameController.cs b/Samples/Monofoxe.Samples/GameController.cs index fab4371..73bec3d 100644 --- a/Samples/Monofoxe.Samples/GameController.cs +++ b/Samples/Monofoxe.Samples/GameController.cs @@ -28,6 +28,9 @@ public GameController() : base(SceneMgr.GetScene("default")["default"]) GameMgr.MaxGameSpeed = 60; GameMgr.MinGameSpeed = 60; // Fixing framerate on 60. + //Setting random seed for Perlin noise. + PerlinNoise.SetSeed(RandomExt.Global.Next()); + MainCamera.BackgroundColor = new Color(38, 38, 38); GameMgr.WindowManager.CanvasSize = MainCamera.Size; diff --git a/Samples/Monofoxe.Samples/SceneSwitcher.cs b/Samples/Monofoxe.Samples/SceneSwitcher.cs index 14e9bde..1402f38 100644 --- a/Samples/Monofoxe.Samples/SceneSwitcher.cs +++ b/Samples/Monofoxe.Samples/SceneSwitcher.cs @@ -26,6 +26,7 @@ public class SceneSwitcher : Entity new SceneFactory(typeof(VertexBatchDemo)), new SceneFactory(typeof(CoroutinesDemo)), new SceneFactory(typeof(CollisionsDemo)), + new SceneFactory(typeof(ShakeDemo), ShakeDemo.Description), }; public int CurrentSceneID {get; private set;} = 0; From 33e12ac6c92168b3ac5f139d9b494a8bf6b4cf8f Mon Sep 17 00:00:00 2001 From: Ne1gh Date: Wed, 21 May 2025 00:03:21 +0300 Subject: [PATCH 2/3] Creating cash for Carmody and Gustavson. --- Monofoxe/Monofoxe.Engine/Utils/PerlinNoise.cs | 4 ++++ Samples/Monofoxe.Samples/GameController.cs | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Monofoxe/Monofoxe.Engine/Utils/PerlinNoise.cs b/Monofoxe/Monofoxe.Engine/Utils/PerlinNoise.cs index f7beaeb..53ec11c 100644 --- a/Monofoxe/Monofoxe.Engine/Utils/PerlinNoise.cs +++ b/Monofoxe/Monofoxe.Engine/Utils/PerlinNoise.cs @@ -18,6 +18,10 @@ public static int Seed get => _seed; } + /// + /// Updates seed and creating cache for Carmody and Gustavson simplexes. + /// + /// public static void SetSeed(int seed) { _seed = seed; diff --git a/Samples/Monofoxe.Samples/GameController.cs b/Samples/Monofoxe.Samples/GameController.cs index 73bec3d..49c26d2 100644 --- a/Samples/Monofoxe.Samples/GameController.cs +++ b/Samples/Monofoxe.Samples/GameController.cs @@ -28,7 +28,6 @@ public GameController() : base(SceneMgr.GetScene("default")["default"]) GameMgr.MaxGameSpeed = 60; GameMgr.MinGameSpeed = 60; // Fixing framerate on 60. - //Setting random seed for Perlin noise. PerlinNoise.SetSeed(RandomExt.Global.Next()); MainCamera.BackgroundColor = new Color(38, 38, 38); From 5dcd4252c3fb85893f7e281585ebe246f257687b Mon Sep 17 00:00:00 2001 From: Ne1gh Date: Mon, 2 Jun 2025 13:30:39 +0300 Subject: [PATCH 3/3] backup --- .../Content/Graphics/Default/Handshake.json | 6 ++ .../Content/Graphics/Default/Handshake.png | Bin 0 -> 13874 bytes .../Misc/Components/ActorComponent.cs | 52 +++++++++++++ .../Misc/Components/PositionComponent.cs | 32 ++++++++ .../Misc/Components/TileCollisionComponent.cs | 58 ++++++++++++++ .../Monofoxe.Samples/Misc/Entities/Ball.cs | 53 +++++++++++++ Samples/Monofoxe.Samples/Misc/Entities/Bot.cs | 39 ++++++++++ .../Monofoxe.Samples/Misc/Entities/Player.cs | 72 ++++++++++++++++++ 8 files changed, 312 insertions(+) create mode 100644 Samples/Monofoxe.Samples.Content/Content/Graphics/Default/Handshake.json create mode 100644 Samples/Monofoxe.Samples.Content/Content/Graphics/Default/Handshake.png create mode 100644 Samples/Monofoxe.Samples/Misc/Components/ActorComponent.cs create mode 100644 Samples/Monofoxe.Samples/Misc/Components/PositionComponent.cs create mode 100644 Samples/Monofoxe.Samples/Misc/Components/TileCollisionComponent.cs create mode 100644 Samples/Monofoxe.Samples/Misc/Entities/Ball.cs create mode 100644 Samples/Monofoxe.Samples/Misc/Entities/Bot.cs create mode 100644 Samples/Monofoxe.Samples/Misc/Entities/Player.cs diff --git a/Samples/Monofoxe.Samples.Content/Content/Graphics/Default/Handshake.json b/Samples/Monofoxe.Samples.Content/Content/Graphics/Default/Handshake.json new file mode 100644 index 0000000..86af8ec --- /dev/null +++ b/Samples/Monofoxe.Samples.Content/Content/Graphics/Default/Handshake.json @@ -0,0 +1,6 @@ +{ + "v": 1, + "h": 1, + "originX": "center", + "originY": "center" +} diff --git a/Samples/Monofoxe.Samples.Content/Content/Graphics/Default/Handshake.png b/Samples/Monofoxe.Samples.Content/Content/Graphics/Default/Handshake.png new file mode 100644 index 0000000000000000000000000000000000000000..97c33ec543ba4b2a321b961f036daf4c04bf8494 GIT binary patch literal 13874 zcmcgzRZ|>Hv;=}%kPYs>K^I>V0Isq~7#Io| z8S!sw?pbGDZl^ZOE}IY99a&DcPfZV=-sPs!5!B)8fO!Vq0(q{l5ePE&QChw<0jgNk zAarq5ivEOT_^>bvk*q%`rxdl=1a*{z%EgtxwUsF)!WF2;YbWhjG8cW%Hatf*JojR~ zZ9PmzJa0BQH(2c*eD_kE?zPS4FLa0(v>?%3{|~^!vD+l%r%62dy4C$jzeqm5AnS14 zKoyAwJeYp*yj$VrY)96*_zYuUgT9(!pWW7Gn;Lu}WZU;x?BjO+_ekEGBQGB%;k9$y0UN3;TJcC?W2AE$*+ek`; ztH0>L*&!MqbC?0Q_^Y2d0~TF3vb_F>m~j9r1zoH{rgT<9WuJY&_4eg_6kjxof?>O6 zVcEY`q1*ALvz=B;^d%-hm#d08XA?}CbD`lhTFOYdA)$DXIkN^XNC53uM2R2;Cw6wu zPHh%j##sam|K>{*vHFHX58ubhgKB-D4~LS5SONj3KV78xq7{<_B(y|&wz=O{G6E|z z1q-fR8-~`zzjNQu^Ne2hy)r2mcBP$CVlr8-mtW}8b!1$j9Ab94VWvDK6h+N=wyQN5I|B7=7i=R}di$Q$|VD1X-& zE867yf;J-GfU?L5itC@c&zs=Xoxk={2q)JyfJ z{xhAxi<%VHACzeIty_wV->uVye`-NyA?UC z9ku}oI?ay5z^AzWy8sh5W0+4ee{M^JgDb;dGJ!!N<}qRVa>~h^XC@YGYY+LF=ehdt zhq|DzI;U%j${6;+KVDg#xn=_^8WThM#lOG(TgR>DPoNNdc!{OBYxp(n?wURB)}QiZqp5@$ zLg3Gc^z!Syg)Clm^Wo9PcQbM;#=P>T`<=a^+W>3tt9Zat zf(neylx+&>(Nvsq2NoIa{zElGWfBf0JPoS;EE`y$KaSQ-DOG?^{#8kk8615srJQ9S*BZ+Ycl%4QuY_m4JwFQiAfj#7(+8@Z$_>jaR4TiCF|%1~=wI0vY`; zUi0mYT3yh2*2eJF>ODq!*C;_aI{+D=uO^R}st82R(rKV8T|pXUiiC!DGjj$$9L|c_ zxF-zh05m`W?R|!WkX92=4eSw*w!U|cyR0~w>?M<^{Vph@yNgj9d4sA`>_y=g{-$;O_%BYgQ+lL2LM z`%8MS)5pHIG+)tg9OJXzNfC^+V(^&J^L4fQv$+9vg0;}oPsa&qs@}EIBkKC1QFD9j zViXWTJE1w!XWyN-$9nO#rWM%eE~S$9mun7S0Z>G~pCylS%7xU1lp;AS|3lEY%8-;o zbOssqD+N&x0lFUpPy#355v~x^#jkGpm5O4KR97he?~|cy7V@B~*y#q!`OTcFn!@(> z1ym}5_q+GyBnzI_NhA^N~oz_mu9q-t-H{v%0wg8$?8M5S{G`Mh^`{7FMN3Esz z7>y|cnQuqOAzh=YG1VG+d})_47b`_;OTAjT$^QGdq6t}CQpssVDp{f<)8?|UDxwJo zBYOvRSG=*__i!Eb%nQ1eO?vJu#@4%IU7yO0#FW*-Y-C!QL*kuuReRR1|m@wYu9Ks}jTGVAJM<@<92Z_;wK^ImErVf-DtGp4#BSFWR+A zVq9ztq@@?@yRS}C{mmv$tB}M15KV9qECe@1959^jF|1tLM7tgFPJ}iPAp8AO7|`Ib zvF000Z9-L#i&X=lX5&jfPgO;LTeRy~wTvt*R@BXD>R{VzO_d7W^4os|Z@1oT82-sj zZcdK8nHHUH@Q5y8iDbk|FKcXjVmd6YK9?FrNt~jaPto``>j` z&j5iY#wA{y-!0}8`}Ih`7uCQLOqEP+H292?k}4~qJ0#84i+sw!Xb^Gc7bufb{At?i zQ}EZzPkUQu7%H`1=hK7BOH737w4x%P2oeub|4RHN2)sj>aHF}Xs}^a;sf*dol-Lcs zLAKoroM?(-I9$_J3C!eoWt8s9D`~rE!=g$36sG<=;8sI|v!;U^8N05wu8MP`=XOFV z-B!1joAWn7!gWP6F1WByJaVRB7%m`;#ro|vAkooT+7cgSB=cxnJ^D)^8@N3EyH5GY z7Fv%Er9~tPUVp+fljlT{oAl{WQ!dnY*-G#X&Oyii)wyS)Od(6Nd<#bJ{!3NUZ>Cuc zDp6U{(r~-y<_E{~$ODi2wHuO6!tK!@P((kG3mB67pIXf+_>xd#A3T{mn;drc+rhk6 zOO;~aCo`QWySGZp2`6T)C7(&oKpI-g{Sk%(+oDwkkJW`0oeUNqO|y4>tAu<315>C= zacH-r4)=T4ft@C&-zuOovik)K-|S1NxZ_um87k`m#IX3EqAUkRaZZ9m+j`0p&*seL zFjPgDl$5)_<+ScFsK7mh_-H=^#YQc-SxaJXlO)a~H#X%m6!H}OlajDX01|&s8U8K* ztASnuI_MEDFb4SNh$0LilmQK{wBIb*ES=83VU$9*&>_h zWK&N4icG|l$Ae+jZmlCl$sLs+zPH+Us@FhGKomzG;4P4zh?apnL$K?)OixYT4@mhV zZ3k*(HItB^(X*hqZ<=8_xK-(;8SgbfJPzNJ>-NcNd7Ua=#s2)yl}iI3%w-QQw9mFP zQ4v)LEfP9S4l0LLW#Y&#GsB~iX!Kvo!Jf-Dq{>B9X1wCvPxJWmX-82$jK^C z$cmmT>`XPs(w5ULQamuBovs51WKu;MJz&dcWCoE z^WlBmB1*IA$R{G&zVCwzWdZLuP{~T!0lAsg;}>@f1d-Qkja*`Ds@}+D-CiGW~Dd zGrZ~O(?Kt&BALF`Rg;u^=F*z5hg5)vUf=L5^xTjDRk3#g*quj$$6G^H4%qf z8oO1Fv%|EZo8<)G&}*MY9y`YR)1bMv{ug4bOHAy0;t$A6QYit-PJKUJ=oKk+h-jK1 zkkzqzRHx^OonBv}J}*3P;m(7(8_S-HHsJ3{z)z+C35DF3%!3!Ww`dZPj>;=hxM^s4 zoIY$~FMG@28rQHD9_Q4!WyvfsxlPK|=kflxa7$&K-dQ+qKCcVB8V^(i2PlyzIiEN1 zp!TJu(YpM%Yu9IhvMpfBO30Bm+&s#NlW40wqM~qA{&TfbG|7O?w!$gbp$c!6XcTr} zadxvjed=;Im3(P9#}BszOMwCcj9PVADgsTGgd8V@rv9wLdGbYB%&*Sc#NL;}&sei& z^sme0UID)asJHZvb-Rp5m)WLUq;fmpPdS3rGuN$9;wgE%<@ICa5q}3H!LF< zz!YU_`-b}o?FYkNklFDK%agc<{MPHwL^x2U+-=CnAV~%Ol4mUXr$UTAGhvVT2cJ>z z)&gq8Gt%?q0+$2?1p1)<_hM{CVV9Vet~XdoVro5gqX^U!FQ^~gi>Bkc+dd=>O=EH} zV)VkHnx|X8?|vDy`GC7QTJzTML&`bIjnu@X&N9tRl+)jZ?nNl00&RF=vYXWa(r{vl zP6b4w%U~ZL)Jh0{+kDAG#(Pg1(6bs>kMtmI?8cHoWY>4XFnxR4KHS* zjzD*$-@M-`}!@^^l#=xm+4z%~GX=-@Z(2II#v^g=Np`wq7Ay2>`^RK!WoJ zbS9}M@tmdHD!vc6cF*-w-14mCS9QKG4v3~ATuKo$lyph}Osn7m&c}DNEAOOLvp1Uy z$lgvuum`6p*_#J^gC4!M_Tm%K19Y!EB;(^kN3PD9KWZ4dwUr!c=(;x+yT(`gk%Dlt z?CT=R)7)ZT*duyZtub{lai>?S(WbVTxNX>mXKedg>5(H@8##$0!%+nmUn$wT{yRFA zOp=vbFPvFhr+xKLHwIEgJ1}%Jd0N-K=xFH{dcBlZm6+LD(2vdyET{a6XjZXzH7rZ_ z$~UG^SyX9r)FkQ}UK(OKxtYcdg^as^!WzA-sz#*y+|Ghmy4p)A;!KV%SB=qXm9Xk2Gs_1w=jW`1M@d&}5+k2^gL{j$}3v-TA{gQtN2WLA9Ot9)+AjA~WN zQF1>{`7*_frg50mjL}L}l~zTZiJgMfs(1RdUV97}-L`X@ci90;*IY|5S)#=yQCAW; zsLNp|UzySbhY+}22@VisksDROL71D%YpdaR*r0z1QfC26RxwAksf-eb>BZm{I@Uwt zecU05gI_ovgKp4RH>mG`-P=j*&DgB!chUy6Pm!!a)n|u%flc8`Unx!Kp$zCV^wwut zU}N-Funpf!orbM3c#8P_KB07(8l8#wrDbrj+ob20=a zy&hA4-+d5@opJE$n{j5=K%b{j;zZccHBM)cqfN(@jpc1&dL+ifkhZjSoLVFqjC_AV z;dm4zR044DSUBy|`4L1lr3=%VZi#g@y8cR^vJzIb-7jQj5Wr^pjckcd!|U@w zw|SZ4`SeKc1FQIViD*vL%9woI!Z*-D(P4BG64f)&bc$BBO#0`{=ls9E^Psm5YniT# z>l58a1zLTkA(w9-I5nAV9Fx$_{Mh%hYac7On@2l&HQs6f11Q7A^wP_msH8XFAkjug zcgzT*lTE4lJf1nU9k=__YjUFd29cwUB=ZOeL{5p5bI1(CyGaq{y9FKMF&QZhKYRna z-_BJ@405hAKdvVUF_`<}>78K@F!WQciWX3mbw^ZcxgU_+FDOk)H}u@7H&H6tC2SE| z?#>k7D>z7koOGJaP+Yzx{PLg}JFx^?>x+9X+*5WLcJ=A}U9v9eQKr!`qwUW_{LL3a`*3&!n=m*tcAI89CH8Zz0$4>hV~typyD{z~dt1R-Hb;?7H-mCr?_VJK@n_MWPwTUqo;`oWDAzrig2&go&h>%q!6kFpZsHD;{PJ?ry7c#%9dc-5OAh5XWZ}1bdpJb7S#?v))dhR1jiT$rs zc=|{J6{W&svG2QfGm@BOAZ4I~m@;sJKBSLw{?Golhf%HfmDk24B^8bBZTBvpwzhbj zlcaP;%Lw7hQPes))W!6Ou^#Q>Ns$$9$n1SOE+3t1*lDaiVQK8bu!lFaE}o!=SMFHs zC&UpfW?dr7JR)RgtOrT)#8;W#KsOOeHm(on3i%zM#^-Zu*B!YG`?_`Eo3czhXeXZ6Z3qV^X{vWTimGG`_7s?01l1yJvV~DK^Bq|7MCDV6?!0R@5^f@O@ZBL@`W*V}dJd`N@K;%J>wN)gKJ_s=Kdt-Jyh_ay&~0CKiLitUmovxU{U zgSBP^y5}3{Z%r{zNV$SXB_>QEN1ffRAKWzpn@r&5>lT2g69IcRLo-+tZ*>hV4Gtuk z^S+Hh_A;hPsF7)H$J08)L7+3@KZxJV6G>);QDTSMW}sVz`a>4z#!?*QKw1iZOY#Yh&`qSB3TrbyeywmkAldt)gz<}#5d-@5M~b1g zLruEj_=USz%d3Zh{0|V^DA7iwJIP|&VA{Curg9Re)M`Sz!GB}ZI*vPDWxS34^W&=- zixLK$?yA^Gz6t;#Fn&$l? z-{;Utb|Op$xl!}L4JraJS~*#zkEi_csL*i+5@!V4IcbpJtnhp9EWqxIn|>{f(3{*v zsrRwWuHg^XFBq8)f%Qxr>OZrB$2`Qe`6B$!&}#7Nyi4nNv@}iNa)3P^aalgKCGd6H zLyczQOPLmB{FH`_XU{-WTm%MvGMWS&Zf>tDR%q|QB?13KvJ$3D0)s;E%AP<~@FWMh za30;uEpM8XTLsFP+JQFy3S}8xF{TYYXQG(sIvc;hc7>web~-(E>7=z%vY~YWli<4dx!uKdbcW9Rzxnl3P2ZJl-t|SscSa1ud z(k!AK=GckSzy`Vli?AdW7N&bAk>IhfL*28ft_I03yCrF8va1MeA(OsWpk`L(iaz6ZLMM@WLYT_>Kx?AtVDoQE!$#Tc&s+AF`p#>;#3s z5u?(1u1pn7#8Hy2p&ysW>$;n%|1GNf?!neYzb127?3cI|I`v~;~ z{xQaitu9MU7T2}si62O=h`4y^ZoK5K36;JTUzTrf=RO{6%32HMXE9pzt7wU-tB2uw z8C@p#fTfhRypC$W+e_4$^D3RNN%((3YW|a%K$ACMMlNSv&FH9)=L3Sf##8igQF{<* zV!Ozn6Clw%!n>iOsoD))5g-G8w|d{6B-`7*L5ku=BlB3)UFHM96ej(JdF^C5ex5Lo z1*Oa>USJCt?|%vq!(-w@X3hswG+{_--kW36wXKg4_*}T%FAH)@%C9!a0uGSmx!3^o zn=fc?rjLVYEUWr&xA=el8zF=x3Mz|wMKOh0I`-*P8t9Hhz4!sF-nluoXg$|ZJ$Vn% z1VlePf|f8cF)INpbs6jTtxgm_qgI=yx3-!=K3xS1>1z@kj?5=*^O-{jl5ocAb(F#9c-bLF}a(x%q z*DI#nQP4@8kXVYyX86H(cl%7gRrgPJ^xq$J8*e|d0&7{TEFzIJ1H}!XjJHn z;v9X73++NC#1@fBL0sZC;dNq=KqH|}jhisCaR^Dn6PqBA^^V;64T7-P%`$wQZ<;>S zN~gJxm+l-mDC?gLXC_-oZfN6A9>vhYvjpxK6}u3UnTc^yJiprAN#$LtDO)8xE25m^ zS{bAG4g!{f91k4DB3xoHDUrSc_e)OGkS&f{Oy6IHIyyq~8pmLbU|_!d``=vv4=uf_ z6qe@x>EVuFHu!Saz#Lxf+j zpduEzA%^~aTRTVC4TUiUX%jj+?AhY9(0QrW2aaynp#i7rk43n0ot=g$waCZD9LAVN z^8KJ8^1UTti0TZTws^W^6?m(!nPlv-`ll-EBPZUTBgJ}- zmeef%u+w6lgC(K^j7>6KLKz&w1~%jd|Ma~;RTOv@IEy!H1+LvR?^s_Dc_}Lc2HAv+ zvM454y<4BdOxX)F=NlXN+*rqPTL)(UQVYG8#XzDzu=vliu02z%>w<9Q1TCzV=a2xISw6aW0^IpiAI9g2VQl$Z@F)X@scCNnYkIfYzQ6DB*?KX z`u1<%+-T+NRnIa-3Yv8>5`X2O3&zm}Z#3y*uMFKN8tmWXwg=&WJubM-&dH6@+GmTXL(?lX;Nmf^w7a{4+=Ic}_87q66r;krJQSad?qVqmxC}HDOrt;yN3%~%PmuP zee+5%k#I2)2E)&R+hb&_7Y2GJT(3H`x!I5c2J=!;*Y^)W+&`!u@G23~i&!0g|6VklrH4fTQAiGCMTgYlfrp(nFLcz% zY;PwKV<#6pY_(8$Vg@yf4ivEyUZ)R4Mc=D9##CKNep=Jf0Qju<&Q#+lo;1-|vxV<* zTY`TUTMfM@75hGJII7T>MP;XdEo|-g9jVZ)_&JH1Thgt7vf$bv}Ls zfgo4#lq-wYE`=D^eGpVywqdQOkZZ+iXn8v5jnVVJ06*}CbW%vrsB;VKF<_+M&x9B< z)Qf)7f7xj9_;1t7AkpeILn@f3&+TI_H&2ziTzlN^9DR9-h;4%j7@Fc}E;9on@Z{!c zsYBLJ-Gu8mnZ`^LfOv_9w<< z7!Z@};1{^g33dO+{BYU7GwE=3#5m`vnlT(yosK1@5W!rqzMbEniHQ***oDB8;@DDDWJAB`Cic)-^-f>p=SGDQ>d60Z*q^)Jzltqd# z(g8sfhRYk5v5Pq61r6b79ZI6-4gqG-wv7p|F5B=f;oes_p5`!Ui)X=2c(ijn{?{o& z{m#b>?IQl+fR4h)SK)V-lCv+&6){zi|D1;y(~^YR;%CfA6bMsdA<+ezihQKMy_3PO zoU@$Jo}+?D>R56eC4ZFPuDe0ze+;jumfzj`(}+Xr%FYfnv;(j3c1Wv9?2k7B63t?- z{&Q(N+qjxx`G1KcORP8G!`>ckfZs(OOO`>2vJto{!XIdKLTa?1ehd!iqjmBH@ znw6tU+Zv{mX`gP~TyK0FAF6j6FK)Y3)X~k#6|vEnrdwE%N(c%UB*vbQl z^7dNz_GI_OgBq36eL&DDJ14$FS(4l9*jba!I_WK(2l1-ns9yZ1Wr#k zb0bpT2P(&#ERG&qqtYD2RXXur03-U#ICP`jWXU{l!PN%s;^zFFlh7ZhI&5#qjwwWQ zzXK3yXjSB$8#12lWh-Cw%{}pJ8JIK3Xci8DnbIEnCr2?v9uJD$&qu*0SI_fWp%{VY zbu`rmU}I{vq~cOycWcv&`z(6OW?A_uE@l?@e(pCV_MR{ChF=Th9ehN@@n>(C(vj)) zi^}M=loJ4tz4iHut|Cd*qO(6`nN{C%N_o4+4B|$jzt!I_1N)cd zBwsu~dDJ?*jDL0`s?qIHQE%s-ZWmeQ&EN}t4o==^ybG8o6g^}WO_gyg8J%dW`x#P} zt6XFQeJ!SUpp6=YOk{z-v10(~RA#X=MKW=yU<*E(eY48?Dnm1?X3d>W<-H*HvhdYb zXh+1F%-Q&>;-WU0*NH{7wZh}f0n6o>swWA*L2WPsPW5F`(hH1q+YzU8c=egN*=YUv z@$=&`N&bp^6U#93mJ6en?GE5>{y_Qc8f#eth$^)#9x=nxC}B<{ieGjTGo>T1QUL0Y z8)g3QsT(Dv57c*+w9Y}lyh3JFwP(-%*F5?LQM7LlEmZz95nU)($6(8E$&f9L;Hzc) z3PiDpUdSlybeq6Hybpd2)CNsNgF8oFqgMS}1X}`Vf-_0Cw$79M$5X|@)A7M)LSf;Z zTkq8+s;yCW#pt8Zrx0#8n!)yB4E+r+?_vfrtK!w=t)`*MZh%25ii+QlH}dZ6WeB+{ zyd*VV_Uzt}3dXB3RAW)1otqtuTI7Rg2D;RP^6p&L?n7S`?eBXqMy~v-vPmP1>e3|F z^J`}N3fGKHnTDMoN7#rYyo`81){+VX2_=l` zVzOKm1F+c^c7w{Byq^OF!m50C(@*QnxOPdY4#9fBJ>|sY2KgA%xml7dtxPH0VuBl| zj788*VK{Z!;b7b{fQkXhM#RJDvT@=XGa|3dzDZf040jpTOQ z!vr%89uIW44Eu{ux>4MW&5ALR=Z+?Q({n(ohxAq@}bY-{QNZG|`#qDW|ORb&(}^<=CmzkbvbAM?4d~syohhqx$N}7(lJk}jsX~zNC-;?2b^7ixC!$9PVt;b>5!SA;01gTLiJOnlxS{J}_9MxU4 zTpl@D`B+ndEBqhEFKMGkkx)X>q46B`X*=;azIK`2Bl^!^0rN6M`^mt;-yyVn1d@f> z0%i7E%!m&YsvATk$>doZ2fr6mVM(x%&x{*q@I`) z6UUU-x?9zLzd1$v^fhAYepo?Hby5C-o`lSx_+-Ew$7|32?dM*5NJXOHzz*eJj7hDo z^&1FQuw@IcZ->ctM3i9V$5M@;yw#U>n5u(dlw~;K9`o9gst_EU5hXmt*uGTjE+uo4- z>IwJfbU`+LHArBZ_JRiVVEte@1a0AfLg3_EsEA)4d-o0477I*rt1Be_KU5TN?mBJ|#2K{77ph&{ z5T>1up7`fWPTx_`xM}3DGEEvb{p3byQ%V8D(wg9~(~n2ELB$Ebe&-p7^D@DSHU48R zjXz&TneSDGtRt(>pK}j@0F(U^z}bzRR(=4FzhUteJOmRYW!SXY^=+8aC-p{mK=imp zjH@iqG}jE;si>e-{W92;0P=bgCGJEg8eaom3+BcQyz!^KPiTv@_; z5NcBLNojqp{j(+yr_KT_FySEfACwy7|VE^RiZG9p#3+7u53y z=hLbF5$}U@sAX@e5;A&ZnEGfk_jYKlP3X3yzunXF{rrz1{-{M-t|yJl{O%ikHFb$2 zt=;CRe7j`DNH67xsd>`GE<*g=uHOAtoSz{#7V(_wDUNpn_~$>AXA4|6Vg#K33OMOJ?IiVicnw>>IOU!+8H|CAE`*kboOSR^C*=ZzXI|KzJs zV19ljdIp~{M`5;t`H7FAlptjjM^eADYj~uhehOqzt}-yGU7)KPbw@M#dm%yAdv-)BX#L+yck4Eg9Z%9HFLpZX?VWoRAm#(Ng@ zA2FYr%gU!P#M{PSO^{A!?BA5EtR=5;6)mYaPdeslPB-^}11ayTYT$OYTv4$FO$L#l>muivy07s0PR95 zK?@V_V6_Biy>FwUuI}-T=FjUnkAzLXZwQ$Po3FWDeAGb(pWSCs7UZ@Hg`G&Et88sP zFO$jNxEFo3BS{_v-d8G`)eh8^hVQIn`##aeud9j)I#@>4{gth6+MHvU!@~7!AgsEH zL%dEQe;%{)YOW#O08G48iR_?L%Q{U_^qt(78rYm27j{u;RhQGpkN-f1#pS}4dJ)Wo z*zp~WLtNY?j-w$p^FS!eB^SNc`~V7KW@(!KD=KKBc9>%O7pyMSmPNcT5Vr0bCzG(w z0;NIVh_}S=%nY59W!!d3U@L6uzN3Apej{Co`aFj$wHc zc>S@zie}d1uuFAiy%e^drJ`uaEfe%ha+b3(k#L+-xV@dSiPS~V#NOV&dg)h{;tki} zVwNH7>yU5`+imceb8K@?ym3j@SgM$MJVQ}49eiKiXY#4$@LzZ`)BDKm#R5h(zC}sW z)Am}{R} + /// Actor component. Actors can move in some direction. + /// NOTE: PositionComponent is required. + /// + public class ActorComponent : Component + { + public bool Move = false; + + public Angle Direction; + public float Speed = 120; + + public Sprite Sprite; + + public ActorComponent(Sprite sprite) + { + Visible = true; // Components are not visible by default. + + Sprite = sprite; + } + + public override void Update() + { + base.Update(); + if (Move) + { + // Retrieving the position component from entity. + var position = Owner.GetComponent(); + + position.PreviousPosition = position.Position; + position.Position += TimeKeeper.Global.Time(Speed) * Direction.ToVector2(); + } + } + + public override void Draw() + { + base.Draw(); + var position = Owner.GetComponent(); + + GraphicsMgr.CurrentColor = Color.White; + + Sprite.Draw(position.Position); + } + + } +} diff --git a/Samples/Monofoxe.Samples/Misc/Components/PositionComponent.cs b/Samples/Monofoxe.Samples/Misc/Components/PositionComponent.cs new file mode 100644 index 0000000..c0dcde3 --- /dev/null +++ b/Samples/Monofoxe.Samples/Misc/Components/PositionComponent.cs @@ -0,0 +1,32 @@ +using Microsoft.Xna.Framework; +using Monofoxe.Engine.EC; + +namespace Monofoxe.Samples.Misc +{ + /// + /// Basic position component. + /// + public class PositionComponent : Component + { + + /// + /// Entity position on the scene. + /// + public Vector2 Position; + + /// + /// Starting entity position on the scene. + /// + public Vector2 StartingPosition; + + public Vector2 PreviousPosition; + + + public PositionComponent(Vector2 position) + { + Position = position; + PreviousPosition = position; + StartingPosition = position; + } + } +} diff --git a/Samples/Monofoxe.Samples/Misc/Components/TileCollisionComponent.cs b/Samples/Monofoxe.Samples/Misc/Components/TileCollisionComponent.cs new file mode 100644 index 0000000..5ea9269 --- /dev/null +++ b/Samples/Monofoxe.Samples/Misc/Components/TileCollisionComponent.cs @@ -0,0 +1,58 @@ +using Monofoxe.Engine.EC; +using Monofoxe.Engine.SceneSystem; +using Monofoxe.Engine.Utils.Tilemaps; +using Monofoxe.Samples.Misc.Tiled; + +namespace Monofoxe.Samples.Misc +{ + /// + /// A component used for actors to enable tile collisions. + /// + public class TileCollisionComponent : Component + { + public override void Update() + { + // The most basic and crappy collision system imaginable. + // The point of it is to just show how to get tile data, + // and not how to actually implement collisions. + + base.Update(); + + if (Owner.Scene.TryGetLayer("Walls", out Layer layer)) + { + var position = Owner.GetComponent(); + + var tilemaps = layer.GetEntityList(); + + foreach (BasicTilemap tilemap in tilemaps) + { + // Getting the tile player is currently standing on. + var tile = tilemap.GetTile( + (int)(position.Position.X / tilemap.TileWidth), + (int)(position.Position.Y / tilemap.TileHeight) + ); + + if (tile != null) + { + var tilesetTile = tile.Value.GetTilesetTile(); + + if ( + tilesetTile != null + && tilesetTile is SolidTilesetTile + && ((SolidTilesetTile)tilesetTile).Solid + ) // If this tile is solid - perform "collision." + { + position.Position = position.PreviousPosition; + } + + } + } + } + + + } + + } + + +} diff --git a/Samples/Monofoxe.Samples/Misc/Entities/Ball.cs b/Samples/Monofoxe.Samples/Misc/Entities/Ball.cs new file mode 100644 index 0000000..65fe13d --- /dev/null +++ b/Samples/Monofoxe.Samples/Misc/Entities/Ball.cs @@ -0,0 +1,53 @@ +using Microsoft.Xna.Framework; +using Monofoxe.Engine.Drawing; +using Monofoxe.Engine.EC; +using Monofoxe.Engine.SceneSystem; +using Monofoxe.Engine.Utils; +using Monofoxe.Engine.Utils.Coroutines; +using System.Collections; + +namespace Monofoxe.Samples.Misc +{ + public class Ball : Entity + { + private Vector2 _position; + private Vector2 _velocity; + private Color _color; + private float _r; + + private static RandomExt _rng = new RandomExt(); + + public Ball(Layer layer, Vector2 position) : base(layer) + { + _position = position; + StartCoroutine(DestructionCountdown()); + _velocity = new Vector2(_rng.Next(-100, 100), _rng.Next(-300, -200)); + _color = new Color(_rng.Next(256), _rng.Next(256), _rng.Next(256)); + _r = _rng.Next(4, 10); + } + + private IEnumerator DestructionCountdown() + { + // After waiting for 6 seconds, the entity will be destroyed. + yield return Wait.ForSeconds(6); + DestroyEntity(); + } + + public override void Update() + { + base.Update(); + + _velocity.Y += 1000 * (float)TimeKeeper.Global.Time(); + + _position += _velocity * (float)TimeKeeper.Global.Time(); + } + + public override void Draw() + { + base.Draw(); + GraphicsMgr.CurrentColor = _color; + CircleShape.Draw(_position, _r, ShapeFill.Solid); + } + + } +} diff --git a/Samples/Monofoxe.Samples/Misc/Entities/Bot.cs b/Samples/Monofoxe.Samples/Misc/Entities/Bot.cs new file mode 100644 index 0000000..3aae35a --- /dev/null +++ b/Samples/Monofoxe.Samples/Misc/Entities/Bot.cs @@ -0,0 +1,39 @@ +using Microsoft.Xna.Framework; +using Monofoxe.Engine.Drawing; +using Monofoxe.Engine.EC; +using Monofoxe.Engine.Resources; +using Monofoxe.Engine.SceneSystem; +using Monofoxe.Engine.Utils; + +namespace Monofoxe.Samples.Misc +{ + /// + /// Basic position component. + /// + public class Bot : Entity + { + public float TurningSpeed = 60; + + private readonly ActorComponent _actor; + + public Bot(Layer layer) : base(layer) + { + var botSprite = ResourceHub.GetResource("DefaultSprites", "Bot"); + + AddComponent(new PositionComponent(Vector2.Zero)); + _actor = AddComponent(new ActorComponent(botSprite)); + + // It is recommended to reuse random objects. + TurningSpeed = GameController.Random.Next(120, 240); + + } + + + public override void Update() + { + base.Update(); + _actor.Move = true; + _actor.Direction += TimeKeeper.Global.Time(TurningSpeed); // ni-ni-ni-ni-ni-ni-ni-ni-ni-ni-ni-ni-ni-ni + } + } +} diff --git a/Samples/Monofoxe.Samples/Misc/Entities/Player.cs b/Samples/Monofoxe.Samples/Misc/Entities/Player.cs new file mode 100644 index 0000000..9626513 --- /dev/null +++ b/Samples/Monofoxe.Samples/Misc/Entities/Player.cs @@ -0,0 +1,72 @@ +using Microsoft.Xna.Framework; +using Monofoxe.Engine; +using Monofoxe.Engine.Drawing; +using Monofoxe.Engine.EC; +using Monofoxe.Engine.Resources; +using Monofoxe.Engine.SceneSystem; + +namespace Monofoxe.Samples.Misc +{ + public class Player : Entity + { + public const Buttons UpButton = Buttons.W; + public const Buttons DownButton = Buttons.S; + public const Buttons LeftButton = Buttons.A; + public const Buttons RightButton = Buttons.D; + + private Sprite _playerSprite; + + // The player uses hybrid EC - it's a derived entity with components inside. + // You also can ditch components entirely and only use entities. + + // I recommend using hybrid entities in places, + // where you know that this entity's code will not be reused anywhere else. + + private ActorComponent _actor; + private PositionComponent _position; + + public Player(Layer layer, Vector2 position) : base(layer) + { + _playerSprite = ResourceHub.GetResource("DefaultSprites", "Player"); + + // You can add components right in the constructor. + _position = AddComponent(new PositionComponent(position)); + _actor = AddComponent(new ActorComponent(_playerSprite)); + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + } + + public override void Update() + { + base.Update(); + + // Very basic controls. + var movement = new Vector2( + Input.CheckButton(RightButton).ToInt() - Input.CheckButton(LeftButton).ToInt(), + Input.CheckButton(DownButton).ToInt() - Input.CheckButton(UpButton).ToInt() + ); + + // Telling our actor component to move in a specific direction. + _actor.Move = movement != Vector2.Zero; + _actor.Direction = movement.ToAngle(); + } + + public override void Draw() + { + base.Draw(); + + + // Layers and scenes have methods for searching entities/components. + foreach(var bot in Layer.GetEntityList()) + { + var botPosition = bot.GetComponent(); + + LineShape.Draw(_position.Position, botPosition.Position, Color.Transparent, Color.White * 0.2f); + } + + } + } +}