Big restructuring - sample game is currently broken

This commit is contained in:
Maddy Thorson 2021-02-04 23:11:51 -08:00
parent 05c79b296e
commit 3d5130b45b
28 changed files with 551 additions and 866 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
SampleGame/build/* SampleGame/build/*
SampleGame/BeefSpace_User.toml SampleGame/BeefSpace_User.toml
build/* build/*
SampleGame/recovery/*

View File

@ -7,8 +7,8 @@ namespace Strawberry.Sample
public this() public this()
{ {
Add(new Player(.(50, 50))); Add(new Player(.(50, 50)));
Add(new Solid(.(0, 168), .(0, 0, 320, 12))); Add(new OldSolid(.(0, 168), .(0, 0, 320, 12)));
Add(new JumpThru(.(200, 132), 48)); Add(new OldJumpThru(.(200, 132), 48));
Add(new MovingJumpThru(.(136, 100), 32, .(124, 140), 2f)); Add(new MovingJumpThru(.(136, 100), 32, .(124, 140), 2f));
} }
} }

View File

@ -1,6 +1,6 @@
namespace Strawberry.Sample namespace Strawberry.Sample
{ {
public class MovingJumpThru : JumpThru public class MovingJumpThru : OldJumpThru
{ {
private Point moveFrom; private Point moveFrom;
private Point moveTo; private Point moveTo;

View File

@ -2,56 +2,49 @@ using System;
namespace Strawberry.Sample namespace Strawberry.Sample
{ {
[Reflect] public class Physics : Component, IHasHitbox, IUpdate
public class Actor : Entity
{ {
public Hitbox Hitbox { get; private set; }
public Vector Speed;
private Vector remainder; private Vector remainder;
// The amount that geometry has pushed or carried this Actor since the last frame public this(Hitbox hitbox)
public Point MovedByGeometry { get; private set; }
public this(Point position)
: base(position)
{ {
Hitbox = hitbox;
} }
public Level Level => SceneAs<Level>(); public Level Level => Entity.SceneAs<Level>();
public bool Check(Level level) 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) 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) public bool GroundCheck(int distance = 1)
{ {
return Check<Solid>(.(0, distance)) || Check(Level, .(0, distance)) || CheckOutside<JumpThru>(.(0, distance)); return Hitbox.Check<Solid>(.(0, distance)) || Check(Level, .(0, distance)) || Hitbox.CheckOutside<JumpThru>(.(0, distance));
} }
public virtual bool IsRiding(Solid solid) public virtual bool IsRiding(Solid solid)
{ {
return Check(solid, .(0, 1)); return Hitbox.Check(solid, .(0, 1));
} }
public virtual bool IsRiding(JumpThru jumpThru) 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(); MoveX(Speed.X * Time.Delta);
} MoveY(Speed.Y * Time.Delta);
public override void Update()
{
base.Update();
MovedByGeometry = Point.Zero;
} }
public bool MoveX(float amount, delegate void(Collision) onCollide = null) public bool MoveX(float amount, delegate void(Collision) onCollide = null)
@ -83,130 +76,85 @@ namespace Strawberry.Sample
[Inline] [Inline]
public void MoveToX(float x) public void MoveToX(float x)
{ {
MoveX(x - (X + remainder.X), null); MoveX(x - (Entity.X + remainder.X), null);
} }
[Inline] [Inline]
public void MoveToY(float y) 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 move = amount;
int sign = Math.Sign(amount); int sign = Math.Sign(amount);
bool byGeometry = carrier != null || pusher != null;
while (move != 0) while (move != 0)
{ {
let hit = First<Solid>(.(sign, 0)); if (Check(Level, .(sign, 0)) || Hitbox.Check<Solid>(.(sign, 0)))
if (hit != null)
{ {
let c = Collision( let c = Collision(
Cardinals.FromPoint(Point.Right * sign), Cardinals.FromPoint(Point.Right * sign),
Math.Abs(amount), Math.Abs(amount),
Math.Abs(amount - move), Math.Abs(amount - move)
hit,
pusher
); );
onCollide?.Invoke(c); onCollide?.Invoke(c);
return true; return true;
} }
if (Check(Level, .(sign, 0))) Entity.X += sign;
{
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;
move -= sign; move -= sign;
} }
return false; 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 move = amount;
int sign = Math.Sign(amount); int sign = Math.Sign(amount);
bool byGeometry = carrier != null || pusher != null;
while (move != 0) while (move != 0)
{ {
Geometry hit = First<Solid>(.(0, sign)); if (Check(Level, .(0, sign)) || Hitbox.Check<Solid>(.(0, sign)) || Hitbox.CheckOutside<JumpThru>(.(0, sign)))
if (hit == null && sign == 1)
hit = FirstOutside<JumpThru>(.(0, sign));
if (hit != null)
{ {
let c = Collision( let c = Collision(
Cardinals.FromPoint(Point.Down * sign), Cardinals.FromPoint(Point.Down * sign),
Math.Abs(amount), Math.Abs(amount),
Math.Abs(amount - move), Math.Abs(amount - move)
hit,
pusher
); );
onCollide?.Invoke(c); onCollide?.Invoke(c);
return true; return true;
} }
if (Check(Level, .(0, sign))) Entity.Y += 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;
move -= sign; move -= sign;
} }
return false; return false;
} }
[Inline]
public void ZeroRemainderX() public void ZeroRemainderX()
{ {
remainder.X = 0; remainder.X = 0;
} }
[Inline]
public void ZeroRemainderY() public void ZeroRemainderY()
{ {
remainder.Y = 0; remainder.Y = 0;
} }
[Inline]
public void ZeroRemainders() public void ZeroRemainders()
{ {
remainder = Vector.Zero; remainder = Vector.Zero;
} }
private void MoveByGeometry(Point amount)
{
MovedByGeometry += amount;
}
public bool CornerCorrection(Cardinals direction, int maxAmount, int lookAhead = 1, int onlySign = 0) public bool CornerCorrection(Cardinals direction, int maxAmount, int lookAhead = 1, int onlySign = 0)
{ {
Point dir = direction; Point dir = direction;
@ -215,10 +163,10 @@ namespace Strawberry.Sample
perp.Y = Math.Abs(perp.Y); perp.Y = Math.Abs(perp.Y);
delegate bool(Point) checker; delegate bool(Point) checker;
if (dir == Point.Down) if (dir == Point.Down)
checker = scope:: (p) => !Check(Level, p) && !Check<Solid>(p) && !CheckOutside<JumpThru>(p); checker = scope:: (p) => !Check(Level, p) && !Hitbox.Check<Solid>(p) && !Hitbox.CheckOutside<JumpThru>(p);
else else
checker = scope:: (p) => !Check(Level, p) && !Check<Solid>(p); checker = scope:: (p) => !Check(Level, p) && !Hitbox.Check<Solid>(p);
for (int i = 1; i <= maxAmount; i++) for (int i = 1; i <= maxAmount; i++)
{ {
@ -230,7 +178,7 @@ namespace Strawberry.Sample
let offset = dir * lookAhead + perp * i * j; let offset = dir * lookAhead + perp * i * j;
if (checker(offset)) if (checker(offset))
{ {
Position += offset; Entity.Position += offset;
return true; return true;
} }
} }

View File

@ -2,7 +2,7 @@ using System;
namespace Strawberry.Sample namespace Strawberry.Sample
{ {
public class Player : Actor public class Player : Component, IUpdate
{ {
public Vector Speed; public Vector Speed;
@ -18,10 +18,8 @@ namespace Strawberry.Sample
Add(tVarJump = new Timer()); 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 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 varJumpTime = 0.2f; // Time after jumping that you can hold the jump button to continue gaining upward speed
const float jumpSpeed = -160; const float jumpSpeed = -160;

View File

@ -6,16 +6,12 @@ namespace Strawberry.Sample
public Cardinals Direction; public Cardinals Direction;
public int Magnitude; public int Magnitude;
public int Completed; 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; Direction = direction;
Magnitude = magnitude; Magnitude = magnitude;
Completed = completed; Completed = completed;
Stopper = stopper;
Pusher = pusher;
} }
} }
} }

View File

@ -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<Actor> GetRiders(List<Actor> into);
public void ZeroRemainderX()
{
remainder.X = 0;
}
public void ZeroRemainderY()
{
remainder.Y = 0;
}
public void ZeroRemainders()
{
remainder = Vector.Zero;
}
}
}

View File

@ -1,80 +1,14 @@
using System.Collections; using System.Collections;
using System;
namespace Strawberry.Sample namespace Strawberry.Sample
{ {
public class JumpThru : Geometry public class JumpThru : Component, IHasHitbox
{ {
public this(Point position, int width) public Hitbox Hitbox { get; private set; }
: base(position)
public this(Hitbox hitbox)
{ {
Hitbox = Rect(0, 0, width, 2); Hitbox = hitbox;
}
public this(JSON json)
: this(.(json), json["width"])
{
}
public override void MoveExactX(int amount)
{
if (Collidable)
{
let riders = GetRiders(scope List<Actor>());
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<Actor>());
if (amount < 0)
{
for (var a in Scene.All<Actor>(scope List<Actor>()))
{
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<Actor> GetRiders(List<Actor> into)
{
for (var a in Scene.All<Actor>(scope List<Actor>()))
if (a.IsRiding(this))
into.Add(a);
return into;
}
public override void Draw()
{
DrawHitbox(.LightGray);
} }
} }
} }

View File

@ -2,93 +2,13 @@ using System.Collections;
namespace Strawberry.Sample namespace Strawberry.Sample
{ {
public class Solid : Geometry public class Solid : Component, IHasHitbox
{ {
public this(Point position, Rect hitbox) public Hitbox Hitbox { get; private set; }
: base(position)
public this(Hitbox hitbox)
{ {
Hitbox = hitbox; Hitbox = hitbox;
} }
public override List<Actor> GetRiders(List<Actor> into)
{
for (var a in Scene.All<Actor>(scope List<Actor>()))
if (a.IsRiding(this))
into.Add(a);
return into;
}
public override void MoveExactX(int amount)
{
if (Collidable)
{
let riders = GetRiders(scope List<Actor>());
X += amount;
Collidable = false;
for (Actor a in Scene.All<Actor>(scope List<Actor>()))
{
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<Actor>());
Y += amount;
Collidable = false;
for (Actor a in Scene.All<Actor>(scope List<Actor>()))
{
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);
}
} }
} }

View File

@ -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>(T other) where T : Component, IHasHitbox
{
return Check(other.Hitbox);
}
public bool Check<T>(T other, Point offset) where T : Component, IHasHitbox
{
return Check(other.Hitbox, offset);
}
public bool CheckOutside<T>(T other, Point offset) where T : Component, IHasHitbox
{
return CheckOutside(other.Hitbox, offset);
}
public bool Check<T>() where T : Component, IHasHitbox
{
for (var e in Scene.All<T>(scope List<T>()))
if (Check(e.Hitbox))
return true;
return false;
}
public bool Check<T>(Point offset) where T : Component, IHasHitbox
{
for (var e in Scene.All<T>(scope List<T>()))
if (Check(e.Hitbox, offset))
return true;
return false;
}
public bool CheckOutside<T>(Point offset) where T : Component, IHasHitbox
{
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e.Hitbox, offset))
return true;
return false;
}
public T First<T>() where T : Component, IHasHitbox
{
for (var e in Scene.All<T>(scope List<T>()))
if (Check(e.Hitbox))
return e;
return null;
}
public T First<T>(Point offset) where T : Component, IHasHitbox
{
for (var e in Scene.All<T>(scope List<T>()))
if (Check(e.Hitbox, offset))
return e;
return null;
}
public T FirstOutside<T>(Point offset) where T : Component, IHasHitbox
{
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e.Hitbox, offset))
return e;
return null;
}
public T LeftmostOutside<T>(Point offset) where T : Component, IHasHitbox
{
T ret = null;
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e.Hitbox, offset) && (ret == null || e.Hitbox.Left < ret.Hitbox.Left))
ret = e;
return ret;
}
public T RightmostOutside<T>(Point offset) where T : Component, IHasHitbox
{
T ret = null;
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e.Hitbox, offset) && (ret == null || e.Hitbox.Right > ret.Hitbox.Right))
ret = e;
return ret;
}
public T TopmostOutside<T>(Point offset) where T : Component, IHasHitbox
{
T ret = null;
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e.Hitbox, offset) && (ret == null || e.Hitbox.Top < ret.Hitbox.Top))
ret = e;
return ret;
}
public T BottommostOutside<T>(Point offset) where T : Component, IHasHitbox
{
T ret = null;
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e.Hitbox, offset) && (ret == null || e.Hitbox.Bottom > ret.Hitbox.Bottom))
ret = e;
return ret;
}
public List<T> All<T>(List<T> into) where T : Component, IHasHitbox
{
for (var e in Scene.All<T>(scope List<T>()))
if (Check(e.Hitbox))
into.Add(e);
return into;
}
public List<T> All<T>(Point offset, List<T> into) where T : Component, IHasHitbox
{
for (var e in Scene.All<T>(scope List<T>()))
if (Check(e.Hitbox, offset))
into.Add(e);
return into;
}
public List<T> AllOutside<T>(Point offset, List<T> into) where T : Component, IHasHitbox
{
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e.Hitbox, offset))
into.Add(e);
return into;
}
}
}

View File

@ -1,14 +1,16 @@
using System.Collections; using System.Collections;
namespace Strawberry namespace Strawberry
{ {
public class OnCollide<T> : Component where T : Entity public class OnCollide<T> : 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. // Takes as parameter the T collided with. Return false to stop checking for collisions until next frame.
public delegate bool(T) Action; public delegate bool(T) Action;
public this(delegate bool(T) action) public this(Hitbox hitbox, delegate bool(T) action)
: base(true, false)
{ {
Hitbox = hitbox;
Action = action; Action = action;
} }
@ -17,13 +19,13 @@ namespace Strawberry
delete Action; delete Action;
} }
public override void Update() public void Update()
{ {
if (Action != null) if (Action != null)
{ {
let list = Entity.Scene.All<T>(scope List<T>()); let list = Entity.Scene.All<T>(scope List<T>());
for (let t in list) for (let t in list)
if (Entity.Check(t) && !Action(t)) if (Hitbox.Check(t) && !Action(t))
break; break;
} }
} }

View File

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

View File

@ -0,0 +1,8 @@
namespace Strawberry
{
[ComponentInterface]
public interface IDebugDraw
{
public void DebugDraw();
}
}

View File

@ -0,0 +1,8 @@
namespace Strawberry
{
[ComponentInterface]
public interface IDraw
{
public void Draw();
}
}

View File

@ -0,0 +1,8 @@
namespace Strawberry
{
[ComponentInterface]
public interface IHasHitbox
{
public Hitbox Hitbox { get; }
}
}

View File

@ -0,0 +1,8 @@
namespace Strawberry
{
[ComponentInterface]
public interface ILateUpdate
{
public void LateUpdate();
}
}

View File

@ -0,0 +1,8 @@
namespace Strawberry
{
[ComponentInterface]
public interface IUpdate
{
public void Update();
}
}

View File

@ -4,7 +4,7 @@ using System.Diagnostics;
namespace Strawberry namespace Strawberry
{ {
public class StateMachine<TIndex> : Component where TIndex : struct, IHashable public class StateMachine<TIndex> : Component, IUpdate where TIndex : struct, IHashable
{ {
private Dictionary<TIndex, State> states = new Dictionary<TIndex, State>() ~ delete _; private Dictionary<TIndex, State> states = new Dictionary<TIndex, State>() ~ delete _;
private TIndex state; private TIndex state;
@ -14,7 +14,6 @@ namespace Strawberry
public TIndex NextState { get; private set; } public TIndex NextState { get; private set; }
public this(TIndex startState) public this(TIndex startState)
: base(true, false)
{ {
NextState = PreviousState = state = startState; NextState = PreviousState = state = startState;
} }
@ -30,7 +29,7 @@ namespace Strawberry
CallEnter(); CallEnter();
} }
public override void Update() public void Update()
{ {
CallUpdate(); CallUpdate();
} }

View File

@ -3,35 +3,27 @@ using System.Diagnostics;
namespace Strawberry namespace Strawberry
{ {
public class Timer : Component public class Timer : Component, IUpdate
{ {
private float value; private float value;
public Action OnComplete ~ delete _; public Action OnComplete ~ delete _;
public bool RemoveOnComplete; public bool RemoveOnComplete;
public this()
: base(false, false)
{
}
public this(Action onComplete, bool destroyOnComplete = false) public this(Action onComplete, bool destroyOnComplete = false)
: base(false, false)
{ {
OnComplete = onComplete; OnComplete = onComplete;
RemoveOnComplete = destroyOnComplete; RemoveOnComplete = destroyOnComplete;
} }
public this(float value, Action onComplete, bool destroyOnComplete = false) public this(float value, Action onComplete, bool destroyOnComplete = false)
: base(false, false)
{ {
Value = value; Value = value;
OnComplete = onComplete; OnComplete = onComplete;
RemoveOnComplete = destroyOnComplete; RemoveOnComplete = destroyOnComplete;
} }
public override void Update() public void Update()
{ {
if (value > 0) if (value > 0)
{ {
@ -39,8 +31,6 @@ namespace Strawberry
if (value <= 0) if (value <= 0)
{ {
value = 0; value = 0;
Active = false;
OnComplete?.Invoke(); OnComplete?.Invoke();
if (RemoveOnComplete) if (RemoveOnComplete)
RemoveSelf(); RemoveSelf();
@ -60,7 +50,6 @@ namespace Strawberry
set set
{ {
this.value = Math.Max(0, value); this.value = Math.Max(0, value);
Active = (this.value > 0);
} }
} }
@ -68,7 +57,6 @@ namespace Strawberry
public void Clear() public void Clear()
{ {
value = 0; value = 0;
Active = false;
} }
static public implicit operator bool(Timer timer) static public implicit operator bool(Timer timer)

View File

@ -1,33 +1,42 @@
using System; using System;
namespace Strawberry namespace Strawberry
{ {
public class Tween : Component public class Tween : Component, IUpdate
{ {
public Ease.Easer Easer ~ delete _; public Ease.Easer Easer ~ delete _;
public delegate void(float) OnUpdate ~ delete _; public delegate void(float) OnUpdate ~ delete _;
public delegate void() OnComplete ~ delete _; public delegate void() OnComplete ~ delete _;
public bool RemoveOnComplete; public bool RemoveOnComplete;
public bool Playing { get; private set; }
public float T { 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) 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; Easer = easer;
OnUpdate = onUpdate; OnUpdate = onUpdate;
OnComplete = onComplete; OnComplete = onComplete;
RemoveOnComplete = removeOnComplete; RemoveOnComplete = removeOnComplete;
} }
[Inline]
public float Eased => Easer != null ? Easer(T) : T; public float Eased => Easer != null ? Easer(T) : T;
[Inline]
public void Play() public void Play()
{ {
T = 0; 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); T = Math.Min(T + Time.Delta, 1);
OnUpdate?.Invoke(Eased); OnUpdate?.Invoke(Eased);
@ -35,7 +44,7 @@ namespace Strawberry
if (T >= 1) if (T >= 1)
{ {
OnComplete?.Invoke(); OnComplete?.Invoke();
Active = false; Playing = false;
if (RemoveOnComplete) if (RemoveOnComplete)
RemoveSelf(); RemoveSelf();
} }

View File

@ -6,15 +6,6 @@ namespace Strawberry
{ {
public Entity Entity { get; private set; } 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) private void Added(Entity entity)
{ {
Entity = entity; Entity = entity;
@ -27,13 +18,20 @@ namespace Strawberry
public virtual void Started() { } public virtual void Started() { }
public virtual void Ended() { } public virtual void Ended() { }
public virtual void Update() { }
public virtual void Draw() { }
[Inline] [Inline]
public void RemoveSelf() public void RemoveSelf()
{ {
Entity?.Remove(this); Entity?.Remove(this);
} }
[Inline]
public Scene Scene => Entity?.Scene;
[Inline]
public T SceneAs<T>() where T : Scene
{
return Entity.SceneAs<T>();
}
} }
} }

View File

@ -0,0 +1,9 @@
using System;
namespace Strawberry
{
public struct ComponentInterfaceAttribute : Attribute
{
}
}

View File

@ -3,13 +3,9 @@ using System.Collections;
namespace Strawberry namespace Strawberry
{ {
public abstract class Entity public sealed class Entity
{ {
public Scene Scene { get; private set; } 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; public bool DeleteOnRemove = true;
private List<Component> components = new List<Component>() ~ delete _; private List<Component> components = new List<Component>() ~ delete _;
@ -40,32 +36,18 @@ namespace Strawberry
Scene = null; Scene = null;
} }
public virtual void Started() public void Started()
{ {
for (var c in components) for (var c in components)
c.Started(); c.Started();
} }
public virtual void Ended() public void Ended()
{ {
for (var c in components) for (var c in components)
c.Ended(); 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] [Inline]
public void RemoveSelf() 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<T>() where T : Entity
{
for (var e in Scene.All<T>(scope List<T>()))
if (Check(e))
return true;
return false;
}
public bool Check<T>(Point offset) where T : Entity
{
for (var e in Scene.All<T>(scope List<T>()))
if (Check(e, offset))
return true;
return false;
}
public bool CheckOutside<T>(Point offset) where T : Entity
{
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e, offset))
return true;
return false;
}
public T First<T>() where T : Entity
{
for (var e in Scene.All<T>(scope List<T>()))
if (Check(e))
return e;
return null;
}
public T First<T>(Point offset) where T : Entity
{
for (var e in Scene.All<T>(scope List<T>()))
if (Check(e, offset))
return e;
return null;
}
public T FirstOutside<T>(Point offset) where T : Entity
{
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e, offset))
return e;
return null;
}
public T LeftmostOutside<T>(Point offset) where T : Entity
{
T ret = null;
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e, offset) && (ret == null || e.Left < ret.Left))
ret = e;
return ret;
}
public T RightmostOutside<T>(Point offset) where T : Entity
{
T ret = null;
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e, offset) && (ret == null || e.Right > ret.Right))
ret = e;
return ret;
}
public T TopmostOutside<T>(Point offset) where T : Entity
{
T ret = null;
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e, offset) && (ret == null || e.Top < ret.Top))
ret = e;
return ret;
}
public T BottommostOutside<T>(Point offset) where T : Entity
{
T ret = null;
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e, offset) && (ret == null || e.Bottom > ret.Bottom))
ret = e;
return ret;
}
public List<T> All<T>(List<T> into) where T : Entity
{
for (var e in Scene.All<T>(scope List<T>()))
if (Check(e))
into.Add(e);
return into;
}
public List<T> All<T>(Point offset, List<T> into) where T : Entity
{
for (var e in Scene.All<T>(scope List<T>()))
if (Check(e, offset))
into.Add(e);
return into;
}
public List<T> AllOutside<T>(Point offset, List<T> into) where T : Entity
{
for (var e in Scene.All<T>(scope List<T>()))
if (CheckOutside(e, offset))
into.Add(e);
return into;
}
// ===== Misc ===== // ===== Misc =====
public void DrawHitbox(Color color) [Inline]
{
Game.Batcher.Rect(SceneHitbox, color);
}
public void DrawHitboxOutline(Color color)
{
Game.Batcher.Rect(SceneHitboxOutline, color);
}
public T SceneAs<T>() where T : Scene public T SceneAs<T>() where T : Scene
{ {
Runtime.Assert(Scene is T, "Scene type mismatch!"); Runtime.Assert(Scene is T, "Scene type mismatch!");
return Scene as T; return Scene as T;
} }
static public int Compare(Entity a, Entity b)
{
return a.Priority <=> b.Priority;
}
} }
} }

View File

@ -25,8 +25,6 @@ namespace Strawberry
private Scene scene; private Scene scene;
private Scene switchToScene; private Scene switchToScene;
private bool updating; private bool updating;
private Dictionary<Type, List<Type>> entityAssignableLists;
private Dictionary<Type, List<Type>> componentAssignableLists;
public PlatformLayer PlatformLayer { get; private set; } public PlatformLayer PlatformLayer { get; private set; }
public Batcher Batcher { get; private set; } public Batcher Batcher { get; private set; }
@ -60,9 +58,9 @@ namespace Strawberry
VirtualInputs = new List<VirtualInput>(); VirtualInputs = new List<VirtualInput>();
Input.[Friend]Init(); Input.[Friend]Init();
BuildTypeLists(); Tracker.[Friend]BuildAssignmentLists();
Assets.LoadAll(); Assets.LoadAll();
Strawberry.Console.Init(); Strawberry.StrwConsole.Init();
} }
public ~this() public ~this()
@ -83,9 +81,8 @@ namespace Strawberry
} }
Assets.DisposeAll(); Assets.DisposeAll();
DisposeTypeLists();
Input.[Friend]Dispose(); Input.[Friend]Dispose();
Strawberry.Console.Dispose(); Strawberry.StrwConsole.Dispose();
delete Batcher; delete Batcher;
@ -157,7 +154,7 @@ namespace Strawberry
Time.Elapsed += Time.Delta; Time.Elapsed += Time.Delta;
} }
Strawberry.Console.[Friend]Update(); Strawberry.StrwConsole.[Friend]Update();
} }
private void Render() private void Render()
@ -171,8 +168,8 @@ namespace Strawberry
{ {
Scene?.Draw(); Scene?.Draw();
if (Strawberry.Console.Enabled) if (Strawberry.StrwConsole.Enabled)
Strawberry.Console.[Friend]Draw(); Strawberry.StrwConsole.[Friend]Draw();
Batcher.Draw(); Batcher.Draw();
} }
@ -191,57 +188,5 @@ namespace Strawberry
switchToScene = value; 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<Type, List<Type>>();
for (let type in Type.Enumerator())
{
if (type != typeof(Entity) && type.IsSubtypeOf(typeof(Entity)))
{
let list = new List<Type>();
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<Type, List<Type>>();
for (let type in Type.Enumerator())
{
if (type != typeof(Component) && type.IsSubtypeOf(typeof(Component)))
{
let list = new List<Type>();
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;
}
} }
} }

View File

@ -10,7 +10,6 @@ namespace Strawberry
private List<Entity> entities; private List<Entity> entities;
private HashSet<Entity> toRemove; private HashSet<Entity> toRemove;
private HashSet<Entity> toAdd; private HashSet<Entity> toAdd;
private Dictionary<Type, List<Entity>> entityTracker;
private Dictionary<Type, List<Component>> componentTracker; private Dictionary<Type, List<Component>> componentTracker;
public this() public this()
@ -19,12 +18,10 @@ namespace Strawberry
toAdd = new HashSet<Entity>(); toAdd = new HashSet<Entity>();
toRemove = new HashSet<Entity>(); toRemove = new HashSet<Entity>();
entityTracker = new Dictionary<Type, List<Entity>>();
for (let type in Game.[Friend]entityAssignableLists.Keys)
entityTracker.Add(type, new List<Entity>());
componentTracker = new Dictionary<Type, List<Component>>(); componentTracker = new Dictionary<Type, List<Component>>();
for (let type in Game.[Friend]componentAssignableLists.Keys) for (let type in Tracker.AssignmentLists.Keys)
componentTracker.Add(type, new List<Component>());
for (let type in Tracker.Interfaces)
componentTracker.Add(type, new List<Component>()); componentTracker.Add(type, new List<Component>());
} }
@ -42,10 +39,6 @@ namespace Strawberry
delete toRemove; delete toRemove;
for (let list in entityTracker.Values)
delete list;
delete entityTracker;
for (let list in componentTracker.Values) for (let list in componentTracker.Values)
delete list; delete list;
delete componentTracker; delete componentTracker;
@ -59,26 +52,23 @@ namespace Strawberry
public virtual void Update() public virtual void Update()
{ {
UpdateLists(); UpdateLists();
for (let e in entities)
if (e.Active)
e.Update();
} }
public virtual void Draw() public virtual void Draw()
{ {
for (let e in entities)
if (e.Visible)
e.Draw();
} }
public T Add<T>(T e) where T : Entity public Entity Add(Entity e)
{ {
if (e.Scene == null) if (e.Scene == null)
toAdd.Add(e); toAdd.Add(e);
return e; return e;
} }
public T Remove<T>(T e) where T : Entity public Entity Remove(Entity e)
{ {
if (e.Scene == this) if (e.Scene == this)
toRemove.Add(e); toRemove.Add(e);
@ -92,7 +82,6 @@ namespace Strawberry
for (let e in toRemove) for (let e in toRemove)
{ {
entities.Remove(e); entities.Remove(e);
UntrackEntity(e);
e.[Friend]Removed(); e.[Friend]Removed();
if (e.DeleteOnRemove) if (e.DeleteOnRemove)
delete e; delete e;
@ -106,11 +95,8 @@ namespace Strawberry
for (let e in toAdd) for (let e in toAdd)
{ {
entities.Add(e); entities.Add(e);
TrackEntity(e);
e.[Friend]Added(this); e.[Friend]Added(this);
} }
entities.Sort(scope => Entity.Compare);
} }
for (let e in entities) for (let e in entities)
@ -126,33 +112,15 @@ namespace Strawberry
// Tracking // 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) 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); componentTracker[t].Add(c);
} }
private void UntrackComponent(Component 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); componentTracker[t].Remove(c);
} }
@ -171,75 +139,6 @@ namespace Strawberry
return (TimeElapsed - offset) % (interval * 2) >= interval; return (TimeElapsed - offset) % (interval * 2) >= interval;
} }
// Finding Entities
public int Count<T>() where T : Entity
{
return entityTracker[typeof(T)].Count;
}
public bool Check<T>(Point point) where T : Entity
{
for (let e in entityTracker[typeof(T)])
if (e.Check(point))
return true;
return false;
}
public bool Check<T>(Rect rect) where T : Entity
{
for (let e in entityTracker[typeof(T)])
if (e.Check(rect))
return true;
return false;
}
public T First<T>() where T : Entity
{
for (let e in entityTracker[typeof(T)])
return e as T;
return null;
}
public T First<T>(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<T>(Rect rect) where T : Entity
{
for (let e in entityTracker[typeof(T)])
if (e.Check(rect))
return e as T;
return null;
}
public List<T> All<T>(List<T> into) where T : Entity
{
for (let e in entityTracker[typeof(T)])
into.Add(e as T);
return into;
}
public List<T> All<T>(Point point, List<T> into) where T : Entity
{
for (let e in entityTracker[typeof(T)])
if (e.Check(point))
into.Add(e as T);
return into;
}
public List<T> All<T>(Rect rect, List<T> into) where T : Entity
{
for (let e in entityTracker[typeof(T)])
if (e.Check(rect))
into.Add(e as T);
return into;
}
/* /*
Finding Components Finding Components
*/ */
@ -249,25 +148,64 @@ namespace Strawberry
return componentTracker[typeof(T)].Count; return componentTracker[typeof(T)].Count;
} }
public List<T> All<T>(List<T> into) where T : Component public bool Check<T>(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<T>(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<T>() where T : Component, IHasHitbox
{
for (T c in componentTracker[typeof(T)])
return c;
return null;
}
public T First<T>(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<T>(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<T> All<T>(List<T> into) where T : Component, IHasHitbox
{
for (T c in componentTracker[typeof(T)])
into.Add(c as T); into.Add(c as T);
return into; return into;
} }
public List<T> All<T>(Point point, List<T> into) where T : Component public List<T> All<T>(Point point, List<T> into) where T : Component, IHasHitbox
{ {
for (let c in componentTracker[typeof(T)]) for (T c in componentTracker[typeof(T)])
if (c.Entity.Check(point)) if (c.Hitbox.Check(point))
into.Add(c as T); into.Add(c as T);
return into; return into;
} }
public List<T> All<T>(Rect rect, List<T> into) where T : Component public List<T> All<T>(Rect rect, List<T> into) where T : Component, IHasHitbox
{ {
for (let c in componentTracker[typeof(T)]) for (T c in componentTracker[typeof(T)])
if (c.Entity.Check(rect)) if (c.Hitbox.Check(rect))
into.Add(c as T); into.Add(c as T);
return into; return into;
} }

View File

@ -143,9 +143,14 @@ namespace Strawberry
return p.X >= 0 && p.Y >= 0 && p.X < CellsX && p.Y < CellsY; 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) public bool Check(Rect rect)

View File

@ -19,7 +19,7 @@ namespace Strawberry
} }
[Reflect] [Reflect]
static public class Console static public class StrwConsole
{ {
static public bool Open; static public bool Open;

38
src/Static/Tracker.bf Normal file
View File

@ -0,0 +1,38 @@
using System.Collections;
using System;
namespace Strawberry
{
static public class Tracker
{
static public Dictionary<Type, List<Type>> AssignmentLists = new .() ~ DeleteDictionaryAndValues!(_);
static public List<Type> Interfaces = new .() ~ delete _;
static private void BuildAssignmentLists()
{
// Find all interfaces with ComponentInterfaceAttribute
for (let type in Type.Enumerator())
if (type.IsInterface && type.HasCustomAttribute<ComponentInterfaceAttribute>())
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<Type>();
list.Add(type);
for (let check in Interfaces)
if (type.IsSubtypeOf(check))
list.Add(check);
AssignmentLists.Add(type, list);
}
}
}
}
}