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/BeefSpace_User.toml
build/*
SampleGame/recovery/*

View File

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

View File

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

View File

@ -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<Level>();
public Level Level => Entity.SceneAs<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)
{
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<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)
{
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<Solid>(.(sign, 0));
if (hit != null)
if (Check(Level, .(sign, 0)) || Hitbox.Check<Solid>(.(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<Solid>(.(0, sign));
if (hit == null && sign == 1)
hit = FirstOutside<JumpThru>(.(0, sign));
if (hit != null)
if (Check(Level, .(0, sign)) || Hitbox.Check<Solid>(.(0, sign)) || Hitbox.CheckOutside<JumpThru>(.(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;
@ -216,9 +164,9 @@ namespace Strawberry.Sample
delegate bool(Point) checker;
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
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++)
{
@ -230,7 +178,7 @@ namespace Strawberry.Sample
let offset = dir * lookAhead + perp * i * j;
if (checker(offset))
{
Position += offset;
Entity.Position += offset;
return true;
}
}

View File

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

View File

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

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;
namespace Strawberry.Sample
{
public class JumpThru : Geometry
public class JumpThru : Component, IHasHitbox
{
public this(Point position, int width)
: base(position)
{
Hitbox = Rect(0, 0, width, 2);
}
public Hitbox Hitbox { get; private set; }
public this(JSON json)
: this(.(json), json["width"])
public this(Hitbox hitbox)
{
}
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);
Hitbox = hitbox;
}
}
}

View File

@ -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<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;
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.
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<T>(scope List<T>());
for (let t in list)
if (Entity.Check(t) && !Action(t))
if (Hitbox.Check(t) && !Action(t))
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
{
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 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();
}

View File

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

View File

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

View File

@ -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<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
{
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<Component> components = new List<Component>() ~ 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<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 =====
public void DrawHitbox(Color color)
{
Game.Batcher.Rect(SceneHitbox, color);
}
public void DrawHitboxOutline(Color color)
{
Game.Batcher.Rect(SceneHitboxOutline, color);
}
[Inline]
public T SceneAs<T>() 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;
}
}
}

View File

@ -25,8 +25,6 @@ namespace Strawberry
private Scene scene;
private Scene switchToScene;
private bool updating;
private Dictionary<Type, List<Type>> entityAssignableLists;
private Dictionary<Type, List<Type>> componentAssignableLists;
public PlatformLayer PlatformLayer { get; private set; }
public Batcher Batcher { get; private set; }
@ -60,9 +58,9 @@ namespace Strawberry
VirtualInputs = new List<VirtualInput>();
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<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 HashSet<Entity> toRemove;
private HashSet<Entity> toAdd;
private Dictionary<Type, List<Entity>> entityTracker;
private Dictionary<Type, List<Component>> componentTracker;
public this()
@ -19,12 +18,10 @@ namespace Strawberry
toAdd = 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>>();
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>());
}
@ -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>(T e) where T : Entity
public Entity Add(Entity e)
{
if (e.Scene == null)
toAdd.Add(e);
return e;
}
public T Remove<T>(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<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
*/
@ -249,25 +148,64 @@ namespace Strawberry
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);
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)])
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<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)])
if (c.Entity.Check(rect))
for (T c in componentTracker[typeof(T)])
if (c.Hitbox.Check(rect))
into.Add(c as T);
return into;
}

View File

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

View File

@ -19,7 +19,7 @@ namespace Strawberry
}
[Reflect]
static public class Console
static public class StrwConsole
{
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);
}
}
}
}
}