diff --git a/.gitignore b/.gitignore index 85dfe18..0046971 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ SampleGame/build/* SampleGame/BeefSpace_User.toml build/* +SampleGame/recovery/* diff --git a/SampleGame/src/Level.bf b/SampleGame/src/Level.bf index 893542a..6ad8e46 100644 --- a/SampleGame/src/Level.bf +++ b/SampleGame/src/Level.bf @@ -7,8 +7,8 @@ namespace Strawberry.Sample public this() { Add(new Player(.(50, 50))); - Add(new Solid(.(0, 168), .(0, 0, 320, 12))); - Add(new JumpThru(.(200, 132), 48)); + Add(new OldSolid(.(0, 168), .(0, 0, 320, 12))); + Add(new OldJumpThru(.(200, 132), 48)); Add(new MovingJumpThru(.(136, 100), 32, .(124, 140), 2f)); } } diff --git a/SampleGame/src/MovingJumpThru.bf b/SampleGame/src/MovingJumpThru.bf index 0aa7517..8be62ce 100644 --- a/SampleGame/src/MovingJumpThru.bf +++ b/SampleGame/src/MovingJumpThru.bf @@ -1,6 +1,6 @@ namespace Strawberry.Sample { - public class MovingJumpThru : JumpThru + public class MovingJumpThru : OldJumpThru { private Point moveFrom; private Point moveTo; diff --git a/SampleGame/src/physics/Actor.bf b/SampleGame/src/Physics/Physics.bf similarity index 51% rename from SampleGame/src/physics/Actor.bf rename to SampleGame/src/Physics/Physics.bf index 368b7f8..02d9811 100644 --- a/SampleGame/src/physics/Actor.bf +++ b/SampleGame/src/Physics/Physics.bf @@ -2,56 +2,49 @@ using System; namespace Strawberry.Sample { - [Reflect] - public class Actor : Entity + public class Physics : Component, IHasHitbox, IUpdate { + public Hitbox Hitbox { get; private set; } + public Vector Speed; + private Vector remainder; - // The amount that geometry has pushed or carried this Actor since the last frame - public Point MovedByGeometry { get; private set; } - - public this(Point position) - : base(position) + public this(Hitbox hitbox) { - + Hitbox = hitbox; } - public Level Level => SceneAs(); + public Level Level => Entity.SceneAs(); public bool Check(Level level) { - return level.SolidGrid != null && Check(level.SolidGrid); + return level.SolidGrid != null && Hitbox.Check(level.SolidGrid); } public bool Check(Level level, Point offset) { - return level.SolidGrid != null && Check(level.SolidGrid, offset); + return level.SolidGrid != null && Hitbox.Check(level.SolidGrid, offset); } public bool GroundCheck(int distance = 1) { - return Check(.(0, distance)) || Check(Level, .(0, distance)) || CheckOutside(.(0, distance)); + return Hitbox.Check(.(0, distance)) || Check(Level, .(0, distance)) || Hitbox.CheckOutside(.(0, distance)); } public virtual bool IsRiding(Solid solid) { - return Check(solid, .(0, 1)); + return Hitbox.Check(solid, .(0, 1)); } public virtual bool IsRiding(JumpThru jumpThru) { - return CheckOutside(jumpThru, .(0, 1)); + return Hitbox.CheckOutside(jumpThru, .(0, 1)); } - public virtual void Squish(Collision collision) + public void Update() { - RemoveSelf(); - } - - public override void Update() - { - base.Update(); - MovedByGeometry = Point.Zero; + MoveX(Speed.X * Time.Delta); + MoveY(Speed.Y * Time.Delta); } public bool MoveX(float amount, delegate void(Collision) onCollide = null) @@ -83,130 +76,85 @@ namespace Strawberry.Sample [Inline] public void MoveToX(float x) { - MoveX(x - (X + remainder.X), null); + MoveX(x - (Entity.X + remainder.X), null); } [Inline] public void MoveToY(float y) { - MoveY(y - (Y + remainder.Y), null); + MoveY(y - (Entity.Y + remainder.Y), null); } - public bool MoveExactX(int amount, delegate void(Collision) onCollide = null, Geometry pusher = null, Geometry carrier = null) + public bool MoveExactX(int amount, delegate void(Collision) onCollide = null) { int move = amount; int sign = Math.Sign(amount); - bool byGeometry = carrier != null || pusher != null; while (move != 0) { - let hit = First(.(sign, 0)); - if (hit != null) + if (Check(Level, .(sign, 0)) || Hitbox.Check(.(sign, 0))) { let c = Collision( Cardinals.FromPoint(Point.Right * sign), Math.Abs(amount), - Math.Abs(amount - move), - hit, - pusher + Math.Abs(amount - move) ); onCollide?.Invoke(c); return true; } - if (Check(Level, .(sign, 0))) - { - let c = Collision( - Cardinals.FromPoint(Point.Right * sign), - Math.Abs(amount), - Math.Abs(amount - move), - null, - pusher - ); - - onCollide?.Invoke(c); - return true; - } - - X += sign; - if (byGeometry) - MovedByGeometry.X += sign; + Entity.X += sign; move -= sign; } return false; } - public bool MoveExactY(int amount, delegate void(Collision) onCollide = null, Geometry pusher = null, Geometry carrier = null) + public bool MoveExactY(int amount, delegate void(Collision) onCollide = null) { int move = amount; int sign = Math.Sign(amount); - bool byGeometry = carrier != null || pusher != null; while (move != 0) { - Geometry hit = First(.(0, sign)); - if (hit == null && sign == 1) - hit = FirstOutside(.(0, sign)); - - if (hit != null) + if (Check(Level, .(0, sign)) || Hitbox.Check(.(0, sign)) || Hitbox.CheckOutside(.(0, sign))) { let c = Collision( Cardinals.FromPoint(Point.Down * sign), Math.Abs(amount), - Math.Abs(amount - move), - hit, - pusher + Math.Abs(amount - move) ); onCollide?.Invoke(c); return true; } - if (Check(Level, .(0, sign))) - { - let c = Collision( - Cardinals.FromPoint(Point.Down * sign), - Math.Abs(amount), - Math.Abs(amount - move), - null, - pusher - ); - - onCollide?.Invoke(c); - return true; - } - - Y += sign; - if (byGeometry) - MovedByGeometry.Y += sign; + Entity.Y += sign; move -= sign; } return false; } + [Inline] public void ZeroRemainderX() { remainder.X = 0; } + [Inline] public void ZeroRemainderY() { remainder.Y = 0; } + [Inline] public void ZeroRemainders() { remainder = Vector.Zero; } - private void MoveByGeometry(Point amount) - { - MovedByGeometry += amount; - } - public bool CornerCorrection(Cardinals direction, int maxAmount, int lookAhead = 1, int onlySign = 0) { Point dir = direction; @@ -215,10 +163,10 @@ namespace Strawberry.Sample perp.Y = Math.Abs(perp.Y); delegate bool(Point) checker; - if (dir == Point.Down) - checker = scope:: (p) => !Check(Level, p) && !Check(p) && !CheckOutside(p); + if (dir == Point.Down) + checker = scope:: (p) => !Check(Level, p) && !Hitbox.Check(p) && !Hitbox.CheckOutside(p); else - checker = scope:: (p) => !Check(Level, p) && !Check(p); + checker = scope:: (p) => !Check(Level, p) && !Hitbox.Check(p); for (int i = 1; i <= maxAmount; i++) { @@ -230,7 +178,7 @@ namespace Strawberry.Sample let offset = dir * lookAhead + perp * i * j; if (checker(offset)) { - Position += offset; + Entity.Position += offset; return true; } } diff --git a/SampleGame/src/Player.bf b/SampleGame/src/Player.bf index 63ea746..bf2d4ee 100644 --- a/SampleGame/src/Player.bf +++ b/SampleGame/src/Player.bf @@ -2,7 +2,7 @@ using System; namespace Strawberry.Sample { - public class Player : Actor + public class Player : Component, IUpdate { public Vector Speed; @@ -18,10 +18,8 @@ namespace Strawberry.Sample Add(tVarJump = new Timer()); } - public override void Update() + public void Update() { - base.Update(); - const float coyoteTime = 0.1f; // Time after leaving a ledge when you can still jump const float varJumpTime = 0.2f; // Time after jumping that you can hold the jump button to continue gaining upward speed const float jumpSpeed = -160; diff --git a/SampleGame/src/physics/Collision.bf b/SampleGame/src/physics/Collision.bf index 37fab56..1d2c430 100644 --- a/SampleGame/src/physics/Collision.bf +++ b/SampleGame/src/physics/Collision.bf @@ -6,16 +6,12 @@ namespace Strawberry.Sample public Cardinals Direction; public int Magnitude; public int Completed; - public Geometry Stopper; - public Geometry Pusher; - public this(Cardinals direction, int magnitude, int completed, Geometry stopper, Geometry pusher) + public this(Cardinals direction, int magnitude, int completed) { Direction = direction; Magnitude = magnitude; Completed = completed; - Stopper = stopper; - Pusher = pusher; } } } diff --git a/SampleGame/src/physics/Geometry.bf b/SampleGame/src/physics/Geometry.bf deleted file mode 100644 index cfc9b55..0000000 --- a/SampleGame/src/physics/Geometry.bf +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Collections; - -namespace Strawberry.Sample -{ - public abstract class Geometry : Entity - { - private Vector remainder; - - public this(Point position) - : base(position) - { - - } - - public void MoveX(float amount) - { - remainder.X += amount; - let move = (int)Math.Round(remainder.X); - if (move != 0) - { - remainder.X -= move; - MoveExactX(move); - } - } - - public void MoveY(float amount) - { - remainder.Y += amount; - let move = (int)Math.Round(remainder.Y); - if (move != 0) - { - remainder.Y -= move; - MoveExactY(move); - } - } - - [Inline] - public void Move(Vector amount) - { - MoveX(amount.X); - MoveY(amount.Y); - } - - [Inline] - public void MoveToX(float x) - { - MoveX(x - (X + remainder.X)); - } - - [Inline] - public void MoveToY(float y) - { - MoveY(y - (Y + remainder.Y)); - } - - [Inline] - public void MoveTo(Vector target) - { - MoveToX(target.X); - MoveToY(target.Y); - } - - public abstract void MoveExactX(int amount); - public abstract void MoveExactY(int amount); - public abstract List GetRiders(List into); - - public void ZeroRemainderX() - { - remainder.X = 0; - } - - public void ZeroRemainderY() - { - remainder.Y = 0; - } - - public void ZeroRemainders() - { - remainder = Vector.Zero; - } - } -} diff --git a/SampleGame/src/physics/JumpThru.bf b/SampleGame/src/physics/JumpThru.bf index 97f9ec3..68223a1 100644 --- a/SampleGame/src/physics/JumpThru.bf +++ b/SampleGame/src/physics/JumpThru.bf @@ -1,80 +1,14 @@ using System.Collections; -using System; namespace Strawberry.Sample { - public class JumpThru : Geometry + public class JumpThru : Component, IHasHitbox { - public this(Point position, int width) - : base(position) + public Hitbox Hitbox { get; private set; } + + public this(Hitbox hitbox) { - Hitbox = Rect(0, 0, width, 2); - } - - public this(JSON json) - : this(.(json), json["width"]) - { - - } - - public override void MoveExactX(int amount) - { - if (Collidable) - { - let riders = GetRiders(scope List()); - - X += amount; - for (var a in riders) - a.MoveExactX(amount, null, null, this); - } - else - X += amount; - } - - public override void MoveExactY(int amount) - { - if (Collidable) - { - let riders = GetRiders(scope List()); - - if (amount < 0) - { - for (var a in Scene.All(scope List())) - { - if (riders.Contains(a) || CheckOutside(a, Point.UnitY * amount)) - { - let move = (Top + amount) - a.Bottom; - a.MoveExactY(move, null, null, this); - } - } - Y += amount; - } - else - { - Collidable = false; - - for (var a in riders) - a.MoveExactY(amount, null, null, this); - - Collidable = true; - Y += amount; - } - } - else - Y += amount; - } - - public override List GetRiders(List into) - { - for (var a in Scene.All(scope List())) - if (a.IsRiding(this)) - into.Add(a); - return into; - } - - public override void Draw() - { - DrawHitbox(.LightGray); + Hitbox = hitbox; } } } diff --git a/SampleGame/src/physics/Solid.bf b/SampleGame/src/physics/Solid.bf index 65409c8..6a7041d 100644 --- a/SampleGame/src/physics/Solid.bf +++ b/SampleGame/src/physics/Solid.bf @@ -2,93 +2,13 @@ using System.Collections; namespace Strawberry.Sample { - public class Solid : Geometry + public class Solid : Component, IHasHitbox { - public this(Point position, Rect hitbox) - : base(position) + public Hitbox Hitbox { get; private set; } + + public this(Hitbox hitbox) { Hitbox = hitbox; } - - public override List GetRiders(List into) - { - for (var a in Scene.All(scope List())) - if (a.IsRiding(this)) - into.Add(a); - return into; - } - - public override void MoveExactX(int amount) - { - if (Collidable) - { - let riders = GetRiders(scope List()); - - X += amount; - Collidable = false; - - for (Actor a in Scene.All(scope List())) - { - if (Check(a)) - { - //Push - int move; - if (amount > 0) - move = Right - a.Left; - else - move = Left - a.Right; - a.MoveExactX(move, scope => a.Squish, this); - } - else if (riders.Contains(a)) - { - //Carry - a.MoveExactX(amount, null, null, this); - } - } - - Collidable = true; - } - else - X += amount; - } - - public override void MoveExactY(int amount) - { - if (Collidable) - { - let riders = GetRiders(scope List()); - - Y += amount; - Collidable = false; - - for (Actor a in Scene.All(scope List())) - { - if (Check(a)) - { - //Push - int move; - if (amount > 0) - move = Bottom - a.Top; - else - move = Top - a.Bottom; - a.MoveExactY(move, scope => a.Squish, this); - } - else if (riders.Contains(a)) - { - //Carry - a.MoveExactY(amount, null, null, this); - } - } - - Collidable = true; - } - else - Y += amount; - } - - public override void Draw() - { - DrawHitbox(.White); - } } } diff --git a/src/Components/Collision/Hitbox.bf b/src/Components/Collision/Hitbox.bf new file mode 100644 index 0000000..c2c725f --- /dev/null +++ b/src/Components/Collision/Hitbox.bf @@ -0,0 +1,288 @@ +using System; +using System.Collections; + +namespace Strawberry +{ + public class Hitbox : Component, IDebugDraw + { + public bool Collidable = true; + public Rect Rect; + + public void DebugDraw() + { + Game.Batcher.Rect(SceneHitbox, .Red); + } + + public int Width + { + [Inline] + get + { + return Rect.Width; + } + + [Inline] + set + { + Rect.Width = value; + } + } + + public int Height + { + [Inline] + get + { + return Rect.Height; + } + + [Inline] + set + { + Rect.Height = value; + } + } + + public Rect SceneHitbox + { + [Inline] + get + { + return Rect + Entity.Position; + } + } + + public int Left + { + [Inline] + get + { + return Entity.X + Rect.Left; + } + + [Inline] + set + { + Entity.X = value - Rect.Left; + } + } + + public int Right + { + [Inline] + get + { + return Entity.X + Rect.Right; + } + + [Inline] + set + { + Entity.X = value - Rect.Right; + } + } + + public int Top + { + [Inline] + get + { + return Entity.Y + Rect.Top; + } + + [Inline] + set + { + Entity.Y = value - Rect.Top; + } + } + + public int Bottom + { + [Inline] + get + { + return Entity.Y + Rect.Bottom; + } + + [Inline] + set + { + Entity.Y = value - Rect.Bottom; + } + } + + // ===== Collisions ===== + + public bool Check(Point point) + { + return SceneHitbox.Contains(point); + } + + public bool Check(Rect rect) + { + return SceneHitbox.Intersects(rect); + } + + public bool Check(Grid grid) + { + return grid != null && grid.Check(SceneHitbox); + } + + public bool Check(Grid grid, Point offset) + { + return grid != null && grid.Check(SceneHitbox + offset); + } + + public bool Check(Hitbox other) + { + return other.Collidable && SceneHitbox.Intersects(other.SceneHitbox); + } + + public bool Check(Hitbox other, Point offset) + { + return other.Collidable && (SceneHitbox + offset).Intersects(other.SceneHitbox); + } + + public bool CheckOutside(Hitbox other, Point offset) + { + return other.Collidable && !SceneHitbox.Intersects(other.SceneHitbox) && (SceneHitbox + offset).Intersects(other.SceneHitbox); + } + + public bool Check(T other) where T : Component, IHasHitbox + { + return Check(other.Hitbox); + } + + public bool Check(T other, Point offset) where T : Component, IHasHitbox + { + return Check(other.Hitbox, offset); + } + + public bool CheckOutside(T other, Point offset) where T : Component, IHasHitbox + { + return CheckOutside(other.Hitbox, offset); + } + + public bool Check() where T : Component, IHasHitbox + { + for (var e in Scene.All(scope List())) + if (Check(e.Hitbox)) + return true; + + return false; + } + + public bool Check(Point offset) where T : Component, IHasHitbox + { + for (var e in Scene.All(scope List())) + if (Check(e.Hitbox, offset)) + return true; + + return false; + } + + public bool CheckOutside(Point offset) where T : Component, IHasHitbox + { + for (var e in Scene.All(scope List())) + if (CheckOutside(e.Hitbox, offset)) + return true; + + return false; + } + + public T First() where T : Component, IHasHitbox + { + for (var e in Scene.All(scope List())) + if (Check(e.Hitbox)) + return e; + + return null; + } + + public T First(Point offset) where T : Component, IHasHitbox + { + for (var e in Scene.All(scope List())) + if (Check(e.Hitbox, offset)) + return e; + + return null; + } + + public T FirstOutside(Point offset) where T : Component, IHasHitbox + { + for (var e in Scene.All(scope List())) + if (CheckOutside(e.Hitbox, offset)) + return e; + + return null; + } + + public T LeftmostOutside(Point offset) where T : Component, IHasHitbox + { + T ret = null; + for (var e in Scene.All(scope List())) + if (CheckOutside(e.Hitbox, offset) && (ret == null || e.Hitbox.Left < ret.Hitbox.Left)) + ret = e; + + return ret; + } + + public T RightmostOutside(Point offset) where T : Component, IHasHitbox + { + T ret = null; + for (var e in Scene.All(scope List())) + if (CheckOutside(e.Hitbox, offset) && (ret == null || e.Hitbox.Right > ret.Hitbox.Right)) + ret = e; + + return ret; + } + + public T TopmostOutside(Point offset) where T : Component, IHasHitbox + { + T ret = null; + for (var e in Scene.All(scope List())) + if (CheckOutside(e.Hitbox, offset) && (ret == null || e.Hitbox.Top < ret.Hitbox.Top)) + ret = e; + + return ret; + } + + public T BottommostOutside(Point offset) where T : Component, IHasHitbox + { + T ret = null; + for (var e in Scene.All(scope List())) + if (CheckOutside(e.Hitbox, offset) && (ret == null || e.Hitbox.Bottom > ret.Hitbox.Bottom)) + ret = e; + + return ret; + } + + public List All(List into) where T : Component, IHasHitbox + { + for (var e in Scene.All(scope List())) + if (Check(e.Hitbox)) + into.Add(e); + + return into; + } + + public List All(Point offset, List into) where T : Component, IHasHitbox + { + for (var e in Scene.All(scope List())) + if (Check(e.Hitbox, offset)) + into.Add(e); + + return into; + } + + public List AllOutside(Point offset, List into) where T : Component, IHasHitbox + { + for (var e in Scene.All(scope List())) + if (CheckOutside(e.Hitbox, offset)) + into.Add(e); + + return into; + } + } +} diff --git a/src/Components/Logic/OnCollide.bf b/src/Components/Collision/OnCollide.bf similarity index 59% rename from src/Components/Logic/OnCollide.bf rename to src/Components/Collision/OnCollide.bf index f6cee9d..4795cf2 100644 --- a/src/Components/Logic/OnCollide.bf +++ b/src/Components/Collision/OnCollide.bf @@ -1,14 +1,16 @@ using System.Collections; namespace Strawberry { - public class OnCollide : Component where T : Entity + public class OnCollide : Component, IHasHitbox, IUpdate where T : Component, IHasHitbox { + public Hitbox Hitbox { get; private set; } + // Takes as parameter the T collided with. Return false to stop checking for collisions until next frame. public delegate bool(T) Action; - public this(delegate bool(T) action) - : base(true, false) + public this(Hitbox hitbox, delegate bool(T) action) { + Hitbox = hitbox; Action = action; } @@ -17,13 +19,13 @@ namespace Strawberry delete Action; } - public override void Update() + public void Update() { if (Action != null) { let list = Entity.Scene.All(scope List()); for (let t in list) - if (Entity.Check(t) && !Action(t)) + if (Hitbox.Check(t) && !Action(t)) break; } } diff --git a/src/Components/Drawing/DrawHitbox.bf b/src/Components/Drawing/DrawHitbox.bf new file mode 100644 index 0000000..8151eb5 --- /dev/null +++ b/src/Components/Drawing/DrawHitbox.bf @@ -0,0 +1,19 @@ +namespace Strawberry +{ + public class DrawHitbox : Component, IHasHitbox, IDraw + { + public Hitbox Hitbox { get; private set; } + public Color Color; + + public this(Hitbox hitbox, Color color) + { + Hitbox = hitbox; + Color = color; + } + + public void Draw() + { + Game.Batcher.Rect(Hitbox.SceneHitbox, Color); + } + } +} diff --git a/src/Components/Interfaces/IDebugDraw.bf b/src/Components/Interfaces/IDebugDraw.bf new file mode 100644 index 0000000..1656a03 --- /dev/null +++ b/src/Components/Interfaces/IDebugDraw.bf @@ -0,0 +1,8 @@ +namespace Strawberry +{ + [ComponentInterface] + public interface IDebugDraw + { + public void DebugDraw(); + } +} diff --git a/src/Components/Interfaces/IDraw.bf b/src/Components/Interfaces/IDraw.bf new file mode 100644 index 0000000..e89a4d2 --- /dev/null +++ b/src/Components/Interfaces/IDraw.bf @@ -0,0 +1,8 @@ +namespace Strawberry +{ + [ComponentInterface] + public interface IDraw + { + public void Draw(); + } +} diff --git a/src/Components/Interfaces/IHasHitbox.bf b/src/Components/Interfaces/IHasHitbox.bf new file mode 100644 index 0000000..cc86eb2 --- /dev/null +++ b/src/Components/Interfaces/IHasHitbox.bf @@ -0,0 +1,8 @@ +namespace Strawberry +{ + [ComponentInterface] + public interface IHasHitbox + { + public Hitbox Hitbox { get; } + } +} diff --git a/src/Components/Interfaces/ILateUpdate.bf b/src/Components/Interfaces/ILateUpdate.bf new file mode 100644 index 0000000..01bf170 --- /dev/null +++ b/src/Components/Interfaces/ILateUpdate.bf @@ -0,0 +1,8 @@ +namespace Strawberry +{ + [ComponentInterface] + public interface ILateUpdate + { + public void LateUpdate(); + } +} diff --git a/src/Components/Interfaces/IUpdate.bf b/src/Components/Interfaces/IUpdate.bf new file mode 100644 index 0000000..b04fdac --- /dev/null +++ b/src/Components/Interfaces/IUpdate.bf @@ -0,0 +1,8 @@ +namespace Strawberry +{ + [ComponentInterface] + public interface IUpdate + { + public void Update(); + } +} diff --git a/src/Components/Logic/StateMachine.bf b/src/Components/Logic/StateMachine.bf index 9cb060d..81b02ea 100644 --- a/src/Components/Logic/StateMachine.bf +++ b/src/Components/Logic/StateMachine.bf @@ -4,7 +4,7 @@ using System.Diagnostics; namespace Strawberry { - public class StateMachine : Component where TIndex : struct, IHashable + public class StateMachine : Component, IUpdate where TIndex : struct, IHashable { private Dictionary states = new Dictionary() ~ delete _; private TIndex state; @@ -14,7 +14,6 @@ namespace Strawberry public TIndex NextState { get; private set; } public this(TIndex startState) - : base(true, false) { NextState = PreviousState = state = startState; } @@ -30,7 +29,7 @@ namespace Strawberry CallEnter(); } - public override void Update() + public void Update() { CallUpdate(); } diff --git a/src/Components/Logic/Timer.bf b/src/Components/Logic/Timer.bf index 76cd254..89b7866 100644 --- a/src/Components/Logic/Timer.bf +++ b/src/Components/Logic/Timer.bf @@ -3,35 +3,27 @@ using System.Diagnostics; namespace Strawberry { - public class Timer : Component + public class Timer : Component, IUpdate { private float value; public Action OnComplete ~ delete _; public bool RemoveOnComplete; - public this() - : base(false, false) - { - - } - public this(Action onComplete, bool destroyOnComplete = false) - : base(false, false) { OnComplete = onComplete; RemoveOnComplete = destroyOnComplete; } public this(float value, Action onComplete, bool destroyOnComplete = false) - : base(false, false) { Value = value; OnComplete = onComplete; RemoveOnComplete = destroyOnComplete; } - public override void Update() + public void Update() { if (value > 0) { @@ -39,8 +31,6 @@ namespace Strawberry if (value <= 0) { value = 0; - Active = false; - OnComplete?.Invoke(); if (RemoveOnComplete) RemoveSelf(); @@ -60,7 +50,6 @@ namespace Strawberry set { this.value = Math.Max(0, value); - Active = (this.value > 0); } } @@ -68,7 +57,6 @@ namespace Strawberry public void Clear() { value = 0; - Active = false; } static public implicit operator bool(Timer timer) diff --git a/src/Components/Logic/Tween.bf b/src/Components/Logic/Tween.bf index 6790cb5..1b4ab03 100644 --- a/src/Components/Logic/Tween.bf +++ b/src/Components/Logic/Tween.bf @@ -1,33 +1,42 @@ using System; namespace Strawberry { - public class Tween : Component + public class Tween : Component, IUpdate { public Ease.Easer Easer ~ delete _; public delegate void(float) OnUpdate ~ delete _; public delegate void() OnComplete ~ delete _; public bool RemoveOnComplete; + public bool Playing { get; private set; } public float T { get; private set; } public this(Ease.Easer easer = null, delegate void(float) onUpdate = null, delegate void() onComplete = null, bool removeOnComplete = true, bool start = true) - : base(start, false) { + Playing = start; Easer = easer; OnUpdate = onUpdate; OnComplete = onComplete; RemoveOnComplete = removeOnComplete; } + [Inline] public float Eased => Easer != null ? Easer(T) : T; + [Inline] public void Play() { T = 0; - Active = true; + Playing = true; } - public override void Update() + [Inline] + public void Stop() + { + Playing = false; + } + + public void Update() { T = Math.Min(T + Time.Delta, 1); OnUpdate?.Invoke(Eased); @@ -35,7 +44,7 @@ namespace Strawberry if (T >= 1) { OnComplete?.Invoke(); - Active = false; + Playing = false; if (RemoveOnComplete) RemoveSelf(); } diff --git a/src/Core/Component.bf b/src/Core/Component.bf index 814e7fc..24aef0e 100644 --- a/src/Core/Component.bf +++ b/src/Core/Component.bf @@ -6,15 +6,6 @@ namespace Strawberry { public Entity Entity { get; private set; } - public bool Active; - public bool Visible; - - public this(bool active, bool visible) - { - Active = active; - Visible = visible; - } - private void Added(Entity entity) { Entity = entity; @@ -27,13 +18,20 @@ namespace Strawberry public virtual void Started() { } public virtual void Ended() { } - public virtual void Update() { } - public virtual void Draw() { } [Inline] public void RemoveSelf() { Entity?.Remove(this); } + + [Inline] + public Scene Scene => Entity?.Scene; + + [Inline] + public T SceneAs() where T : Scene + { + return Entity.SceneAs(); + } } } diff --git a/src/Core/ComponentInterfaceAttribute.bf b/src/Core/ComponentInterfaceAttribute.bf new file mode 100644 index 0000000..613ad4b --- /dev/null +++ b/src/Core/ComponentInterfaceAttribute.bf @@ -0,0 +1,9 @@ +using System; + +namespace Strawberry +{ + public struct ComponentInterfaceAttribute : Attribute + { + + } +} diff --git a/src/Core/Entity.bf b/src/Core/Entity.bf index 7a3173a..f1bfa3e 100644 --- a/src/Core/Entity.bf +++ b/src/Core/Entity.bf @@ -3,13 +3,9 @@ using System.Collections; namespace Strawberry { - public abstract class Entity + public sealed class Entity { public Scene Scene { get; private set; } - public int Priority; - public bool Active = true; - public bool Visible = true; - public bool Collidable = true; public bool DeleteOnRemove = true; private List components = new List() ~ delete _; @@ -40,32 +36,18 @@ namespace Strawberry Scene = null; } - public virtual void Started() + public void Started() { for (var c in components) c.Started(); } - public virtual void Ended() + public void Ended() { for (var c in components) c.Ended(); } - public virtual void Update() - { - for (var c in components) - if (c.Active) - c.Update(); - } - - public virtual void Draw() - { - for (var c in components) - if (c.Visible) - c.Draw(); - } - [Inline] public void RemoveSelf() { @@ -195,302 +177,13 @@ namespace Strawberry } } - // ===== Hitbox ===== - - public Rect Hitbox; - - public int Width - { - [Inline] - get - { - return Hitbox.Width; - } - - [Inline] - set - { - Hitbox.Width = value; - } - } - - public int Height - { - [Inline] - get - { - return Hitbox.Height; - } - - [Inline] - set - { - Hitbox.Height = value; - } - } - - public Rect SceneHitbox - { - [Inline] - get - { - return Hitbox + Position; - } - } - - public Rect SceneHitboxOutline - { - [Inline] - get - { - Rect hb = Hitbox + Position; - hb.X -= 1; - hb.Y -= 1; - hb.Width += 2; - hb.Height += 2; - return hb; - } - } - - public int Left - { - [Inline] - get - { - return Position.X + Hitbox.Left; - } - - [Inline] - set - { - X = value - Hitbox.Left; - } - } - - public int Right - { - [Inline] - get - { - return Position.X + Hitbox.Right; - } - - [Inline] - set - { - Y = value - Hitbox.Right; - } - } - - public int Top - { - [Inline] - get - { - return Position.Y + Hitbox.Top; - } - - [Inline] - set - { - Y = value - Hitbox.Top; - } - } - - public int Bottom - { - [Inline] - get - { - return Position.Y + Hitbox.Bottom; - } - - [Inline] - set - { - Y = value - Hitbox.Bottom; - } - } - - // ===== Collisions ===== - - public bool Check(Point point) - { - return SceneHitbox.Contains(point); - } - - public bool Check(Rect rect) - { - return SceneHitbox.Intersects(rect); - } - - public bool Check(Grid grid) - { - return grid != null && grid.Check(SceneHitbox); - } - - public bool Check(Grid grid, Point offset) - { - return grid != null && grid.Check(SceneHitbox + offset); - } - - public bool Check(Entity other) - { - return other.Collidable && SceneHitbox.Intersects(other.SceneHitbox); - } - - public bool Check(Entity other, Point offset) - { - return other.Collidable && (SceneHitbox + offset).Intersects(other.SceneHitbox); - } - - public bool CheckOutside(Entity other, Point offset) - { - return other.Collidable && !SceneHitbox.Intersects(other.SceneHitbox) && (SceneHitbox + offset).Intersects(other.SceneHitbox); - } - - public bool Check() where T : Entity - { - for (var e in Scene.All(scope List())) - if (Check(e)) - return true; - - return false; - } - - public bool Check(Point offset) where T : Entity - { - for (var e in Scene.All(scope List())) - if (Check(e, offset)) - return true; - - return false; - } - - public bool CheckOutside(Point offset) where T : Entity - { - for (var e in Scene.All(scope List())) - if (CheckOutside(e, offset)) - return true; - - return false; - } - - public T First() where T : Entity - { - for (var e in Scene.All(scope List())) - if (Check(e)) - return e; - - return null; - } - - public T First(Point offset) where T : Entity - { - for (var e in Scene.All(scope List())) - if (Check(e, offset)) - return e; - - return null; - } - - public T FirstOutside(Point offset) where T : Entity - { - for (var e in Scene.All(scope List())) - if (CheckOutside(e, offset)) - return e; - - return null; - } - - public T LeftmostOutside(Point offset) where T : Entity - { - T ret = null; - for (var e in Scene.All(scope List())) - if (CheckOutside(e, offset) && (ret == null || e.Left < ret.Left)) - ret = e; - - return ret; - } - - public T RightmostOutside(Point offset) where T : Entity - { - T ret = null; - for (var e in Scene.All(scope List())) - if (CheckOutside(e, offset) && (ret == null || e.Right > ret.Right)) - ret = e; - - return ret; - } - - public T TopmostOutside(Point offset) where T : Entity - { - T ret = null; - for (var e in Scene.All(scope List())) - if (CheckOutside(e, offset) && (ret == null || e.Top < ret.Top)) - ret = e; - - return ret; - } - - public T BottommostOutside(Point offset) where T : Entity - { - T ret = null; - for (var e in Scene.All(scope List())) - if (CheckOutside(e, offset) && (ret == null || e.Bottom > ret.Bottom)) - ret = e; - - return ret; - } - - public List All(List into) where T : Entity - { - for (var e in Scene.All(scope List())) - if (Check(e)) - into.Add(e); - - return into; - } - - public List All(Point offset, List into) where T : Entity - { - for (var e in Scene.All(scope List())) - if (Check(e, offset)) - into.Add(e); - - return into; - } - - public List AllOutside(Point offset, List into) where T : Entity - { - for (var e in Scene.All(scope List())) - if (CheckOutside(e, offset)) - into.Add(e); - - return into; - } - // ===== Misc ===== - public void DrawHitbox(Color color) - { - Game.Batcher.Rect(SceneHitbox, color); - } - - public void DrawHitboxOutline(Color color) - { - Game.Batcher.Rect(SceneHitboxOutline, color); - } - + [Inline] public T SceneAs() where T : Scene { Runtime.Assert(Scene is T, "Scene type mismatch!"); return Scene as T; } - - static public int Compare(Entity a, Entity b) - { - return a.Priority <=> b.Priority; - } } } diff --git a/src/Core/Game.bf b/src/Core/Game.bf index e9c2a00..cde21e0 100644 --- a/src/Core/Game.bf +++ b/src/Core/Game.bf @@ -25,8 +25,6 @@ namespace Strawberry private Scene scene; private Scene switchToScene; private bool updating; - private Dictionary> entityAssignableLists; - private Dictionary> componentAssignableLists; public PlatformLayer PlatformLayer { get; private set; } public Batcher Batcher { get; private set; } @@ -60,9 +58,9 @@ namespace Strawberry VirtualInputs = new List(); Input.[Friend]Init(); - BuildTypeLists(); + Tracker.[Friend]BuildAssignmentLists(); Assets.LoadAll(); - Strawberry.Console.Init(); + Strawberry.StrwConsole.Init(); } public ~this() @@ -83,9 +81,8 @@ namespace Strawberry } Assets.DisposeAll(); - DisposeTypeLists(); Input.[Friend]Dispose(); - Strawberry.Console.Dispose(); + Strawberry.StrwConsole.Dispose(); delete Batcher; @@ -157,7 +154,7 @@ namespace Strawberry Time.Elapsed += Time.Delta; } - Strawberry.Console.[Friend]Update(); + Strawberry.StrwConsole.[Friend]Update(); } private void Render() @@ -171,8 +168,8 @@ namespace Strawberry { Scene?.Draw(); - if (Strawberry.Console.Enabled) - Strawberry.Console.[Friend]Draw(); + if (Strawberry.StrwConsole.Enabled) + Strawberry.StrwConsole.[Friend]Draw(); Batcher.Draw(); } @@ -191,57 +188,5 @@ namespace Strawberry switchToScene = value; } } - - // Type assignable caching - - private void BuildTypeLists() - { - /* - For each Type that extends Entity, we build a list of all the other Entity Types that it is assignable to. - We cache these lists, and use them later to bucket Entities as they are added to the Scene. - This allows us to retrieve Entities by type very easily. - */ - - entityAssignableLists = new Dictionary>(); - for (let type in Type.Enumerator()) - { - if (type != typeof(Entity) && type.IsSubtypeOf(typeof(Entity))) - { - let list = new List(); - for (let check in Type.Enumerator()) - if (check != typeof(Entity) && check.IsSubtypeOf(typeof(Entity)) && type.IsSubtypeOf(check)) - list.Add(check); - entityAssignableLists.Add(type, list); - } - } - - /* - And then we also do this for components - */ - - componentAssignableLists = new Dictionary>(); - for (let type in Type.Enumerator()) - { - if (type != typeof(Component) && type.IsSubtypeOf(typeof(Component))) - { - let list = new List(); - for (let check in Type.Enumerator()) - if (check != typeof(Component) && check.IsSubtypeOf(typeof(Component)) && type.IsSubtypeOf(check)) - list.Add(check); - componentAssignableLists.Add(type, list); - } - } - } - - private void DisposeTypeLists() - { - for (let list in entityAssignableLists.Values) - delete list; - delete entityAssignableLists; - - for (let list in componentAssignableLists.Values) - delete list; - delete componentAssignableLists; - } } } diff --git a/src/Core/Scene.bf b/src/Core/Scene.bf index a9a7909..d3e3255 100644 --- a/src/Core/Scene.bf +++ b/src/Core/Scene.bf @@ -10,7 +10,6 @@ namespace Strawberry private List entities; private HashSet toRemove; private HashSet toAdd; - private Dictionary> entityTracker; private Dictionary> componentTracker; public this() @@ -19,12 +18,10 @@ namespace Strawberry toAdd = new HashSet(); toRemove = new HashSet(); - entityTracker = new Dictionary>(); - for (let type in Game.[Friend]entityAssignableLists.Keys) - entityTracker.Add(type, new List()); - componentTracker = new Dictionary>(); - for (let type in Game.[Friend]componentAssignableLists.Keys) + for (let type in Tracker.AssignmentLists.Keys) + componentTracker.Add(type, new List()); + for (let type in Tracker.Interfaces) componentTracker.Add(type, new List()); } @@ -42,10 +39,6 @@ namespace Strawberry delete toRemove; - for (let list in entityTracker.Values) - delete list; - delete entityTracker; - for (let list in componentTracker.Values) delete list; delete componentTracker; @@ -59,26 +52,23 @@ namespace Strawberry public virtual void Update() { UpdateLists(); - for (let e in entities) - if (e.Active) - e.Update(); + + } public virtual void Draw() { - for (let e in entities) - if (e.Visible) - e.Draw(); + } - public T Add(T e) where T : Entity + public Entity Add(Entity e) { if (e.Scene == null) toAdd.Add(e); return e; } - public T Remove(T e) where T : Entity + public Entity Remove(Entity e) { if (e.Scene == this) toRemove.Add(e); @@ -92,7 +82,6 @@ namespace Strawberry for (let e in toRemove) { entities.Remove(e); - UntrackEntity(e); e.[Friend]Removed(); if (e.DeleteOnRemove) delete e; @@ -106,11 +95,8 @@ namespace Strawberry for (let e in toAdd) { entities.Add(e); - TrackEntity(e); e.[Friend]Added(this); } - - entities.Sort(scope => Entity.Compare); } for (let e in entities) @@ -126,33 +112,15 @@ namespace Strawberry // Tracking - private void TrackEntity(Entity e) - { - for (let t in Game.[Friend]entityAssignableLists[e.GetType()]) - entityTracker[t].Add(e); - - for (let c in e.[Friend]components) - TrackComponent(c); - } - - private void UntrackEntity(Entity e) - { - for (let t in Game.[Friend]entityAssignableLists[e.GetType()]) - entityTracker[t].Remove(e); - - for (let c in e.[Friend]components) - UntrackComponent(c); - } - private void TrackComponent(Component c) { - for (let t in Game.[Friend]componentAssignableLists[c.GetType()]) + for (let t in Tracker.AssignmentLists[c.GetType()]) componentTracker[t].Add(c); } private void UntrackComponent(Component c) { - for (let t in Game.[Friend]componentAssignableLists[c.GetType()]) + for (let t in Tracker.AssignmentLists[c.GetType()]) componentTracker[t].Remove(c); } @@ -171,75 +139,6 @@ namespace Strawberry return (TimeElapsed - offset) % (interval * 2) >= interval; } - // Finding Entities - - public int Count() where T : Entity - { - return entityTracker[typeof(T)].Count; - } - - public bool Check(Point point) where T : Entity - { - for (let e in entityTracker[typeof(T)]) - if (e.Check(point)) - return true; - return false; - } - - public bool Check(Rect rect) where T : Entity - { - for (let e in entityTracker[typeof(T)]) - if (e.Check(rect)) - return true; - return false; - } - - public T First() where T : Entity - { - for (let e in entityTracker[typeof(T)]) - return e as T; - return null; - } - - public T First(Point point) where T : Entity - { - for (let e in entityTracker[typeof(T)]) - if (e.Check(point)) - return e as T; - return null; - } - - public T First(Rect rect) where T : Entity - { - for (let e in entityTracker[typeof(T)]) - if (e.Check(rect)) - return e as T; - return null; - } - - public List All(List into) where T : Entity - { - for (let e in entityTracker[typeof(T)]) - into.Add(e as T); - return into; - } - - public List All(Point point, List into) where T : Entity - { - for (let e in entityTracker[typeof(T)]) - if (e.Check(point)) - into.Add(e as T); - return into; - } - - public List All(Rect rect, List into) where T : Entity - { - for (let e in entityTracker[typeof(T)]) - if (e.Check(rect)) - into.Add(e as T); - return into; - } - /* Finding Components */ @@ -249,25 +148,64 @@ namespace Strawberry return componentTracker[typeof(T)].Count; } - public List All(List into) where T : Component + public bool Check(Point point) where T : Component, IHasHitbox { - for (let c in componentTracker[typeof(T)]) + for (T c in componentTracker[typeof(T)]) + if (c.Hitbox.Check(point)) + return true; + return false; + } + + public bool Check(Rect rect) where T : Component, IHasHitbox + { + for (T c in componentTracker[typeof(T)]) + if (c.Hitbox.Check(rect)) + return true; + return false; + } + + public T First() where T : Component, IHasHitbox + { + for (T c in componentTracker[typeof(T)]) + return c; + return null; + } + + public T First(Point point) where T : Component, IHasHitbox + { + for (T c in componentTracker[typeof(T)]) + if (c.Hitbox.Check(point)) + return c as T; + return null; + } + + public T First(Rect rect) where T : Component, IHasHitbox + { + for (T c in componentTracker[typeof(T)]) + if (c.Hitbox.Check(rect)) + return c as T; + return null; + } + + public List All(List into) where T : Component, IHasHitbox + { + for (T c in componentTracker[typeof(T)]) into.Add(c as T); return into; } - public List All(Point point, List into) where T : Component + public List All(Point point, List into) where T : Component, IHasHitbox { - for (let c in componentTracker[typeof(T)]) - if (c.Entity.Check(point)) + for (T c in componentTracker[typeof(T)]) + if (c.Hitbox.Check(point)) into.Add(c as T); return into; } - public List All(Rect rect, List into) where T : Component + public List All(Rect rect, List into) where T : Component, IHasHitbox { - for (let c in componentTracker[typeof(T)]) - if (c.Entity.Check(rect)) + for (T c in componentTracker[typeof(T)]) + if (c.Hitbox.Check(rect)) into.Add(c as T); return into; } diff --git a/src/Physics/Grid.bf b/src/Physics/Grid.bf index a179d26..80b2fdf 100644 --- a/src/Physics/Grid.bf +++ b/src/Physics/Grid.bf @@ -143,9 +143,14 @@ namespace Strawberry return p.X >= 0 && p.Y >= 0 && p.X < CellsX && p.Y < CellsY; } - public bool Check(Entity entity) + public bool Check(Hitbox hitbox) { - return Check(entity.SceneHitbox); + return Check(hitbox.SceneHitbox); + } + + public bool Check(IHasHitbox other) + { + return Check(other.Hitbox.SceneHitbox); } public bool Check(Rect rect) diff --git a/src/Static/Console.bf b/src/Static/StrwConsole.bf similarity index 99% rename from src/Static/Console.bf rename to src/Static/StrwConsole.bf index 9cf19f4..1ed04d0 100644 --- a/src/Static/Console.bf +++ b/src/Static/StrwConsole.bf @@ -19,7 +19,7 @@ namespace Strawberry } [Reflect] - static public class Console + static public class StrwConsole { static public bool Open; diff --git a/src/Static/Tracker.bf b/src/Static/Tracker.bf new file mode 100644 index 0000000..9ca1491 --- /dev/null +++ b/src/Static/Tracker.bf @@ -0,0 +1,38 @@ +using System.Collections; +using System; +namespace Strawberry +{ + static public class Tracker + { + static public Dictionary> AssignmentLists = new .() ~ DeleteDictionaryAndValues!(_); + static public List Interfaces = new .() ~ delete _; + + static private void BuildAssignmentLists() + { + // Find all interfaces with ComponentInterfaceAttribute + for (let type in Type.Enumerator()) + if (type.IsInterface && type.HasCustomAttribute()) + Interfaces.Add(type); + + /* + For each Type that extends Component, we build a list of all the tracked Interfaces it implements. + We use these lists later to bucket Components as they are added to the Scene. + This allows us to retrieve Components by their type or by any of their implemented interface types. + */ + + for (let type in Type.Enumerator()) + { + if (type != typeof(Component) && type.IsSubtypeOf(typeof(Component))) + { + let list = new List(); + list.Add(type); + for (let check in Interfaces) + if (type.IsSubtypeOf(check)) + list.Add(check); + + AssignmentLists.Add(type, list); + } + } + } + } +}