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..53ec11c 100644 --- a/Monofoxe/Monofoxe.Engine/Utils/PerlinNoise.cs +++ b/Monofoxe/Monofoxe.Engine/Utils/PerlinNoise.cs @@ -12,17 +12,22 @@ 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 - } + } + + /// + /// Updates seed and creating cache for Carmody and Gustavson simplexes. + /// + /// + 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 0000000..fb2b3f6 Binary files /dev/null and b/Samples/Monofoxe.Samples.Content/Content/Graphics/Default/AutismCatDish.png differ 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 0000000..97c33ec Binary files /dev/null and b/Samples/Monofoxe.Samples.Content/Content/Graphics/Default/Handshake.png differ 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..49c26d2 100644 --- a/Samples/Monofoxe.Samples/GameController.cs +++ b/Samples/Monofoxe.Samples/GameController.cs @@ -28,6 +28,8 @@ public GameController() : base(SceneMgr.GetScene("default")["default"]) GameMgr.MaxGameSpeed = 60; GameMgr.MinGameSpeed = 60; // Fixing framerate on 60. + PerlinNoise.SetSeed(RandomExt.Global.Next()); + MainCamera.BackgroundColor = new Color(38, 38, 38); GameMgr.WindowManager.CanvasSize = MainCamera.Size; diff --git a/Samples/Monofoxe.Samples/Misc/Components/ActorComponent.cs b/Samples/Monofoxe.Samples/Misc/Components/ActorComponent.cs new file mode 100644 index 0000000..5853da5 --- /dev/null +++ b/Samples/Monofoxe.Samples/Misc/Components/ActorComponent.cs @@ -0,0 +1,52 @@ +using Microsoft.Xna.Framework; +using Monofoxe.Engine.Drawing; +using Monofoxe.Engine.EC; +using Monofoxe.Engine.Utils; + +namespace Monofoxe.Samples.Misc +{ + /// + /// 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); + } + + } + } +} 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;