diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d3a38c --- /dev/null +++ b/.gitignore @@ -0,0 +1,341 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ +# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true +**/wwwroot/lib/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb \ No newline at end of file diff --git a/MonocleEngineDemo/Monocle/Colliders/Circle.cs b/MonocleEngineDemo/Monocle/Colliders/Circle.cs new file mode 100644 index 0000000..f0c2361 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Colliders/Circle.cs @@ -0,0 +1,103 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Monocle +{ + public class Circle : Collider + { + public float Radius; + + public Circle(float radius, float x = 0, float y = 0) + { + Radius = radius; + Position.X = x; + Position.Y = y; + } + + public override float Width + { + get { return Radius * 2; } + set { Radius = value / 2; } + } + + public override float Height + { + get { return Radius * 2; } + set { Radius = value / 2; } + } + + public override float Left + { + get { return Position.X - Radius; } + set { Position.X = value + Radius; } + } + + public override float Top + { + get { return Position.Y - Radius; } + set { Position.Y = value + Radius; } + } + + public override float Right + { + get { return Position.X + Radius; } + set { Position.X = value - Radius; } + } + + public override float Bottom + { + get { return Position.Y + Radius; } + set { Position.Y = value - Radius; } + } + + public override Collider Clone() + { + return new Circle(Radius, Position.X, Position.Y); + } + + public override void Render(Camera camera, Color color) + { + Draw.Circle(AbsolutePosition, Radius, color, 4); + } + + /* + * Checking against other colliders + */ + + public override bool Collide(Vector2 point) + { + return Monocle.Collide.CircleToPoint(AbsolutePosition, Radius, point); + } + + public override bool Collide(Rectangle rect) + { + return Monocle.Collide.RectToCircle(rect, AbsolutePosition, Radius); + } + + public override bool Collide(Vector2 from, Vector2 to) + { + return Monocle.Collide.CircleToLine(AbsolutePosition, Radius, from, to); + } + + public override bool Collide(Circle circle) + { + return Vector2.DistanceSquared(AbsolutePosition, circle.AbsolutePosition) < (Radius + circle.Radius) * (Radius + circle.Radius); + } + + public override bool Collide(Hitbox hitbox) + { + return hitbox.Collide(this); + } + + public override bool Collide(Grid grid) + { + return grid.Collide(this); + } + + public override bool Collide(ColliderList list) + { + return list.Collide(this); + } + + } +} diff --git a/MonocleEngineDemo/Monocle/Colliders/Collide.cs b/MonocleEngineDemo/Monocle/Colliders/Collide.cs new file mode 100644 index 0000000..3d04a17 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Colliders/Collide.cs @@ -0,0 +1,440 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; + +namespace Monocle +{ + [Flags] + public enum PointSectors { Center = 0, Top = 1, Bottom = 2, TopLeft = 9, TopRight = 5, Left = 8, Right = 4, BottomLeft = 10, BottomRight = 6 }; + + public static class Collide + { + #region Entity vs Entity + + public static bool Check(Entity a, Entity b) + { + if (a.Collider == null || b.Collider == null) + return false; + else + return a != b && b.Collidable && a.Collider.Collide(b); + } + + public static bool Check(Entity a, Entity b, Vector2 at) + { + Vector2 old = a.Position; + a.Position = at; + bool ret = Check(a, b); + a.Position = old; + return ret; + } + + public static bool Check(Entity a, CollidableComponent b) + { + if (a.Collider == null || b.Collider == null) + return false; + else + return b.Collidable && b.Entity.Collidable && a.Collider.Collide(b); + } + + public static bool Check(Entity a, CollidableComponent b, Vector2 at) + { + Vector2 old = a.Position; + a.Position = at; + bool ret = Check(a, b); + a.Position = old; + return ret; + } + + #endregion + + #region Entity vs Entity Enumerable + + #region Check + + public static bool Check(Entity a, IEnumerable b) + { + foreach (var e in b) + if (Check(a, e)) + return true; + + return false; + } + + public static bool Check(Entity a, IEnumerable b, Vector2 at) + { + Vector2 old = a.Position; + a.Position = at; + bool ret = Check(a, b); + a.Position = old; + return ret; + } + + #endregion + + #region First + + public static Entity First(Entity a, IEnumerable b) + { + foreach (var e in b) + if (Check(a, e)) + return e; + + return null; + } + + public static Entity First(Entity a, IEnumerable b, Vector2 at) + { + Vector2 old = a.Position; + a.Position = at; + Entity ret = First(a, b); + a.Position = old; + return ret; + } + + #endregion + + #region All + + public static List All(Entity a, IEnumerable b, List into) + { + foreach (var e in b) + if (Check(a, e)) + into.Add(e); + + return into; + } + + public static List All(Entity a, IEnumerable b, List into, Vector2 at) + { + Vector2 old = a.Position; + a.Position = at; + List ret = All(a, b, into); + a.Position = old; + return ret; + } + + public static List All(Entity a, IEnumerable b) + { + return All(a, b, new List()); + } + + public static List All(Entity a, IEnumerable b, Vector2 at) + { + return All(a, b, new List(), at); + } + + #endregion + + #endregion + + #region Entity vs Point + + public static bool CheckPoint(Entity a, Vector2 point) + { + if (a.Collider == null) + return false; + else + return a.Collider.Collide(point); + } + + public static bool CheckPoint(Entity a, Vector2 point, Vector2 at) + { + Vector2 old = a.Position; + a.Position = at; + bool ret = CheckPoint(a, point); + a.Position = old; + return ret; + } + + #endregion + + #region Entity vs Line + + public static bool CheckLine(Entity a, Vector2 from, Vector2 to) + { + if (a.Collider == null) + return false; + else + return a.Collider.Collide(from, to); + } + + public static bool CheckLine(Entity a, Vector2 from, Vector2 to, Vector2 at) + { + Vector2 old = a.Position; + a.Position = at; + bool ret = CheckLine(a, from, to); + a.Position = old; + return ret; + } + + #endregion + + #region Entity vs Rectangle + + public static bool CheckRect(Entity a, Rectangle rect) + { + if (a.Collider == null) + return false; + else + return a.Collider.Collide(rect); + } + + public static bool CheckRect(Entity a, Rectangle rect, Vector2 at) + { + Vector2 old = a.Position; + a.Position = at; + bool ret = CheckRect(a, rect); + a.Position = old; + return ret; + } + + #endregion + + #region Line + + public static bool LineCheck(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2) + { + Vector2 b = a2 - a1; + Vector2 d = b2 - b1; + float bDotDPerp = b.X * d.Y - b.Y * d.X; + + // if b dot d == 0, it means the lines are parallel so have infinite intersection points + if (bDotDPerp == 0) + return false; + + Vector2 c = b1 - a1; + float t = (c.X * d.Y - c.Y * d.X) / bDotDPerp; + if (t < 0 || t > 1) + return false; + + float u = (c.X * b.Y - c.Y * b.X) / bDotDPerp; + if (u < 0 || u > 1) + return false; + + return true; + } + + public static bool LineCheck(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2, out Vector2 intersection) + { + intersection = Vector2.Zero; + + Vector2 b = a2 - a1; + Vector2 d = b2 - b1; + float bDotDPerp = b.X * d.Y - b.Y * d.X; + + // if b dot d == 0, it means the lines are parallel so have infinite intersection points + if (bDotDPerp == 0) + return false; + + Vector2 c = b1 - a1; + float t = (c.X * d.Y - c.Y * d.X) / bDotDPerp; + if (t < 0 || t > 1) + return false; + + float u = (c.X * b.Y - c.Y * b.X) / bDotDPerp; + if (u < 0 || u > 1) + return false; + + intersection = a1 + t * b; + + return true; + } + + #endregion + + #region Circle + + public static bool CircleToLine(Vector2 cPosiition, float cRadius, Vector2 lineFrom, Vector2 lineTo) + { + return Vector2.DistanceSquared(cPosiition, Calc.ClosestPointOnLine(lineFrom, lineTo, cPosiition)) < cRadius * cRadius; + } + + public static bool CircleToPoint(Vector2 cPosition, float cRadius, Vector2 point) + { + return Vector2.DistanceSquared(cPosition, point) < cRadius * cRadius; + } + + public static bool CircleToRect(Vector2 cPosition, float cRadius, float rX, float rY, float rW, float rH) + { + return RectToCircle(rX, rY, rW, rH, cPosition, cRadius); + } + + public static bool CircleToRect(Vector2 cPosition, float cRadius, Rectangle rect) + { + return RectToCircle(rect, cPosition, cRadius); + } + + #endregion + + #region Rect + + public static bool RectToCircle(float rX, float rY, float rW, float rH, Vector2 cPosition, float cRadius) + { + //Check if the rectangle contains the circle's center point + if (Collide.RectToPoint(rX, rY, rW, rH, cPosition)) + return true; + + //Check the circle against the relevant edges + Vector2 edgeFrom; + Vector2 edgeTo; + PointSectors sector = GetSector(rX, rY, rW, rH, cPosition); + + if ((sector & PointSectors.Top) != 0) + { + edgeFrom = new Vector2(rX, rY); + edgeTo = new Vector2(rX + rW, rY); + if (CircleToLine(cPosition, cRadius, edgeFrom, edgeTo)) + return true; + } + + if ((sector & PointSectors.Bottom) != 0) + { + edgeFrom = new Vector2(rX, rY + rH); + edgeTo = new Vector2(rX + rW, rY + rH); + if (CircleToLine(cPosition, cRadius, edgeFrom, edgeTo)) + return true; + } + + if ((sector & PointSectors.Left) != 0) + { + edgeFrom = new Vector2(rX, rY); + edgeTo = new Vector2(rX, rY + rH); + if (CircleToLine(cPosition, cRadius, edgeFrom, edgeTo)) + return true; + } + + if ((sector & PointSectors.Right) != 0) + { + edgeFrom = new Vector2(rX + rW, rY); + edgeTo = new Vector2(rX + rW, rY + rH); + if (CircleToLine(cPosition, cRadius, edgeFrom, edgeTo)) + return true; + } + + return false; + } + + public static bool RectToCircle(Rectangle rect, Vector2 cPosition, float cRadius) + { + return RectToCircle(rect.X, rect.Y, rect.Width, rect.Height, cPosition, cRadius); + } + + public static bool RectToLine(float rX, float rY, float rW, float rH, Vector2 lineFrom, Vector2 lineTo) + { + PointSectors fromSector = Monocle.Collide.GetSector(rX, rY, rW, rH, lineFrom); + PointSectors toSector = Monocle.Collide.GetSector(rX, rY, rW, rH, lineTo); + + if (fromSector == PointSectors.Center || toSector == PointSectors.Center) + return true; + else if ((fromSector & toSector) != 0) + return false; + else + { + PointSectors both = fromSector | toSector; + + //Do line checks against the edges + Vector2 edgeFrom; + Vector2 edgeTo; + + if ((both & PointSectors.Top) != 0) + { + edgeFrom = new Vector2(rX, rY); + edgeTo = new Vector2(rX + rW, rY); + if (Monocle.Collide.LineCheck(edgeFrom, edgeTo, lineFrom, lineTo)) + return true; + } + + if ((both & PointSectors.Bottom) != 0) + { + edgeFrom = new Vector2(rX, rY + rH); + edgeTo = new Vector2(rX + rW, rY + rH); + if (Monocle.Collide.LineCheck(edgeFrom, edgeTo, lineFrom, lineTo)) + return true; + } + + if ((both & PointSectors.Left) != 0) + { + edgeFrom = new Vector2(rX, rY); + edgeTo = new Vector2(rX, rY + rH); + if (Monocle.Collide.LineCheck(edgeFrom, edgeTo, lineFrom, lineTo)) + return true; + } + + if ((both & PointSectors.Right) != 0) + { + edgeFrom = new Vector2(rX + rW, rY); + edgeTo = new Vector2(rX + rW, rY + rH); + if (Monocle.Collide.LineCheck(edgeFrom, edgeTo, lineFrom, lineTo)) + return true; + } + } + + return false; + } + + public static bool RectToLine(Rectangle rect, Vector2 lineFrom, Vector2 lineTo) + { + return RectToLine(rect.X, rect.Y, rect.Width, rect.Height, lineFrom, lineTo); + } + + public static bool RectToPoint(float rX, float rY, float rW, float rH, Vector2 point) + { + return point.X >= rX && point.Y >= rY && point.X < rX + rW && point.Y < rY + rH; + } + + public static bool RectToPoint(Rectangle rect, Vector2 point) + { + return RectToPoint(rect.X, rect.Y, rect.Width, rect.Height, point); + } + + #endregion + + #region Sectors + + /* + * Bitflags and helpers for using the Cohen–Sutherland algorithm + * http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm + * + * Sector bitflags: + * 1001 1000 1010 + * 0001 0000 0010 + * 0101 0100 0110 + */ + + public static PointSectors GetSector(Rectangle rect, Vector2 point) + { + PointSectors sector = PointSectors.Center; + + if (point.X < rect.Left) + sector |= PointSectors.Left; + else if (point.X >= rect.Right) + sector |= PointSectors.Right; + + if (point.Y < rect.Top) + sector |= PointSectors.Top; + else if (point.Y >= rect.Bottom) + sector |= PointSectors.Bottom; + + return sector; + } + + public static PointSectors GetSector(float rX, float rY, float rW, float rH, Vector2 point) + { + PointSectors sector = PointSectors.Center; + + if (point.X < rX) + sector |= PointSectors.Left; + else if (point.X >= rX + rW) + sector |= PointSectors.Right; + + if (point.Y < rY) + sector |= PointSectors.Top; + else if (point.Y >= rY + rH) + sector |= PointSectors.Bottom; + + return sector; + } + + #endregion + } +} diff --git a/MonocleEngineDemo/Monocle/Colliders/Collider.cs b/MonocleEngineDemo/Monocle/Colliders/Collider.cs new file mode 100644 index 0000000..b94715e --- /dev/null +++ b/MonocleEngineDemo/Monocle/Colliders/Collider.cs @@ -0,0 +1,342 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Monocle +{ + public abstract class Collider + { + public Entity Entity { get; internal set; } + public Component Component { get; private set; } + public Vector2 Position; + + internal virtual void Added(Entity entity) + { + Entity = entity; + Component = null; + } + + internal virtual void Added(Component component) + { + Entity = component.Entity; + Component = component; + } + + internal virtual void Removed() + { + Entity = null; + Component = null; + } + + public bool Collide(Entity entity) + { + return Collide(entity.Collider); + } + + public bool Collide(CollidableComponent component) + { + return Collide(component.Collider); + } + + public bool Collide(Collider collider) + { + if (collider is Hitbox) + { + return Collide(collider as Hitbox); + } + else if (collider is Grid) + { + return Collide(collider as Grid); + } + else if (collider is ColliderList) + { + return Collide(collider as ColliderList); + } + else if (collider is Circle) + { + return Collide(collider as Circle); + } + else + throw new Exception("Collisions against the collider type are not implemented!"); + } + + public abstract bool Collide(Vector2 point); + public abstract bool Collide(Rectangle rect); + public abstract bool Collide(Vector2 from, Vector2 to); + public abstract bool Collide(Hitbox hitbox); + public abstract bool Collide(Grid grid); + public abstract bool Collide(Circle circle); + public abstract bool Collide(ColliderList list); + public abstract Collider Clone(); + public abstract void Render(Camera camera, Color color); + public abstract float Width { get; set; } + public abstract float Height { get; set; } + public abstract float Top { get; set; } + public abstract float Bottom { get; set; } + public abstract float Left { get; set; } + public abstract float Right { get; set; } + + public void CenterOrigin() + { + Position.X = -Width / 2; + Position.Y = -Height / 2; + } + + public float CenterX + { + get + { + return Left + Width / 2f; + } + + set + { + Left = value - Width / 2f; + } + } + + public float CenterY + { + get + { + return Top + Height / 2f; + } + + set + { + Top = value - Height / 2f; + } + } + + public Vector2 TopLeft + { + get + { + return new Vector2(Left, Top); + } + + set + { + Left = value.X; + Top = value.Y; + } + } + + public Vector2 TopCenter + { + get + { + return new Vector2(CenterX, Top); + } + + set + { + CenterX = value.X; + Top = value.Y; + } + } + + public Vector2 TopRight + { + get + { + return new Vector2(Right, Top); + } + + set + { + Right = value.X; + Top = value.Y; + } + } + + public Vector2 CenterLeft + { + get + { + return new Vector2(Left, CenterY); + } + + set + { + Left = value.X; + CenterY = value.Y; + } + } + + public Vector2 Center + { + get + { + return new Vector2(CenterX, CenterY); + } + + set + { + CenterX = value.X; + CenterY = value.Y; + } + } + + public Vector2 Size + { + get + { + return new Vector2(Width, Height); + } + } + + public Vector2 HalfSize + { + get + { + return Size * .5f; + } + } + + public Vector2 CenterRight + { + get + { + return new Vector2(Right, CenterY); + } + + set + { + Right = value.X; + CenterY = value.Y; + } + } + + public Vector2 BottomLeft + { + get + { + return new Vector2(Left, Bottom); + } + + set + { + Left = value.X; + Bottom = value.Y; + } + } + + public Vector2 BottomCenter + { + get + { + return new Vector2(CenterX, Bottom); + } + + set + { + CenterX = value.X; + Bottom = value.Y; + } + } + + public Vector2 BottomRight + { + get + { + return new Vector2(Right, Bottom); + } + + set + { + Right = value.X; + Bottom = value.Y; + } + } + + public void Render(Camera camera) + { + Render(camera, Color.Red); + } + + public Vector2 AbsolutePosition + { + get + { + if (Entity != null) + return Entity.Position + Position; + else + return Position; + } + } + + public float AbsoluteX + { + get + { + if (Entity != null) + return Entity.Position.X + Position.X; + else + return Position.X; + } + } + + public float AbsoluteY + { + get + { + if (Entity != null) + return Entity.Position.Y + Position.Y; + else + return Position.Y; + } + } + + public float AbsoluteTop + { + get + { + if (Entity != null) + return Top + Entity.Position.Y; + else + return Top; + } + } + + public float AbsoluteBottom + { + get + { + if (Entity != null) + return Bottom + Entity.Position.Y; + else + return Bottom; + } + } + + public float AbsoluteLeft + { + get + { + if (Entity != null) + return Left + Entity.Position.X; + else + return Left; + } + } + + public float AbsoluteRight + { + get + { + if (Entity != null) + return Right + Entity.Position.X; + else + return Right; + } + } + + public Rectangle Bounds + { + get + { + return new Rectangle((int)AbsoluteLeft, (int)AbsoluteTop, (int)Width, (int)Height); + } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Colliders/ColliderList.cs b/MonocleEngineDemo/Monocle/Colliders/ColliderList.cs new file mode 100644 index 0000000..d772c5c --- /dev/null +++ b/MonocleEngineDemo/Monocle/Colliders/ColliderList.cs @@ -0,0 +1,266 @@ +using Microsoft.Xna.Framework; +using System; +using System.Linq; + +namespace Monocle +{ + public class ColliderList : Collider + { + public Collider[] colliders { get; private set; } + + public ColliderList(params Collider[] colliders) + { +#if DEBUG + foreach (var c in colliders) + if (c == null) + throw new Exception("Cannot add a null Collider to a ColliderList."); +#endif + this.colliders = colliders; + } + + public void Add(params Collider[] toAdd) + { +#if DEBUG + foreach (var c in toAdd) + { + if (colliders.Contains(c)) + throw new Exception("Adding a Collider to a ColliderList that already contains it!"); + else if (c == null) + throw new Exception("Cannot add a null Collider to a ColliderList."); + } +#endif + + Collider[] newColliders = new Collider[colliders.Length + toAdd.Length]; + for (int i = 0; i < colliders.Length; i++) + newColliders[i] = colliders[i]; + for (int i = 0; i < toAdd.Length; i++) + { + newColliders[i + colliders.Length] = toAdd[i]; + toAdd[i].Added(Entity); + } + colliders = newColliders; + } + + public void Remove(params Collider[] toRemove) + { +#if DEBUG + foreach (var c in toRemove) + { + if (!colliders.Contains(c)) + throw new Exception("Removing a Collider from a ColliderList that does not contain it!"); + else if (c == null) + throw new Exception("Cannot remove a null Collider from a ColliderList."); + } +#endif + + Collider[] newColliders = new Collider[colliders.Length - toRemove.Length]; + int at = 0; + foreach (var c in colliders) + { + if (!toRemove.Contains(c)) + { + newColliders[at] = c; + at++; + } + } + colliders = newColliders; + } + + internal override void Added(Entity entity) + { + base.Added(entity); + foreach (var c in colliders) + c.Added(entity); + } + + internal override void Removed() + { + base.Removed(); + foreach (var c in colliders) + c.Removed(); + } + + public override float Width + { + get + { + return Right - Left; + } + + set + { + throw new NotImplementedException(); + } + } + + public override float Height + { + get + { + return Bottom - Top; + } + set + { + throw new NotImplementedException(); + } + } + + public override float Left + { + get + { + float left = colliders[0].Left; + for (int i = 1; i < colliders.Length; i++) + if (colliders[i].Left < left) + left = colliders[i].Left; + return left; + } + + set + { + float changeX = value - Left; + foreach (var c in colliders) + Position.X += changeX; + } + } + + public override float Right + { + get + { + float right = colliders[0].Right; + for (int i = 1; i < colliders.Length; i++) + if (colliders[i].Right > right) + right = colliders[i].Right; + return right; + } + + set + { + float changeX = value - Right; + foreach (var c in colliders) + Position.X += changeX; + } + } + + public override float Top + { + get + { + float top = colliders[0].Top; + for (int i = 1; i < colliders.Length; i++) + if (colliders[i].Top < top) + top = colliders[i].Top; + return top; + } + + set + { + float changeY = value - Top; + foreach (var c in colliders) + Position.Y += changeY; + } + } + + public override float Bottom + { + get + { + float bottom = colliders[0].Bottom; + for (int i = 1; i < colliders.Length; i++) + if (colliders[i].Bottom > bottom) + bottom = colliders[i].Bottom; + return bottom; + } + + set + { + float changeY = value - Bottom; + foreach (var c in colliders) + Position.Y += changeY; + } + } + + public override Collider Clone() + { + Collider[] clones = new Collider[colliders.Length]; + for (int i = 0; i < colliders.Length; i++) + clones[i] = colliders[i].Clone(); + + return new ColliderList(clones); + } + + public override void Render(Camera camera, Color color) + { + foreach (var c in colliders) + c.Render(camera, color); + } + + /* + * Checking against other colliders + */ + + public override bool Collide(Vector2 point) + { + foreach (var c in colliders) + if (c.Collide(point)) + return true; + + return false; + } + + public override bool Collide(Rectangle rect) + { + foreach (var c in colliders) + if (c.Collide(rect)) + return true; + + return false; + } + + public override bool Collide(Vector2 from, Vector2 to) + { + foreach (var c in colliders) + if (c.Collide(from, to)) + return true; + + return false; + } + + public override bool Collide(Hitbox hitbox) + { + foreach (var c in colliders) + if (c.Collide(hitbox)) + return true; + + return false; + } + + public override bool Collide(Grid grid) + { + foreach (var c in colliders) + if (c.Collide(grid)) + return true; + + return false; + } + + public override bool Collide(Circle circle) + { + foreach (var c in colliders) + if (c.Collide(circle)) + return true; + + return false; + } + + public override bool Collide(ColliderList list) + { + foreach (var c in colliders) + if (c.Collide(list)) + return true; + + return false; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Colliders/Grid.cs b/MonocleEngineDemo/Monocle/Colliders/Grid.cs new file mode 100644 index 0000000..f7df847 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Colliders/Grid.cs @@ -0,0 +1,489 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Monocle +{ + public class Grid : Collider + { + public VirtualMap Data; + + public float CellWidth { get; private set; } + public float CellHeight { get; private set; } + + public Grid(int cellsX, int cellsY, float cellWidth, float cellHeight) + { + Data = new VirtualMap(cellsX, cellsY); + + CellWidth = cellWidth; + CellHeight = cellHeight; + } + + public Grid(float cellWidth, float cellHeight, string bitstring) + { + CellWidth = cellWidth; + CellHeight = cellHeight; + + //Find minimal size from bitstring + int longest = 0; + int currentX = 0; + int currentY = 1; + for (int i = 0; i < bitstring.Length; i++) + { + if (bitstring[i] == '\n') + { + currentY++; + longest = Math.Max(currentX, longest); + currentX = 0; + } + else + currentX++; + } + + Data = new VirtualMap(longest, currentY); + LoadBitstring(bitstring); + } + + public Grid(float cellWidth, float cellHeight, bool[,] data) + { + CellWidth = cellWidth; + CellHeight = cellHeight; + + this.Data = new VirtualMap(data); + } + + public Grid(float cellWidth, float cellHeight, VirtualMap data) + { + CellWidth = cellWidth; + CellHeight = cellHeight; + + this.Data = data; + } + + public void Extend(int left, int right, int up, int down) + { + Position -= new Vector2(left * CellWidth, up * CellHeight); + + int newWidth = Data.Columns + left + right; + int newHeight = Data.Rows + up + down; + if (newWidth <= 0 || newHeight <= 0) + { + Data = new VirtualMap(0, 0); + return; + } + + var newData = new VirtualMap(newWidth, newHeight); + + //Center + for (int x = 0; x < Data.Columns; x++) + { + for (int y = 0; y < Data.Rows; y++) + { + int atX = x + left; + int atY = y + up; + + if (atX >= 0 && atX < newWidth && atY >= 0 && atY < newHeight) + newData[atX, atY] = Data[x, y]; + } + } + + //Left + for (int x = 0; x < left; x++) + for (int y = 0; y < newHeight; y++) + newData[x, y] = Data[0, Calc.Clamp(y - up, 0, Data.Rows - 1)]; + + //Right + for (int x = newWidth - right; x < newWidth; x++) + for (int y = 0; y < newHeight; y++) + newData[x, y] = Data[Data.Columns - 1, Calc.Clamp(y - up, 0, Data.Rows - 1)]; + + //Top + for (int y = 0; y < up; y++) + for (int x = 0; x < newWidth; x++) + newData[x, y] = Data[Calc.Clamp(x - left, 0, Data.Columns - 1), 0]; + + //Bottom + for (int y = newHeight - down; y < newHeight; y++) + for (int x = 0; x < newWidth; x++) + newData[x, y] = Data[Calc.Clamp(x - left, 0, Data.Columns - 1), Data.Rows - 1]; + + Data = newData; + } + + public void LoadBitstring(string bitstring) + { + int x = 0; + int y = 0; + + for (int i = 0; i < bitstring.Length; i++) + { + if (bitstring[i] == '\n') + { + while (x < CellsX) + { + Data[x, y] = false; + x++; + } + + x = 0; + y++; + + if (y >= CellsY) + return; + } + else if (x < CellsX) + { + if (bitstring[i] == '0') + { + Data[x, y] = false; + x++; + } + else + { + Data[x, y] = true; + x++; + } + } + } + } + + public string GetBitstring() + { + string bits = ""; + for (int y = 0; y < CellsY; y++) + { + if (y != 0) + bits += "\n"; + + for (int x = 0; x < CellsX; x++) + { + if (Data[x, y]) + bits += "1"; + else + bits += "0"; + } + } + + return bits; + } + + public void Clear(bool to = false) + { + for (int i = 0; i < CellsX; i++) + for (int j = 0; j < CellsY; j++) + Data[i, j] = to; + } + + public void SetRect(int x, int y, int width, int height, bool to = true) + { + if (x < 0) + { + width += x; + x = 0; + } + + if (y < 0) + { + height += y; + y = 0; + } + + if (x + width > CellsX) + width = CellsX - x; + + if (y + height > CellsY) + height = CellsY - y; + + for (int i = 0; i < width; i++) + for (int j = 0; j < height; j++) + Data[x + i, y + j] = to; + } + + public bool CheckRect(int x, int y, int width, int height) + { + if (x < 0) + { + width += x; + x = 0; + } + + if (y < 0) + { + height += y; + y = 0; + } + + if (x + width > CellsX) + width = CellsX - x; + + if (y + height > CellsY) + height = CellsY - y; + + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + if (Data[x + i, y + j]) + return true; + } + } + + return false; + } + + public bool CheckColumn(int x) + { + for (int i = 0; i < CellsY; i++) + if (!Data[x, i]) + return false; + return true; + } + + public bool CheckRow(int y) + { + for (int i = 0; i < CellsX; i++) + if (!Data[i, y]) + return false; + return true; + } + + public bool this[int x, int y] + { + get + { + if (x >= 0 && y >= 0 && x < CellsX && y < CellsY) + return Data[x, y]; + else + return false; + } + + set + { + Data[x, y] = value; + } + } + + public int CellsX + { + get + { + return Data.Columns; + } + } + + public int CellsY + { + get + { + return Data.Rows; + } + } + + public override float Width + { + get + { + return CellWidth * CellsX; + } + + set + { + throw new NotImplementedException(); + } + } + + public override float Height + { + get + { + return CellHeight * CellsY; + } + + set + { + throw new NotImplementedException(); + } + } + + public bool IsEmpty + { + get + { + for (int i = 0; i < CellsX; i++) + for (int j = 0; j < CellsY; j++) + if (Data[i, j]) + return false; + return true; + } + } + + public override float Left + { + get { return Position.X; } + set { Position.X = value; } + } + + public override float Top + { + get { return Position.Y; } + set { Position.Y = value; } + } + + public override float Right + { + get { return Position.X + Width; } + set { Position.X = value - Width; } + } + + public override float Bottom + { + get { return Position.Y + Height; } + set { Position.Y = value - Height; } + } + + public override Collider Clone() + { + return new Grid(CellWidth, CellHeight, Data.Clone()); + } + + public override void Render(Camera camera, Color color) + { + if (camera == null) + { + for (int i = 0; i < CellsX; i++) + for (int j = 0; j < CellsY; j++) + if (Data[i, j]) + Draw.HollowRect(AbsoluteLeft + i * CellWidth, AbsoluteTop + j * CellHeight, CellWidth, CellHeight, color); + } + else + { + int left = (int)Math.Max(0, ((camera.Left - AbsoluteLeft) / CellWidth)); + int right = (int)Math.Min(CellsX - 1, Math.Ceiling((camera.Right - AbsoluteLeft) / CellWidth)); + int top = (int)Math.Max(0, ((camera.Top - AbsoluteTop) / CellHeight)); + int bottom = (int)Math.Min(CellsY - 1, Math.Ceiling((camera.Bottom - AbsoluteTop) / CellHeight)); + + for (int tx = left; tx <= right; tx++) + for (int ty = top; ty <= bottom; ty++) + if (Data[tx, ty]) + Draw.HollowRect(AbsoluteLeft + tx * CellWidth, AbsoluteTop + ty * CellHeight, CellWidth, CellHeight, color); + } + } + + /* + * Checking against other colliders + */ + + override public bool Collide(Vector2 point) + { + if (point.X >= AbsoluteLeft && point.Y >= AbsoluteTop && point.X < AbsoluteRight && point.Y < AbsoluteBottom) + return Data[(int)((point.X - AbsoluteLeft) / CellWidth), (int)((point.Y - AbsoluteTop) / CellHeight)]; + else + return false; + } + + public override bool Collide(Rectangle rect) + { + if (rect.Intersects(Bounds)) + { + int x = (int)((rect.Left - AbsoluteLeft) / CellWidth); + int y = (int)((rect.Top - AbsoluteTop) / CellHeight); + int w = (int)((rect.Right - AbsoluteLeft - 1) / CellWidth) - x + 1; + int h = (int)((rect.Bottom - AbsoluteTop - 1) / CellHeight) - y + 1; + + return CheckRect(x, y, w, h); + } + else + return false; + } + + public override bool Collide(Vector2 from, Vector2 to) + { + from -= AbsolutePosition; + to -= AbsolutePosition; + from /= new Vector2(CellWidth, CellHeight); + to /= new Vector2(CellWidth, CellHeight); + + bool steep = Math.Abs(to.Y - from.Y) > Math.Abs(to.X - from.X); + if (steep) + { + float temp = from.X; + from.X = from.Y; + from.Y = temp; + + temp = to.X; + to.X = to.Y; + to.Y = temp; + } + if (from.X > to.X) + { + Vector2 temp = from; + from = to; + to = temp; + } + + float error = 0; + float deltaError = Math.Abs(to.Y - from.Y) / (to.X - from.X); + + int yStep = (from.Y < to.Y) ? 1 : -1; + int y = (int)from.Y; + int toX = (int)to.X; + + for (int x = (int)from.X; x <= toX; x++) + { + if (steep) + { + if (this[y, x]) + return true; + } + else + { + if (this[x, y]) + return true; + } + + error += deltaError; + if (error >= .5f) + { + y += yStep; + error -= 1.0f; + } + } + + return false; + } + + public override bool Collide(Hitbox hitbox) + { + return Collide(hitbox.Bounds); + } + + public override bool Collide(Grid grid) + { + throw new NotImplementedException(); + } + + public override bool Collide(Circle circle) + { + return false; + } + + public override bool Collide(ColliderList list) + { + return list.Collide(this); + } + + /* + * Static utilities + */ + + public static bool IsBitstringEmpty(string bitstring) + { + for (int i = 0; i < bitstring.Length; i++) + { + if (bitstring[i] == '1') + return false; + } + + return true; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Colliders/Hitbox.cs b/MonocleEngineDemo/Monocle/Colliders/Hitbox.cs new file mode 100644 index 0000000..554602a --- /dev/null +++ b/MonocleEngineDemo/Monocle/Colliders/Hitbox.cs @@ -0,0 +1,161 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Monocle +{ + public class Hitbox : Collider + { + private float width; + private float height; + + public Hitbox(float width, float height, float x = 0, float y = 0) + { + this.width = width; + this.height = height; + + Position.X = x; + Position.Y = y; + } + + public override float Width + { + get { return width; } + set { width = value; } + } + + public override float Height + { + get { return height; } + set { height = value; } + } + + public override float Left + { + get { return Position.X; } + set { Position.X = value; } + } + + public override float Top + { + get { return Position.Y; } + set { Position.Y = value; } + } + + public override float Right + { + get { return Position.X + Width; } + set { Position.X = value - Width; } + } + + public override float Bottom + { + get { return Position.Y + Height; } + set { Position.Y = value - Height; } + } + + public bool Intersects(Hitbox hitbox) + { + return AbsoluteLeft < hitbox.AbsoluteRight && AbsoluteRight > hitbox.AbsoluteLeft && AbsoluteBottom > hitbox.AbsoluteTop && AbsoluteTop < hitbox.AbsoluteBottom; + } + + public bool Intersects(float x, float y, float width, float height) + { + return AbsoluteRight > x && AbsoluteBottom > y && AbsoluteLeft < x + width && AbsoluteTop < y + height; + } + + public override Collider Clone() + { + return new Hitbox(width, height, Position.X, Position.Y); + } + + public override void Render(Camera camera, Color color) + { + Draw.HollowRect(AbsoluteX, AbsoluteY, Width, Height, color); + } + + public void SetFromRectangle(Rectangle rect) + { + Position = new Vector2(rect.X, rect.Y); + Width = rect.Width; + Height = rect.Height; + } + + public void Set(float x, float y, float w, float h) + { + Position = new Vector2(x, y); + Width = w; + Height = h; + } + + /* + * Get edges + */ + + public void GetTopEdge(out Vector2 from, out Vector2 to) + { + from.X = AbsoluteLeft; + to.X = AbsoluteRight; + from.Y = to.Y = AbsoluteTop; + } + + public void GetBottomEdge(out Vector2 from, out Vector2 to) + { + from.X = AbsoluteLeft; + to.X = AbsoluteRight; + from.Y = to.Y = AbsoluteBottom; + } + + public void GetLeftEdge(out Vector2 from, out Vector2 to) + { + from.Y = AbsoluteTop; + to.Y = AbsoluteBottom; + from.X = to.X = AbsoluteLeft; + } + + public void GetRightEdge(out Vector2 from, out Vector2 to) + { + from.Y = AbsoluteTop; + to.Y = AbsoluteBottom; + from.X = to.X = AbsoluteRight; + } + + /* + * Checking against other colliders + */ + + public override bool Collide(Vector2 point) + { + return Monocle.Collide.RectToPoint(AbsoluteLeft, AbsoluteTop, Width, Height, point); + } + + public override bool Collide(Rectangle rect) + { + return AbsoluteRight > rect.Left && AbsoluteBottom > rect.Top && AbsoluteLeft < rect.Right && AbsoluteTop < rect.Bottom; + } + + public override bool Collide(Vector2 from, Vector2 to) + { + return Monocle.Collide.RectToLine(AbsoluteLeft, AbsoluteTop, Width, Height, from, to); + } + + public override bool Collide(Hitbox hitbox) + { + return Intersects(hitbox); + } + + public override bool Collide(Grid grid) + { + return grid.Collide(Bounds); + } + + public override bool Collide(Circle circle) + { + return Monocle.Collide.RectToCircle(AbsoluteLeft, AbsoluteTop, Width, Height, circle.AbsolutePosition, circle.Radius); + } + + public override bool Collide(ColliderList list) + { + return list.Collide(this); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/CollidableComponent.cs b/MonocleEngineDemo/Monocle/Components/CollidableComponent.cs new file mode 100644 index 0000000..9ec4b0a --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/CollidableComponent.cs @@ -0,0 +1,324 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Monocle +{ + public class CollidableComponent : Component + { + public bool Collidable; + + private Collider collider; + + public CollidableComponent(bool active, bool visible, bool colllidable) + : base(active, visible) + { + Collidable = colllidable; + } + + public override void Added(Entity entity) + { + base.Added(entity); + if (collider != null) + collider.Entity = entity; + } + + public override void Removed(Entity entity) + { + if (collider != null) + collider.Entity = null; + base.Removed(entity); + } + + public Collider Collider + { + get + { + if (collider == null) + return Entity.Collider; + else + return collider; + } + + set + { + if (value == collider) + return; +#if DEBUG + if (value.Entity != null) + throw new Exception("Setting an Entity's Collider to a Collider already in use by another object"); +#endif + if (collider != null) + collider.Removed(); + collider = value; + if (collider != null) + collider.Added(this); + } + } + + public float Width + { + get + { + if (collider == null) + return Entity.Width; + else + return collider.Width; + } + } + + public float Height + { + get + { + if (collider == null) + return Entity.Height; + else + return collider.Height; + } + } + + public float Left + { + get + { + if (collider == null) + return Entity.Left; + else + return Entity.X + collider.Left; + } + + set + { + if (collider == null) + Entity.Left = value; + else + Entity.X = value - collider.Left; + } + } + + public float Right + { + get + { + if (collider == null) + return Entity.Right; + else + return Entity.X + collider.Right; + } + + set + { + if (collider == null) + Entity.Right = value; + else + Entity.X = value - collider.Right; + } + } + + public float Top + { + get + { + if (collider == null) + return Entity.Top; + else + return Entity.Y + collider.Top; + } + + set + { + if (collider == null) + Entity.Top = value; + else + Entity.Y = value - collider.Top; + } + } + + public float Bottom + { + get + { + if (collider == null) + return Entity.Bottom; + else + return Entity.Y + collider.Bottom; + } + + set + { + if (collider == null) + Entity.Bottom = value; + else + Entity.Y = value - collider.Bottom; + } + } + + public float CenterX + { + get + { + if (collider == null) + return Entity.CenterX; + else + return Entity.X + collider.CenterX; + } + + set + { + if (collider == null) + Entity.CenterX = value; + else + Entity.X = value - collider.CenterX; + } + } + + public float CenterY + { + get + { + if (collider == null) + return Entity.CenterY; + else + return Entity.Y + collider.CenterY; + } + + set + { + if (collider == null) + Entity.CenterY = value; + else + Entity.Y = value - collider.CenterY; + } + } + + public Vector2 TopLeft + { + get + { + return new Vector2(Left, Top); + } + + set + { + Left = value.X; + Top = value.Y; + } + } + + public Vector2 TopRight + { + get + { + return new Vector2(Right, Top); + } + + set + { + Right = value.X; + Top = value.Y; + } + } + + public Vector2 BottomLeft + { + get + { + return new Vector2(Left, Bottom); + } + + set + { + Left = value.X; + Bottom = value.Y; + } + } + + public Vector2 BottomRight + { + get + { + return new Vector2(Right, Bottom); + } + + set + { + Right = value.X; + Bottom = value.Y; + } + } + + public Vector2 Center + { + get + { + return new Vector2(CenterX, CenterY); + } + + set + { + CenterX = value.X; + CenterY = value.Y; + } + } + + public Vector2 CenterLeft + { + get + { + return new Vector2(Left, CenterY); + } + + set + { + Left = value.X; + CenterY = value.Y; + } + } + + public Vector2 CenterRight + { + get + { + return new Vector2(Right, CenterY); + } + + set + { + Right = value.X; + CenterY = value.Y; + } + } + + public Vector2 TopCenter + { + get + { + return new Vector2(CenterX, Top); + } + + set + { + CenterX = value.X; + Top = value.Y; + } + } + + public Vector2 BottomCenter + { + get + { + return new Vector2(CenterX, Bottom); + } + + set + { + CenterX = value.X; + Bottom = value.Y; + } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Component.cs b/MonocleEngineDemo/Monocle/Components/Component.cs new file mode 100644 index 0000000..2af564d --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Component.cs @@ -0,0 +1,98 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Monocle +{ + public class Component + { + public Entity Entity { get; private set; } + public bool Active; + public bool Visible; + + public Component(bool active, bool visible) + { + Active = active; + Visible = visible; + } + + public virtual void Added(Entity entity) + { + Entity = entity; + if (Scene != null) + Scene.Tracker.ComponentAdded(this); + } + + public virtual void Removed(Entity entity) + { + if (Scene != null) + Scene.Tracker.ComponentRemoved(this); + Entity = null; + } + + public virtual void EntityAdded(Scene scene) + { + scene.Tracker.ComponentAdded(this); + } + + public virtual void EntityRemoved(Scene scene) + { + scene.Tracker.ComponentRemoved(this); + } + + public virtual void SceneEnd(Scene scene) + { + + } + + public virtual void EntityAwake() + { + + } + + public virtual void Update() + { + + } + + public virtual void Render() + { + + } + + public virtual void DebugRender(Camera camera) + { + + } + + public virtual void HandleGraphicsReset() + { + + } + + public virtual void HandleGraphicsCreate() + { + + } + + public void RemoveSelf() + { + if (Entity != null) + Entity.Remove(this); + } + + public T SceneAs() where T : Scene + { + return Scene as T; + } + + public T EntityAs() where T : Entity + { + return Entity as T; + } + + public Scene Scene + { + get { return Entity?.Scene; } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Graphics/GraphicsComponent.cs b/MonocleEngineDemo/Monocle/Components/Graphics/GraphicsComponent.cs new file mode 100644 index 0000000..6e8ae8d --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Graphics/GraphicsComponent.cs @@ -0,0 +1,99 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Monocle +{ + public abstract class GraphicsComponent : Component + { + public Vector2 Position; + public Vector2 Origin; + public Vector2 Scale = Vector2.One; + public float Rotation; + public Color Color = Color.White; + public SpriteEffects Effects = SpriteEffects.None; + + public GraphicsComponent(bool active) + : base(active, true) + { + + } + + public float X + { + get { return Position.X; } + set { Position.X = value; } + } + + public float Y + { + get { return Position.Y; } + set { Position.Y = value; } + } + + public bool FlipX + { + get + { + return (Effects & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally; + } + + set + { + Effects = value ? (Effects | SpriteEffects.FlipHorizontally) : (Effects & ~SpriteEffects.FlipHorizontally); + } + } + + public bool FlipY + { + get + { + return (Effects & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically; + } + + set + { + Effects = value ? (Effects | SpriteEffects.FlipVertically) : (Effects & ~SpriteEffects.FlipVertically); + } + } + + public Vector2 RenderPosition + { + get + { + return (Entity == null ? Vector2.Zero : Entity.Position) + Position; + } + + set + { + Position = value - (Entity == null ? Vector2.Zero : Entity.Position); + } + } + + public void DrawOutline(int offset = 1) + { + DrawOutline(Color.Black, offset); + } + + public void DrawOutline(Color color, int offset = 1) + { + Vector2 pos = Position; + Color was = Color; + Color = color; + + for (int i = -1; i < 2; i++) + { + for (int j = -1; j < 2; j++) + { + if (i != 0 || j != 0) + { + Position = pos + new Vector2(i * offset, j * offset); + Render(); + } + } + } + + Position = pos; + Color = was; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Graphics/Image.cs b/MonocleEngineDemo/Monocle/Components/Graphics/Image.cs new file mode 100644 index 0000000..ff33933 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Graphics/Image.cs @@ -0,0 +1,65 @@ +using Microsoft.Xna.Framework; + +namespace Monocle +{ + public class Image : GraphicsComponent + { + public MTexture Texture; + + public Image(MTexture texture) + : base(false) + { + Texture = texture; + } + + internal Image(MTexture texture, bool active) + : base(active) + { + Texture = texture; + } + + public override void Render() + { + if (Texture != null) + Texture.Draw(RenderPosition, Origin, Color, Scale, Rotation, Effects); + } + + public virtual float Width + { + get { return Texture.Width; } + } + + public virtual float Height + { + get { return Texture.Height; } + } + + public Image SetOrigin(float x, float y) + { + Origin.X = x; + Origin.Y = y; + return this; + } + + public Image CenterOrigin() + { + Origin.X = Width / 2f; + Origin.Y = Height / 2f; + return this; + } + + public Image JustifyOrigin(Vector2 at) + { + Origin.X = Width * at.X; + Origin.Y = Height * at.Y; + return this; + } + + public Image JustifyOrigin(float x, float y) + { + Origin.X = Width * x; + Origin.Y = Height * y; + return this; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Graphics/ParticleEmitter.cs b/MonocleEngineDemo/Monocle/Components/Graphics/ParticleEmitter.cs new file mode 100644 index 0000000..8642963 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Graphics/ParticleEmitter.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Xna.Framework; + +namespace Monocle +{ + public class ParticleEmitter : Component + { + + public ParticleSystem System; + public ParticleType Type; + + public Entity Track; + public float Interval; + public Vector2 Position; + public Vector2 Range; + public int Amount; + public float? Direction; + + private float timer = 0f; + + public ParticleEmitter(ParticleSystem system, ParticleType type, Vector2 position, Vector2 range, int amount, float interval) : base(true, false) + { + System = system; + Type = type; + Position = position; + Range = range; + Amount = amount; + Interval = interval; + } + + public ParticleEmitter(ParticleSystem system, ParticleType type, Vector2 position, Vector2 range, float direction, int amount, float interval) + : this(system, type, position, range, amount, interval) + { + Direction = direction; + } + + public ParticleEmitter(ParticleSystem system, ParticleType type, Entity track, Vector2 position, Vector2 range, float direction, int amount, float interval) + : this(system, type, position, range, amount, interval) + { + Direction = direction; + Track = track; + } + + public void SimulateCycle() + { + Simulate(Type.LifeMax); + } + + public void Simulate(float duration) + { + var steps = duration / Interval; + for (var i = 0; i < steps; i++) + { + for (int j = 0; j < Amount; j++) + { + // create the particle + var particle = new Particle(); + var pos = Entity.Position + Position + Calc.Random.Range(-Range, Range); + if (Direction.HasValue) + particle = Type.Create(ref particle, pos, Direction.Value); + else + particle = Type.Create(ref particle, pos); + particle.Track = Track; + + // simulate for a duration + var simulateFor = duration - Interval * i; + if (particle.SimulateFor(simulateFor)) + System.Add(particle); + } + } + } + + public void Emit() + { + if (Direction.HasValue) + System.Emit(Type, Amount, Entity.Position + Position, Range, Direction.Value); + else + System.Emit(Type, Amount, Entity.Position + Position, Range); + } + + public override void Update() + { + timer -= Engine.DeltaTime; + if (timer <= 0) + { + timer = Interval; + Emit(); + } + } + + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Graphics/PixelText.cs b/MonocleEngineDemo/Monocle/Components/Graphics/PixelText.cs new file mode 100644 index 0000000..fad7ea3 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Graphics/PixelText.cs @@ -0,0 +1,125 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Monocle +{ + public class PixelText : Component + { + + private struct Char + { + public Vector2 Offset; + public PixelFontCharacter CharData; + public Rectangle Bounds; + } + + private List characters = new List(); + private PixelFont font; + private PixelFontSize size; + private string text; + private bool dirty; + + public PixelFont Font + { + get { return font; } + set + { + if (value != font) + dirty = true; + font = value; + } + } + + public float Size + { + get { return size.Size; } + set + { + if (value != size.Size) + dirty = true; + size = font.Get(value); + } + } + + public string Text + { + get { return text; } + set + { + if (value != text) + dirty = true; + text = value; + } + } + + public Vector2 Position = new Vector2(); + public Color Color = Color.White; + public Vector2 Scale = Vector2.One; + public int Width { get; private set; } + public int Height { get; private set; } + + public PixelText(PixelFont font, string text, Color color) + : base(false, true) + { + Font = font; + Text = text; + Color = color; + Text = text; + size = Font.Sizes[0]; + Refresh(); + } + + public void Refresh() + { + dirty = false; + characters.Clear(); + + var widest = 0; + var lines = 1; + var offset = Vector2.Zero; + + for (int i = 0; i < text.Length; i++) + { + // new line + if (text[i] == '\n') + { + offset.X = 0; + offset.Y += size.LineHeight; + lines++; + } + + // add char + var fontChar = size.Get(text[i]); + if (fontChar != null) + { + characters.Add(new Char() + { + Offset = offset + new Vector2(fontChar.XOffset, fontChar.YOffset), + CharData = fontChar, + Bounds = fontChar.Texture.ClipRect, + }); + + if (offset.X > widest) + widest = (int)offset.X; + offset.X += fontChar.XAdvance; + } + } + + Width = widest; + Height = lines * size.LineHeight; + } + + public override void Render() + { + if (dirty) + Refresh(); + + for (var i = 0; i < characters.Count; i++) + characters[i].CharData.Texture.Draw(Position + characters[i].Offset, Vector2.Zero, Color); + } + + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Graphics/Sprite.cs b/MonocleEngineDemo/Monocle/Components/Graphics/Sprite.cs new file mode 100644 index 0000000..ba577e0 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Graphics/Sprite.cs @@ -0,0 +1,520 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Monocle +{ + public class Sprite : Image + { + public float Rate = 1f; + public bool UseRawDeltaTime; + public Vector2? Justify; + public Action OnFinish; + public Action OnLoop; + public Action OnFrameChange; + public Action OnLastFrame; + public Action OnChange; + + private Atlas atlas; + public string Path; + private Dictionary animations; + private Animation currentAnimation; + private float animationTimer; + private int width; + private int height; + + public Sprite(Atlas atlas, string path) + : base(null, true) + { + this.atlas = atlas; + this.Path = path; + animations = new Dictionary(StringComparer.OrdinalIgnoreCase); + CurrentAnimationID = ""; + } + + public void Reset(Atlas atlas, string path) + { + this.atlas = atlas; + this.Path = path; + animations = new Dictionary(StringComparer.OrdinalIgnoreCase); + + currentAnimation = null; + CurrentAnimationID = ""; + OnFinish = null; + OnLoop = null; + OnFrameChange = null; + OnChange = null; + Animating = false; + } + + public MTexture GetFrame(string animation, int frame) + { + return animations[animation].Frames[frame]; + } + + public Vector2 Center + { + get + { + return new Vector2(Width / 2, Height / 2); + } + } + + public override void Update() + { + if (Animating) + { + //Timer + if (UseRawDeltaTime) + animationTimer += Engine.RawDeltaTime * Rate; + else + animationTimer += Engine.DeltaTime * Rate; + + //Next Frame + if (Math.Abs(animationTimer) >= currentAnimation.Delay) + { + CurrentAnimationFrame += Math.Sign(animationTimer); + animationTimer -= Math.Sign(animationTimer) * currentAnimation.Delay; + + //End of Animation + if (CurrentAnimationFrame < 0 || CurrentAnimationFrame >= currentAnimation.Frames.Length) + { + var was = CurrentAnimationID; + if (OnLastFrame != null) + OnLastFrame(CurrentAnimationID); + + // only do stuff if OnLastFrame didn't just change the animation + if (was == CurrentAnimationID) + { + //Looped + if (currentAnimation.Goto != null) + { + CurrentAnimationID = currentAnimation.Goto.Choose(); + + if (OnChange != null) + OnChange(LastAnimationID, CurrentAnimationID); + + LastAnimationID = CurrentAnimationID; + currentAnimation = animations[LastAnimationID]; + if (CurrentAnimationFrame < 0) + CurrentAnimationFrame = currentAnimation.Frames.Length - 1; + else + CurrentAnimationFrame = 0; + + SetFrame(currentAnimation.Frames[CurrentAnimationFrame]); + if (OnLoop != null) + OnLoop(CurrentAnimationID); + } + else + { + //Ended + if (CurrentAnimationFrame < 0) + CurrentAnimationFrame = 0; + else + CurrentAnimationFrame = currentAnimation.Frames.Length - 1; + + Animating = false; + var id = CurrentAnimationID; + CurrentAnimationID = ""; + currentAnimation = null; + animationTimer = 0; + if (OnFinish != null) + OnFinish(id); + } + } + } + else + { + //Continue Animation + SetFrame(currentAnimation.Frames[CurrentAnimationFrame]); + } + } + } + } + + private void SetFrame(MTexture texture) + { + if (texture == Texture) + return; + + Texture = texture; + if (Justify.HasValue) + Origin = new Vector2(Texture.Width * Justify.Value.X, Texture.Height * Justify.Value.Y); + if (OnFrameChange != null) + OnFrameChange(CurrentAnimationID); + } + + public void SetAnimationFrame(int frame) + { + animationTimer = 0; + CurrentAnimationFrame = frame % currentAnimation.Frames.Length; + SetFrame(currentAnimation.Frames[CurrentAnimationFrame]); + } + + #region Define Animations + + public void AddLoop(string id, string path, float delay) + { + animations[id] = new Animation() + { + Delay = delay, + Frames = GetFrames(path), + Goto = new Chooser(id, 1f) + }; + } + + public void AddLoop(string id, string path, float delay, params int[] frames) + { + animations[id] = new Animation() + { + Delay = delay, + Frames = GetFrames(path, frames), + Goto = new Chooser(id, 1f) + }; + } + + public void Add(string id, string path) + { + animations[id] = new Animation() + { + Delay = 0, + Frames = GetFrames(path), + Goto = null + }; + } + + public void Add(string id, string path, float delay) + { + animations[id] = new Animation() + { + Delay = delay, + Frames = GetFrames(path), + Goto = null + }; + } + + public void Add(string id, string path, float delay, params int[] frames) + { + animations[id] = new Animation() + { + Delay = delay, + Frames = GetFrames(path, frames), + Goto = null + }; + } + + public void Add(string id, string path, float delay, string into) + { + animations[id] = new Animation() + { + Delay = delay, + Frames = GetFrames(path), + Goto = Chooser.FromString(into) + }; + } + + public void Add(string id, string path, float delay, Chooser into) + { + animations[id] = new Animation() + { + Delay = delay, + Frames = GetFrames(path), + Goto = into + }; + } + + public void Add(string id, string path, float delay, string into, params int[] frames) + { + animations[id] = new Animation() + { + Delay = delay, + Frames = GetFrames(path, frames), + Goto = Chooser.FromString(into) + }; + } + + public void Add(string id, string path, float delay, Chooser into, params int[] frames) + { + animations[id] = new Animation() + { + Delay = delay, + Frames = GetFrames(path, frames), + Goto = into + }; + } + + private MTexture[] GetFrames(string path, int[] frames = null) + { + MTexture[] ret; + + if (frames == null || frames.Length <= 0) + ret = atlas.GetAtlasSubtextures(this.Path + path).ToArray(); + else + { + var fullPath = this.Path + path; + var finalFrames = new MTexture[frames.Length]; + for (int i = 0; i < frames.Length; i++) + { + var frame = atlas.GetAtlasSubtexturesAt(fullPath, frames[i]); + if (frame == null) + + throw new Exception("Can't find sprite " + fullPath + " with index " + frames[i]); + finalFrames[i] = frame; + } + ret = finalFrames; + } + +#if DEBUG + if (ret.Length == 0) + throw new Exception("No frames found for animation path '" + this.Path + path + "'!"); +#endif + + width = Math.Max(ret[0].Width, width); + height = Math.Max(ret[0].Height, height); + + return ret; + } + + public void ClearAnimations() + { + animations.Clear(); + } + + #endregion + + #region Animation Playback + + public void Play(string id, bool restart = false, bool randomizeFrame = false) + { + if (CurrentAnimationID != id || restart) + { +#if DEBUG + if (!animations.ContainsKey(id)) + throw new Exception("No Animation defined for ID: " + id); +#endif + if (OnChange != null) + OnChange(LastAnimationID, id); + + LastAnimationID = CurrentAnimationID = id; + currentAnimation = animations[id]; + Animating = currentAnimation.Delay > 0; + + if (randomizeFrame) + { + animationTimer = Calc.Random.NextFloat(currentAnimation.Delay); + CurrentAnimationFrame = Calc.Random.Next(currentAnimation.Frames.Length); + } + else + { + animationTimer = 0; + CurrentAnimationFrame = 0; + } + + SetFrame(currentAnimation.Frames[CurrentAnimationFrame]); + } + } + + public void PlayOffset(string id, float offset, bool restart = false) + { + if (CurrentAnimationID != id || restart) + { +#if DEBUG + if (!animations.ContainsKey(id)) + throw new Exception("No Animation defined for ID: " + id); +#endif + if (OnChange != null) + OnChange(LastAnimationID, id); + + LastAnimationID = CurrentAnimationID = id; + currentAnimation = animations[id]; + + if (currentAnimation.Delay > 0) + { + Animating = true; + float at = (currentAnimation.Delay * currentAnimation.Frames.Length) * offset; + + CurrentAnimationFrame = 0; + while (at >= currentAnimation.Delay) + { + CurrentAnimationFrame++; + at -= currentAnimation.Delay; + } + + CurrentAnimationFrame %= currentAnimation.Frames.Length; + animationTimer = at; + SetFrame(currentAnimation.Frames[CurrentAnimationFrame]); + } + else + { + animationTimer = 0; + Animating = false; + CurrentAnimationFrame = 0; + SetFrame(currentAnimation.Frames[0]); + } + } + } + + public IEnumerator PlayRoutine(string id, bool restart = false) + { + Play(id, restart); + return PlayUtil(); + } + + public IEnumerator ReverseRoutine(string id, bool restart = false) + { + Reverse(id, restart); + return PlayUtil(); + } + + private IEnumerator PlayUtil() + { + while (Animating) + yield return null; + } + + public void Reverse(string id, bool restart = false) + { + Play(id, restart); + if (Rate > 0) + Rate *= -1; + } + + public bool Has(string id) + { + return id != null && animations.ContainsKey(id); + } + + public void Stop() + { + Animating = false; + currentAnimation = null; + CurrentAnimationID = ""; + } + + #endregion + + #region Properties + + public bool Animating + { + get; private set; + } + + public string CurrentAnimationID + { + get; private set; + } + + public string LastAnimationID + { + get; private set; + } + + public int CurrentAnimationFrame + { + get; private set; + } + + public int CurrentAnimationTotalFrames + { + get + { + if (currentAnimation != null) + return currentAnimation.Frames.Length; + else + return 0; + } + } + + public override float Width + { + get + { + return width; + } + } + + public override float Height + { + get + { + return height; + } + } + + #endregion + + #region Cloning from SpriteBank + + internal Sprite() + : base(null, true) + { + + } + + internal Sprite CreateClone() + { + return CloneInto(new Sprite()); + } + + internal Sprite CloneInto(Sprite clone) + { + clone.Texture = Texture; + clone.Position = Position; + clone.Justify = Justify; + clone.Origin = Origin; + + clone.animations = new Dictionary(animations, StringComparer.OrdinalIgnoreCase); + clone.currentAnimation = currentAnimation; + clone.animationTimer = animationTimer; + clone.width = width; + clone.height = height; + + clone.Animating = Animating; + clone.CurrentAnimationID = CurrentAnimationID; + clone.LastAnimationID = LastAnimationID; + clone.CurrentAnimationFrame = CurrentAnimationFrame; + + return clone; + } + + #endregion + + public void DrawSubrect(Vector2 offset, Rectangle rectangle) + { + if (Texture != null) + { + var clip = Texture.GetRelativeRect(rectangle); + var clipOffset = new Vector2(-Math.Min(rectangle.X - Texture.DrawOffset.X, 0), -Math.Min(rectangle.Y - Texture.DrawOffset.Y, 0)); + Draw.SpriteBatch.Draw(Texture.Texture, RenderPosition + offset, clip, Color, Rotation, Origin - clipOffset, Scale, Effects, 0); + } + } + + public void LogAnimations() + { + StringBuilder str = new StringBuilder(); + + foreach (var kv in animations) + { + var anim = kv.Value; + + str.Append(kv.Key); + str.Append("\n{\n\t"); + str.Append(string.Join("\n\t", (object[])anim.Frames)); + str.Append("\n}\n"); + } + + Calc.Log(str.ToString()); + } + + private class Animation + { + public float Delay; + public MTexture[] Frames; + public Chooser Goto; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Graphics/Spritesheet.cs b/MonocleEngineDemo/Monocle/Components/Graphics/Spritesheet.cs new file mode 100644 index 0000000..2cabd16 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Graphics/Spritesheet.cs @@ -0,0 +1,243 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; + +namespace Monocle +{ + public class Spritesheet : Image + { + public int CurrentFrame; + public float Rate = 1; + public bool UseRawDeltaTime; + public Action OnFinish; + public Action OnLoop; + public Action OnAnimate; + + private Dictionary animations; + private Animation currentAnimation; + private float animationTimer; + private bool played; + + public Spritesheet(MTexture texture, int frameWidth, int frameHeight, int frameSep = 0) + : base(texture, true) + { + SetFrames(texture, frameWidth, frameHeight, frameSep); + animations = new Dictionary(); + } + + public void SetFrames(MTexture texture, int frameWidth, int frameHeight, int frameSep = 0) + { + List frames = new List(); + int x = 0, y = 0; + + while (y <= texture.Height - frameHeight) + { + while (x <= texture.Width - frameWidth) + { + frames.Add(texture.GetSubtexture(x, y, frameWidth, frameHeight)); + x += frameWidth + frameSep; + } + + y += frameHeight + frameSep; + x = 0; + } + + Frames = frames.ToArray(); + } + + public override void Update() + { + if (Animating && currentAnimation.Delay > 0) + { + //Timer + if (UseRawDeltaTime) + animationTimer += Engine.RawDeltaTime * Rate; + else + animationTimer += Engine.DeltaTime * Rate; + + //Next Frame + if (Math.Abs(animationTimer) >= currentAnimation.Delay) + { + CurrentAnimationFrame += Math.Sign(animationTimer); + animationTimer -= Math.Sign(animationTimer) * currentAnimation.Delay; + + //End of Animation + if (CurrentAnimationFrame < 0 || CurrentAnimationFrame >= currentAnimation.Frames.Length) + { + //Looped + if (currentAnimation.Loop) + { + CurrentAnimationFrame -= Math.Sign(CurrentAnimationFrame) * currentAnimation.Frames.Length; + CurrentFrame = currentAnimation.Frames[CurrentAnimationFrame]; + + if (OnAnimate != null) + OnAnimate(CurrentAnimationID); + if (OnLoop != null) + OnLoop(CurrentAnimationID); + } + else + { + //Ended + if (CurrentAnimationFrame < 0) + CurrentAnimationFrame = 0; + else + CurrentAnimationFrame = currentAnimation.Frames.Length - 1; + + Animating = false; + animationTimer = 0; + if (OnFinish != null) + OnFinish(CurrentAnimationID); + } + } + else + { + //Continue Animation + CurrentFrame = currentAnimation.Frames[CurrentAnimationFrame]; + if (OnAnimate != null) + OnAnimate(CurrentAnimationID); + } + } + } + } + + public override void Render() + { + Texture = Frames[CurrentFrame]; + base.Render(); + } + + #region Animation Definition + + public void Add(T id, bool loop, float delay, params int[] frames) + { +#if DEBUG + foreach (var i in frames) + if (i >= Frames.Length) + throw new IndexOutOfRangeException("Specified frames is out of max range for this Spritesheet"); +#endif + + animations[id] = new Animation() + { + Delay = delay, + Frames = frames, + Loop = loop, + }; + } + + public void Add(T id, float delay, params int[] frames) + { + Add(id, true, delay, frames); + } + + public void Add(T id, int frame) + { + Add(id, false, 0, frame); + } + + public void ClearAnimations() + { + animations.Clear(); + } + + #endregion + + #region Animation Playback + + public bool IsPlaying(T id) + { + if (!played) + return false; + else if (CurrentAnimationID == null) + return id == null; + else + return CurrentAnimationID.Equals(id); + } + + public void Play(T id, bool restart = false) + { + if (!IsPlaying(id) || restart) + { +#if DEBUG + if (!animations.ContainsKey(id)) + throw new Exception("No Animation defined for ID: " + id.ToString()); +#endif + CurrentAnimationID = id; + currentAnimation = animations[id]; + animationTimer = 0; + CurrentAnimationFrame = 0; + played = true; + + Animating = currentAnimation.Frames.Length > 1; + CurrentFrame = currentAnimation.Frames[0]; + } + } + + public void Reverse(T id, bool restart = false) + { + Play(id, restart); + if (Rate > 0) + Rate *= -1; + } + + public void Stop() + { + Animating = false; + played = false; + } + + #endregion + + #region Properties + + public MTexture[] Frames + { + get; private set; + } + + public bool Animating + { + get; private set; + } + + public T CurrentAnimationID + { + get; private set; + } + + public int CurrentAnimationFrame + { + get; private set; + } + + public override float Width + { + get + { + if (Frames.Length > 0) + return Frames[0].Width; + else + return 0; + } + } + + public override float Height + { + get + { + if (Frames.Length > 0) + return Frames[0].Height; + else + return 0; + } + } + + #endregion + + private struct Animation + { + public float Delay; + public int[] Frames; + public bool Loop; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Graphics/Text/NumberText.cs b/MonocleEngineDemo/Monocle/Components/Graphics/Text/NumberText.cs new file mode 100644 index 0000000..a75472d --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Graphics/Text/NumberText.cs @@ -0,0 +1,70 @@ +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace Monocle +{ + public class NumberText : GraphicsComponent + { + private SpriteFont font; + private int value; + private string prefix; + private string drawString; + + private bool centered; + + public Action OnValueUpdate; + + public NumberText(SpriteFont font, string prefix, int value, bool centered = false) + : base(false) + { + this.font = font; + this.prefix = prefix; + this.value = value; + this.centered = centered; + UpdateString(); + } + + public int Value + { + get + { + return value; + } + + set + { + if (this.value != value) + { + int oldValue = this.value; + this.value = value; + UpdateString(); + if (OnValueUpdate != null) + OnValueUpdate(oldValue); + } + } + } + + public void UpdateString() + { + drawString = prefix + value.ToString(); + + if (centered) + Origin = Calc.Floor(font.MeasureString(drawString) / 2); + } + + public override void Render() + { + Draw.SpriteBatch.DrawString(font, drawString, RenderPosition, Color, Rotation, Origin, Scale, Effects, 0); + } + + public float Width + { + get { return font.MeasureString(drawString).X; } + } + + public float Height + { + get { return font.MeasureString(drawString).Y; } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Graphics/Text/OutlineText.cs b/MonocleEngineDemo/Monocle/Components/Graphics/Text/OutlineText.cs new file mode 100644 index 0000000..b2dfc64 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Graphics/Text/OutlineText.cs @@ -0,0 +1,38 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Monocle +{ + public class OutlineText : Text + { + public Color OutlineColor = Color.Black; + public int OutlineOffset = 1; + + public OutlineText(SpriteFont font, string text, Vector2 position, Color color, HorizontalAlign horizontalAlign = HorizontalAlign.Center, VerticalAlign verticalAlign = VerticalAlign.Center) + : base(font, text, position, color, horizontalAlign, verticalAlign) + { + + } + + public OutlineText(SpriteFont font, string text, Vector2 position, HorizontalAlign horizontalAlign = HorizontalAlign.Center, VerticalAlign verticalAlign = VerticalAlign.Center) + : this(font, text, position, Color.White, horizontalAlign, verticalAlign) + { + + } + + public OutlineText(SpriteFont font, string text) + : this(font, text, Vector2.Zero, Color.White, HorizontalAlign.Center, VerticalAlign.Center) + { + + } + + public override void Render() + { + for (int i = -1; i < 2; i++) + for (int j = -1; j < 2; j++) + if (i != 0 || j != 0) + Draw.SpriteBatch.DrawString(Font, DrawText, RenderPosition + new Vector2(i * OutlineOffset, j * OutlineOffset), OutlineColor, Rotation, Origin, Scale, Effects, 0); + base.Render(); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Graphics/Text/Text.cs b/MonocleEngineDemo/Monocle/Components/Graphics/Text/Text.cs new file mode 100644 index 0000000..1a7e090 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Graphics/Text/Text.cs @@ -0,0 +1,115 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Monocle +{ + public class Text : GraphicsComponent + { + public enum HorizontalAlign { Left, Center, Right }; + public enum VerticalAlign { Top, Center, Bottom }; + + private SpriteFont font; + private string text; + private HorizontalAlign horizontalOrigin; + private VerticalAlign verticalOrigin; + private Vector2 size; + + public Text(SpriteFont font, string text, Vector2 position, Color color, HorizontalAlign horizontalAlign = HorizontalAlign.Center, VerticalAlign verticalAlign = VerticalAlign.Center) + : base(false) + { + this.font = font; + this.text = text; + Position = position; + Color = color; + this.horizontalOrigin = horizontalAlign; + this.verticalOrigin = verticalAlign; + UpdateSize(); + } + + public Text(SpriteFont font, string text, Vector2 position, HorizontalAlign horizontalAlign = HorizontalAlign.Center, VerticalAlign verticalAlign = VerticalAlign.Center) + : this(font, text, position, Color.White, horizontalAlign, verticalAlign) + { + + } + + public SpriteFont Font + { + get { return font; } + set + { + font = value; + UpdateSize(); + } + } + + public string DrawText + { + get { return text; } + set + { + text = value; + UpdateSize(); + } + } + + public HorizontalAlign HorizontalOrigin + { + get { return horizontalOrigin; } + set + { + horizontalOrigin = value; + UpdateCentering(); + } + } + + public VerticalAlign VerticalOrigin + { + get { return verticalOrigin; } + set + { + verticalOrigin = value; + UpdateCentering(); + } + } + + public float Width + { + get { return size.X; } + } + + public float Height + { + get { return size.Y; } + } + + private void UpdateSize() + { + size = font.MeasureString(text); + UpdateCentering(); + } + + private void UpdateCentering() + { + if (horizontalOrigin == HorizontalAlign.Left) + Origin.X = 0; + else if (horizontalOrigin == HorizontalAlign.Center) + Origin.X = size.X / 2; + else + Origin.X = size.X; + + if (verticalOrigin == VerticalAlign.Top) + Origin.Y = 0; + else if (verticalOrigin == VerticalAlign.Center) + Origin.Y = size.Y / 2; + else + Origin.Y = size.Y; + + Origin = Origin.Floor(); + } + + public override void Render() + { + Draw.SpriteBatch.DrawString(font, text, RenderPosition, Color, Rotation, Origin, Scale, Effects, 0); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Graphics/Text/TimerText.cs b/MonocleEngineDemo/Monocle/Components/Graphics/Text/TimerText.cs new file mode 100644 index 0000000..c38f683 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Graphics/Text/TimerText.cs @@ -0,0 +1,136 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace Monocle +{ + public class TimerText : GraphicsComponent + { + private const float DELTA_TIME = 1 / 60f; + + public enum CountModes { Down, Up }; + public enum TimerModes { SecondsMilliseconds }; + + private SpriteFont font; + private int frames; + private TimerModes timerMode; + private Vector2 justify; + + public string Text { get; private set; } + public Action OnComplete; + public CountModes CountMode; + + public TimerText(SpriteFont font, TimerModes mode, CountModes countMode, int frames, Vector2 justify, Action onComplete = null) + : base(true) + { + this.font = font; + this.timerMode = mode; + this.CountMode = countMode; + this.frames = frames; + this.justify = justify; + +#if DEBUG + if (frames < 0) + throw new Exception("Frames must be larger than or equal to zero!"); +#endif + + OnComplete = onComplete; + + UpdateText(); + CalculateOrigin(); + } + + private void UpdateText() + { + switch (timerMode) + { + case TimerModes.SecondsMilliseconds: + float seconds = (frames / 60) + (frames % 60) * DELTA_TIME; + Text = seconds.ToString("0.00"); + break; + } + } + + private void CalculateOrigin() + { + Origin = (font.MeasureString(Text) * justify).Floor(); + } + + public override void Update() + { + base.Update(); + + if (CountMode == CountModes.Down) + { + if (frames > 0) + { + frames--; + if (frames == 0 && OnComplete != null) + OnComplete(); + + UpdateText(); + CalculateOrigin(); + } + } + else + { + frames++; + UpdateText(); + CalculateOrigin(); + } + } + + public override void Render() + { + Draw.SpriteBatch.DrawString(font, Text, RenderPosition, Color, Rotation, Origin, Scale, Effects, 0); + } + + public SpriteFont Font + { + get { return font; } + set + { + font = value; + CalculateOrigin(); + } + } + + public int Frames + { + get { return frames; } + set + { +#if DEBUG + if (value < 0) + throw new Exception("Frames must be larger than or equal to zero!"); +#endif + if (frames != value) + { + frames = value; + UpdateText(); + CalculateOrigin(); + } + } + } + + public Vector2 Justify + { + get { return justify; } + set + { + justify = value; + CalculateOrigin(); + } + } + + public float Width + { + get { return font.MeasureString(Text).X; } + } + + public float Height + { + get { return font.MeasureString(Text).Y; } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Graphics/TileGrid.cs b/MonocleEngineDemo/Monocle/Components/Graphics/TileGrid.cs new file mode 100644 index 0000000..e5fc73d --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Graphics/TileGrid.cs @@ -0,0 +1,196 @@ +using Microsoft.Xna.Framework; +using Monocle; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Monocle +{ + public class TileGrid : Component + { + public Vector2 Position; + public Color Color = Color.White; + public int VisualExtend = 0; + public VirtualMap Tiles; + public Camera ClipCamera; + public float Alpha = 1f; + + public TileGrid(int tileWidth, int tileHeight, int tilesX, int tilesY) + : base(false, true) + { + TileWidth = tileWidth; + TileHeight = tileHeight; + Tiles = new VirtualMap(tilesX, tilesY); + } + + #region Properties + + public int TileWidth + { + get; private set; + } + + public int TileHeight + { + get; private set; + } + + public int TilesX + { + get + { + return Tiles.Columns; + } + } + + public int TilesY + { + get + { + return Tiles.Rows; + } + } + + #endregion + + public void Populate(Tileset tileset, int[,] tiles, int offsetX = 0, int offsetY = 0) + { + for (int x = 0; x < tiles.GetLength(0) && x + offsetX < TilesX; x++) + for (int y = 0; y < tiles.GetLength(1) && y + offsetY < TilesY; y++) + Tiles[x + offsetX, y + offsetY] = tileset[tiles[x, y]]; + } + + public void Overlay(Tileset tileset, int[,] tiles, int offsetX = 0, int offsetY = 0) + { + for (int x = 0; x < tiles.GetLength(0) && x + offsetX < TilesX; x++) + for (int y = 0; y < tiles.GetLength(1) && y + offsetY < TilesY; y++) + if (tiles[x, y] >= 0) + Tiles[x + offsetX, y + offsetY] = tileset[tiles[x, y]]; + } + + public void Extend(int left, int right, int up, int down) + { + Position -= new Vector2(left * TileWidth, up * TileHeight); + + int newWidth = TilesX + left + right; + int newHeight = TilesY + up + down; + if (newWidth <= 0 || newHeight <= 0) + { + Tiles = new VirtualMap(0, 0); + return; + } + + var newTiles = new VirtualMap(newWidth, newHeight); + + //Center + for (int x = 0; x < TilesX; x++) + { + for (int y = 0; y < TilesY; y++) + { + int atX = x + left; + int atY = y + up; + + if (atX >= 0 && atX < newWidth && atY >= 0 && atY < newHeight) + newTiles[atX, atY] = Tiles[x, y]; + } + } + + //Left + for (int x = 0; x < left; x++) + for (int y = 0; y < newHeight; y++) + newTiles[x, y] = Tiles[0, Calc.Clamp(y - up, 0, TilesY - 1)]; + + //Right + for (int x = newWidth - right; x < newWidth; x++) + for (int y = 0; y < newHeight; y++) + newTiles[x, y] = Tiles[TilesX - 1, Calc.Clamp(y - up, 0, TilesY - 1)]; + + //Top + for (int y = 0; y < up; y++) + for (int x = 0; x < newWidth; x++) + newTiles[x, y] = Tiles[Calc.Clamp(x - left, 0, TilesX - 1), 0]; + + //Bottom + for (int y = newHeight - down; y < newHeight; y++) + for (int x = 0; x < newWidth; x++) + newTiles[x, y] = Tiles[Calc.Clamp(x - left, 0, TilesX - 1), TilesY - 1]; + + Tiles = newTiles; + } + + public void FillRect(int x, int y, int columns, int rows, MTexture tile) + { + int left = Math.Max(0, x); + int top = Math.Max(0, y); + int right = Math.Min(TilesX, x + columns); + int bottom = Math.Min(TilesY, y + rows); + + for (int tx = left; tx < right; tx++) + for (int ty = top; ty < bottom; ty++) + Tiles[tx, ty] = tile; + } + + public void Clear() + { + for (int tx = 0; tx < TilesX; tx++) + for (int ty = 0; ty < TilesY; ty++) + Tiles[tx, ty] = null; + } + + public Rectangle GetClippedRenderTiles() + { + var pos = Entity.Position + Position; + + int left, top, right, bottom; + if (ClipCamera == null) + { + //throw new Exception("NULL CLIP: " + Entity.GetType().ToString()); + left = -VisualExtend; + top = -VisualExtend; + right = TilesX + VisualExtend; + bottom = TilesY + VisualExtend; + } + else + { + var camera = ClipCamera; + left = (int)Math.Max(0, Math.Floor((camera.Left - pos.X) / TileWidth) - VisualExtend); + top = (int)Math.Max(0, Math.Floor((camera.Top - pos.Y) / TileHeight) - VisualExtend); + right = (int)Math.Min(TilesX, Math.Ceiling((camera.Right - pos.X) / TileWidth) + VisualExtend); + bottom = (int)Math.Min(TilesY, Math.Ceiling((camera.Bottom - pos.Y) / TileHeight) + VisualExtend); + } + + // clamp + left = Math.Max(left, 0); + top = Math.Max(top, 0); + right = Math.Min(right, TilesX); + bottom = Math.Min(bottom, TilesY); + + return new Rectangle(left, top, right - left, bottom - top); + } + + public override void Render() + { + RenderAt(Entity.Position + Position); + } + + public void RenderAt(Vector2 position) + { + if (Alpha <= 0) + return; + + var clip = GetClippedRenderTiles(); + var color = Color * Alpha; + MTexture tile; + + for (int tx = clip.Left; tx < clip.Right; tx++) + for (int ty = clip.Top; ty < clip.Bottom; ty++) + { + tile = Tiles[tx, ty]; + if (tile != null) + tile.Draw(position + new Vector2(tx * TileWidth, ty * TileHeight), Vector2.Zero, color); + } + } + + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Logic/Alarm.cs b/MonocleEngineDemo/Monocle/Components/Logic/Alarm.cs new file mode 100644 index 0000000..f293daf --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Logic/Alarm.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; + +namespace Monocle +{ + public class Alarm : Component + { + public enum AlarmMode { Persist, Oneshot, Looping }; + + public Action OnComplete; + + public AlarmMode Mode { get; private set; } + public float Duration { get; private set; } + public float TimeLeft { get; private set; } + + #region Static + + private static Stack cached = new Stack(); + + public static Alarm Create(AlarmMode mode, Action onComplete, float duration = 1f, bool start = false) + { + Alarm alarm; + if (cached.Count == 0) + alarm = new Alarm(); + else + alarm = cached.Pop(); + + alarm.Init(mode, onComplete, duration, start); + return alarm; + } + + public static Alarm Set(Entity entity, float duration, Action onComplete, AlarmMode alarmMode = AlarmMode.Oneshot) + { + Alarm alarm = Alarm.Create(alarmMode, onComplete, duration, true); + entity.Add(alarm); + return alarm; + } + + #endregion + + private Alarm() + : base(false, false) + { + + } + + private void Init(AlarmMode mode, Action onComplete, float duration = 1f, bool start = false) + { +#if DEBUG + if (duration <= 0) + throw new Exception("Alarm duration cannot be less than zero"); +#endif + Mode = mode; + Duration = duration; + OnComplete = onComplete; + + Active = false; + TimeLeft = 0; + + if (start) + Start(); + } + + public override void Update() + { + TimeLeft -= Engine.DeltaTime; + if (TimeLeft <= 0) + { + TimeLeft = 0; + if (OnComplete != null) + OnComplete(); + + if (Mode == AlarmMode.Looping) + Start(); + else if (Mode == AlarmMode.Oneshot) + RemoveSelf(); + else if (TimeLeft <= 0) + Active = false; + } + } + + public override void Removed(Entity entity) + { + base.Removed(entity); + cached.Push(this); + } + + public void Start() + { + Active = true; + TimeLeft = Duration; + } + + public void Start(float duration) + { +#if DEBUG + if (duration <= 0) + throw new Exception("Alarm duration cannot be <= 0"); +#endif + + Duration = duration; + Start(); + } + + public void Stop() + { + Active = false; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Logic/Coroutine.cs b/MonocleEngineDemo/Monocle/Components/Logic/Coroutine.cs new file mode 100644 index 0000000..1bb1215 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Logic/Coroutine.cs @@ -0,0 +1,84 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Monocle +{ + public class Coroutine : Component + { + public bool Finished { get; private set; } + public bool RemoveOnComplete = true; + public bool UseRawDeltaTime = false; + + private Stack enumerators; + private float waitTimer; + private bool ended; + + public Coroutine(IEnumerator functionCall, bool removeOnComplete = true) + : base(true, false) + { + enumerators = new Stack(); + enumerators.Push(functionCall); + RemoveOnComplete = removeOnComplete; + } + + public Coroutine(bool removeOnComplete = true) + : base(false, false) + { + RemoveOnComplete = removeOnComplete; + enumerators = new Stack(); + } + + public override void Update() + { + ended = false; + + if (waitTimer > 0) + waitTimer -= (UseRawDeltaTime ? Engine.RawDeltaTime : Engine.DeltaTime); + else if (enumerators.Count > 0) + { + IEnumerator now = enumerators.Peek(); + if (now.MoveNext() && !ended) + { + if (now.Current is int) + waitTimer = (int)now.Current; + if (now.Current is float) + waitTimer = (float)now.Current; + else if (now.Current is IEnumerator) + enumerators.Push(now.Current as IEnumerator); + } + else if (!ended) + { + enumerators.Pop(); + if (enumerators.Count == 0) + { + Finished = true; + Active = false; + if (RemoveOnComplete) + RemoveSelf(); + } + } + } + } + + public void Cancel() + { + Active = false; + Finished = true; + waitTimer = 0; + enumerators.Clear(); + + ended = true; + } + + public void Replace(IEnumerator functionCall) + { + Active = true; + Finished = false; + waitTimer = 0; + enumerators.Clear(); + enumerators.Push(functionCall); + + ended = true; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Logic/CoroutineHolder.cs b/MonocleEngineDemo/Monocle/Components/Logic/CoroutineHolder.cs new file mode 100644 index 0000000..a543427 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Logic/CoroutineHolder.cs @@ -0,0 +1,90 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Monocle +{ + public class CoroutineHolder : Component + { + private List coroutineList; + private HashSet toRemove; + private int nextID; + private bool isRunning; + + public CoroutineHolder() + : base(true, false) + { + coroutineList = new List(); + toRemove = new HashSet(); + } + + public override void Update() + { + isRunning = true; + for (int i = 0; i < coroutineList.Count; i++) + { + var now = coroutineList[i].Data.Peek(); + + if (now.MoveNext()) + { + if (now.Current is IEnumerator) + coroutineList[i].Data.Push(now.Current as IEnumerator); + } + else + { + coroutineList[i].Data.Pop(); + if (coroutineList[i].Data.Count == 0) + toRemove.Add(coroutineList[i]); + } + } + isRunning = false; + + if (toRemove.Count > 0) + { + foreach (var r in toRemove) + coroutineList.Remove(r); + toRemove.Clear(); + } + } + + public void EndCoroutine(int id) + { + foreach (var c in coroutineList) + { + if (c.ID == id) + { + if (isRunning) + toRemove.Add(c); + else + coroutineList.Remove(c); + break; + } + } + } + + public int StartCoroutine(IEnumerator functionCall) + { + var data = new CoroutineData(nextID++, functionCall); + coroutineList.Add(data); + return data.ID; + } + + public static IEnumerator WaitForFrames(int frames) + { + for (int i = 0; i < frames; i++) + yield return 0; + } + + private class CoroutineData + { + public int ID; + public Stack Data; + + public CoroutineData(int id, IEnumerator functionCall) + { + ID = id; + Data = new Stack(); + Data.Push(functionCall); + } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Logic/CounterSet.cs b/MonocleEngineDemo/Monocle/Components/Logic/CounterSet.cs new file mode 100644 index 0000000..2cbbe60 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Logic/CounterSet.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Monocle +{ + public class CounterSet : Component + { + private Dictionary counters; + private float timer; + + public CounterSet() + : base(true, false) + { + counters = new Dictionary(); + } + + public float this[T index] + { + get + { + float value; + if (counters.TryGetValue(index, out value)) + return Math.Max(value - timer, 0); + else + return 0; + } + + set + { + counters[index] = timer + value; + } + } + + public bool Check(T index) + { + float value; + if (counters.TryGetValue(index, out value)) + return value - timer > 0; + else + return false; + } + + public override void Update() + { + timer += Engine.DeltaTime; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Logic/Shaker.cs b/MonocleEngineDemo/Monocle/Components/Logic/Shaker.cs new file mode 100644 index 0000000..82524c6 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Logic/Shaker.cs @@ -0,0 +1,90 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Monocle +{ + public class Shaker : Component + { + public Vector2 Value; + public float Interval = .05f; + public float Timer; + public bool RemoveOnFinish; + public Action OnShake; + + private bool on; + + public Shaker(bool on = true, Action onShake = null) + : base(true, false) + { + this.on = on; + OnShake = onShake; + } + + public Shaker(float time, bool removeOnFinish, Action onShake = null) + : this(true, onShake) + { + Timer = time; + RemoveOnFinish = removeOnFinish; + } + + public bool On + { + get + { + return on; + } + + set + { + on = value; + if (!on) + { + Timer = 0; + if (Value != Vector2.Zero) + { + Value = Vector2.Zero; + if (OnShake != null) + OnShake(Vector2.Zero); + } + } + } + } + + public Shaker ShakeFor(float seconds, bool removeOnFinish) + { + on = true; + Timer = seconds; + RemoveOnFinish = removeOnFinish; + + return this; + } + + public override void Update() + { + if (on && Timer > 0) + { + Timer -= Engine.DeltaTime; + if (Timer <= 0) + { + on = false; + Value = Vector2.Zero; + if (OnShake != null) + OnShake(Vector2.Zero); + if (RemoveOnFinish) + RemoveSelf(); + return; + } + } + + if (on && Scene.OnInterval(Interval)) + { + Value = Calc.Random.ShakeVector(); + if (OnShake != null) + OnShake(Value); + } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Logic/ShakerList.cs b/MonocleEngineDemo/Monocle/Components/Logic/ShakerList.cs new file mode 100644 index 0000000..be44235 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Logic/ShakerList.cs @@ -0,0 +1,94 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Monocle +{ + public class ShakerList : Component + { + public Vector2[] Values; + public float Interval = .05f; + public float Timer; + public bool RemoveOnFinish; + public Action OnShake; + + private bool on; + + public ShakerList(int length, bool on = true, Action onShake = null) + : base(true, false) + { + Values = new Vector2[length]; + this.on = on; + OnShake = onShake; + } + + public ShakerList(int length, float time, bool removeOnFinish, Action onShake = null) + : this(length, true, onShake) + { + Timer = time; + RemoveOnFinish = removeOnFinish; + } + + public bool On + { + get + { + return on; + } + + set + { + on = value; + if (!on) + { + Timer = 0; + if (Values[0] != Vector2.Zero) + { + for (var i = 0; i < Values.Length; i++) + Values[i] = Vector2.Zero; + if (OnShake != null) + OnShake(Values); + } + } + } + } + + public ShakerList ShakeFor(float seconds, bool removeOnFinish) + { + on = true; + Timer = seconds; + RemoveOnFinish = removeOnFinish; + + return this; + } + + public override void Update() + { + if (on && Timer > 0) + { + Timer -= Engine.DeltaTime; + if (Timer <= 0) + { + on = false; + for (var i = 0; i < Values.Length; i++) + Values[i] = Vector2.Zero; + if (OnShake != null) + OnShake(Values); + if (RemoveOnFinish) + RemoveSelf(); + return; + } + } + + if (on && Scene.OnInterval(Interval)) + { + for (var i = 0; i < Values.Length; i++) + Values[i] = Calc.Random.ShakeVector(); + if (OnShake != null) + OnShake(Values); + } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Logic/SineWave.cs b/MonocleEngineDemo/Monocle/Components/Logic/SineWave.cs new file mode 100644 index 0000000..e44eb6f --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Logic/SineWave.cs @@ -0,0 +1,107 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Monocle +{ + public class SineWave : Component + { + /* + * SINE WAVE: + * + * 1 x + * | x x + * | x x + * | x x + * -x-------------x-------------x + * | x x + * | x x + * | x x + * -1 x + * + * COS WAVE: + * + * 1x x + * | x x + * | x x + * | x x + * --------x-------------x------- + * | x x + * | x x + * | x x + * -1 x + * + */ + + public float Frequency = 1f; + public float Rate = 1f; + public float Value { get; private set; } + public float ValueOverTwo { get; private set; } + public float TwoValue { get; private set; } + public Action OnUpdate; + public bool UseRawDeltaTime; + + private float counter; + + public SineWave() + : base(true, false) + { + + } + + public SineWave(float frequency) + : this() + { + Frequency = frequency; + } + + public override void Update() + { + Counter += MathHelper.TwoPi * Frequency * Rate * (UseRawDeltaTime ? Engine.RawDeltaTime : Engine.DeltaTime); + if (OnUpdate != null) + OnUpdate(Value); + } + + public float ValueOffset(float offset) + { + return (float)Math.Sin(counter + offset); + } + + public SineWave Randomize() + { + Counter = Calc.Random.NextFloat() * MathHelper.TwoPi * 2; + return this; + } + + public void Reset() + { + Counter = 0; + } + + public void StartUp() + { + Counter = MathHelper.PiOver2; + } + + public void StartDown() + { + Counter = MathHelper.PiOver2 * 3f; + } + + public float Counter + { + get + { + return counter; + } + + set + { + counter = (value + MathHelper.TwoPi * 4) % (MathHelper.TwoPi * 4); + + Value = (float)Math.Sin(counter); + ValueOverTwo = (float)Math.Sin(counter / 2); + TwoValue = (float)Math.Sin(counter * 2); + } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Logic/StateMachine.cs b/MonocleEngineDemo/Monocle/Components/Logic/StateMachine.cs new file mode 100644 index 0000000..b2ae3c3 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Logic/StateMachine.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections; + +namespace Monocle +{ + public class StateMachine : Component + { + private int state; + private Action[] begins; + private Func[] updates; + private Action[] ends; + private Func[] coroutines; + private Coroutine currentCoroutine; + + public bool ChangedStates; + public bool Log; + public int PreviousState { get; private set; } + public bool Locked; + + public StateMachine(int maxStates = 10) + : base(true, false) + { + PreviousState = state = -1; + + begins = new Action[maxStates]; + updates = new Func[maxStates]; + ends = new Action[maxStates]; + coroutines = new Func[maxStates]; + + currentCoroutine = new Coroutine(); + currentCoroutine.RemoveOnComplete = false; + } + + public override void Added(Entity entity) + { + base.Added(entity); + + if (Entity.Scene != null && state == -1) + State = 0; + } + + public override void EntityAdded(Scene scene) + { + base.EntityAdded(scene); + + if (state == -1) + State = 0; + } + + public int State + { + get { return state; } + set + { +#if DEBUG + if (value >= updates.Length || value < 0) + throw new Exception("StateMachine state out of range"); +#endif + + if (!Locked && state != value) + { + if (Log) + Calc.Log("Enter State " + value + " (leaving " + state + ")"); + + ChangedStates = true; + PreviousState = state; + state = value; + + if (PreviousState != -1 && ends[PreviousState] != null) + { + if (Log) + Calc.Log("Calling End " + PreviousState); + ends[PreviousState](); + } + + if (begins[state] != null) + { + if (Log) + Calc.Log("Calling Begin " + state); + begins[state](); + } + + if (coroutines[state] != null) + { + if (Log) + Calc.Log("Starting Coroutine " + state); + currentCoroutine.Replace(coroutines[state]()); + } + else + currentCoroutine.Cancel(); + } + } + } + + public void ForceState(int toState) + { + if (state != toState) + State = toState; + else + { + if (Log) + Calc.Log("Enter State " + toState + " (leaving " + state + ")"); + + ChangedStates = true; + PreviousState = state; + state = toState; + + if (PreviousState != -1 && ends[PreviousState] != null) + { + if (Log) + Calc.Log("Calling End " + state); + ends[PreviousState](); + } + + if (begins[state] != null) + { + if (Log) + Calc.Log("Calling Begin " + state); + begins[state](); + } + + if (coroutines[state] != null) + { + if (Log) + Calc.Log("Starting Coroutine " + state); + currentCoroutine.Replace(coroutines[state]()); + } + else + currentCoroutine.Cancel(); + } + } + + public void SetCallbacks(int state, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null) + { + updates[state] = onUpdate; + begins[state] = begin; + ends[state] = end; + coroutines[state] = coroutine; + } + + public void ReflectState(Entity from, int index, string name) + { + updates[index] = (Func)Calc.GetMethod>(from, name + "Update"); + begins[index] = (Action)Calc.GetMethod(from, name + "Begin"); + ends[index] = (Action)Calc.GetMethod(from, name + "End"); + coroutines[index] = (Func)Calc.GetMethod>(from, name + "Coroutine"); + } + + public override void Update() + { + ChangedStates = false; + + if (updates[state] != null) + State = updates[state](); + if (currentCoroutine.Active) + { + currentCoroutine.Update(); + if (!ChangedStates && Log && currentCoroutine.Finished) + Calc.Log("Finished Coroutine " + state); + } + } + + public static implicit operator int(StateMachine s) + { + return s.state; + } + + public void LogAllStates() + { + for (int i = 0; i < updates.Length; i++) + LogState(i); + } + + public void LogState(int index) + { + Calc.Log("State " + index + ": " + + (updates[index] != null ? "U" : "") + + (begins[index] != null ? "B" : "") + + (ends[index] != null ? "E" : "") + + (coroutines[index] != null ? "C" : "")); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Logic/Tween.cs b/MonocleEngineDemo/Monocle/Components/Logic/Tween.cs new file mode 100644 index 0000000..e69741d --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Logic/Tween.cs @@ -0,0 +1,213 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Monocle +{ + public class Tween : Component + { + public enum TweenMode { Persist, Oneshot, Looping, YoyoOneshot, YoyoLooping }; + + public Ease.Easer Easer; + public Action OnUpdate; + public Action OnComplete; + public Action OnStart; + public bool UseRawDeltaTime; + + public TweenMode Mode { get; private set; } + public float Duration { get; private set; } + public float TimeLeft { get; private set; } + public float Percent { get; private set; } + public float Eased { get; private set; } + public bool Reverse { get; private set; } + + private bool startedReversed; + + #region Static + + private static Stack cached = new Stack(); + + public static Tween Create(TweenMode mode, Ease.Easer easer = null, float duration = 1f, bool start = false) + { + Tween tween; + if (cached.Count == 0) + tween = new Tween(); + else + tween = cached.Pop(); + tween.OnUpdate = tween.OnComplete = tween.OnStart = null; + + tween.Init(mode, easer, duration, start); + return tween; + } + + public static Tween Set(Entity entity, TweenMode tweenMode, float duration, Ease.Easer easer, Action onUpdate, Action onComplete = null) + { + Tween tween = Tween.Create(tweenMode, easer, duration, true); + tween.OnUpdate += onUpdate; + tween.OnComplete += onComplete; + entity.Add(tween); + return tween; + } + + public static Tween Position(Entity entity, Vector2 targetPosition, float duration, Ease.Easer easer, TweenMode tweenMode = TweenMode.Oneshot) + { + Vector2 startPosition = entity.Position; + Tween tween = Tween.Create(tweenMode, easer, duration, true); + tween.OnUpdate = (t) => { entity.Position = Vector2.Lerp(startPosition, targetPosition, t.Eased); }; + entity.Add(tween); + return tween; + } + + #endregion + + private Tween() + : base(false, false) + { + + } + + private void Init(TweenMode mode, Ease.Easer easer, float duration, bool start) + { +#if DEBUG + if (duration <= 0) + throw new Exception("Tween duration cannot be less than zero"); +#else + if (duration <= 0) + duration = .000001f; +#endif + + UseRawDeltaTime = false; + Mode = mode; + Easer = easer; + Duration = duration; + + TimeLeft = 0; + Percent = 0; + Active = false; + + if (start) + Start(); + } + + public override void Removed(Entity entity) + { + base.Removed(entity); + cached.Push(this); + } + + public override void Update() + { + TimeLeft -= (UseRawDeltaTime ? Engine.RawDeltaTime : Engine.DeltaTime); + + //Update the percentage and eased percentage + Percent = Math.Max(0, TimeLeft) / (float)Duration; + if (!Reverse) + Percent = 1 - Percent; + if (Easer != null) + Eased = Easer(Percent); + else + Eased = Percent; + + //Update the tween + if (OnUpdate != null) + OnUpdate(this); + + //When finished... + if (TimeLeft <= 0) + { + TimeLeft = 0; + + if (OnComplete != null) + OnComplete(this); + + switch (Mode) + { + case TweenMode.Persist: + Active = false; + break; + + case TweenMode.Oneshot: + Active = false; + RemoveSelf(); + break; + + case TweenMode.Looping: + Start(Reverse); + break; + + case TweenMode.YoyoOneshot: + if (Reverse == startedReversed) + { + Start(!Reverse); + startedReversed = !Reverse; + } + else + { + Active = false; + RemoveSelf(); + } + break; + + case TweenMode.YoyoLooping: + Start(!Reverse); + break; + } + } + } + + public void Start() + { + Start(false); + } + + public void Start(bool reverse) + { + startedReversed = Reverse = reverse; + + TimeLeft = Duration; + Eased = Percent = Reverse ? 1 : 0; + + Active = true; + + if (OnStart != null) + OnStart(this); + } + + public void Start(float duration, bool reverse = false) + { +#if DEBUG + if (duration <= 0) + throw new Exception("Tween duration cannot be <= 0"); +#endif + + Duration = duration; + Start(reverse); + } + + public void Stop() + { + Active = false; + } + + public void Reset() + { + TimeLeft = Duration; + Eased = Percent = Reverse ? 1 : 0; + } + + public IEnumerator Wait() + { + while (Active) + yield return 0; + } + + public float Inverted + { + get + { + return 1f - Eased; + } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Components/Logic/Wiggler.cs b/MonocleEngineDemo/Monocle/Components/Logic/Wiggler.cs new file mode 100644 index 0000000..00a183a --- /dev/null +++ b/MonocleEngineDemo/Monocle/Components/Logic/Wiggler.cs @@ -0,0 +1,131 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; + +namespace Monocle +{ + public class Wiggler : Component + { + private static Stack cache = new Stack(); + + public float Counter { get; private set; } + public float Value { get; private set; } + public bool StartZero; + public bool UseRawDeltaTime; + + private float sineCounter; + + private float increment; + private float sineAdd; + private Action onChange; + private bool removeSelfOnFinish; + + public static Wiggler Create(float duration, float frequency, Action onChange = null, bool start = false, bool removeSelfOnFinish = false) + { + Wiggler wiggler; + + if (cache.Count > 0) + wiggler = cache.Pop(); + else + wiggler = new Wiggler(); + wiggler.Init(duration, frequency, onChange, start, removeSelfOnFinish); + + return wiggler; + } + + private Wiggler() + : base(false, false) + { + + } + + private void Init(float duration, float frequency, Action onChange, bool start, bool removeSelfOnFinish) + { + Counter = sineCounter = 0; + UseRawDeltaTime = false; + + increment = 1f / duration; + sineAdd = MathHelper.TwoPi * frequency; + this.onChange = onChange; + this.removeSelfOnFinish = removeSelfOnFinish; + + if (start) + Start(); + else + Active = false; + } + + public override void Removed(Entity entity) + { + base.Removed(entity); + cache.Push(this); + } + + public void Start() + { + Counter = 1f; + + if (StartZero) + { + sineCounter = MathHelper.PiOver2; + Value = 0; + if (onChange != null) + onChange(0); + } + else + { + sineCounter = 0; + Value = 1f; + if (onChange != null) + onChange(1f); + } + + Active = true; + } + + public void Start(float duration, float frequency) + { + increment = 1f / duration; + sineAdd = MathHelper.TwoPi * frequency; + Start(); + } + + public void Stop() + { + Active = false; + } + + public void StopAndClear() + { + Stop(); + Value = 0; + } + + public override void Update() + { + if (UseRawDeltaTime) + { + sineCounter += sineAdd * Engine.RawDeltaTime; + Counter -= increment * Engine.RawDeltaTime; + } + else + { + sineCounter += sineAdd * Engine.DeltaTime; + Counter -= increment * Engine.DeltaTime; + } + + if (Counter <= 0) + { + Counter = 0; + Active = false; + if (removeSelfOnFinish) + RemoveSelf(); + } + + Value = (float)Math.Cos(sineCounter) * Counter; + + if (onChange != null) + onChange(Value); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Content/Monocle/MonocleDefault.spritefont b/MonocleEngineDemo/Monocle/Content/Monocle/MonocleDefault.spritefont new file mode 100644 index 0000000..95035fa --- /dev/null +++ b/MonocleEngineDemo/Monocle/Content/Monocle/MonocleDefault.spritefont @@ -0,0 +1,60 @@ + + + + + + + Consolas + + + 12 + + + 0 + + + true + + + + + + + + + + + + ~ + + + + diff --git a/MonocleEngineDemo/Monocle/Content/Monocle/MonocleDefault.xnb b/MonocleEngineDemo/Monocle/Content/Monocle/MonocleDefault.xnb new file mode 100644 index 0000000..ce83ae5 Binary files /dev/null and b/MonocleEngineDemo/Monocle/Content/Monocle/MonocleDefault.xnb differ diff --git a/MonocleEngineDemo/Monocle/Engine.cs b/MonocleEngineDemo/Monocle/Engine.cs new file mode 100644 index 0000000..e0fcabd --- /dev/null +++ b/MonocleEngineDemo/Monocle/Engine.cs @@ -0,0 +1,411 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.IO; +using System.Reflection; +using System.Runtime; + +namespace Monocle +{ + public class Engine : Game + { + + public string Title; + public Version Version; + + // references + public static Engine Instance { get; private set; } + public static GraphicsDeviceManager Graphics { get; private set; } + public static Commands Commands { get; private set; } + public static Pooler Pooler { get; private set; } + public static Action OverloadGameLoop; + + // screen size + public static int Width { get; private set; } + public static int Height { get; private set; } + public static int ViewWidth { get; private set; } + public static int ViewHeight { get; private set; } + public static int ViewPadding + { + get { return viewPadding; } + set + { + viewPadding = value; + Instance.UpdateView(); + } + } + private static int viewPadding = 0; + private static bool resizing; + + // time + public static float DeltaTime { get; private set; } + public static float RawDeltaTime { get; private set; } + public static float TimeRate = 1f; + public static float FreezeTimer; + public static int FPS; + private TimeSpan counterElapsed = TimeSpan.Zero; + private int fpsCounter = 0; + + // content directory +#if !CONSOLE + private static string AssemblyDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); +#endif + + public static string ContentDirectory + { +#if PS4 + get { return Path.Combine("/app0/", Instance.Content.RootDirectory); } +#elif NSWITCH + get { return Path.Combine("rom:/", Instance.Content.RootDirectory); } +#elif XBOXONE + get { return Instance.Content.RootDirectory; } +#else + get { return Path.Combine(AssemblyDirectory, Instance.Content.RootDirectory); } +#endif + } + + // util + public static Color ClearColor; + public static bool ExitOnEscapeKeypress; + + // scene + private Scene scene; + private Scene nextScene; + + public Engine(int width, int height, int windowWidth, int windowHeight, string windowTitle, bool fullscreen) + { + Instance = this; + + Title = Window.Title = windowTitle; + Width = width; + Height = height; + ClearColor = Color.Black; + + Graphics = new GraphicsDeviceManager(this); + Graphics.DeviceReset += OnGraphicsReset; + Graphics.DeviceCreated += OnGraphicsCreate; + Graphics.SynchronizeWithVerticalRetrace = true; + Graphics.PreferMultiSampling = false; + Graphics.GraphicsProfile = GraphicsProfile.HiDef; + Graphics.PreferredBackBufferFormat = SurfaceFormat.Color; + Graphics.PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8; + Graphics.ApplyChanges(); + +#if PS4 || XBOXONE + Graphics.PreferredBackBufferWidth = 1920; + Graphics.PreferredBackBufferHeight = 1080; +#elif NSWITCH + Graphics.PreferredBackBufferWidth = 1280; + Graphics.PreferredBackBufferHeight = 720; +#else + Window.AllowUserResizing = true; + Window.ClientSizeChanged += OnClientSizeChanged; + + if (fullscreen) + { + Graphics.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width; + Graphics.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height; + Graphics.IsFullScreen = true; + } + else + { + Graphics.PreferredBackBufferWidth = windowWidth; + Graphics.PreferredBackBufferHeight = windowHeight; + Graphics.IsFullScreen = false; + } +#endif + + Content.RootDirectory = @"Content"; + + IsMouseVisible = false; + IsFixedTimeStep = false; + ExitOnEscapeKeypress = true; + + GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency; + } + +#if !CONSOLE + protected virtual void OnClientSizeChanged(object sender, EventArgs e) + { + if (Window.ClientBounds.Width > 0 && Window.ClientBounds.Height > 0 && !resizing) + { + resizing = true; + + Graphics.PreferredBackBufferWidth = Window.ClientBounds.Width; + Graphics.PreferredBackBufferHeight = Window.ClientBounds.Height; + UpdateView(); + + resizing = false; + } + } +#endif + + protected virtual void OnGraphicsReset(object sender, EventArgs e) + { + UpdateView(); + + if (scene != null) + scene.HandleGraphicsReset(); + if (nextScene != null && nextScene != scene) + nextScene.HandleGraphicsReset(); + } + + protected virtual void OnGraphicsCreate(object sender, EventArgs e) + { + UpdateView(); + + if (scene != null) + scene.HandleGraphicsCreate(); + if (nextScene != null && nextScene != scene) + nextScene.HandleGraphicsCreate(); + } + + protected override void OnActivated(object sender, EventArgs args) + { + base.OnActivated(sender, args); + + if (scene != null) + scene.GainFocus(); + } + + protected override void OnDeactivated(object sender, EventArgs args) + { + base.OnDeactivated(sender, args); + + if (scene != null) + scene.LoseFocus(); + } + + protected override void Initialize() + { + base.Initialize(); + + MInput.Initialize(); + Tracker.Initialize(); + Pooler = new Monocle.Pooler(); + Commands = new Commands(); + } + + protected override void LoadContent() + { + base.LoadContent(); + + Monocle.Draw.Initialize(GraphicsDevice); + } + + protected override void Update(GameTime gameTime) + { + RawDeltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds; + DeltaTime = RawDeltaTime * TimeRate; + + //Update input + MInput.Update(); + +#if !CONSOLE + if (ExitOnEscapeKeypress && MInput.Keyboard.Pressed(Microsoft.Xna.Framework.Input.Keys.Escape)) + { + Exit(); + return; + } +#endif + + if (OverloadGameLoop != null) + { + OverloadGameLoop(); + base.Update(gameTime); + return; + } + + //Update current scene + if (FreezeTimer > 0) + FreezeTimer = Math.Max(FreezeTimer - RawDeltaTime, 0); + else if (scene != null) + { + scene.BeforeUpdate(); + scene.Update(); + scene.AfterUpdate(); + } + + //Debug Console + if (Commands.Open) + Commands.UpdateOpen(); + else if (Commands.Enabled) + Commands.UpdateClosed(); + + //Changing scenes + if (scene != nextScene) + { + var lastScene = scene; + if (scene != null) + scene.End(); + scene = nextScene; + OnSceneTransition(lastScene, nextScene); + if (scene != null) + scene.Begin(); + } + + base.Update(gameTime); + } + + protected override void Draw(GameTime gameTime) + { + RenderCore(); + + base.Draw(gameTime); + if (Commands.Open) + Commands.Render(); + + //Frame counter + fpsCounter++; + counterElapsed += gameTime.ElapsedGameTime; + if (counterElapsed >= TimeSpan.FromSeconds(1)) + { +#if DEBUG + Window.Title = Title + " " + fpsCounter.ToString() + " fps - " + (GC.GetTotalMemory(false) / 1048576f).ToString("F") + " MB"; +#endif + FPS = fpsCounter; + fpsCounter = 0; + counterElapsed -= TimeSpan.FromSeconds(1); + } + } + + /// + /// Override if you want to change the core rendering functionality of Monocle Engine. + /// By default, this simply sets the render target to null, clears the screen, and renders the current Scene + /// + protected virtual void RenderCore() + { + if (scene != null) + scene.BeforeRender(); + + GraphicsDevice.SetRenderTarget(null); + GraphicsDevice.Viewport = Viewport; + GraphicsDevice.Clear(ClearColor); + + if (scene != null) + { + scene.Render(); + scene.AfterRender(); + } + } + + protected override void OnExiting(object sender, EventArgs args) + { + base.OnExiting(sender, args); + MInput.Shutdown(); + } + + public void RunWithLogging() + { + try + { + Run(); + } + catch (Exception e) + { + ErrorLog.Write(e); + ErrorLog.Open(); + } + } + + #region Scene + + /// + /// Called after a Scene ends, before the next Scene begins + /// + protected virtual void OnSceneTransition(Scene from, Scene to) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + + TimeRate = 1f; + } + + /// + /// The currently active Scene. Note that if set, the Scene will not actually change until the end of the Update + /// + public static Scene Scene + { + get { return Instance.scene; } + set { Instance.nextScene = value; } + } + + #endregion + + #region Screen + + public static Viewport Viewport { get; private set; } + public static Matrix ScreenMatrix; + + public static void SetWindowed(int width, int height) + { +#if !CONSOLE + if (width > 0 && height > 0) + { + resizing = true; + Graphics.PreferredBackBufferWidth = width; + Graphics.PreferredBackBufferHeight = height; + Graphics.IsFullScreen = false; + Graphics.ApplyChanges(); + Console.WriteLine("WINDOW-" + width + "x" + height); + resizing = false; + } +#endif + } + + public static void SetFullscreen() + { +#if !CONSOLE + resizing = true; + Graphics.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width; + Graphics.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height; + Graphics.IsFullScreen = true; + Graphics.ApplyChanges(); + Console.WriteLine("FULLSCREEN"); + resizing = false; +#endif + } + + private void UpdateView() + { + float screenWidth = GraphicsDevice.PresentationParameters.BackBufferWidth; + float screenHeight = GraphicsDevice.PresentationParameters.BackBufferHeight; + + // get View Size + if (screenWidth / Width > screenHeight / Height) + { + ViewWidth = (int)(screenHeight / Height * Width); + ViewHeight = (int)screenHeight; + } + else + { + ViewWidth = (int)screenWidth; + ViewHeight = (int)(screenWidth / Width * Height); + } + + // apply View Padding + var aspect = ViewHeight / (float)ViewWidth; + ViewWidth -= ViewPadding * 2; + ViewHeight -= (int)(aspect * ViewPadding * 2); + + // update screen matrix + ScreenMatrix = Matrix.CreateScale(ViewWidth / (float)Width); + + // update viewport + Viewport = new Viewport + { + X = (int)(screenWidth / 2 - ViewWidth / 2), + Y = (int)(screenHeight / 2 - ViewHeight / 2), + Width = ViewWidth, + Height = ViewHeight, + MinDepth = 0, + MaxDepth = 1 + }; + + //Debug Log + //Calc.Log("Update View - " + screenWidth + "x" + screenHeight + " - " + viewport.Width + "x" + viewport.GuiHeight + " - " + viewport.X + "," + viewport.Y); + } + + #endregion + } +} diff --git a/MonocleEngineDemo/Monocle/Entity.cs b/MonocleEngineDemo/Monocle/Entity.cs new file mode 100644 index 0000000..7176c7b --- /dev/null +++ b/MonocleEngineDemo/Monocle/Entity.cs @@ -0,0 +1,1170 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Monocle +{ + public class Entity : IEnumerable, IEnumerable + { + public bool Active = true; + public bool Visible = true; + public bool Collidable = true; + public Vector2 Position; + + public Scene Scene { get; private set; } + public ComponentList Components { get; private set; } + + private int tag; + private Collider collider; + internal int depth = 0; + internal double actualDepth = 0; + + public Entity(Vector2 position) + { + Position = position; + Components = new ComponentList(this); + } + + public Entity() + : this(Vector2.Zero) + { + + } + + /// + /// Called when the containing Scene Begins + /// + public virtual void SceneBegin(Scene scene) + { + + } + + /// + /// Called when the containing Scene Ends + /// + public virtual void SceneEnd(Scene scene) + { + if (Components != null) + foreach (var c in Components) + c.SceneEnd(scene); + } + + /// + /// Called before the frame starts, after Entities are added and removed, on the frame that the Entity was added + /// Useful if you added two Entities in the same frame, and need them to detect each other before they start Updating + /// + /// + public virtual void Awake(Scene scene) + { + if (Components != null) + foreach (var c in Components) + c.EntityAwake(); + } + + /// + /// Called when this Entity is added to a Scene, which only occurs immediately before each Update. + /// Keep in mind, other Entities to be added this frame may be added after this Entity. + /// See Awake() for after all Entities are added, but still before the frame Updates. + /// + /// + public virtual void Added(Scene scene) + { + Scene = scene; + if (Components != null) + foreach (var c in Components) + c.EntityAdded(scene); + Scene.SetActualDepth(this); + } + + /// + /// Called when the Entity is removed from a Scene + /// + /// + public virtual void Removed(Scene scene) + { + if (Components != null) + foreach (var c in Components) + c.EntityRemoved(scene); + Scene = null; + } + + /// + /// Do game logic here, but do not render here. Not called if the Entity is not Active + /// + public virtual void Update() + { + Components.Update(); + } + + /// + /// Draw the Entity here. Not called if the Entity is not Visible + /// + public virtual void Render() + { + Components.Render(); + } + + /// + /// Draw any debug visuals here. Only called if the console is open, but still called even if the Entity is not Visible + /// + public virtual void DebugRender(Camera camera) + { + if (Collider != null) + Collider.Render(camera, Collidable ? Color.Red : Color.DarkRed); + + Components.DebugRender(camera); + } + + /// + /// Called when the graphics device resets. When this happens, any RenderTargets or other contents of VRAM will be wiped and need to be regenerated + /// + public virtual void HandleGraphicsReset() + { + Components.HandleGraphicsReset(); + } + + public virtual void HandleGraphicsCreate() + { + Components.HandleGraphicsCreate(); + } + + public void RemoveSelf() + { + if (Scene != null) + Scene.Entities.Remove(this); + } + + public int Depth + { + get { return depth; } + set + { + if (depth != value) + { + depth = value; + if (Scene != null) + Scene.SetActualDepth(this); + } + } + } + + public float X + { + get { return Position.X; } + set { Position.X = value; } + } + + public float Y + { + get { return Position.Y; } + set { Position.Y = value; } + } + + #region Collider + + public Collider Collider + { + get { return collider; } + set + { + if (value == collider) + return; +#if DEBUG + if (value.Entity != null) + throw new Exception("Setting an Entity's Collider to a Collider already in use by another object"); +#endif + if (collider != null) + collider.Removed(); + collider = value; + if (collider != null) + collider.Added(this); + } + } + + public float Width + { + get + { + if (collider == null) + return 0; + else + return collider.Width; + } + } + + public float Height + { + get + { + if (collider == null) + return 0; + else + return collider.Height; + } + } + + public float Left + { + get + { + if (collider == null) + return X; + else + return Position.X + collider.Left; + } + + set + { + if (collider == null) + Position.X = value; + else + Position.X = value - collider.Left; + } + } + + public float Right + { + get + { + if (collider == null) + return Position.X; + else + return Position.X + collider.Right; + } + + set + { + if (collider == null) + Position.X = value; + else + Position.X = value - collider.Right; + } + } + + public float Top + { + get + { + if (collider == null) + return Position.Y; + else + return Position.Y + collider.Top; + } + + set + { + if (collider == null) + Position.Y = value; + else + Position.Y = value - collider.Top; + } + } + + public float Bottom + { + get + { + if (collider == null) + return Position.Y; + else + return Position.Y + collider.Bottom; + } + + set + { + if (collider == null) + Position.Y = value; + else + Position.Y = value - collider.Bottom; + } + } + + public float CenterX + { + get + { + if (collider == null) + return Position.X; + else + return Position.X + collider.CenterX; + } + + set + { + if (collider == null) + Position.X = value; + else + Position.X = value - collider.CenterX; + } + } + + public float CenterY + { + get + { + if (collider == null) + return Position.Y; + else + return Position.Y + collider.CenterY; + } + + set + { + if (collider == null) + Position.Y = value; + else + Position.Y = value - collider.CenterY; + } + } + + public Vector2 TopLeft + { + get + { + return new Vector2(Left, Top); + } + + set + { + Left = value.X; + Top = value.Y; + } + } + + public Vector2 TopRight + { + get + { + return new Vector2(Right, Top); + } + + set + { + Right = value.X; + Top = value.Y; + } + } + + public Vector2 BottomLeft + { + get + { + return new Vector2(Left, Bottom); + } + + set + { + Left = value.X; + Bottom = value.Y; + } + } + + public Vector2 BottomRight + { + get + { + return new Vector2(Right, Bottom); + } + + set + { + Right = value.X; + Bottom = value.Y; + } + } + + public Vector2 Center + { + get + { + return new Vector2(CenterX, CenterY); + } + + set + { + CenterX = value.X; + CenterY = value.Y; + } + } + + public Vector2 CenterLeft + { + get + { + return new Vector2(Left, CenterY); + } + + set + { + Left = value.X; + CenterY = value.Y; + } + } + + public Vector2 CenterRight + { + get + { + return new Vector2(Right, CenterY); + } + + set + { + Right = value.X; + CenterY = value.Y; + } + } + + public Vector2 TopCenter + { + get + { + return new Vector2(CenterX, Top); + } + + set + { + CenterX = value.X; + Top = value.Y; + } + } + + public Vector2 BottomCenter + { + get + { + return new Vector2(CenterX, Bottom); + } + + set + { + CenterX = value.X; + Bottom = value.Y; + } + } + + #endregion + + #region Tag + + public int Tag + { + get + { + return tag; + } + + set + { + if (tag != value) + { + if (Scene != null) + { + for (int i = 0; i < Monocle.BitTag.TotalTags; i++) + { + int check = 1 << i; + bool add = (value & check) != 0; + bool has = (Tag & check) != 0; + + if (has != add) + { + if (add) + Scene.TagLists[i].Add(this); + else + Scene.TagLists[i].Remove(this); + } + } + } + + tag = value; + } + } + } + + public bool TagFullCheck(int tag) + { + return (this.tag & tag) == tag; + } + + public bool TagCheck(int tag) + { + return (this.tag & tag) != 0; + } + + public void AddTag(int tag) + { + Tag |= tag; + } + + public void RemoveTag(int tag) + { + Tag &= ~tag; + } + + #endregion + + #region Collision Shortcuts + + #region Collide Check + + public bool CollideCheck(Entity other) + { + return Collide.Check(this, other); + } + + public bool CollideCheck(Entity other, Vector2 at) + { + return Collide.Check(this, other, at); + } + + public bool CollideCheck(CollidableComponent other) + { + return Collide.Check(this, other); + } + + public bool CollideCheck(CollidableComponent other, Vector2 at) + { + return Collide.Check(this, other, at); + } + + public bool CollideCheck(BitTag tag) + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against a tag list when it is not a member of a Scene"); +#endif + return Collide.Check(this, Scene[tag]); + } + + public bool CollideCheck(BitTag tag, Vector2 at) + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against a tag list when it is not a member of a Scene"); +#endif + return Collide.Check(this, Scene[tag], at); + } + + public bool CollideCheck() where T : Entity + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked Entities when it is not a member of a Scene"); + else if (!Scene.Tracker.Entities.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked Entity type"); +#endif + + return Collide.Check(this, Scene.Tracker.Entities[typeof(T)]); + } + + public bool CollideCheck(Vector2 at) where T : Entity + { + return Collide.Check(this, Scene.Tracker.Entities[typeof(T)], at); + } + + public bool CollideCheck() where T : Entity where Exclude : Entity + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked objects when it is not a member of a Scene"); + else if (!Scene.Tracker.Entities.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked Entity type"); + else if (!Scene.Tracker.Entities.ContainsKey(typeof(Exclude))) + throw new Exception("Excluded type is an untracked Entity type!"); +#endif + + var exclude = Scene.Tracker.Entities[typeof(Exclude)]; + foreach (var e in Scene.Tracker.Entities[typeof(T)]) + if (!exclude.Contains(e)) + if (Collide.Check(this, e)) + return true; + return false; + } + + public bool CollideCheck(Vector2 at) where T : Entity where Exclude : Entity + { + var was = Position; + Position = at; + var ret = CollideCheck(); + Position = was; + return ret; + } + + public bool CollideCheckByComponent() where T : CollidableComponent + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked CollidableComponents when it is not a member of a Scene"); + else if (!Scene.Tracker.Components.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked CollidableComponent type"); +#endif + + foreach (var c in Scene.Tracker.CollidableComponents[typeof(T)]) + if (Collide.Check(this, c)) + return true; + return false; + } + + public bool CollideCheckByComponent(Vector2 at) where T : CollidableComponent + { + Vector2 old = Position; + Position = at; + bool ret = CollideCheckByComponent(); + Position = old; + return ret; + } + + #endregion + + #region Collide CheckOutside + + public bool CollideCheckOutside(Entity other, Vector2 at) + { + return !Collide.Check(this, other) && Collide.Check(this, other, at); + } + + public bool CollideCheckOutside(BitTag tag, Vector2 at) + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against a tag list when it is not a member of a Scene"); +#endif + + foreach (var entity in Scene[tag]) + if (!Collide.Check(this, entity) && Collide.Check(this, entity, at)) + return true; + + return false; + } + + public bool CollideCheckOutside(Vector2 at) where T : Entity + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked Entities when it is not a member of a Scene"); + else if (!Scene.Tracker.Entities.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked Entity type"); +#endif + + foreach (var entity in Scene.Tracker.Entities[typeof(T)]) + if (!Collide.Check(this, entity) && Collide.Check(this, entity, at)) + return true; + return false; + } + + public bool CollideCheckOutsideByComponent(Vector2 at) where T : CollidableComponent + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked CollidableComponents when it is not a member of a Scene"); + else if (!Scene.Tracker.CollidableComponents.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked CollidableComponent type"); +#endif + + foreach (var component in Scene.Tracker.CollidableComponents[typeof(T)]) + if (!Collide.Check(this, component) && Collide.Check(this, component, at)) + return true; + return false; + } + + #endregion + + #region Collide First + + public Entity CollideFirst(BitTag tag) + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against a tag list when it is not a member of a Scene"); +#endif + return Collide.First(this, Scene[tag]); + } + + public Entity CollideFirst(BitTag tag, Vector2 at) + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against a tag list when it is not a member of a Scene"); +#endif + return Collide.First(this, Scene[tag], at); + } + + public T CollideFirst() where T : Entity + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked Entities when it is not a member of a Scene"); + else if (!Scene.Tracker.Entities.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked Entity type"); +#endif + return Collide.First(this, Scene.Tracker.Entities[typeof(T)]) as T; + } + + public T CollideFirst(Vector2 at) where T : Entity + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked Entities when it is not a member of a Scene"); + else if (!Scene.Tracker.Entities.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked Entity type"); +#endif + return Collide.First(this, Scene.Tracker.Entities[typeof(T)], at) as T; + } + + public T CollideFirstByComponent() where T : CollidableComponent + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked CollidableComponents when it is not a member of a Scene"); + else if (!Scene.Tracker.CollidableComponents.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked CollidableComponent type"); +#endif + + foreach (var component in Scene.Tracker.CollidableComponents[typeof(T)]) + if (Collide.Check(this, component)) + return component as T; + return null; + } + + public T CollideFirstByComponent(Vector2 at) where T : CollidableComponent + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked CollidableComponents when it is not a member of a Scene"); + else if (!Scene.Tracker.CollidableComponents.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked CollidableComponent type"); +#endif + + foreach (var component in Scene.Tracker.CollidableComponents[typeof(T)]) + if (Collide.Check(this, component, at)) + return component as T; + return null; + } + + #endregion + + #region Collide FirstOutside + + public Entity CollideFirstOutside(BitTag tag, Vector2 at) + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against a tag list when it is not a member of a Scene"); +#endif + + foreach (var entity in Scene[tag]) + if (!Collide.Check(this, entity) && Collide.Check(this, entity, at)) + return entity; + return null; + } + + public T CollideFirstOutside(Vector2 at) where T : Entity + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked Entities when it is not a member of a Scene"); + else if (!Scene.Tracker.Entities.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked Entity type"); +#endif + + foreach (var entity in Scene.Tracker.Entities[typeof(T)]) + if (!Collide.Check(this, entity) && Collide.Check(this, entity, at)) + return entity as T; + return null; + } + + public T CollideFirstOutsideByComponent(Vector2 at) where T : CollidableComponent + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked CollidableComponents when it is not a member of a Scene"); + else if (!Scene.Tracker.CollidableComponents.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked CollidableComponent type"); +#endif + + foreach (var component in Scene.Tracker.CollidableComponents[typeof(T)]) + if (!Collide.Check(this, component) && Collide.Check(this, component, at)) + return component as T; + return null; + } + + #endregion + + #region Collide All + + public List CollideAll(BitTag tag) + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against a tag list when it is not a member of a Scene"); +#endif + return Collide.All(this, Scene[tag]); + } + + public List CollideAll(BitTag tag, Vector2 at) + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against a tag list when it is not a member of a Scene"); +#endif + return Collide.All(this, Scene[tag], at); + } + + public List CollideAll() where T : Entity + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked Entities when it is not a member of a Scene"); + else if (!Scene.Tracker.Entities.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked Entity type"); +#endif + + return Collide.All(this, Scene.Tracker.Entities[typeof(T)]); + } + + public List CollideAll(Vector2 at) where T : Entity + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked Entities when it is not a member of a Scene"); + else if (!Scene.Tracker.Entities.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked Entity type"); +#endif + + return Collide.All(this, Scene.Tracker.Entities[typeof(T)], at); + } + + public List CollideAll(Vector2 at, List into) where T : Entity + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked Entities when it is not a member of a Scene"); + else if (!Scene.Tracker.Entities.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked Entity type"); +#endif + + into.Clear(); + return Collide.All(this, Scene.Tracker.Entities[typeof(T)], into, at); + } + + public List CollideAllByComponent() where T : CollidableComponent + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked CollidableComponents when it is not a member of a Scene"); + else if (!Scene.Tracker.CollidableComponents.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked CollidableComponent type"); +#endif + + List list = new List(); + foreach (var component in Scene.Tracker.CollidableComponents[typeof(T)]) + if (Collide.Check(this, component)) + list.Add(component as T); + return list; + } + + public List CollideAllByComponent(Vector2 at) where T : CollidableComponent + { + Vector2 old = Position; + Position = at; + var ret = CollideAllByComponent(); + Position = old; + return ret; + } + + #endregion + + #region Collide Do + + public bool CollideDo(BitTag tag, Action action) + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against a tag list when it is not a member of a Scene"); +#endif + + bool hit = false; + foreach (var other in Scene[tag]) + { + if (CollideCheck(other)) + { + action(other); + hit = true; + } + } + return hit; + } + + public bool CollideDo(BitTag tag, Action action, Vector2 at) + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against a tag list when it is not a member of a Scene"); +#endif + + bool hit = false; + var was = Position; + Position = at; + + foreach (var other in Scene[tag]) + { + if (CollideCheck(other)) + { + action(other); + hit = true; + } + } + + Position = was; + return hit; + } + + public bool CollideDo(Action action) where T : Entity + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked Entities when it is not a member of a Scene"); + else if (!Scene.Tracker.Entities.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked Entity type"); +#endif + + bool hit = false; + foreach (var other in Scene.Tracker.Entities[typeof(T)]) + { + if (CollideCheck(other)) + { + action(other as T); + hit = true; + } + } + return hit; + } + + public bool CollideDo(Action action, Vector2 at) where T : Entity + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked Entities when it is not a member of a Scene"); + else if (!Scene.Tracker.Entities.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked Entity type"); +#endif + + bool hit = false; + var was = Position; + Position = at; + + foreach (var other in Scene.Tracker.Entities[typeof(T)]) + { + if (CollideCheck(other)) + { + action(other as T); + hit = true; + } + } + + Position = was; + return hit; + } + + public bool CollideDoByComponent(Action action) where T : CollidableComponent + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked CollidableComponents when it is not a member of a Scene"); + else if (!Scene.Tracker.CollidableComponents.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked CollidableComponent type"); +#endif + + bool hit = false; + foreach (var component in Scene.Tracker.CollidableComponents[typeof(T)]) + { + if (CollideCheck(component)) + { + action(component as T); + hit = true; + } + } + return hit; + } + + public bool CollideDoByComponent(Action action, Vector2 at) where T : CollidableComponent + { +#if DEBUG + if (Scene == null) + throw new Exception("Can't collide check an Entity against tracked CollidableComponents when it is not a member of a Scene"); + else if (!Scene.Tracker.CollidableComponents.ContainsKey(typeof(T))) + throw new Exception("Can't collide check an Entity against an untracked CollidableComponent type"); +#endif + + bool hit = false; + var was = Position; + Position = at; + + foreach (var component in Scene.Tracker.CollidableComponents[typeof(T)]) + { + if (CollideCheck(component)) + { + action(component as T); + hit = true; + } + } + + Position = was; + return hit; + } + + #endregion + + #region Collide Geometry + + public bool CollidePoint(Vector2 point) + { + return Collide.CheckPoint(this, point); + } + + public bool CollidePoint(Vector2 point, Vector2 at) + { + return Collide.CheckPoint(this, point, at); + } + + public bool CollideLine(Vector2 from, Vector2 to) + { + return Collide.CheckLine(this, from, to); + } + + public bool CollideLine(Vector2 from, Vector2 to, Vector2 at) + { + return Collide.CheckLine(this, from, to, at); + } + + public bool CollideRect(Rectangle rect) + { + return Collide.CheckRect(this, rect); + } + + public bool CollideRect(Rectangle rect, Vector2 at) + { + return Collide.CheckRect(this, rect, at); + } + + #endregion + + #endregion + + #region Components Shortcuts + + /// + /// Shortcut function for adding a Component to the Entity's Components list + /// + /// The Component to add + public void Add(Component component) + { + Components.Add(component); + } + + /// + /// Shortcut function for removing an Component from the Entity's Components list + /// + /// The Component to remove + public void Remove(Component component) + { + Components.Remove(component); + } + + /// + /// Shortcut function for adding a set of Components from the Entity's Components list + /// + /// The Components to add + public void Add(params Component[] components) + { + Components.Add(components); + } + + /// + /// Shortcut function for removing a set of Components from the Entity's Components list + /// + /// The Components to remove + public void Remove(params Component[] components) + { + Components.Remove(components); + } + + public T Get() where T : Component + { + return Components.Get(); + } + + /// + /// Allows you to iterate through all Components in the Entity + /// + /// + public IEnumerator GetEnumerator() + { + return Components.GetEnumerator(); + } + + /// + /// Allows you to iterate through all Components in the Entity + /// + /// + IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Misc Utils + + public Entity Closest(params Entity[] entities) + { + Entity closest = entities[0]; + float dist = Vector2.DistanceSquared(Position, closest.Position); + + for (int i = 1; i < entities.Length; i++) + { + float current = Vector2.DistanceSquared(Position, entities[i].Position); + if (current < dist) + { + closest = entities[i]; + dist = current; + } + } + + return closest; + } + + public Entity Closest(BitTag tag) + { + var list = Scene[tag]; + Entity closest = null; + float dist; + + if (list.Count >= 1) + { + closest = list[0]; + dist = Vector2.DistanceSquared(Position, closest.Position); + + for (int i = 1; i < list.Count; i++) + { + float current = Vector2.DistanceSquared(Position, list[i].Position); + if (current < dist) + { + closest = list[i]; + dist = current; + } + } + } + + return closest; + } + + public T SceneAs() where T : Scene + { + return Scene as T; + } + + #endregion + } +} diff --git a/MonocleEngineDemo/Monocle/Graphics/Atlas.cs b/MonocleEngineDemo/Monocle/Graphics/Atlas.cs new file mode 100644 index 0000000..2f29dc4 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Graphics/Atlas.cs @@ -0,0 +1,417 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace Monocle +{ + /// + /// Represents a texture with multiple sprites placed on it. Along with it goes the placement of the independent sprites + /// inside the texture, aswell their paths that can be used when creating SpriteBanks. + /// + public class Atlas + { + public List Sources; + private Dictionary textures = new Dictionary(StringComparer.OrdinalIgnoreCase); + private Dictionary> orderedTexturesCache = new Dictionary>(); + + public enum AtlasDataFormat + { + TexturePacker_Sparrow, + CrunchXml, + CrunchBinary, + CrunchXmlOrBinary, + CrunchBinaryNoAtlas, + Packer, + PackerNoAtlas + }; + + public static Atlas FromAtlas(string path, AtlasDataFormat format) + { + var atlas = new Atlas(); + atlas.Sources = new List(); + ReadAtlasData(atlas, path, format); + return atlas; + } + + private static void ReadAtlasData(Atlas atlas, string path, AtlasDataFormat format) + { + switch (format) + { + case AtlasDataFormat.TexturePacker_Sparrow: + { + XmlDocument xml = Calc.LoadContentXML(path); + XmlElement at = xml["TextureAtlas"]; + + var texturePath = at.Attr("imagePath", ""); + var fileStream = new FileStream(Path.Combine(Path.GetDirectoryName(path), texturePath), FileMode.Open, FileAccess.Read); + var texture = Texture2D.FromStream(Engine.Instance.GraphicsDevice, fileStream); + fileStream.Close(); + + var mTexture = new MTexture(texture); + atlas.Sources.Add(texture); + + var subtextures = at.GetElementsByTagName("SubTexture"); + + foreach (XmlElement sub in subtextures) + { + var name = sub.Attr("name"); + var clipRect = sub.Rect(); + if (sub.HasAttr("frameX")) + atlas.textures[name] = new MTexture(mTexture, name, clipRect, new Vector2(-sub.AttrInt("frameX"), -sub.AttrInt("frameY")), sub.AttrInt("frameWidth"), sub.AttrInt("frameHeight")); + else + atlas.textures[name] = new MTexture(mTexture, name, clipRect); + } + } + break; + case AtlasDataFormat.CrunchXml: + { + XmlDocument xml = Calc.LoadContentXML(path); + XmlElement at = xml["atlas"]; + + foreach (XmlElement tex in at) + { + var texturePath = tex.Attr("n", ""); + string fsloc = Engine.ContentDirectory +"\\"+ Path.GetDirectoryName(path)+ texturePath + ".png"; + var fileStream = new FileStream(fsloc, FileMode.Open, FileAccess.Read); + var texture = Texture2D.FromStream(Engine.Instance.GraphicsDevice, fileStream); + fileStream.Close(); + + var mTexture = new MTexture(texture); + atlas.Sources.Add(texture); + + foreach (XmlElement sub in tex) + { + var name = sub.Attr("n"); + var clipRect = new Rectangle(sub.AttrInt("x"), sub.AttrInt("y"), sub.AttrInt("w"), sub.AttrInt("h")); + if (sub.HasAttr("fx")) + atlas.textures[name] = new MTexture(mTexture, name, clipRect, new Vector2(-sub.AttrInt("fx"), -sub.AttrInt("fy")), sub.AttrInt("fw"), sub.AttrInt("fh")); + else + atlas.textures[name] = new MTexture(mTexture, name, clipRect); + } + } + } + break; + + case AtlasDataFormat.CrunchBinary: + using (var stream = File.OpenRead(Path.Combine(Engine.ContentDirectory, path))) + { + var reader = new BinaryReader(stream); + var textures = reader.ReadInt16(); + + for (int i = 0; i < textures; i++) + { + var textureName = reader.ReadNullTerminatedString(); + var texturePath = Path.Combine(Path.GetDirectoryName(path), textureName + ".png"); + var fileStream = new FileStream(texturePath, FileMode.Open, FileAccess.Read); + var texture = Texture2D.FromStream(Engine.Instance.GraphicsDevice, fileStream); + fileStream.Close(); + + atlas.Sources.Add(texture); + + var mTexture = new MTexture(texture); + var subtextures = reader.ReadInt16(); + for (int j = 0; j < subtextures; j++) + { + var name = reader.ReadNullTerminatedString(); + var x = reader.ReadInt16(); + var y = reader.ReadInt16(); + var w = reader.ReadInt16(); + var h = reader.ReadInt16(); + var fx = reader.ReadInt16(); + var fy = reader.ReadInt16(); + var fw = reader.ReadInt16(); + var fh = reader.ReadInt16(); + + atlas.textures[name] = new MTexture(mTexture, name, new Rectangle(x, y, w, h), new Vector2(-fx, -fy), fw, fh); + } + } + } + break; + + case AtlasDataFormat.CrunchBinaryNoAtlas: + using (var stream = File.OpenRead(Path.Combine(Engine.ContentDirectory, path + ".bin"))) + { + var reader = new BinaryReader(stream); + var folders = reader.ReadInt16(); + + for (int i = 0; i < folders; i++) + { + var folderName = reader.ReadNullTerminatedString(); + var folderPath = Path.Combine(Path.GetDirectoryName(path), folderName); + + var subtextures = reader.ReadInt16(); + for (int j = 0; j < subtextures; j++) + { + var name = reader.ReadNullTerminatedString(); + var x = reader.ReadInt16(); + var y = reader.ReadInt16(); + var w = reader.ReadInt16(); + var h = reader.ReadInt16(); + var fx = reader.ReadInt16(); + var fy = reader.ReadInt16(); + var fw = reader.ReadInt16(); + var fh = reader.ReadInt16(); + + var fileStream = new FileStream(Path.Combine(folderPath, name + ".png"), FileMode.Open, FileAccess.Read); + var texture = Texture2D.FromStream(Engine.Instance.GraphicsDevice, fileStream); + fileStream.Close(); + + atlas.Sources.Add(texture); + atlas.textures[name] = new MTexture(texture, new Vector2(-fx, -fy), fw, fh); + } + } + } + break; + + case AtlasDataFormat.Packer: + + using (var stream = File.OpenRead(Path.Combine(Engine.ContentDirectory, path + ".meta"))) + { + var reader = new BinaryReader(stream); + reader.ReadInt32(); // version + reader.ReadString(); // args + reader.ReadInt32(); // hash + + var textures = reader.ReadInt16(); + for (int i = 0; i < textures; i++) + { + var textureName = reader.ReadString(); + var texturePath = Path.Combine(Path.GetDirectoryName(path), textureName + ".data"); + var fileStream = new FileStream(texturePath, FileMode.Open, FileAccess.Read); + var texture = Texture2D.FromStream(Engine.Instance.GraphicsDevice, fileStream); + fileStream.Close(); + + atlas.Sources.Add(texture); + + var mTexture = new MTexture(texture); + var subtextures = reader.ReadInt16(); + for (int j = 0; j < subtextures; j++) + { + var name = reader.ReadString().Replace('\\', '/'); + var x = reader.ReadInt16(); + var y = reader.ReadInt16(); + var w = reader.ReadInt16(); + var h = reader.ReadInt16(); + var fx = reader.ReadInt16(); + var fy = reader.ReadInt16(); + var fw = reader.ReadInt16(); + var fh = reader.ReadInt16(); + + atlas.textures[name] = new MTexture(mTexture, name, new Rectangle(x, y, w, h), new Vector2(-fx, -fy), fw, fh); + } + } + } + + break; + + case AtlasDataFormat.PackerNoAtlas: + using (var stream = File.OpenRead(Path.Combine(Engine.ContentDirectory, path + ".meta"))) + { + var reader = new BinaryReader(stream); + reader.ReadInt32(); // version + reader.ReadString(); // args + reader.ReadInt32(); // hash + + var folders = reader.ReadInt16(); + for (int i = 0; i < folders; i++) + { + var folderName = reader.ReadString(); + var folderPath = Path.Combine(Path.GetDirectoryName(path), folderName); + + var subtextures = reader.ReadInt16(); + for (int j = 0; j < subtextures; j++) + { + var name = reader.ReadString().Replace('\\', '/'); + var x = reader.ReadInt16(); + var y = reader.ReadInt16(); + var w = reader.ReadInt16(); + var h = reader.ReadInt16(); + var fx = reader.ReadInt16(); + var fy = reader.ReadInt16(); + var fw = reader.ReadInt16(); + var fh = reader.ReadInt16(); + + var fileStream = new FileStream(Path.Combine(folderPath, name + ".data"), FileMode.Open, FileAccess.Read); + var texture = Texture2D.FromStream(Engine.Instance.GraphicsDevice, fileStream); + fileStream.Close(); + + atlas.Sources.Add(texture); + atlas.textures[name] = new MTexture(texture, new Vector2(-fx, -fy), fw, fh); + } + } + } + break; + + case AtlasDataFormat.CrunchXmlOrBinary: + + if (File.Exists(Path.Combine(Engine.ContentDirectory, path + ".bin"))) + ReadAtlasData(atlas, path + ".bin", AtlasDataFormat.CrunchBinary); + else + ReadAtlasData(atlas, path + ".xml", AtlasDataFormat.CrunchXml); + + break; + + + default: + throw new NotImplementedException(); + } + } + + public static Atlas FromMultiAtlas(string rootPath, string[] dataPath, AtlasDataFormat format) + { + var atlas = new Atlas(); + atlas.Sources = new List(); + + for (int i = 0; i < dataPath.Length; i ++) + ReadAtlasData(atlas, Path.Combine(rootPath, dataPath[i]), format); + + return atlas; + } + + public static Atlas FromMultiAtlas(string rootPath, string filename, AtlasDataFormat format) + { + var atlas = new Atlas(); + atlas.Sources = new List(); + + var index = 0; + while (true) + { + var dataPath = Path.Combine(rootPath, filename + index.ToString() + ".xml"); + + if (!File.Exists(Path.Combine(Engine.ContentDirectory, dataPath))) + break; + + ReadAtlasData(atlas, dataPath, format); + index++; + } + + return atlas; + } + + public static Atlas FromDirectory(string path) + { + var atlas = new Atlas(); + atlas.Sources = new List(); + + var contentDirectory = Engine.ContentDirectory; + var contentDirectoryLength = contentDirectory.Length; + var contentPath = Path.Combine(contentDirectory, path); + var contentPathLength = contentPath.Length; + + foreach (var file in Directory.GetFiles(contentPath, "*", SearchOption.AllDirectories)) + { + var ext = Path.GetExtension(file); + if (ext != ".png" && ext != ".xnb") + continue; + + // get path and load + var fileStream = new FileStream(file.Substring(contentDirectoryLength + 1), FileMode.Open, FileAccess.Read); + var texture = Texture2D.FromStream(Engine.Instance.GraphicsDevice, fileStream); + fileStream.Close(); + + atlas.Sources.Add(texture); + + // make nice for dictionary + var filepath = file.Substring(contentPathLength + 1); + filepath = filepath.Substring(0, filepath.Length - 4); + filepath = filepath.Replace('\\', '/'); + + // load + atlas.textures.Add(filepath, new MTexture(texture)); + } + + return atlas; + } + + public MTexture this[string id] + { + get { return textures[id]; } + set { textures[id] = value; } + } + + public bool Has(string id) + { + return textures.ContainsKey(id); + } + + public MTexture GetOrDefault(string id, MTexture defaultTexture) + { + if (String.IsNullOrEmpty(id) || !Has(id)) + return defaultTexture; + return textures[id]; + } + + public List GetAtlasSubtextures(string key) + { + List list; + + if (!orderedTexturesCache.TryGetValue(key, out list)) + { + list = new List(); + + var index = 0; + while (true) + { + var texture = GetAtlasSubtextureFromAtlasAt(key, index); + if (texture != null) + list.Add(texture); + else + break; + index++; + } + + orderedTexturesCache.Add(key, list); + } + + return list; + } + + private MTexture GetAtlasSubtextureFromCacheAt(string key, int index) + { + return orderedTexturesCache[key][index]; + } + + private MTexture GetAtlasSubtextureFromAtlasAt(string key, int index) + { + if (index == 0 && textures.ContainsKey(key)) + return textures[key]; + + var indexString = index.ToString(); + var startLength = indexString.Length; + while (indexString.Length < startLength + 6) + { + MTexture result; + if (textures.TryGetValue(key + indexString, out result)) + return result; + indexString = "0" + indexString; + } + + return null; + } + + public MTexture GetAtlasSubtexturesAt(string key, int index) + { + List list; + if (orderedTexturesCache.TryGetValue(key, out list)) + return list[index]; + else + return GetAtlasSubtextureFromAtlasAt(key, index); + } + + public void Dispose() + { + foreach (var texture in Sources) + texture.Dispose(); + Sources.Clear(); + textures.Clear(); + } + + + } +} diff --git a/MonocleEngineDemo/Monocle/Graphics/MTexture.cs b/MonocleEngineDemo/Monocle/Graphics/MTexture.cs new file mode 100644 index 0000000..96c233d --- /dev/null +++ b/MonocleEngineDemo/Monocle/Graphics/MTexture.cs @@ -0,0 +1,827 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; + +namespace Monocle +{ + /// + /// Monocle texture is a wrapper class that handles textures without any complex setups. + /// + public class MTexture + { + static public MTexture FromFile(string filename) + { + var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read); + var texture = Texture2D.FromStream(Engine.Instance.GraphicsDevice, fileStream); + fileStream.Close(); + + return new MTexture(texture); + } + + public MTexture() { } + + public MTexture(Texture2D texture) + { + Texture = texture; + AtlasPath = null; + ClipRect = new Rectangle(0, 0, Texture.Width, Texture.Height); + DrawOffset = Vector2.Zero; + Width = ClipRect.Width; + Height = ClipRect.Height; + SetUtil(); + } + + public MTexture(MTexture parent, int x, int y, int width, int height) + { + Texture = parent.Texture; + AtlasPath = null; + + ClipRect = parent.GetRelativeRect(x, y, width, height); + DrawOffset = new Vector2(-Math.Min(x - parent.DrawOffset.X, 0), -Math.Min(y - parent.DrawOffset.Y, 0)); + Width = width; + Height = height; + SetUtil(); + } + + public MTexture(MTexture parent, Rectangle clipRect) + : this(parent, clipRect.X, clipRect.Y, clipRect.Width, clipRect.Height) + { + + } + + public MTexture(MTexture parent, string atlasPath, Rectangle clipRect, Vector2 drawOffset, int width, int height) + { + Texture = parent.Texture; + AtlasPath = atlasPath; + + ClipRect = parent.GetRelativeRect(clipRect); + DrawOffset = drawOffset; + Width = width; + Height = height; + SetUtil(); + } + + public MTexture(MTexture parent, string atlasPath, Rectangle clipRect) + : this(parent, clipRect) + { + AtlasPath = atlasPath; + } + + public MTexture(Texture2D texture, Vector2 drawOffset, int frameWidth, int frameHeight) + { + Texture = texture; + ClipRect = new Rectangle(0, 0, texture.Width, texture.Height); + DrawOffset = drawOffset; + Width = frameWidth; + Height = frameHeight; + SetUtil(); + } + + public MTexture(int width, int height, Color color) + { + Texture = new Texture2D(Engine.Instance.GraphicsDevice, width, height); + var colors = new Color[width * height]; + for (int i = 0; i < width * height; i++) + colors[i] = color; + Texture.SetData(colors); + + ClipRect = new Rectangle(0, 0, width, height); + DrawOffset = Vector2.Zero; + Width = width; + Height = height; + SetUtil(); + } + + private void SetUtil() + { + Center = new Vector2(Width, Height) * 0.5f; + LeftUV = ClipRect.Left / (float)Texture.Width; + RightUV = ClipRect.Right / (float)Texture.Width; + TopUV = ClipRect.Top / (float)Texture.Height; + BottomUV = ClipRect.Bottom / (float)Texture.Height; + } + + public void Unload() + { + Texture.Dispose(); + Texture = null; + } + + public MTexture GetSubtexture(int x, int y, int width, int height, MTexture applyTo = null) + { + if (applyTo == null) + return new MTexture(this, x, y, width, height); + else + { + applyTo.Texture = Texture; + applyTo.AtlasPath = null; + + applyTo.ClipRect = GetRelativeRect(x, y, width, height); + applyTo.DrawOffset = new Vector2(-Math.Min(x - DrawOffset.X, 0), -Math.Min(y - DrawOffset.Y, 0)); + applyTo.Width = width; + applyTo.Height = height; + applyTo.SetUtil(); + + return applyTo; + } + } + + public MTexture GetSubtexture(Rectangle rect) + { + return new MTexture(this, rect); + } + + public void Dispose() + { + Texture.Dispose(); + } + + #region Properties + + public Texture2D Texture { get; private set; } + public Rectangle ClipRect { get; private set; } + public string AtlasPath { get; private set; } + public Vector2 DrawOffset { get; private set; } + public int Width { get; private set; } + public int Height { get; private set; } + public Vector2 Center { get; private set; } + public float LeftUV { get; private set; } + public float RightUV { get; private set; } + public float TopUV { get; private set; } + public float BottomUV { get; private set; } + + #endregion + + #region Helpers + + public override string ToString() + { + if (AtlasPath != null) + return AtlasPath; + else + return "MTexture [" + Texture.Width + " x " + Texture.Height + "]"; + } + + public Rectangle GetRelativeRect(Rectangle rect) + { + return GetRelativeRect(rect.X, rect.Y, rect.Width, rect.Height); + } + + public Rectangle GetRelativeRect(int x, int y, int width, int height) + { + int atX = (int)(ClipRect.X - DrawOffset.X + x); + int atY = (int)(ClipRect.Y - DrawOffset.Y + y); + + int rX = (int)MathHelper.Clamp(atX, ClipRect.Left, ClipRect.Right); + int rY = (int)MathHelper.Clamp(atY, ClipRect.Top, ClipRect.Bottom); + int rW = Math.Max(0, Math.Min(atX + width, ClipRect.Right) - rX); + int rH = Math.Max(0, Math.Min(atY + height, ClipRect.Bottom) - rY); + + return new Rectangle(rX, rY, rW, rH); + } + + + public int TotalPixels + { + get { return Width * Height; } + } + + #endregion + + #region Draw + + public void Draw(Vector2 position) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, Color.White, 0, -DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void Draw(Vector2 position, Vector2 origin) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, Color.White, 0, origin - DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void Draw(Vector2 position, Vector2 origin, Color color) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, origin - DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void Draw(Vector2 position, Vector2 origin, Color color, float scale) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, origin - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void Draw(Vector2 position, Vector2 origin, Color color, float scale, float rotation) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, origin - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void Draw(Vector2 position, Vector2 origin, Color color, float scale, float rotation, SpriteEffects flip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, origin - DrawOffset, scale, flip, 0); + } + + public void Draw(Vector2 position, Vector2 origin, Color color, Vector2 scale) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, origin - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void Draw(Vector2 position, Vector2 origin, Color color, Vector2 scale, float rotation) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, origin - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void Draw(Vector2 position, Vector2 origin, Color color, Vector2 scale, float rotation, SpriteEffects flip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, origin - DrawOffset, scale, flip, 0); + } + + public void Draw(Vector2 position, Vector2 origin, Color color, Vector2 scale, float rotation, Rectangle clip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, GetRelativeRect(clip), color, rotation, origin - DrawOffset, scale, SpriteEffects.None, 0); + } + + #endregion + + #region Draw Centered + + public void DrawCentered(Vector2 position) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, Color.White, 0, Center - DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void DrawCentered(Vector2 position, Color color) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, Center - DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void DrawCentered(Vector2 position, Color color, float scale) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, Center - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawCentered(Vector2 position, Color color, float scale, float rotation) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, Center - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawCentered(Vector2 position, Color color, float scale, float rotation, SpriteEffects flip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, Center - DrawOffset, scale, flip, 0); + } + + public void DrawCentered(Vector2 position, Color color, Vector2 scale) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, Center - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawCentered(Vector2 position, Color color, Vector2 scale, float rotation) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, Center - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawCentered(Vector2 position, Color color, Vector2 scale, float rotation, SpriteEffects flip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, Center - DrawOffset, scale, flip, 0); + } + + #endregion + + #region Draw Justified + + public void DrawJustified(Vector2 position, Vector2 justify) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, Color.White, 0, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void DrawJustified(Vector2 position, Vector2 justify, Color color) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void DrawJustified(Vector2 position, Vector2 justify, Color color, float scale) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawJustified(Vector2 position, Vector2 justify, Color color, float scale, float rotation) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawJustified(Vector2 position, Vector2 justify, Color color, float scale, float rotation, SpriteEffects flip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, flip, 0); + } + + public void DrawJustified(Vector2 position, Vector2 justify, Color color, Vector2 scale) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawJustified(Vector2 position, Vector2 justify, Color color, Vector2 scale, float rotation) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawJustified(Vector2 position, Vector2 justify, Color color, Vector2 scale, float rotation, SpriteEffects flip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, flip, 0); + } + + #endregion + + #region Draw Outline + + public void DrawOutline(Vector2 position) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, -DrawOffset, 1f, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, Color.White, 0, -DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void DrawOutline(Vector2 position, Vector2 origin) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, origin - DrawOffset, 1f, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, Color.White, 0, origin - DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void DrawOutline(Vector2 position, Vector2 origin, Color color) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, origin - DrawOffset, 1f, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, origin - DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void DrawOutline(Vector2 position, Vector2 origin, Color color, float scale) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, origin - DrawOffset, scale, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, origin - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawOutline(Vector2 position, Vector2 origin, Color color, float scale, float rotation) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, rotation, origin - DrawOffset, scale, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, origin - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawOutline(Vector2 position, Vector2 origin, Color color, float scale, float rotation, SpriteEffects flip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, rotation, origin - DrawOffset, scale, flip, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, origin - DrawOffset, scale, flip, 0); + } + + public void DrawOutline(Vector2 position, Vector2 origin, Color color, Vector2 scale) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, origin - DrawOffset, scale, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, origin - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawOutline(Vector2 position, Vector2 origin, Color color, Vector2 scale, float rotation) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, rotation, origin - DrawOffset, scale, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, origin - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawOutline(Vector2 position, Vector2 origin, Color color, Vector2 scale, float rotation, SpriteEffects flip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, rotation, origin - DrawOffset, scale, flip, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, origin - DrawOffset, scale, flip, 0); + } + + #endregion + + #region Draw Outline Centered + + public void DrawOutlineCentered(Vector2 position) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, Center - DrawOffset, 1f, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, Color.White, 0, Center - DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void DrawOutlineCentered(Vector2 position, Color color) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, Center - DrawOffset, 1f, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, Center - DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void DrawOutlineCentered(Vector2 position, Color color, float scale) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, Center - DrawOffset, scale, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, Center - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawOutlineCentered(Vector2 position, Color color, float scale, float rotation) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, rotation, Center - DrawOffset, scale, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, Center - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawOutlineCentered(Vector2 position, Color color, float scale, float rotation, SpriteEffects flip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, rotation, Center - DrawOffset, scale, flip, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, Center - DrawOffset, scale, flip, 0); + } + + public void DrawOutlineCentered(Vector2 position, Color color, Vector2 scale) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, Center - DrawOffset, scale, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, Center - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawOutlineCentered(Vector2 position, Color color, Vector2 scale, float rotation) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, rotation, Center - DrawOffset, scale, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, Center - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawOutlineCentered(Vector2 position, Color color, Vector2 scale, float rotation, SpriteEffects flip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, rotation, Center - DrawOffset, scale, flip, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, Center - DrawOffset, scale, flip, 0); + } + + #endregion + + #region Draw Outline Justified + + public void DrawOutlineJustified(Vector2 position, Vector2 justify) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, 1f, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, Color.White, 0, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void DrawOutlineJustified(Vector2 position, Vector2 justify, Color color) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, 1f, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, 1f, SpriteEffects.None, 0); + } + + public void DrawOutlineJustified(Vector2 position, Vector2 justify, Color color, float scale) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawOutlineJustified(Vector2 position, Vector2 justify, Color color, float scale, float rotation) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, rotation, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawOutlineJustified(Vector2 position, Vector2 justify, Color color, float scale, float rotation, SpriteEffects flip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, rotation, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, flip, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, flip, 0); + } + + public void DrawOutlineJustified(Vector2 position, Vector2 justify, Color color, Vector2 scale) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, 0, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, 0, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawOutlineJustified(Vector2 position, Vector2 justify, Color color, Vector2 scale, float rotation) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, rotation, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, SpriteEffects.None, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, SpriteEffects.None, 0); + } + + public void DrawOutlineJustified(Vector2 position, Vector2 justify, Color color, Vector2 scale, float rotation, SpriteEffects flip) + { +#if DEBUG + if (Texture.IsDisposed) + throw new Exception("Texture2D Is Disposed"); +#endif + + for (var i = -1; i <= 1; i++) + for (var j = -1; j <= 1; j++) + if (i != 0 || j != 0) + Monocle.Draw.SpriteBatch.Draw(Texture, position + new Vector2(i, j), ClipRect, Color.Black, rotation, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, flip, 0); + + Monocle.Draw.SpriteBatch.Draw(Texture, position, ClipRect, color, rotation, new Vector2(Width * justify.X, Height * justify.Y) - DrawOffset, scale, flip, 0); + } + + #endregion + } +} diff --git a/MonocleEngineDemo/Monocle/Graphics/SpriteBank.cs b/MonocleEngineDemo/Monocle/Graphics/SpriteBank.cs new file mode 100644 index 0000000..0aef3e7 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Graphics/SpriteBank.cs @@ -0,0 +1,74 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Monocle +{ + /// + /// SpriteBank is a collection of sprites. Used along with atlases to make sprite creation easier. + /// + public class SpriteBank + { + public Atlas Atlas; + public XmlDocument XML; + public Dictionary SpriteData; + + /// + /// Constructor of a SpriteBank. + /// + /// The atlas from which are the textures mapped. + /// The XML file that contains all the data of sprites and their animations. + public SpriteBank(Atlas atlas, XmlDocument xml) + { + Atlas = atlas; + XML = xml; + + SpriteData = new Dictionary(StringComparer.OrdinalIgnoreCase); + var elements = new Dictionary(); + foreach (var e in XML["Sprites"].ChildNodes) + { + if (e is XmlElement) + { + var element = e as XmlElement; + elements.Add(element.Name, element); + + if (SpriteData.ContainsKey(element.Name)) + throw new Exception("Duplicate sprite name in SpriteData: '" + element.Name + "'!"); + + var data = SpriteData[element.Name] = new SpriteData(Atlas); + if (element.HasAttr("copy")) + data.Add(elements[element.Attr("copy")], element.Attr("path")); + data.Add(element); + } + } + } + + public SpriteBank(Atlas atlas, string xmlPath) + : this(atlas, Calc.LoadContentXML(xmlPath)) + { + + } + + public bool Has(string id) + { + return SpriteData.ContainsKey(id); + } + + public Sprite Create(string id) + { + if (SpriteData.ContainsKey(id)) + return SpriteData[id].Create(); + else + throw new Exception("Missing animation name in SpriteData: '" + id + "'!"); + } + + public Sprite CreateOn(Sprite sprite, string id) + { + if (SpriteData.ContainsKey(id)) + return SpriteData[id].CreateOn(sprite); + else + throw new Exception("Missing animation name in SpriteData: '" + id + "'!"); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Graphics/SpriteData.cs b/MonocleEngineDemo/Monocle/Graphics/SpriteData.cs new file mode 100644 index 0000000..8f10e37 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Graphics/SpriteData.cs @@ -0,0 +1,163 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace Monocle +{ + public class SpriteDataSource + { + public XmlElement XML; + public string Path; + public string OverridePath; + } + + public class SpriteData + { + public List Sources = new List(); + public Sprite Sprite; + public Atlas Atlas; + + public SpriteData(Atlas atlas) + { + Sprite = new Sprite(atlas, ""); + Atlas = atlas; + } + + public void Add(XmlElement xml, string overridePath = null) + { + var source = new SpriteDataSource(); + source.XML = xml; + source.Path = source.XML.Attr("path"); + source.OverridePath = overridePath; + + //Error Checking + { + var prefix = "Sprite '" + source.XML.Name + "': "; + + //Path + if (!source.XML.HasAttr("path") && string.IsNullOrEmpty(overridePath)) + throw new Exception(prefix + "'path' is missing!"); + + //Anims + var ids = new HashSet(); + foreach (XmlElement anim in source.XML.GetElementsByTagName("Anim")) + CheckAnimXML(anim, prefix, ids); + foreach (XmlElement loop in source.XML.GetElementsByTagName("Loop")) + CheckAnimXML(loop, prefix, ids); + + //Start + if (source.XML.HasAttr("start") && !ids.Contains(source.XML.Attr("start"))) + throw new Exception(prefix + "starting animation '" + source.XML.Attr("start") + "' is missing!"); + + //Origin + if (source.XML.HasChild("Justify") && source.XML.HasChild("Origin")) + throw new Exception(prefix + "has both Origin and Justify tags!"); + } + + //Create the Sprite + { + var normalPath = source.XML.Attr("path", ""); + var masterDelay = source.XML.AttrFloat("delay", 0); + + //Build Animations + foreach (XmlElement anim in source.XML.GetElementsByTagName("Anim")) + { + Chooser into; + if (anim.HasAttr("goto")) + into = Chooser.FromString(anim.Attr("goto")); + else + into = null; + + var id = anim.Attr("id"); + var path = anim.Attr("path", ""); + var frames = Calc.ReadCSVIntWithTricks(anim.Attr("frames", "")); + + if (!string.IsNullOrEmpty(overridePath) && HasFrames(Atlas, overridePath + path, frames)) + path = overridePath + path; + else + path = normalPath + path; + + Sprite.Add(id, path, anim.AttrFloat("delay", masterDelay), into, frames); + } + + //Build Loops + foreach (XmlElement loop in source.XML.GetElementsByTagName("Loop")) + { + var id = loop.Attr("id"); + var path = loop.Attr("path", ""); + var frames = Calc.ReadCSVIntWithTricks(loop.Attr("frames", "")); + + if (!string.IsNullOrEmpty(overridePath) && HasFrames(Atlas, overridePath + path, frames)) + path = overridePath + path; + else + path = normalPath + path; + + Sprite.AddLoop(id, path, loop.AttrFloat("delay", masterDelay), frames); + } + + //Origin + if (source.XML.HasChild("Center")) + { + Sprite.CenterOrigin(); + Sprite.Justify = new Vector2(.5f, .5f); + } + else if (source.XML.HasChild("Justify")) + { + Sprite.JustifyOrigin(source.XML.ChildPosition("Justify")); + Sprite.Justify = source.XML.ChildPosition("Justify"); + } + else if (source.XML.HasChild("Origin")) + Sprite.Origin = source.XML.ChildPosition("Origin"); + + //Position + if (source.XML.HasChild("Position")) + Sprite.Position = source.XML.ChildPosition("Position"); + + //Start Animation + if (source.XML.HasAttr("start")) + Sprite.Play(source.XML.Attr("start")); + } + + Sources.Add(source); + } + + private bool HasFrames(Atlas atlas, string path, int[] frames = null) + { + if (frames == null || frames.Length <= 0) + return atlas.GetAtlasSubtexturesAt(path, 0) != null; + else + { + for (int i = 0; i < frames.Length; i++) + if (atlas.GetAtlasSubtexturesAt(path, frames[i]) == null) + return false; + + return true; + } + } + + private void CheckAnimXML(XmlElement xml, string prefix, HashSet ids) + { + if (!xml.HasAttr("id")) + throw new Exception(prefix + "'id' is missing on " + xml.Name + "!"); + + if (ids.Contains(xml.Attr("id"))) + throw new Exception(prefix + "multiple animations with id '" + xml.Attr("id") + "'!"); + + ids.Add(xml.Attr("id")); + } + + public Sprite Create() + { + return Sprite.CreateClone(); + } + + public Sprite CreateOn(Sprite sprite) + { + return Sprite.CloneInto(sprite); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Graphics/Tileset.cs b/MonocleEngineDemo/Monocle/Graphics/Tileset.cs new file mode 100644 index 0000000..a8930b2 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Graphics/Tileset.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace Monocle +{ + public class Tileset + { + private MTexture[,] tiles; + + public Tileset(MTexture texture, int tileWidth, int tileHeight) + { + Texture = texture; + TileWidth = tileWidth; + TileHeight = TileHeight; + + tiles = new MTexture[Texture.Width / tileWidth, Texture.Height / tileHeight]; + for (int x = 0; x < Texture.Width / tileWidth; x++) + for (int y = 0; y < Texture.Height / tileHeight; y++) + tiles[x, y] = new MTexture(Texture, x * tileWidth, y * tileHeight, tileWidth, tileHeight); + } + + public MTexture Texture + { + get; private set; + } + + public int TileWidth + { + get; private set; + } + + public int TileHeight + { + get; private set; + } + + public MTexture this[int x, int y] + { + get + { + return tiles[x, y]; + } + } + + public MTexture this[int index] + { + get + { + if (index < 0) + return null; + else + return tiles[index % tiles.GetLength(0), index / tiles.GetLength(0)]; + } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Input/MInput.cs b/MonocleEngineDemo/Monocle/Input/MInput.cs new file mode 100644 index 0000000..04288b2 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Input/MInput.cs @@ -0,0 +1,872 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using System; +using System.Collections.Generic; + +namespace Monocle +{ + public static class MInput + { + public static KeyboardData Keyboard { get; private set; } + public static MouseData Mouse { get; private set; } + public static GamePadData[] GamePads { get; private set; } + + internal static List VirtualInputs; + + public static bool Active = true; + public static bool Disabled = false; + + internal static void Initialize() + { + //Init devices + Keyboard = new KeyboardData(); + Mouse = new MouseData(); + GamePads = new GamePadData[4]; + for (int i = 0; i < 4; i++) + GamePads[i] = new GamePadData((PlayerIndex)i); + VirtualInputs = new List(); + } + + internal static void Shutdown() + { + foreach (var gamepad in GamePads) + gamepad.StopRumble(); + } + + internal static void Update() + { + if (Engine.Instance.IsActive && Active) + { + if (Engine.Commands.Open) + { + Keyboard.UpdateNull(); + Mouse.UpdateNull(); + } + else + { + Keyboard.Update(); + Mouse.Update(); + } + + for (int i = 0; i < 4; i++) + GamePads[i].Update(); + } + else + { + Keyboard.UpdateNull(); + Mouse.UpdateNull(); + for (int i = 0; i < 4; i++) + GamePads[i].UpdateNull(); + } + + UpdateVirtualInputs(); + } + + public static void UpdateNull() + { + Keyboard.UpdateNull(); + Mouse.UpdateNull(); + for (int i = 0; i < 4; i++) + GamePads[i].UpdateNull(); + + UpdateVirtualInputs(); + } + + private static void UpdateVirtualInputs() + { + foreach (var virtualInput in VirtualInputs) + virtualInput.Update(); + } + + #region Keyboard + + public class KeyboardData + { + public KeyboardState PreviousState; + public KeyboardState CurrentState; + + internal KeyboardData() + { + + } + + internal void Update() + { + PreviousState = CurrentState; + CurrentState = Microsoft.Xna.Framework.Input.Keyboard.GetState(); + } + + internal void UpdateNull() + { + PreviousState = CurrentState; + CurrentState = new KeyboardState(); + } + + #region Basic Checks + + public bool Check(Keys key) + { + if (Disabled) + return false; + + return CurrentState.IsKeyDown(key); + } + + public bool Pressed(Keys key) + { + if (Disabled) + return false; + + return CurrentState.IsKeyDown(key) && !PreviousState.IsKeyDown(key); + } + + public bool Released(Keys key) + { + if (Disabled) + return false; + + return !CurrentState.IsKeyDown(key) && PreviousState.IsKeyDown(key); + } + + #endregion + + #region Convenience Checks + + public bool Check(Keys keyA, Keys keyB) + { + return Check(keyA) || Check(keyB); + } + + public bool Pressed(Keys keyA, Keys keyB) + { + return Pressed(keyA) || Pressed(keyB); + } + + public bool Released(Keys keyA, Keys keyB) + { + return Released(keyA) || Released(keyB); + } + + public bool Check(Keys keyA, Keys keyB, Keys keyC) + { + return Check(keyA) || Check(keyB) || Check(keyC); + } + + public bool Pressed(Keys keyA, Keys keyB, Keys keyC) + { + return Pressed(keyA) || Pressed(keyB) || Pressed(keyC); + } + + public bool Released(Keys keyA, Keys keyB, Keys keyC) + { + return Released(keyA) || Released(keyB) || Released(keyC); + } + + #endregion + + #region Axis + + public int AxisCheck(Keys negative, Keys positive) + { + if (Check(negative)) + { + if (Check(positive)) + return 0; + else + return -1; + } + else if (Check(positive)) + return 1; + else + return 0; + } + + public int AxisCheck(Keys negative, Keys positive, int both) + { + if (Check(negative)) + { + if (Check(positive)) + return both; + else + return -1; + } + else if (Check(positive)) + return 1; + else + return 0; + } + + #endregion + } + + #endregion + + #region Mouse + + public class MouseData + { + public MouseState PreviousState; + public MouseState CurrentState; + + internal MouseData() + { + PreviousState = new MouseState(); + CurrentState = new MouseState(); + } + + internal void Update() + { + PreviousState = CurrentState; + CurrentState = Microsoft.Xna.Framework.Input.Mouse.GetState(); + } + + internal void UpdateNull() + { + PreviousState = CurrentState; + CurrentState = new MouseState(); + } + + #region Buttons + + public bool CheckLeftButton + { + get { return CurrentState.LeftButton == ButtonState.Pressed; } + } + + public bool CheckRightButton + { + get { return CurrentState.RightButton == ButtonState.Pressed; } + } + + public bool CheckMiddleButton + { + get { return CurrentState.MiddleButton == ButtonState.Pressed; } + } + + public bool PressedLeftButton + { + get { return CurrentState.LeftButton == ButtonState.Pressed && PreviousState.LeftButton == ButtonState.Released; } + } + + public bool PressedRightButton + { + get { return CurrentState.RightButton == ButtonState.Pressed && PreviousState.RightButton == ButtonState.Released; } + } + + public bool PressedMiddleButton + { + get { return CurrentState.MiddleButton == ButtonState.Pressed && PreviousState.MiddleButton == ButtonState.Released; } + } + + public bool ReleasedLeftButton + { + get { return CurrentState.LeftButton == ButtonState.Released && PreviousState.LeftButton == ButtonState.Pressed; } + } + + public bool ReleasedRightButton + { + get { return CurrentState.RightButton == ButtonState.Released && PreviousState.RightButton == ButtonState.Pressed; } + } + + public bool ReleasedMiddleButton + { + get { return CurrentState.MiddleButton == ButtonState.Released && PreviousState.MiddleButton == ButtonState.Pressed; } + } + + #endregion + + #region Wheel + + public int Wheel + { + get { return CurrentState.ScrollWheelValue; } + } + + public int WheelDelta + { + get { return CurrentState.ScrollWheelValue - PreviousState.ScrollWheelValue; } + } + + #endregion + + #region Position + + public bool WasMoved + { + get + { + return CurrentState.X != PreviousState.X + || CurrentState.Y != PreviousState.Y; + } + } + + public float X + { + get { return Position.X; } + set { Position = new Vector2(value, Position.Y); } + } + + public float Y + { + get { return Position.Y; } + set { Position = new Vector2(Position.X, value); } + } + + public Vector2 Position + { + get + { + return Vector2.Transform(new Vector2(CurrentState.X, CurrentState.Y), Matrix.Invert(Engine.ScreenMatrix)); + } + + set + { + var vector = Vector2.Transform(value, Engine.ScreenMatrix); + Microsoft.Xna.Framework.Input.Mouse.SetPosition((int)Math.Round(vector.X), (int)Math.Round(vector.Y)); + } + } + + #endregion + } + + #endregion + + #region GamePads + + public class GamePadData + { + public PlayerIndex PlayerIndex { get; private set; } + public GamePadState PreviousState; + public GamePadState CurrentState; + public bool Attached; + + private float rumbleStrength; + private float rumbleTime; + + internal GamePadData(PlayerIndex playerIndex) + { + PlayerIndex = playerIndex; + } + + public void Update() + { + PreviousState = CurrentState; + CurrentState = Microsoft.Xna.Framework.Input.GamePad.GetState(PlayerIndex); + Attached = CurrentState.IsConnected; + + if (rumbleTime > 0) + { + rumbleTime -= Engine.DeltaTime; + if (rumbleTime <= 0) + GamePad.SetVibration(PlayerIndex, 0, 0); + } + } + + public void UpdateNull() + { + PreviousState = CurrentState; + CurrentState = new GamePadState(); + Attached = Microsoft.Xna.Framework.Input.GamePad.GetState(PlayerIndex).IsConnected; + + if (rumbleTime > 0) + rumbleTime -= Engine.DeltaTime; + + GamePad.SetVibration(PlayerIndex, 0, 0); + } + + public void Rumble(float strength, float time) + { + if (rumbleTime <= 0 || strength > rumbleStrength || (strength == rumbleStrength && time > rumbleTime)) + { + GamePad.SetVibration(PlayerIndex, strength, strength); + rumbleStrength = strength; + rumbleTime = time; + } + } + + public void StopRumble() + { + GamePad.SetVibration(PlayerIndex, 0, 0); + rumbleTime = 0; + } + + #region Buttons + + public bool Check(Buttons button) + { + if (Disabled) + return false; + + return CurrentState.IsButtonDown(button); + } + + public bool Pressed(Buttons button) + { + if (Disabled) + return false; + + return CurrentState.IsButtonDown(button) && PreviousState.IsButtonUp(button); + } + + public bool Released(Buttons button) + { + if (Disabled) + return false; + + return CurrentState.IsButtonUp(button) && PreviousState.IsButtonDown(button); + } + + public bool Check(Buttons buttonA, Buttons buttonB) + { + return Check(buttonA) || Check(buttonB); + } + + public bool Pressed(Buttons buttonA, Buttons buttonB) + { + return Pressed(buttonA) || Pressed(buttonB); + } + + public bool Released(Buttons buttonA, Buttons buttonB) + { + return Released(buttonA) || Released(buttonB); + } + + public bool Check(Buttons buttonA, Buttons buttonB, Buttons buttonC) + { + return Check(buttonA) || Check(buttonB) || Check(buttonC); + } + + public bool Pressed(Buttons buttonA, Buttons buttonB, Buttons buttonC) + { + return Pressed(buttonA) || Pressed(buttonB) || Check(buttonC); + } + + public bool Released(Buttons buttonA, Buttons buttonB, Buttons buttonC) + { + return Released(buttonA) || Released(buttonB) || Check(buttonC); + } + + #endregion + + #region Sticks + + public Vector2 GetLeftStick() + { + Vector2 ret = CurrentState.ThumbSticks.Left; + ret.Y = -ret.Y; + return ret; + } + + public Vector2 GetLeftStick(float deadzone) + { + Vector2 ret = CurrentState.ThumbSticks.Left; + if (ret.LengthSquared() < deadzone * deadzone) + ret = Vector2.Zero; + else + ret.Y = -ret.Y; + return ret; + } + + public Vector2 GetRightStick() + { + Vector2 ret = CurrentState.ThumbSticks.Right; + ret.Y = -ret.Y; + return ret; + } + + public Vector2 GetRightStick(float deadzone) + { + Vector2 ret = CurrentState.ThumbSticks.Right; + if (ret.LengthSquared() < deadzone * deadzone) + ret = Vector2.Zero; + else + ret.Y = -ret.Y; + return ret; + } + + #region Left Stick Directions + + public bool LeftStickLeftCheck(float deadzone) + { + return CurrentState.ThumbSticks.Left.X <= -deadzone; + } + + public bool LeftStickLeftPressed(float deadzone) + { + return CurrentState.ThumbSticks.Left.X <= -deadzone && PreviousState.ThumbSticks.Left.X > -deadzone; + } + + public bool LeftStickLeftReleased(float deadzone) + { + return CurrentState.ThumbSticks.Left.X > -deadzone && PreviousState.ThumbSticks.Left.X <= -deadzone; + } + + public bool LeftStickRightCheck(float deadzone) + { + return CurrentState.ThumbSticks.Left.X >= deadzone; + } + + public bool LeftStickRightPressed(float deadzone) + { + return CurrentState.ThumbSticks.Left.X >= deadzone && PreviousState.ThumbSticks.Left.X < deadzone; + } + + public bool LeftStickRightReleased(float deadzone) + { + return CurrentState.ThumbSticks.Left.X < deadzone && PreviousState.ThumbSticks.Left.X >= deadzone; + } + + public bool LeftStickDownCheck(float deadzone) + { + return CurrentState.ThumbSticks.Left.Y <= -deadzone; + } + + public bool LeftStickDownPressed(float deadzone) + { + return CurrentState.ThumbSticks.Left.Y <= -deadzone && PreviousState.ThumbSticks.Left.Y > -deadzone; + } + + public bool LeftStickDownReleased(float deadzone) + { + return CurrentState.ThumbSticks.Left.Y > -deadzone && PreviousState.ThumbSticks.Left.Y <= -deadzone; + } + + public bool LeftStickUpCheck(float deadzone) + { + return CurrentState.ThumbSticks.Left.Y >= deadzone; + } + + public bool LeftStickUpPressed(float deadzone) + { + return CurrentState.ThumbSticks.Left.Y >= deadzone && PreviousState.ThumbSticks.Left.Y < deadzone; + } + + public bool LeftStickUpReleased(float deadzone) + { + return CurrentState.ThumbSticks.Left.Y < deadzone && PreviousState.ThumbSticks.Left.Y >= deadzone; + } + + public float LeftStickHorizontal(float deadzone) + { + float h = CurrentState.ThumbSticks.Left.X; + if (Math.Abs(h) < deadzone) + return 0; + else + return h; + } + + public float LeftStickVertical(float deadzone) + { + float v = CurrentState.ThumbSticks.Left.Y; + if (Math.Abs(v) < deadzone) + return 0; + else + return -v; + } + + #endregion + + #region Right Stick Directions + + public bool RightStickLeftCheck(float deadzone) + { + return CurrentState.ThumbSticks.Right.X <= -deadzone; + } + + public bool RightStickLeftPressed(float deadzone) + { + return CurrentState.ThumbSticks.Right.X <= -deadzone && PreviousState.ThumbSticks.Right.X > -deadzone; + } + + public bool RightStickLeftReleased(float deadzone) + { + return CurrentState.ThumbSticks.Right.X > -deadzone && PreviousState.ThumbSticks.Right.X <= -deadzone; + } + + public bool RightStickRightCheck(float deadzone) + { + return CurrentState.ThumbSticks.Right.X >= deadzone; + } + + public bool RightStickRightPressed(float deadzone) + { + return CurrentState.ThumbSticks.Right.X >= deadzone && PreviousState.ThumbSticks.Right.X < deadzone; + } + + public bool RightStickRightReleased(float deadzone) + { + return CurrentState.ThumbSticks.Right.X < deadzone && PreviousState.ThumbSticks.Right.X >= deadzone; + } + + public bool RightStickUpCheck(float deadzone) + { + return CurrentState.ThumbSticks.Right.Y <= -deadzone; + } + + public bool RightStickUpPressed(float deadzone) + { + return CurrentState.ThumbSticks.Right.Y <= -deadzone && PreviousState.ThumbSticks.Right.Y > -deadzone; + } + + public bool RightStickUpReleased(float deadzone) + { + return CurrentState.ThumbSticks.Right.Y > -deadzone && PreviousState.ThumbSticks.Right.Y <= -deadzone; + } + + public bool RightStickDownCheck(float deadzone) + { + return CurrentState.ThumbSticks.Right.Y >= deadzone; + } + + public bool RightStickDownPressed(float deadzone) + { + return CurrentState.ThumbSticks.Right.Y >= deadzone && PreviousState.ThumbSticks.Right.Y < deadzone; + } + + public bool RightStickDownReleased(float deadzone) + { + return CurrentState.ThumbSticks.Right.Y < deadzone && PreviousState.ThumbSticks.Right.Y >= deadzone; + } + + public float RightStickHorizontal(float deadzone) + { + float h = CurrentState.ThumbSticks.Right.X; + if (Math.Abs(h) < deadzone) + return 0; + else + return h; + } + + public float RightStickVertical(float deadzone) + { + float v = CurrentState.ThumbSticks.Right.Y; + if (Math.Abs(v) < deadzone) + return 0; + else + return -v; + } + + #endregion + + #endregion + + #region DPad + + public int DPadHorizontal + { + get + { + return CurrentState.DPad.Right == ButtonState.Pressed ? 1 : (CurrentState.DPad.Left == ButtonState.Pressed ? -1 : 0); + } + } + + public int DPadVertical + { + get + { + return CurrentState.DPad.Down == ButtonState.Pressed ? 1 : (CurrentState.DPad.Up == ButtonState.Pressed ? -1 : 0); + } + } + + public Vector2 DPad + { + get + { + return new Vector2(DPadHorizontal, DPadVertical); + } + } + + public bool DPadLeftCheck + { + get + { + return CurrentState.DPad.Left == ButtonState.Pressed; + } + } + + public bool DPadLeftPressed + { + get + { + return CurrentState.DPad.Left == ButtonState.Pressed && PreviousState.DPad.Left == ButtonState.Released; + } + } + + public bool DPadLeftReleased + { + get + { + return CurrentState.DPad.Left == ButtonState.Released && PreviousState.DPad.Left == ButtonState.Pressed; + } + } + + public bool DPadRightCheck + { + get + { + return CurrentState.DPad.Right == ButtonState.Pressed; + } + } + + public bool DPadRightPressed + { + get + { + return CurrentState.DPad.Right == ButtonState.Pressed && PreviousState.DPad.Right == ButtonState.Released; + } + } + + public bool DPadRightReleased + { + get + { + return CurrentState.DPad.Right == ButtonState.Released && PreviousState.DPad.Right == ButtonState.Pressed; + } + } + + public bool DPadUpCheck + { + get + { + return CurrentState.DPad.Up == ButtonState.Pressed; + } + } + + public bool DPadUpPressed + { + get + { + return CurrentState.DPad.Up == ButtonState.Pressed && PreviousState.DPad.Up == ButtonState.Released; + } + } + + public bool DPadUpReleased + { + get + { + return CurrentState.DPad.Up == ButtonState.Released && PreviousState.DPad.Up == ButtonState.Pressed; + } + } + + public bool DPadDownCheck + { + get + { + return CurrentState.DPad.Down == ButtonState.Pressed; + } + } + + public bool DPadDownPressed + { + get + { + return CurrentState.DPad.Down == ButtonState.Pressed && PreviousState.DPad.Down == ButtonState.Released; + } + } + + public bool DPadDownReleased + { + get + { + return CurrentState.DPad.Down == ButtonState.Released && PreviousState.DPad.Down == ButtonState.Pressed; + } + } + + #endregion + + #region Triggers + + public bool LeftTriggerCheck(float threshold) + { + if (Disabled) + return false; + + return CurrentState.Triggers.Left >= threshold; + } + + public bool LeftTriggerPressed(float threshold) + { + if (Disabled) + return false; + + return CurrentState.Triggers.Left >= threshold && PreviousState.Triggers.Left < threshold; + } + + public bool LeftTriggerReleased(float threshold) + { + if (Disabled) + return false; + + return CurrentState.Triggers.Left < threshold && PreviousState.Triggers.Left >= threshold; + } + + public bool RightTriggerCheck(float threshold) + { + if (Disabled) + return false; + + return CurrentState.Triggers.Right >= threshold; + } + + public bool RightTriggerPressed(float threshold) + { + if (Disabled) + return false; + + return CurrentState.Triggers.Right >= threshold && PreviousState.Triggers.Right < threshold; + } + + public bool RightTriggerReleased(float threshold) + { + if (Disabled) + return false; + + return CurrentState.Triggers.Right < threshold && PreviousState.Triggers.Right >= threshold; + } + + #endregion + } + + #endregion + + #region Helpers + + public static void RumbleFirst(float strength, float time) + { + GamePads[0].Rumble(strength, time); + } + + public static int Axis(bool negative, bool positive, int bothValue) + { + if (negative) + { + if (positive) + return bothValue; + else + return -1; + } + else if (positive) + return 1; + else + return 0; + } + + public static int Axis(float axisValue, float deadzone) + { + if (Math.Abs(axisValue) >= deadzone) + return Math.Sign(axisValue); + else + return 0; + } + + public static int Axis(bool negative, bool positive, int bothValue, float axisValue, float deadzone) + { + int ret = Axis(axisValue, deadzone); + if (ret == 0) + ret = Axis(negative, positive, bothValue); + return ret; + } + + #endregion + } +} diff --git a/MonocleEngineDemo/Monocle/Input/VirtualAxis.cs b/MonocleEngineDemo/Monocle/Input/VirtualAxis.cs new file mode 100644 index 0000000..482a72f --- /dev/null +++ b/MonocleEngineDemo/Monocle/Input/VirtualAxis.cs @@ -0,0 +1,249 @@ +using Microsoft.Xna.Framework.Input; +using System.Collections.Generic; +using System; + +namespace Monocle +{ + /// + /// A virtual input represented as a float between -1 and 1 + /// + public class VirtualAxis : VirtualInput + { + public List Nodes; + + public float Value { get; private set; } + public float PreviousValue { get; private set; } + + public VirtualAxis() + : base() + { + Nodes = new List(); + } + + public VirtualAxis(params Node[] nodes) + : base() + { + Nodes = new List(nodes); + } + + public override void Update() + { + foreach (var node in Nodes) + node.Update(); + + PreviousValue = Value; + Value = 0; + foreach (var node in Nodes) + { + float value = node.Value; + if (value != 0) + { + Value = value; + break; + } + } + } + + public static implicit operator float(VirtualAxis axis) + { + return axis.Value; + } + + public abstract class Node : VirtualInputNode + { + public abstract float Value { get; } + } + + public class PadLeftStickX : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadLeftStickX(int gamepadIndex, float deadzone) + { + GamepadIndex = gamepadIndex; + Deadzone = deadzone; + } + + public override float Value + { + get + { + return Calc.SignThreshold(MInput.GamePads[GamepadIndex].GetLeftStick().X, Deadzone); + } + } + } + + public class PadLeftStickY : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadLeftStickY(int gamepadIndex, float deadzone) + { + GamepadIndex = gamepadIndex; + Deadzone = deadzone; + } + + public override float Value + { + get + { + return Calc.SignThreshold(MInput.GamePads[GamepadIndex].GetLeftStick().Y, Deadzone); + } + } + } + + public class PadRightStickX : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadRightStickX(int gamepadIndex, float deadzone) + { + GamepadIndex = gamepadIndex; + Deadzone = deadzone; + } + + public override float Value + { + get + { + return Calc.SignThreshold(MInput.GamePads[GamepadIndex].GetRightStick().X, Deadzone); + } + } + } + + public class PadRightStickY : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadRightStickY(int gamepadIndex, float deadzone) + { + GamepadIndex = gamepadIndex; + Deadzone = deadzone; + } + + public override float Value + { + get + { + return Calc.SignThreshold(MInput.GamePads[GamepadIndex].GetRightStick().Y, Deadzone); + } + } + } + + public class PadDpadLeftRight : Node + { + public int GamepadIndex; + + public PadDpadLeftRight(int gamepadIndex) + { + GamepadIndex = gamepadIndex; + } + + public override float Value + { + get + { + if (MInput.GamePads[GamepadIndex].DPadRightCheck) + return 1f; + else if (MInput.GamePads[GamepadIndex].DPadLeftCheck) + return -1f; + else + return 0; + } + } + } + + public class PadDpadUpDown : Node + { + public int GamepadIndex; + + public PadDpadUpDown(int gamepadIndex) + { + GamepadIndex = gamepadIndex; + } + + public override float Value + { + get + { + if (MInput.GamePads[GamepadIndex].DPadDownCheck) + return 1f; + else if (MInput.GamePads[GamepadIndex].DPadUpCheck) + return -1f; + else + return 0; + } + } + } + + public class KeyboardKeys : Node + { + public OverlapBehaviors OverlapBehavior; + public Keys Positive; + public Keys Negative; + + private float value; + private bool turned; + + public KeyboardKeys(OverlapBehaviors overlapBehavior, Keys negative, Keys positive) + { + OverlapBehavior = overlapBehavior; + Negative = negative; + Positive = positive; + } + + public override void Update() + { + if (MInput.Keyboard.Check(Positive)) + { + if (MInput.Keyboard.Check(Negative)) + { + switch (OverlapBehavior) + { + default: + case OverlapBehaviors.CancelOut: + value = 0; + break; + + case OverlapBehaviors.TakeNewer: + if (!turned) + { + value *= -1; + turned = true; + } + break; + + case OverlapBehaviors.TakeOlder: + //value stays the same + break; + } + } + else + { + turned = false; + value = 1; + } + } + else if (MInput.Keyboard.Check(Negative)) + { + turned = false; + value = -1; + } + else + { + turned = false; + value = 0; + } + } + + public override float Value + { + get { return value; } + } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Input/VirtualButton.cs b/MonocleEngineDemo/Monocle/Input/VirtualButton.cs new file mode 100644 index 0000000..1a42abb --- /dev/null +++ b/MonocleEngineDemo/Monocle/Input/VirtualButton.cs @@ -0,0 +1,902 @@ +using Microsoft.Xna.Framework.Input; +using System.Collections.Generic; + +namespace Monocle +{ + /// + /// A virtual input that is represented as a boolean. As well as simply checking the current button state, you can ask whether it was just pressed or released this frame. You can also keep the button press stored in a buffer for a limited time, or until it is consumed by calling ConsumeBuffer() + /// + public class VirtualButton : VirtualInput + { + public List Nodes; + public float BufferTime; + public bool Repeating { get; private set; } + + private float firstRepeatTime; + private float multiRepeatTime; + private float bufferCounter; + private float repeatCounter; + private bool canRepeat; + private bool consumed; + + public VirtualButton(float bufferTime) + : base() + { + Nodes = new List(); + BufferTime = bufferTime; + } + + public VirtualButton() + : this(0) + { + + } + + public VirtualButton(float bufferTime, params Node[] nodes) + : base() + { + Nodes = new List(nodes); + BufferTime = bufferTime; + } + + public VirtualButton(params Node[] nodes) + : this(0, nodes) + { + + } + + public void SetRepeat(float repeatTime) + { + SetRepeat(repeatTime, repeatTime); + } + + public void SetRepeat(float firstRepeatTime, float multiRepeatTime) + { + this.firstRepeatTime = firstRepeatTime; + this.multiRepeatTime = multiRepeatTime; + canRepeat = (this.firstRepeatTime > 0); + if (!canRepeat) + Repeating = false; + } + + public override void Update() + { + consumed = false; + bufferCounter -= Engine.DeltaTime; + + bool check = false; + foreach (var node in Nodes) + { + node.Update(); + if (node.Pressed) + { + bufferCounter = BufferTime; + check = true; + } + else if (node.Check) + check = true; + } + + if (!check) + { + Repeating = false; + repeatCounter = 0; + bufferCounter = 0; + } + else if (canRepeat) + { + Repeating = false; + if (repeatCounter == 0) + repeatCounter = firstRepeatTime; + else + { + repeatCounter -= Engine.DeltaTime; + if (repeatCounter <= 0) + { + Repeating = true; + repeatCounter = multiRepeatTime; + } + } + } + } + + public bool Check + { + get + { + if (MInput.Disabled) + return false; + + foreach (var node in Nodes) + if (node.Check) + return true; + return false; + } + } + + public bool Pressed + { + get + { + if (MInput.Disabled) + return false; + + if (consumed) + return false; + + if (bufferCounter > 0 || Repeating) + return true; + + foreach (var node in Nodes) + if (node.Pressed) + return true; + return false; + } + } + + public bool Released + { + get + { + if (MInput.Disabled) + return false; + + foreach (var node in Nodes) + if (node.Released) + return true; + return false; + } + } + + /// + /// Ends the Press buffer for this button + /// + public void ConsumeBuffer() + { + bufferCounter = 0; + } + + /// + /// This button will not register a Press for the rest of the current frame, but otherwise continues to function normally. If the player continues to hold the button, next frame will not count as a Press. Also ends the Press buffer for this button + /// + public void ConsumePress() + { + bufferCounter = 0; + consumed = true; + } + + public static implicit operator bool(VirtualButton button) + { + return button.Check; + } + + public abstract class Node : VirtualInputNode + { + public abstract bool Check { get; } + public abstract bool Pressed { get; } + public abstract bool Released { get; } + } + + public class KeyboardKey : Node + { + public Keys Key; + + public KeyboardKey(Keys key) + { + Key = key; + } + + public override bool Check + { + get { return MInput.Keyboard.Check(Key); } + } + + public override bool Pressed + { + get { return MInput.Keyboard.Pressed(Key); } + } + + public override bool Released + { + get { return MInput.Keyboard.Released(Key); } + } + } + + public class PadButton : Node + { + public int GamepadIndex; + public Buttons Button; + + public PadButton(int gamepadIndex, Buttons button) + { + GamepadIndex = gamepadIndex; + Button = button; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].Check(Button); } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].Pressed(Button); } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].Released(Button); } + } + } + + #region Pad Left Stick + + public class PadLeftStickRight : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadLeftStickRight(int gamepadindex, float deadzone) + { + GamepadIndex = gamepadindex; + Deadzone = deadzone; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].LeftStickRightCheck(Deadzone); } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].LeftStickRightPressed(Deadzone); } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].LeftStickRightReleased(Deadzone); } + } + } + + public class PadLeftStickLeft : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadLeftStickLeft(int gamepadindex, float deadzone) + { + GamepadIndex = gamepadindex; + Deadzone = deadzone; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].LeftStickLeftCheck(Deadzone); } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].LeftStickLeftPressed(Deadzone); } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].LeftStickLeftReleased(Deadzone); } + } + } + + public class PadLeftStickUp : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadLeftStickUp(int gamepadindex, float deadzone) + { + GamepadIndex = gamepadindex; + Deadzone = deadzone; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].LeftStickUpCheck(Deadzone); } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].LeftStickUpPressed(Deadzone); } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].LeftStickUpReleased(Deadzone); } + } + } + + public class PadLeftStickDown : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadLeftStickDown(int gamepadindex, float deadzone) + { + GamepadIndex = gamepadindex; + Deadzone = deadzone; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].LeftStickDownCheck(Deadzone); } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].LeftStickDownPressed(Deadzone); } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].LeftStickDownReleased(Deadzone); } + } + } + + #endregion + + #region Pad Right Stick + + public class PadRightStickRight : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadRightStickRight(int gamepadindex, float deadzone) + { + GamepadIndex = gamepadindex; + Deadzone = deadzone; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].RightStickRightCheck(Deadzone); } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].RightStickRightPressed(Deadzone); } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].RightStickRightReleased(Deadzone); } + } + } + + public class PadRightStickLeft : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadRightStickLeft(int gamepadindex, float deadzone) + { + GamepadIndex = gamepadindex; + Deadzone = deadzone; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].RightStickLeftCheck(Deadzone); } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].RightStickLeftPressed(Deadzone); } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].RightStickLeftReleased(Deadzone); } + } + } + + public class PadRightStickUp : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadRightStickUp(int gamepadindex, float deadzone) + { + GamepadIndex = gamepadindex; + Deadzone = deadzone; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].RightStickUpCheck(Deadzone); } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].RightStickUpPressed(Deadzone); } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].RightStickUpReleased(Deadzone); } + } + } + + public class PadRightStickDown : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadRightStickDown(int gamepadindex, float deadzone) + { + GamepadIndex = gamepadindex; + Deadzone = deadzone; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].RightStickDownCheck(Deadzone); } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].RightStickDownPressed(Deadzone); } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].RightStickDownReleased(Deadzone); } + } + } + + #endregion + + #region Pad Triggers + + public class PadLeftTrigger : Node + { + public int GamepadIndex; + public float Threshold; + + public PadLeftTrigger(int gamepadIndex, float threshold) + { + GamepadIndex = gamepadIndex; + Threshold = threshold; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].LeftTriggerCheck(Threshold); } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].LeftTriggerPressed(Threshold); } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].LeftTriggerReleased(Threshold); } + } + } + + public class PadRightTrigger : Node + { + public int GamepadIndex; + public float Threshold; + + public PadRightTrigger(int gamepadIndex, float threshold) + { + GamepadIndex = gamepadIndex; + Threshold = threshold; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].RightTriggerCheck(Threshold); } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].RightTriggerPressed(Threshold); } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].RightTriggerReleased(Threshold); } + } + } + + #endregion + + #region Pad DPad + + public class PadDPadRight : Node + { + public int GamepadIndex; + + public PadDPadRight(int gamepadIndex) + { + GamepadIndex = gamepadIndex; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].DPadRightCheck; } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].DPadRightPressed; } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].DPadRightReleased; } + } + } + + public class PadDPadLeft : Node + { + public int GamepadIndex; + + public PadDPadLeft(int gamepadIndex) + { + GamepadIndex = gamepadIndex; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].DPadLeftCheck; } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].DPadLeftPressed; } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].DPadLeftReleased; } + } + } + + public class PadDPadUp : Node + { + public int GamepadIndex; + + public PadDPadUp(int gamepadIndex) + { + GamepadIndex = gamepadIndex; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].DPadUpCheck; } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].DPadUpPressed; } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].DPadUpReleased; } + } + } + + public class PadDPadDown : Node + { + public int GamepadIndex; + + public PadDPadDown(int gamepadIndex) + { + GamepadIndex = gamepadIndex; + } + + public override bool Check + { + get { return MInput.GamePads[GamepadIndex].DPadDownCheck; } + } + + public override bool Pressed + { + get { return MInput.GamePads[GamepadIndex].DPadDownPressed; } + } + + public override bool Released + { + get { return MInput.GamePads[GamepadIndex].DPadDownReleased; } + } + } + + #endregion + + #region Mouse + + public class MouseLeftButton : Node + { + public override bool Check + { + get { return MInput.Mouse.CheckLeftButton; } + } + + public override bool Pressed + { + get { return MInput.Mouse.PressedLeftButton; } + } + + public override bool Released + { + get { return MInput.Mouse.ReleasedLeftButton; } + } + } + + public class MouseRightButton : Node + { + public override bool Check + { + get { return MInput.Mouse.CheckRightButton; } + } + + public override bool Pressed + { + get { return MInput.Mouse.PressedRightButton; } + } + + public override bool Released + { + get { return MInput.Mouse.ReleasedRightButton; } + } + } + + public class MouseMiddleButton : Node + { + public override bool Check + { + get { return MInput.Mouse.CheckMiddleButton; } + } + + public override bool Pressed + { + get { return MInput.Mouse.PressedMiddleButton; } + } + + public override bool Released + { + get { return MInput.Mouse.ReleasedMiddleButton; } + } + } + + #endregion + + #region Other Virtual Inputs + + public class VirtualAxisTrigger : Node + { + public enum Modes { LargerThan, LessThan, Equals }; + + public VirtualInput.ThresholdModes Mode; + public float Threshold; + + private VirtualAxis axis; + + public VirtualAxisTrigger(VirtualAxis axis, VirtualInput.ThresholdModes mode, float threshold) + { + this.axis = axis; + Mode = mode; + Threshold = threshold; + } + + public override bool Check + { + get + { + if (Mode == VirtualInput.ThresholdModes.LargerThan) + return axis.Value >= Threshold; + else if (Mode == VirtualInput.ThresholdModes.LessThan) + return axis.Value <= Threshold; + else + return axis.Value == Threshold; + } + } + + public override bool Pressed + { + get + { + if (Mode == VirtualInput.ThresholdModes.LargerThan) + return axis.Value >= Threshold && axis.PreviousValue < Threshold; + else if (Mode == VirtualInput.ThresholdModes.LessThan) + return axis.Value <= Threshold && axis.PreviousValue > Threshold; + else + return axis.Value == Threshold && axis.PreviousValue != Threshold; + } + } + + public override bool Released + { + get + { + if (Mode == VirtualInput.ThresholdModes.LargerThan) + return axis.Value < Threshold && axis.PreviousValue >= Threshold; + else if (Mode == VirtualInput.ThresholdModes.LessThan) + return axis.Value > Threshold && axis.PreviousValue <= Threshold; + else + return axis.Value != Threshold && axis.PreviousValue == Threshold; + } + } + } + + public class VirtualIntegerAxisTrigger : Node + { + public enum Modes { LargerThan, LessThan, Equals }; + + public VirtualInput.ThresholdModes Mode; + public int Threshold; + + private VirtualIntegerAxis axis; + + public VirtualIntegerAxisTrigger(VirtualIntegerAxis axis, VirtualInput.ThresholdModes mode, int threshold) + { + this.axis = axis; + Mode = mode; + Threshold = threshold; + } + + public override bool Check + { + get + { + if (Mode == VirtualInput.ThresholdModes.LargerThan) + return axis.Value >= Threshold; + else if (Mode == VirtualInput.ThresholdModes.LessThan) + return axis.Value <= Threshold; + else + return axis.Value == Threshold; + } + } + + public override bool Pressed + { + get + { + if (Mode == VirtualInput.ThresholdModes.LargerThan) + return axis.Value >= Threshold && axis.PreviousValue < Threshold; + else if (Mode == VirtualInput.ThresholdModes.LessThan) + return axis.Value <= Threshold && axis.PreviousValue > Threshold; + else + return axis.Value == Threshold && axis.PreviousValue != Threshold; + } + } + + public override bool Released + { + get + { + if (Mode == VirtualInput.ThresholdModes.LargerThan) + return axis.Value < Threshold && axis.PreviousValue >= Threshold; + else if (Mode == VirtualInput.ThresholdModes.LessThan) + return axis.Value > Threshold && axis.PreviousValue <= Threshold; + else + return axis.Value != Threshold && axis.PreviousValue == Threshold; + } + } + } + + public class VirtualJoystickXTrigger : Node + { + public enum Modes { LargerThan, LessThan, Equals }; + + public VirtualInput.ThresholdModes Mode; + public float Threshold; + + private VirtualJoystick joystick; + + public VirtualJoystickXTrigger(VirtualJoystick joystick, VirtualInput.ThresholdModes mode, float threshold) + { + this.joystick = joystick; + Mode = mode; + Threshold = threshold; + } + + public override bool Check + { + get + { + if (Mode == VirtualInput.ThresholdModes.LargerThan) + return joystick.Value.X >= Threshold; + else if (Mode == VirtualInput.ThresholdModes.LessThan) + return joystick.Value.X <= Threshold; + else + return joystick.Value.X == Threshold; + } + } + + public override bool Pressed + { + get + { + if (Mode == VirtualInput.ThresholdModes.LargerThan) + return joystick.Value.X >= Threshold && joystick.PreviousValue.X < Threshold; + else if (Mode == VirtualInput.ThresholdModes.LessThan) + return joystick.Value.X <= Threshold && joystick.PreviousValue.X > Threshold; + else + return joystick.Value.X == Threshold && joystick.PreviousValue.X != Threshold; + } + } + + public override bool Released + { + get + { + if (Mode == VirtualInput.ThresholdModes.LargerThan) + return joystick.Value.X < Threshold && joystick.PreviousValue.X >= Threshold; + else if (Mode == VirtualInput.ThresholdModes.LessThan) + return joystick.Value.X > Threshold && joystick.PreviousValue.X <= Threshold; + else + return joystick.Value.X != Threshold && joystick.PreviousValue.X == Threshold; + } + } + } + + public class VirtualJoystickYTrigger : Node + { + public VirtualInput.ThresholdModes Mode; + public float Threshold; + + private VirtualJoystick joystick; + + public VirtualJoystickYTrigger(VirtualJoystick joystick, VirtualInput.ThresholdModes mode, float threshold) + { + this.joystick = joystick; + Mode = mode; + Threshold = threshold; + } + + public override bool Check + { + get + { + if (Mode == VirtualInput.ThresholdModes.LargerThan) + return joystick.Value.X >= Threshold; + else if (Mode == VirtualInput.ThresholdModes.LessThan) + return joystick.Value.X <= Threshold; + else + return joystick.Value.X == Threshold; + } + } + + public override bool Pressed + { + get + { + if (Mode == VirtualInput.ThresholdModes.LargerThan) + return joystick.Value.X >= Threshold && joystick.PreviousValue.X < Threshold; + else if (Mode == VirtualInput.ThresholdModes.LessThan) + return joystick.Value.X <= Threshold && joystick.PreviousValue.X > Threshold; + else + return joystick.Value.X == Threshold && joystick.PreviousValue.X != Threshold; + } + } + + public override bool Released + { + get + { + if (Mode == VirtualInput.ThresholdModes.LargerThan) + return joystick.Value.X < Threshold && joystick.PreviousValue.X >= Threshold; + else if (Mode == VirtualInput.ThresholdModes.LessThan) + return joystick.Value.X > Threshold && joystick.PreviousValue.X <= Threshold; + else + return joystick.Value.X != Threshold && joystick.PreviousValue.X == Threshold; + } + } + } + + #endregion + } +} diff --git a/MonocleEngineDemo/Monocle/Input/VirtualInput.cs b/MonocleEngineDemo/Monocle/Input/VirtualInput.cs new file mode 100644 index 0000000..99d49a3 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Input/VirtualInput.cs @@ -0,0 +1,36 @@ + +namespace Monocle +{ + /// + /// Represents a virtual button, axis or joystick whose state is determined by the state of its VirtualInputNodes + /// + public abstract class VirtualInput + { + public enum OverlapBehaviors { CancelOut, TakeOlder, TakeNewer }; + public enum ThresholdModes { LargerThan, LessThan, EqualTo }; + + public VirtualInput() + { + MInput.VirtualInputs.Add(this); + } + + public void Deregister() + { + MInput.VirtualInputs.Remove(this); + } + + public abstract void Update(); + } + + /// + /// Add these to your VirtualInput to define how it determines its current input state. + /// For example, if you want to check whether a keyboard key is pressed, create a VirtualButton and add to it a VirtualButton.KeyboardKey + /// + public abstract class VirtualInputNode + { + public virtual void Update() + { + + } + } +} diff --git a/MonocleEngineDemo/Monocle/Input/VirtualIntegerAxis.cs b/MonocleEngineDemo/Monocle/Input/VirtualIntegerAxis.cs new file mode 100644 index 0000000..50103b3 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Input/VirtualIntegerAxis.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; + +namespace Monocle +{ + /// + /// A virtual input that is represented as a int that is either -1, 0, or 1 + /// + public class VirtualIntegerAxis : VirtualInput + { + public List Nodes; + + public int Value; + public int PreviousValue { get; private set; } + + public VirtualIntegerAxis() + : base() + { + Nodes = new List(); + } + + public VirtualIntegerAxis(params VirtualAxis.Node[] nodes) + : base() + { + Nodes = new List(nodes); + } + + public override void Update() + { + foreach (var node in Nodes) + node.Update(); + + PreviousValue = Value; + Value = 0; + foreach (var node in Nodes) + { + float value = node.Value; + if (value != 0) + { + Value = Math.Sign(value); + break; + } + } + } + + public static implicit operator int(VirtualIntegerAxis axis) + { + return axis.Value; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Input/VirtualJoystick.cs b/MonocleEngineDemo/Monocle/Input/VirtualJoystick.cs new file mode 100644 index 0000000..ee23201 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Input/VirtualJoystick.cs @@ -0,0 +1,257 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using System.Collections.Generic; + +namespace Monocle +{ + /// + /// A virtual input that is represented as a Vector2, with both X and Y as values between -1 and 1 + /// + public class VirtualJoystick : VirtualInput + { + public List Nodes; + public bool Normalized; + public float? SnapSlices; + + public Vector2 Value { get; private set; } + public Vector2 PreviousValue { get; private set; } + + public VirtualJoystick(bool normalized) + : base() + { + Nodes = new List(); + Normalized = normalized; + } + + public VirtualJoystick(bool normalized, params Node[] nodes) + : base() + { + Nodes = new List(nodes); + Normalized = normalized; + } + + public override void Update() + { + foreach (var node in Nodes) + node.Update(); + + PreviousValue = Value; + Value = Vector2.Zero; + foreach (var node in Nodes) + { + Vector2 value = node.Value; + if (value != Vector2.Zero) + { + if (Normalized) + { + if (SnapSlices.HasValue) + value = value.SnappedNormal(SnapSlices.Value); + else + value.Normalize(); + } + else if (SnapSlices.HasValue) + value = value.Snapped(SnapSlices.Value); + + Value = value; + break; + } + } + } + + public static implicit operator Vector2(VirtualJoystick joystick) + { + return joystick.Value; + } + + public abstract class Node : VirtualInputNode + { + public abstract Vector2 Value { get; } + } + + public class PadLeftStick : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadLeftStick(int gamepadIndex, float deadzone) + { + GamepadIndex = gamepadIndex; + Deadzone = deadzone; + } + + public override Vector2 Value + { + get + { + return MInput.GamePads[GamepadIndex].GetLeftStick(Deadzone); + } + } + } + + public class PadRightStick : Node + { + public int GamepadIndex; + public float Deadzone; + + public PadRightStick(int gamepadIndex, float deadzone) + { + GamepadIndex = gamepadIndex; + Deadzone = deadzone; + } + + public override Vector2 Value + { + get + { + return MInput.GamePads[GamepadIndex].GetRightStick(Deadzone); + } + } + } + + public class PadDpad : Node + { + public int GamepadIndex; + + public PadDpad(int gamepadIndex) + { + GamepadIndex = gamepadIndex; + } + + public override Vector2 Value + { + get + { + Vector2 value = Vector2.Zero; + + if (MInput.GamePads[GamepadIndex].DPadRightCheck) + value.X = 1f; + else if (MInput.GamePads[GamepadIndex].DPadLeftCheck) + value.X = -1f; + + if (MInput.GamePads[GamepadIndex].DPadDownCheck) + value.Y = 1f; + else if (MInput.GamePads[GamepadIndex].DPadUpCheck) + value.Y = -1f; + + return value; + } + } + } + + public class KeyboardKeys : Node + { + public OverlapBehaviors OverlapBehavior; + public Keys Left; + public Keys Right; + public Keys Up; + public Keys Down; + + private bool turnedX; + private bool turnedY; + private Vector2 value; + + public KeyboardKeys(OverlapBehaviors overlapBehavior, Keys left, Keys right, Keys up, Keys down) + { + OverlapBehavior = overlapBehavior; + Left = left; + Right = right; + Up = up; + Down = down; + } + + public override void Update() + { + //X Axis + if (MInput.Keyboard.Check(Left)) + { + if (MInput.Keyboard.Check(Right)) + { + switch (OverlapBehavior) + { + default: + case OverlapBehaviors.CancelOut: + value.X = 0; + break; + + case OverlapBehaviors.TakeNewer: + if (!turnedX) + { + value.X *= -1; + turnedX = true; + } + break; + + case OverlapBehaviors.TakeOlder: + //X stays the same + break; + } + } + else + { + turnedX = false; + value.X = -1; + } + } + else if (MInput.Keyboard.Check(Right)) + { + turnedX = false; + value.X = 1; + } + else + { + turnedX = false; + value.X = 0; + } + + //Y Axis + if (MInput.Keyboard.Check(Up)) + { + if (MInput.Keyboard.Check(Down)) + { + switch (OverlapBehavior) + { + default: + case OverlapBehaviors.CancelOut: + value.Y = 0; + break; + + case OverlapBehaviors.TakeNewer: + if (!turnedY) + { + value.Y *= -1; + turnedY = true; + } + break; + + case OverlapBehaviors.TakeOlder: + //Y stays the same + break; + } + } + else + { + turnedY = false; + value.Y = -1; + } + } + else if (MInput.Keyboard.Check(Down)) + { + turnedY = false; + value.Y = 1; + } + else + { + turnedY = false; + value.Y = 0; + } + } + + public override Vector2 Value + { + get { return value; } + } + } + } + + +} diff --git a/MonocleEngineDemo/Monocle/InternalUtilities/ComponentList.cs b/MonocleEngineDemo/Monocle/InternalUtilities/ComponentList.cs new file mode 100644 index 0000000..4a6a0e3 --- /dev/null +++ b/MonocleEngineDemo/Monocle/InternalUtilities/ComponentList.cs @@ -0,0 +1,252 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Monocle +{ + public class ComponentList : IEnumerable, IEnumerable + { + public enum LockModes { Open, Locked, Error }; + public Entity Entity { get; internal set; } + + private List components; + private List toAdd; + private List toRemove; + + private HashSet current; + private HashSet adding; + private HashSet removing; + + private LockModes lockMode; + + internal ComponentList(Entity entity) + { + Entity = entity; + + components = new List(); + toAdd = new List(); + toRemove = new List(); + current = new HashSet(); + adding = new HashSet(); + removing = new HashSet(); + } + + internal LockModes LockMode + { + get + { + return lockMode; + } + + set + { + lockMode = value; + + if (toAdd.Count > 0) + { + foreach (var component in toAdd) + { + if (!current.Contains(component)) + { + current.Add(component); + components.Add(component); + component.Added(Entity); + } + } + + adding.Clear(); + toAdd.Clear(); + } + + if (toRemove.Count > 0) + { + foreach (var component in toRemove) + { + if (current.Contains(component)) + { + current.Remove(component); + components.Remove(component); + component.Removed(Entity); + } + } + + removing.Clear(); + toRemove.Clear(); + } + } + } + + public void Add(Component component) + { + switch (lockMode) + { + case LockModes.Open: + if (!current.Contains(component)) + { + current.Add(component); + components.Add(component); + component.Added(Entity); + } + break; + + case LockModes.Locked: + if (!current.Contains(component) && !adding.Contains(component)) + { + adding.Add(component); + toAdd.Add(component); + } + break; + + case LockModes.Error: + throw new Exception("Cannot add or remove Entities at this time!"); + } + } + + public void Remove(Component component) + { + switch (lockMode) + { + case LockModes.Open: + if (current.Contains(component)) + { + current.Remove(component); + components.Remove(component); + component.Removed(Entity); + } + break; + + case LockModes.Locked: + if (current.Contains(component) && !removing.Contains(component)) + { + removing.Add(component); + toRemove.Add(component); + } + break; + + case LockModes.Error: + throw new Exception("Cannot add or remove Entities at this time!"); + } + } + + public void Add(IEnumerable components) + { + foreach (var component in components) + Add(component); + } + + public void Remove(IEnumerable components) + { + foreach (var component in components) + Remove(component); + } + + public void RemoveAll() where T : Component + { + Remove(GetAll()); + } + + public void Add(params Component[] components) + { + foreach (var component in components) + Add(component); + } + + public void Remove(params Component[] components) + { + foreach (var component in components) + Remove(component); + } + + public int Count + { + get + { + return components.Count; + } + } + + public Component this[int index] + { + get + { + if (index < 0 || index >= components.Count) + throw new IndexOutOfRangeException(); + else + return components[index]; + } + } + + public IEnumerator GetEnumerator() + { + return components.GetEnumerator(); + } + + IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public Component[] ToArray() + { + return components.ToArray(); + } + + internal void Update() + { + LockMode = ComponentList.LockModes.Locked; + foreach (var component in components) + if (component.Active) + component.Update(); + LockMode = ComponentList.LockModes.Open; + } + + internal void Render() + { + LockMode = ComponentList.LockModes.Error; + foreach (var component in components) + if (component.Visible) + component.Render(); + LockMode = ComponentList.LockModes.Open; + } + + internal void DebugRender(Camera camera) + { + LockMode = ComponentList.LockModes.Error; + foreach (var component in components) + component.DebugRender(camera); + LockMode = ComponentList.LockModes.Open; + } + + internal void HandleGraphicsReset() + { + LockMode = ComponentList.LockModes.Error; + foreach (var component in components) + component.HandleGraphicsReset(); + LockMode = ComponentList.LockModes.Open; + } + + internal void HandleGraphicsCreate() + { + LockMode = ComponentList.LockModes.Error; + foreach (var component in components) + component.HandleGraphicsCreate(); + LockMode = ComponentList.LockModes.Open; + } + + public T Get() where T : Component + { + foreach (var component in components) + if (component is T) + return component as T; + return null; + } + + public IEnumerable GetAll() where T : Component + { + foreach (var component in components) + if (component is T) + yield return component as T; + } + } +} diff --git a/MonocleEngineDemo/Monocle/InternalUtilities/EntityList.cs b/MonocleEngineDemo/Monocle/InternalUtilities/EntityList.cs new file mode 100644 index 0000000..d00b1ee --- /dev/null +++ b/MonocleEngineDemo/Monocle/InternalUtilities/EntityList.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Monocle +{ + public class EntityList : IEnumerable, IEnumerable + { + public Scene Scene { get; private set; } + + private List entities; + private List toAdd; + private List toAwake; + private List toRemove; + + private HashSet current; + private HashSet adding; + private HashSet removing; + + private bool unsorted; + + internal EntityList(Scene scene) + { + Scene = scene; + + entities = new List(); + toAdd = new List(); + toAwake = new List(); + toRemove = new List(); + + current = new HashSet(); + adding = new HashSet(); + removing = new HashSet(); + } + + internal void MarkUnsorted() + { + unsorted = true; + } + + public void UpdateLists() + { + if (toAdd.Count > 0) + { + for (int i = 0; i < toAdd.Count; i++) + { + var entity = toAdd[i]; + if (!current.Contains(entity)) + { + current.Add(entity); + entities.Add(entity); + + if (Scene != null) + { + Scene.TagLists.EntityAdded(entity); + Scene.Tracker.EntityAdded(entity); + entity.Added(Scene); + } + } + } + + unsorted = true; + } + + if (toRemove.Count > 0) + { + for (int i = 0; i < toRemove.Count; i++) + { + var entity = toRemove[i]; + if (entities.Contains(entity)) + { + current.Remove(entity); + entities.Remove(entity); + + if (Scene != null) + { + entity.Removed(Scene); + Scene.TagLists.EntityRemoved(entity); + Scene.Tracker.EntityRemoved(entity); + Engine.Pooler.EntityRemoved(entity); + } + } + } + + toRemove.Clear(); + removing.Clear(); + } + + if (unsorted) + { + unsorted = false; + entities.Sort(CompareDepth); + } + + if (toAdd.Count > 0) + { + toAwake.AddRange(toAdd); + toAdd.Clear(); + adding.Clear(); + + foreach (var entity in toAwake) + if (entity.Scene == Scene) + entity.Awake(Scene); + toAwake.Clear(); + } + } + + public void Add(Entity entity) + { + if (!adding.Contains(entity) && !current.Contains(entity)) + { + adding.Add(entity); + toAdd.Add(entity); + } + } + + public void Remove(Entity entity) + { + if (!removing.Contains(entity) && current.Contains(entity)) + { + removing.Add(entity); + toRemove.Add(entity); + } + } + + public void Add(IEnumerable entities) + { + foreach (var entity in entities) + Add(entity); + } + + public void Remove(IEnumerable entities) + { + foreach (var entity in entities) + Remove(entity); + } + + public void Add(params Entity[] entities) + { + for (int i = 0; i < entities.Length; i++) + Add(entities[i]); + } + + public void Remove(params Entity[] entities) + { + for (int i = 0; i < entities.Length; i++) + Remove(entities[i]); + } + + public int Count + { + get + { + return entities.Count; + } + } + + public Entity this[int index] + { + get + { + if (index < 0 || index >= entities.Count) + throw new IndexOutOfRangeException(); + else + return entities[index]; + } + } + + public int AmountOf() where T : Entity + { + int count = 0; + foreach (var e in entities) + if (e is T) + count++; + + return count; + } + + public T FindFirst() where T : Entity + { + foreach (var e in entities) + if (e is T) + return e as T; + + return null; + } + + public List FindAll() where T : Entity + { + List list = new List(); + + foreach (var e in entities) + if (e is T) + list.Add(e as T); + + return list; + } + + public void With(Action action) where T : Entity + { + foreach (var e in entities) + if (e is T) + action(e as T); + } + + public IEnumerator GetEnumerator() + { + return entities.GetEnumerator(); + } + + IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public Entity[] ToArray() + { + return entities.ToArray(); + } + + public bool HasVisibleEntities(int matchTags) + { + foreach (var entity in entities) + if (entity.Visible && entity.TagCheck(matchTags)) + return true; + return false; + } + + internal void Update() + { + foreach (var entity in entities) + if (entity.Active) + entity.Update(); + } + + public void Render() + { + foreach (var entity in entities) + if (entity.Visible) + entity.Render(); + } + + public void RenderOnly(int matchTags) + { + foreach (var entity in entities) + if (entity.Visible && entity.TagCheck(matchTags)) + entity.Render(); + } + + public void RenderOnlyFullMatch(int matchTags) + { + foreach (var entity in entities) + if (entity.Visible && entity.TagFullCheck(matchTags)) + entity.Render(); + } + + public void RenderExcept(int excludeTags) + { + foreach (var entity in entities) + if (entity.Visible && !entity.TagCheck(excludeTags)) + entity.Render(); + } + + public void DebugRender(Camera camera) + { + foreach (var entity in entities) + entity.DebugRender(camera); + } + + internal void HandleGraphicsReset() + { + foreach (var entity in entities) + entity.HandleGraphicsReset(); + } + + internal void HandleGraphicsCreate() + { + foreach (var entity in entities) + entity.HandleGraphicsCreate(); + } + + public static Comparison CompareDepth = (a, b) => { return Math.Sign(b.actualDepth - a.actualDepth); }; + } +} diff --git a/MonocleEngineDemo/Monocle/InternalUtilities/RendererList.cs b/MonocleEngineDemo/Monocle/InternalUtilities/RendererList.cs new file mode 100644 index 0000000..f4353b4 --- /dev/null +++ b/MonocleEngineDemo/Monocle/InternalUtilities/RendererList.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Monocle +{ + public class RendererList + { + public List Renderers; + private List adding; + private List removing; + private Scene scene; + + internal RendererList(Scene scene) + { + this.scene = scene; + + Renderers = new List(); + adding = new List(); + removing = new List(); + } + + internal void UpdateLists() + { + if (adding.Count > 0) + foreach (var renderer in adding) + Renderers.Add(renderer); + adding.Clear(); + if (removing.Count > 0) + foreach (var renderer in removing) + Renderers.Remove(renderer); + removing.Clear(); + } + + internal void Update() + { + foreach (var renderer in Renderers) + renderer.Update(scene); + } + + internal void BeforeRender() + { + for (int i = 0; i < Renderers.Count; i++) + { + if (!Renderers[i].Visible) + continue; + Draw.Renderer = Renderers[i]; + Renderers[i].BeforeRender(scene); + } + } + + internal void Render() + { + for (int i = 0; i < Renderers.Count; i++) + { + if (!Renderers[i].Visible) + continue; + Draw.Renderer = Renderers[i]; + Renderers[i].Render(scene); + } + } + + internal void AfterRender() + { + for (int i = 0; i < Renderers.Count; i++) + { + if (!Renderers[i].Visible) + continue; + Draw.Renderer = Renderers[i]; + Renderers[i].AfterRender(scene); + } + } + + public void MoveToFront(Renderer renderer) + { + Renderers.Remove(renderer); + Renderers.Add(renderer); + } + + public void Add(Renderer renderer) + { + adding.Add(renderer); + } + + public void Remove(Renderer renderer) + { + removing.Add(renderer); + } + + } +} diff --git a/MonocleEngineDemo/Monocle/InternalUtilities/TagLists.cs b/MonocleEngineDemo/Monocle/InternalUtilities/TagLists.cs new file mode 100644 index 0000000..53cd233 --- /dev/null +++ b/MonocleEngineDemo/Monocle/InternalUtilities/TagLists.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Monocle +{ + public class TagLists + { + private List[] lists; + private bool[] unsorted; + private bool areAnyUnsorted; + + internal TagLists() + { + lists = new List[BitTag.TotalTags]; + unsorted = new bool[BitTag.TotalTags]; + for (int i = 0; i < lists.Length; i++) + lists[i] = new List(); + } + + public List this[int index] + { + get + { + return lists[index]; + } + } + + internal void MarkUnsorted(int index) + { + areAnyUnsorted = true; + unsorted[index] = true; + } + + internal void UpdateLists() + { + if (areAnyUnsorted) + { + for (int i = 0; i < lists.Length; i++) + { + if (unsorted[i]) + { + lists[i].Sort(EntityList.CompareDepth); + unsorted[i] = false; + } + } + + areAnyUnsorted = false; + } + } + + internal void EntityAdded(Entity entity) + { + for (int i = 0; i < BitTag.TotalTags; i++) + { + if (entity.TagCheck(1 << i)) + { + this[i].Add(entity); + areAnyUnsorted = true; + unsorted[i] = true; + } + } + } + + internal void EntityRemoved(Entity entity) + { + for (int i = 0; i < BitTag.TotalTags; i++) + if (entity.TagCheck(1 << i)) + lists[i].Remove(entity); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Monocle.MonoGame.csproj b/MonocleEngineDemo/Monocle/Monocle.MonoGame.csproj new file mode 100644 index 0000000..593841b --- /dev/null +++ b/MonocleEngineDemo/Monocle/Monocle.MonoGame.csproj @@ -0,0 +1,132 @@ + + + + + Debug + AnyCPU + {6EC3C0A6-C5BE-42CE-AEA5-881D8A97EB60} + Library + Properties + Monocle + Monocle + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + \ No newline at end of file diff --git a/MonocleEngineDemo/Monocle/Monocle.csproj b/MonocleEngineDemo/Monocle/Monocle.csproj new file mode 100644 index 0000000..6653446 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Monocle.csproj @@ -0,0 +1,185 @@ + + + + {25D1847C-BCB0-4D6F-BBA5-CD1DC9B8D295} + {6D335F3A-9D43-41b4-9D22-F6F17C4BE596};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Debug + x86 + Library + Properties + Monocle + Monocle + v4.5.2 + + v4.0 + Windows + HiDef + 4e152ded-4ea8-416f-aabf-dd49c36c042c + Library + + + true + full + false + bin\x86\Debug + DEBUG;TRACE;WINDOWS + prompt + 4 + true + false + x86 + false + + + pdbonly + true + bin\x86\Release + TRACE;WINDOWS + prompt + 4 + true + false + x86 + true + + + true + bin\x86\SteamDebug\ + DEBUG;TRACE;WINDOWS + true + full + x86 + false + prompt + MinimumRecommendedRules.ruleset + + + bin\x86\SteamRelease\ + TRACE;WINDOWS + true + true + pdbonly + x86 + false + prompt + MinimumRecommendedRules.ruleset + + + + + + + False + + + False + + + False + + + 4.0 + False + + + 4.0 + False + + + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + + \ No newline at end of file diff --git a/MonocleEngineDemo/Monocle/Particles/Particle.cs b/MonocleEngineDemo/Monocle/Particles/Particle.cs new file mode 100644 index 0000000..f3a8511 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Particles/Particle.cs @@ -0,0 +1,139 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace Monocle +{ + public struct Particle + { + public Entity Track; + public ParticleType Type; + public MTexture Source; + + public bool Active; + public Color Color; + public Color StartColor; + public Vector2 Position; + public Vector2 Speed; + public float Size; + public float StartSize; + public float Life; + public float StartLife; + public float ColorSwitch; + public float Rotation; + public float Spin; + + public bool SimulateFor(float duration) + { + if (duration > Life) + { + Life = 0; + Active = false; + return false; + } + else + { + var dt = Engine.TimeRate * (Engine.Instance.TargetElapsedTime.Milliseconds / 1000f); + if (dt > 0) + for (var t = 0f; t < duration; t += dt) + Update(dt); + + return true; + } + } + + public void Update(float? delta = null) + { + var dt = 0f; + if (delta.HasValue) + dt = delta.Value; + else + dt = (Type.UseActualDeltaTime ? Engine.RawDeltaTime : Engine.DeltaTime); + + var ease = Life / StartLife; + + //Life + Life -= dt; + if (Life <= 0) + { + Active = false; + return; + } + + //Spin + if (Type.RotationMode == ParticleType.RotationModes.SameAsDirection) + { + if (Speed != Vector2.Zero) + Rotation = Speed.Angle(); + } + else + Rotation += Spin * dt; + + //Fade + float alpha; + if (Type.FadeMode == ParticleType.FadeModes.Linear) + alpha = ease; + else if (Type.FadeMode == ParticleType.FadeModes.Late) + alpha = Math.Min(1f, ease / .25f); + else if (Type.FadeMode == ParticleType.FadeModes.InAndOut) + { + if (ease > .75f) + alpha = 1 - ((ease - .75f) / .25f); + else if (ease < .25f) + alpha = ease / .25f; + else + alpha = 1f; + } + else + alpha = 1f; + + + //Color switch with alpha + if (alpha == 0) + Color = Color.Transparent; + else + { + if (Type.ColorMode == ParticleType.ColorModes.Static) + Color = StartColor; + else if (Type.ColorMode == ParticleType.ColorModes.Fade) + Color = Color.Lerp(Type.Color2, StartColor, ease); + else if (Type.ColorMode == ParticleType.ColorModes.Blink) + Color = (Calc.BetweenInterval(Life, .1f) ? StartColor : Type.Color2); + else if (Type.ColorMode == ParticleType.ColorModes.Choose) + Color = StartColor; + + if (alpha < 1f) + Color *= alpha; + } + + //Speed + Position += Speed * dt; + Speed += Type.Acceleration * dt; + Speed = Calc.Approach(Speed, Vector2.Zero, Type.Friction * dt); + if (Type.SpeedMultiplier != 1) + Speed *= (float)Math.Pow(Type.SpeedMultiplier, dt); + + //Scale Out + if (Type.ScaleOut) + Size = StartSize * Ease.CubeOut(ease); + } + + public void Render() + { + var renderAt = new Vector2((int)Position.X, (int)Position.Y); + if (Track != null) + renderAt += Track.Position; + + Draw.SpriteBatch.Draw(Source.Texture, renderAt, Source.ClipRect, Color, Rotation, Source.Center, Size, SpriteEffects.None, 0); + } + + public void Render(float alpha) + { + var renderAt = new Vector2((int)Position.X, (int)Position.Y); + if (Track != null) + renderAt += Track.Position; + + Draw.SpriteBatch.Draw(Source.Texture, renderAt, Source.ClipRect, Color * alpha, Rotation, Source.Center, Size, SpriteEffects.None, 0); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Particles/ParticleSystem.cs b/MonocleEngineDemo/Monocle/Particles/ParticleSystem.cs new file mode 100644 index 0000000..8c9a7de --- /dev/null +++ b/MonocleEngineDemo/Monocle/Particles/ParticleSystem.cs @@ -0,0 +1,134 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Monocle +{ + public class ParticleSystem : Entity + { + private Particle[] particles; + private int nextSlot; + + public ParticleSystem(int depth, int maxParticles) + : base() + { + particles = new Particle[maxParticles]; + Depth = depth; + } + + public void Clear() + { + for (int i = 0; i < particles.Length; i++) + particles[i].Active = false; + } + + public void ClearRect(Rectangle rect, bool inside) + { + for (int i = 0; i < particles.Length; i ++) + { + var pos = particles[i].Position; + var isInside = (pos.X > rect.Left && pos.Y > rect.Top && pos.X < rect.Right && pos.Y < rect.Bottom); + + if (isInside == inside) + particles[i].Active = false; + } + } + + public override void Update() + { + for (int i = 0; i < particles.Length; i++) + if (particles[i].Active) + particles[i].Update(); + } + + public override void Render() + { + foreach (var p in particles) + if (p.Active) + p.Render(); + } + + public void Render(float alpha) + { + foreach (var p in particles) + if (p.Active) + p.Render(alpha); + } + + public void Simulate(float duration, float interval, Action emitter) + { + var delta = 0.016f; + for (float time = 0f; time < duration; time += delta) + { + if ((int)((time - delta) / interval) < (int)(time / interval)) + emitter(this); + + for (int i = 0; i < particles.Length; i++) + if (particles[i].Active) + particles[i].Update(delta); + } + } + + public void Add(Particle particle) + { + particles[nextSlot] = particle; + nextSlot = (nextSlot + 1) % particles.Length; + } + + public void Emit(ParticleType type, Vector2 position) + { + type.Create(ref particles[nextSlot], position); + nextSlot = (nextSlot + 1) % particles.Length; + } + + public void Emit(ParticleType type, Vector2 position, float direction) + { + type.Create(ref particles[nextSlot], position, direction); + nextSlot = (nextSlot + 1) % particles.Length; + } + + public void Emit(ParticleType type, Vector2 position, Color color) + { + type.Create(ref particles[nextSlot], position, color); + nextSlot = (nextSlot + 1) % particles.Length; + } + + public void Emit(ParticleType type, Vector2 position, Color color, float direction) + { + type.Create(ref particles[nextSlot], position, color, direction); + nextSlot = (nextSlot + 1) % particles.Length; + } + + public void Emit(ParticleType type, int amount, Vector2 position, Vector2 positionRange) + { + for (int i = 0; i < amount; i++) + Emit(type, Calc.Random.Range(position - positionRange, position + positionRange)); + } + + public void Emit(ParticleType type, int amount, Vector2 position, Vector2 positionRange, float direction) + { + for (int i = 0; i < amount; i++) + Emit(type, Calc.Random.Range(position - positionRange, position + positionRange), direction); + } + + public void Emit(ParticleType type, int amount, Vector2 position, Vector2 positionRange, Color color) + { + for (int i = 0; i < amount; i++) + Emit(type, Calc.Random.Range(position - positionRange, position + positionRange), color); + } + + public void Emit(ParticleType type, int amount, Vector2 position, Vector2 positionRange, Color color, float direction) + { + for (int i = 0; i < amount; i++) + Emit(type, Calc.Random.Range(position - positionRange, position + positionRange), color, direction); + } + + public void Emit(ParticleType type, Entity track, int amount, Vector2 position, Vector2 positionRange, float direction) + { + for (int i = 0; i < amount; i++) + { + type.Create(ref particles[nextSlot], track, Calc.Random.Range(position - positionRange, position + positionRange), direction, type.Color); + nextSlot = (nextSlot + 1) % particles.Length; + } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Particles/ParticleType.cs b/MonocleEngineDemo/Monocle/Particles/ParticleType.cs new file mode 100644 index 0000000..5c19ba5 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Particles/ParticleType.cs @@ -0,0 +1,159 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; + +namespace Monocle +{ + public class ParticleType + { + public enum ColorModes { Static, Choose, Blink, Fade }; + public enum FadeModes { None, Linear, Late, InAndOut }; + public enum RotationModes { None, Random, SameAsDirection }; + + private static List AllTypes = new List(); + + public MTexture Source; + public Chooser SourceChooser; + public Color Color; + public Color Color2; + public ColorModes ColorMode; + public FadeModes FadeMode; + public float SpeedMin; + public float SpeedMax; + public float SpeedMultiplier; + public Vector2 Acceleration; + public float Friction; + public float Direction; + public float DirectionRange; + public float LifeMin; + public float LifeMax; + public float Size; + public float SizeRange; + public float SpinMin; + public float SpinMax; + public bool SpinFlippedChance; + public RotationModes RotationMode; + public bool ScaleOut; + public bool UseActualDeltaTime; + + public ParticleType() + { + Color = Color2 = Color.White; + ColorMode = ColorModes.Static; + FadeMode = FadeModes.None; + SpeedMin = SpeedMax = 0; + SpeedMultiplier = 1; + Acceleration = Vector2.Zero; + Friction = 0f; + Direction = DirectionRange = 0; + LifeMin = LifeMax = 0; + Size = 2; + SizeRange = 0; + SpinMin = SpinMax = 0; + SpinFlippedChance = false; + RotationMode = RotationModes.None; + + AllTypes.Add(this); + } + + public ParticleType(ParticleType copyFrom) + { + Source = copyFrom.Source; + SourceChooser = copyFrom.SourceChooser; + Color = copyFrom.Color; + Color2 = copyFrom.Color2; + ColorMode = copyFrom.ColorMode; + FadeMode = copyFrom.FadeMode; + SpeedMin = copyFrom.SpeedMin; + SpeedMax = copyFrom.SpeedMax; + SpeedMultiplier = copyFrom.SpeedMultiplier; + Acceleration = copyFrom.Acceleration; + Friction = copyFrom.Friction; + Direction = copyFrom.Direction; + DirectionRange = copyFrom.DirectionRange; + LifeMin = copyFrom.LifeMin; + LifeMax = copyFrom.LifeMax; + Size = copyFrom.Size; + SizeRange = copyFrom.SizeRange; + RotationMode = copyFrom.RotationMode; + SpinMin = copyFrom.SpinMin; + SpinMax = copyFrom.SpinMax; + SpinFlippedChance = copyFrom.SpinFlippedChance; + ScaleOut = copyFrom.ScaleOut; + UseActualDeltaTime = copyFrom.UseActualDeltaTime; + + AllTypes.Add(this); + } + + public Particle Create(ref Particle particle, Vector2 position) + { + return Create(ref particle, position, Direction); + } + + public Particle Create(ref Particle particle, Vector2 position, Color color) + { + return Create(ref particle, null, position, Direction, color); + } + + public Particle Create(ref Particle particle, Vector2 position, float direction) + { + return Create(ref particle, null, position, direction, Color); + } + + public Particle Create(ref Particle particle, Vector2 position, Color color, float direction) + { + return Create(ref particle, null, position, direction, color); + } + + public Particle Create(ref Particle particle, Entity entity, Vector2 position, float direction, Color color) + { + particle.Track = entity; + particle.Type = this; + particle.Active = true; + particle.Position = position; + + // source texture + if (SourceChooser != null) + particle.Source = SourceChooser.Choose(); + else if (Source != null) + particle.Source = Source; + else + particle.Source = Draw.Particle; + + // size + if (SizeRange != 0) + particle.StartSize = particle.Size = Size - SizeRange * .5f + Calc.Random.NextFloat(SizeRange); + else + particle.StartSize = particle.Size = Size; + + // color + if (ColorMode == ColorModes.Choose) + particle.StartColor = particle.Color = Calc.Random.Choose(color, Color2); + else + particle.StartColor = particle.Color = color; + + // speed / direction + var moveDirection = direction - DirectionRange / 2 + Calc.Random.NextFloat() * DirectionRange; + particle.Speed = Calc.AngleToVector(moveDirection, Calc.Random.Range(SpeedMin, SpeedMax)); + + // life + particle.StartLife = particle.Life = Calc.Random.Range(LifeMin, LifeMax); + + // rotation + if (RotationMode == RotationModes.Random) + particle.Rotation = Calc.Random.NextAngle(); + else if (RotationMode == RotationModes.SameAsDirection) + particle.Rotation = moveDirection; + else + particle.Rotation = 0; + + // spin + particle.Spin = Calc.Random.Range(SpinMin, SpinMax); + if (SpinFlippedChance) + particle.Spin *= Calc.Random.Choose(1, -1); + + return particle; + } + + } +} diff --git a/MonocleEngineDemo/Monocle/Properties/AssemblyInfo.cs b/MonocleEngineDemo/Monocle/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0d4f5e7 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Monocle")] +[assembly: AssemblyProduct("Monocle")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyCompany("Matt Makes Games Inc.")] +[assembly: AssemblyCopyright("Copyright © Matt Makes Games Inc. 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. Only Windows +// assemblies support COM. +[assembly: ComVisible(false)] + +// On Windows, the following GUID is for the ID of the typelib if this +// project is exposed to COM. On other platforms, it unique identifies the +// title storage container when deploying this assembly to the device. +[assembly: Guid("9b1f2162-e5f3-44fa-8b15-ccae8caf333b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] diff --git a/MonocleEngineDemo/Monocle/Renderers/EverythingRenderer.cs b/MonocleEngineDemo/Monocle/Renderers/EverythingRenderer.cs new file mode 100644 index 0000000..56759c4 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Renderers/EverythingRenderer.cs @@ -0,0 +1,43 @@ +using Microsoft.Xna.Framework.Graphics; + +namespace Monocle +{ + /// + /// Used for displaying data from current scene. + /// + public class EverythingRenderer : Renderer + { + public BlendState BlendState; + public SamplerState SamplerState; + public Effect Effect; + public Camera Camera; + + public EverythingRenderer() + { + BlendState = BlendState.AlphaBlend; + SamplerState = SamplerState.LinearClamp; + Camera = new Camera(); + } + + public override void BeforeRender(Scene scene) + { + + } + + public override void Render(Scene scene) + { + Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState, SamplerState, DepthStencilState.None, RasterizerState.CullNone, Effect, Camera.Matrix * Engine.ScreenMatrix); + + scene.Entities.Render(); + if (Engine.Commands.Open) + scene.Entities.DebugRender(Camera); + + Draw.SpriteBatch.End(); + } + + public override void AfterRender(Scene scene) + { + + } + } +} diff --git a/MonocleEngineDemo/Monocle/Renderers/Renderer.cs b/MonocleEngineDemo/Monocle/Renderers/Renderer.cs new file mode 100644 index 0000000..7755f10 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Renderers/Renderer.cs @@ -0,0 +1,12 @@ + +namespace Monocle +{ + public abstract class Renderer + { + public bool Visible = true; + public virtual void Update(Scene scene) { } + public virtual void BeforeRender(Scene scene) { } + public virtual void Render(Scene scene) { } + public virtual void AfterRender(Scene scene) { } + } +} diff --git a/MonocleEngineDemo/Monocle/Renderers/SingleTagRenderer.cs b/MonocleEngineDemo/Monocle/Renderers/SingleTagRenderer.cs new file mode 100644 index 0000000..1d2917f --- /dev/null +++ b/MonocleEngineDemo/Monocle/Renderers/SingleTagRenderer.cs @@ -0,0 +1,46 @@ +using Microsoft.Xna.Framework.Graphics; + +namespace Monocle +{ + public class SingleTagRenderer : Renderer + { + public BitTag Tag; + public BlendState BlendState; + public SamplerState SamplerState; + public Effect Effect; + public Camera Camera; + + public SingleTagRenderer(BitTag tag) + { + Tag = tag; + BlendState = BlendState.AlphaBlend; + SamplerState = SamplerState.LinearClamp; + Camera = new Camera(); + } + + public override void BeforeRender(Scene scene) + { + + } + + public override void Render(Scene scene) + { + Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState, SamplerState, DepthStencilState.None, RasterizerState.CullNone, Effect, Camera.Matrix * Engine.ScreenMatrix); + + foreach (var entity in scene[Tag]) + if (entity.Visible) + entity.Render(); + + if (Engine.Commands.Open) + foreach (var entity in scene[Tag]) + entity.DebugRender(Camera); + + Draw.SpriteBatch.End(); + } + + public override void AfterRender(Scene scene) + { + + } + } +} diff --git a/MonocleEngineDemo/Monocle/Renderers/TagExcludeRenderer.cs b/MonocleEngineDemo/Monocle/Renderers/TagExcludeRenderer.cs new file mode 100644 index 0000000..4702e70 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Renderers/TagExcludeRenderer.cs @@ -0,0 +1,51 @@ +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Monocle +{ + public class TagExcludeRenderer : Renderer + { + public BlendState BlendState; + public SamplerState SamplerState; + public Effect Effect; + public Camera Camera; + public int ExcludeTag; + + public TagExcludeRenderer(int excludeTag) + { + ExcludeTag = excludeTag; + BlendState = BlendState.AlphaBlend; + SamplerState = SamplerState.LinearClamp; + Camera = new Camera(); + } + + public override void BeforeRender(Scene scene) + { + + } + + public override void Render(Scene scene) + { + Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState, SamplerState, DepthStencilState.None, RasterizerState.CullNone, Effect, Camera.Matrix * Engine.ScreenMatrix); + + foreach (var entity in scene.Entities) + if (entity.Visible && (entity.Tag & ExcludeTag) == 0) + entity.Render(); + + if (Engine.Commands.Open) + foreach (var entity in scene.Entities) + if ((entity.Tag & ExcludeTag) == 0) + entity.DebugRender(Camera); + + Draw.SpriteBatch.End(); + } + + public override void AfterRender(Scene scene) + { + + } + } +} diff --git a/MonocleEngineDemo/Monocle/Scene.cs b/MonocleEngineDemo/Monocle/Scene.cs new file mode 100644 index 0000000..804fe57 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Scene.cs @@ -0,0 +1,861 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Monocle +{ + public class Scene : IEnumerable, IEnumerable + { + public bool Paused; + public float TimeActive; + public float RawTimeActive; + public bool Focused { get; private set; } + public EntityList Entities { get; private set; } + public TagLists TagLists { get; private set; } + public RendererList RendererList { get; private set; } + public Entity HelperEntity { get; private set; } + public Tracker Tracker { get; private set; } + + private Dictionary actualDepthLookup; + + public event Action OnEndOfFrame; + + public Scene() + { + Tracker = new Tracker(); + Entities = new EntityList(this); + TagLists = new TagLists(); + RendererList = new RendererList(this); + + actualDepthLookup = new Dictionary(); + + HelperEntity = new Entity(); + Entities.Add(HelperEntity); + } + + public virtual void Begin() + { + Focused = true; + foreach (var entity in Entities) + entity.SceneBegin(this); + } + + public virtual void End() + { + Focused = false; + foreach (var entity in Entities) + entity.SceneEnd(this); + } + + public virtual void BeforeUpdate() + { + if (!Paused) + TimeActive += Engine.DeltaTime; + RawTimeActive += Engine.RawDeltaTime; + + Entities.UpdateLists(); + TagLists.UpdateLists(); + RendererList.UpdateLists(); + } + + public virtual void Update() + { + if (!Paused) + { + Entities.Update(); + RendererList.Update(); + } + } + + public virtual void AfterUpdate() + { + if (OnEndOfFrame != null) + { + OnEndOfFrame(); + OnEndOfFrame = null; + } + } + + public virtual void BeforeRender() + { + RendererList.BeforeRender(); + } + + public virtual void Render() + { + RendererList.Render(); + } + + public virtual void AfterRender() + { + RendererList.AfterRender(); + } + + public virtual void HandleGraphicsReset() + { + Entities.HandleGraphicsReset(); + } + + public virtual void HandleGraphicsCreate() + { + Entities.HandleGraphicsCreate(); + } + + public virtual void GainFocus() + { + + } + + public virtual void LoseFocus() + { + + } + + #region Interval + + /// + /// Returns whether the Scene timer has passed the given time interval since the last frame. Ex: given 2.0f, this will return true once every 2 seconds + /// + /// The time interval to check for + /// + public bool OnInterval(float interval) + { + return (int)((TimeActive - Engine.DeltaTime) / interval) < (int)(TimeActive / interval); + } + + /// + /// Returns whether the Scene timer has passed the given time interval since the last frame. Ex: given 2.0f, this will return true once every 2 seconds + /// + /// The time interval to check for + /// + public bool OnInterval(float interval, float offset) + { + return Math.Floor((TimeActive - offset - Engine.DeltaTime) / interval) < Math.Floor((TimeActive - offset) / interval); + } + + public bool BetweenInterval(float interval) + { + return Calc.BetweenInterval(TimeActive, interval); + } + + public bool OnRawInterval(float interval) + { + return (int)((RawTimeActive - Engine.RawDeltaTime) / interval) < (int)(RawTimeActive / interval); + } + + public bool OnRawInterval(float interval, float offset) + { + return Math.Floor((RawTimeActive - offset - Engine.RawDeltaTime) / interval) < Math.Floor((RawTimeActive - offset) / interval); + } + + public bool BetweenRawInterval(float interval) + { + return Calc.BetweenInterval(RawTimeActive, interval); + } + + #endregion + + #region Collisions v Tags + + public bool CollideCheck(Vector2 point, int tag) + { + var list = TagLists[(int)tag]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollidePoint(point)) + return true; + return false; + } + + public bool CollideCheck(Vector2 from, Vector2 to, int tag) + { + var list = TagLists[(int)tag]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideLine(from, to)) + return true; + return false; + } + + public bool CollideCheck(Rectangle rect, int tag) + { + var list = TagLists[(int)tag]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideRect(rect)) + return true; + return false; + } + + public bool CollideCheck(Rectangle rect, Entity entity) + { + return (entity.Collidable && entity.CollideRect(rect)); + } + + public Entity CollideFirst(Vector2 point, int tag) + { + var list = TagLists[(int)tag]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollidePoint(point)) + return list[i]; + return null; + } + + public Entity CollideFirst(Vector2 from, Vector2 to, int tag) + { + var list = TagLists[(int)tag]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideLine(from, to)) + return list[i]; + return null; + } + + public Entity CollideFirst(Rectangle rect, int tag) + { + var list = TagLists[(int)tag]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideRect(rect)) + return list[i]; + return null; + } + + public void CollideInto(Vector2 point, int tag, List hits) + { + var list = TagLists[(int)tag]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollidePoint(point)) + hits.Add(list[i]); + } + + public void CollideInto(Vector2 from, Vector2 to, int tag, List hits) + { + var list = TagLists[(int)tag]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideLine(from, to)) + hits.Add(list[i]); + } + + public void CollideInto(Rectangle rect, int tag, List hits) + { + var list = TagLists[(int)tag]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideRect(rect)) + list.Add(list[i]); + } + + public List CollideAll(Vector2 point, int tag) + { + List list = new List(); + CollideInto(point, tag, list); + return list; + } + + public List CollideAll(Vector2 from, Vector2 to, int tag) + { + List list = new List(); + CollideInto(from, to, tag, list); + return list; + } + + public List CollideAll(Rectangle rect, int tag) + { + List list = new List(); + CollideInto(rect, tag, list); + return list; + } + + public void CollideDo(Vector2 point, int tag, Action action) + { + var list = TagLists[(int)tag]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollidePoint(point)) + action(list[i]); + } + + public void CollideDo(Vector2 from, Vector2 to, int tag, Action action) + { + var list = TagLists[(int)tag]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideLine(from, to)) + action(list[i]); + } + + public void CollideDo(Rectangle rect, int tag, Action action) + { + var list = TagLists[(int)tag]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideRect(rect)) + action(list[i]); + } + + public Vector2 LineWalkCheck(Vector2 from, Vector2 to, int tag, float precision) + { + Vector2 add = to - from; + add.Normalize(); + add *= precision; + + int amount = (int)Math.Floor((from - to).Length() / precision); + Vector2 prev = from; + Vector2 at = from + add; + + for (int i = 0; i <= amount; i++) + { + if (CollideCheck(at, tag)) + return prev; + prev = at; + at += add; + } + + return to; + } + + #endregion + + #region Collisions v Tracked List Entities + + public bool CollideCheck(Vector2 point) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollidePoint(point)) + return true; + return false; + } + + public bool CollideCheck(Vector2 from, Vector2 to) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideLine(from, to)) + return true; + return false; + } + + public bool CollideCheck(Rectangle rect) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideRect(rect)) + return true; + return false; + } + + public T CollideFirst(Vector2 point) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollidePoint(point)) + return list[i] as T; + return null; + } + + public T CollideFirst(Vector2 from, Vector2 to) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideLine(from, to)) + return list[i] as T; + return null; + } + + public T CollideFirst(Rectangle rect) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideRect(rect)) + return list[i] as T; + return null; + } + + public void CollideInto(Vector2 point, List hits) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollidePoint(point)) + hits.Add(list[i]); + } + + public void CollideInto(Vector2 from, Vector2 to, List hits) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideLine(from, to)) + hits.Add(list[i]); + } + + public void CollideInto(Rectangle rect, List hits) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideRect(rect)) + list.Add(list[i]); + } + + public void CollideInto(Vector2 point, List hits) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollidePoint(point)) + hits.Add(list[i] as T); + } + + public void CollideInto(Vector2 from, Vector2 to, List hits) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideLine(from, to)) + hits.Add(list[i] as T); + } + + public void CollideInto(Rectangle rect, List hits) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideRect(rect)) + hits.Add(list[i] as T); + } + + public List CollideAll(Vector2 point) where T : Entity + { + List list = new List(); + CollideInto(point, list); + return list; + } + + public List CollideAll(Vector2 from, Vector2 to) where T : Entity + { + List list = new List(); + CollideInto(from, to, list); + return list; + } + + public List CollideAll(Rectangle rect) where T : Entity + { + List list = new List(); + CollideInto(rect, list); + return list; + } + + public void CollideDo(Vector2 point, Action action) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollidePoint(point)) + action(list[i] as T); + } + + public void CollideDo(Vector2 from, Vector2 to, Action action) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideLine(from, to)) + action(list[i] as T); + } + + public void CollideDo(Rectangle rect, Action action) where T : Entity + { + var list = Tracker.Entities[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Collidable && list[i].CollideRect(rect)) + action(list[i] as T); + } + + public Vector2 LineWalkCheck(Vector2 from, Vector2 to, float precision) where T : Entity + { + Vector2 add = to - from; + add.Normalize(); + add *= precision; + + int amount = (int)Math.Floor((from - to).Length() / precision); + Vector2 prev = from; + Vector2 at = from + add; + + for (int i = 0; i <= amount; i++) + { + if (CollideCheck(at)) + return prev; + prev = at; + at += add; + } + + return to; + } + + #endregion + + #region Collisions v Tracked List Components + + public bool CollideCheckByComponent(Vector2 point) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollidePoint(point)) + return true; + return false; + } + + public bool CollideCheckByComponent(Vector2 from, Vector2 to) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollideLine(from, to)) + return true; + return false; + } + + public bool CollideCheckByComponent(Rectangle rect) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollideRect(rect)) + return true; + return false; + } + + public T CollideFirstByComponent(Vector2 point) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollidePoint(point)) + return list[i] as T; + return null; + } + + public T CollideFirstByComponent(Vector2 from, Vector2 to) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollideLine(from, to)) + return list[i] as T; + return null; + } + + public T CollideFirstByComponent(Rectangle rect) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollideRect(rect)) + return list[i] as T; + return null; + } + + public void CollideIntoByComponent(Vector2 point, List hits) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollidePoint(point)) + hits.Add(list[i]); + } + + public void CollideIntoByComponent(Vector2 from, Vector2 to, List hits) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollideLine(from, to)) + hits.Add(list[i]); + } + + public void CollideIntoByComponent(Rectangle rect, List hits) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollideRect(rect)) + list.Add(list[i]); + } + + public void CollideIntoByComponent(Vector2 point, List hits) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollidePoint(point)) + hits.Add(list[i] as T); + } + + public void CollideIntoByComponent(Vector2 from, Vector2 to, List hits) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollideLine(from, to)) + hits.Add(list[i] as T); + } + + public void CollideIntoByComponent(Rectangle rect, List hits) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollideRect(rect)) + list.Add(list[i] as T); + } + + public List CollideAllByComponent(Vector2 point) where T : Component + { + List list = new List(); + CollideIntoByComponent(point, list); + return list; + } + + public List CollideAllByComponent(Vector2 from, Vector2 to) where T : Component + { + List list = new List(); + CollideIntoByComponent(from, to, list); + return list; + } + + public List CollideAllByComponent(Rectangle rect) where T : Component + { + List list = new List(); + CollideIntoByComponent(rect, list); + return list; + } + + public void CollideDoByComponent(Vector2 point, Action action) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollidePoint(point)) + action(list[i] as T); + } + + public void CollideDoByComponent(Vector2 from, Vector2 to, Action action) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollideLine(from, to)) + action(list[i] as T); + } + + public void CollideDoByComponent(Rectangle rect, Action action) where T : Component + { + var list = Tracker.Components[typeof(T)]; + + for (int i = 0; i < list.Count; i++) + if (list[i].Entity.Collidable && list[i].Entity.CollideRect(rect)) + action(list[i] as T); + } + + public Vector2 LineWalkCheckByComponent(Vector2 from, Vector2 to, float precision) where T : Component + { + Vector2 add = to - from; + add.Normalize(); + add *= precision; + + int amount = (int)Math.Floor((from - to).Length() / precision); + Vector2 prev = from; + Vector2 at = from + add; + + for (int i = 0; i <= amount; i++) + { + if (CollideCheckByComponent(at)) + return prev; + prev = at; + at += add; + } + + return to; + } + + #endregion + + #region Utils + + internal void SetActualDepth(Entity entity) + { + const double theta = .000001f; + + double add = 0; + if (actualDepthLookup.TryGetValue(entity.depth, out add)) + actualDepthLookup[entity.depth] += theta; + else + actualDepthLookup.Add(entity.depth, theta); + entity.actualDepth = entity.depth - add; + + //Mark lists unsorted + Entities.MarkUnsorted(); + for (int i = 0; i < BitTag.TotalTags; i++) + if (entity.TagCheck(1 << i)) + TagLists.MarkUnsorted(i); + } + + #endregion + + #region Entity Shortcuts + + /// + /// Shortcut to call Engine.Pooler.Create, add the Entity to this Scene, and return it. Entity type must be marked as Pooled + /// + /// Pooled Entity type to create + /// + public T CreateAndAdd() where T : Entity, new() + { + var entity = Engine.Pooler.Create(); + Add(entity); + return entity; + } + + /// + /// Quick access to entire tag lists of Entities. Result will never be null + /// + /// The tag list to fetch + /// + public List this[BitTag tag] + { + get + { + return TagLists[tag.ID]; + } + } + + /// + /// Shortcut function for adding an Entity to the Scene's Entities list + /// + /// The Entity to add + public void Add(Entity entity) + { + Entities.Add(entity); + } + + /// + /// Shortcut function for removing an Entity from the Scene's Entities list + /// + /// The Entity to remove + public void Remove(Entity entity) + { + Entities.Remove(entity); + } + + /// + /// Shortcut function for adding a set of Entities from the Scene's Entities list + /// + /// The Entities to add + public void Add(IEnumerable entities) + { + Entities.Add(entities); + } + + /// + /// Shortcut function for removing a set of Entities from the Scene's Entities list + /// + /// The Entities to remove + public void Remove(IEnumerable entities) + { + Entities.Remove(entities); + } + + /// + /// Shortcut function for adding a set of Entities from the Scene's Entities list + /// + /// The Entities to add + public void Add(params Entity[] entities) + { + Entities.Add(entities); + } + + /// + /// Shortcut function for removing a set of Entities from the Scene's Entities list + /// + /// The Entities to remove + public void Remove(params Entity[] entities) + { + Entities.Remove(entities); + } + + /// + /// Allows you to iterate through all Entities in the Scene + /// + /// + public IEnumerator GetEnumerator() + { + return Entities.GetEnumerator(); + } + + /// + /// Allows you to iterate through all Entities in the Scene + /// + /// + IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public List GetEntitiesByTagMask(int mask) + { + List list = new List(); + foreach (var entity in Entities) + if ((entity.Tag & mask) != 0) + list.Add(entity); + return list; + } + + public List GetEntitiesExcludingTagMask(int mask) + { + List list = new List(); + foreach (var entity in Entities) + if ((entity.Tag & mask) == 0) + list.Add(entity); + return list; + } + + #endregion + + #region Renderer Shortcuts + + /// + /// Shortcut function to add a Renderer to the Renderer list + /// + /// The Renderer to add + public void Add(Renderer renderer) + { + RendererList.Add(renderer); + } + + /// + /// Shortcut function to remove a Renderer from the Renderer list + /// + /// The Renderer to remove + public void Remove(Renderer renderer) + { + RendererList.Remove(renderer); + } + + #endregion + } +} diff --git a/MonocleEngineDemo/Monocle/Util/BitTag.cs b/MonocleEngineDemo/Monocle/Util/BitTag.cs new file mode 100644 index 0000000..f60de45 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/BitTag.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Monocle +{ + /// + /// Used mainly for categorizing types of objects (especially for collisions). You can create up to 32 different tags! + /// + public class BitTag + { + internal static int TotalTags = 0; + internal static BitTag[] byID = new BitTag[32]; + private static Dictionary byName = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public static BitTag Get(string name) + { +#if DEBUG + if (!byName.ContainsKey(name)) + throw new Exception("No tag with the name '" + name + "' has been defined!"); +#endif + return byName[name]; + } + + public int ID; + public int Value; + public string Name; + + public BitTag(string name) + { +#if DEBUG + if (TotalTags >= 32) + throw new Exception("Maximum tag limit of 32 exceeded!"); + if (byName.ContainsKey(name)) + throw new Exception("Two tags defined with the same name: '" + name + "'!"); +#endif + + ID = TotalTags; + Value = 1 << TotalTags; + Name = name; + + byID[ID] = this; + byName[name] = this; + + TotalTags++; + } + + public static implicit operator int(BitTag tag) + { + return tag.Value; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/Cache.cs b/MonocleEngineDemo/Monocle/Util/Cache.cs new file mode 100644 index 0000000..7051982 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/Cache.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; + +namespace Monocle +{ + public static class Cache + { + public static Dictionary> cache; + + private static void Init() where T : Entity, new() + { + if (cache == null) + cache = new Dictionary>(); + if (!cache.ContainsKey(typeof(T))) + cache.Add(typeof(T), new Stack()); + } + + public static void Store(T instance) where T : Entity, new() + { + Init(); + cache[typeof(T)].Push(instance); + } + + public static T Create() where T : Entity, new() + { + Init(); + if (cache[typeof(T)].Count > 0) + return cache[typeof(T)].Pop() as T; + else + return new T(); + } + + public static void Clear() where T : Entity, new() + { + if (cache != null && cache.ContainsKey(typeof(T))) + cache[typeof(T)].Clear(); + } + + public static void ClearAll() + { + if (cache != null) + foreach (var kv in cache) + kv.Value.Clear(); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/Calc.cs b/MonocleEngineDemo/Monocle/Util/Calc.cs new file mode 100644 index 0000000..f7f614b --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/Calc.cs @@ -0,0 +1,2198 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Collections; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Xml; +using System.Xml.Serialization; + +namespace Monocle +{ + public static class Calc + { + #region Enums + + public static int EnumLength(Type e) + { + return Enum.GetNames(e).Length; + } + + public static T StringToEnum(string str) where T : struct + { + if (Enum.IsDefined(typeof(T), str)) + return (T)Enum.Parse(typeof(T), str); + else + throw new Exception("The string cannot be converted to the enum type."); + } + + public static T[] StringsToEnums(string[] strs) where T : struct + { + T[] ret = new T[strs.Length]; + for (int i = 0; i < strs.Length; i++) + ret[i] = StringToEnum(strs[i]); + return ret; + } + + public static bool EnumHasString(string str) where T : struct + { + return Enum.IsDefined(typeof(T), str); + } + + #endregion + + #region Strings + + public static bool StartsWith(this string str, string match) + { + return str.IndexOf(match) == 0; + } + + public static bool EndsWith(this string str, string match) + { + return str.LastIndexOf(match) == str.Length - match.Length; + } + + public static bool IsIgnoreCase(this string str, params string[] matches) + { + if (string.IsNullOrEmpty(str)) + return false; + + foreach (var match in matches) + if (str.Equals(match, StringComparison.InvariantCultureIgnoreCase)) + return true; + + return false; + } + + public static string ToString(this int num, int minDigits) + { + string ret = num.ToString(); + while (ret.Length < minDigits) + ret = "0" + ret; + return ret; + } + + public static string[] SplitLines(string text, SpriteFont font, int maxLineWidth, char newLine = '\n') + { + List lines = new List(); + + foreach (var forcedLine in text.Split(newLine)) + { + string line = ""; + + foreach (string word in forcedLine.Split(' ')) + { + if (font.MeasureString(line + " " + word).X > maxLineWidth) + { + lines.Add(line); + line = word; + } + else + { + if (line != "") + line += " "; + line += word; + } + } + + lines.Add(line); + } + + return lines.ToArray(); + } + + #endregion + + #region Count + + public static int Count(T target, T a, T b) + { + int num = 0; + + if (a.Equals(target)) + num++; + if (b.Equals(target)) + num++; + + return num; + } + + public static int Count(T target, T a, T b, T c) + { + int num = 0; + + if (a.Equals(target)) + num++; + if (b.Equals(target)) + num++; + if (c.Equals(target)) + num++; + + return num; + } + + public static int Count(T target, T a, T b, T c, T d) + { + int num = 0; + + if (a.Equals(target)) + num++; + if (b.Equals(target)) + num++; + if (c.Equals(target)) + num++; + if (d.Equals(target)) + num++; + + return num; + } + + public static int Count(T target, T a, T b, T c, T d, T e) + { + int num = 0; + + if (a.Equals(target)) + num++; + if (b.Equals(target)) + num++; + if (c.Equals(target)) + num++; + if (d.Equals(target)) + num++; + if (e.Equals(target)) + num++; + + return num; + } + + public static int Count(T target, T a, T b, T c, T d, T e, T f) + { + int num = 0; + + if (a.Equals(target)) + num++; + if (b.Equals(target)) + num++; + if (c.Equals(target)) + num++; + if (d.Equals(target)) + num++; + if (e.Equals(target)) + num++; + if (f.Equals(target)) + num++; + + return num; + } + + #endregion + + #region Give Me + + public static T GiveMe(int index, T a, T b) + { + switch (index) + { + default: + throw new Exception("Index was out of range!"); + + case 0: + return a; + case 1: + return b; + } + } + + public static T GiveMe(int index, T a, T b, T c) + { + switch (index) + { + default: + throw new Exception("Index was out of range!"); + + case 0: + return a; + case 1: + return b; + case 2: + return c; + } + } + + public static T GiveMe(int index, T a, T b, T c, T d) + { + switch (index) + { + default: + throw new Exception("Index was out of range!"); + + case 0: + return a; + case 1: + return b; + case 2: + return c; + case 3: + return d; + } + } + + public static T GiveMe(int index, T a, T b, T c, T d, T e) + { + switch (index) + { + default: + throw new Exception("Index was out of range!"); + + case 0: + return a; + case 1: + return b; + case 2: + return c; + case 3: + return d; + case 4: + return e; + } + } + + public static T GiveMe(int index, T a, T b, T c, T d, T e, T f) + { + switch (index) + { + default: + throw new Exception("Index was out of range!"); + + case 0: + return a; + case 1: + return b; + case 2: + return c; + case 3: + return d; + case 4: + return e; + case 5: + return f; + } + } + + #endregion + + #region Random + + public static Random Random = new Random(); + private static Stack randomStack = new Stack(); + + public static void PushRandom(int newSeed) + { + randomStack.Push(Calc.Random); + Calc.Random = new Random(newSeed); + } + + public static void PushRandom(Random random) + { + randomStack.Push(Calc.Random); + Calc.Random = random; + } + + public static void PushRandom() + { + randomStack.Push(Calc.Random); + Calc.Random = new Random(); + } + + public static void PopRandom() + { + Calc.Random = randomStack.Pop(); + } + + #region Choose + + public static T Choose(this Random random, T a, T b) + { + return GiveMe(random.Next(2), a, b); + } + + public static T Choose(this Random random, T a, T b, T c) + { + return GiveMe(random.Next(3), a, b, c); + } + + public static T Choose(this Random random, T a, T b, T c, T d) + { + return GiveMe(random.Next(4), a, b, c, d); + } + + public static T Choose(this Random random, T a, T b, T c, T d, T e) + { + return GiveMe(random.Next(5), a, b, c, d, e); + } + + public static T Choose(this Random random, T a, T b, T c, T d, T e, T f) + { + return GiveMe(random.Next(6), a, b, c, d, e, f); + } + + public static T Choose(this Random random, params T[] choices) + { + return choices[random.Next(choices.Length)]; + } + + public static T Choose(this Random random, List choices) + { + return choices[random.Next(choices.Count)]; + } + + #endregion + + #region Range + + /// + /// Returns a random integer between min (inclusive) and max (exclusive) + /// + /// + /// + /// + /// + public static int Range(this Random random, int min, int max) + { + return min + random.Next(max - min); + } + + /// + /// Returns a random float between min (inclusive) and max (exclusive) + /// + /// + /// + /// + /// + public static float Range(this Random random, float min, float max) + { + return min + random.NextFloat(max - min); + } + + /// + /// Returns a random Vector2, and x- and y-values of which are between min (inclusive) and max (exclusive) + /// + /// + /// + /// + /// + public static Vector2 Range(this Random random, Vector2 min, Vector2 max) + { + return min + new Vector2(random.NextFloat(max.X - min.X), random.NextFloat(max.Y - min.Y)); + } + + #endregion + + public static int Facing(this Random random) + { + return (random.NextFloat() < 0.5f ? -1 : 1); + } + + public static bool Chance(this Random random, float chance) + { + return random.NextFloat() < chance; + } + + public static float NextFloat(this Random random) + { + return (float)random.NextDouble(); + } + + public static float NextFloat(this Random random, float max) + { + return random.NextFloat() * max; + } + + public static float NextAngle(this Random random) + { + return random.NextFloat() * MathHelper.TwoPi; + } + + private static int[] shakeVectorOffsets = new int[] { -1, -1, 0, 1, 1 }; + + public static Vector2 ShakeVector(this Random random) + { + return new Vector2(random.Choose(shakeVectorOffsets), random.Choose(shakeVectorOffsets)); + } + + #endregion + + #region Lists + + public static Vector2 ClosestTo(this List list, Vector2 to) + { + Vector2 best = list[0]; + float distSq = Vector2.DistanceSquared(list[0], to); + + for (int i = 1; i < list.Count; i++) + { + float d = Vector2.DistanceSquared(list[i], to); + if (d < distSq) + { + distSq = d; + best = list[i]; + } + } + + return best; + } + + public static Vector2 ClosestTo(this Vector2[] list, Vector2 to) + { + Vector2 best = list[0]; + float distSq = Vector2.DistanceSquared(list[0], to); + + for (int i = 1; i < list.Length; i++) + { + float d = Vector2.DistanceSquared(list[i], to); + if (d < distSq) + { + distSq = d; + best = list[i]; + } + } + + return best; + } + + public static Vector2 ClosestTo(this Vector2[] list, Vector2 to, out int index) + { + index = 0; + Vector2 best = list[0]; + float distSq = Vector2.DistanceSquared(list[0], to); + + for (int i = 1; i < list.Length; i++) + { + float d = Vector2.DistanceSquared(list[i], to); + if (d < distSq) + { + index = i; + distSq = d; + best = list[i]; + } + } + + return best; + } + + public static void Shuffle(this List list, Random random) + { + int i = list.Count; + int j; + T t; + + while (--i > 0) + { + t = list[i]; + list[i] = list[j = random.Next(i + 1)]; + list[j] = t; + } + } + + public static void Shuffle(this List list) + { + list.Shuffle(Random); + } + + public static void ShuffleSetFirst(this List list, Random random, T first) + { + int amount = 0; + while (list.Contains(first)) + { + list.Remove(first); + amount++; + } + + list.Shuffle(random); + + for (int i = 0; i < amount; i++) + list.Insert(0, first); + } + + public static void ShuffleSetFirst(this List list, T first) + { + list.ShuffleSetFirst(Random, first); + } + + public static void ShuffleNotFirst(this List list, Random random, T notFirst) + { + int amount = 0; + while (list.Contains(notFirst)) + { + list.Remove(notFirst); + amount++; + } + + list.Shuffle(random); + + for (int i = 0; i < amount; i++) + list.Insert(random.Next(list.Count - 1) + 1, notFirst); + } + + public static void ShuffleNotFirst(this List list, T notFirst) + { + list.ShuffleNotFirst(Random, notFirst); + } + + #endregion + + #region Colors + + public static Color Invert(this Color color) + { + return new Color(255 - color.R, 255 - color.G, 255 - color.B, color.A); + } + + public static Color HexToColor(string hex) + { + if (hex.Length >= 6) + { + float r = (HexToByte(hex[0]) * 16 + HexToByte(hex[1])) / 255.0f; + float g = (HexToByte(hex[2]) * 16 + HexToByte(hex[3])) / 255.0f; + float b = (HexToByte(hex[4]) * 16 + HexToByte(hex[5])) / 255.0f; + return new Color(r, g, b); + } + + return Color.White; + } + + #endregion + + #region Time + + public static string ShortGameplayFormat(this TimeSpan time) + { + if (time.TotalHours >= 1) + return ((int)time.Hours) + ":" + time.ToString(@"mm\:ss\.fff"); + else + return time.ToString(@"m\:ss\.fff"); + } + + public static string LongGameplayFormat(this TimeSpan time) + { + StringBuilder str = new StringBuilder(); + + if (time.TotalDays >= 2) + { + str.Append((int)time.TotalDays); + str.Append(" days, "); + } + else if (time.TotalDays >= 1) + str.Append("1 day, "); + + str.Append((time.TotalHours - ((int)time.TotalDays * 24)).ToString("0.0")); + str.Append(" hours"); + + return str.ToString(); + } + + #endregion + + #region Math + + public const float Right = 0; + public const float Up = -MathHelper.PiOver2; + public const float Left = MathHelper.Pi; + public const float Down = MathHelper.PiOver2; + public const float UpRight = -MathHelper.PiOver4; + public const float UpLeft = -MathHelper.PiOver4 - MathHelper.PiOver2; + public const float DownRight = MathHelper.PiOver4; + public const float DownLeft = MathHelper.PiOver4 + MathHelper.PiOver2; + public const float DegToRad = MathHelper.Pi / 180f; + public const float RadToDeg = 180f / MathHelper.Pi; + public const float DtR = DegToRad; + public const float RtD = RadToDeg; + public const float Circle = MathHelper.TwoPi; + public const float HalfCircle = MathHelper.Pi; + public const float QuarterCircle = MathHelper.PiOver2; + public const float EighthCircle = MathHelper.PiOver4; + private const string Hex = "0123456789ABCDEF"; + + public static int Digits(this int num) + { + int digits = 1; + int target = 10; + + while (num >= target) + { + digits++; + target *= 10; + } + + return digits; + } + + public static byte HexToByte(char c) + { + return (byte)Hex.IndexOf(char.ToUpper(c)); + } + + public static float Percent(float num, float zeroAt, float oneAt) + { + return MathHelper.Clamp((num - zeroAt) / oneAt, 0, 1); + } + + public static float SignThreshold(float value, float threshold) + { + if (Math.Abs(value) >= threshold) + return Math.Sign(value); + else + return 0; + } + + public static float Min(params float[] values) + { + float min = values[0]; + for (int i = 1; i < values.Length; i++) + min = MathHelper.Min(values[i], min); + return min; + } + + public static float Max(params float[] values) + { + float max = values[0]; + for (int i = 1; i < values.Length; i++) + max = MathHelper.Max(values[i], max); + return max; + } + + public static float ToRad(this float f) + { + return f * DegToRad; + } + + public static float ToDeg(this float f) + { + return f * RadToDeg; + } + + public static int Axis(bool negative, bool positive, int both = 0) + { + if (negative) + { + if (positive) + return both; + else + return -1; + } + else if (positive) + return 1; + else + return 0; + } + + public static int Clamp(int value, int min, int max) + { + return Math.Min(Math.Max(value, min), max); + } + + public static float Clamp(float value, float min, float max) + { + return Math.Min(Math.Max(value, min), max); + } + + public static float YoYo(float value) + { + if (value <= .5f) + return value * 2; + else + return 1 - ((value - .5f) * 2); + } + + public static float Map(float val, float min, float max, float newMin = 0, float newMax = 1) + { + return ((val - min) / (max - min)) * (newMax - newMin) + newMin; + } + + public static float SineMap(float counter, float newMin, float newMax) + { + return Calc.Map((float)Math.Sin(counter), 01, 1, newMin, newMax); + } + + public static float ClampedMap(float val, float min, float max, float newMin = 0, float newMax = 1) + { + return MathHelper.Clamp((val - min) / (max - min), 0, 1) * (newMax - newMin) + newMin; + } + + public static float LerpSnap(float value1, float value2, float amount, float snapThreshold = .1f) + { + float ret = MathHelper.Lerp(value1, value2, amount); + if (Math.Abs(ret - value2) < snapThreshold) + return value2; + else + return ret; + } + + public static float LerpClamp(float value1, float value2, float lerp) + { + return MathHelper.Lerp(value1, value2, MathHelper.Clamp(lerp, 0, 1)); + } + + public static Vector2 LerpSnap(Vector2 value1, Vector2 value2, float amount, float snapThresholdSq = .1f) + { + Vector2 ret = Vector2.Lerp(value1, value2, amount); + if ((ret - value2).LengthSquared() < snapThresholdSq) + return value2; + else + return ret; + } + + + public static Vector2 Sign(this Vector2 vec) + { + return new Vector2(Math.Sign(vec.X), Math.Sign(vec.Y)); + } + + public static Vector2 SafeNormalize(this Vector2 vec) + { + return SafeNormalize(vec, Vector2.Zero); + } + + public static Vector2 SafeNormalize(this Vector2 vec, float length) + { + return SafeNormalize(vec, Vector2.Zero, length); + } + + public static Vector2 SafeNormalize(this Vector2 vec, Vector2 ifZero) + { + if (vec == Vector2.Zero) + return ifZero; + else + { + vec.Normalize(); + return vec; + } + } + + public static Vector2 SafeNormalize(this Vector2 vec, Vector2 ifZero, float length) + { + if (vec == Vector2.Zero) + return ifZero * length; + else + { + vec.Normalize(); + return vec * length; + } + } + + public static float ReflectAngle(float angle, float axis = 0) + { + return -(angle + axis) - axis; + } + + public static float ReflectAngle(float angleRadians, Vector2 axis) + { + return ReflectAngle(angleRadians, axis.Angle()); + } + + public static Vector2 ClosestPointOnLine(Vector2 lineA, Vector2 lineB, Vector2 closestTo) + { + Vector2 v = lineB - lineA; + Vector2 w = closestTo - lineA; + float t = Vector2.Dot(w, v) / Vector2.Dot(v, v); + t = MathHelper.Clamp(t, 0, 1); + + return lineA + v * t; + } + + public static Vector2 Round(this Vector2 vec) + { + return new Vector2((float)Math.Round(vec.X), (float)Math.Round(vec.Y)); + } + + public static float Snap(float value, float increment) + { + return (float)Math.Round(value / increment) * increment; + } + + public static float Snap(float value, float increment, float offset) + { + return ((float)Math.Round((value - offset) / increment) * increment) + offset; + } + + public static float WrapAngleDeg(float angleDegrees) + { + return (((angleDegrees * Math.Sign(angleDegrees) + 180) % 360) - 180) * Math.Sign(angleDegrees); + } + + public static float WrapAngle(float angleRadians) + { + return (((angleRadians * Math.Sign(angleRadians) + MathHelper.Pi) % (MathHelper.Pi * 2)) - MathHelper.Pi) * Math.Sign(angleRadians); + } + + public static Vector2 AngleToVector(float angleRadians, float length) + { + return new Vector2((float)Math.Cos(angleRadians) * length, (float)Math.Sin(angleRadians) * length); + } + + public static float AngleApproach(float val, float target, float maxMove) + { + var diff = AngleDiff(val, target); + if (Math.Abs(diff) < maxMove) + return target; + return val + MathHelper.Clamp(diff, -maxMove, maxMove); + } + + public static float AngleLerp(float startAngle, float endAngle, float percent) + { + return startAngle + AngleDiff(startAngle, endAngle) * percent; + } + + public static float Approach(float val, float target, float maxMove) + { + return val > target ? Math.Max(val - maxMove, target) : Math.Min(val + maxMove, target); + } + + public static float AngleDiff(float radiansA, float radiansB) + { + float diff = radiansB - radiansA; + + while (diff > MathHelper.Pi) { diff -= MathHelper.TwoPi; } + while (diff <= -MathHelper.Pi) { diff += MathHelper.TwoPi; } + + return diff; + } + + public static float AbsAngleDiff(float radiansA, float radiansB) + { + return Math.Abs(AngleDiff(radiansA, radiansB)); + } + + public static int SignAngleDiff(float radiansA, float radiansB) + { + return Math.Sign(AngleDiff(radiansA, radiansB)); + } + + public static float Angle(Vector2 from, Vector2 to) + { + return (float)Math.Atan2(to.Y - from.Y, to.X - from.X); + } + + public static Color ToggleColors(Color current, Color a, Color b) + { + if (current == a) + return b; + else + return a; + } + + public static float ShorterAngleDifference(float currentAngle, float angleA, float angleB) + { + if (Math.Abs(Calc.AngleDiff(currentAngle, angleA)) < Math.Abs(Calc.AngleDiff(currentAngle, angleB))) + return angleA; + else + return angleB; + } + + public static float ShorterAngleDifference(float currentAngle, float angleA, float angleB, float angleC) + { + if (Math.Abs(Calc.AngleDiff(currentAngle, angleA)) < Math.Abs(Calc.AngleDiff(currentAngle, angleB))) + return ShorterAngleDifference(currentAngle, angleA, angleC); + else + return ShorterAngleDifference(currentAngle, angleB, angleC); + } + + public static bool IsInRange(this T[] array, int index) + { + return index >= 0 && index < array.Length; + } + + public static bool IsInRange(this List list, int index) + { + return index >= 0 && index < list.Count; + } + + public static T[] Array(params T[] items) + { + return items; + } + + public static T[] VerifyLength(this T[] array, int length) + { + if (array == null) + return new T[length]; + else if (array.Length != length) + { + T[] newArray = new T[length]; + for (int i = 0; i < Math.Min(length, array.Length); i++) + newArray[i] = array[i]; + return newArray; + } + else + return array; + } + + public static T[][] VerifyLength(this T[][] array, int length0, int length1) + { + array = VerifyLength(array, length0); + for (int i = 0; i < array.Length; i++) + array[i] = VerifyLength(array[i], length1); + return array; + } + + public static bool BetweenInterval(float val, float interval) + { + return val % (interval * 2) > interval; + } + + public static bool OnInterval(float val, float prevVal, float interval) + { + return (int)(prevVal / interval) != (int)(val / interval); + } + + + #endregion + + #region Vector2 + + public static Vector2 Toward(Vector2 from, Vector2 to, float length) + { + if (from == to) + return Vector2.Zero; + else + return (to - from).SafeNormalize(length); + } + + public static Vector2 Toward(Entity from, Entity to, float length) + { + return Toward(from.Position, to.Position, length); + } + + public static Vector2 Perpendicular(this Vector2 vector) + { + return new Vector2(-vector.Y, vector.X); + } + + public static float Angle(this Vector2 vector) + { + return (float)Math.Atan2(vector.Y, vector.X); + } + + public static Vector2 Clamp(this Vector2 val, float minX, float minY, float maxX, float maxY) + { + return new Vector2(MathHelper.Clamp(val.X, minX, maxX), MathHelper.Clamp(val.Y, minY, maxY)); + } + + public static Vector2 Floor(this Vector2 val) + { + return new Vector2((int)Math.Floor(val.X), (int)Math.Floor(val.Y)); + } + + public static Vector2 Ceiling(this Vector2 val) + { + return new Vector2((int)Math.Ceiling(val.X), (int)Math.Ceiling(val.Y)); + } + + public static Vector2 Abs(this Vector2 val) + { + return new Vector2(Math.Abs(val.X), Math.Abs(val.Y)); + } + + public static Vector2 Approach(Vector2 val, Vector2 target, float maxMove) + { + if (maxMove == 0 || val == target) + return val; + + Vector2 diff = target - val; + float length = diff.Length(); + + if (length < maxMove) + return target; + else + { + diff.Normalize(); + return val + diff * maxMove; + } + } + + public static Vector2 FourWayNormal(this Vector2 vec) + { + if (vec == Vector2.Zero) + return Vector2.Zero; + + float angle = vec.Angle(); + angle = (float)Math.Floor((angle + MathHelper.PiOver2 / 2f) / MathHelper.PiOver2) * MathHelper.PiOver2; + + vec = AngleToVector(angle, 1f); + if (Math.Abs(vec.X) < .5f) + vec.X = 0; + else + vec.X = Math.Sign(vec.X); + + if (Math.Abs(vec.Y) < .5f) + vec.Y = 0; + else + vec.Y = Math.Sign(vec.Y); + + return vec; + } + + public static Vector2 EightWayNormal(this Vector2 vec) + { + if (vec == Vector2.Zero) + return Vector2.Zero; + + float angle = vec.Angle(); + angle = (float)Math.Floor((angle + MathHelper.PiOver4 / 2f) / MathHelper.PiOver4) * MathHelper.PiOver4; + + vec = AngleToVector(angle, 1f); + if (Math.Abs(vec.X) < .5f) + vec.X = 0; + else if (Math.Abs(vec.Y) < .5f) + vec.Y = 0; + + return vec; + } + + public static Vector2 SnappedNormal(this Vector2 vec, float slices) + { + float divider = MathHelper.TwoPi / slices; + + float angle = vec.Angle(); + angle = (float)Math.Floor((angle + divider / 2f) / divider) * divider; + return AngleToVector(angle, 1f); + } + + public static Vector2 Snapped(this Vector2 vec, float slices) + { + float divider = MathHelper.TwoPi / slices; + + float angle = vec.Angle(); + angle = (float)Math.Floor((angle + divider / 2f) / divider) * divider; + return AngleToVector(angle, vec.Length()); + } + + public static Vector2 XComp(this Vector2 vec) + { + return Vector2.UnitX * vec.X; + } + + public static Vector2 YComp(this Vector2 vec) + { + return Vector2.UnitY * vec.Y; + } + + public static Vector2[] ParseVector2List(string list, char seperator = '|') + { + var entries = list.Split(seperator); + var data = new Vector2[entries.Length]; + + for (int i = 0; i < entries.Length; i++) + { + var sides = entries[i].Split(','); + data[i] = new Vector2(Convert.ToInt32(sides[0]), Convert.ToInt32(sides[1])); + } + + return data; + } + + #endregion + + #region Vector3 / Quaternion + + public static Vector2 Rotate(this Vector2 vec, float angleRadians) + { + return AngleToVector(vec.Angle() + angleRadians, vec.Length()); + } + + public static Vector2 RotateTowards(this Vector2 vec, float targetAngleRadians, float maxMoveRadians) + { + float angle = AngleApproach(vec.Angle(), targetAngleRadians, maxMoveRadians); + return AngleToVector(angle, vec.Length()); + } + + public static Vector3 RotateTowards(this Vector3 from, Vector3 target, float maxRotationRadians) + { + var c = Vector3.Cross(from, target); + var alen = from.Length(); + var blen = target.Length(); + var w = (float)Math.Sqrt((alen * alen) * (blen * blen)) + Vector3.Dot(from, target); + var q = new Quaternion(c.X, c.Y, c.Z, w); + + if (q.Length() <= maxRotationRadians) + return target; + + q.Normalize(); + q *= maxRotationRadians; + + return Vector3.Transform(from, q); + } + + public static Vector2 XZ(this Vector3 vector) + { + return new Vector2(vector.X, vector.Z); + } + + + public static Vector3 Approach(this Vector3 v, Vector3 target, float amount) + { + if (amount > (target - v).Length()) + return target; + return v + (target - v).SafeNormalize() * amount; + } + + public static Vector3 SafeNormalize(this Vector3 v) + { + var len = v.Length(); + if (len > 0) + return v / len; + return Vector3.Zero; + } + + #endregion + + #region CSV + + public static int[,] ReadCSVIntGrid(string csv, int width, int height) + { + int[,] data = new int[width, height]; + + for (int x = 0; x < width; x++) + for (int y = 0; y < height; y++) + data[x, y] = -1; + + string[] lines = csv.Split('\n'); + for (int y = 0; y < height && y < lines.Length; y++) + { + string[] line = lines[y].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + for (int x = 0; x < width && x < line.Length; x++) + data[x, y] = Convert.ToInt32(line[x]); + } + + return data; + } + + public static int[] ReadCSVInt(string csv) + { + if (csv == "") + return new int[0]; + + string[] values = csv.Split(','); + int[] ret = new int[values.Length]; + + for (int i = 0; i < values.Length; i++) + ret[i] = Convert.ToInt32(values[i].Trim()); + + return ret; + } + + /// + /// Read positive-integer CSV with some added tricks. + /// Use - to add inclusive range. Ex: 3-6 = 3,4,5,6 + /// Use * to add multiple values. Ex: 4*3 = 4,4,4 + /// + /// + /// + public static int[] ReadCSVIntWithTricks(string csv) + { + if (csv == "") + return new int[0]; + + string[] values = csv.Split(','); + List ret = new List(); + + foreach (var val in values) + { + if (val.IndexOf('-') != -1) + { + var split = val.Split('-'); + int a = Convert.ToInt32(split[0]); + int b = Convert.ToInt32(split[1]); + + for (int i = a; i != b; i += Math.Sign(b - a)) + ret.Add(i); + ret.Add(b); + } + else if (val.IndexOf('*') != -1) + { + var split = val.Split('*'); + int a = Convert.ToInt32(split[0]); + int b = Convert.ToInt32(split[1]); + + for (int i = 0; i < b; i++) + ret.Add(a); + } + else + ret.Add(Convert.ToInt32(val)); + } + + return ret.ToArray(); + } + + public static string[] ReadCSV(string csv) + { + if (csv == "") + return new string[0]; + + string[] values = csv.Split(','); + for (int i = 0; i < values.Length; i++) + values[i] = values[i].Trim(); + + return values; + } + + public static string IntGridToCSV(int[,] data) + { + StringBuilder str = new StringBuilder(); + + List line = new List(); + int newLines = 0; + + for (int y = 0; y < data.GetLength(1); y++) + { + int empties = 0; + + for (int x = 0; x < data.GetLength(0); x++) + { + if (data[x, y] == -1) + empties++; + else + { + for (int i = 0; i < newLines; i++) + str.Append('\n'); + for (int i = 0; i < empties; i++) + line.Add(-1); + empties = newLines = 0; + + line.Add(data[x, y]); + } + } + + if (line.Count > 0) + { + str.Append(string.Join(",", line)); + line.Clear(); + } + + newLines++; + } + + return str.ToString(); + } + + #endregion + + #region Data Parse + + public static bool[,] GetBitData(string data, char rowSep = '\n') + { + int lengthX = 0; + for (int i = 0; i < data.Length; i++) + { + if (data[i] == '1' || data[i] == '0') + lengthX++; + else if (data[i] == rowSep) + break; + } + + int lengthY = data.Count(c => c == '\n') + 1; + + bool[,] bitData = new bool[lengthX, lengthY]; + int x = 0; + int y = 0; + for (int i = 0; i < data.Length; i++) + { + switch (data[i]) + { + case '1': + bitData[x, y] = true; + x++; + break; + + case '0': + bitData[x, y] = false; + x++; + break; + + case '\n': + x = 0; + y++; + break; + + default: + break; + } + } + + return bitData; + } + + public static void CombineBitData(bool[,] combineInto, string data, char rowSep = '\n') + { + int x = 0; + int y = 0; + for (int i = 0; i < data.Length; i++) + { + switch (data[i]) + { + case '1': + combineInto[x, y] = true; + x++; + break; + + case '0': + x++; + break; + + case '\n': + x = 0; + y++; + break; + + default: + break; + } + } + } + + public static void CombineBitData(bool[,] combineInto, bool[,] data) + { + for (int i = 0; i < combineInto.GetLength(0); i++) + for (int j = 0; j < combineInto.GetLength(1); j++) + if (data[i, j]) + combineInto[i, j] = true; + } + + public static int[] ConvertStringArrayToIntArray(string[] strings) + { + int[] ret = new int[strings.Length]; + for (int i = 0; i < strings.Length; i++) + ret[i] = Convert.ToInt32(strings[i]); + return ret; + } + + public static float[] ConvertStringArrayToFloatArray(string[] strings) + { + float[] ret = new float[strings.Length]; + for (int i = 0; i < strings.Length; i++) + ret[i] = Convert.ToSingle(strings[i], CultureInfo.InvariantCulture); + return ret; + } + + #endregion + + #region Save and Load Data + + public static bool FileExists(string filename) + { + return File.Exists(filename); + } + + public static bool SaveFile(T obj, string filename) where T : new() + { + Stream stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); + + try + { + XmlSerializer serializer = new XmlSerializer(typeof(T)); + serializer.Serialize(stream, obj); + stream.Close(); + return true; + } + catch + { + stream.Close(); + return false; + } + } + + public static bool LoadFile(string filename, ref T data) where T : new() + { + Stream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); + + try + { + XmlSerializer serializer = new XmlSerializer(typeof(T)); + T obj = (T)serializer.Deserialize(stream); + stream.Close(); + data = obj; + return true; + } + catch + { + stream.Close(); + return false; + } + } + + #endregion + + #region XML + + public static XmlDocument LoadContentXML(string filename) + { + XmlDocument xml = new XmlDocument(); + xml.Load(TitleContainer.OpenStream(Path.Combine(Engine.Instance.Content.RootDirectory, filename))); + return xml; + } + + public static XmlDocument LoadXML(string filename) + { + XmlDocument xml = new XmlDocument(); + using (var stream = File.OpenRead(filename)) + xml.Load(stream); + return xml; + } + + public static bool ContentXMLExists(string filename) + { + return File.Exists(Path.Combine(Engine.ContentDirectory, filename)); + } + + public static bool XMLExists(string filename) + { + return File.Exists(filename); + } + + #region Attributes + + public static bool HasAttr(this XmlElement xml, string attributeName) + { + return xml.Attributes[attributeName] != null; + } + + public static string Attr(this XmlElement xml, string attributeName) + { +#if DEBUG + if (!xml.HasAttr(attributeName)) + throw new Exception("Element does not contain the attribute \"" + attributeName + "\""); +#endif + return xml.Attributes[attributeName].InnerText; + } + + public static string Attr(this XmlElement xml, string attributeName, string defaultValue) + { + if (!xml.HasAttr(attributeName)) + return defaultValue; + else + return xml.Attributes[attributeName].InnerText; + } + + public static int AttrInt(this XmlElement xml, string attributeName) + { +#if DEBUG + if (!xml.HasAttr(attributeName)) + throw new Exception("Element does not contain the attribute \"" + attributeName + "\""); +#endif + return Convert.ToInt32(xml.Attributes[attributeName].InnerText); + } + + public static int AttrInt(this XmlElement xml, string attributeName, int defaultValue) + { + if (!xml.HasAttr(attributeName)) + return defaultValue; + else + return Convert.ToInt32(xml.Attributes[attributeName].InnerText); + } + + public static float AttrFloat(this XmlElement xml, string attributeName) + { +#if DEBUG + if (!xml.HasAttr(attributeName)) + throw new Exception("Element does not contain the attribute \"" + attributeName + "\""); +#endif + return Convert.ToSingle(xml.Attributes[attributeName].InnerText, CultureInfo.InvariantCulture); + } + + public static float AttrFloat(this XmlElement xml, string attributeName, float defaultValue) + { + if (!xml.HasAttr(attributeName)) + return defaultValue; + else + return Convert.ToSingle(xml.Attributes[attributeName].InnerText, CultureInfo.InvariantCulture); + } + + public static Vector3 AttrVector3(this XmlElement xml, string attributeName) + { + var attr = xml.Attr(attributeName).Split(','); + var x = float.Parse(attr[0].Trim(), CultureInfo.InvariantCulture); + var y = float.Parse(attr[1].Trim(), CultureInfo.InvariantCulture); + var z = float.Parse(attr[2].Trim(), CultureInfo.InvariantCulture); + + return new Vector3(x, y, z); + } + + public static Vector2 AttrVector2(this XmlElement xml, string xAttributeName, string yAttributeName) + { + return new Vector2(xml.AttrFloat(xAttributeName), xml.AttrFloat(yAttributeName)); + } + + public static Vector2 AttrVector2(this XmlElement xml, string xAttributeName, string yAttributeName, Vector2 defaultValue) + { + return new Vector2(xml.AttrFloat(xAttributeName, defaultValue.X), xml.AttrFloat(yAttributeName, defaultValue.Y)); + } + + public static bool AttrBool(this XmlElement xml, string attributeName) + { +#if DEBUG + if (!xml.HasAttr(attributeName)) + throw new Exception("Element does not contain the attribute \"" + attributeName + "\""); +#endif + return Convert.ToBoolean(xml.Attributes[attributeName].InnerText); + } + + public static bool AttrBool(this XmlElement xml, string attributeName, bool defaultValue) + { + if (!xml.HasAttr(attributeName)) + return defaultValue; + else + return AttrBool(xml, attributeName); + } + + public static char AttrChar(this XmlElement xml, string attributeName) + { +#if DEBUG + if (!xml.HasAttr(attributeName)) + throw new Exception("Element does not contain the attribute \"" + attributeName + "\""); +#endif + return Convert.ToChar(xml.Attributes[attributeName].InnerText); + } + + public static char AttrChar(this XmlElement xml, string attributeName, char defaultValue) + { + if (!xml.HasAttr(attributeName)) + return defaultValue; + else + return AttrChar(xml, attributeName); + } + + public static T AttrEnum(this XmlElement xml, string attributeName) where T : struct + { +#if DEBUG + if (!xml.HasAttr(attributeName)) + throw new Exception("Element does not contain the attribute \"" + attributeName + "\""); +#endif + if (Enum.IsDefined(typeof(T), xml.Attributes[attributeName].InnerText)) + return (T)Enum.Parse(typeof(T), xml.Attributes[attributeName].InnerText); + else + throw new Exception("The attribute value cannot be converted to the enum type."); + } + + public static T AttrEnum(this XmlElement xml, string attributeName, T defaultValue) where T : struct + { + if (!xml.HasAttr(attributeName)) + return defaultValue; + else + return xml.AttrEnum(attributeName); + } + + public static Color AttrHexColor(this XmlElement xml, string attributeName) + { +#if DEBUG + if (!xml.HasAttr(attributeName)) + throw new Exception("Element does not contain the attribute \"" + attributeName + "\""); +#endif + return Calc.HexToColor(xml.Attr(attributeName)); + } + + public static Color AttrHexColor(this XmlElement xml, string attributeName, Color defaultValue) + { + if (!xml.HasAttr(attributeName)) + return defaultValue; + else + return AttrHexColor(xml, attributeName); + } + + public static Color AttrHexColor(this XmlElement xml, string attributeName, string defaultValue) + { + if (!xml.HasAttr(attributeName)) + return Calc.HexToColor(defaultValue); + else + return AttrHexColor(xml, attributeName); + } + + public static Vector2 Position(this XmlElement xml) + { + return new Vector2(xml.AttrFloat("x"), xml.AttrFloat("y")); + } + + public static Vector2 Position(this XmlElement xml, Vector2 defaultPosition) + { + return new Vector2(xml.AttrFloat("x", defaultPosition.X), xml.AttrFloat("y", defaultPosition.Y)); + } + + public static int X(this XmlElement xml) + { + return xml.AttrInt("x"); + } + + public static int X(this XmlElement xml, int defaultX) + { + return xml.AttrInt("x", defaultX); + } + + public static int Y(this XmlElement xml) + { + return xml.AttrInt("y"); + } + + public static int Y(this XmlElement xml, int defaultY) + { + return xml.AttrInt("y", defaultY); + } + + public static int Width(this XmlElement xml) + { + return xml.AttrInt("width"); + } + + public static int Width(this XmlElement xml, int defaultWidth) + { + return xml.AttrInt("width", defaultWidth); + } + + public static int Height(this XmlElement xml) + { + return xml.AttrInt("height"); + } + + public static int Height(this XmlElement xml, int defaultHeight) + { + return xml.AttrInt("height", defaultHeight); + } + + public static Rectangle Rect(this XmlElement xml) + { + return new Rectangle(xml.X(), xml.Y(), xml.Width(), xml.Height()); + } + + public static int ID(this XmlElement xml) + { + return xml.AttrInt("id"); + } + + #endregion + + #region Inner Text + + public static int InnerInt(this XmlElement xml) + { + return Convert.ToInt32(xml.InnerText); + } + + public static float InnerFloat(this XmlElement xml) + { + return Convert.ToSingle(xml.InnerText, CultureInfo.InvariantCulture); + } + + public static bool InnerBool(this XmlElement xml) + { + return Convert.ToBoolean(xml.InnerText); + } + + public static T InnerEnum(this XmlElement xml) where T : struct + { + if (Enum.IsDefined(typeof(T), xml.InnerText)) + return (T)Enum.Parse(typeof(T), xml.InnerText); + else + throw new Exception("The attribute value cannot be converted to the enum type."); + } + + public static Color InnerHexColor(this XmlElement xml) + { + return Calc.HexToColor(xml.InnerText); + } + + #endregion + + #region Child Inner Text + + public static bool HasChild(this XmlElement xml, string childName) + { + return xml[childName] != null; + } + + public static string ChildText(this XmlElement xml, string childName) + { +#if DEBUG + if (!xml.HasChild(childName)) + throw new Exception("Cannot find child xml tag with name '" + childName + "'."); +#endif + return xml[childName].InnerText; + } + + public static string ChildText(this XmlElement xml, string childName, string defaultValue) + { + if (xml.HasChild(childName)) + return xml[childName].InnerText; + else + return defaultValue; + } + + public static int ChildInt(this XmlElement xml, string childName) + { +#if DEBUG + if (!xml.HasChild(childName)) + throw new Exception("Cannot find child xml tag with name '" + childName + "'."); +#endif + return xml[childName].InnerInt(); + } + + public static int ChildInt(this XmlElement xml, string childName, int defaultValue) + { + if (xml.HasChild(childName)) + return xml[childName].InnerInt(); + else + return defaultValue; + } + + public static float ChildFloat(this XmlElement xml, string childName) + { +#if DEBUG + if (!xml.HasChild(childName)) + throw new Exception("Cannot find child xml tag with name '" + childName + "'."); +#endif + return xml[childName].InnerFloat(); + } + + public static float ChildFloat(this XmlElement xml, string childName, float defaultValue) + { + if (xml.HasChild(childName)) + return xml[childName].InnerFloat(); + else + return defaultValue; + } + + public static bool ChildBool(this XmlElement xml, string childName) + { +#if DEBUG + if (!xml.HasChild(childName)) + throw new Exception("Cannot find child xml tag with name '" + childName + "'."); +#endif + return xml[childName].InnerBool(); + } + + public static bool ChildBool(this XmlElement xml, string childName, bool defaultValue) + { + if (xml.HasChild(childName)) + return xml[childName].InnerBool(); + else + return defaultValue; + } + + public static T ChildEnum(this XmlElement xml, string childName) where T : struct + { +#if DEBUG + if (!xml.HasChild(childName)) + throw new Exception("Cannot find child xml tag with name '" + childName + "'."); +#endif + if (Enum.IsDefined(typeof(T), xml[childName].InnerText)) + return (T)Enum.Parse(typeof(T), xml[childName].InnerText); + else + throw new Exception("The attribute value cannot be converted to the enum type."); + } + + public static T ChildEnum(this XmlElement xml, string childName, T defaultValue) where T : struct + { + if (xml.HasChild(childName)) + { + if (Enum.IsDefined(typeof(T), xml[childName].InnerText)) + return (T)Enum.Parse(typeof(T), xml[childName].InnerText); + else + throw new Exception("The attribute value cannot be converted to the enum type."); + } + else + return defaultValue; + } + + public static Color ChildHexColor(this XmlElement xml, string childName) + { +#if DEBUG + if (!xml.HasChild(childName)) + throw new Exception("Cannot find child xml tag with name '" + childName + "'."); +#endif + return Calc.HexToColor(xml[childName].InnerText); + } + + public static Color ChildHexColor(this XmlElement xml, string childName, Color defaultValue) + { + if (xml.HasChild(childName)) + return Calc.HexToColor(xml[childName].InnerText); + else + return defaultValue; + } + + public static Color ChildHexColor(this XmlElement xml, string childName, string defaultValue) + { + if (xml.HasChild(childName)) + return Calc.HexToColor(xml[childName].InnerText); + else + return Calc.HexToColor(defaultValue); + } + + public static Vector2 ChildPosition(this XmlElement xml, string childName) + { +#if DEBUG + if (!xml.HasChild(childName)) + throw new Exception("Cannot find child xml tag with name '" + childName + "'."); +#endif + return xml[childName].Position(); + } + + public static Vector2 ChildPosition(this XmlElement xml, string childName, Vector2 defaultValue) + { + if (xml.HasChild(childName)) + return xml[childName].Position(defaultValue); + else + return defaultValue; + } + + #endregion + + #region Ogmo Nodes + + public static Vector2 FirstNode(this XmlElement xml) + { + if (xml["node"] == null) + return Vector2.Zero; + else + return new Vector2((int)xml["node"].AttrFloat("x"), (int)xml["node"].AttrFloat("y")); + } + + public static Vector2? FirstNodeNullable(this XmlElement xml) + { + if (xml["node"] == null) + return null; + else + return new Vector2((int)xml["node"].AttrFloat("x"), (int)xml["node"].AttrFloat("y")); + } + + public static Vector2? FirstNodeNullable(this XmlElement xml, Vector2 offset) + { + if (xml["node"] == null) + return null; + else + return new Vector2((int)xml["node"].AttrFloat("x"), (int)xml["node"].AttrFloat("y")) + offset; + } + + public static Vector2[] Nodes(this XmlElement xml, bool includePosition = false) + { + XmlNodeList nodes = xml.GetElementsByTagName("node"); + if (nodes == null) + return includePosition ? new Vector2[] { xml.Position() } : new Vector2[0]; + + Vector2[] ret; + if (includePosition) + { + ret = new Vector2[nodes.Count + 1]; + ret[0] = xml.Position(); + for (int i = 0; i < nodes.Count; i++) + ret[i + 1] = new Vector2(Convert.ToInt32(nodes[i].Attributes["x"].InnerText), Convert.ToInt32(nodes[i].Attributes["y"].InnerText)); + } + else + { + ret = new Vector2[nodes.Count]; + for (int i = 0; i < nodes.Count; i++) + ret[i] = new Vector2(Convert.ToInt32(nodes[i].Attributes["x"].InnerText), Convert.ToInt32(nodes[i].Attributes["y"].InnerText)); + } + + return ret; + } + + public static Vector2[] Nodes(this XmlElement xml, Vector2 offset, bool includePosition = false) + { + var nodes = Calc.Nodes(xml, includePosition); + + for (int i = 0; i < nodes.Length; i++) + nodes[i] += offset; + + return nodes; + } + + public static Vector2 GetNode(this XmlElement xml, int nodeNum) + { + return xml.Nodes()[nodeNum]; + } + + public static Vector2? GetNodeNullable(this XmlElement xml, int nodeNum) + { + if (xml.Nodes().Length > nodeNum) + return xml.Nodes()[nodeNum]; + else + return null; + } + + #endregion + + #region Add Stuff + + public static void SetAttr(this XmlElement xml, string attributeName, Object setTo) + { + XmlAttribute attr; + + if (xml.HasAttr(attributeName)) + attr = xml.Attributes[attributeName]; + else + { + attr = xml.OwnerDocument.CreateAttribute(attributeName); + xml.Attributes.Append(attr); + } + + attr.Value = setTo.ToString(); + } + + public static void SetChild(this XmlElement xml, string childName, Object setTo) + { + XmlElement ele; + + if (xml.HasChild(childName)) + ele = xml[childName]; + else + { + ele = xml.OwnerDocument.CreateElement(null, childName, xml.NamespaceURI); + xml.AppendChild(ele); + } + + ele.InnerText = setTo.ToString(); + } + + public static XmlElement CreateChild(this XmlDocument doc, string childName) + { + XmlElement ele = doc.CreateElement(null, childName, doc.NamespaceURI); + doc.AppendChild(ele); + return ele; + } + + public static XmlElement CreateChild(this XmlElement xml, string childName) + { + XmlElement ele = xml.OwnerDocument.CreateElement(null, childName, xml.NamespaceURI); + xml.AppendChild(ele); + return ele; + } + + #endregion + + #endregion + + #region Sorting + + public static int SortLeftToRight(Entity a, Entity b) + { + return (int)((a.X - b.X) * 100); + } + + public static int SortRightToLeft(Entity a, Entity b) + { + return (int)((b.X - a.X) * 100); + } + + public static int SortTopToBottom(Entity a, Entity b) + { + return (int)((a.Y - b.Y) * 100); + } + + public static int SortBottomToTop(Entity a, Entity b) + { + return (int)((b.Y - a.Y) * 100); + } + + public static int SortByDepth(Entity a, Entity b) + { + return a.Depth - b.Depth; + } + + public static int SortByDepthReversed(Entity a, Entity b) + { + return b.Depth - a.Depth; + } + + #endregion + + #region Debug + + public static void Log() + { + Debug.WriteLine("Log"); + } + + public static void TimeLog() + { + Debug.WriteLine(Engine.Scene.RawTimeActive); + } + + public static void Log(params object[] obj) + { + foreach (var o in obj) + { + if (o == null) + Debug.WriteLine("null"); + else + Debug.WriteLine(o.ToString()); + } + } + + public static void TimeLog(object obj) + { + Debug.WriteLine(Engine.Scene.RawTimeActive + " : " + obj); + } + + public static void LogEach(IEnumerable collection) + { + foreach (var o in collection) + Debug.WriteLine(o.ToString()); + } + + public static void Dissect(Object obj) + { + Debug.Write(obj.GetType().Name + " { "); + foreach (var v in obj.GetType().GetFields()) + Debug.Write(v.Name + ": " + v.GetValue(obj) + ", "); + Debug.WriteLine(" }"); + } + + private static Stopwatch stopwatch; + + public static void StartTimer() + { + stopwatch = new Stopwatch(); + stopwatch.Start(); + } + + public static void EndTimer() + { + if (stopwatch != null) + { + stopwatch.Stop(); + + string message = "Timer: " + stopwatch.ElapsedTicks + " ticks, or " + TimeSpan.FromTicks(stopwatch.ElapsedTicks).TotalSeconds.ToString("00.0000000") + " seconds"; + Debug.WriteLine(message); +#if DESKTOP && DEBUG + //Commands.Trace(message); +#endif + stopwatch = null; + } + } + + #endregion + + #region Reflection + + public static Delegate GetMethod(Object obj, string method) where T : class + { + var info = obj.GetType().GetMethod(method, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); + if (info == null) + return null; + else + return Delegate.CreateDelegate(typeof(T), obj, method); + } + + #endregion + + public static T At(this T[,] arr, Pnt at) + { + return arr[at.X, at.Y]; + } + + public static string ConvertPath(string path) + { + return path.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar); + } + + public static string ReadNullTerminatedString(this System.IO.BinaryReader stream) + { + string str = ""; + char ch; + while ((int)(ch = stream.ReadChar()) != 0) + str = str + ch; + return str; + } + + public static IEnumerator Do(params IEnumerator[] numerators) + { + if (numerators.Length == 0) + yield break; + else if (numerators.Length == 1) + yield return numerators[0]; + else + { + List routines = new List(); + foreach (var enumerator in numerators) + routines.Add(new Coroutine(enumerator)); + + while (true) + { + bool moving = false; + foreach (var routine in routines) + { + routine.Update(); + if (!routine.Finished) + moving = true; + } + + if (moving) + yield return null; + else + break; + } + } + } + + public static Rectangle ClampTo(this Rectangle rect, Rectangle clamp) + { + if (rect.X < clamp.X) + { + rect.Width -= (clamp.X - rect.X); + rect.X = clamp.X; + } + + if (rect.Y < clamp.Y) + { + rect.Height -= (clamp.Y - rect.Y); + rect.Y = clamp.Y; + } + + if (rect.Right > clamp.Right) + rect.Width = clamp.Right - rect.X; + if (rect.Bottom > clamp.Bottom) + rect.Height = clamp.Bottom - rect.Y; + + return rect; + } + } + + public static class QuaternionExt + { + public static Quaternion Conjugated(this Quaternion q) + { + var c = q; + c.Conjugate(); + return c; + } + + public static Quaternion LookAt(this Quaternion q, Vector3 from, Vector3 to, Vector3 up) + { + return Quaternion.CreateFromRotationMatrix(Matrix.CreateLookAt(from, to, up)); + } + + public static Quaternion LookAt(this Quaternion q, Vector3 direction, Vector3 up) + { + return Quaternion.CreateFromRotationMatrix(Matrix.CreateLookAt(Vector3.Zero, direction, up)); + } + + public static Vector3 Forward(this Quaternion q) + { + return Vector3.Transform(Vector3.Forward, q.Conjugated()); + } + + public static Vector3 Left(this Quaternion q) + { + return Vector3.Transform(Vector3.Left, q.Conjugated()); + } + + public static Vector3 Up(this Quaternion q) + { + return Vector3.Transform(Vector3.Up, q.Conjugated()); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/Camera.cs b/MonocleEngineDemo/Monocle/Util/Camera.cs new file mode 100644 index 0000000..1f627b1 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/Camera.cs @@ -0,0 +1,252 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace Monocle +{ + public class Camera + { + private Matrix matrix = Matrix.Identity; + private Matrix inverse = Matrix.Identity; + private bool changed; + + private Vector2 position = Vector2.Zero; + private Vector2 zoom = Vector2.One; + private Vector2 origin = Vector2.Zero; + private float angle = 0; + + public Viewport Viewport; + + public Camera() + { + Viewport = new Viewport(); + Viewport.Width = Engine.Width; + Viewport.Height = Engine.Height; + UpdateMatrices(); + } + + public Camera(int width, int height) + { + Viewport = new Viewport(); + Viewport.Width = width; + Viewport.Height = height; + UpdateMatrices(); + } + + public override string ToString() + { + return "Camera:\n\tViewport: { " + Viewport.X + ", " + Viewport.Y + ", " + Viewport.Width + ", " + Viewport.Height + + " }\n\tPosition: { " + position.X + ", " + position.Y + + " }\n\tOrigin: { " + origin.X + ", " + origin.Y + + " }\n\tZoom: { " + zoom.X + ", " + zoom.Y + + " }\n\tAngle: " + angle; + } + + private void UpdateMatrices() + { + matrix = Matrix.Identity * + Matrix.CreateTranslation(new Vector3(-new Vector2((int)Math.Floor(position.X), (int)Math.Floor(position.Y)), 0)) * + Matrix.CreateRotationZ(angle) * + Matrix.CreateScale(new Vector3(zoom, 1)) * + Matrix.CreateTranslation(new Vector3(new Vector2((int)Math.Floor(origin.X), (int)Math.Floor(origin.Y)), 0)); + + inverse = Matrix.Invert(matrix); + + changed = false; + } + + public void CopyFrom(Camera other) + { + position = other.position; + origin = other.origin; + angle = other.angle; + zoom = other.zoom; + changed = true; + } + + public Matrix Matrix + { + get + { + if (changed) + UpdateMatrices(); + return matrix; + } + } + + public Matrix Inverse + { + get + { + if (changed) + UpdateMatrices(); + return inverse; + } + } + + public Vector2 Position + { + get { return position; } + set + { + changed = true; + position = value; + } + } + + public Vector2 Origin + { + get { return origin; } + set + { + changed = true; + origin = value; + } + } + + public float X + { + get { return position.X; } + set + { + changed = true; + position.X = value; + } + } + + public float Y + { + get { return position.Y; } + set + { + changed = true; + position.Y = value; + } + } + + public float Zoom + { + get { return zoom.X; } + set + { + changed = true; + zoom.X = zoom.Y = value; + } + } + + public float Angle + { + get { return angle; } + set + { + changed = true; + angle = value; + } + } + + public float Left + { + get + { + if (changed) + UpdateMatrices(); + return Vector2.Transform(Vector2.Zero, Inverse).X; + } + + set + { + if (changed) + UpdateMatrices(); + X = Vector2.Transform(Vector2.UnitX * value, Matrix).X; + } + } + + public float Right + { + get + { + if (changed) + UpdateMatrices(); + return Vector2.Transform(Vector2.UnitX * Viewport.Width, Inverse).X; + } + + set + { + throw new NotImplementedException(); + } + } + + public float Top + { + get + { + if (changed) + UpdateMatrices(); + return Vector2.Transform(Vector2.Zero, Inverse).Y; + } + + set + { + if (changed) + UpdateMatrices(); + Y = Vector2.Transform(Vector2.UnitY * value, Matrix).Y; + } + } + + public float Bottom + { + get + { + if (changed) + UpdateMatrices(); + return Vector2.Transform(Vector2.UnitY * Viewport.Height, Inverse).Y; + } + + set + { + throw new NotImplementedException(); + } + } + + /* + * Utils + */ + + public void CenterOrigin() + { + origin = new Vector2((float)Viewport.Width / 2, (float)Viewport.Height / 2); + changed = true; + } + + public void RoundPosition() + { + position.X = (float)Math.Round(position.X); + position.Y = (float)Math.Round(position.Y); + changed = true; + } + + public Vector2 ScreenToCamera(Vector2 position) + { + return Vector2.Transform(position, Inverse); + } + + public Vector2 CameraToScreen(Vector2 position) + { + return Vector2.Transform(position, Matrix); + } + + public void Approach(Vector2 position, float ease) + { + Position += (position - Position) * ease; + } + + public void Approach(Vector2 position, float ease, float maxDistance) + { + Vector2 move = (position - Position) * ease; + if (move.Length() > maxDistance) + Position += Vector2.Normalize(move) * maxDistance; + else + Position += move; + } + } +} \ No newline at end of file diff --git a/MonocleEngineDemo/Monocle/Util/CheatListener.cs b/MonocleEngineDemo/Monocle/Util/CheatListener.cs new file mode 100644 index 0000000..54e8b00 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/CheatListener.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; + +namespace Monocle +{ + public class CheatListener : Entity + { + public string CurrentInput; + public bool Logging; + + private List>> inputs; + private List> cheats; + private int maxInput; + + public CheatListener() + { + Visible = false; + CurrentInput = ""; + + inputs = new List>>(); + cheats = new List>(); + } + + public override void Update() + { + //Detect input + bool changed = false; + foreach (var input in inputs) + { + if (input.Item2()) + { + CurrentInput += input.Item1; + changed = true; + } + } + + //Handle changes + if (changed) + { + if (CurrentInput.Length > maxInput) + CurrentInput = CurrentInput.Substring(CurrentInput.Length - maxInput); + + if (Logging) + Calc.Log(CurrentInput); + + foreach (var cheat in cheats) + { + if (CurrentInput.Contains(cheat.Item1)) + { + CurrentInput = ""; + if (cheat.Item2 != null) + cheat.Item2(); + cheats.Remove(cheat); + + if (Logging) + Calc.Log("Cheat Activated: " + cheat.Item1); + + break; + } + } + } + } + + public void AddCheat(string code, Action onEntered = null) + { + cheats.Add(new Tuple(code, onEntered)); + maxInput = Math.Max(code.Length, maxInput); + } + + public void AddInput(char id, Func checker) + { + inputs.Add(new Tuple>(id, checker)); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/ChoiceSet.cs b/MonocleEngineDemo/Monocle/Util/ChoiceSet.cs new file mode 100644 index 0000000..403a0ac --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/ChoiceSet.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Monocle +{ + public class ChoiceSet + { + public int TotalWeight { get; private set; } + private Dictionary choices; + + public ChoiceSet() + { + choices = new Dictionary(); + TotalWeight = 0; + } + + /// + /// Sets the weight of a choice + /// + /// + /// + public void Set(T choice, int weight) + { + int oldWeight = 0; + choices.TryGetValue(choice, out oldWeight); + TotalWeight -= oldWeight; + + if (weight <= 0) + { + if (choices.ContainsKey(choice)) + choices.Remove(choice); + } + else + { + TotalWeight += weight; + choices[choice] = weight; + } + } + + /// + /// Sets the weight of a choice, or gets its weight + /// + /// + /// + public int this[T choice] + { + get + { + int weight = 0; + choices.TryGetValue(choice, out weight); + return weight; + } + + set + { + Set(choice, value); + } + } + + /// + /// Sets the chance of a choice. The chance is calculated based on the current state of ChoiceSet, so if + /// other choices are changed later the chance will not be guaranteed to remain the same + /// + /// + /// A chance between 0 and 1.0f + public void Set(T choice, float chance) + { + int oldWeight = 0; + choices.TryGetValue(choice, out oldWeight); + TotalWeight -= oldWeight; + + int weight = (int)Math.Round(TotalWeight / (1f - chance)); + if (weight <= 0 && chance > 0) + weight = 1; + + if (weight <= 0) + { + if (choices.ContainsKey(choice)) + choices.Remove(choice); + } + else + { + TotalWeight += weight; + choices[choice] = weight; + } + } + + /// + /// Sets the chance of many choices. Takes the chance of any of the given choices being picked, not the chance of + /// any individual choice. The chances are calculated based on the current state of ChoiceSet, so if + /// other choices are changed later the chances will not be guaranteed to remain the same + /// + /// + /// A chance between 0 and 1.0f + public void SetMany(float totalChance, params T[] choices) + { + if (choices.Length > 0) + { + float chance = totalChance / choices.Length; + + int oldTotalWeight = 0; + foreach (var c in choices) + { + int oldWeight = 0; + this.choices.TryGetValue(c, out oldWeight); + oldTotalWeight += oldWeight; + } + TotalWeight -= oldTotalWeight; + + int weight = (int)Math.Round((TotalWeight / (1f - totalChance)) / choices.Length); + if (weight <= 0 && totalChance > 0) + weight = 1; + + if (weight <= 0) + { + foreach (var c in choices) + if (this.choices.ContainsKey(c)) + this.choices.Remove(c); + } + else + { + TotalWeight += weight * choices.Length; + foreach (var c in choices) + this.choices[c] = weight; + } + } + } + + /// + /// Chooses a random choice in the set + /// + /// + /// + public T Get(Random random) + { + int at = random.Next(TotalWeight); + + foreach (var kv in choices) + { + if (at < kv.Value) + return kv.Key; + else + at -= kv.Value; + } + + throw new Exception("Random choice error!"); + } + + /// + /// Chooses a random choice in the set, using Calc.Random to choose + /// + /// + public T Get() + { + return Get(Calc.Random); + } + + private struct Choice + { + public T Data; + public int Weight; + + public Choice(T data, int weight) + { + Data = data; + Weight = weight; + } + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/Chooser.cs b/MonocleEngineDemo/Monocle/Util/Chooser.cs new file mode 100644 index 0000000..e554965 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/Chooser.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Monocle +{ + /// + /// Utility class for making weighted random choices from a set. + /// + public class Chooser + { + private List choices; + + public Chooser() + { + choices = new List(); + } + + /// + /// Initialize with a single choice with the given weight. + /// + public Chooser(T firstChoice, float weight) + : this() + { + Add(firstChoice, weight); + } + + /// + /// Initialize with a list of choices, all with a weight of 1. + /// + public Chooser(params T[] choices) + : this() + { + foreach (var choice in choices) + Add(choice, 1); + } + + public int Count + { + get + { + return choices.Count; + } + } + + public T this[int index] + { + get + { + if (index < 0 || index >= Count) + throw new IndexOutOfRangeException(); + + return choices[index].Value; + } + + set + { + if (index < 0 || index >= Count) + throw new IndexOutOfRangeException(); + + choices[index].Value = value; + } + } + + public Chooser Add(T choice, float weight) + { + weight = Math.Max(weight, 0); + choices.Add(new Choice(choice, weight)); + TotalWeight += weight; + return this; + } + + public T Choose() + { + if (TotalWeight <= 0) + return default(T); + else if (choices.Count == 1) + return choices[0].Value; + + var roll = Calc.Random.NextDouble() * TotalWeight; + float check = 0; + + for (int i = 0; i < choices.Count - 1; i++) + { + check += choices[i].Weight; + if (roll < check) + return choices[i].Value; + } + + return choices[choices.Count - 1].Value; + } + + public float TotalWeight + { + get; private set; + } + + public bool CanChoose + { + get + { + return TotalWeight > 0; + } + } + + private class Choice + { + public T Value; + public float Weight; + + public Choice(T value, float weight) + { + Value = value; + Weight = weight; + } + } + + /// + /// Parses a chooser from a string. + /// + /// Choices to parse. Format: "choice0:weight,choice1:weight,..." + /// + public static Chooser FromString(string data) where TT : IConvertible + { + var chooser = new Chooser(); + string[] choices = data.Split(','); + + //If it's just a single choice with no weight, add it and return + if (choices.Length == 1 && choices[0].IndexOf(':') == -1) + { + chooser.Add((TT)Convert.ChangeType(choices[0], typeof(TT)), 1f); + return chooser; + } + + //Parse the individual choices + foreach (var choice in choices) + { + if (choice.IndexOf(':') == -1) + { + //No weight, default to weight of 1 + chooser.Add((TT)Convert.ChangeType(choice, typeof(TT)), 1f); + } + else + { + //Has weight, handle that correctly + var parts = choice.Split(':'); + var key = parts[0].Trim(); + var weight = parts[1].Trim(); + + chooser.Add((TT)Convert.ChangeType(key, typeof(TT)), Convert.ToSingle(weight)); + } + } + + return chooser; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/Commands.cs b/MonocleEngineDemo/Monocle/Util/Commands.cs new file mode 100644 index 0000000..4f3c447 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/Commands.cs @@ -0,0 +1,844 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace Monocle +{ + public class Commands + { + private const float UNDERSCORE_TIME = .5f; + private const float REPEAT_DELAY = .5f; + private const float REPEAT_EVERY = 1 / 30f; + private const float OPACITY = .8f; + + public bool Enabled = true; + public bool Open; + public Action[] FunctionKeyActions { get; private set; } + + private Dictionary commands; + private List sorted; + + private KeyboardState oldState; + private KeyboardState currentState; + private string currentText = ""; + private List drawCommands; + private bool underscore; + private float underscoreCounter; + private List commandHistory; + private int seekIndex = -1; + private int tabIndex = -1; + private string tabSearch; + private float repeatCounter = 0; + private Keys? repeatKey = null; + private bool canOpen; + + public Commands() + { + commandHistory = new List(); + drawCommands = new List(); + commands = new Dictionary(); + sorted = new List(); + FunctionKeyActions = new Action[12]; + + BuildCommandsList(); + } + + public void Log(object obj, Color color) + { + string str = obj.ToString(); + + //Newline splits + if (str.Contains("\n")) + { + var all = str.Split('\n'); + foreach (var line in all) + Log(line, color); + return; + } + + //Split the string if you overlow horizontally + int maxWidth = Engine.Instance.Window.ClientBounds.Width - 40; + while (Draw.DefaultFont.MeasureString(str).X > maxWidth) + { + int split = -1; + for (int i = 0; i < str.Length; i++) + { + if (str[i] == ' ') + { + if (Draw.DefaultFont.MeasureString(str.Substring(0, i)).X <= maxWidth) + split = i; + else + break; + } + } + + if (split == -1) + break; + + drawCommands.Insert(0, new Line(str.Substring(0, split), color)); + str = str.Substring(split + 1); + } + + drawCommands.Insert(0, new Line(str, color)); + + //Don't overflow top of window + int maxCommands = (Engine.Instance.Window.ClientBounds.Height - 100) / 30; + while (drawCommands.Count > maxCommands) + drawCommands.RemoveAt(drawCommands.Count - 1); + } + + public void Log(object obj) + { + Log(obj, Color.White); + } + + #region Updating and Rendering + + internal void UpdateClosed() + { + if (!canOpen) + canOpen = true; + else if (MInput.Keyboard.Pressed(Keys.OemTilde, Keys.Oem8)) + { + Open = true; + currentState = Keyboard.GetState(); + } + + for (int i = 0; i < FunctionKeyActions.Length; i++) + if (MInput.Keyboard.Pressed((Keys)(Keys.F1 + i))) + ExecuteFunctionKeyAction(i); + } + + internal void UpdateOpen() + { + oldState = currentState; + currentState = Keyboard.GetState(); + + underscoreCounter += Engine.DeltaTime; + while (underscoreCounter >= UNDERSCORE_TIME) + { + underscoreCounter -= UNDERSCORE_TIME; + underscore = !underscore; + } + + if (repeatKey.HasValue) + { + if (currentState[repeatKey.Value] == KeyState.Down) + { + repeatCounter += Engine.DeltaTime; + + while (repeatCounter >= REPEAT_DELAY) + { + HandleKey(repeatKey.Value); + repeatCounter -= REPEAT_EVERY; + } + } + else + repeatKey = null; + } + + foreach (Keys key in currentState.GetPressedKeys()) + { + if (oldState[key] == KeyState.Up) + { + HandleKey(key); + break; + } + } + } + + private void HandleKey(Keys key) + { + if (key != Keys.Tab && key != Keys.LeftShift && key != Keys.RightShift && key != Keys.RightAlt && key != Keys.LeftAlt && key != Keys.RightControl && key != Keys.LeftControl) + tabIndex = -1; + + if (key != Keys.OemTilde && key != Keys.Oem8 && key != Keys.Enter && repeatKey != key) + { + repeatKey = key; + repeatCounter = 0; + } + + switch (key) + { + default: + if (key.ToString().Length == 1) + { + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += key.ToString(); + else + currentText += key.ToString().ToLower(); + } + break; + + case (Keys.D1): + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '!'; + else + currentText += '1'; + break; + case (Keys.D2): + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '@'; + else + currentText += '2'; + break; + case (Keys.D3): + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '#'; + else + currentText += '3'; + break; + case (Keys.D4): + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '$'; + else + currentText += '4'; + break; + case (Keys.D5): + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '%'; + else + currentText += '5'; + break; + case (Keys.D6): + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '^'; + else + currentText += '6'; + break; + case (Keys.D7): + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '&'; + else + currentText += '7'; + break; + case (Keys.D8): + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '*'; + else + currentText += '8'; + break; + case (Keys.D9): + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '('; + else + currentText += '9'; + break; + case (Keys.D0): + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += ')'; + else + currentText += '0'; + break; + case (Keys.OemComma): + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '<'; + else + currentText += ','; + break; + case Keys.OemPeriod: + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '>'; + else + currentText += '.'; + break; + case Keys.OemQuestion: + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '?'; + else + currentText += '/'; + break; + case Keys.OemSemicolon: + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += ':'; + else + currentText += ';'; + break; + case Keys.OemQuotes: + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '"'; + else + currentText += '\''; + break; + case Keys.OemBackslash: + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '|'; + else + currentText += '\\'; + break; + case Keys.OemOpenBrackets: + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '{'; + else + currentText += '['; + break; + case Keys.OemCloseBrackets: + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '}'; + else + currentText += ']'; + break; + case Keys.OemMinus: + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '_'; + else + currentText += '-'; + break; + case Keys.OemPlus: + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + currentText += '+'; + else + currentText += '='; + break; + + case Keys.Space: + currentText += " "; + break; + case Keys.Back: + if (currentText.Length > 0) + currentText = currentText.Substring(0, currentText.Length - 1); + break; + case Keys.Delete: + currentText = ""; + break; + + case Keys.Up: + if (seekIndex < commandHistory.Count - 1) + { + seekIndex++; + currentText = string.Join(" ", commandHistory[seekIndex]); + } + break; + case Keys.Down: + if (seekIndex > -1) + { + seekIndex--; + if (seekIndex == -1) + currentText = ""; + else + currentText = string.Join(" ", commandHistory[seekIndex]); + } + break; + + case Keys.Tab: + if (currentState[Keys.LeftShift] == KeyState.Down || currentState[Keys.RightShift] == KeyState.Down) + { + if (tabIndex == -1) + { + tabSearch = currentText; + FindLastTab(); + } + else + { + tabIndex--; + if (tabIndex < 0 || (tabSearch != "" && sorted[tabIndex].IndexOf(tabSearch) != 0)) + FindLastTab(); + } + } + else + { + if (tabIndex == -1) + { + tabSearch = currentText; + FindFirstTab(); + } + else + { + tabIndex++; + if (tabIndex >= sorted.Count || (tabSearch != "" && sorted[tabIndex].IndexOf(tabSearch) != 0)) + FindFirstTab(); + } + } + if (tabIndex != -1) + currentText = sorted[tabIndex]; + break; + + case Keys.F1: + case Keys.F2: + case Keys.F3: + case Keys.F4: + case Keys.F5: + case Keys.F6: + case Keys.F7: + case Keys.F8: + case Keys.F9: + case Keys.F10: + case Keys.F11: + case Keys.F12: + ExecuteFunctionKeyAction((int)(key - Keys.F1)); + break; + + case Keys.Enter: + if (currentText.Length > 0) + EnterCommand(); + break; + + case Keys.Oem8: + case Keys.OemTilde: + Open = canOpen = false; + break; + } + } + + private void EnterCommand() + { + string[] data = currentText.Split(new char[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries); + if (commandHistory.Count == 0 || commandHistory[0] != currentText) + commandHistory.Insert(0, currentText); + drawCommands.Insert(0, new Line(currentText, Color.Aqua)); + currentText = ""; + seekIndex = -1; + + string[] args = new string[data.Length - 1]; + for (int i = 1; i < data.Length; i++) + args[i - 1] = data[i]; + ExecuteCommand(data[0].ToLower(), args); + } + + private void FindFirstTab() + { + for (int i = 0; i < sorted.Count; i++) + { + if (tabSearch == "" || sorted[i].IndexOf(tabSearch) == 0) + { + tabIndex = i; + break; + } + } + } + + private void FindLastTab() + { + for (int i = 0; i < sorted.Count; i++) + if (tabSearch == "" || sorted[i].IndexOf(tabSearch) == 0) + tabIndex = i; + } + + internal void Render() + { + int screenWidth = Engine.ViewWidth; + int screenHeight = Engine.ViewHeight; + + Draw.SpriteBatch.Begin(); + + Draw.Rect(10, screenHeight - 50, screenWidth - 20, 40, Color.Black * OPACITY); + if (underscore) + Draw.SpriteBatch.DrawString(Draw.DefaultFont, ">" + currentText + "_", new Vector2(20, screenHeight - 42), Color.White); + else + Draw.SpriteBatch.DrawString(Draw.DefaultFont, ">" + currentText, new Vector2(20, screenHeight - 42), Color.White); + + if (drawCommands.Count > 0) + { + int height = 10 + (30 * drawCommands.Count); + Draw.Rect(10, screenHeight - height - 60, screenWidth - 20, height, Color.Black * OPACITY); + for (int i = 0; i < drawCommands.Count; i++) + Draw.SpriteBatch.DrawString(Draw.DefaultFont, drawCommands[i].Text, new Vector2(20, screenHeight - 92 - (30 * i)), drawCommands[i].Color); + } + + Draw.SpriteBatch.End(); + } + + #endregion + + #region Execute + + public void ExecuteCommand(string command, string[] args) + { + if (commands.ContainsKey(command)) + commands[command].Action(args); + else + Log("Command '" + command + "' not found! Type 'help' for list of commands", Color.Yellow); + } + + public void ExecuteFunctionKeyAction(int num) + { + if (FunctionKeyActions[num] != null) + FunctionKeyActions[num](); + } + + #endregion + + #region Parse Commands + + private void BuildCommandsList() + { +#if !CONSOLE + //Check Monocle for Commands + foreach (var type in Assembly.GetCallingAssembly().GetTypes()) + foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + ProcessMethod(method); + + //Check the calling assembly for Commands + foreach (var type in Assembly.GetEntryAssembly().GetTypes()) + foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + ProcessMethod(method); + + //Maintain the sorted command list + foreach (var command in commands) + sorted.Add(command.Key); + sorted.Sort(); +#endif + } + + private void ProcessMethod(MethodInfo method) + { + Command attr = null; + { + var attrs = method.GetCustomAttributes(typeof(Command), false); + if (attrs.Length > 0) + attr = attrs[0] as Command; + } + + if (attr != null) + { + if (!method.IsStatic) + throw new Exception(method.DeclaringType.Name + "." + method.Name + " is marked as a command, but is not static"); + else + { + CommandInfo info = new CommandInfo(); + info.Help = attr.Help; + + var parameters = method.GetParameters(); + var defaults = new object[parameters.Length]; + string[] usage = new string[parameters.Length]; + + for (int i = 0; i < parameters.Length; i++) + { + var p = parameters[i]; + usage[i] = p.Name + ":"; + + if (p.ParameterType == typeof(string)) + usage[i] += "string"; + else if (p.ParameterType == typeof(int)) + usage[i] += "int"; + else if (p.ParameterType == typeof(float)) + usage[i] += "float"; + else if (p.ParameterType == typeof(bool)) + usage[i] += "bool"; + else + throw new Exception(method.DeclaringType.Name + "." + method.Name + " is marked as a command, but has an invalid parameter type. Allowed types are: string, int, float, and bool"); + + if (p.DefaultValue == DBNull.Value) + defaults[i] = null; + else if (p.DefaultValue != null) + { + defaults[i] = p.DefaultValue; + if (p.ParameterType == typeof(string)) + usage[i] += "=\"" + p.DefaultValue + "\""; + else + usage[i] += "=" + p.DefaultValue; + } + else + defaults[i] = null; + } + + if (usage.Length == 0) + info.Usage = ""; + else + info.Usage = "[" + string.Join(" ", usage) + "]"; + + info.Action = (args) => + { + if (parameters.Length == 0) + InvokeMethod(method); + else + { + object[] param = (object[])defaults.Clone(); + + for (int i = 0; i < param.Length && i < args.Length; i++) + { + if (parameters[i].ParameterType == typeof(string)) + param[i] = ArgString(args[i]); + else if (parameters[i].ParameterType == typeof(int)) + param[i] = ArgInt(args[i]); + else if (parameters[i].ParameterType == typeof(float)) + param[i] = ArgFloat(args[i]); + else if (parameters[i].ParameterType == typeof(bool)) + param[i] = ArgBool(args[i]); + } + + InvokeMethod(method, param); + } + }; + + commands[attr.Name] = info; + } + } + } + + private void InvokeMethod(MethodInfo method, object[] param = null) + { + try + { + method.Invoke(null, param); + } + catch (Exception e) + { + Engine.Commands.Log(e.InnerException.Message, Color.Yellow); + LogStackTrace(e.InnerException.StackTrace); + } + } + + private void LogStackTrace(string stackTrace) + { + foreach (var call in stackTrace.Split('\n')) + { + string log = call; + + //Remove File Path + { + var from = log.LastIndexOf(" in ") + 4; + var to = log.LastIndexOf('\\') + 1; + if (from != -1 && to != -1) + log = log.Substring(0, from) + log.Substring(to); + } + + //Remove arguments list + { + var from = log.IndexOf('(') + 1; + var to = log.IndexOf(')'); + if (from != -1 && to != -1) + log = log.Substring(0, from) + log.Substring(to); + } + + //Space out the colon line number + var colon = log.LastIndexOf(':'); + if (colon != -1) + log = log.Insert(colon + 1, " ").Insert(colon, " "); + + log = log.TrimStart(); + log = "-> " + log; + + Engine.Commands.Log(log, Color.White); + } + } + + private struct CommandInfo + { + public Action Action; + public string Help; + public string Usage; + } + + #region Parsing Arguments + + private static string ArgString(string arg) + { + if (arg == null) + return ""; + else + return arg; + } + + private static bool ArgBool(string arg) + { + if (arg != null) + return !(arg == "0" || arg.ToLower() == "false" || arg.ToLower() == "f"); + else + return false; + } + + private static int ArgInt(string arg) + { + try + { + return Convert.ToInt32(arg); + } + catch + { + return 0; + } + } + + private static float ArgFloat(string arg) + { + try + { + return Convert.ToSingle(arg); + } + catch + { + return 0; + } + } + + #endregion + + #endregion + + #region Built-In Commands +#if !CONSOLE + [Command("clear", "Clears the terminal")] + public static void Clear() + { + Engine.Commands.drawCommands.Clear(); + } + + [Command("exit", "Exits the game")] + private static void Exit() + { + Engine.Instance.Exit(); + } + + [Command("vsync", "Enables or disables vertical sync")] + private static void Vsync(bool enabled = true) + { + Engine.Graphics.SynchronizeWithVerticalRetrace = enabled; + Engine.Graphics.ApplyChanges(); + Engine.Commands.Log("Vertical Sync " + (enabled ? "Enabled" : "Disabled")); + } + + [Command("fixed", "Enables or disables fixed time step")] + private static void Fixed(bool enabled = true) + { + Engine.Instance.IsFixedTimeStep = enabled; + Engine.Commands.Log("Fixed Time Step " + (enabled ? "Enabled" : "Disabled")); + } + + [Command("framerate", "Sets the target framerate")] + private static void Framerate(float target) + { + Engine.Instance.TargetElapsedTime = TimeSpan.FromSeconds(1.0 / target); + } + + [Command("count", "Logs amount of Entities in the Scene. Pass a tagIndex to count only Entities with that tag")] + private static void Count(int tagIndex = -1) + { + if (Engine.Scene == null) + { + Engine.Commands.Log("Current Scene is null!"); + return; + } + + if (tagIndex < 0) + Engine.Commands.Log(Engine.Scene.Entities.Count.ToString()); + else + Engine.Commands.Log(Engine.Scene.TagLists[tagIndex].Count.ToString()); + } + + [Command("tracker", "Logs all tracked objects in the scene. Set mode to 'e' for just entities, 'c' for just components, or 'cc' for just collidable components")] + private static void Tracker(string mode) + { + if (Engine.Scene == null) + { + Engine.Commands.Log("Current Scene is null!"); + return; + } + + switch (mode) + { + default: + Engine.Commands.Log("-- Entities --"); + Engine.Scene.Tracker.LogEntities(); + Engine.Commands.Log("-- Components --"); + Engine.Scene.Tracker.LogComponents(); + Engine.Commands.Log("-- Collidable Components --"); + Engine.Scene.Tracker.LogCollidableComponents(); + break; + + case "e": + Engine.Scene.Tracker.LogEntities(); + break; + + case "c": + Engine.Scene.Tracker.LogComponents(); + break; + + case "cc": + Engine.Scene.Tracker.LogCollidableComponents(); + break; + } + } + + [Command("pooler", "Logs the pooled Entity counts")] + private static void Pooler() + { + Engine.Pooler.Log(); + } + + [Command("fullscreen", "Switches to fullscreen mode")] + private static void Fullscreen() + { + Engine.SetFullscreen(); + } + + [Command("window", "Switches to window mode")] + private static void Window(int scale = 1) + { + Engine.SetWindowed(Engine.Width * scale, Engine.Height * scale); + } + + [Command("help", "Shows usage help for a given command")] + private static void Help(string command) + { + if (Engine.Commands.sorted.Contains(command)) + { + var c = Engine.Commands.commands[command]; + StringBuilder str = new StringBuilder(); + + //Title + str.Append(":: "); + str.Append(command); + + //Usage + if (!string.IsNullOrEmpty(c.Usage)) + { + str.Append(" "); + str.Append(c.Usage); + } + Engine.Commands.Log(str.ToString()); + + //Help + if (string.IsNullOrEmpty(c.Help)) + Engine.Commands.Log("No help info set"); + else + Engine.Commands.Log(c.Help); + } + else + { + StringBuilder str = new StringBuilder(); + str.Append("Commands list: "); + str.Append(string.Join(", ", Engine.Commands.sorted)); + Engine.Commands.Log(str.ToString()); + Engine.Commands.Log("Type 'help command' for more info on that command!"); + } + } +#endif + #endregion + + private struct Line + { + public string Text; + public Color Color; + + public Line(string text) + { + Text = text; + Color = Color.White; + } + + public Line(string text, Color color) + { + Text = text; + Color = color; + } + } + } + + public class Command : Attribute + { + public string Name; + public string Help; + + public Command(string name, string help) + { + Name = name; + Help = help; + } + } +} + diff --git a/MonocleEngineDemo/Monocle/Util/Draw.cs b/MonocleEngineDemo/Monocle/Util/Draw.cs new file mode 100644 index 0000000..1a29e4e --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/Draw.cs @@ -0,0 +1,389 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace Monocle +{ + public static class Draw + { + /// + /// The currently-rendering Renderer + /// + public static Renderer Renderer { get; internal set; } + + /// + /// All 2D rendering is done through this SpriteBatch instance + /// + public static SpriteBatch SpriteBatch { get; private set; } + + /// + /// The default Monocle font (Consolas 12). Loaded automatically by Monocle at startup + /// + public static SpriteFont DefaultFont { get; private set; } + + /// + /// A subtexture used to draw particle systems. + /// Will be generated at startup, but you can replace this with a subtexture from your Atlas to reduce texture swaps. + /// Should be a 2x2 white pixel + /// + public static MTexture Particle; + + /// + /// A subtexture used to draw rectangles and lines. + /// Will be generated at startup, but you can replace this with a subtexture from your Atlas to reduce texture swaps. + /// Use the top left pixel of your Particle Subtexture if you replace it! + /// Should be a 1x1 white pixel + /// + public static MTexture Pixel; + + private static Rectangle rect; + + internal static void Initialize(GraphicsDevice graphicsDevice) + { + SpriteBatch = new SpriteBatch(graphicsDevice); + DefaultFont = Engine.Instance.Content.Load(@"Monocle\MonocleDefault"); + UseDebugPixelTexture(); + } + + public static void UseDebugPixelTexture() + { + MTexture texture = new MTexture(2, 2, Color.White); + Pixel = new MTexture(texture, 0, 0, 1, 1); + Particle = new MTexture(texture, 0, 0, 2, 2); + } + + public static void Point(Vector2 at, Color color) + { + SpriteBatch.Draw(Pixel.Texture, at, Pixel.ClipRect, color, 0, Vector2.Zero, 1f, SpriteEffects.None, 0); + } + + #region Line + + public static void Line(Vector2 start, Vector2 end, Color color) + { + LineAngle(start, Calc.Angle(start, end), Vector2.Distance(start, end), color); + } + + public static void Line(Vector2 start, Vector2 end, Color color, float thickness) + { + LineAngle(start, Calc.Angle(start, end), Vector2.Distance(start, end), color, thickness); + } + + public static void Line(float x1, float y1, float x2, float y2, Color color) + { + Line(new Vector2(x1, y1), new Vector2(x2, y2), color); + } + + #endregion + + #region Line Angle + + public static void LineAngle(Vector2 start, float angle, float length, Color color) + { + SpriteBatch.Draw(Pixel.Texture, start, Pixel.ClipRect, color, angle, Vector2.Zero, new Vector2(length, 1), SpriteEffects.None, 0); + } + + public static void LineAngle(Vector2 start, float angle, float length, Color color, float thickness) + { + SpriteBatch.Draw(Pixel.Texture, start, Pixel.ClipRect, color, angle, new Vector2(0, .5f), new Vector2(length, thickness), SpriteEffects.None, 0); + } + + public static void LineAngle(float startX, float startY, float angle, float length, Color color) + { + LineAngle(new Vector2(startX, startY), angle, length, color); + } + + #endregion + + #region Circle + + public static void Circle(Vector2 position, float radius, Color color, int resolution) + { + Vector2 last = Vector2.UnitX * radius; + Vector2 lastP = last.Perpendicular(); + for (int i = 1; i <= resolution; i++) + { + Vector2 at = Calc.AngleToVector(i * MathHelper.PiOver2 / resolution, radius); + Vector2 atP = at.Perpendicular(); + + Draw.Line(position + last, position + at, color); + Draw.Line(position - last, position - at, color); + Draw.Line(position + lastP, position + atP, color); + Draw.Line(position - lastP, position - atP, color); + + last = at; + lastP = atP; + } + } + + public static void Circle(float x, float y, float radius, Color color, int resolution) + { + Circle(new Vector2(x, y), radius, color, resolution); + } + + public static void Circle(Vector2 position, float radius, Color color, float thickness, int resolution) + { + Vector2 last = Vector2.UnitX * radius; + Vector2 lastP = last.Perpendicular(); + for (int i = 1; i <= resolution; i++) + { + Vector2 at = Calc.AngleToVector(i * MathHelper.PiOver2 / resolution, radius); + Vector2 atP = at.Perpendicular(); + + Draw.Line(position + last, position + at, color, thickness); + Draw.Line(position - last, position - at, color, thickness); + Draw.Line(position + lastP, position + atP, color, thickness); + Draw.Line(position - lastP, position - atP, color, thickness); + + last = at; + lastP = atP; + } + } + + public static void Circle(float x, float y, float radius, Color color, float thickness, int resolution) + { + Circle(new Vector2(x, y), radius, color, thickness, resolution); + } + + #endregion + + #region Rect + + public static void Rect(float x, float y, float width, float height, Color color) + { + rect.X = (int)x; + rect.Y = (int)y; + rect.Width = (int)width; + rect.Height = (int)height; + SpriteBatch.Draw(Pixel.Texture, rect, Pixel.ClipRect, color); + } + + public static void Rect(Vector2 position, float width, float height, Color color) + { + Rect(position.X, position.Y, width, height, color); + } + + public static void Rect(Rectangle rect, Color color) + { + Draw.rect = rect; + SpriteBatch.Draw(Pixel.Texture, rect, Pixel.ClipRect, color); + } + + public static void Rect(Collider collider, Color color) + { + Rect(collider.AbsoluteLeft, collider.AbsoluteTop, collider.Width, collider.Height, color); + } + + #endregion + + #region Hollow Rect + + public static void HollowRect(float x, float y, float width, float height, Color color) + { + rect.X = (int)x; + rect.Y = (int)y; + rect.Width = (int)width; + rect.Height = 1; + + SpriteBatch.Draw(Pixel.Texture, rect, Pixel.ClipRect, color); + + rect.Y += (int)height - 1; + + SpriteBatch.Draw(Pixel.Texture, rect, Pixel.ClipRect, color); + + rect.Y -= (int)height - 1; + rect.Width = 1; + rect.Height = (int)height; + + SpriteBatch.Draw(Pixel.Texture, rect, Pixel.ClipRect, color); + + rect.X += (int)width - 1; + + SpriteBatch.Draw(Pixel.Texture, rect, Pixel.ClipRect, color); + } + + public static void HollowRect(Vector2 position, float width, float height, Color color) + { + HollowRect(position.X, position.Y, width, height, color); + } + + public static void HollowRect(Rectangle rect, Color color) + { + HollowRect(rect.X, rect.Y, rect.Width, rect.Height, color); + } + + public static void HollowRect(Collider collider, Color color) + { + HollowRect(collider.AbsoluteLeft, collider.AbsoluteTop, collider.Width, collider.Height, color); + } + + #endregion + + #region Text + + public static void Text(SpriteFont font, string text, Vector2 position, Color color) + { + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position), color); + } + + public static void Text(SpriteFont font, string text, Vector2 position, Color color, Vector2 origin, Vector2 scale, float rotation) + { + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position), color, rotation, origin, scale, SpriteEffects.None, 0); + } + + public static void TextJustified(SpriteFont font, string text, Vector2 position, Color color, Vector2 justify) + { + Vector2 origin = font.MeasureString(text); + origin.X *= justify.X; + origin.Y *= justify.Y; + + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position), color, 0, origin, 1, SpriteEffects.None, 0); + } + + public static void TextJustified(SpriteFont font, string text, Vector2 position, Color color, float scale, Vector2 justify) + { + Vector2 origin = font.MeasureString(text); + origin.X *= justify.X; + origin.Y *= justify.Y; + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position), color, 0, origin, scale, SpriteEffects.None, 0); + } + + public static void TextCentered(SpriteFont font, string text, Vector2 position) + { + Text(font, text, position - font.MeasureString(text) * .5f, Color.White); + } + + public static void TextCentered(SpriteFont font, string text, Vector2 position, Color color) + { + Text(font, text, position - font.MeasureString(text) * .5f, color); + } + + public static void TextCentered(SpriteFont font, string text, Vector2 position, Color color, float scale) + { + Text(font, text, position, color, font.MeasureString(text) * .5f, Vector2.One * scale, 0); + } + + public static void TextCentered(SpriteFont font, string text, Vector2 position, Color color, float scale, float rotation) + { + Text(font, text, position, color, font.MeasureString(text) * .5f, Vector2.One * scale, rotation); + } + + public static void OutlineTextCentered(SpriteFont font, string text, Vector2 position, Color color, float scale) + { + Vector2 origin = font.MeasureString(text) / 2; + + for (int i = -1; i < 2; i++) + for (int j = -1; j < 2; j++) + if (i != 0 || j != 0) + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position) + new Vector2(i, j), Color.Black, 0, origin, scale, SpriteEffects.None, 0); + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position), color, 0, origin, scale, SpriteEffects.None, 0); + } + + public static void OutlineTextCentered(SpriteFont font, string text, Vector2 position, Color color, Color outlineColor) + { + Vector2 origin = font.MeasureString(text) / 2; + + for (int i = -1; i < 2; i++) + for (int j = -1; j < 2; j++) + if (i != 0 || j != 0) + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position) + new Vector2(i, j), outlineColor, 0, origin, 1, SpriteEffects.None, 0); + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position), color, 0, origin, 1, SpriteEffects.None, 0); + } + + public static void OutlineTextCentered(SpriteFont font, string text, Vector2 position, Color color, Color outlineColor, float scale) + { + Vector2 origin = font.MeasureString(text) / 2; + + for (int i = -1; i < 2; i++) + for (int j = -1; j < 2; j++) + if (i != 0 || j != 0) + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position) + new Vector2(i, j), outlineColor, 0, origin, scale, SpriteEffects.None, 0); + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position), color, 0, origin, scale, SpriteEffects.None, 0); + } + + public static void OutlineTextJustify(SpriteFont font, string text, Vector2 position, Color color, Color outlineColor, Vector2 justify) + { + Vector2 origin = font.MeasureString(text) * justify; + + for (int i = -1; i < 2; i++) + for (int j = -1; j < 2; j++) + if (i != 0 || j != 0) + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position) + new Vector2(i, j), outlineColor, 0, origin, 1, SpriteEffects.None, 0); + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position), color, 0, origin, 1, SpriteEffects.None, 0); + } + + public static void OutlineTextJustify(SpriteFont font, string text, Vector2 position, Color color, Color outlineColor, Vector2 justify, float scale) + { + Vector2 origin = font.MeasureString(text) * justify; + + for (int i = -1; i < 2; i++) + for (int j = -1; j < 2; j++) + if (i != 0 || j != 0) + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position) + new Vector2(i, j), outlineColor, 0, origin, scale, SpriteEffects.None, 0); + Draw.SpriteBatch.DrawString(font, text, Calc.Floor(position), color, 0, origin, scale, SpriteEffects.None, 0); + } + + #endregion + + #region Weird Textures + + public static void SineTextureH(MTexture tex, Vector2 position, Vector2 origin, Vector2 scale, float rotation, Color color, SpriteEffects effects, float sineCounter, float amplitude = 2, int sliceSize = 2, float sliceAdd = MathHelper.TwoPi / 8) + { + position = Calc.Floor(position); + Rectangle clip = tex.ClipRect; + clip.Width = sliceSize; + + int num = 0; + while (clip.X < tex.ClipRect.X + tex.ClipRect.Width) + { + Vector2 add = new Vector2(sliceSize * num, (float)Math.Round(Math.Sin(sineCounter + sliceAdd * num) * amplitude)); + Draw.SpriteBatch.Draw(tex.Texture, position, clip, color, rotation, origin - add, scale, effects, 0); + + num++; + clip.X += sliceSize; + clip.Width = Math.Min(sliceSize, tex.ClipRect.X + tex.ClipRect.Width - clip.X); + } + } + + public static void SineTextureV(MTexture tex, Vector2 position, Vector2 origin, Vector2 scale, float rotation, Color color, SpriteEffects effects, float sineCounter, float amplitude = 2, int sliceSize = 2, float sliceAdd = MathHelper.TwoPi / 8) + { + position = Calc.Floor(position); + Rectangle clip = tex.ClipRect; + clip.Height = sliceSize; + + int num = 0; + while (clip.Y < tex.ClipRect.Y + tex.ClipRect.Height) + { + Vector2 add = new Vector2((float)Math.Round(Math.Sin(sineCounter + sliceAdd * num) * amplitude), sliceSize * num); + Draw.SpriteBatch.Draw(tex.Texture, position, clip, color, rotation, origin - add, scale, effects, 0); + + num++; + clip.Y += sliceSize; + clip.Height = Math.Min(sliceSize, tex.ClipRect.Y + tex.ClipRect.Height - clip.Y); + } + } + + public static void TextureBannerV(MTexture tex, Vector2 position, Vector2 origin, Vector2 scale, float rotation, Color color, SpriteEffects effects, float sineCounter, float amplitude = 2, int sliceSize = 2, float sliceAdd = MathHelper.TwoPi / 8) + { + position = Calc.Floor(position); + Rectangle clip = tex.ClipRect; + clip.Height = sliceSize; + + int num = 0; + while (clip.Y < tex.ClipRect.Y + tex.ClipRect.Height) + { + float fade = (clip.Y - tex.ClipRect.Y) / (float)tex.ClipRect.Height; + clip.Height = (int)MathHelper.Lerp(sliceSize, 1, fade); + clip.Height = Math.Min(sliceSize, tex.ClipRect.Y + tex.ClipRect.Height - clip.Y); + + Vector2 add = new Vector2((float)Math.Round(Math.Sin(sineCounter + sliceAdd * num) * amplitude * fade), clip.Y - tex.ClipRect.Y); + Draw.SpriteBatch.Draw(tex.Texture, position, clip, color, rotation, origin - add, scale, effects, 0); + + num++; + clip.Y += clip.Height; + } + } + + #endregion + } +} diff --git a/MonocleEngineDemo/Monocle/Util/Ease.cs b/MonocleEngineDemo/Monocle/Util/Ease.cs new file mode 100644 index 0000000..7890fac --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/Ease.cs @@ -0,0 +1,125 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Monocle +{ + public static class Ease + { + public delegate float Easer(float t); + + public static readonly Easer Linear = (float t) => { return t; }; + + public static readonly Easer SineIn = (float t) => { return -(float)Math.Cos(MathHelper.PiOver2 * t) + 1; }; + public static readonly Easer SineOut = (float t) => { return (float)Math.Sin(MathHelper.PiOver2 * t); }; + public static readonly Easer SineInOut = (float t) => { return -(float)Math.Cos(MathHelper.Pi * t) / 2f + .5f; }; + + public static readonly Easer QuadIn = (float t) => { return t * t; }; + public static readonly Easer QuadOut = Invert(QuadIn); + public static readonly Easer QuadInOut = Follow(QuadIn, QuadOut); + + public static readonly Easer CubeIn = (float t) => { return t * t * t; }; + public static readonly Easer CubeOut = Invert(CubeIn); + public static readonly Easer CubeInOut = Follow(CubeIn, CubeOut); + + public static readonly Easer QuintIn = (float t) => { return t * t * t * t * t; }; + public static readonly Easer QuintOut = Invert(QuintIn); + public static readonly Easer QuintInOut = Follow(QuintIn, QuintOut); + + public static readonly Easer ExpoIn = (float t) => { return (float)Math.Pow(2, 10 * (t - 1)); }; + public static readonly Easer ExpoOut = Invert(ExpoIn); + public static readonly Easer ExpoInOut = Follow(ExpoIn, ExpoOut); + + public static readonly Easer BackIn = (float t) => { return t * t * (2.70158f * t - 1.70158f); }; + public static readonly Easer BackOut = Invert(BackIn); + public static readonly Easer BackInOut = Follow(BackIn, BackOut); + + public static readonly Easer BigBackIn = (float t) => { return t * t * (4f * t - 3f); }; + public static readonly Easer BigBackOut = Invert(BigBackIn); + public static readonly Easer BigBackInOut = Follow(BigBackIn, BigBackOut); + + public static readonly Easer ElasticIn = (float t) => + { + var ts = t * t; + var tc = ts * t; + return (33 * tc * ts + -59 * ts * ts + 32 * tc + -5 * ts); + }; + public static readonly Easer ElasticOut = (float t) => + { + var ts = t * t; + var tc = ts * t; + return (33 * tc * ts + -106 * ts * ts + 126 * tc + -67 * ts + 15 * t); + }; + public static readonly Easer ElasticInOut = Follow(ElasticIn, ElasticOut); + + private const float B1 = 1f / 2.75f; + private const float B2 = 2f / 2.75f; + private const float B3 = 1.5f / 2.75f; + private const float B4 = 2.5f / 2.75f; + private const float B5 = 2.25f / 2.75f; + private const float B6 = 2.625f / 2.75f; + + public static readonly Easer BounceIn = (float t) => + { + t = 1 - t; + if (t < B1) + return 1 - 7.5625f * t * t; + if (t < B2) + return 1 - (7.5625f * (t - B3) * (t - B3) + .75f); + if (t < B4) + return 1 - (7.5625f * (t - B5) * (t - B5) + .9375f); + return 1 - (7.5625f * (t - B6) * (t - B6) + .984375f); + }; + + public static readonly Easer BounceOut = (float t) => + { + if (t < B1) + return 7.5625f * t * t; + if (t < B2) + return 7.5625f * (t - B3) * (t - B3) + .75f; + if (t < B4) + return 7.5625f * (t - B5) * (t - B5) + .9375f; + return 7.5625f * (t - B6) * (t - B6) + .984375f; + }; + + public static readonly Easer BounceInOut = (float t) => + { + if (t < .5f) + { + t = 1 - t * 2; + if (t < B1) + return (1 - 7.5625f * t * t) / 2; + if (t < B2) + return (1 - (7.5625f * (t - B3) * (t - B3) + .75f)) / 2; + if (t < B4) + return (1 - (7.5625f * (t - B5) * (t - B5) + .9375f)) / 2; + return (1 - (7.5625f * (t - B6) * (t - B6) + .984375f)) / 2; + } + t = t * 2 - 1; + if (t < B1) + return (7.5625f * t * t) / 2 + .5f; + if (t < B2) + return (7.5625f * (t - B3) * (t - B3) + .75f) / 2 + .5f; + if (t < B4) + return (7.5625f * (t - B5) * (t - B5) + .9375f) / 2 + .5f; + return (7.5625f * (t - B6) * (t - B6) + .984375f) / 2 + .5f; + }; + + public static Easer Invert(Easer easer) + { + return (float t) => { return 1 - easer(1 - t); }; + } + + public static Easer Follow(Easer first, Easer second) + { + return (float t) => { return (t <= 0.5f) ? first(t * 2) / 2 : second(t * 2 - 1) / 2 + 0.5f; }; + } + + public static float UpDown(float eased) + { + if (eased <= .5f) + return eased * 2; + else + return 1 - (eased - .5f) * 2; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/ErrorLog.cs b/MonocleEngineDemo/Monocle/Util/ErrorLog.cs new file mode 100644 index 0000000..6b08566 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/ErrorLog.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Monocle +{ + public static class ErrorLog + { + public const string Filename = "error_log.txt"; + public const string Marker = "=========================================="; + + public static void Write(Exception e) + { + Write(e.ToString()); + } + + public static void Write(string str) + { + StringBuilder s = new StringBuilder(); + + //Get the previous contents + string content = ""; + if (File.Exists(Filename)) + { + TextReader tr = new StreamReader(Filename); + content = tr.ReadToEnd(); + tr.Close(); + + if (!content.Contains(Marker)) + content = ""; + } + + //Header + if (Engine.Instance != null) + s.Append(Engine.Instance.Title); + else + s.Append("Monocle Engine"); + s.AppendLine(" Error Log"); + s.AppendLine(Marker); + s.AppendLine(); + + //Version Number + if (Engine.Instance.Version != null) + { + s.Append("Ver "); + s.AppendLine(Engine.Instance.Version.ToString()); + } + + //Datetime + s.AppendLine(DateTime.Now.ToString()); + + //String + s.AppendLine(str); + + //If the file wasn't empty, preserve the old errors + if (content != "") + { + int at = content.IndexOf(Marker) + Marker.Length; + string after = content.Substring(at); + s.AppendLine(after); + } + + TextWriter tw = new StreamWriter(Filename, false); + tw.Write(s.ToString()); + tw.Close(); + } + + public static void Open() + { + if (File.Exists(Filename)) + System.Diagnostics.Process.Start(Filename); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/MethodHandle.cs b/MonocleEngineDemo/Monocle/Util/MethodHandle.cs new file mode 100644 index 0000000..10c4963 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/MethodHandle.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Monocle +{ + public class MethodHandle where T : Entity + { + private MethodInfo info; + + public MethodHandle(string methodName) + { + info = typeof(T).GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic); + } + + public void Call(T instance) + { + info.Invoke(instance, null); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/PixelFont.cs b/MonocleEngineDemo/Monocle/Util/PixelFont.cs new file mode 100644 index 0000000..b0ec07e --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/PixelFont.cs @@ -0,0 +1,430 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; +using System.Text.RegularExpressions; +using System.IO; + +namespace Monocle +{ + public class PixelFontCharacter + { + public int Character; + public MTexture Texture; + public int XOffset; + public int YOffset; + public int XAdvance; + public Dictionary Kerning = new Dictionary(); + + public PixelFontCharacter(int character, MTexture texture, XmlElement xml) + { + Character = character; + Texture = texture.GetSubtexture(xml.AttrInt("x"), xml.AttrInt("y"), xml.AttrInt("width"), xml.AttrInt("height")); + XOffset = xml.AttrInt("xoffset"); + YOffset = xml.AttrInt("yoffset"); + XAdvance = xml.AttrInt("xadvance"); + } + } + + public class PixelFontSize + { + public List Textures; + public Dictionary Characters; + public int LineHeight; + public float Size; + public bool Outline; + + private StringBuilder temp = new StringBuilder(); + + public string AutoNewline(string text, int width) + { + if (string.IsNullOrEmpty(text)) + return text; + + temp.Clear(); + + var words = Regex.Split(text, @"(\s)"); + var lineWidth = 0f; + + foreach (var word in words) + { + var wordWidth = Measure(word).X; + if (wordWidth + lineWidth > width) + { + temp.Append('\n'); + lineWidth = 0; + + if (word.Equals(" ")) + continue; + } + + // this word is longer than the max-width, split where ever we can + if (wordWidth > width) + { + int i = 1, start = 0; + for (; i < word.Length; i++) + if (i - start > 1 && Measure(word.Substring(start, i - start - 1)).X > width) + { + temp.Append(word.Substring(start, i - start - 1)); + temp.Append('\n'); + start = i - 1; + } + + + var remaining = word.Substring(start, word.Length - start); + temp.Append(remaining); + lineWidth += Measure(remaining).X; + } + // normal word, add it + else + { + lineWidth += wordWidth; + temp.Append(word); + } + } + + return temp.ToString(); + } + + public PixelFontCharacter Get(int id) + { + PixelFontCharacter val = null; + if (Characters.TryGetValue(id, out val)) + return val; + return null; + } + + public Vector2 Measure(char text) + { + PixelFontCharacter c = null; + if (Characters.TryGetValue(text, out c)) + return new Vector2(c.XAdvance, LineHeight); + return Vector2.Zero; + } + + public Vector2 Measure(string text) + { + if (string.IsNullOrEmpty(text)) + return Vector2.Zero; + + var size = new Vector2(0, LineHeight); + var currentLineWidth = 0f; + + for (var i = 0; i < text.Length; i++) + { + if (text[i] == '\n') + { + size.Y += LineHeight; + if (currentLineWidth > size.X) + size.X = currentLineWidth; + currentLineWidth = 0f; + } + else + { + PixelFontCharacter c = null; + if (Characters.TryGetValue(text[i], out c)) + { + currentLineWidth += c.XAdvance; + + int kerning; + if (i < text.Length - 1 && c.Kerning.TryGetValue(text[i + 1], out kerning)) + currentLineWidth += kerning; + } + } + } + + if (currentLineWidth > size.X) + size.X = currentLineWidth; + + return size; + } + + public float WidthToNextLine(string text, int start) + { + if (string.IsNullOrEmpty(text)) + return 0; + + var currentLineWidth = 0f; + + for (int i = start, j = text.Length; i < j; i++) + { + if (text[i] == '\n') + break; + + PixelFontCharacter c = null; + if (Characters.TryGetValue(text[i], out c)) + { + currentLineWidth += c.XAdvance; + + int kerning; + if (i < j - 1 && c.Kerning.TryGetValue(text[i + 1], out kerning)) + currentLineWidth += kerning; + } + } + + return currentLineWidth; + } + + public float HeightOf(string text) + { + if (string.IsNullOrEmpty(text)) + return 0; + + int lines = 1; + if (text.IndexOf('\n') >= 0) + for (int i = 0; i < text.Length; i++) + if (text[i] == '\n') + lines++; + return lines * LineHeight; + } + + public void Draw(char character, Vector2 position, Vector2 justify, Vector2 scale, Color color) + { + if (char.IsWhiteSpace(character)) + return; + + PixelFontCharacter c = null; + if (Characters.TryGetValue(character, out c)) + { + var measure = Measure(character); + var justified = new Vector2(measure.X * justify.X, measure.Y * justify.Y); + var pos = position + (new Vector2(c.XOffset, c.YOffset) - justified) * scale; + c.Texture.Draw(Calc.Floor(pos), Vector2.Zero, color, scale); + } + } + + public void Draw(string text, Vector2 position, Vector2 justify, Vector2 scale, Color color, float edgeDepth, Color edgeColor, float stroke, Color strokeColor) + { + if (string.IsNullOrEmpty(text)) + return; + + var offset = Vector2.Zero; + var lineWidth = (justify.X != 0 ? WidthToNextLine(text, 0) : 0); + var justified = new Vector2(lineWidth * justify.X, HeightOf(text) * justify.Y); + + for (int i = 0; i < text.Length; i++) + { + if (text[i] == '\n') + { + offset.X = 0; + offset.Y += LineHeight; + if (justify.X != 0) + justified.X = WidthToNextLine(text, i + 1) * justify.X; + continue; + } + + PixelFontCharacter c = null; + if (Characters.TryGetValue(text[i], out c)) + { + var pos = (position + (offset + new Vector2(c.XOffset, c.YOffset) - justified) * scale); + + // draw stroke + if (stroke > 0 && !Outline) + { + if (edgeDepth > 0) + { + c.Texture.Draw(pos + new Vector2(0, -stroke), Vector2.Zero, strokeColor, scale); + for (var j = -stroke; j < edgeDepth + stroke; j += stroke) + { + c.Texture.Draw(pos + new Vector2(-stroke, j), Vector2.Zero, strokeColor, scale); + c.Texture.Draw(pos + new Vector2(stroke, j), Vector2.Zero, strokeColor, scale); + } + c.Texture.Draw(pos + new Vector2(-stroke, edgeDepth + stroke), Vector2.Zero, strokeColor, scale); + c.Texture.Draw(pos + new Vector2(0, edgeDepth + stroke), Vector2.Zero, strokeColor, scale); + c.Texture.Draw(pos + new Vector2(stroke, edgeDepth + stroke), Vector2.Zero, strokeColor, scale); + } + else + { + c.Texture.Draw(pos + new Vector2(-1, -1) * stroke, Vector2.Zero, strokeColor, scale); + c.Texture.Draw(pos + new Vector2(0, -1) * stroke, Vector2.Zero, strokeColor, scale); + c.Texture.Draw(pos + new Vector2(1, -1) * stroke, Vector2.Zero, strokeColor, scale); + c.Texture.Draw(pos + new Vector2(-1, 0) * stroke, Vector2.Zero, strokeColor, scale); + c.Texture.Draw(pos + new Vector2(1, 0) * stroke, Vector2.Zero, strokeColor, scale); + c.Texture.Draw(pos + new Vector2(-1, 1) * stroke, Vector2.Zero, strokeColor, scale); + c.Texture.Draw(pos + new Vector2(0, 1) * stroke, Vector2.Zero, strokeColor, scale); + c.Texture.Draw(pos + new Vector2(1, 1) * stroke, Vector2.Zero, strokeColor, scale); + } + } + + // draw edge + if (edgeDepth > 0) + c.Texture.Draw(pos + Vector2.UnitY * edgeDepth, Vector2.Zero, edgeColor, scale); + + // draw normal character + c.Texture.Draw(pos, Vector2.Zero, color, scale); + + offset.X += c.XAdvance; + + int kerning; + if (i < text.Length - 1 && c.Kerning.TryGetValue(text[i + 1], out kerning)) + offset.X += kerning; + } + } + + } + + public void Draw(string text, Vector2 position, Color color) + { + Draw(text, position, Vector2.Zero, Vector2.One, color, 0, Color.Transparent, 0, Color.Transparent); + } + + public void Draw(string text, Vector2 position, Vector2 justify, Vector2 scale, Color color) + { + Draw(text, position, justify, scale, color, 0, Color.Transparent, 0, Color.Transparent); + } + + public void DrawOutline(string text, Vector2 position, Vector2 justify, Vector2 scale, Color color, float stroke, Color strokeColor) + { + Draw(text, position, justify, scale, color, 0f, Color.Transparent, stroke, strokeColor); + } + + public void DrawEdgeOutline(string text, Vector2 position, Vector2 justify, Vector2 scale, Color color, float edgeDepth, Color edgeColor, float stroke = 0f, Color strokeColor = default(Color)) + { + Draw(text, position, justify, scale, color, edgeDepth, edgeColor, stroke, strokeColor); + } + } + + public class PixelFont + { + public string Face; + public List Sizes = new List(); + public List Textures; + + public PixelFont(string face) + { + Face = face; + } + + public PixelFontSize AddFontSize(string path, Atlas atlas = null, bool outline = false) + { + var data = Calc.LoadXML(path)["font"]; + return AddFontSize(path, data, atlas, outline); + } + + public PixelFontSize AddFontSize(string path, XmlElement data, Atlas atlas = null, bool outline = false) + { + // check if size already exists + var size = data["info"].AttrFloat("size"); + foreach (var fs in Sizes) + if (fs.Size == size) + return fs; + + // get textures + Textures = new List(); + var pages = data["pages"]; + foreach (XmlElement page in pages) + { + var file = page.Attr("file"); + var atlasPath = Path.GetFileNameWithoutExtension(file); + + if (atlas != null && atlas.Has(atlasPath)) + { + Textures.Add(atlas[atlasPath]); + } + else + { + var dir = Path.GetDirectoryName(path); + dir = dir.Substring(Engine.ContentDirectory.Length + 1); + Textures.Add(MTexture.FromFile(Path.Combine(dir, file))); + } + } + + // create font size + var fontSize = new PixelFontSize() + { + Textures = Textures, + Characters = new Dictionary(), + LineHeight = data["common"].AttrInt("lineHeight"), + Size = size, + Outline = outline + }; + + // get characters + foreach (XmlElement character in data["chars"]) + { + int id = character.AttrInt("id"); + int page = character.AttrInt("page", 0); + fontSize.Characters.Add(id, new PixelFontCharacter(id, Textures[page], character)); + } + + // get kerning + if (data["kernings"] != null) + foreach (XmlElement kerning in data["kernings"]) + { + var from = kerning.AttrInt("first"); + var to = kerning.AttrInt("second"); + var push = kerning.AttrInt("amount"); + + PixelFontCharacter c = null; + if (fontSize.Characters.TryGetValue(from, out c)) + c.Kerning.Add(to, push); + } + + // add font size + Sizes.Add(fontSize); + Sizes.Sort((a, b) => { return Math.Sign(a.Size - b.Size); }); + + return fontSize; + } + + public PixelFontSize Get(float size) + { + for (int i = 0, j = Sizes.Count - 1; i < j; i++) + if (Sizes[i].Size >= size) + return Sizes[i]; + return Sizes[Sizes.Count - 1]; + } + + public void Draw(float baseSize, char character, Vector2 position, Vector2 justify, Vector2 scale, Color color) + { + var fontSize = Get(baseSize * Math.Max(scale.X, scale.Y)); + scale *= (baseSize / fontSize.Size); + fontSize.Draw(character, position, justify, scale, color); + } + + public void Draw(float baseSize, string text, Vector2 position, Vector2 justify, Vector2 scale, Color color, float edgeDepth, Color edgeColor, float stroke, Color strokeColor) + { + var fontSize = Get(baseSize * Math.Max(scale.X, scale.Y)); + scale *= (baseSize / fontSize.Size); + fontSize.Draw(text, position, justify, scale, color, edgeDepth, edgeColor, stroke, strokeColor); + } + + public void Draw(float baseSize, string text, Vector2 position, Color color) + { + var scale = Vector2.One; + var fontSize = Get(baseSize * Math.Max(scale.X, scale.Y)); + scale *= (baseSize / fontSize.Size); + fontSize.Draw(text, position, Vector2.Zero, Vector2.One, color, 0, Color.Transparent, 0, Color.Transparent); + } + + public void Draw(float baseSize, string text, Vector2 position, Vector2 justify, Vector2 scale, Color color) + { + var fontSize = Get(baseSize * Math.Max(scale.X, scale.Y)); + scale *= (baseSize / fontSize.Size); + fontSize.Draw(text, position, justify, scale, color, 0, Color.Transparent, 0, Color.Transparent); + } + + public void DrawOutline(float baseSize, string text, Vector2 position, Vector2 justify, Vector2 scale, Color color, float stroke, Color strokeColor) + { + var fontSize = Get(baseSize * Math.Max(scale.X, scale.Y)); + scale *= (baseSize / fontSize.Size); + fontSize.Draw(text, position, justify, scale, color, 0f, Color.Transparent, stroke, strokeColor); + } + + public void DrawEdgeOutline(float baseSize, string text, Vector2 position, Vector2 justify, Vector2 scale, Color color, float edgeDepth, Color edgeColor, float stroke = 0f, Color strokeColor = default(Color)) + { + var fontSize = Get(baseSize * Math.Max(scale.X, scale.Y)); + scale *= (baseSize / fontSize.Size); + fontSize.Draw(text, position, justify, scale, color, edgeDepth, edgeColor, stroke, strokeColor); + } + + public void Dispose() + { + foreach (var tex in Textures) + tex.Dispose(); + Sizes.Clear(); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/Pnt.cs b/MonocleEngineDemo/Monocle/Util/Pnt.cs new file mode 100644 index 0000000..124093c --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/Pnt.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Monocle +{ + public struct Pnt + { + public static readonly Pnt Zero = new Pnt(0, 0); + public static readonly Pnt UnitX = new Pnt(1, 0); + public static readonly Pnt UnitY = new Pnt(0, 1); + public static readonly Pnt One = new Pnt(1, 1); + + public int X; + public int Y; + + public Pnt(int x, int y) + { + X = x; + Y = y; + } + + #region Pnt operators + + public static bool operator ==(Pnt a, Pnt b) + { + return a.X == b.X && a.Y == b.Y; + } + + public static bool operator !=(Pnt a, Pnt b) + { + return a.X != b.X || a.Y != b.Y; + } + + public static Pnt operator +(Pnt a, Pnt b) + { + return new Pnt(a.X + b.X, a.Y + b.Y); + } + + public static Pnt operator -(Pnt a, Pnt b) + { + return new Pnt(a.X - b.X, a.Y - b.Y); + } + + public static Pnt operator *(Pnt a, Pnt b) + { + return new Pnt(a.X * b.X, a.Y * b.Y); + } + + public static Pnt operator /(Pnt a, Pnt b) + { + return new Pnt(a.X / b.X, a.Y / b.Y); + } + + public static Pnt operator %(Pnt a, Pnt b) + { + return new Pnt(a.X % b.X, a.Y % b.Y); + } + + #endregion + + #region int operators + + public static bool operator ==(Pnt a, int b) + { + return a.X == b && a.Y == b; + } + + public static bool operator !=(Pnt a, int b) + { + return a.X != b || a.Y != b; + } + + public static Pnt operator +(Pnt a, int b) + { + return new Pnt(a.X + b, a.Y + b); + } + + public static Pnt operator -(Pnt a, int b) + { + return new Pnt(a.X - b, a.Y - b); + } + + public static Pnt operator *(Pnt a, int b) + { + return new Pnt(a.X * b, a.Y * b); + } + + public static Pnt operator /(Pnt a, int b) + { + return new Pnt(a.X / b, a.Y / b); + } + + public static Pnt operator %(Pnt a, int b) + { + return new Pnt(a.X % b, a.Y % b); + } + + #endregion + + public override bool Equals(object obj) + { + return false; + } + + public override int GetHashCode() + { + return X * 10000 + Y; + } + + public override string ToString() + { + return "{ X: " + X + ", Y: " + Y + " }"; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/Pooler.cs b/MonocleEngineDemo/Monocle/Util/Pooler.cs new file mode 100644 index 0000000..e13fe38 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/Pooler.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Monocle +{ + public class Pooler + { + internal Dictionary> Pools { get; private set; } + + public Pooler() + { + Pools = new Dictionary>(); + + foreach (var type in Assembly.GetEntryAssembly().GetTypes()) + { + if (type.GetCustomAttributes(typeof(Pooled), false).Length > 0) + { + if (!typeof(Entity).IsAssignableFrom(type)) + throw new Exception("Type '" + type.Name + "' cannot be Pooled because it doesn't derive from Entity"); + else if (type.GetConstructor(Type.EmptyTypes) == null) + throw new Exception("Type '" + type.Name + "' cannot be Pooled because it doesn't have a parameterless constructor"); + else + Pools.Add(type, new Queue()); + } + } + } + + public T Create() where T : Entity, new() + { + if (!Pools.ContainsKey(typeof(T))) + return new T(); + + var queue = Pools[typeof(T)]; + if (queue.Count == 0) + return new T(); + else + return queue.Dequeue() as T; + } + + internal void EntityRemoved(Entity entity) + { + var type = entity.GetType(); + if (Pools.ContainsKey(type)) + Pools[type].Enqueue(entity); + } + + public void Log() + { + if (Pools.Count == 0) + Engine.Commands.Log("No Entity types are marked as Pooled!"); + + foreach (var kv in Pools) + { + string output = kv.Key.Name + " : " + kv.Value.Count; + Engine.Commands.Log(output); + } + } + } + + public class Pooled : Attribute + { + + } +} diff --git a/MonocleEngineDemo/Monocle/Util/SaveLoad.cs b/MonocleEngineDemo/Monocle/Util/SaveLoad.cs new file mode 100644 index 0000000..3be9f40 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/SaveLoad.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace Monocle +{ + public static class SaveLoad + { + public enum SerializeModes { Binary, XML }; + + #region Save + + /// + /// Save an object to a file so you can load it later + /// + public static void SerializeToFile(T obj, string filepath, SerializeModes mode) + { + using (var fileStream = new FileStream(filepath, FileMode.Create)) + { + //Serialize + if (mode == SerializeModes.Binary) + { + var bf = new BinaryFormatter(); + bf.Serialize(fileStream, obj); + } + else if (mode == SerializeModes.XML) + { + var xs = new XmlSerializer(typeof(T)); + xs.Serialize(fileStream, obj); + } + } + } + + /// + /// Save an object to a file so you can load it later. + /// Will not crash if the save fails + /// + /// Whether the save succeeded + public static bool SafeSerializeToFile(T obj, string filepath, SerializeModes mode) + { + try + { + SerializeToFile(obj, filepath, mode); + return true; + } + catch + { + return false; + } + } + + #endregion + + #region Load + + /// + /// Load an object that was previously serialized to a file + /// + public static T DeserializeFromFile(string filepath, SerializeModes mode) + { + T data; + using (var fileStream = File.OpenRead(filepath)) + { + + //Deserialize + if (mode == SerializeModes.Binary) + { + var bf = new BinaryFormatter(); + data = (T)bf.Deserialize(fileStream); + } + else + { + var xs = new XmlSerializer(typeof(T)); + data = (T)xs.Deserialize(fileStream); + } + } + + return data; + } + + /// + /// Load an object that was previously serialized to a file + /// If the load fails or the file does not exist, default(T) will be returned + /// + public static T SafeDeserializeFromFile(string filepath, SerializeModes mode, bool debugUnsafe = false) + { + if (File.Exists(filepath)) + { + if (debugUnsafe) + return SaveLoad.DeserializeFromFile(filepath, mode); + else + { + try + { + return SaveLoad.DeserializeFromFile(filepath, mode); + } + catch + { + return default(T); + } + } + } + else + return default(T); + } + + /// + /// Load an object that was previously serialized to a file + /// If the load fails or the file does not exist, default(T) will be returned + /// + /// True if the load fails despite the requested file existing (for example due to corrupted data) + public static T SafeDeserializeFromFile(string filepath, SerializeModes mode, out bool loadError, bool debugUnsafe = false) + { + if (File.Exists(filepath)) + { + if (debugUnsafe) + { + loadError = false; + return SaveLoad.DeserializeFromFile(filepath, mode); + } + else + { + try + { + loadError = false; + return SaveLoad.DeserializeFromFile(filepath, mode); + } + catch + { + loadError = true; + return default(T); + } + } + } + else + { + loadError = false; + return default(T); + } + } + + #endregion + } +} diff --git a/MonocleEngineDemo/Monocle/Util/SimpleCurve.cs b/MonocleEngineDemo/Monocle/Util/SimpleCurve.cs new file mode 100644 index 0000000..f4acf2d --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/SimpleCurve.cs @@ -0,0 +1,78 @@ +using Microsoft.Xna.Framework; + +namespace Monocle +{ + public struct SimpleCurve + { + public Vector2 Begin; + public Vector2 End; + public Vector2 Control; + + public SimpleCurve(Vector2 begin, Vector2 end, Vector2 control) + { + Begin = begin; + End = end; + Control = control; + } + + public void DoubleControl() + { + Vector2 axis = End - Begin; + Vector2 mid = Begin + axis/2; + Vector2 diff = Control - mid; + Control += diff; + } + + public Vector2 GetPoint(float percent) + { + float reverse = 1 - percent; + return (reverse * reverse * Begin) + (2f * reverse * percent * Control) + (percent * percent * End); + } + + public float GetLengthParametric(int resolution) + { + Vector2 last = Begin; + float length = 0; + for (int i = 1; i <= resolution; i++) + { + Vector2 at = GetPoint(i / (float)resolution); + length += (at - last).Length(); + last = at; + } + + return length; + } + + public void Render(Vector2 offset, Color color, int resolution) + { + Vector2 last = offset + Begin; + for (int i = 1; i <= resolution; i++) + { + Vector2 at = offset + GetPoint(i / (float)resolution); + Draw.Line(last, at, color); + last = at; + } + } + + public void Render(Vector2 offset, Color color, int resolution, float thickness) + { + Vector2 last = offset + Begin; + for (int i = 1; i <= resolution; i++) + { + Vector2 at = offset + GetPoint(i / (float)resolution); + Draw.Line(last, at, color, thickness); + last = at; + } + } + + public void Render(Color color, int resolution) + { + Render(Vector2.Zero, color, resolution); + } + + public void Render(Color color, int resolution, float thickness) + { + Render(Vector2.Zero, color, resolution, thickness); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/SpecEntity.cs b/MonocleEngineDemo/Monocle/Util/SpecEntity.cs new file mode 100644 index 0000000..81443d7 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/SpecEntity.cs @@ -0,0 +1,34 @@ +using Microsoft.Xna.Framework; + +namespace Monocle +{ + public class SpecEntity : Entity where T : Scene + { + public T SpecScene { get; private set; } + + public SpecEntity(Vector2 position) + : base(position) + { + + } + + public SpecEntity() + : base() + { + + } + + public override void Added(Scene scene) + { + base.Added(scene); + if (Scene is T) + SpecScene = Scene as T; + } + + public override void Removed(Scene scene) + { + SpecScene = null; + base.Removed(scene); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/Tiler.cs b/MonocleEngineDemo/Monocle/Util/Tiler.cs new file mode 100644 index 0000000..e54ef1f --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/Tiler.cs @@ -0,0 +1,275 @@ +using System; +using System.Xml; + +namespace Monocle +{ + public static class Tiler + { + public enum EdgeBehavior { True, False, Wrap }; + + public static int[,] Tile(bool[,] bits, Func tileDecider, Action tileOutput, int tileWidth, int tileHeight, EdgeBehavior edges) + { + int boundsX = bits.GetLength(0); + int boundsY = bits.GetLength(1); + int[,] tiles = new int[boundsX, boundsY]; + + for (TileX = 0; TileX < boundsX; TileX++) + { + for (TileY = 0; TileY < boundsY; TileY++) + { + if (bits[TileX, TileY]) + { + switch (edges) + { + case EdgeBehavior.True: + Left = TileX == 0 ? true : bits[TileX - 1, TileY]; + Right = TileX == boundsX - 1 ? true : bits[TileX + 1, TileY]; + Up = TileY == 0 ? true : bits[TileX, TileY - 1]; + Down = TileY == boundsY - 1 ? true : bits[TileX, TileY + 1]; + + UpLeft = (TileX == 0 || TileY == 0) ? true : bits[TileX - 1, TileY - 1]; + UpRight = (TileX == boundsX - 1 || TileY == 0) ? true : bits[TileX + 1, TileY - 1]; + DownLeft = (TileX == 0 || TileY == boundsY - 1) ? true : bits[TileX - 1, TileY + 1]; + DownRight = (TileX == boundsX - 1 || TileY == boundsY - 1) ? true : bits[TileX + 1, TileY + 1]; + break; + + case EdgeBehavior.False: + Left = TileX == 0 ? false : bits[TileX - 1, TileY]; + Right = TileX == boundsX - 1 ? false : bits[TileX + 1, TileY]; + Up = TileY == 0 ? false : bits[TileX, TileY - 1]; + Down = TileY == boundsY - 1 ? false : bits[TileX, TileY + 1]; + + UpLeft = (TileX == 0 || TileY == 0) ? false : bits[TileX - 1, TileY - 1]; + UpRight = (TileX == boundsX - 1 || TileY == 0) ? false : bits[TileX + 1, TileY - 1]; + DownLeft = (TileX == 0 || TileY == boundsY - 1) ? false : bits[TileX - 1, TileY + 1]; + DownRight = (TileX == boundsX - 1 || TileY == boundsY - 1) ? false : bits[TileX + 1, TileY + 1]; + break; + + case EdgeBehavior.Wrap: + Left = bits[(TileX + boundsX - 1) % boundsX, TileY]; + Right = bits[(TileX + 1) % boundsX, TileY]; + Up = bits[TileX, (TileY + boundsY - 1) % boundsY]; + Down = bits[TileX, (TileY + 1) % boundsY]; + + UpLeft = bits[(TileX + boundsX - 1) % boundsX, (TileY + boundsY - 1) % boundsY]; + UpRight = bits[(TileX + 1) % boundsX, (TileY + boundsY - 1) % boundsY]; + DownLeft = bits[(TileX + boundsX - 1) % boundsX, (TileY + 1) % boundsY]; + DownRight = bits[(TileX + 1) % boundsX, (TileY + 1) % boundsY]; + break; + } + + int tile = tileDecider(); + tileOutput(tile); + tiles[TileX, TileY] = tile; + } + } + } + + return tiles; + } + + /* + The "mask" will also be used for tile checks! + A tile is solid if bits[x, y] OR mask[x, y] is solid + */ + public static int[,] Tile(bool[,] bits, bool[,] mask, Func tileDecider, Action tileOutput, int tileWidth, int tileHeight, EdgeBehavior edges) + { + int boundsX = bits.GetLength(0); + int boundsY = bits.GetLength(1); + int[,] tiles = new int[boundsX, boundsY]; + + for (TileX = 0; TileX < boundsX; TileX++) + { + for (TileY = 0; TileY < boundsY; TileY++) + { + if (bits[TileX, TileY]) + { + switch (edges) + { + case EdgeBehavior.True: + Left = TileX == 0 ? true : bits[TileX - 1, TileY] || mask[TileX - 1, TileY]; + Right = TileX == boundsX - 1 ? true : bits[TileX + 1, TileY] || mask[TileX + 1, TileY]; + Up = TileY == 0 ? true : bits[TileX, TileY - 1] || mask[TileX, TileY - 1]; + Down = TileY == boundsY - 1 ? true : bits[TileX, TileY + 1] || mask[TileX, TileY + 1]; + + UpLeft = (TileX == 0 || TileY == 0) ? true : bits[TileX - 1, TileY - 1] || mask[TileX - 1, TileY - 1]; + UpRight = (TileX == boundsX - 1 || TileY == 0) ? true : bits[TileX + 1, TileY - 1] || mask[TileX + 1, TileY - 1]; + DownLeft = (TileX == 0 || TileY == boundsY - 1) ? true : bits[TileX - 1, TileY + 1] || mask[TileX - 1, TileY + 1]; + DownRight = (TileX == boundsX - 1 || TileY == boundsY - 1) ? true : bits[TileX + 1, TileY + 1] || mask[TileX + 1, TileY + 1]; + break; + + case EdgeBehavior.False: + Left = TileX == 0 ? false : bits[TileX - 1, TileY] || mask[TileX - 1, TileY]; + Right = TileX == boundsX - 1 ? false : bits[TileX + 1, TileY] || mask[TileX + 1, TileY]; + Up = TileY == 0 ? false : bits[TileX, TileY - 1] || mask[TileX, TileY - 1]; + Down = TileY == boundsY - 1 ? false : bits[TileX, TileY + 1] || mask[TileX, TileY + 1]; + + UpLeft = (TileX == 0 || TileY == 0) ? false : bits[TileX - 1, TileY - 1] || mask[TileX - 1, TileY - 1]; + UpRight = (TileX == boundsX - 1 || TileY == 0) ? false : bits[TileX + 1, TileY - 1] || mask[TileX + 1, TileY - 1]; + DownLeft = (TileX == 0 || TileY == boundsY - 1) ? false : bits[TileX - 1, TileY + 1] || mask[TileX - 1, TileY + 1]; + DownRight = (TileX == boundsX - 1 || TileY == boundsY - 1) ? false : bits[TileX + 1, TileY + 1] || mask[TileX + 1, TileY + 1]; + break; + + case EdgeBehavior.Wrap: + Left = bits[(TileX + boundsX - 1) % boundsX, TileY] || mask[(TileX + boundsX - 1) % boundsX, TileY]; + Right = bits[(TileX + 1) % boundsX, TileY] || mask[(TileX + 1) % boundsX, TileY]; + Up = bits[TileX, (TileY + boundsY - 1) % boundsY] || mask[TileX, (TileY + boundsY - 1) % boundsY]; + Down = bits[TileX, (TileY + 1) % boundsY] || mask[TileX, (TileY + 1) % boundsY]; + + UpLeft = bits[(TileX + boundsX - 1) % boundsX, (TileY + boundsY - 1) % boundsY] || mask[(TileX + boundsX - 1) % boundsX, (TileY + boundsY - 1) % boundsY]; + UpRight = bits[(TileX + 1) % boundsX, (TileY + boundsY - 1) % boundsY] || mask[(TileX + 1) % boundsX, (TileY + boundsY - 1) % boundsY]; + DownLeft = bits[(TileX + boundsX - 1) % boundsX, (TileY + 1) % boundsY] || mask[(TileX + boundsX - 1) % boundsX, (TileY + 1) % boundsY]; + DownRight = bits[(TileX + 1) % boundsX, (TileY + 1) % boundsY] || mask[(TileX + 1) % boundsX, (TileY + 1) % boundsY]; + break; + } + + int tile = tileDecider(); + tileOutput(tile); + tiles[TileX, TileY] = tile; + } + } + } + + return tiles; + } + + public static int[,] Tile(bool[,] bits, AutotileData autotileData, Action tileOutput, int tileWidth, int tileHeight, EdgeBehavior edges) + { + return Tile(bits, autotileData.TileHandler, tileOutput, tileWidth, tileHeight, edges); + } + + public static int[,] Tile(bool[,] bits, bool[,] mask, AutotileData autotileData, Action tileOutput, int tileWidth, int tileHeight, EdgeBehavior edges) + { + return Tile(bits, mask, autotileData.TileHandler, tileOutput, tileWidth, tileHeight, edges); + } + + public static int TileX { get; private set; } + public static int TileY { get; private set; } + public static bool Left { get; private set; } + public static bool Right { get; private set; } + public static bool Up { get; private set; } + public static bool Down { get; private set; } + public static bool UpLeft { get; private set; } + public static bool UpRight { get; private set; } + public static bool DownLeft { get; private set; } + public static bool DownRight { get; private set; } + } + + public class AutotileData + { + public int[] Center; + public int[] Single; + public int[] SingleHorizontalLeft; + public int[] SingleHorizontalCenter; + public int[] SingleHorizontalRight; + public int[] SingleVerticalTop; + public int[] SingleVerticalCenter; + public int[] SingleVerticalBottom; + public int[] Top; + public int[] Bottom; + public int[] Left; + public int[] Right; + public int[] TopLeft; + public int[] TopRight; + public int[] BottomLeft; + public int[] BottomRight; + public int[] InsideTopLeft; + public int[] InsideTopRight; + public int[] InsideBottomLeft; + public int[] InsideBottomRight; + + public AutotileData(XmlElement xml) + { + Center = Calc.ReadCSVInt(xml.ChildText("Center", "")); + Single = Calc.ReadCSVInt(xml.ChildText("Single", "")); + + SingleHorizontalLeft = Calc.ReadCSVInt(xml.ChildText("SingleHorizontalLeft", "")); + SingleHorizontalCenter = Calc.ReadCSVInt(xml.ChildText("SingleHorizontalCenter", "")); + SingleHorizontalRight = Calc.ReadCSVInt(xml.ChildText("SingleHorizontalRight", "")); + + SingleVerticalTop = Calc.ReadCSVInt(xml.ChildText("SingleVerticalTop", "")); + SingleVerticalCenter = Calc.ReadCSVInt(xml.ChildText("SingleVerticalCenter", "")); + SingleVerticalBottom = Calc.ReadCSVInt(xml.ChildText("SingleVerticalBottom", "")); + + Top = Calc.ReadCSVInt(xml.ChildText("Top", "")); + Bottom = Calc.ReadCSVInt(xml.ChildText("Bottom", "")); + Left = Calc.ReadCSVInt(xml.ChildText("Left", "")); + Right = Calc.ReadCSVInt(xml.ChildText("Right", "")); + + TopLeft = Calc.ReadCSVInt(xml.ChildText("TopLeft", "")); + TopRight = Calc.ReadCSVInt(xml.ChildText("TopRight", "")); + BottomLeft = Calc.ReadCSVInt(xml.ChildText("BottomLeft", "")); + BottomRight = Calc.ReadCSVInt(xml.ChildText("BottomRight", "")); + + InsideTopLeft = Calc.ReadCSVInt(xml.ChildText("InsideTopLeft", "")); + InsideTopRight = Calc.ReadCSVInt(xml.ChildText("InsideTopRight", "")); + InsideBottomLeft = Calc.ReadCSVInt(xml.ChildText("InsideBottomLeft", "")); + InsideBottomRight = Calc.ReadCSVInt(xml.ChildText("InsideBottomRight", "")); + } + + public int TileHandler() + { + if (Tiler.Left && Tiler.Right && Tiler.Up && Tiler.Down && Tiler.UpLeft && Tiler.UpRight && Tiler.DownLeft && Tiler.DownRight) + return GetTileID(Center); + + else if (!Tiler.Up && !Tiler.Down) + { + if (Tiler.Left && Tiler.Right) + return GetTileID(SingleHorizontalCenter); + else if (!Tiler.Left && !Tiler.Right) + return GetTileID(Single); + else if (Tiler.Left) + return GetTileID(SingleHorizontalRight); + else + return GetTileID(SingleHorizontalLeft); + } + else if (!Tiler.Left && !Tiler.Right) + { + if (Tiler.Up && Tiler.Down) + return GetTileID(SingleVerticalCenter); + else if (Tiler.Down) + return GetTileID(SingleVerticalTop); + else + return GetTileID(SingleVerticalBottom); + } + + else if (Tiler.Up && Tiler.Down && Tiler.Left && !Tiler.Right) + return GetTileID(Right); + else if (Tiler.Up && Tiler.Down && !Tiler.Left && Tiler.Right) + return GetTileID(Left); + + else if (Tiler.Up && !Tiler.Left && Tiler.Right && !Tiler.Down) + return GetTileID(BottomLeft); + else if (Tiler.Up && Tiler.Left && !Tiler.Right && !Tiler.Down) + return GetTileID(BottomRight); + else if (Tiler.Down && Tiler.Right && !Tiler.Left && !Tiler.Up) + return GetTileID(TopLeft); + else if (Tiler.Down && !Tiler.Right && Tiler.Left && !Tiler.Up) + return GetTileID(TopRight); + + else if (Tiler.Up && Tiler.Down && !Tiler.DownRight && Tiler.DownLeft) + return GetTileID(InsideTopLeft); + else if (Tiler.Up && Tiler.Down && Tiler.DownRight && !Tiler.DownLeft) + return GetTileID(InsideTopRight); + else if (Tiler.Up && Tiler.Down && Tiler.UpLeft && !Tiler.UpRight) + return GetTileID(InsideBottomLeft); + else if (Tiler.Up && Tiler.Down && !Tiler.UpLeft && Tiler.UpRight) + return GetTileID(InsideBottomRight); + + else if (!Tiler.Down) + return GetTileID(Bottom); + else + return GetTileID(Top); + } + + private int GetTileID(int[] choices) + { + if (choices.Length == 0) + return -1; + else if (choices.Length == 1) + return choices[0]; + else + return Calc.Random.Choose(choices); + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/Tracker.cs b/MonocleEngineDemo/Monocle/Util/Tracker.cs new file mode 100644 index 0000000..2c1be77 --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/Tracker.cs @@ -0,0 +1,398 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Monocle +{ + public class Tracker + { + #region Static + + public static Dictionary> TrackedEntityTypes { get; private set; } + public static Dictionary> TrackedComponentTypes { get; private set; } + public static Dictionary> TrackedCollidableComponentTypes { get; private set; } + public static HashSet StoredEntityTypes { get; private set; } + public static HashSet StoredComponentTypes { get; private set; } + public static HashSet StoredCollidableComponentTypes { get; private set; } + + public static void Initialize() + { + TrackedEntityTypes = new Dictionary>(); + TrackedComponentTypes = new Dictionary>(); + TrackedCollidableComponentTypes = new Dictionary>(); + StoredEntityTypes = new HashSet(); + StoredComponentTypes = new HashSet(); + StoredCollidableComponentTypes = new HashSet(); + + foreach (var type in Assembly.GetEntryAssembly().GetTypes()) + { + var attrs = type.GetCustomAttributes(typeof(Tracked), false); + if (attrs.Length > 0) + { + bool inherited = (attrs[0] as Tracked).Inherited; + + if (typeof(Entity).IsAssignableFrom(type)) + { + if (!type.IsAbstract) + { + if (!TrackedEntityTypes.ContainsKey(type)) + TrackedEntityTypes.Add(type, new List()); + TrackedEntityTypes[type].Add(type); + } + + StoredEntityTypes.Add(type); + + if (inherited) + { + foreach (var subclass in GetSubclasses(type)) + { + if (!subclass.IsAbstract) + { + if (!TrackedEntityTypes.ContainsKey(subclass)) + TrackedEntityTypes.Add(subclass, new List()); + TrackedEntityTypes[subclass].Add(type); + } + } + } + } + else if (typeof(Component).IsAssignableFrom(type)) + { + if (!type.IsAbstract) + { + if (!TrackedComponentTypes.ContainsKey(type)) + TrackedComponentTypes.Add(type, new List()); + TrackedComponentTypes[type].Add(type); + } + + StoredComponentTypes.Add(type); + + if (inherited) + { + foreach (var subclass in GetSubclasses(type)) + { + if (!subclass.IsAbstract) + { + if (!TrackedComponentTypes.ContainsKey(subclass)) + TrackedComponentTypes.Add(subclass, new List()); + TrackedComponentTypes[subclass].Add(type); + } + } + } + + if (typeof(CollidableComponent).IsAssignableFrom(type)) + { + if (!type.IsAbstract) + { + if (!TrackedCollidableComponentTypes.ContainsKey(type)) + TrackedCollidableComponentTypes.Add(type, new List()); + TrackedCollidableComponentTypes[type].Add(type); + } + + StoredCollidableComponentTypes.Add(type); + + if (inherited) + { + foreach (var subclass in GetSubclasses(type)) + { + if (!subclass.IsAbstract) + { + if (!TrackedCollidableComponentTypes.ContainsKey(subclass)) + TrackedCollidableComponentTypes.Add(subclass, new List()); + TrackedCollidableComponentTypes[subclass].Add(type); + } + } + } + } + } + else + throw new Exception("Type '" + type.Name + "' cannot be Tracked because it does not derive from Entity or Component"); + } + } + } + + private static List GetSubclasses(Type type) + { + List matches = new List(); + + foreach (var check in Assembly.GetEntryAssembly().GetTypes()) + if (type != check && type.IsAssignableFrom(check)) + matches.Add(check); + + return matches; + } + + #endregion + + public Dictionary> Entities { get; private set; } + public Dictionary> Components { get; private set; } + public Dictionary> CollidableComponents { get; private set; } + + public Tracker() + { + Entities = new Dictionary>(TrackedEntityTypes.Count); + foreach (var type in StoredEntityTypes) + Entities.Add(type, new List()); + + Components = new Dictionary>(TrackedComponentTypes.Count); + foreach (var type in StoredComponentTypes) + Components.Add(type, new List()); + + CollidableComponents = new Dictionary>(TrackedComponentTypes.Count); + foreach (var type in StoredCollidableComponentTypes) + CollidableComponents.Add(type, new List()); + } + + public bool IsEntityTracked() where T : Entity + { + return Entities.ContainsKey(typeof(T)); + } + + public bool IsComponentTracked() where T : Component + { + return Components.ContainsKey(typeof(T)) || CollidableComponents.ContainsKey(typeof(T)); + } + + public T GetEntity() where T : Entity + { +#if DEBUG + if (!IsEntityTracked()) + throw new Exception("Entity type '" + typeof(T).Name + "' is not marked with the Tracked attribute!"); +#endif + + var list = Entities[typeof(T)]; + if (list.Count == 0) + return null; + else + return list[0] as T; + } + + public T GetNearestEntity(Vector2 nearestTo) where T : Entity + { + var list = GetEntities(); + + T nearest = null; + float nearestDistSq = 0; + + foreach (T entity in list) + { + float distSq = Vector2.DistanceSquared(nearestTo, entity.Position); + + if (nearest == null || distSq < nearestDistSq) + { + nearest = entity; + nearestDistSq = distSq; + } + } + + return nearest; + } + + public List GetEntities() where T : Entity + { +#if DEBUG + if (!IsEntityTracked()) + throw new Exception("Entity type '" + typeof(T).Name + "' is not marked with the Tracked attribute!"); +#endif + + return Entities[typeof(T)]; + } + + public List GetEntitiesCopy() where T : Entity + { + return new List(GetEntities()); + } + + public IEnumerator EnumerateEntities() where T : Entity + { +#if DEBUG + if (!IsEntityTracked()) + throw new Exception("Entity type '" + typeof(T).Name + "' is not marked with the Tracked attribute!"); +#endif + + foreach (var e in Entities[typeof(T)]) + yield return e as T; + } + + public int CountEntities() where T : Entity + { +#if DEBUG + if (!IsEntityTracked()) + throw new Exception("Entity type '" + typeof(T).Name + "' is not marked with the Tracked attribute!"); +#endif + + return Entities[typeof(T)].Count; + } + + public T GetComponent() where T : Component + { +#if DEBUG + if (!IsComponentTracked()) + throw new Exception("Component type '" + typeof(T).Name + "' is not marked with the Tracked attribute!"); +#endif + + var list = Components[typeof(T)]; + if (list.Count == 0) + return null; + else + return list[0] as T; + } + + public T GetNearestComponent(Vector2 nearestTo) where T : Component + { + var list = GetComponents(); + + T nearest = null; + float nearestDistSq = 0; + + foreach (T component in list) + { + float distSq = Vector2.DistanceSquared(nearestTo, component.Entity.Position); + + if (nearest == null || distSq < nearestDistSq) + { + nearest = component; + nearestDistSq = distSq; + } + } + + return nearest; + } + + public List GetComponents() where T : Component + { +#if DEBUG + if (!IsComponentTracked()) + throw new Exception("Component type '" + typeof(T).Name + "' is not marked with the Tracked attribute!"); +#endif + + return Components[typeof(T)]; + } + + public List GetComponentsCopy() where T : Component + { + return new List(GetComponents()); + } + + public IEnumerator EnumerateComponents() where T : Component + { +#if DEBUG + if (!IsComponentTracked()) + throw new Exception("Component type '" + typeof(T).Name + "' is not marked with the Tracked attribute!"); +#endif + + foreach (var c in Components[typeof(T)]) + yield return c as T; + } + + public int CountComponents() where T : Component + { +#if DEBUG + if (!IsComponentTracked()) + throw new Exception("Component type '" + typeof(T).Name + "' is not marked with the Tracked attribute!"); +#endif + + return Components[typeof(T)].Count; + } + + internal void EntityAdded(Entity entity) + { + var type = entity.GetType(); + List trackAs; + + if (TrackedEntityTypes.TryGetValue(type, out trackAs)) + foreach (var track in trackAs) + Entities[track].Add(entity); + } + + internal void EntityRemoved(Entity entity) + { + var type = entity.GetType(); + List trackAs; + + if (TrackedEntityTypes.TryGetValue(type, out trackAs)) + foreach (var track in trackAs) + Entities[track].Remove(entity); + } + + internal void ComponentAdded(Component component) + { + var type = component.GetType(); + List trackAs; + + if (TrackedComponentTypes.TryGetValue(type, out trackAs)) + foreach (var track in trackAs) + Components[track].Add(component); + if (TrackedCollidableComponentTypes.TryGetValue(type, out trackAs)) + foreach (var track in trackAs) + CollidableComponents[track].Add(component as CollidableComponent); + } + + internal void ComponentRemoved(Component component) + { + var type = component.GetType(); + List trackAs; + + if (TrackedComponentTypes.TryGetValue(type, out trackAs)) + foreach (var track in trackAs) + Components[track].Remove(component); + if (TrackedCollidableComponentTypes.TryGetValue(type, out trackAs)) + foreach (var track in trackAs) + CollidableComponents[track].Remove(component as CollidableComponent); + } + + public void LogEntities() + { + if (Entities.Count == 0) + Engine.Commands.Log("n/a", Color.Red); + else + { + foreach (var kv in Entities) + { + string output = kv.Key.Name + " : " + kv.Value.Count; + Engine.Commands.Log(output); + } + } + } + + public void LogComponents() + { + if (Components.Count == 0) + Engine.Commands.Log("n/a", Color.Red); + else + { + foreach (var kv in Components) + { + string output = kv.Key.Name + " : " + kv.Value.Count; + Engine.Commands.Log(output); + } + } + } + + public void LogCollidableComponents() + { + if (CollidableComponents.Count == 0) + Engine.Commands.Log("n/a", Color.Red); + else + { + foreach (var kv in CollidableComponents) + { + string output = kv.Key.Name + " : " + kv.Value.Count; + Engine.Commands.Log(output); + } + } + } + } + + public class Tracked : Attribute + { + public bool Inherited; + + public Tracked(bool inherited = false) + { + Inherited = inherited; + } + } +} diff --git a/MonocleEngineDemo/Monocle/Util/VirtualMap.cs b/MonocleEngineDemo/Monocle/Util/VirtualMap.cs new file mode 100644 index 0000000..10d011f --- /dev/null +++ b/MonocleEngineDemo/Monocle/Util/VirtualMap.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Monocle +{ + public class VirtualMap + { + public const int SegmentSize = 50; + public readonly int Columns; + public readonly int Rows; + public readonly int SegmentColumns; + public readonly int SegmentRows; + public readonly T EmptyValue; + + private T[,][,] segments; + + public VirtualMap(int columns, int rows, T emptyValue = default(T)) + { + Columns = columns; + Rows = rows; + SegmentColumns = (columns / SegmentSize) + 1; + SegmentRows = (rows / SegmentSize) + 1; + segments = new T[SegmentColumns, SegmentRows][,]; + EmptyValue = emptyValue; + } + + public VirtualMap(T[,] map, T emptyValue = default(T)) : this(map.GetLength(0), map.GetLength(1), emptyValue) + { + for (int x = 0; x < Columns; x++) + for (int y = 0; y < Rows; y++) + this[x, y] = map[x, y]; + } + + public bool AnyInSegmentAtTile(int x, int y) + { + int cx = x / SegmentSize; + int cy = y / SegmentSize; + + return segments[cx, cy] != null; + } + + public bool AnyInSegment(int segmentX, int segmentY) + { + return segments[segmentX, segmentY] != null; + } + + public T InSegment(int segmentX, int segmentY, int x, int y) + { + return segments[segmentX, segmentY][x, y]; + } + + public T[,] GetSegment(int segmentX, int segmentY) + { + return segments[segmentX, segmentY]; + } + + public T SafeCheck(int x, int y) + { + if (x >= 0 && y >= 0 && x < Columns && y < Rows) + return this[x, y]; + return EmptyValue; + } + + public T this[int x, int y] + { + get + { + int cx = x / SegmentSize; + int cy = y / SegmentSize; + + var seg = segments[cx, cy]; + if (seg == null) + return EmptyValue; + + return seg[x - cx * SegmentSize, y - cy * SegmentSize]; + } + + set + { + int cx = x / SegmentSize; + int cy = y / SegmentSize; + + if (segments[cx, cy] == null) + { + segments[cx, cy] = new T[SegmentSize, SegmentSize]; + + // fill with custom Empty Value data + if (EmptyValue != null && !EmptyValue.Equals(default(T))) + for (int tx = 0; tx < SegmentSize; tx++) + for (int ty = 0; ty < SegmentSize; ty++) + segments[cx, cy][tx, ty] = EmptyValue; + } + + segments[cx, cy][x - cx * SegmentSize, y - cy * SegmentSize] = value; + } + } + + public T[,] ToArray() + { + var array = new T[Columns, Rows]; + for (int x = 0; x < Columns; x++) + for (int y = 0; y < Rows; y++) + array[x, y] = this[x, y]; + return array; + } + + public VirtualMap Clone() + { + var clone = new VirtualMap(Columns, Rows, EmptyValue); + for (int x = 0; x < Columns; x++) + for (int y = 0; y < Rows; y++) + clone[x, y] = this[x, y]; + return clone; + } + + + } +} diff --git a/MonocleEngineDemo/MonocleDemo/Content/Atlases/testboxes.xml b/MonocleEngineDemo/MonocleDemo/Content/Atlases/testboxes.xml new file mode 100644 index 0000000..3f35144 --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/Content/Atlases/testboxes.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MonocleEngineDemo/MonocleDemo/Content/Atlases/testboxes0.png b/MonocleEngineDemo/MonocleDemo/Content/Atlases/testboxes0.png new file mode 100644 index 0000000..cbb900d Binary files /dev/null and b/MonocleEngineDemo/MonocleDemo/Content/Atlases/testboxes0.png differ diff --git a/MonocleEngineDemo/MonocleDemo/Content/Content.mgcb b/MonocleEngineDemo/MonocleDemo/Content/Content.mgcb new file mode 100644 index 0000000..ddc4c36 --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/Content/Content.mgcb @@ -0,0 +1,15 @@ + +#----------------------------- Global Properties ----------------------------# + +/outputDir:bin/$(Platform) +/intermediateDir:obj/$(Platform) +/platform:DesktopGL +/config: +/profile:Reach +/compress:False + +#-------------------------------- References --------------------------------# + + +#---------------------------------- Content ---------------------------------# + diff --git a/MonocleEngineDemo/MonocleDemo/Content/spriteData/Notes.txt b/MonocleEngineDemo/MonocleDemo/Content/spriteData/Notes.txt new file mode 100644 index 0000000..fb1e711 --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/Content/spriteData/Notes.txt @@ -0,0 +1,62 @@ +/////////////////////////////////////////////////////////////////////// + +Notes regarding the Sprites.xml file used for creating a SpriteBank. + +/////////////////////////////////////////////////////////////////////// + + + + +Inside the 'Sprites' tag add all different sprites that you want to use. +Each of them can be used, for instance, for a different entity. +Each sprite has a 'path' attribute that represents the location or +folder inside the Atlas that we are accessing from a SpriteBank. That +attribute should be only the path represented by folders (not files). +Optionally, you can select a start animation for a sprite using the +'start' attribute (which is recommended) and naming the animation id. + +Inside the sprite there are several types of tags. +With the
tag, you can specify the sprite origin being in the +center of the sprite. +With the tag, you can set a specific point of +the sprite origin, tuning the x and y attributes in the range between +0 and 1 ( where (0,0) is the top-left and (1,1) is the bottom-right position). +There are two main types of tags that define the type of sprite: + and tags. +When a sprite has a tag, it means that once the sprite animation +has finished, it will start over. Unlike that, the tag defines that +the sprite animation when done remains as the last frame. +Both, the and tag, have an 'id' attribute which is the name of +that type of animation referenced by the sprite (like in the start attribute +of the sprite tag). The 'path' attribute represents the name of the file +located inside the sprite path. If the file is an animation consisting of +multiple frames, you can enter the name of the file ommiting the number of +the frame in the file name (for example, we have an animation of 8 frames, +where each frame has the name like 'arrow0', 'arrow1', 'arrow2' and so on. We +can just say path="arrow"). In case of multiple frames per animation, we can +also specify which frames at which order are the part of our animation, using +the frames attribute (for example: frames="0-7,9-10,2-4,15" will make an animation +going through the first seven frames, then skipping the eight and going from ninth +to tenth, then going from the second to the fourth and finally fifteenth frame of a file). +Instead of repeating a value several times just to make the animation, for example, frozen, +we can say frames="0*10", which means that it will repeat the frame with index 0 ten times. +If we assign this to an object, we will notice that the animation will go in an instant. +We can set the speed of the animation (in seconds) using the 'delay' attribute. The delay +will be set between each two frames we set in the 'frames' attribute. +Once the animation of the type was finished, we can change to another animation inside +the sprite using the 'goto' attribute and naming the id of the animation. You can also chain +multiple animations and once the current animation is over, it will execute them in the entered +order, starting from left to right. Additionally, you can also set the number of repeatings for +each of the provided animations using the format "idOfAnimation:numberOfRepeats" (for example: +goto="idle:10,sleep:6,wakeUp"). + +example: + + +
+ + + + + +/////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/MonocleEngineDemo/MonocleDemo/Content/spriteData/Sprites.xml b/MonocleEngineDemo/MonocleDemo/Content/spriteData/Sprites.xml new file mode 100644 index 0000000..b0bf9a0 --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/Content/spriteData/Sprites.xml @@ -0,0 +1,18 @@ + + + + + +
+ + + + + +
+ + + + \ No newline at end of file diff --git a/MonocleEngineDemo/MonocleDemo/FNA.dll b/MonocleEngineDemo/MonocleDemo/FNA.dll new file mode 100644 index 0000000..6002438 Binary files /dev/null and b/MonocleEngineDemo/MonocleDemo/FNA.dll differ diff --git a/MonocleEngineDemo/MonocleDemo/GameEntities/Abstract/Dummy.cs b/MonocleEngineDemo/MonocleDemo/GameEntities/Abstract/Dummy.cs new file mode 100644 index 0000000..4923348 --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/GameEntities/Abstract/Dummy.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonocleDemoNamespace.Logic; +using Microsoft.Xna.Framework; +using Monocle; + +namespace MonocleDemoNamespace.GameEntities.Abstract +{ + public class Dummy: Entity + { + Sprite Sprite; + + public Dummy() + { + Add(Sprite = GFX.AllSprites.Create("player")); + Collider = new Hitbox(32, 32, -16, -16); + } + + public void Move(Vector2 add) + { + if (add.X != 0 || add.Y != 0) + { + Sprite.Play("move", false); + + // Move maximum in a direction until hitting a wall + + if (CollideCheck(GAccess.WallTag, new Vector2(X + add.X, Y))) + { + while (!CollideCheck(GAccess.WallTag, new Vector2(X + Calc.Sign(add).X, Y))) + X += Calc.Sign(add).X; + } + else + X += add.X* 60f * Engine.DeltaTime; + + if (CollideCheck(GAccess.WallTag, new Vector2(X, Y + add.Y))) + { + while (!CollideCheck(GAccess.WallTag, new Vector2(X, Y + Calc.Sign(add).Y))) + Y += Calc.Sign(add).Y; + } + else + Y += add.Y* 60f * Engine.DeltaTime; + } + } + + } +} diff --git a/MonocleEngineDemo/MonocleDemo/GameEntities/Abstract/SolidWall.cs b/MonocleEngineDemo/MonocleDemo/GameEntities/Abstract/SolidWall.cs new file mode 100644 index 0000000..3f4339f --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/GameEntities/Abstract/SolidWall.cs @@ -0,0 +1,29 @@ +using MonocleDemoNamespace.Logic; +using Microsoft.Xna.Framework; +using Monocle; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MonocleDemoNamespace.GameEntities.Abstract +{ + public class SolidWall: Entity + { + Sprite Sprite; // Entity sprite + + public SolidWall() + { + Add(Sprite = GFX.AllSprites.Create("wall")); + Collider = new Hitbox(32, 32, -16, -16); + Tag = GAccess.WallTag; + } + + public SolidWall(Vector2 pos) : this() + { + Position = pos; + } + + } +} diff --git a/MonocleEngineDemo/MonocleDemo/Icon.bmp b/MonocleEngineDemo/MonocleDemo/Icon.bmp new file mode 100644 index 0000000..2b48165 Binary files /dev/null and b/MonocleEngineDemo/MonocleDemo/Icon.bmp differ diff --git a/MonocleEngineDemo/MonocleDemo/Icon.ico b/MonocleEngineDemo/MonocleDemo/Icon.ico new file mode 100644 index 0000000..7d9dec1 Binary files /dev/null and b/MonocleEngineDemo/MonocleDemo/Icon.ico differ diff --git a/MonocleEngineDemo/MonocleDemo/Logic/GAccess.cs b/MonocleEngineDemo/MonocleDemo/Logic/GAccess.cs new file mode 100644 index 0000000..5918cca --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/Logic/GAccess.cs @@ -0,0 +1,24 @@ +using Monocle; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MonocleDemoNamespace.Logic +{ + /// + /// Global Access class + /// + public static class GAccess + { + #region Game Tags + + public static BitTag WallTag = new BitTag("wall"); + public static BitTag ItemTag = new BitTag("item"); + + #endregion + + } + +} diff --git a/MonocleEngineDemo/MonocleDemo/Logic/GFX.cs b/MonocleEngineDemo/MonocleDemo/Logic/GFX.cs new file mode 100644 index 0000000..3e75ead --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/Logic/GFX.cs @@ -0,0 +1,66 @@ +using Monocle; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MonocleDemoNamespace.Logic +{ + public class GFX + { + + #region Fields + + private static SpriteBank _spriteBank = null; + List Atlases; + + public static SpriteBank AllSprites + { + get + { + return _spriteBank; + } + } + + #endregion + + #region Singleton + + private static GFX _instance = null; + + public static GFX Instance + { + get + { + if (_instance == null) + _instance = new GFX(); + return _instance; + } + } + + private GFX() + { + InitStuff(); + } + + #endregion + + private void InitStuff() + { + Atlases = new List(); + Atlases.Add(Atlas.FromAtlas("Atlases\\testboxes", Atlas.AtlasDataFormat.CrunchXmlOrBinary)); + + _spriteBank = new SpriteBank(Atlases[0], "spriteData\\Sprites.xml"); + } + + /// + /// Reloads atlases from file (should not be called so often). + /// + public void RefreshData() + { + InitStuff(); + } + + } +} diff --git a/MonocleEngineDemo/MonocleDemo/MonoDemo.cs b/MonocleEngineDemo/MonocleDemo/MonoDemo.cs new file mode 100644 index 0000000..c2b0a3a --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/MonoDemo.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MonocleDemoNamespace.Logic; +using MonocleDemoNamespace.Scenes.Menus; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework.Media; +using Monocle; + +namespace MonocleDemoNamespace +{ + /// + /// This is the main type for your game. + /// + public class MonoDemo : Engine + { + public MonoDemo() : base(1280, 720, 640, 360, "MonocleEngine Test", false) + { + Engine.ClearColor = Color.CornflowerBlue; + Content.RootDirectory = "Content"; + ViewPadding = -32; + } + + /// + /// Allows the game to perform any initialization it needs to before starting to run. + /// This is where it can query for any required services and load any non-graphic + /// related content. Calling base.Initialize will enumerate through any components + /// and initialize them as well. + /// + protected override void Initialize() + { + // TODO: Add your initialization logic here + + // Initialize some components + { + int initGAccess = GAccess.WallTag.ID; + GFX graphicsInit = GFX.Instance; + } + + base.Initialize(); + InitScene isc = new InitScene(); + Scene = isc; + } + + /// + /// LoadContent will be called once per game and is the place to load + /// all of your content. + /// + protected override void LoadContent() + { + // Create a new SpriteBatch, which can be used to draw textures. + + + // TODO: use this.Content to load your game content here + base.LoadContent(); + + //SetFullscreen(); + + } + + /// + /// UnloadContent will be called once per game and is the place to unload + /// game-specific content. + /// + protected override void UnloadContent() + { + // TODO: Unload any non ContentManager content here + } + + /// + /// Allows the game to run logic such as updating the world, + /// checking for collisions, gathering input, and playing audio. + /// + /// Provides a snapshot of timing values. + protected override void Update(GameTime gameTime) + { + if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) + Exit(); + + // TODO: Add your update logic here + + base.Update(gameTime); + } + + /// + /// This is called when the game should draw itself. + /// + /// Provides a snapshot of timing values. + protected override void Draw(GameTime gameTime) + { + base.Draw(gameTime); + + + // TODO: Add your drawing code here + } + } +} diff --git a/MonocleEngineDemo/MonocleDemo/Monocle.dll b/MonocleEngineDemo/MonocleDemo/Monocle.dll new file mode 100644 index 0000000..a5dc4ef Binary files /dev/null and b/MonocleEngineDemo/MonocleDemo/Monocle.dll differ diff --git a/MonocleEngineDemo/MonocleDemo/MonocleDemo.csproj b/MonocleEngineDemo/MonocleDemo/MonocleDemo.csproj new file mode 100644 index 0000000..c871a8e --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/MonocleDemo.csproj @@ -0,0 +1,171 @@ + + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {1A6DD559-1A6A-437C-9321-76D9D0BB2CA0} + WinExe + Properties + MonocleDemoNamespace + MonocleDemo + 512 + DesktopGL + v4.5.2 + + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + true + bin\$(MonoGamePlatform)\$(Platform)\$(Configuration)\ + DEBUG;TRACE;LINUX + full + AnyCPU + prompt + false + 4 + + + bin\$(MonoGamePlatform)\$(Platform)\$(Configuration)\ + TRACE;LINUX + true + pdbonly + AnyCPU + prompt + false + 4 + + + Icon.ico + + + app.manifest + + + + + + + + + + + + + + + $(MonoGameInstallDirectory)\MonoGame\v3.0\Assemblies\DesktopGL\MonoGame.Framework.dll + + + + + + + + + + + + x86\SDL2.dll + PreserveNewest + + + x64\SDL2.dll + PreserveNewest + + + x86\soft_oal.dll + PreserveNewest + + + x64\soft_oal.dll + PreserveNewest + + + x86\libSDL2-2.0.so.0 + PreserveNewest + + + x64\libSDL2-2.0.so.0 + PreserveNewest + + + x86\libopenal.so.1 + PreserveNewest + + + x64\libopenal.so.1 + PreserveNewest + + + libSDL2-2.0.0.dylib + PreserveNewest + + + libopenal.1.dylib + PreserveNewest + + + MonoGame.Framework.dll.config + PreserveNewest + + + + + + + Always + + + Always + + + + Always + + + + + {6ec3c0a6-c5be-42ce-aea5-881d8a97eb60} + Monocle.MonoGame + + + + + False + Microsoft .NET Framework 4.5.2 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + + \ No newline at end of file diff --git a/MonocleEngineDemo/MonocleDemo/Program.cs b/MonocleEngineDemo/MonocleDemo/Program.cs new file mode 100644 index 0000000..f7c7ca2 --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/Program.cs @@ -0,0 +1,24 @@ +using System; +using MonocleDemoNamespace.Logic; +using Monocle; + +namespace MonocleDemoNamespace +{ + /// + /// The main class. + /// + public static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + using (var game = new MonoDemo()) + { + game.Run(); + } + } + } +} diff --git a/MonocleEngineDemo/MonocleDemo/Properties/AssemblyInfo.cs b/MonocleEngineDemo/MonocleDemo/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ea7c9c6 --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MonocleDemoNamespace")] +[assembly: AssemblyProduct("MonocleDemoNamespace")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4879dc03-2a08-42b9-b350-6e426a4ede08")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MonocleEngineDemo/MonocleDemo/Scenes/Menus/InitScene.cs b/MonocleEngineDemo/MonocleDemo/Scenes/Menus/InitScene.cs new file mode 100644 index 0000000..528a738 --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/Scenes/Menus/InitScene.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Monocle; +using MonocleDemoNamespace.GameEntities.Abstract; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace MonocleDemoNamespace.Scenes.Menus +{ + public class InitScene: Scene + { + Camera camera; // Camera handler (so that we can modify it's paramteres when needed, like zoom) + Dummy dum; // Dummy + int dummyMove = 4; // Dummy movement speed + + public InitScene() : base() + {} + + public override void Begin() + { + base.Begin(); + + // Add the EverythingRenderer object so it will actually display the contents of the scene + EverythingRenderer er = new EverythingRenderer(); + Add(er); + + // Setup the camera + camera = new Camera(640, 360); + camera.CenterOrigin(); + camera.Zoom = 1.5f; + er.Camera = camera; // Attach the new camera to the EverythingRenderer + + // Create the dummy + dum = new Dummy(); + Vector2 screenHalf = (new Vector2(Engine.Width, Engine.Height))/2; + dum.Position = screenHalf; + Add(dum); + + // Create walls (mainly to limit the movement of the dummy and to test collisions) + CreateWallBorder(); + AdditionalWalls(); + } + + private void CreateWallBorder() + { + for (int i = 16; i < Engine.Width; i += 32) + { + SolidWall sw = new SolidWall(new Vector2(i, 0)); + Add(sw); + sw = new SolidWall(new Vector2(i, Engine.Height - 16)); + Add(sw); + } + + for (int i = 0; i < Engine.Height; i += 32) + { + SolidWall sw = new SolidWall(new Vector2(-16, i)); + Add(sw); + sw = new SolidWall(new Vector2(Engine.Width - 16, i)); + Add(sw); + } + + + } + + private void AdditionalWalls() + { + // Above and bellow the player + for (int i = 0; i < 3; i++) + { + Vector2 pos = new Vector2(dum.X, dum.Y - 32 * (i + 2)); + Vector2 pos2 = new Vector2(dum.X, dum.Y + 32 * (i + 2)); + SolidWall sw = new SolidWall(pos); + SolidWall sw2 = new SolidWall(pos2); + Add(sw); + Add(sw2); + } + + // Left and right to the player + for (int i = 0; i < 5; i++) + { + Vector2 pos = new Vector2(dum.X - 32 * (i + 3), dum.Y); + Vector2 pos2 = new Vector2(dum.X + 32 * (i + 3), dum.Y); + SolidWall sw = new SolidWall(pos); + SolidWall sw2 = new SolidWall(pos2); + Add(sw); + Add(sw2); + } + + } + + public override void Update() + { + base.Update(); + + int nx = dummyMove * MInput.Keyboard.AxisCheck(Keys.Left, Keys.Right); + int ny = dummyMove * MInput.Keyboard.AxisCheck(Keys.Up, Keys.Down); + + dum.Move( new Vector2(nx, ny)); + + // This is a direct transition to test scene + if (MInput.Keyboard.Check(Keys.LeftShift,Keys.RightShift) && MInput.Keyboard.Check(Keys.Add)) + Engine.Scene = new TestScene(); + } + + public override void AfterUpdate() + { + base.AfterUpdate(); + Vector2 tresh = new Vector2(120, 90); // Padding (horizontal,vertical) + Vector2 npos = new Vector2(dum.X - Engine.ViewWidth / 4, dum.Y - Engine.ViewHeight / 4); + + // Clamp camera position + if (npos.X > Engine.Width / 2 + tresh.X) + npos.X = Math.Min(npos.X, Engine.Width / 2 + tresh.X); + else if (npos.X < Engine.Width / 2 - tresh.X) + npos.X = Math.Max(npos.X, tresh.X / 2); + + if (npos.Y > Engine.Height / 2 + tresh.Y) + npos.Y = Math.Min(npos.Y, Engine.Height / 2 + tresh.Y); + else if (npos.Y < Engine.Height / 2 - tresh.Y) + npos.Y = Math.Max(npos.Y, tresh.Y / 2); + + camera.Position = npos; + } + + + } +} diff --git a/MonocleEngineDemo/MonocleDemo/Scenes/Menus/TestScene.cs b/MonocleEngineDemo/MonocleDemo/Scenes/Menus/TestScene.cs new file mode 100644 index 0000000..cf15fa3 --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/Scenes/Menus/TestScene.cs @@ -0,0 +1,24 @@ +using Monocle; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MonocleDemoNamespace.Scenes.Menus +{ + public class TestScene: Scene + { + + public override void Begin() + { + base.Begin(); + + var renderer = new EverythingRenderer(); + Add(renderer); + + + } + + } +} diff --git a/MonocleEngineDemo/MonocleDemo/app.config b/MonocleEngineDemo/MonocleDemo/app.config new file mode 100644 index 0000000..ff99501 --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/app.config @@ -0,0 +1,3 @@ + + + diff --git a/MonocleEngineDemo/MonocleDemo/app.manifest b/MonocleEngineDemo/MonocleDemo/app.manifest new file mode 100644 index 0000000..408b23f --- /dev/null +++ b/MonocleEngineDemo/MonocleDemo/app.manifest @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true/pm + + + + diff --git a/MonocleEngineDemo/MonocleEngineDemo.sln b/MonocleEngineDemo/MonocleEngineDemo.sln new file mode 100644 index 0000000..6c812c0 --- /dev/null +++ b/MonocleEngineDemo/MonocleEngineDemo.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2035 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonocleDemo", "MonocleDemo\MonocleDemo.csproj", "{1A6DD559-1A6A-437C-9321-76D9D0BB2CA0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Monocle.MonoGame", "Monocle\Monocle.MonoGame.csproj", "{6EC3C0A6-C5BE-42CE-AEA5-881D8A97EB60}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A6DD559-1A6A-437C-9321-76D9D0BB2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A6DD559-1A6A-437C-9321-76D9D0BB2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A6DD559-1A6A-437C-9321-76D9D0BB2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A6DD559-1A6A-437C-9321-76D9D0BB2CA0}.Release|Any CPU.Build.0 = Release|Any CPU + {6EC3C0A6-C5BE-42CE-AEA5-881D8A97EB60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EC3C0A6-C5BE-42CE-AEA5-881D8A97EB60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EC3C0A6-C5BE-42CE-AEA5-881D8A97EB60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EC3C0A6-C5BE-42CE-AEA5-881D8A97EB60}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B9E28CA4-002D-4B1C-8139-01B44CBA23BC} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index 2ae55d5..a808607 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # MonocleEngineDemo A working project made using MonocleEngine by Matt Thorson and MonoGame, covering basing things. + +This project contains the project, MonocleEngine (https://bitbucket.org/MattThorson/monocle-engine/src/default/) source and a built version of Cevyray's Crunch project (https://github.com/chevyray/crunch), along with some sprites for demonstration purposes. + +It contains two scenes, entities, loading atlases from generated xml crunch files, creating a spritebank and using it inside entities, basic collisions, camera adjustments and object movement using MInput. \ No newline at end of file diff --git a/Sources/Atlases/testboxes.xml b/Sources/Atlases/testboxes.xml new file mode 100644 index 0000000..3f35144 --- /dev/null +++ b/Sources/Atlases/testboxes.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/Atlases/testboxes0.png b/Sources/Atlases/testboxes0.png new file mode 100644 index 0000000..cbb900d Binary files /dev/null and b/Sources/Atlases/testboxes0.png differ diff --git a/Sources/Graphics/testing/base_cubes/arrow0.png b/Sources/Graphics/testing/base_cubes/arrow0.png new file mode 100644 index 0000000..860ea96 Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/arrow0.png differ diff --git a/Sources/Graphics/testing/base_cubes/arrow1.png b/Sources/Graphics/testing/base_cubes/arrow1.png new file mode 100644 index 0000000..64e62b2 Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/arrow1.png differ diff --git a/Sources/Graphics/testing/base_cubes/arrow2.png b/Sources/Graphics/testing/base_cubes/arrow2.png new file mode 100644 index 0000000..5091bf5 Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/arrow2.png differ diff --git a/Sources/Graphics/testing/base_cubes/arrow3.png b/Sources/Graphics/testing/base_cubes/arrow3.png new file mode 100644 index 0000000..c4e4b50 Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/arrow3.png differ diff --git a/Sources/Graphics/testing/base_cubes/arrow4.png b/Sources/Graphics/testing/base_cubes/arrow4.png new file mode 100644 index 0000000..cff3e93 Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/arrow4.png differ diff --git a/Sources/Graphics/testing/base_cubes/arrow5.png b/Sources/Graphics/testing/base_cubes/arrow5.png new file mode 100644 index 0000000..2603f22 Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/arrow5.png differ diff --git a/Sources/Graphics/testing/base_cubes/arrow6.png b/Sources/Graphics/testing/base_cubes/arrow6.png new file mode 100644 index 0000000..9c55536 Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/arrow6.png differ diff --git a/Sources/Graphics/testing/base_cubes/arrow7.png b/Sources/Graphics/testing/base_cubes/arrow7.png new file mode 100644 index 0000000..87ed53b Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/arrow7.png differ diff --git a/Sources/Graphics/testing/base_cubes/black_base.png b/Sources/Graphics/testing/base_cubes/black_base.png new file mode 100644 index 0000000..a9d8c65 Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/black_base.png differ diff --git a/Sources/Graphics/testing/base_cubes/blue_base.png b/Sources/Graphics/testing/base_cubes/blue_base.png new file mode 100644 index 0000000..f7ea527 Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/blue_base.png differ diff --git a/Sources/Graphics/testing/base_cubes/green_base.png b/Sources/Graphics/testing/base_cubes/green_base.png new file mode 100644 index 0000000..5ca9579 Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/green_base.png differ diff --git a/Sources/Graphics/testing/base_cubes/red_base.png b/Sources/Graphics/testing/base_cubes/red_base.png new file mode 100644 index 0000000..ff3fbd0 Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/red_base.png differ diff --git a/Sources/Graphics/testing/base_cubes/white_base.png b/Sources/Graphics/testing/base_cubes/white_base.png new file mode 100644 index 0000000..174204e Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/white_base.png differ diff --git a/Sources/Graphics/testing/base_cubes/yellow_base.png b/Sources/Graphics/testing/base_cubes/yellow_base.png new file mode 100644 index 0000000..d08949d Binary files /dev/null and b/Sources/Graphics/testing/base_cubes/yellow_base.png differ diff --git a/Tools/Crunch/crunch.exe b/Tools/Crunch/crunch.exe new file mode 100644 index 0000000..073b49e Binary files /dev/null and b/Tools/Crunch/crunch.exe differ diff --git a/Tools/Crunch/readme.md b/Tools/Crunch/readme.md new file mode 100644 index 0000000..a5a7886 --- /dev/null +++ b/Tools/Crunch/readme.md @@ -0,0 +1,120 @@ +# crunch + +This is a command line tool that will pack a bunch of images into a single, larger image. It was designed for [Celeste](http://www.celestegame.com/), but could be very helpful for other games. + +It is designed using libraries with permissible licenses, so you are able to use it freely in your commercial and non-commercial projects. Please see each source file for its respective copyright and license. + +### Features + +- Export XML, JSON, or binary data +- Trim excess transparency +- Rotate images to fit +- Control atlas size and padding +- Premultiply pixel values +- Recursively scans folders +- Remove duplicate images +- Caching to prevent redundant builds +- Multi-image atlas when the sprites don't fit + +### What does it do? + +Given a folder with several images, like so: + +``` +images/ + player.png + tree.png + enemy.png +``` + +It will output something like this: + +``` +bin/ + images.png + images.xml + images.hash +``` + +Where `images.png` is the packed image, `images.xml` is an xml file describing where each sub-image is located, and `images.hash` is used for file caching (if none of the input files have changed since the last pack, the program will terminate). + +There is also an option to use a binary format instead of xml. + +### Usage + +`crunch [OUTPUT] [INPUT1,INPUT2,INPUT3...] [OPTIONS...]` + +For example... + +`crunch bin/atlases/atlas assets/characters,assets/tiles -p -t -v -u -r -j` + +This will output the following files: + +``` +bin/atlases/atlas.png +bin/atlases/atlas.json +bin/atlases/atlas.hash +``` + +### Options + +| option | alias | description | +| ------------- | ------------- | ------------| +| -d | --default | use default settings (-x -p -t -u) +| -x | --xml | saves the atlas data as a .xml file +| -b | --binary | saves the atlas data as a .bin file +| -j | --json | saves the atlas data as a .json file +| -p | --premultiply | premultiplies the pixels of the bitmaps by their alpha channel +| -t | --trim | trims excess transparency off the bitmaps +| -v | --verbose | print to the debug console as the packer works +| -f | --force | ignore caching, forcing the packer to repack +| -u | --unique | remove duplicate bitmaps from the atlas +| -r | --rotate | enabled rotating bitmaps 90 degrees clockwise when packing +| -s# | --size# | max atlas size (# can be 4096, 2048, 1024, 512, 256, 128, or 64) +| -p# | --pad# | padding between images (# can be from 0 to 16) + +### Binary Format + + ``` + [int16] num_textures (below block is repeated this many times) + [string] name + [int16] num_images (below block is repeated this many times) + [string] img_name + [int16] img_x + [int16] img_y + [int16] img_width + [int16] img_height + [int16] img_frame_x (if --trim enabled) + [int16] img_frame_y (if --trim enabled) + [int16] img_frame_width (if --trim enabled) + [int16] img_frame_height (if --trim enabled) + [byte] img_rotated (if --rotate enabled) +``` + +### License + +Unless otherwise specified in a source file, everything in this project falls under the following license: + +``` +MIT License + +Copyright (c) 2017 Chevy Ray Johnston + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +```