diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..59d79bd
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,37 @@
+## The Basics
+
+Thanks for making a pull request! Before making a pull request, we have some
+things for you to read through first:
+
+- We generally do not accept patches for formatting fixes, unless the formatting
+ fixes are part of a functional patch (for example, when fixing a bug in a
+ function you can fix up the lines surrounding it if needed).
+- Patches that break compatibility with the original game data or save data will
+ not be accepted.
+- New features and user interface changes will most likely not be accepted
+ unless they are for improving user accessibility.
+- New platforms are acceptable if they use FNA and don't mess with the game
+ source too much.
+ - (No homebrew console targets, sorry! Maybe do the work in SDL instead?)
+- Translations and localizations of the game are not a community effort. If you
+ want to translate the game, you should contact Cellar Door Games.
+- Pull requests that do not fill out the Legal Stuff will be closed
+ automatically.
+
+If you understand these notes, you can delete the text in this section. Pull
+requests that still have this text will be closed automatically.
+
+
+## Changes:
+
+Describe your patch here!
+
+
+## Legal Stuff:
+
+By submitting this pull request, I confirm that...
+
+- [ ] My changes may be used in a future commercial release of Rogue Legacy
+- [ ] I will be credited in a `CONTRIBUTORS` file and the "GitHub Friends"
+ section of the credits for all of said releases, but will NOT be compensated
+ for these changes unless there is a prior written agreement
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..841c196
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,25 @@
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+ linux:
+ name: Linux
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: true
+
+ - name: Clone FNA
+ run: |
+ git clone --recursive https://github.com/FNA-XNA/FNA.git
+ mv FNA ../FNA
+
+ - name: dotnet build (Debug)
+ run: |
+ dotnet build -c Debug
+
+ - name: dotnet build (Release)
+ run: |
+ dotnet build -c Release
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..59bee9d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+bin/
+obj/
+obj_core/
+*.pidb
+*.user
+*.userprefs
+*.suo
+*.vs
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..9c753c8
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,25 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch",
+ "type": "mono",
+ "request": "launch",
+ "program": "${workspaceRoot}/RogueCastle/bin/x64/Debug/net40/RogueLegacy.exe",
+ "cwd": "${workspaceRoot}/RogueCastle/bin/x64/Debug/net40/",
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/fnalibs3/lib64/"
+ }
+ },
+ {
+ "name": "Attach",
+ "type": "mono",
+ "request": "attach",
+ "address": "localhost",
+ "port": 55555
+ }
+ ]
+}
diff --git a/DS2DEngine/DS2DEngine.csproj b/DS2DEngine/DS2DEngine.csproj
new file mode 100644
index 0000000..3f54192
--- /dev/null
+++ b/DS2DEngine/DS2DEngine.csproj
@@ -0,0 +1,26 @@
+
+
+
+ Library
+ net40
+
+
+
+
+ {4EFA1C2F-A065-4520-A8AC-A71EA1751C54}
+ InputSystem
+
+
+ {92C40872-2B5C-4894-AABB-602547E1DFC3}
+ SpriteSystem
+
+
+ {D9583122-AC6D-41EB-8292-04BDD0519D7C}
+ Tweener
+
+
+ {35253CE1-C864-4CD3-8249-4D1319748E8F}
+ FNA
+
+
+
diff --git a/DS2DEngine/src/AI Logic/Actions/ChangePropertyLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/ChangePropertyLogicAction.cs
new file mode 100644
index 0000000..f238ccd
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/ChangePropertyLogicAction.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Reflection;
+
+namespace DS2DEngine
+{
+ public class ChangePropertyLogicAction : LogicAction
+ {
+ private string m_propertyName;
+ private object m_propertyArg;
+ private object m_object;
+
+ public ChangePropertyLogicAction(object propertyObject, string propertyName, object propertyArg)
+ {
+ m_object = propertyObject;
+ m_propertyName = propertyName;
+ m_propertyArg = propertyArg;
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive)
+ {
+ PropertyInfo propertyInfo = m_object.GetType().GetProperty(m_propertyName);
+ propertyInfo.SetValue(m_object, m_propertyArg, null);
+ base.Execute();
+ }
+ }
+
+ public override object Clone()
+ {
+ return new ChangePropertyLogicAction(m_object, m_propertyName, m_propertyArg);
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ m_propertyArg = null;
+ m_object = null;
+ base.Dispose();
+ }
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/ChangeSpriteLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/ChangeSpriteLogicAction.cs
new file mode 100644
index 0000000..a67e6f4
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/ChangeSpriteLogicAction.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Tweener;
+using Tweener.Ease;
+
+namespace DS2DEngine
+{
+ public class ChangeSpriteLogicAction : LogicAction
+ {
+ private string m_spriteName;
+ private bool m_playAnimation;
+ private bool m_loopAnimation;
+
+ //private TweenObject m_tweenDelay;
+ //private BlankObj m_blankObj; // Storing the blank object so that we can dispose it properly later.
+ private IAnimateableObj m_animateableObj;
+
+ public ChangeSpriteLogicAction(string spriteName, bool playAnimation = true, bool loopAnimation = true)
+ {
+ m_spriteName = spriteName;
+ m_playAnimation = playAnimation;
+ m_loopAnimation = loopAnimation;
+ //m_blankObj = new BlankObj(1, 1);
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ m_animateableObj = this.ParentLogicSet.ParentGameObj as IAnimateableObj;
+ if (m_animateableObj != null && (m_animateableObj.SpriteName != m_spriteName || m_animateableObj.IsAnimating == false))
+ {
+ this.ParentLogicSet.ParentGameObj.ChangeSprite(m_spriteName);
+ if (m_playAnimation == true)
+ {
+ m_animateableObj.PlayAnimation(m_loopAnimation);
+ //if (SequenceType == Types.Sequence.Serial && animateableObj.IsLooping == false)
+ //{
+ // m_tweenDelay = Tween.To(m_blankObj, animateableObj.TotalFrames, Linear.EaseNone, "X", "1");
+ // m_tweenDelay.UseTicks = true;
+ //}
+ }
+ }
+ base.Execute();
+ }
+ }
+
+ public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
+ {
+ this.ExecuteNext();
+ base.Update(gameTime);
+ }
+
+ public override void ExecuteNext()
+ {
+ if (m_playAnimation == false || m_loopAnimation == true || (m_animateableObj != null && SequenceType == Types.Sequence.Serial && m_animateableObj.IsLooping == false && m_animateableObj.IsAnimating == false))
+ {
+ base.ExecuteNext();
+ }
+ }
+
+ //public override void ExecuteNext()
+ //{
+ // if (m_tweenDelay != null)
+ // {
+ // if (NextLogicAction != null)
+ // m_tweenDelay.EndHandler(NextLogicAction, "Execute");
+ // else
+ // m_tweenDelay.EndHandler(ParentLogicSet, "ExecuteComplete");
+ // }
+ // else
+ // base.ExecuteNext();
+ //}
+
+ public override void Stop()
+ {
+ // Okay. Big problem with delay tweens. Because logic actions are never disposed in-level (due to garbage collection concerns) the reference to the delay tween in this logic action will exist until it is
+ // disposed, EVEN if the tween completes. If the tween completes, it goes back into the pool, and then something else will call it, but this logic action's delay tween reference will still be pointing to it.
+ // This becomes a HUGE problem if this logic action's Stop() method is called, because it will then stop the tween that this tween reference is pointing to.
+ // The solution is to comment out the code below. This means that this tween reference will always call it's endhandler, which in this case is either Execute() or ExecuteComplete(). This turns out
+ // to not be a problem, because when a logic set is called to stop, its IsActive flag is set to false, and all Execute() methods in logic actions have to have their parent logic set's IsActive flag to true
+ // in order to run. Therefore, when the tween calls Execute() or ExecuteComplete() nothing will happen.
+ // - This bug kept you confused for almost 5 hours. DO NOT FORGET IT. That is what this long explanation is for.
+ // TL;DR - DO NOT UNCOMMENT THE CODE BELOW OR LOGIC SETS BREAK.
+
+ //if (m_tweenDelay != null)
+ // m_tweenDelay.StopTween(false);
+
+ IAnimateableObj obj = this.ParentLogicSet.ParentGameObj as IAnimateableObj;
+ if (obj != null)
+ obj.StopAnimation();
+ base.Stop();
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ // See above for explanation.
+ //if (m_tweenDelay != null)
+ // m_tweenDelay.StopTween(false);
+
+ //m_tweenDelay = null;
+ //m_blankObj.Dispose();
+ //m_blankObj = null;
+ m_animateableObj = null;
+
+ base.Dispose();
+ }
+ }
+
+ public override object Clone()
+ {
+ return new ChangeSpriteLogicAction(m_spriteName, m_playAnimation, m_loopAnimation);
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/ChangeStateLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/ChangeStateLogicAction.cs
new file mode 100644
index 0000000..4cf4ab2
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/ChangeStateLogicAction.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public class ChangeStateLogicAction : LogicAction
+ {
+ private int m_state;
+
+ public ChangeStateLogicAction(int state)
+ {
+ m_state = state;
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ if (ParentLogicSet.ParentGameObj is IStateObj)
+ (ParentLogicSet.ParentGameObj as IStateObj).State = m_state;
+ base.Execute();
+ }
+ }
+
+ public override object Clone()
+ {
+ return new ChangeStateLogicAction(m_state);
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ base.Dispose();
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/ChangeWeightLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/ChangeWeightLogicAction.cs
new file mode 100644
index 0000000..a5193a7
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/ChangeWeightLogicAction.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public class ChangeWeightLogicAction : LogicAction
+ {
+ private bool m_isWeighted = false;
+
+ public ChangeWeightLogicAction(bool isWeighted)
+ {
+ m_isWeighted = isWeighted;
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ IPhysicsObj physicsObj = ParentLogicSet.ParentGameObj as IPhysicsObj;
+ if (physicsObj != null)
+ physicsObj.IsWeighted = m_isWeighted;
+ base.Execute();
+ }
+ }
+
+ public override object Clone()
+ {
+ return new ChangeWeightLogicAction(m_isWeighted);
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/ChaseLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/ChaseLogicAction.cs
new file mode 100644
index 0000000..fe1e886
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/ChaseLogicAction.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public class ChaseLogicAction : LogicAction
+ {
+ private bool m_moveTowards;
+ private GameObj m_target;
+ private float m_speed;
+ private float m_duration;
+ private float m_durationCounter;
+ private Vector2 m_minRelativePos = Vector2.Zero;
+ private Vector2 m_maxRelativePos = Vector2.Zero;
+ private Vector2 m_storedRelativePos;
+
+ //To stop an object from moving, call this method and set overrideSpeed to 0.
+ public ChaseLogicAction(GameObj target, bool moveTowards, float duration, float overrideSpeed = -1)
+ {
+ m_moveTowards = moveTowards;
+ m_target = target;
+ m_speed = overrideSpeed;
+ m_duration = duration;
+ }
+
+ public ChaseLogicAction(GameObj target, Vector2 minRelativePos, Vector2 maxRelativePos, bool moveTowards, float duration, float overrideSpeed = -1)
+ {
+ m_minRelativePos = minRelativePos;
+ m_maxRelativePos = maxRelativePos;
+ m_moveTowards = moveTowards;
+ m_target = target;
+ m_speed = overrideSpeed;
+ m_duration = duration;
+ }
+
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ if (m_speed == -1)
+ m_speed = this.ParentLogicSet.ParentGameObj.Speed;
+
+ this.ParentLogicSet.ParentGameObj.CurrentSpeed = m_speed;
+
+ if (m_target != null)
+ {
+ m_durationCounter = m_duration;
+
+ m_storedRelativePos = new Vector2(CDGMath.RandomInt((int)m_minRelativePos.X, (int)m_maxRelativePos.X), CDGMath.RandomInt((int)m_minRelativePos.Y, (int)m_maxRelativePos.Y));
+
+ Vector2 seekPosition;
+
+ if (m_moveTowards == true)
+ seekPosition = m_target.Position + m_storedRelativePos;
+ else
+ seekPosition = 1000 * this.ParentLogicSet.ParentGameObj.Position - m_target.Position;
+
+ TurnToFace(seekPosition, this.ParentLogicSet.ParentGameObj.TurnSpeed, 1/60f);
+
+ this.ParentLogicSet.ParentGameObj.HeadingX = (float)Math.Cos(this.ParentLogicSet.ParentGameObj.Orientation);
+ this.ParentLogicSet.ParentGameObj.HeadingY = (float)Math.Sin(this.ParentLogicSet.ParentGameObj.Orientation);
+
+ if (ParentLogicSet.ParentGameObj.LockFlip == false)
+ {
+ if (ParentLogicSet.ParentGameObj.X > m_target.X)
+ ParentLogicSet.ParentGameObj.Flip = Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally;
+ else
+ ParentLogicSet.ParentGameObj.Flip = Microsoft.Xna.Framework.Graphics.SpriteEffects.None;
+ }
+ }
+ base.Execute();
+ }
+ }
+
+ public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
+ {
+ float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;
+ m_durationCounter -= elapsedSeconds;
+ if (m_target != null && m_durationCounter > 0)
+ {
+ Vector2 seekPosition;
+ if (m_moveTowards == true)
+ seekPosition = m_target.Position + m_storedRelativePos;
+ else
+ seekPosition = 2 * this.ParentLogicSet.ParentGameObj.Position - m_target.Position;
+
+ TurnToFace(seekPosition, this.ParentLogicSet.ParentGameObj.TurnSpeed, elapsedSeconds);
+
+ this.ParentLogicSet.ParentGameObj.HeadingX = (float)Math.Cos(this.ParentLogicSet.ParentGameObj.Orientation);
+ this.ParentLogicSet.ParentGameObj.HeadingY = (float)Math.Sin(this.ParentLogicSet.ParentGameObj.Orientation);
+
+ if (ParentLogicSet.ParentGameObj.LockFlip == false)
+ {
+ if (ParentLogicSet.ParentGameObj.X > m_target.X)
+ ParentLogicSet.ParentGameObj.Flip = Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally;
+ else
+ ParentLogicSet.ParentGameObj.Flip = Microsoft.Xna.Framework.Graphics.SpriteEffects.None;
+ }
+ }
+ this.ExecuteNext();
+ base.Update(gameTime);
+ }
+
+ public override void ExecuteNext()
+ {
+ if (m_durationCounter <= 0)
+ base.ExecuteNext();
+ }
+
+
+ public override void Stop()
+ {
+ this.ParentLogicSet.ParentGameObj.CurrentSpeed = 0;
+ base.Stop();
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ m_target = null; // This class is not the class that should be disposing the target.
+ base.Dispose();
+ }
+ }
+
+ public override object Clone()
+ {
+ return new ChaseLogicAction(m_target, m_minRelativePos, m_maxRelativePos, m_moveTowards, m_duration, m_speed);
+ }
+
+ public void TurnToFace(float angle, float turnSpeed, float elapsedSeconds)
+ {
+ float desiredAngle = angle;
+ float difference = MathHelper.WrapAngle(desiredAngle - this.ParentLogicSet.ParentGameObj.Orientation);
+
+ float elapsedTurnSpeed = (turnSpeed * 60) * elapsedSeconds;
+
+ difference = MathHelper.Clamp(difference, -elapsedTurnSpeed, elapsedTurnSpeed);
+ this.ParentLogicSet.ParentGameObj.Orientation = MathHelper.WrapAngle(this.ParentLogicSet.ParentGameObj.Orientation + difference);
+ }
+
+ public void TurnToFace(Vector2 facePosition, float turnSpeed, float elapsedSeconds)
+ {
+ float x = facePosition.X - this.ParentLogicSet.ParentGameObj.Position.X;
+ float y = facePosition.Y - this.ParentLogicSet.ParentGameObj.Position.Y;
+
+ float desiredAngle = (float)Math.Atan2(y, x);
+ float difference = MathHelper.WrapAngle(desiredAngle - this.ParentLogicSet.ParentGameObj.Orientation);
+
+ float elapsedTurnSpeed = (turnSpeed * 60) * elapsedSeconds;
+
+ difference = MathHelper.Clamp(difference, -elapsedTurnSpeed, elapsedTurnSpeed);
+ this.ParentLogicSet.ParentGameObj.Orientation = MathHelper.WrapAngle(this.ParentLogicSet.ParentGameObj.Orientation + difference);
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/DebugTraceLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/DebugTraceLogicAction.cs
new file mode 100644
index 0000000..69bb860
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/DebugTraceLogicAction.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public class DebugTraceLogicAction : LogicAction
+ {
+ private string m_debugText;
+
+ public DebugTraceLogicAction(string debugText)
+ {
+ m_debugText = debugText;
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ Console.WriteLine(m_debugText);
+ base.Execute();
+ }
+ }
+
+ public override object Clone()
+ {
+ return new DebugTraceLogicAction(m_debugText);
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ base.Dispose();
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/DelayLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/DelayLogicAction.cs
new file mode 100644
index 0000000..f1d6d37
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/DelayLogicAction.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Tweener;
+using Tweener.Ease;
+
+namespace DS2DEngine
+{
+ public class DelayLogicAction : LogicAction
+ {
+ private float m_minDelayDuration = 0;
+ private float m_maxDelayDuration = 0;
+ private bool m_useTicks;
+ //private TweenObject m_delayTween;
+ //private BlankObj m_blankObj; // Storing the blank object so that we can dispose it properly later.
+ private float m_delayCounter = 0;
+
+ public DelayLogicAction(float delayInSecs, bool useTicks = false)
+ {
+ m_minDelayDuration = delayInSecs;
+ //m_blankObj = new BlankObj(1, 1);
+ m_useTicks = useTicks;
+ m_delayCounter = 0;
+ }
+
+ public DelayLogicAction(float minDelayInSecs, float maxDelayInSecs, bool useTicks = false)
+ {
+ m_minDelayDuration = minDelayInSecs;
+ m_maxDelayDuration = maxDelayInSecs;
+ //m_blankObj = new BlankObj(1, 1);
+ m_useTicks = useTicks;
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ SequenceType = Types.Sequence.Serial;
+
+ if (m_maxDelayDuration > m_minDelayDuration)
+ m_minDelayDuration = CDGMath.RandomFloat(m_minDelayDuration, m_maxDelayDuration);
+
+ m_delayCounter = m_minDelayDuration;
+ base.Execute();
+ }
+ }
+
+ public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
+ {
+ m_delayCounter -= (float)gameTime.ElapsedGameTime.TotalSeconds;
+ this.ExecuteNext();
+ base.Update(gameTime);
+ }
+
+ public override void ExecuteNext()
+ {
+ if (m_delayCounter <= 0)
+ base.ExecuteNext();
+ }
+
+ //public override void Execute()
+ //{
+ // if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ // {
+ // SequenceType = Types.Sequence.Serial;
+
+ // if (m_maxDelayDuration > m_minDelayDuration)
+ // m_minDelayDuration = CDGMath.RandomFloat(m_minDelayDuration, m_maxDelayDuration);
+
+ // m_delayTween = Tween.To(m_blankObj, m_minDelayDuration, Linear.EaseNone, "X", "1");
+ // m_delayTween.UseTicks = m_useTicks;
+ // base.Execute();
+ // }
+ //}
+
+ //public override void ExecuteNext()
+ //{
+ // if (m_delayTween != null)
+ // {
+ // if (NextLogicAction != null)
+ // m_delayTween.EndHandler(NextLogicAction, "Execute");
+ // else
+ // m_delayTween.EndHandler(ParentLogicSet, "ExecuteComplete");
+ // }
+ // else
+ // base.ExecuteNext();
+ // //base.ExecuteNext(); Must override base.ExecuteNext() entirely.
+ //}
+
+ public override void Stop()
+ {
+ // Okay. Big problem with delay tweens. Because logic actions are never disposed in-level (due to garbage collection concerns) the reference to the delay tween in this logic action will exist until it is
+ // disposed, EVEN if the tween completes. If the tween completes, it goes back into the pool, and then something else will call it, but this logic action's delay tween reference will still be pointing to it.
+ // This becomes a HUGE problem if this logic action's Stop() method is called, because it will then stop the tween that this tween reference is pointing to.
+ // The solution is to comment out the code below. This means that this tween reference will always call it's endhandler, which in this case is either Execute() or ExecuteComplete(). This turns out
+ // to not be a problem, because when a logic set is called to stop, its IsActive flag is set to false, and all Execute() methods in logic actions have to have their parent logic set's IsActive flag to true
+ // in order to run. Therefore, when the tween calls Execute() or ExecuteComplete() nothing will happen.
+ // - This bug kept you confused for almost 5 hours. DO NOT FORGET IT. That is what this long explanation is for.
+ // TL;DR - DO NOT UNCOMMENT THE CODE BELOW OR LOGIC SETS BREAK.
+
+ //if (m_delayTween != null)
+ // m_delayTween.StopTween(false);
+
+ base.Stop();
+ }
+
+ public override object Clone()
+ {
+ return new DelayLogicAction(m_minDelayDuration, m_maxDelayDuration, m_useTicks);
+ }
+
+ //public override void Dispose()
+ //{
+ // if (IsDisposed == false)
+ // {
+ // // See above for explanation.
+ // //if (m_delayTween != null)
+ // // m_delayTween.StopTween(false);
+
+ // m_delayTween = null;
+ // m_blankObj.Dispose();
+ // m_blankObj = null;
+ // base.Dispose();
+ // }
+ //}
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/MoveDirectionLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/MoveDirectionLogicAction.cs
new file mode 100644
index 0000000..784f0de
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/MoveDirectionLogicAction.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public class MoveDirectionLogicAction : LogicAction
+ {
+ private Vector2 m_direction;
+ private float m_speed;
+ private bool m_moveBasedOnFlip = false;
+
+ //To stop an object from moving, call this method and set overrideSpeed to 0.
+ public MoveDirectionLogicAction(Vector2 direction, float overrideSpeed = -1)
+ {
+ m_direction = direction;
+ m_speed = overrideSpeed;
+ m_direction.Normalize();
+ m_moveBasedOnFlip = false;
+ }
+
+ // Move an object based on its flip.
+ public MoveDirectionLogicAction(float overrideSpeed = -1)
+ {
+ m_moveBasedOnFlip = true;
+ m_speed = overrideSpeed;
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ if (m_speed == -1)
+ m_speed = this.ParentLogicSet.ParentGameObj.Speed;
+
+ if (m_moveBasedOnFlip == false)
+ {
+ this.ParentLogicSet.ParentGameObj.CurrentSpeed = m_speed;
+ this.ParentLogicSet.ParentGameObj.Heading = m_direction;
+ }
+ else
+ {
+ this.ParentLogicSet.ParentGameObj.CurrentSpeed = m_speed;
+ if (this.ParentLogicSet.ParentGameObj.Flip == Microsoft.Xna.Framework.Graphics.SpriteEffects.None)
+ this.ParentLogicSet.ParentGameObj.Heading = new Vector2(1, 0);
+ else
+ this.ParentLogicSet.ParentGameObj.Heading = new Vector2(-1, 0);
+ }
+
+ base.Execute();
+ }
+ /* Vector2 seekPosition;
+
+ if (m_moveTowards == true)
+ seekPosition = m_target.Position;
+ else
+ seekPosition = 2 * this.ParentLogicSet.ParentGameObj.Position - m_target.Position;
+
+ TurnToFace(seekPosition, this.ParentLogicSet.ParentGameObj.TurnSpeed);
+
+ this.ParentLogicSet.ParentGameObj.HeadingX = (float)Math.Cos(this.ParentLogicSet.ParentGameObj.Orientation);
+ this.ParentLogicSet.ParentGameObj.HeadingY = (float)Math.Sin(this.ParentLogicSet.ParentGameObj.Orientation);
+ */
+
+ }
+
+ public override void Stop()
+ {
+ this.ParentLogicSet.ParentGameObj.CurrentSpeed = 0;
+ base.Stop();
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ base.Dispose();
+ }
+ }
+
+ public override object Clone()
+ {
+ if (m_direction == Vector2.Zero)
+ return new MoveDirectionLogicAction(m_speed);
+ return new MoveDirectionLogicAction(m_direction, m_speed);
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/MoveLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/MoveLogicAction.cs
new file mode 100644
index 0000000..cc71e72
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/MoveLogicAction.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public class MoveLogicAction : LogicAction
+ {
+ private bool m_moveTowards;
+ private GameObj m_target;
+ private float m_speed;
+
+ //To stop an object from moving, call this method and set overrideSpeed to 0.
+ public MoveLogicAction(GameObj target, bool moveTowards, float overrideSpeed = -1)
+ {
+ m_moveTowards = moveTowards;
+ m_target = target;
+ m_speed = overrideSpeed;
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ if (m_speed == -1)
+ m_speed = this.ParentLogicSet.ParentGameObj.Speed;
+
+ this.ParentLogicSet.ParentGameObj.CurrentSpeed = m_speed;
+
+ if (m_target != null)
+ {
+ Vector2 seekPosition;
+
+ if (m_moveTowards == true)
+ seekPosition = m_target.Position;
+ else
+ seekPosition = 2 * this.ParentLogicSet.ParentGameObj.Position - m_target.Position;
+
+ TurnToFace(seekPosition, this.ParentLogicSet.ParentGameObj.TurnSpeed);
+
+ this.ParentLogicSet.ParentGameObj.HeadingX = (float)Math.Cos(this.ParentLogicSet.ParentGameObj.Orientation);
+ this.ParentLogicSet.ParentGameObj.HeadingY = (float)Math.Sin(this.ParentLogicSet.ParentGameObj.Orientation);
+
+ if (ParentLogicSet.ParentGameObj.LockFlip == false)
+ {
+ if (ParentLogicSet.ParentGameObj.X > m_target.X)
+ ParentLogicSet.ParentGameObj.Flip = Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally;
+ else
+ ParentLogicSet.ParentGameObj.Flip = Microsoft.Xna.Framework.Graphics.SpriteEffects.None;
+ }
+ }
+ base.Execute();
+ }
+ }
+
+ public override void Stop()
+ {
+ this.ParentLogicSet.ParentGameObj.CurrentSpeed = 0;
+ base.Stop();
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ m_target = null; // This class is not the class that should be disposing the target.
+ base.Dispose();
+ }
+ }
+
+ public override object Clone()
+ {
+ return new MoveLogicAction(m_target, m_moveTowards, m_speed);
+ }
+
+ public void TurnToFace(float angle, float turnSpeed)
+ {
+ float desiredAngle = angle;
+ float difference = MathHelper.WrapAngle(desiredAngle - this.ParentLogicSet.ParentGameObj.Orientation);
+
+ difference = MathHelper.Clamp(difference, -turnSpeed, turnSpeed);
+ this.ParentLogicSet.ParentGameObj.Orientation = MathHelper.WrapAngle(this.ParentLogicSet.ParentGameObj.Orientation + difference);
+ }
+
+ public void TurnToFace(Vector2 facePosition, float turnSpeed)
+ {
+ float x = facePosition.X - this.ParentLogicSet.ParentGameObj.Position.X;
+ float y = facePosition.Y - this.ParentLogicSet.ParentGameObj.Position.Y;
+
+ float desiredAngle = (float)Math.Atan2(y, x);
+ float difference = MathHelper.WrapAngle(desiredAngle - this.ParentLogicSet.ParentGameObj.Orientation);
+
+ difference = MathHelper.Clamp(difference, -turnSpeed, turnSpeed);
+ this.ParentLogicSet.ParentGameObj.Orientation = MathHelper.WrapAngle(this.ParentLogicSet.ParentGameObj.Orientation + difference);
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/Play3DSoundLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/Play3DSoundLogicAction.cs
new file mode 100644
index 0000000..d2fa913
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/Play3DSoundLogicAction.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public class Play3DSoundLogicAction : LogicAction
+ {
+ private string[] m_sounds;
+ private GameObj m_listener;
+ private GameObj m_emitter;
+
+ public Play3DSoundLogicAction(GameObj emitter, GameObj listener, params string[] sounds)
+ {
+ m_sounds = sounds;
+ m_emitter = emitter;
+ m_listener = listener;
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ SoundManager.Play3DSound(m_emitter, m_listener, m_sounds);
+ base.Execute();
+ }
+ }
+
+ public override object Clone()
+ {
+ return new Play3DSoundLogicAction(m_emitter, m_listener, m_sounds);
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ m_listener = null;
+ m_emitter = null;
+ base.Dispose();
+ }
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/PlayAnimationLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/PlayAnimationLogicAction.cs
new file mode 100644
index 0000000..ceb731f
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/PlayAnimationLogicAction.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Tweener;
+using Tweener.Ease;
+
+namespace DS2DEngine
+{
+ public class PlayAnimationLogicAction : LogicAction
+ {
+ private int m_startFrame;
+ private int m_endFrame;
+ private bool m_loop;
+
+ private string m_startLabel = "";
+ private string m_endLabel = "";
+ private bool m_useLabel = false;
+
+ //private TweenObject m_tweenDelay;
+ //private BlankObj m_blankObj;
+ private IAnimateableObj m_animateableObj;
+
+ public PlayAnimationLogicAction(int startingFrame, int endFrame, bool loopAnimation = false)
+ {
+ m_startFrame = startingFrame;
+ m_endFrame = endFrame;
+ m_loop = loopAnimation;
+ //m_blankObj = new BlankObj(1, 1);
+ }
+
+ public PlayAnimationLogicAction(string startLabel, string endLabel, bool loopAnimation = false)
+ {
+ m_startLabel = startLabel;
+ m_endLabel = endLabel;
+ m_loop = loopAnimation;
+ m_useLabel = true;
+ //m_blankObj = new BlankObj(1, 1);
+ }
+
+ public PlayAnimationLogicAction(bool loopAnimation = true)
+ {
+ m_loop = loopAnimation;
+ m_startFrame = 1;
+ m_endFrame = 1000;
+ //m_blankObj = new BlankObj(1, 1);
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ //IAnimateableObj animateableObj = this.ParentLogicSet.ParentGameObj as IAnimateableObj;
+ m_animateableObj = this.ParentLogicSet.ParentGameObj as IAnimateableObj;
+
+ if (m_animateableObj != null)
+ {
+ if (m_useLabel == true)
+ {
+ m_startFrame = m_animateableObj.FindLabelIndex(m_startLabel);
+ m_endFrame = m_animateableObj.FindLabelIndex(m_endLabel);
+ if (m_startFrame == -1)
+ throw new Exception("Could not find starting label " + m_startLabel);
+ else if (m_endFrame == -1)
+ throw new Exception("Could not find ending label " + m_endLabel);
+ }
+
+ m_animateableObj.PlayAnimation(m_startFrame, m_endFrame, m_loop);
+ //if (SequenceType == Types.Sequence.Serial && animateableObj.IsLooping == false)
+ //{
+ // m_startFrame = (m_startFrame - 1) * animateableObj.AnimationSpeed + 1;
+ // m_endFrame = m_endFrame * animateableObj.AnimationSpeed;
+ // if (m_endFrame > animateableObj.TotalFrames)
+ // m_endFrame = animateableObj.TotalFrames;
+
+ // m_tweenDelay = Tween.To(m_blankObj, m_endFrame - m_startFrame, Linear.EaseNone, "X", "1");
+ // m_tweenDelay.UseTicks = true;
+ //}
+ }
+
+ base.Execute();
+ }
+ }
+
+ public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
+ {
+ this.ExecuteNext();
+ base.Update(gameTime);
+ }
+
+ public override void ExecuteNext()
+ {
+ if (m_loop == true || m_loop == true || (m_animateableObj != null && SequenceType == Types.Sequence.Serial && m_animateableObj.IsLooping == false && m_animateableObj.IsAnimating == false))
+ {
+ base.ExecuteNext();
+ }
+ }
+
+
+ //public override void ExecuteNext()
+ //{
+ // if (m_tweenDelay != null)
+ // {
+ // if (NextLogicAction != null)
+ // m_tweenDelay.EndHandler(NextLogicAction, "Execute");
+ // else
+ // m_tweenDelay.EndHandler(ParentLogicSet, "ExecuteComplete");
+ // }
+ // else
+ // base.ExecuteNext();
+ //}
+
+ public override void Stop()
+ {
+ // Okay. Big problem with delay tweens. Because logic actions are never disposed in-level (due to garbage collection concerns) the reference to the delay tween in this logic action will exist until it is
+ // disposed, EVEN if the tween completes. If the tween completes, it goes back into the pool, and then something else will call it, but this logic action's delay tween reference will still be pointing to it.
+ // This becomes a HUGE problem if this logic action's Stop() method is called, because it will then stop the tween that this tween reference is pointing to.
+ // The solution is to comment out the code below. This means that this tween reference will always call it's endhandler, which in this case is either Execute() or ExecuteComplete(). This turns out
+ // to not be a problem, because when a logic set is called to stop, its IsActive flag is set to false, and all Execute() methods in logic actions have to have their parent logic set's IsActive flag to true
+ // in order to run. Therefore, when the tween calls Execute() or ExecuteComplete() nothing will happen.
+ // - This bug kept you confused for almost 5 hours. DO NOT FORGET IT. That is what this long explanation is for.
+ // TL;DR - DO NOT UNCOMMENT THE CODE BELOW OR LOGIC SETS BREAK.
+
+ //if (m_tweenDelay != null)
+ // m_tweenDelay.StopTween(false);
+
+ IAnimateableObj obj = this.ParentLogicSet.ParentGameObj as IAnimateableObj;
+ if (obj != null)
+ obj.StopAnimation();
+ base.Stop();
+ }
+
+ public override object Clone()
+ {
+ if (m_useLabel == true)
+ return new PlayAnimationLogicAction(m_startLabel, m_endLabel, m_loop);
+ else
+ return new PlayAnimationLogicAction(m_startFrame, m_endFrame, m_loop);
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ // See above for explanation.
+ //if (m_tweenDelay != null)
+ // m_tweenDelay.StopTween(false);
+
+ //m_tweenDelay = null;
+ //m_blankObj.Dispose();
+ //m_blankObj = null;
+ m_animateableObj = null;
+ base.Dispose();
+ }
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/PlaySoundLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/PlaySoundLogicAction.cs
new file mode 100644
index 0000000..2a4fb3d
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/PlaySoundLogicAction.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public class PlaySoundLogicAction : LogicAction
+ {
+ private string[] m_sounds;
+
+ public PlaySoundLogicAction(params string[] sounds)
+ {
+ m_sounds = sounds;
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ SoundManager.PlaySound(m_sounds);
+ base.Execute();
+ }
+ }
+
+ public override object Clone()
+ {
+ return new PlaySoundLogicAction(m_sounds);
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/RunFunctionLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/RunFunctionLogicAction.cs
new file mode 100644
index 0000000..a3a53b6
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/RunFunctionLogicAction.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Reflection;
+
+namespace DS2DEngine
+{
+ public class RunFunctionLogicAction : LogicAction
+ {
+ MethodInfo m_methodInfo;
+ object m_methodObject;
+ object[] m_args;
+ string m_functionName;
+ Type m_objectType;
+
+ public RunFunctionLogicAction(object methodObject, string functionName, params object[] args)
+ {
+ if (methodObject == null)
+ throw new Exception("methodObject cannot be null");
+
+ m_methodInfo = methodObject.GetType().GetMethod(functionName);
+
+ if (m_methodInfo == null)
+ throw new Exception("Function " + functionName + " not found in class " + methodObject.GetType().ToString());
+
+ m_methodObject = methodObject;
+ m_args = args;
+ m_functionName = functionName;
+ }
+
+ public RunFunctionLogicAction(Type objectType, string functionName, params object[] args)
+ {
+ Type[] argList = new Type[args.Length];
+ for (int i = 0; i < args.Length; i++)
+ argList[i] = args[i].GetType();
+
+ m_methodInfo = objectType.GetMethod(functionName, argList);
+ m_args = args;
+
+ if (m_methodInfo == null)
+ {
+ m_methodInfo = objectType.GetMethod(functionName, new Type[] { args[0].GetType().MakeArrayType() });
+ m_args = new object[1];
+ m_args[0] = args;
+ }
+
+ if (m_methodInfo == null)
+ throw new Exception("Function " + functionName + " not found in class " + objectType.ToString());
+
+ m_methodObject = null;
+ m_functionName = functionName;
+ m_objectType = objectType;
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ m_methodInfo.Invoke(m_methodObject, m_args);
+ base.Execute();
+ }
+ }
+
+ public override object Clone()
+ {
+ if (m_methodObject != null)
+ return new RunFunctionLogicAction(m_methodObject, m_functionName, m_args);
+ else
+ return new RunFunctionLogicAction(m_objectType, m_functionName, m_args);
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ m_methodInfo = null;
+ m_methodObject = null;
+ if (m_args != null)
+ Array.Clear(m_args, 0, m_args.Length);
+ m_args = null;
+ m_objectType = null;
+ base.Dispose();
+ }
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/StopAnimationLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/StopAnimationLogicAction.cs
new file mode 100644
index 0000000..e18d81a
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/StopAnimationLogicAction.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Tweener;
+using Tweener.Ease;
+
+namespace DS2DEngine
+{
+ public class StopAnimationLogicAction : LogicAction
+ {
+ public StopAnimationLogicAction()
+ {
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ IAnimateableObj obj = this.ParentLogicSet.ParentGameObj as IAnimateableObj;
+ if (obj != null)
+ obj.StopAnimation();
+
+ base.Execute();
+ }
+ }
+
+ public override object Clone()
+ {
+ return new StopAnimationLogicAction();
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ base.Dispose();
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Actions/TeleportLogicAction.cs b/DS2DEngine/src/AI Logic/Actions/TeleportLogicAction.cs
new file mode 100644
index 0000000..284ad89
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Actions/TeleportLogicAction.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public class TeleportLogicAction : LogicAction
+ {
+ GameObj m_target = null;
+ Vector2 m_newPosition;
+ Vector2 m_minPosition;
+ Vector2 m_maxPosition;
+
+ public TeleportLogicAction(GameObj target, Vector2 relativePos)
+ {
+ m_target = target;
+ m_newPosition = relativePos;
+ }
+
+ public TeleportLogicAction(GameObj target, Vector2 minPos, Vector2 maxPos)
+ {
+ m_target = target;
+ m_minPosition = minPos;
+ m_maxPosition = maxPos;
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ if (m_minPosition != m_maxPosition)
+ m_newPosition = new Vector2(CDGMath.RandomInt((int)m_minPosition.X, (int)m_maxPosition.X), CDGMath.RandomInt((int)m_minPosition.Y, (int)m_maxPosition.Y));
+
+ if (m_target == null)
+ ParentLogicSet.ParentGameObj.Position = m_newPosition;
+ else
+ ParentLogicSet.ParentGameObj.Position = m_target.Position + m_newPosition;
+ base.Execute();
+ }
+ }
+
+ public override object Clone()
+ {
+ if (m_minPosition != m_maxPosition)
+ return new TeleportLogicAction(m_target, m_minPosition, m_maxPosition);
+ else
+ return new TeleportLogicAction(m_target, m_newPosition);
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == true)
+ {
+ m_target = null;
+ base.Dispose();
+ }
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Base Objects/LogicAction.cs b/DS2DEngine/src/AI Logic/Base Objects/LogicAction.cs
new file mode 100644
index 0000000..0756c9f
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Base Objects/LogicAction.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public abstract class LogicAction : IDisposableObj, ICloneable
+ {
+ // A linked list of trigger actions.
+ public LogicSet ParentLogicSet = null;
+ public LogicAction PreviousLogicAction = null;
+ public LogicAction NextLogicAction = null;
+
+ public string Tag { get; set; }
+ public Types.Sequence SequenceType = Types.Sequence.Serial;
+
+ private bool m_isDisposed = false;
+ public bool IsDisposed { get { return m_isDisposed; } }
+
+ // All classes that override Execute() must be contained in the following if statement:
+ // if (this.ParentLogicSet.IsActive == true) { }
+ public virtual void Execute()
+ {
+ ParentLogicSet.ActiveLogicAction = this;
+
+ if (SequenceType == Types.Sequence.Parallel && NextLogicAction != null)
+ NextLogicAction.Execute();
+ else if (SequenceType == Types.Sequence.Serial)
+ ExecuteNext();
+ else if (SequenceType == Types.Sequence.Parallel && NextLogicAction == null)
+ ParentLogicSet.ExecuteComplete();
+ }
+
+ public virtual void ExecuteNext()
+ {
+ if (NextLogicAction != null)
+ NextLogicAction.Execute();
+ else
+ ParentLogicSet.ExecuteComplete();
+ }
+
+ public virtual void Update(GameTime gameTime) { }
+
+ public virtual void Stop()
+ {
+ //Runs through all the logic actions and runs their Stop() method.
+ if (NextLogicAction != null)
+ NextLogicAction.Stop();
+ }
+
+ public virtual void Dispose()
+ {
+ ParentLogicSet = null;
+ NextLogicAction = null;
+ PreviousLogicAction = null;
+ m_isDisposed = true;
+ }
+
+ public abstract object Clone();
+
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Base Objects/LogicBlock.cs b/DS2DEngine/src/AI Logic/Base Objects/LogicBlock.cs
new file mode 100644
index 0000000..498fdd8
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Base Objects/LogicBlock.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public class LogicBlock : IDisposableObj
+ {
+ List m_logicSetList;
+ private bool m_isActive;
+ private int[] m_percentParams;
+
+ private LogicSet m_activeLS = null;
+
+ private bool m_isDisposed = false;
+
+ public LogicBlock()
+ {
+ m_logicSetList = new List();
+ m_isActive = false;
+ }
+
+ public void RunLogicBlock(params int[] percentParams)
+ {
+ m_percentParams = percentParams;
+
+ if (percentParams.Length != m_logicSetList.Count)
+ throw new Exception("Number of percentage parameters (" + percentParams.Length + ") does not match the number of logic sets in this block (" + m_logicSetList.Count + ").");
+
+ m_isActive = true;
+
+ int chance = CDGMath.RandomInt(1, 100);
+ int totalChance = 0;
+
+ for (int i = 0; i < m_logicSetList.Count; i++)
+ {
+ // Automatically execute this logic set if it is the last one in the list.
+ if (i == m_logicSetList.Count - 1)
+ {
+ m_activeLS = m_logicSetList[i];
+ m_logicSetList[i].Execute();
+ break;
+ }
+ else
+ {
+ totalChance += percentParams[i];
+ if (chance <= totalChance)
+ {
+ m_activeLS = m_logicSetList[i];
+ m_logicSetList[i].Execute();
+ break;
+ }
+ }
+ }
+ }
+
+ public void Update(GameTime gameTime)
+ {
+ if (IsActive == true)
+ {
+ foreach (LogicSet set in m_logicSetList)
+ {
+ if (set.IsActive == true)
+ set.Update(gameTime);
+ }
+ }
+ }
+
+ public void StopLogicBlock()
+ {
+ foreach (LogicSet set in m_logicSetList)
+ {
+ set.Stop();
+ }
+ LogicBlockComplete();
+ }
+
+ public void LogicBlockComplete()
+ {
+ m_isActive = false;
+ }
+
+ public void AddLogicSet(params LogicSet[] logicSet)
+ {
+ foreach (LogicSet set in logicSet)
+ {
+ LogicSet setToAdd = set.Clone();
+ m_logicSetList.Add(setToAdd);
+ setToAdd.ParentLogicBlock = this;
+ }
+ }
+
+ // Doesn't work because logicsets are cloned so the comparison will never be made.
+ //public void RemoveLogicSet(params LogicSet[] logicSet)
+ //{
+ // foreach (LogicSet set in logicSet)
+ // {
+ // m_logicSetList.Remove(set);
+ // set.ParentLogicBlock = null;
+ // }
+ //}
+
+ public void ClearAllLogicSets()
+ {
+ foreach (LogicSet set in m_logicSetList)
+ {
+ set.ParentLogicBlock = null;
+ }
+ m_logicSetList.Clear();
+ }
+
+ public bool IsActive
+ {
+ get { return m_isActive; }
+ }
+
+ public void Dispose()
+ {
+ if (m_isDisposed == false)
+ {
+ m_isDisposed = true;
+ foreach (LogicSet set in m_logicSetList)
+ set.Dispose();
+ m_logicSetList.Clear();
+ m_logicSetList = null;
+ m_activeLS = null; // No need to dispose since it should already be disposed at this point.
+ m_isActive = false;
+ }
+ }
+
+ public bool IsDisposed
+ {
+ get { return m_isDisposed; }
+ }
+
+ public List LogicSetList
+ {
+ get { return m_logicSetList; }
+ }
+
+ public LogicSet ActiveLS
+ {
+ get { return m_activeLS; }
+ }
+ }
+}
diff --git a/DS2DEngine/src/AI Logic/Base Objects/LogicSet.cs b/DS2DEngine/src/AI Logic/Base Objects/LogicSet.cs
new file mode 100644
index 0000000..d5112be
--- /dev/null
+++ b/DS2DEngine/src/AI Logic/Base Objects/LogicSet.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public class LogicSet : IDisposableObj
+ {
+ private LogicAction m_firstLogicNode;
+ private LogicAction m_currentLogicNode;
+ private LogicAction m_lastLogicNode;
+
+ private bool m_isActive = false;
+ private GameObj m_parentObject;
+ public LogicBlock ParentLogicBlock { get; set; }
+
+ private bool m_isDisposed = false;
+ public int Tag = 0;
+
+ public LogicSet(GameObj parentObj)
+ {
+ m_parentObject = parentObj;
+ }
+
+ public void AddAction(LogicAction logicAction, Types.Sequence sequenceType = Types.Sequence.Serial)
+ {
+ logicAction.SequenceType = sequenceType;
+ logicAction.ParentLogicSet = this;
+
+ if (m_firstLogicNode == null)
+ m_firstLogicNode = logicAction;
+ else
+ m_currentLogicNode.NextLogicAction = logicAction;
+
+ m_currentLogicNode = logicAction;
+ m_lastLogicNode = m_currentLogicNode;
+ }
+
+ public void Execute()
+ {
+ if (m_lastLogicNode == null)
+ throw new Exception("Cannot execute logic set. Call CompleteAddAction() first.");
+
+ m_isActive = true;
+ m_firstLogicNode.Execute();
+ }
+
+ public void ExecuteComplete()
+ {
+ m_isActive = false;
+ //Console.WriteLine("logic set complete");
+ if (ParentLogicBlock != null && ParentLogicBlock.ActiveLS == this)
+ {
+ ParentLogicBlock.LogicBlockComplete();
+ }
+ }
+
+ public void Stop()
+ {
+ m_isActive = false;
+ m_firstLogicNode.Stop();
+ }
+
+ public void Update(GameTime gameTime)
+ {
+ ActiveLogicAction.Update(gameTime);
+ }
+
+ public LogicSet Clone()
+ {
+ LogicSet lsToReturn = new LogicSet(m_parentObject);
+ lsToReturn.AddAction(m_firstLogicNode.Clone() as LogicAction, m_firstLogicNode.SequenceType);
+ m_currentLogicNode = m_firstLogicNode;
+ while (m_currentLogicNode.NextLogicAction != null)
+ {
+ m_currentLogicNode = m_currentLogicNode.NextLogicAction;
+ lsToReturn.AddAction(m_currentLogicNode.Clone() as LogicAction, m_currentLogicNode.SequenceType);
+ }
+ m_currentLogicNode = m_lastLogicNode;
+ lsToReturn.Tag = this.Tag;
+ return lsToReturn;
+ }
+
+ public bool IsActive
+ {
+ get { return m_isActive; }
+ }
+
+ public GameObj ParentGameObj
+ {
+ get { return m_parentObject; }
+ }
+
+ public LogicAction ActiveLogicAction
+ {
+ get
+ {
+ if (IsActive == false)
+ return null;
+ else
+ return m_currentLogicNode;
+ }
+ set { m_currentLogicNode = value; }
+ }
+
+ public void Dispose()
+ {
+ if (m_isDisposed == false)
+ {
+ m_isDisposed = true;
+ m_currentLogicNode = m_firstLogicNode;
+ while (m_currentLogicNode != null)
+ {
+ LogicAction nextNode = m_currentLogicNode.NextLogicAction;
+ m_currentLogicNode.Dispose();
+ m_currentLogicNode = nextNode;
+ }
+ m_isActive = false;
+ }
+ }
+
+ public bool IsDisposed
+ {
+ get { return m_isDisposed; }
+ }
+ }
+}
diff --git a/DS2DEngine/src/CDGMath.cs b/DS2DEngine/src/CDGMath.cs
new file mode 100644
index 0000000..4f77c90
--- /dev/null
+++ b/DS2DEngine/src/CDGMath.cs
@@ -0,0 +1,259 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public class CDGMath
+ {
+ private static Random m_randomSeed = new Random();
+
+ public static float AngleBetweenPts(Vector2 pt1, Vector2 pt2)
+ {
+ float x1 = pt2.X - pt1.X;
+ float y1 = pt2.Y - pt1.Y;
+ float desiredAngle = (float)Math.Atan2(y1,x1);
+ float difference = MathHelper.WrapAngle(desiredAngle);
+ return MathHelper.ToDegrees(difference);
+ }
+
+ public static float VectorToAngle(Vector2 pt)
+ {
+ return AngleBetweenPts(Vector2.Zero, pt);
+ }
+
+ public static Vector2 AngleToVector(float angle)
+ {
+ angle = MathHelper.ToRadians(angle);
+ return new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
+ }
+
+ public static Vector2 VectorBetweenPts(Vector2 pt1, Vector2 pt2)
+ {
+ return new Vector2(pt2.X - pt1.X, pt2.Y - pt1.Y);
+ }
+
+ public static float DistanceBetweenPts(Vector2 pt1, Vector2 pt2)
+ {
+ float dx = pt2.X - pt1.X;
+ float dy = pt2.Y - pt1.Y;
+ return (float)Math.Sqrt(dx* dx + dy * dy);
+ }
+
+ public static float DotProduct(Vector2 pt1, Vector2 pt2)
+ {
+ return pt1.X * pt2.X + pt1.Y * pt2.Y;
+ }
+
+ public static Vector2 RotatedPoint(Vector2 pt, float rotationAngle)
+ {
+ float radianPt = MathHelper.ToRadians(rotationAngle);
+ double cos = Math.Cos(radianPt);
+ double sin = Math.Sin(radianPt);
+ float x2 = (float)(cos * pt.X - sin * pt.Y);
+ float y2 = (float)(sin * pt.X + cos * pt.Y);
+ return new Vector2(x2, y2);
+ }
+
+ public static Vector2 RotatedPoint(Vector2 thePoint, Vector2 theOrigin, float theRotation)
+ {
+ theRotation = MathHelper.ToRadians(theRotation);
+ double cos = Math.Cos(theRotation);
+ double sin = Math.Sin(theRotation);
+ Vector2 aTranslatedPoint = new Vector2();
+ aTranslatedPoint.X = (float)(theOrigin.X + (thePoint.X - theOrigin.X) * cos
+ - (thePoint.Y - theOrigin.Y) * sin);
+ aTranslatedPoint.Y = (float)(theOrigin.Y + (thePoint.Y - theOrigin.Y) * cos
+ + (thePoint.X - theOrigin.X) * sin);
+ return aTranslatedPoint;
+ }
+
+ public static string ConvertToHMS(uint milliseconds)
+ {
+ uint mills = milliseconds;
+ uint hours = ((mills/1000)/3600);
+ uint mins = ((mills/1000)/60) % 60;
+ uint secs = ((mills/1000) % 60);
+
+ string hoursString;
+ string minsString;
+ string secsString;
+
+ if (hours < 10) hoursString = "0" + hours.ToString();
+ else hoursString = hours.ToString();
+
+ if (mins < 10) minsString = "0" + mins.ToString();
+ else minsString = mins.ToString();
+
+ if (secs < 10) secsString = "0" + secs.ToString();
+ else secsString = secs.ToString();
+
+ return (hoursString + ":" + minsString + ":" + secsString);
+ }
+
+ ///
+ /// Returns a random positive integer.
+ ///
+ /// returns a random positive integer.
+ public static int RandomInt()
+ {
+ return m_randomSeed.Next();
+ }
+
+ public static int RandomInt(int min, int max)
+ {
+ return m_randomSeed.Next(min, max + 1); // Plus 1 because Next(int, int) returns a number less than maxvalue.
+ }
+
+ ///
+ /// Returns a random float from 0.0 to 1.0.
+ ///
+ /// Returns a random float from 0.0 to 1.0.
+ public static float RandomFloat()
+ {
+ return (float)m_randomSeed.NextDouble();
+ }
+
+ public static float RandomFloat(float min, float max)
+ {
+ double range = (double)(max - min);
+ double sample = m_randomSeed.NextDouble();
+ double scaled = (sample * range) + min;
+ return (float)scaled;
+ }
+ /*
+ public static float RandomMinMaxDecimal(float min, float max)
+ {
+ if (max < min) throw new Exception("CDGMath.RandomMinMax(): Cannot create random number when max is less than min.");
+ Random rand = new Random();
+ double range = (double)(max - min);
+ double sample = rand.NextDouble();
+ double scaled = (sample * range) + min;
+ return (float)scaled;
+ }*/
+
+ public static int RandomPlusMinus()
+ {
+ /*var rand : Number = Math.random() * 2;
+ if (rand < 1) return -1
+ else return 1;*/
+ int rand = RandomInt(0, 2);
+ if (rand < 1) return -1;
+ return 1;
+ }
+
+ public static void TurnToFace(GameObj obj, float angle)
+ {
+ float desiredAngle = angle;
+ float difference = MathHelper.WrapAngle(desiredAngle - obj.Orientation);
+
+ difference = MathHelper.Clamp(difference, -obj.TurnSpeed, obj.TurnSpeed);
+ obj.Orientation = MathHelper.WrapAngle(obj.Orientation + difference);
+
+ obj.HeadingX = (float)Math.Cos(obj.Orientation);
+ obj.HeadingY = (float)Math.Sin(obj.Orientation);
+ }
+
+ public static void TurnToFace(GameObj obj, Vector2 facePosition)
+ {
+ float x = facePosition.X - obj.Position.X;
+ float y = facePosition.Y - obj.Position.Y;
+
+ float desiredAngle = (float)Math.Atan2(y, x);
+ float difference = MathHelper.WrapAngle(desiredAngle - obj.Orientation);
+
+ difference = MathHelper.Clamp(difference, -obj.TurnSpeed, obj.TurnSpeed);
+ obj.Orientation = MathHelper.WrapAngle(obj.Orientation + difference);
+
+ obj.HeadingX = (float)Math.Cos(obj.Orientation);
+ obj.HeadingY = (float)Math.Sin(obj.Orientation);
+ }
+
+ // Placeholder code for bezier curves.
+ // t represents a time between 0 and 1.
+ public static Vector2 GetBezierPoint(float t, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3)
+ {
+ float cx = 3 * (p1.X - p0.X);
+ float cy = 3 * (p1.Y - p0.Y);
+
+ float bx = 3 * (p2.X - p1.X) - cx;
+ float by = 3 * (p2.Y - p1.Y) - cy;
+
+ float ax = p3.X - p0.X - cx - bx;
+ float ay = p3.Y - p0.Y - cy - by;
+
+ float Cube = t * t * t;
+ float Square = t * t;
+
+ float resX = (ax * Cube) + (bx * Square) + (cx * t) + p0.X;
+ float resY = (ay * Cube) + (by * Square) + (cy * t) + p0.Y;
+
+ return new Vector2(resX, resY);
+ }
+
+ public static Vector2 GetCirclePosition(float angle, float distance, Vector2 centre)
+ {
+ angle = MathHelper.ToRadians(angle);
+ return new Vector2((float)(distance * Math.Cos(angle)), (float)(distance * Math.Sin(angle))) + centre;
+ }
+
+ public static void Shuffle(List array)
+ {
+ for (int i = array.Count; i > 1; i--)
+ {
+ // Pick random element to swap.
+ int j = RandomInt(0, i - 1); // 0 <= j <= i-1
+ // Swap.
+ T tmp = array[j];
+ array[j] = array[i - 1];
+ array[i - 1] = tmp;
+ }
+ }
+
+ public static void Shuffle(T[] array)
+ {
+ for (int i = array.Length; i > 1; i--)
+ {
+ // Pick random element to swap.
+ int j = RandomInt(0, i - 1); // 0 <= j <= i-1
+ // Swap.
+ T tmp = array[j];
+ array[j] = array[i - 1];
+ array[i - 1] = tmp;
+ }
+ }
+
+ public static int NextPowerOf2(int x)
+ {
+ --x;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ return ++x;
+ }
+
+ public static string ToRoman(int number)
+ {
+ if ((number < 0) || (number > 3999)) throw new ArgumentOutOfRangeException("insert value betwheen 1 and 3999");
+ if (number < 1) return string.Empty;
+ if (number >= 1000) return "M" + ToRoman(number - 1000);
+ if (number >= 900) return "CM" + ToRoman(number - 900); //EDIT: i've typed 400 instead 900
+ if (number >= 500) return "D" + ToRoman(number - 500);
+ if (number >= 400) return "CD" + ToRoman(number - 400);
+ if (number >= 100) return "C" + ToRoman(number - 100);
+ if (number >= 90) return "XC" + ToRoman(number - 90);
+ if (number >= 50) return "L" + ToRoman(number - 50);
+ if (number >= 40) return "XL" + ToRoman(number - 40);
+ if (number >= 10) return "X" + ToRoman(number - 10);
+ if (number >= 9) return "IX" + ToRoman(number - 9);
+ if (number >= 5) return "V" + ToRoman(number - 5);
+ if (number >= 4) return "IV" + ToRoman(number - 4);
+ if (number >= 1) return "I" + ToRoman(number - 1);
+ throw new ArgumentOutOfRangeException("something bad happened");
+ }
+ }
+}
diff --git a/DS2DEngine/src/Camera2D.cs b/DS2DEngine/src/Camera2D.cs
new file mode 100644
index 0000000..eda6816
--- /dev/null
+++ b/DS2DEngine/src/Camera2D.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace DS2DEngine
+{
+ public class Camera2D : SpriteBatch
+ {
+ private float m_zoom;
+ private Matrix m_transform;
+ private float m_rotation;
+ private Rectangle m_bounds;
+ private int m_width;
+ private int m_height;
+
+ public Vector2 Position;
+
+ private Texture2D DEBUG_texture;
+ private Color DEBUG_color = new Color(100, 100, 100);
+
+ public GameTime GameTime { get; set; }
+ public float ElapsedTotalSeconds { get; set; } // Used to reduce performance load since this number is called a lot.
+
+ public Camera2D(GraphicsDevice graphicsDevice, int width, int height) : base(graphicsDevice)
+ {
+ m_zoom = 1.0f;
+ m_rotation = 0.0f;
+ Position = Vector2.Zero;
+
+ m_width = width; // EngineEV.ScreenWidth; //graphicsDevice.Viewport.Width;
+ m_height = height; // EngineEV.ScreenHeight; // graphicsDevice.Viewport.Height;
+
+
+ m_bounds = new Rectangle((int)(-Width * 0.5f), (int)(-Height * 0.5f), Width, Height);
+
+ DEBUG_texture = new Texture2D(graphicsDevice, 1, 1, false, SurfaceFormat.Color);
+ Int32[] pixel = { 0xFFFFFF }; // White. 0xFF is Red, 0xFF0000 is Blue
+ DEBUG_texture.SetData(pixel, 0, 1);
+ }
+
+ public Matrix GetTransformation()
+ {
+ m_transform = Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0)) *
+ Matrix.CreateRotationZ(m_rotation) *
+ Matrix.CreateScale(new Vector3(Zoom, Zoom, 1)) *
+ Matrix.CreateTranslation(new Vector3(Width * 0.5f,
+ Height * 0.5f, 0));
+ return m_transform;
+ }
+
+ public void DrawLine(Texture2D texture, float width, Color color, Vector2 pt1, Vector2 pt2)
+ {
+ float angle = (float)Math.Atan2(pt2.Y - pt1.Y, pt2.X - pt1.X);
+ float length = Vector2.Distance(pt1, pt2);
+
+ this.Draw(texture, pt1, null, color,
+ angle, Vector2.Zero, new Vector2(length, width),
+ SpriteEffects.None, 0);
+ }
+
+ public void Draw_CameraBox()
+ {
+ this.Draw(DEBUG_texture, Bounds, DEBUG_color);
+ }
+
+ public float Zoom
+ {
+ get { return m_zoom; }
+ set
+ {
+ m_zoom = value;
+ if (m_zoom < 0.025f) m_zoom = 0.025f;
+ //if (m_zoom > 2) m_zoom = 2;
+ }
+ }
+
+ public float Rotation
+ {
+ get { return MathHelper.ToDegrees(m_rotation); }
+ set { m_rotation = MathHelper.ToRadians(value); }
+ }
+
+ public float X
+ {
+ get { return Position.X; }
+ set { Position.X = value; }
+ }
+
+ public float Y
+ {
+ get { return Position.Y; }
+ set { Position.Y = value; }
+ }
+
+ public int Width
+ {
+ get { return m_width; }
+ }
+
+ public int Height
+ {
+ get { return m_height; }
+ }
+
+ public Rectangle Bounds
+ {
+ get { return new Rectangle((int)(Position.X - (Width * 0.5f * 1/Zoom)),
+ (int)(Position.Y - (Height * 0.5f * 1/Zoom)),
+ (int)(m_bounds.Width * 1/Zoom), (int)(m_bounds.Height * 1/Zoom)); }
+ }
+
+ // A bounding box larger than the actual bounds of the camera that determines whether physics and logic should kick in.
+ public Rectangle LogicBounds
+ {
+ get { return new Rectangle(Bounds.X - 200, Bounds.Y - 200, Bounds.Width + 400, Bounds.Height + 400); }
+ }
+
+ public Vector2 TopLeftCorner
+ {
+ get { return new Vector2(Position.X - (this.Width * 0.5f * 1/Zoom), Position.Y - (this.Height * 0.5f * 1/Zoom)); }
+ }
+
+ public bool CenteredZoom
+ {
+ get { return (Zoom < 1.05f && Zoom > 0.95f); }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (IsDisposed == false)
+ {
+ DEBUG_texture.Dispose();
+ DEBUG_texture = null;
+ base.Dispose(disposing);
+ }
+ }
+ }
+}
diff --git a/DS2DEngine/src/Circle.cs b/DS2DEngine/src/Circle.cs
new file mode 100644
index 0000000..21911c0
--- /dev/null
+++ b/DS2DEngine/src/Circle.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public struct Circle
+ {
+ private float m_x;
+ private float m_y;
+ private float m_radius;
+
+ public Circle(float x, float y, float radius)
+ {
+ m_x = x;
+ m_y = y;
+ m_radius = radius;
+ }
+
+ public static Circle Zero
+ {
+ get { return new Circle(0,0,0); }
+ }
+
+ public float X
+ {
+ get { return m_x; }
+ set { m_x = value; }
+ }
+
+ public float Y
+ {
+ get { return m_y; }
+ set { m_y = value; }
+ }
+
+ public Vector2 Position
+ {
+ get { return new Vector2(m_x, m_y); }
+ set
+ {
+ m_x = value.X;
+ m_y = value.Y;
+ }
+ }
+
+ public float Radius
+ {
+ get { return m_radius; }
+ set { m_radius = value; }
+ }
+ }
+}
diff --git a/DS2DEngine/src/CollisionMath.cs b/DS2DEngine/src/CollisionMath.cs
new file mode 100644
index 0000000..2290d3b
--- /dev/null
+++ b/DS2DEngine/src/CollisionMath.cs
@@ -0,0 +1,615 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace DS2DEngine
+{
+ public class CollisionMath
+ {
+ // Static variables used to calculate rotated rect collision
+ private static Vector2[] aRectangleAxis = new Vector2[4];
+ private static int[] aRectangleAScalars = new int[4];
+ private static int[] aRectangleBScalars = new int[4];
+
+ //Function that detects if two rectangles collide.
+ public static bool Intersects(Rectangle a, Rectangle b)
+ {
+ return a.Intersects(b);
+ }
+
+ public static Vector2 RotatedRectIntersectsMTD(Rectangle a, float rotationA, Vector2 originA, Rectangle b, float rotationB, Vector2 originB)
+ {
+ if (rotationA == 0 && rotationB == 0)
+ return CalculateMTD(a, b);
+
+ //List aRectangleAxis = new List();
+ Vector2 upperLeftA = new Vector2();
+ Vector2 upperRightA = new Vector2();
+ Vector2 lowerLeftA = new Vector2();
+ Vector2 lowerRightA = new Vector2();
+ CalcCorners(ref upperLeftA, ref upperRightA, ref lowerLeftA, ref lowerRightA, a, rotationA, originA);
+ Vector2 upperLeftB = new Vector2();
+ Vector2 upperRightB = new Vector2();
+ Vector2 lowerLeftB = new Vector2();
+ Vector2 lowerRightB = new Vector2();
+ CalcCorners(ref upperLeftB, ref upperRightB, ref lowerLeftB, ref lowerRightB, b, rotationB, originB);
+
+ aRectangleAxis[0] = (upperRightA - upperLeftA);
+ aRectangleAxis[1] = (upperRightA - lowerRightA);
+ aRectangleAxis[2] = (upperLeftB - lowerLeftB);
+ aRectangleAxis[3] = (upperLeftB - upperRightB);
+
+ float shortestLength = int.MaxValue;
+ Vector2 shortestMTD = Vector2.Zero;
+ foreach (Vector2 aAxis in aRectangleAxis)
+ {
+ aAxis.Normalize();
+ Vector2 mtd = AxisCollisionMTD(a, rotationA, originA, b, rotationB, originB, aAxis);
+ if (mtd.Length() < shortestLength)
+ {
+ shortestLength = mtd.Length();
+ shortestMTD = mtd;
+ }
+ }
+ return new Vector2((int)shortestMTD.X, (int)shortestMTD.Y);
+ //return shortestMTD;
+ }
+
+ //Do not convert to radians. The internal functions will do that.
+ //This version of the method assumes the origin of rotation is in the centre of the rectangle.
+ public static bool RotatedRectIntersects(Rectangle a, float rotationA, Rectangle b, float rotationB)
+ {
+ if (rotationA == 0 && rotationB == 0)
+ return a.Intersects(b);
+
+ //List aRectangleAxis = new List();
+ Vector2 upperLeftA = new Vector2();
+ Vector2 upperRightA = new Vector2();
+ Vector2 lowerLeftA = new Vector2();
+ Vector2 lowerRightA = new Vector2();
+ CalcCorners(ref upperLeftA, ref upperRightA, ref lowerLeftA, ref lowerRightA, a, rotationA);
+ Vector2 upperLeftB = new Vector2();
+ Vector2 upperRightB = new Vector2();
+ Vector2 lowerLeftB = new Vector2();
+ Vector2 lowerRightB = new Vector2();
+ CalcCorners(ref upperLeftB, ref upperRightB, ref lowerLeftB, ref lowerRightB, b, rotationB);
+
+ aRectangleAxis[0] = (upperRightA - upperLeftA);
+ aRectangleAxis[1] = (upperRightA - lowerRightA);
+ aRectangleAxis[2] = (upperLeftB - lowerLeftB);
+ aRectangleAxis[3] = (upperLeftB - upperRightB);
+
+ foreach (Vector2 aAxis in aRectangleAxis)
+ {
+ if (!IsAxisCollision(a, rotationA, b, rotationB, aAxis))
+ return false;
+ }
+
+ return true;
+ }
+
+ //Do not convert to radians. The internal functions will do that.
+ public static bool RotatedRectIntersects(Rectangle a, float rotationA, Vector2 originA, Rectangle b, float rotationB, Vector2 originB)
+ {
+ if (rotationA == 0 && rotationB == 0)
+ return a.Intersects(b);
+
+ //List aRectangleAxis = new List();
+ Vector2 upperLeftA = new Vector2();
+ Vector2 upperRightA = new Vector2();
+ Vector2 lowerLeftA = new Vector2();
+ Vector2 lowerRightA = new Vector2();
+ CalcCorners(ref upperLeftA, ref upperRightA, ref lowerLeftA, ref lowerRightA, a, rotationA, originA);
+ Vector2 upperLeftB = new Vector2();
+ Vector2 upperRightB = new Vector2();
+ Vector2 lowerLeftB = new Vector2();
+ Vector2 lowerRightB = new Vector2();
+ CalcCorners(ref upperLeftB, ref upperRightB, ref lowerLeftB, ref lowerRightB, b, rotationB, originB);
+
+ aRectangleAxis[0] = (upperRightA - upperLeftA);
+ aRectangleAxis[1] = (upperRightA - lowerRightA);
+ aRectangleAxis[2] = (upperLeftB - lowerLeftB);
+ aRectangleAxis[3] = (upperLeftB - upperRightB);
+
+ foreach (Vector2 aAxis in aRectangleAxis)
+ {
+ aAxis.Normalize(); // This doesn't seem necessary.
+ if (!IsAxisCollision(a, rotationA, originA, b, rotationB, originB, aAxis))
+ return false;
+ }
+
+ return true;
+ }
+ // Do not convert to radians.
+ private static Vector2 RotatePoint(Vector2 thePoint, Vector2 theOrigin, float theRotation)
+ {
+ double cos = Math.Cos(theRotation);
+ double sin = Math.Sin(theRotation);
+ Vector2 aTranslatedPoint = new Vector2();
+ aTranslatedPoint.X = (float)(theOrigin.X + (thePoint.X - theOrigin.X) * cos
+ - (thePoint.Y - theOrigin.Y) * sin);
+ aTranslatedPoint.Y = (float)(theOrigin.Y + (thePoint.Y - theOrigin.Y) * cos
+ + (thePoint.X - theOrigin.X) * sin);
+ return aTranslatedPoint;
+ }
+
+ private static void RotatePoint(Vector2 thePoint, Vector2 theOrigin, double cos, double sin, ref Vector2 output)
+ {
+ float x = (float)(theOrigin.X + (thePoint.X - theOrigin.X) * cos
+ - (thePoint.Y - theOrigin.Y) * sin);
+ float y = (float)(theOrigin.Y + (thePoint.Y - theOrigin.Y) * cos
+ + (thePoint.X - theOrigin.X) * sin);
+ output.X = x;
+ output.Y = y;
+ }
+
+ public static Vector2 UpperLeftCorner(Rectangle rect, float rotation)
+ {
+ rotation = MathHelper.ToRadians(rotation);
+ Vector2 origin = new Vector2(rect.Width * 0.5f, rect.Height * 0.5f);
+ Vector2 aUpperLeft = new Vector2(rect.Left, rect.Top);
+ aUpperLeft = RotatePoint(aUpperLeft, aUpperLeft + origin, rotation);
+ return aUpperLeft;
+ }
+
+ public static Vector2 UpperLeftCorner(Rectangle rect, float rotation, Vector2 origin)
+ {
+ rotation = MathHelper.ToRadians(rotation);
+ Vector2 aUpperLeft = new Vector2(rect.Left, rect.Top);
+ aUpperLeft = RotatePoint(aUpperLeft, aUpperLeft + origin, rotation);
+ return aUpperLeft;
+ }
+
+ public static Vector2 UpperRightCorner(Rectangle rect, float rotation)
+ {
+ rotation = MathHelper.ToRadians(rotation);
+ Vector2 origin = new Vector2(rect.Width * 0.5f, rect.Height * 0.5f);
+ Vector2 aUpperRight = new Vector2(rect.Right, rect.Top);
+ aUpperRight = RotatePoint(aUpperRight, aUpperRight + new Vector2(-origin.X, origin.Y), rotation);
+ return aUpperRight;
+ }
+
+ public static Vector2 UpperRightCorner(Rectangle rect, float rotation, Vector2 origin)
+ {
+ rotation = MathHelper.ToRadians(rotation);
+ Vector2 aUpperRight = new Vector2(rect.Right, rect.Top);
+ Vector2 aUpperLeft = new Vector2(rect.Left, rect.Top);
+ aUpperRight = RotatePoint(aUpperRight, aUpperLeft + origin, rotation);
+ return aUpperRight;
+ }
+
+ public static Vector2 LowerLeftCorner(Rectangle rect, float rotation)
+ {
+ rotation = MathHelper.ToRadians(rotation);
+ Vector2 origin = new Vector2(rect.Width * 0.5f, rect.Height * 0.5f);
+ Vector2 aLowerLeft = new Vector2(rect.Left, rect.Bottom);
+ aLowerLeft = RotatePoint(aLowerLeft, aLowerLeft + new Vector2(origin.X, -origin.Y), rotation);
+ return aLowerLeft;
+ }
+
+ public static Vector2 LowerLeftCorner(Rectangle rect, float rotation, Vector2 origin)
+ {
+ rotation = MathHelper.ToRadians(rotation);
+ Vector2 aLowerLeft = new Vector2(rect.Left, rect.Bottom);
+ Vector2 aUpperLeft = new Vector2(rect.Left, rect.Top);
+ aLowerLeft = RotatePoint(aLowerLeft, aUpperLeft + origin, rotation);
+ return aLowerLeft;
+ }
+
+ public static Vector2 LowerRightCorner(Rectangle rect, float rotation)
+ {
+ rotation = MathHelper.ToRadians(rotation);
+ Vector2 origin = new Vector2(rect.Width * 0.5f, rect.Height * 0.5f);
+ Vector2 aLowerRight = new Vector2(rect.Right, rect.Bottom);
+ aLowerRight = RotatePoint(aLowerRight, aLowerRight + new Vector2(-origin.X, -origin.Y), rotation);
+ return aLowerRight;
+ }
+
+ public static Vector2 LowerRightCorner(Rectangle rect, float rotation, Vector2 origin)
+ {
+ rotation = MathHelper.ToRadians(rotation);
+ Vector2 aLowerRight = new Vector2(rect.Right, rect.Bottom);
+ Vector2 aUpperLeft = new Vector2(rect.Left, rect.Top);
+ aLowerRight = RotatePoint(aLowerRight, aUpperLeft + origin, rotation);
+ return aLowerRight;
+ }
+
+ // More optimized to call this than each corner function separately
+ public static void CalcCorners(ref Vector2 upperLeft, ref Vector2 upperRight, ref Vector2 lowerLeft, ref Vector2 lowerRight, Rectangle rect, float rotation)
+ {
+ rotation = MathHelper.ToRadians(rotation);
+ Vector2 aUpperLeft = new Vector2(rect.Left, rect.Top);
+ Vector2 aUpperRight = new Vector2(rect.Right, rect.Top);
+ Vector2 aLowerLeft = new Vector2(rect.Left, rect.Bottom);
+ Vector2 aLowerRight = new Vector2(rect.Right, rect.Bottom);
+ Vector2 origin = new Vector2(rect.Width * 0.5f, rect.Height * 0.5f);
+
+ double cos = Math.Cos(rotation);
+ double sin = Math.Sin(rotation);
+ RotatePoint(aUpperLeft, aUpperLeft + origin, cos, sin, ref upperLeft);
+ RotatePoint(aUpperRight, aUpperRight + new Vector2(-origin.X, origin.Y), cos, sin, ref upperRight);
+ RotatePoint(aLowerLeft, aLowerLeft + new Vector2(origin.X, -origin.Y), cos, sin, ref lowerLeft);
+ RotatePoint(aLowerRight, aLowerRight + new Vector2(-origin.X, -origin.Y), cos, sin, ref lowerRight);
+ }
+
+ // More optimized to call this than each corner function separately
+ public static void CalcCorners(ref Vector2 upperLeft, ref Vector2 upperRight, ref Vector2 lowerLeft, ref Vector2 lowerRight, Rectangle rect, float rotation, Vector2 origin)
+ {
+ rotation = MathHelper.ToRadians(rotation);
+ Vector2 aUpperLeft = new Vector2(rect.Left, rect.Top);
+ Vector2 aUpperRight = new Vector2(rect.Right, rect.Top);
+ Vector2 aLowerLeft = new Vector2(rect.Left, rect.Bottom);
+ Vector2 aLowerRight = new Vector2(rect.Right, rect.Bottom);
+
+ double cos = Math.Cos(rotation);
+ double sin = Math.Sin(rotation);
+ RotatePoint(aUpperLeft, aUpperLeft + origin, cos, sin, ref upperLeft);
+ RotatePoint(aUpperRight, aUpperLeft + origin, cos, sin, ref upperRight);
+ RotatePoint(aLowerLeft, aUpperLeft + origin, cos, sin, ref lowerLeft);
+ RotatePoint(aLowerRight, aUpperLeft + origin, cos, sin, ref lowerRight);
+ }
+
+
+ ///
+ /// Determines if a collision has occurred on an Axis of one of the
+ /// planes parallel to the Rectangle
+ ///
+ ///
+ ///
+ ///
+ /// Do not convert to Radians.
+ private static bool IsAxisCollision(Rectangle a, float rotationA, Rectangle b, float rotationB, Vector2 aAxis)
+ {
+ Vector2 upperLeftA = new Vector2();
+ Vector2 upperRightA = new Vector2();
+ Vector2 lowerLeftA = new Vector2();
+ Vector2 lowerRightA = new Vector2();
+ CalcCorners(ref upperLeftA, ref upperRightA, ref lowerLeftA, ref lowerRightA, a, rotationA);
+ Vector2 upperLeftB = new Vector2();
+ Vector2 upperRightB = new Vector2();
+ Vector2 lowerLeftB = new Vector2();
+ Vector2 lowerRightB = new Vector2();
+ CalcCorners(ref upperLeftB, ref upperRightB, ref lowerLeftB, ref lowerRightB, b, rotationB);
+
+ //Project the corners of the Rectangle we are checking on to the Axis and
+ //get a scalar value of that project we can then use for comparison
+ //List aRectangleAScalars = new List();
+ aRectangleAScalars[0] = (GenerateScalar(upperLeftB, aAxis));
+ aRectangleAScalars[1] = (GenerateScalar(upperRightB, aAxis));
+ aRectangleAScalars[2] = (GenerateScalar(lowerLeftB, aAxis));
+ aRectangleAScalars[3] = (GenerateScalar(lowerRightB, aAxis));
+
+ //Project the corners of the current Rectangle on to the Axis and
+ //get a scalar value of that project we can then use for comparison
+ //List aRectangleBScalars = new List();
+ aRectangleBScalars[0] = (GenerateScalar(upperLeftA, aAxis));
+ aRectangleBScalars[1] = (GenerateScalar(upperRightA, aAxis));
+ aRectangleBScalars[2] = (GenerateScalar(lowerLeftA, aAxis));
+ aRectangleBScalars[3] = (GenerateScalar(lowerRightA, aAxis));
+
+ //Get the Maximum and Minium Scalar values for each of the Rectangles
+ int aRectangleAMinimum = aRectangleAScalars.Min();
+ int aRectangleAMaximum = aRectangleAScalars.Max();
+ int aRectangleBMinimum = aRectangleBScalars.Min();
+ int aRectangleBMaximum = aRectangleBScalars.Max();
+
+ //If we have overlaps between the Rectangles (i.e. Min of B is less than Max of A)
+ //then we are detecting a collision between the rectangles on this Axis
+ if (aRectangleBMinimum <= aRectangleAMaximum && aRectangleBMaximum >= aRectangleAMaximum)
+ return true;
+ else if (aRectangleAMinimum <= aRectangleBMaximum && aRectangleAMaximum >= aRectangleBMaximum)
+ return true;
+
+ return false;
+ }
+
+ private static bool IsAxisCollision(Rectangle a, float rotationA, Vector2 originA, Rectangle b, float rotationB, Vector2 originB, Vector2 aAxis)
+ {
+
+ //Project the corners of the Rectangle we are checking on to the Axis and
+ //get a scalar value of that project we can then use for comparison
+ //List aRectangleAScalars = new List();
+ Vector2 upperLeft = new Vector2();
+ Vector2 upperRight = new Vector2();
+ Vector2 lowerLeft = new Vector2();
+ Vector2 lowerRight = new Vector2();
+ CalcCorners(ref upperLeft, ref upperRight, ref lowerLeft, ref lowerRight, b, rotationB, originB);
+ aRectangleAScalars[0] = (GenerateScalar(upperLeft, aAxis));
+ aRectangleAScalars[1] = (GenerateScalar(upperRight, aAxis));
+ aRectangleAScalars[2] = (GenerateScalar(lowerLeft, aAxis));
+ aRectangleAScalars[3] = (GenerateScalar(lowerRight, aAxis));
+
+ //Project the corners of the current Rectangle on to the Axis and
+ //get a scalar value of that project we can then use for comparison
+ //List aRectangleBScalars = new List();
+ CalcCorners(ref upperLeft, ref upperRight, ref lowerLeft, ref lowerRight, a, rotationA, originA);
+ aRectangleBScalars[0] = (GenerateScalar(upperLeft, aAxis));
+ aRectangleBScalars[1] = (GenerateScalar(upperRight, aAxis));
+ aRectangleBScalars[2] = (GenerateScalar(lowerLeft, aAxis));
+ aRectangleBScalars[3] = (GenerateScalar(lowerRight, aAxis));
+
+ //Get the Maximum and Minium Scalar values for each of the Rectangles
+ int aRectangleAMinimum = aRectangleAScalars.Min();
+ int aRectangleAMaximum = aRectangleAScalars.Max();
+ int aRectangleBMinimum = aRectangleBScalars.Min();
+ int aRectangleBMaximum = aRectangleBScalars.Max();
+
+ //If we have overlaps between the Rectangles (i.e. Min of B is less than Max of A)
+ //then we are detecting a collision between the rectangles on this Axis
+ if (aRectangleBMinimum <= aRectangleAMaximum && aRectangleBMaximum >= aRectangleAMaximum)
+ {
+ //Console.WriteLine((aRectangleAMaximum - aRectangleBMinimum) * -aAxis);
+ return true;
+ }
+ else if (aRectangleAMinimum <= aRectangleBMaximum && aRectangleAMaximum >= aRectangleBMaximum)
+ {
+ //Console.WriteLine((aRectangleBMaximum - aRectangleAMinimum) * -aAxis);
+ return true;
+ }
+
+ return false;
+ }
+
+ private static Vector2 AxisCollisionMTD(Rectangle a, float rotationA, Vector2 originA, Rectangle b, float rotationB, Vector2 originB, Vector2 aAxis)
+ {
+ //Project the corners of the Rectangle we are checking on to the Axis and
+ //get a scalar value of that project we can then use for comparison
+ //List aRectangleAScalars = new List();
+ Vector2 upperLeft = new Vector2();
+ Vector2 upperRight = new Vector2();
+ Vector2 lowerLeft = new Vector2();
+ Vector2 lowerRight = new Vector2();
+ CalcCorners(ref upperLeft, ref upperRight, ref lowerLeft, ref lowerRight, b, rotationB, originB);
+
+ aRectangleAScalars[0] = (GenerateScalar(upperLeft, aAxis));
+ aRectangleAScalars[1] = (GenerateScalar(upperRight, aAxis));
+ aRectangleAScalars[2] = (GenerateScalar(lowerLeft, aAxis));
+ aRectangleAScalars[3] = (GenerateScalar(lowerRight, aAxis));
+
+ //Project the corners of the current Rectangle on to the Axis and
+ //get a scalar value of that project we can then use for comparison
+ //List aRectangleBScalars = new List();
+ CalcCorners(ref upperLeft, ref upperRight, ref lowerLeft, ref lowerRight, a, rotationA, originA);
+ aRectangleBScalars[0] = (GenerateScalar(upperLeft, aAxis));
+ aRectangleBScalars[1] = (GenerateScalar(upperRight, aAxis));
+ aRectangleBScalars[2] = (GenerateScalar(lowerLeft, aAxis));
+ aRectangleBScalars[3] = (GenerateScalar(lowerRight, aAxis));
+
+ //Get the Maximum and Minium Scalar values for each of the Rectangles
+ int aRectangleAMinimum = aRectangleAScalars.Min();
+ int aRectangleAMaximum = aRectangleAScalars.Max();
+ int aRectangleBMinimum = aRectangleBScalars.Min();
+ int aRectangleBMaximum = aRectangleBScalars.Max();
+ //If we have overlaps between the Rectangles (i.e. Min of B is less than Max of A)
+ //then we are detecting a collision between the rectangles on this Axis
+ if (aRectangleBMinimum <= aRectangleAMaximum && aRectangleBMaximum >= aRectangleAMaximum)
+ {
+ return (aRectangleAMaximum - aRectangleBMinimum) * aAxis;
+ }
+ else if (aRectangleAMinimum <= aRectangleBMaximum && aRectangleAMaximum >= aRectangleBMaximum)
+ {
+ return (aRectangleBMaximum - aRectangleAMinimum) * -aAxis;
+ }
+ return Vector2.Zero;
+ }
+
+ ///
+ /// Generates a scalar value that can be used to compare where corners of
+ /// a rectangle have been projected onto a particular axis.
+ ///
+ ///
+ ///
+ ///
+ private static int GenerateScalar(Vector2 theRectangleCorner, Vector2 theAxis)
+ {
+ //Using the formula for Vector projection. Take the corner being passed in
+ //and project it onto the given Axis
+ float aNumerator = (theRectangleCorner.X * theAxis.X) + (theRectangleCorner.Y * theAxis.Y);
+ float aDenominator = (theAxis.X * theAxis.X) + (theAxis.Y * theAxis.Y);
+ float aDivisionResult = aNumerator / aDenominator;
+ Vector2 aCornerProjected = new Vector2(aDivisionResult * theAxis.X, aDivisionResult * theAxis.Y);
+
+ //Now that we have our projected Vector, calculate a scalar of that projection
+ //that can be used to more easily do comparisons
+ float aScalar = (theAxis.X * aCornerProjected.X) + (theAxis.Y * aCornerProjected.Y);
+ return (int)aScalar;
+ }
+
+ /*
+ *
+ * This function takes the first hitbox, finds the center, then does a distance
+ * check to the center of the second hitbox
+ *
+ */
+ public static bool CircleIntersects( Rectangle a, float radius, Rectangle b)
+ {
+ // check if two Rectangles intersect
+ //if( a.Left
+ float deltaX = a.Center.X-b.Center.X;
+ float deltaY = a.Center.Y-b.Center.Y;
+ float distanceBetween = (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY );
+
+ if (distanceBetween <= radius)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ // Thank you Microsoft for intellisense with a name like that. ;)
+ public static Vector2 CalculateMTD(Rectangle left, Rectangle right)
+ {
+ // Our displacement result vector containing the translation (movement) information
+ // that resolves our intersection.
+ Vector2 result = Vector2.Zero;
+
+ // This is re-used to calculate the difference in distance between sides.
+ float difference = 0.0f;
+
+ // This stores the absolute minimum distance we'll need to separate our colliding object.
+ float minimumTranslationDistance = 0f;
+
+ // Axis stores the value of X or Y. X = 0, Y = 1.
+ // Side stores the value of left (-1) or right (+1).
+ // They're used later in calculating the result vector.
+ int axis = 0, side = 0;
+
+ // Left
+ difference = left.Right - right.Left;
+ if (difference < 0.0f)
+ {
+ return Vector2.Zero;
+ }
+
+ {
+ // These braces are superfluous but should make it more
+ //clear that they're similiar to the if statements below.
+ minimumTranslationDistance = difference;
+ axis = 0;
+ side = -1;
+ }
+
+ // Right
+ difference = right.Right - left.Left;
+ if (difference < 0.0f)
+ {
+ return Vector2.Zero;
+ }
+ if (difference < minimumTranslationDistance)
+ {
+ minimumTranslationDistance = difference;
+ axis = 0;
+ side = 1;
+ }
+
+ // Down
+ difference = left.Bottom - right.Top;
+ if (difference < 0.0f)
+ {
+ return Vector2.Zero;
+ }
+ if (difference < minimumTranslationDistance)
+ {
+ minimumTranslationDistance = difference;
+ axis = 1;
+ side = -1;
+ }
+
+ // Up
+ difference = right.Bottom - left.Top;
+ if (difference < 0.0f)
+ {
+ return Vector2.Zero;
+ }
+ if (difference < minimumTranslationDistance)
+ {
+ minimumTranslationDistance = difference;
+ axis = 1;
+ side = 1;
+ }
+
+ // Intersection occurred:
+ if (axis == 1) // Y Axis
+ result.Y = (float)side * minimumTranslationDistance;
+ else // X Axis
+ result.X = (float)side * minimumTranslationDistance;
+
+ return result;
+ }
+
+ public static Rectangle RotatedRectBounds(Rectangle rect, Vector2 rotationOrigin, float degreesRotation)
+ {
+ float leftBound = float.MaxValue, topBound = float.MaxValue, rightBound = -float.MaxValue, bottomBound = -float.MaxValue;
+ Vector2 upperLeft = new Vector2();
+ Vector2 upperRight = new Vector2();
+ Vector2 lowerLeft = new Vector2();
+ Vector2 lowerRight = new Vector2();
+ CalcCorners(ref upperLeft, ref upperRight, ref lowerLeft, ref lowerRight, rect, degreesRotation, rotationOrigin);
+
+ if (upperLeft.X < leftBound) leftBound = upperLeft.X;
+ if (upperRight.X < leftBound) leftBound = upperRight.X;
+ if (lowerLeft.X < leftBound) leftBound = lowerLeft.X;
+ if (lowerRight.X < leftBound) leftBound = lowerRight.X;
+
+ if (upperLeft.Y < topBound) topBound = upperLeft.Y;
+ if (upperRight.Y < topBound) topBound = upperRight.Y;
+ if (lowerLeft.Y < topBound) topBound = lowerLeft.Y;
+ if (lowerRight.Y < topBound) topBound = lowerRight.Y;
+
+ if (upperLeft.X > rightBound) rightBound = (upperLeft.X);
+ if (upperRight.X > rightBound) rightBound = (upperRight.X );
+ if (lowerLeft.X > rightBound) rightBound = (lowerLeft.X );
+ if (lowerRight.X > rightBound) rightBound = (lowerRight.X );
+
+ if (upperLeft.Y > bottomBound) bottomBound = (upperLeft.Y);
+ if (upperRight.Y > bottomBound) bottomBound = (upperRight.Y);
+ if (lowerLeft.Y > bottomBound) bottomBound = (lowerLeft.Y);
+ if (lowerRight.Y > bottomBound) bottomBound = (lowerRight.Y);
+
+ return new Rectangle((int)leftBound, (int)topBound, (int)(rightBound - leftBound), (int)(bottomBound - topBound));
+ }
+
+ public static Vector2 LineToLineIntersect(Vector2 pt1Start, Vector2 pt1End, Vector2 pt2Start, Vector2 pt2End )
+ {
+ float ua = (pt2End.X - pt2Start.X) * (pt1Start.Y - pt2Start.Y) - (pt2End.Y - pt2Start.Y) * (pt1Start.X - pt2Start.X);
+ float ub = (pt1End.X - pt1Start.X) * (pt1Start.Y - pt2Start.Y) - (pt1End.Y - pt1Start.Y) * (pt1Start.X - pt2Start.X);
+ float denominator = (pt2End.Y - pt2Start.Y) * (pt1End.X - pt1Start.X) - (pt2End.X - pt2Start.X) * (pt1End.Y - pt1Start.Y);
+
+ Vector2 intersectionPoint = Vector2.Zero;
+
+ if (Math.Abs(denominator) <= 0.00001f)
+ {
+ if (Math.Abs(ua) <= 0.00001f && Math.Abs(ub) <= 0.00001f)
+ {
+ intersectionPoint = (pt1Start + pt1End) / 2;
+ }
+ }
+ else
+ {
+ ua /= denominator;
+ ub /= denominator;
+
+ if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)
+ {
+ intersectionPoint.X = pt1Start.X + ua * (pt1End.X - pt1Start.X);
+ intersectionPoint.Y = pt1Start.Y + ua * (pt1End.Y - pt1Start.Y);
+ }
+ }
+
+ return intersectionPoint;
+ }
+
+ public static Vector2 GetIntersectionDepth(Rectangle rectA, Rectangle rectB)
+ {
+ // Calculate half sizes.
+ float halfWidthA = rectA.Width / 2.0f;
+ float halfHeightA = rectA.Height / 2.0f;
+ float halfWidthB = rectB.Width / 2.0f;
+ float halfHeightB = rectB.Height / 2.0f;
+
+ // Calculate centers.
+ Vector2 centerA = new Vector2(rectA.Left + halfWidthA, rectA.Top + halfHeightA);
+ Vector2 centerB = new Vector2(rectB.Left + halfWidthB, rectB.Top + halfHeightB);
+
+ // Calculate current and minimum-non-intersecting distances between centers.
+ float distanceX = centerA.X - centerB.X;
+ float distanceY = centerA.Y - centerB.Y;
+ float minDistanceX = halfWidthA + halfWidthB;
+ float minDistanceY = halfHeightA + halfHeightB;
+
+ // If we are not intersecting at all, return (0, 0).
+ if (Math.Abs(distanceX) >= minDistanceX || Math.Abs(distanceY) >= minDistanceY)
+ return Vector2.Zero;
+
+ // Calculate and return intersection depths.
+ float depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
+ float depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
+ return new Vector2(depthX, depthY);
+ }
+ }
+}
diff --git a/DS2DEngine/src/Consts.cs b/DS2DEngine/src/Consts.cs
new file mode 100644
index 0000000..ae600fd
--- /dev/null
+++ b/DS2DEngine/src/Consts.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public class Consts
+ {
+ public const bool DEBUG_ShowSafeZones = true;
+
+ public const float FRAMERATE_CAP = 1 / 30f;
+
+ // Hitbox Consts.
+ public const int NULL_HITBOX = -1;
+ public const int TERRAIN_HITBOX = 0;
+ public const int WEAPON_HITBOX = 1;
+ public const int BODY_HITBOX = 2;
+
+ public const int COLLISIONRESPONSE_NULL = 0;
+ public const int COLLISIONRESPONSE_TERRAIN = 1;
+ public const int COLLISIONRESPONSE_FIRSTBOXHIT = 2;
+ public const int COLLISIONRESPONSE_SECONDBOXHIT = 3;
+ }
+}
diff --git a/DS2DEngine/src/DS2DPool.cs b/DS2DEngine/src/DS2DPool.cs
new file mode 100644
index 0000000..a67f78f
--- /dev/null
+++ b/DS2DEngine/src/DS2DPool.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public class DS2DPool : IDisposableObj
+ {
+ private int m_poolSize;
+ private int m_numActiveObjs;
+
+ private Stack m_ObjStack;
+ private List m_activeObjsList; // Since objects are constantly being added and removed, it might be a good idea to turn this into a linked list.
+
+ private bool m_isDisposed = false;
+
+ public DS2DPool()
+ {
+ m_ObjStack = new Stack();
+ m_activeObjsList = new List();
+
+ m_poolSize = 0;
+ m_numActiveObjs = 0;
+ }
+
+ public void AddToPool(OBJ_T obj)
+ {
+ m_ObjStack.Push(obj);
+ m_poolSize++;
+
+ //if (!(obj is IPoolableObj))
+ // throw new Exception("Cannot add object to pool. Please ensure that the object inherits from the IPoolableObj interface");
+ }
+
+ public OBJ_T CheckOut()
+ {
+ if (m_poolSize - m_numActiveObjs < 1)
+ throw new Exception("Resource pool out of items");
+
+ m_numActiveObjs++;
+ OBJ_T objToReturn = m_ObjStack.Pop();
+ //(objToReturn as IPoolableObj).IsCheckedOut = true;
+ m_activeObjsList.Add(objToReturn);
+ return objToReturn;
+ }
+
+
+ public OBJ_T CheckOutReturnNull()
+ {
+ if (m_poolSize - m_numActiveObjs < 1)
+ return default(OBJ_T);
+
+ m_numActiveObjs++;
+ OBJ_T objToReturn = m_ObjStack.Pop();
+ //(objToReturn as IPoolableObj).IsCheckedOut = true;
+ m_activeObjsList.Add(objToReturn);
+ return objToReturn;
+ }
+
+
+ public void CheckIn(OBJ_T obj)
+ {
+ // Make sure to clear loc ID when TextObj is freed. This is because
+ // next use of this TextObj might not have loc ID and should not be
+ // refreshed on language change.
+ if (obj is TextObj)
+ (obj as TextObj).locStringID = "";
+
+ //This pool has been tested and the check below is unnecessary, so for performance purposes it has been removed.
+ //if ((obj as IPoolableObj).IsCheckedOut == true)
+ {
+ //(obj as IPoolableObj).IsCheckedOut = false;
+ m_numActiveObjs--;
+ m_ObjStack.Push(obj);
+ m_activeObjsList.Remove(obj);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ // Checkins all active objects before disposing of the pool.
+ while (m_activeObjsList.Count > 0)
+ {
+ CheckIn(m_activeObjsList[m_activeObjsList.Count-1]);
+ }
+ m_activeObjsList.Clear();
+
+ foreach (OBJ_T obj in m_ObjStack)
+ {
+ IDisposableObj disposableObj = obj as IDisposableObj;
+ if (disposableObj != null)
+ disposableObj.Dispose();
+ }
+ m_ObjStack.Clear();
+ m_isDisposed = true;
+ }
+ }
+
+ public int NumActiveObjs
+ {
+ get { return m_numActiveObjs; }
+ }
+
+ public int TotalPoolSize
+ {
+ get { return m_poolSize; }
+ }
+
+ public int CurrentPoolSize
+ {
+ get { return TotalPoolSize - NumActiveObjs; }
+ }
+
+ public List ActiveObjsList
+ {
+ get { return m_activeObjsList; }
+ }
+
+ public Stack Stack
+ {
+ get { return m_ObjStack; }
+ }
+
+ public bool IsDisposed
+ {
+ get { return m_isDisposed; }
+ }
+ }
+}
diff --git a/DS2DEngine/src/DebugHelper.cs b/DS2DEngine/src/DebugHelper.cs
new file mode 100644
index 0000000..877e204
--- /dev/null
+++ b/DS2DEngine/src/DebugHelper.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public class DebugHelper
+ {
+ public static Texture2D CreateCircleTexture(int radius, GraphicsDevice graphicsDevice)
+ {
+ int outerRadius = radius * 2 + 2; // So circle doesn't go out of bounds
+ Texture2D texture = new Texture2D(graphicsDevice, outerRadius, outerRadius);
+
+ Color[] data = new Color[outerRadius * outerRadius];
+
+ // Colour the entire texture transparent first.
+ for (int i = 0; i < data.Length; i++)
+ data[i] = Color.Transparent;
+
+ // Work out the minimum step necessary using trigonometry + sine approximation.
+ double angleStep = 1f / radius;
+
+ for (double angle = 0; angle < Math.PI * 2; angle += angleStep)
+ {
+ // Use the parametric definition of a circle: http://en.wikipedia.org/wiki/Circle#Cartesian_coordinates
+ int x = (int)Math.Round(radius + radius * Math.Cos(angle));
+ int y = (int)Math.Round(radius + radius * Math.Sin(angle));
+
+ data[y * outerRadius + x + 1] = Color.White;
+ }
+
+ texture.SetData(data);
+ return texture;
+ }
+ }
+}
diff --git a/DS2DEngine/src/EngineEV.cs b/DS2DEngine/src/EngineEV.cs
new file mode 100644
index 0000000..b32436d
--- /dev/null
+++ b/DS2DEngine/src/EngineEV.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace DS2DEngine
+{
+ public class EngineEV
+ {
+ public static int ScreenWidth = 800;
+ public static int ScreenHeight = 600;
+
+ public static Vector2 ScreenRatio = Vector2.One;
+
+ public static void RefreshEngine(GraphicsDevice graphicsDevice)
+ {
+ ScreenRatio = new Vector2((float)ScreenWidth / graphicsDevice.Viewport.Width, (float)ScreenHeight / graphicsDevice.Viewport.Height);
+ }
+ }
+}
diff --git a/DS2DEngine/src/FrameSoundObj.cs b/DS2DEngine/src/FrameSoundObj.cs
new file mode 100644
index 0000000..10a8d47
--- /dev/null
+++ b/DS2DEngine/src/FrameSoundObj.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public class FrameSoundObj : IDisposableObj
+ {
+ private int m_frame;
+ private IAnimateableObj m_obj;
+ private GameObj m_listener;
+ private string[] m_sounds;
+ private bool m_soundPlaying = false;
+ private bool m_isDisposed = false;
+
+ public FrameSoundObj(IAnimateableObj obj, int frame, params string[] sounds)
+ {
+ m_obj = obj;
+ m_frame = frame;
+ m_sounds = sounds;
+ m_listener = null;
+ }
+
+ public FrameSoundObj(IAnimateableObj obj, GameObj listener, int frame, params string[] sounds)
+ {
+ m_obj = obj;
+ m_frame = frame;
+ m_sounds = sounds;
+ m_listener = listener;
+ }
+
+ public void Update()
+ {
+ if (m_soundPlaying == false && m_obj.CurrentFrame == m_frame)
+ {
+ m_soundPlaying = true;
+ if (m_listener == null)
+ SoundManager.PlaySound(m_sounds);
+ else
+ SoundManager.Play3DSound((m_obj as GameObj), m_listener, m_sounds);
+ }
+
+ if (m_obj.CurrentFrame != m_frame)
+ m_soundPlaying = false;
+ }
+
+ public void Dispose()
+ {
+ if (m_isDisposed == false)
+ {
+ m_isDisposed = true;
+ Array.Clear(m_sounds, 0, m_sounds.Length);
+ m_sounds = null;
+ m_obj = null;
+ m_listener = null;
+ }
+ }
+
+ public bool IsDisposed
+ {
+ get { return m_isDisposed; }
+ }
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/BackgroundObj.cs b/DS2DEngine/src/GameObjs/BackgroundObj.cs
new file mode 100644
index 0000000..cd4b9f4
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/BackgroundObj.cs
@@ -0,0 +1,162 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using DS2DEngine;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public class BackgroundObj : SpriteObj
+ {
+ public Vector2 ParallaxSpeed = Vector2.Zero;
+ //public Vector2 ScrollSpeed = Vector2.Zero;
+ //public bool Scrollable = false;
+ private bool m_repeatHorizontal = false;
+ private bool m_repeatVertical = false;
+ private RenderTarget2D m_repeatedTexture;
+ //private Vector2 m_cameraStartPos = Vector2.Zero;
+ private Vector2 m_retainedScale; // The scale that the texture needs to be at to retain its original size (since it was modified to match a power of 2 size).
+
+ private SamplerState m_samplerState;
+
+ public bool isContentLost
+ {
+ get
+ {
+ if (m_repeatedTexture == null)
+ return false;
+ return m_repeatedTexture.IsContentLost;
+ }
+ }
+
+ public BackgroundObj(string spriteName)
+ : base(spriteName)
+ {
+ this.ForceDraw = true;
+ }
+
+ public void SetRepeated(bool repeatHorizontal, bool repeatVertical, Camera2D camera, SamplerState samplerState = null)
+ {
+ m_samplerState = samplerState;
+
+ //if (m_repeatedTexture != null && (repeatHorizontal == true && repeatVertical == true))
+ if (m_repeatedTexture != null)
+ {
+ m_repeatedTexture.Dispose();
+ m_repeatedTexture = null;
+ }
+
+ if (m_repeatedTexture == null && (repeatHorizontal == true || repeatVertical == true))
+ {
+ m_repeatedTexture = this.ConvertToTexture(camera, true, samplerState);
+ m_retainedScale = new Vector2(this.Width / (float)m_repeatedTexture.Width, this.Height / (float)m_repeatedTexture.Height);
+ }
+ //else if (m_repeatedTexture != null && (repeatHorizontal == true && repeatVertical == true))
+ //{
+ // m_repeatedTexture.Dispose();
+ // m_repeatedTexture = null;
+ //}
+ m_repeatHorizontal = repeatHorizontal;
+ m_repeatVertical = repeatVertical;
+ }
+
+ public void ChangeSprite(string spriteName, Camera2D camera)
+ {
+ base.ChangeSprite(spriteName);
+ SetRepeated(m_repeatHorizontal, m_repeatVertical, camera, m_samplerState);
+ }
+
+
+ float m_totalXParallax = 0;
+ float m_totalYParallax = 0;
+ public override void Draw(Camera2D camera)
+ {
+ //if (Scrollable == true && m_cameraStartPos != Vector2.Zero)
+ {
+ m_totalXParallax += (this.ParallaxSpeed.X * 60 * camera.ElapsedTotalSeconds);
+ m_totalYParallax += (this.ParallaxSpeed.Y * 60 * camera.ElapsedTotalSeconds);
+
+ if (RepeatHorizontal == true)
+ m_totalXParallax = m_totalXParallax % (this.Width * m_retainedScale.X); // Don't multiply by ScaleX because Width already does that.
+ if (RepeatVertical == true)
+ m_totalYParallax = m_totalYParallax % (this.Height * m_retainedScale.Y);
+ }
+ // m_cameraStartPos = camera.Position;
+
+ if (RepeatHorizontal == true || RepeatVertical == true)
+ {
+ float posX = this.X;
+ float posY = this.Y;
+ int width = (int)(m_repeatedTexture.Width * ScaleX * m_retainedScale.X);
+ int height = (int)(m_repeatedTexture.Height * ScaleY * m_retainedScale.Y);
+ //int width = (int)(camera.GraphicsDevice.Viewport.Width * (1/(Scale.X * m_retainedScale.X)));
+ //int height = (int)(camera.GraphicsDevice.Viewport.Height * (1/(ScaleY * m_retainedScale.Y)));
+
+ // Problem code below
+ if (RepeatHorizontal == true)
+ {
+ //posX = this.X - camera.GraphicsDevice.Viewport.Width * ScaleX * m_retainedScale.X;
+ //posX = this.X - camera.GraphicsDevice.Viewport.Width;
+ posX = this.X + m_totalXParallax - (m_repeatedTexture.Width * m_retainedScale.X * ScaleX * 5);
+ width = EngineEV.ScreenWidth * 10; //camera.GraphicsDevice.Viewport.Width * 10;
+ }
+ if (RepeatVertical == true)
+ {
+ //posY = this.Y - camera.GraphicsDevice.Viewport.Height * ScaleY * m_retainedScale.Y;
+ posY = this.Y + m_totalYParallax - (m_repeatedTexture.Height * m_retainedScale.Y * ScaleY * 5);
+ height = EngineEV.ScreenHeight * 10; //camera.GraphicsDevice.Viewport.Height * 10;
+ }
+ /////////////////////
+ //camera.Draw(m_repeatedTexture, new Vector2(this.X + -GlobalEV.ScreenWidth, this.Y + -GlobalEV.ScreenHeight), new Rectangle(0, 0, GlobalEV.ScreenWidth * 3, GlobalEV.ScreenHeight * 3), this.TextureColor, 0, Vector2.Zero, this.Scale, SpriteEffects.None, 0);
+ camera.Draw(m_repeatedTexture, new Vector2(posX, posY), new Rectangle(0, 0, width, height), this.TextureColor * this.Opacity, this.Rotation, Vector2.Zero, m_retainedScale * Scale, this.Flip, 0);
+ //camera.Draw(m_repeatedTexture, this.Position, Color.White);
+ }
+ else
+ {
+ base.Draw(camera);
+ }
+ }
+
+ public bool RepeatHorizontal
+ {
+ get { return m_repeatHorizontal; }
+ }
+
+ public bool RepeatVertical
+ {
+ get { return m_repeatVertical; }
+ }
+
+ protected override GameObj CreateCloneInstance()
+ {
+ return new BackgroundObj(_spriteName);
+ }
+
+ protected override void FillCloneInstance(object obj)
+ {
+ base.FillCloneInstance(obj);
+ // This one needs to be fixed.
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ if (m_repeatedTexture != null)
+ {
+ m_repeatedTexture.Dispose();
+ m_repeatedTexture = null;
+ }
+ m_samplerState = null;
+ base.Dispose();
+ }
+ }
+
+ public RenderTarget2D Texture
+ {
+ get { return m_repeatedTexture; }
+ }
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/BlankObj.cs b/DS2DEngine/src/GameObjs/BlankObj.cs
new file mode 100644
index 0000000..c7c7ab1
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/BlankObj.cs
@@ -0,0 +1,224 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+
+ // A blank, invisible object that is mainly used for setting triggers.
+ public class BlankObj : GameObj, IPhysicsObj
+ {
+ public LinkedListNode Node { get; set; }
+
+ public bool CollidesLeft { get; set; }
+ public bool CollidesRight { get; set; }
+ public bool CollidesTop { get; set; }
+ public bool CollidesBottom { get; set; }
+
+ public int CollisionTypeTag { get; set; }
+ private Vector2 _acceleration = Vector2.Zero;
+ public Boolean IsWeighted { get; set; }
+ public Boolean IsCollidable { get; set; }
+ public bool AccelerationXEnabled { get; set; }
+ public bool AccelerationYEnabled { get; set; }
+
+ public bool DisableHitboxUpdating { get; set; }
+
+ private List _collisionBoxes = null;
+ public PhysicsManager PhysicsMngr { get; set; }
+ private Rectangle m_terrainBounds = new Rectangle();
+ public bool DisableAllWeight { get; set; }
+ public bool SameTypesCollide { get; set; }
+ public bool DisableGravity { get; set; }
+
+ // You must pass in the width and height into the constructor since these properties are currently read-only.
+ public BlankObj(int width, int height)
+ {
+ _width = width;
+ _height = height;
+ IsWeighted = false;
+ IsCollidable = false;
+ _collisionBoxes = new List();
+
+ CollidesLeft = true;
+ CollidesRight = true;
+ CollidesTop = true;
+ CollidesBottom = true;
+
+ AccelerationXEnabled = true;
+ AccelerationYEnabled = true;
+
+ DisableHitboxUpdating = true;
+ }
+
+ public void UpdatePhysics(GameTime gameTime)
+ {
+ if (AccelerationXEnabled == true)
+ this.X += _acceleration.X;
+ if (AccelerationYEnabled == true)
+ this.Y += _acceleration.Y;
+ }
+
+ public void UpdateCollisionBoxes() { }
+
+ public void AddCollisionBox(int xPos, int yPos, int width, int height, int collisionType)
+ {
+ _collisionBoxes.Add(new CollisionBox((int)(xPos), (int)(yPos), width, height, collisionType, this));
+ if (collisionType == Consts.TERRAIN_HITBOX)
+ m_terrainBounds = new Rectangle(xPos, yPos, width, height);
+ }
+
+ public void ClearCollisionBoxes()
+ {
+ _collisionBoxes.Clear();
+ }
+
+ public virtual void CollisionResponse(CollisionBox thisBox, CollisionBox otherBox, int collisionResponseType)
+ {
+ if (this.IsWeighted == true && collisionResponseType == Consts.COLLISIONRESPONSE_TERRAIN)
+ {
+ this.AccelerationX = 0;
+ if (this.AccelerationY > 0)
+ this.AccelerationY = 0;
+ Vector2 mtdPos = CollisionMath.CalculateMTD(thisBox.AbsRect, otherBox.AbsRect);
+ this.X += mtdPos.X;
+ this.Y += mtdPos.Y;
+ }
+ }
+
+ public void RemoveFromPhysicsManager()
+ {
+ if (this.PhysicsMngr != null)
+ this.PhysicsMngr.RemoveObject(this);
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ if (PhysicsMngr != null)
+ PhysicsMngr.RemoveObject(this);
+
+ Node = null;
+
+ foreach (CollisionBox box in _collisionBoxes)
+ box.Dispose();
+
+ _collisionBoxes.Clear();
+ base.Dispose();
+ }
+ }
+
+ protected override GameObj CreateCloneInstance()
+ {
+ return new BlankObj(_width, _height);
+ }
+
+ protected override void FillCloneInstance(object obj)
+ {
+ base.FillCloneInstance(obj);
+
+ BlankObj clone = obj as BlankObj;
+ if (this.PhysicsMngr != null)
+ this.PhysicsMngr.AddObject(clone);
+
+ clone.CollidesLeft = this.CollidesLeft;
+ clone.CollidesRight = this.CollidesRight;
+ clone.CollidesBottom = this.CollidesBottom;
+ clone.CollidesTop = this.CollidesTop;
+
+ clone.IsWeighted = this.IsWeighted;
+ clone.IsCollidable = this.IsCollidable;
+ clone.DisableHitboxUpdating = this.DisableHitboxUpdating;
+ clone.CollisionTypeTag = this.CollisionTypeTag;
+ clone.DisableAllWeight = this.DisableAllWeight;
+ clone.SameTypesCollide = this.SameTypesCollide;
+
+ clone.AccelerationX = this.AccelerationX;
+ clone.AccelerationY = this.AccelerationY;
+ clone.AccelerationXEnabled = this.AccelerationXEnabled;
+ clone.AccelerationYEnabled = this.AccelerationYEnabled;
+ clone.DisableGravity = this.DisableGravity;
+ }
+
+ public override void PopulateFromXMLReader(System.Xml.XmlReader reader, System.Globalization.CultureInfo ci) // Needed because this is a hacked together physics object.
+ {
+ base.PopulateFromXMLReader(reader, ci);
+
+ if (reader.MoveToAttribute("CollidesTop"))
+ this.CollidesTop = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("CollidesBottom"))
+ this.CollidesBottom = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("CollidesLeft"))
+ this.CollidesLeft = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("CollidesRight"))
+ this.CollidesRight = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("Collidable"))
+ this.IsCollidable = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("Weighted"))
+ this.IsWeighted = bool.Parse(reader.Value);
+ }
+
+ public List CollisionBoxes
+ {
+ get { return _collisionBoxes; }
+ }
+
+ public Rectangle TerrainBounds
+ {
+ get { return new Rectangle((int)(this.X + m_terrainBounds.X), (int)(this.Y + m_terrainBounds.Y), m_terrainBounds.Width, m_terrainBounds.Height); }
+ }
+
+ public float AccelerationX
+ {
+ set { _acceleration.X = value; }
+ get { return _acceleration.X; }
+ }
+
+ public float AccelerationY
+ {
+ set { _acceleration.Y = value; }
+ get { return _acceleration.Y; }
+ }
+
+ public void SetWidth(int width)
+ {
+ _width = width;
+ }
+
+ public void SetHeight(int height)
+ {
+ _height = height;
+ }
+
+ public bool HasTerrainHitBox
+ {
+ get
+ {
+ foreach (CollisionBox box in _collisionBoxes)
+ {
+ if (box.Type == Consts.TERRAIN_HITBOX)
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public bool DisableCollisionBoxRotations
+ {
+ set
+ {
+ foreach (CollisionBox box in _collisionBoxes)
+ box.DisableRotation = value;
+ }
+ get
+ {
+ foreach (CollisionBox box in _collisionBoxes)
+ return box.DisableRotation;
+ return false;
+ }
+ }
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/CollisionBox.cs b/DS2DEngine/src/GameObjs/CollisionBox.cs
new file mode 100644
index 0000000..5a0648a
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/CollisionBox.cs
@@ -0,0 +1,255 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public class CollisionBox : IDisposableObj
+ {
+ private Rectangle _rectangle;
+ private int _collisionType;
+
+ private GameObj m_parentObj;
+ private float m_rotation;
+ public bool WasAdded = false;
+
+ private bool m_isDisposed = false;
+ public bool DisableRotation = false;
+
+ private Vector2 _cachedRotatedPoint;
+ private Rectangle _cachedAbsRect;
+ private float _cachedAbsRotation;
+
+ public CollisionBox(GameObj parent)
+ {
+ _rectangle = new Rectangle();
+ _cachedRotatedPoint = new Vector2();
+ _cachedAbsRect = new Rectangle();
+ _cachedAbsRotation = 0.0f;
+ m_parentObj = parent;
+ m_rotation = 0;
+
+ UpdateCachedValues();
+ }
+
+ public CollisionBox(int x, int y, int width, int height, int type, GameObj parent)
+ {
+ _rectangle = new Rectangle(x, y, width, height);
+ _cachedRotatedPoint = new Vector2();
+ _cachedAbsRect = new Rectangle();
+ _cachedAbsRotation = 0.0f;
+ _collisionType = type;
+ m_parentObj = parent;
+ m_rotation = 0;
+
+ UpdateCachedValues();
+ }
+
+ public void UpdateCachedValues()
+ {
+ _cachedRotatedPoint = RotatedPoint;
+ _cachedAbsRect = new Rectangle((int)_cachedRotatedPoint.X, (int)_cachedRotatedPoint.Y, Width, Height);
+ _cachedAbsRotation = AbsRotationNoCache();
+ }
+
+ public int X
+ {
+ set { _rectangle.X = value; }
+ get
+ {
+ if (m_rotation == 0 && m_parentObj.Flip == Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally)
+ return (int)-(_rectangle.X + _rectangle.Width);
+ else
+ return _rectangle.X;
+ }
+ }
+
+ public int Y
+ {
+ set { _rectangle.Y = value; }
+ get { return _rectangle.Y; }
+ }
+
+ public int Width
+ {
+ set { _rectangle.Width = value; }
+ get { return _rectangle.Width; }
+ }
+
+ public int Height
+ {
+ set { _rectangle.Height = value; }
+ get { return _rectangle.Height; }
+ }
+
+ public int Type
+ {
+ set { _collisionType = value; }
+ get { return _collisionType; }
+ }
+
+ public override String ToString()
+ {
+ return "[x: " + _rectangle.X + ", y: " + _rectangle.Y + ", absX: " + AbsX + ", absY: " + AbsY + ", width: " + _rectangle.Width + ", Height: " + _rectangle.Height + ", Type: " + _collisionType + "]";
+ }
+
+ public Rectangle Rect
+ {
+ get { return _rectangle; }
+ }
+
+ public int AbsX
+ {
+ get
+ {
+ if (this.Parent.UseCachedValues == true)
+ return (int)(_cachedRotatedPoint.X);
+ else
+ return (int)(RotatedPoint.X);
+ }
+ }
+
+ public int AbsY
+ {
+ get
+ {
+ if (this.Parent.UseCachedValues == true)
+ return (int)(_cachedRotatedPoint.Y);
+ else
+ return (int)(RotatedPoint.Y);
+ }
+ }
+
+ private float Rotation
+ {
+ get
+ {
+ if (DisableRotation == true)
+ return 0;
+
+ if (m_parentObj.Parent != null)
+ return m_parentObj.Parent.Rotation + m_parentObj.Rotation;
+ else
+ return m_parentObj.Rotation;
+ }
+ }
+
+ private float AbsRotationNoCache()
+ {
+ if (DisableRotation == true)
+ return 0;
+
+ if (m_parentObj.Parent != null)
+ return m_parentObj.Parent.Rotation + m_parentObj.Rotation + InternalRotation;
+ else
+ return m_parentObj.Rotation + InternalRotation;
+ }
+
+ public float AbsRotation
+ {
+ get
+ {
+ if (this.Parent.UseCachedValues == true)
+ return _cachedAbsRotation;
+ else
+ return AbsRotationNoCache();
+ }
+ }
+
+ public float InternalRotation
+ {
+ get
+ {
+ if (m_parentObj.Flip == Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally)
+ return MathHelper.ToDegrees(-m_rotation);
+ else
+ return MathHelper.ToDegrees(m_rotation);
+ }
+ set { m_rotation = MathHelper.ToRadians(value); }
+ }
+
+ private Vector2 RotatedPoint
+ {
+ get
+ {
+ if (Rotation == 0)
+ {
+ if (m_rotation != 0 && m_parentObj.Flip == Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally)
+ {
+ Vector2 topRight = CollisionMath.UpperRightCorner(new Rectangle(this.X, this.Y, Width, Height), MathHelper.ToDegrees(m_rotation), Vector2.Zero);
+ topRight.X = -topRight.X;
+ return new Vector2(topRight.X + m_parentObj.AbsX, topRight.Y + m_parentObj.AbsY);
+ }
+ else
+ return new Vector2(this.X + m_parentObj.AbsX, this.Y + m_parentObj.AbsY);
+ }
+ else
+ {
+ //if (m_parentObj.Parent != null)
+ if (m_rotation != 0 && m_parentObj.Parent != null && m_parentObj.Flip == Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally)
+ {
+ // if (m_parentObj.Flip == Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally && m_rotation != 0)
+ {
+
+ Vector2 topRight = CollisionMath.UpperRightCorner(new Rectangle(this.X, this.Y, Width, Height), MathHelper.ToDegrees(m_rotation), Vector2.Zero);
+ topRight.X = -topRight.X;
+ return CDGMath.RotatedPoint(new Vector2(topRight.X + m_parentObj.AbsX, topRight.Y + m_parentObj.AbsY), new Vector2(m_parentObj.Parent.AbsX, m_parentObj.Parent.AbsY), this.Rotation);
+ }
+ // else
+ // return CDGMath.RotatedPoint(new Vector2(this.X + m_parentObj.AbsX, this.Y + m_parentObj.AbsY), new Vector2(m_parentObj.Parent.AbsX, m_parentObj.Parent.AbsY), this.Rotation);
+ }
+ else
+ return CDGMath.RotatedPoint(new Vector2(this.X + m_parentObj.AbsX, this.Y + m_parentObj.AbsY), new Vector2(m_parentObj.AbsX, m_parentObj.AbsY), this.Rotation);
+ }
+ }
+ }
+
+ public Rectangle AbsRect
+ {
+ get
+ {
+ if (this.Parent.UseCachedValues == true)
+ return _cachedAbsRect;
+ else
+ return new Rectangle(AbsX, AbsY, Width, Height);
+ }
+ }
+
+ public Rectangle NonRotatedAbsRect
+ {
+ get { return new Rectangle((int)(this.X + m_parentObj.AbsX), (int)(this.Y + m_parentObj.AbsY), Width, Height); }
+ }
+
+ public GameObj Parent
+ {
+ get { return m_parentObj; }
+ }
+
+ public GameObj AbsParent
+ {
+ get
+ {
+ if (m_parentObj.Parent == null)
+ return m_parentObj;
+ else
+ return m_parentObj.Parent;
+ }
+ }
+
+ public void Dispose()
+ {
+ if (m_isDisposed == false)
+ {
+ m_isDisposed = true;
+ m_parentObj = null;
+ }
+ }
+
+ public bool IsDisposed
+ {
+ get { return m_isDisposed; }
+ }
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/GameObj.cs b/DS2DEngine/src/GameObjs/GameObj.cs
new file mode 100644
index 0000000..99529a0
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/GameObj.cs
@@ -0,0 +1,838 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input.Touch;
+using InputSystem;
+using System.Xml;
+using System.Globalization;
+
+namespace DS2DEngine
+{
+ //TODO: Separate IDrawableObj from GameObj.
+ public abstract class GameObj : ITriggerableObj, IDrawableObj, IDisposableObj, ICloneable, IClickableObj
+ {
+ private Vector2 _position = Vector2.Zero;
+ protected Vector2 _anchor = Vector2.Zero;
+ protected int _width = 0;
+ protected int _height = 0;
+ protected Vector2 _scale = new Vector2(1,1);
+ public Boolean Visible { get; set; }
+ protected float _rotation = 0;
+ protected float _layer = 0;
+ protected float _orientation = 0;
+ protected Vector2 _heading = Vector2.Zero;
+ private ObjContainer _parent = null;
+ private ObjContainer _topmostParent = null; // Optimization for Flip test - calling up to test topmost parent is expensive
+ public bool AddToBounds = true; // Whether an object is added to the bounds of an object container or not.
+ public string Tag = "";
+
+ public float TurnSpeed = 2.0f;
+
+ private bool m_forceDraw = false;
+
+ protected SpriteEffects _flip = SpriteEffects.None;
+
+ private float _speed = 0; // Speed represents the object's internal speed.
+ public float CurrentSpeed { get; set; } // Current Speed represents the object's actual speed. So it could have a speed of 10, but be moving at 0 (which means it's not moving at all).
+
+ public string Name { get; set; }
+
+ public int ID { get; set; }
+
+ private bool m_isDisposed = false;
+ public bool IsDisposed { get { return m_isDisposed; } }
+
+ public bool IsTriggered { get; set; }
+ public bool LockFlip { get; set; }
+
+ public bool OverrideParentScale { get; set; }
+
+ private bool _useCachedValues = false;
+
+ public GameObj()
+ {
+ //GameObjHandler.GameObjList.Add(this);
+ Visible = true;
+ }
+
+ public virtual void ChangeSprite(string spriteName) { }
+
+ public virtual void Draw(Camera2D camera) { }
+ public virtual void DrawOutline(Camera2D camera) { }
+
+ public void DrawBounds(Camera2D camera, Texture2D texture, Color color)
+ {
+ if (Parent != null)
+ camera.Draw(texture, new Rectangle((int)(Bounds.X + this.AbsX - (this.X * Parent.ScaleX)),(int)(Bounds.Y + this.AbsY - (this.Y * Parent.ScaleY)), Bounds.Width, Bounds.Height) , color);
+ else
+ camera.Draw(texture, Bounds, color);
+ }
+
+ public void DrawAbsBounds(Camera2D camera, Texture2D texture, Color color)
+ {
+ camera.Draw(texture, AbsBounds, color);
+ }
+
+ public virtual void PopulateFromXMLReader(XmlReader reader, CultureInfo ci)
+ {
+ if (reader.MoveToAttribute("Name"))
+ this.Name = reader.Value;
+ if (reader.MoveToAttribute("X"))
+ X = float.Parse(reader.Value, NumberStyles.Any, ci);
+ if (reader.MoveToAttribute("Y"))
+ Y = float.Parse(reader.Value, NumberStyles.Any, ci);
+ if (reader.MoveToAttribute("Width"))
+ _width = int.Parse(reader.Value, NumberStyles.Any, ci);
+ if (reader.MoveToAttribute("Height"))
+ _height = int.Parse(reader.Value, NumberStyles.Any, ci);
+ if (reader.MoveToAttribute("ScaleX"))
+ this.ScaleX = float.Parse(reader.Value, NumberStyles.Any, ci);
+ if (reader.MoveToAttribute("ScaleY"))
+ this.ScaleY = float.Parse(reader.Value, NumberStyles.Any, ci);
+ if (reader.MoveToAttribute("Rotation"))
+ this.Rotation= float.Parse(reader.Value, NumberStyles.Any, ci);
+ if (reader.MoveToAttribute("Tag"))
+ this.Tag= reader.Value;
+ if (reader.MoveToAttribute("Layer"))
+ this.Layer = int.Parse(reader.Value, NumberStyles.Any, ci);
+
+ // Flip needs to be passed in as a bool instead of a SpriteEffects enum.
+ bool flip = false;
+ if (reader.MoveToAttribute("Flip"))
+ flip = bool.Parse(reader.Value);
+
+ if (flip == true)
+ this.Flip = SpriteEffects.FlipHorizontally;
+ else
+ this.Flip = SpriteEffects.None;
+ }
+
+ public virtual void Dispose()
+ {
+ //GameObjHandler.GameObjList.Remove(this);
+ _parent = null;
+ _topmostParent = null;
+ m_isDisposed = true;
+ }
+
+ public object Clone()
+ {
+ GameObj obj = CreateCloneInstance();
+ this.FillCloneInstance(obj);
+ return obj;
+ }
+
+ protected abstract GameObj CreateCloneInstance();
+
+ protected virtual void FillCloneInstance(object obj)
+ {
+ // Must set flip to none before cloning data since flip affects some of the object's properties.
+ SpriteEffects storedFlip = this.Flip;
+ this.Flip = SpriteEffects.None;
+
+ GameObj clone = obj as GameObj;
+ clone.Position = this.Position;
+ clone.Name = this.Name;
+ clone.Visible = this.Visible;
+ clone.Scale = this.Scale;
+ clone.Rotation = this.Rotation;
+ clone.Parent= this.Parent;
+
+ clone.Anchor = this.Anchor;
+ clone.Orientation = this.Orientation;
+ clone.ForceDraw = this.ForceDraw;
+ clone.Tag = this.Tag;
+ clone.AddToBounds = this.AddToBounds;
+ clone.IsTriggered = this.IsTriggered;
+ clone.TurnSpeed = this.TurnSpeed;
+ clone.Speed = this.Speed;
+ clone.CurrentSpeed = this.CurrentSpeed;
+ clone.Layer = this.Layer;
+ clone.Heading = this.Heading;
+ clone.Opacity = this.Opacity;
+ clone.TextureColor = this.TextureColor;
+
+ this.Flip = storedFlip; // Reset flip what it is supposed to be before cloning it.
+ clone.Flip = this.Flip;
+ clone.OverrideParentScale = this.OverrideParentScale;
+ clone.UseCachedValues = this.UseCachedValues;
+ }
+
+ #region Properties
+
+ public virtual int Width
+ {
+ get { return (int)(_width * ScaleX); }
+ }
+
+ public virtual int Height
+ {
+ get { return (int)(_height * ScaleY); }
+ }
+
+ public float Rotation
+ {
+ set { _rotation = MathHelper.ToRadians(value); }
+ get { return MathHelper.ToDegrees(_rotation); }
+ }
+
+ public float Layer
+ {
+ set { _layer = value; }
+ get { return _layer; }
+ }
+
+ public float X
+ {
+ set
+ {
+ _position.X = value;
+
+ // This is what used to be here. If you read the logic, they all boiled down to the same thing :/
+ //if (this.Flip == SpriteEffects.FlipHorizontally && Parent != null)
+ // _position.X = value;
+ // //_position.X = value / Parent.ScaleX; // Disabled this because calling ChangeSprite on scaled PhysicsObjectContainers resulted in wrong placements of the physics objects in the container.
+ //else
+ //{
+ // if (Parent == null)
+ // _position.X = value;
+ // else
+ // _position.X = value;
+ // //_position.X = value / Parent.ScaleX;
+ //}
+ }
+ get
+ {
+ if (Parent == null || this.Flip != SpriteEffects.FlipHorizontally)
+ return _position.X;
+ return -_position.X * Parent.ScaleX; // otherwise flip it horizontally
+ }
+ }
+
+ public float Y
+ {
+ set
+ {
+ _position.Y = value;
+
+ // This is what used to be here. If you read the logic, they all boiled down to the same thing :/
+ //if (Parent == null)
+ // _position.Y = value;
+ //else
+ // _position.Y = value;
+ // //_position.Y = value / Parent.ScaleY;
+ }
+ get
+ {
+ return _position.Y;
+
+ // This is what used to be here. If you read the logic, they all boiled down to the same thing :/
+ //if (Parent == null)
+ // return _position.Y;
+ //else
+ // return _position.Y;// *Parent.ScaleY;
+ }
+ }
+
+ public Vector2 Position
+ {
+ set
+ {
+ if (Parent == null || Flip != SpriteEffects.FlipHorizontally)
+ _position = value;
+ else
+ _position = new Vector2(value.X, value.Y);
+
+ // This is what used to be here.
+ //if (Flip == SpriteEffects.FlipHorizontally && Parent != null)
+ // _position = new Vector2(value.X, value.Y);
+ // //_position = new Vector2(value.X / Parent.ScaleX, value.Y / Parent.ScaleY);
+ //else
+ //{
+ // if (Parent == null)
+ // _position = value;
+ // else
+ // _position = value;
+ // //_position = value / Parent.Scale;
+ //}
+ }
+ get
+ {
+ if (Parent == null)
+ return _position;
+ else if (Flip == SpriteEffects.FlipHorizontally)
+ return new Vector2(-_position.X * Parent.ScaleX, _position.Y * Parent.ScaleY);
+ else
+ return _position * Parent.Scale;
+
+ // This is what used to be here.
+ //if (Flip == SpriteEffects.FlipHorizontally && Parent != null)
+ // return new Vector2(-_position.X * Parent.ScaleX, _position.Y * Parent.ScaleY);
+ //else
+ //{
+ // if (Parent == null)
+ // return _position;
+ // else
+ // return _position * Parent.Scale;
+ //}
+ }
+ }
+
+ public float AbsX
+ {
+ get
+ {
+ if (Parent == null)
+ return X;
+ else
+ return Parent.X + RotatedPoint.X;
+ }
+ }
+
+ public Vector2 AbsPosition
+ {
+ get { return new Vector2(AbsX, AbsY); }
+ }
+
+ public float AbsY
+ {
+ get
+ {
+ if (Parent == null)
+ return (int)this.Y; // Casting to int because too sensitive a Y value causes up/down jitter.
+ else
+ return (int)Parent.Y + (int)RotatedPoint.Y;
+ }
+ }
+
+ private Vector2 RotatedPoint
+ {
+ get
+ {
+ if (Parent == null || Parent.Rotation == 0)
+ return Position;
+ else
+ return CDGMath.RotatedPoint(Position, Parent.Rotation);
+ }
+ }
+
+ public virtual float AnchorX
+ {
+ set { _anchor.X = value; }
+ get
+ {
+ if (Flip == SpriteEffects.FlipHorizontally)
+ return this.Width / ScaleX - _anchor.X; // Cannot just call this._width because SpriteObj overrides Width.
+ else
+ return _anchor.X;
+ }
+ }
+
+ public virtual float AnchorY
+ {
+ set { _anchor.Y = value; }
+ get { return _anchor.Y; }
+ }
+
+ public virtual Vector2 Anchor
+ {
+ set { _anchor = value; }
+ get
+ {
+ if (Flip == SpriteEffects.FlipHorizontally)
+ return new Vector2(this.Width / ScaleX - _anchor.X, _anchor.Y); // Cannot just call this._width because SpriteObj overrides Width.
+ else
+ return _anchor;
+ }
+ }
+
+ public virtual float ScaleX
+ {
+ set { _scale.X = value; }
+ get { return _scale.X; }
+ }
+
+ public virtual float ScaleY
+ {
+ set { _scale.Y = value; }
+ get { return _scale.Y; }
+ }
+
+ public virtual Vector2 Scale
+ {
+ set { _scale = value; }
+ get { return _scale; }
+ }
+
+ public float Orientation
+ {
+ get { return _orientation; }
+ set { _orientation = value; }
+ }
+
+ public float HeadingX
+ {
+ get { return _heading.X; }
+ set { _heading.X = value; }
+ }
+
+ public float HeadingY
+ {
+ get { return _heading.Y; }
+ set { _heading.Y = value; }
+ }
+
+ public Vector2 Heading
+ {
+ get { return _heading; }
+ set { _heading = value; }
+ }
+
+ public float Speed
+ {
+ get { return _speed; }
+ set { _speed = value; }
+ }
+
+ public virtual SpriteEffects Flip
+ {
+ get
+ {
+ if (_topmostParent == null)
+ return _flip;
+ else
+ return _topmostParent.Flip;
+ }
+ set
+ {
+ // if (this.Parent == null && (this is SpriteObj) && _flip != value)
+ // this.X += this.Width - this.AnchorX * 2;
+ _flip = value;
+ }
+ }
+
+ public virtual Rectangle Bounds
+ {
+ get
+ {
+ if (Parent == null)
+ {
+ if (this.Rotation == 0)
+ {
+ return new Rectangle((int)(-this.AnchorX * ScaleX + this.X),
+ (int)(-this.AnchorY * ScaleY + this.Y),
+ this.Width, this.Height); // Do not multiply width and height by scale because their property already does that.
+ }
+ else
+ {
+ return CollisionMath.RotatedRectBounds(new Rectangle((int)(-this.AnchorX * ScaleX + this.X),
+ (int)(-this.AnchorY * ScaleY + this.Y),
+ this.Width, this.Height), this.Anchor, Rotation);
+ }
+ }
+ else
+ {
+ if (Parent.Rotation + this.Rotation == 0)
+ {
+ return new Rectangle((int)((-this.AnchorX * ScaleX + this.X) * Parent.ScaleX),
+ (int)((-this.AnchorY * ScaleY + this.Y) * Parent.ScaleY),
+ (int)(this.Width * Parent.ScaleX), (int)(this.Height * Parent.ScaleY)); // Do not multiply width and height by scale because their property already does that.
+ }
+ else
+ {
+ return CollisionMath.RotatedRectBounds(new Rectangle((int)(-this.AnchorX * ScaleX * Parent.ScaleX + this.X),
+ (int)(-this.AnchorY * ScaleY * Parent.ScaleY + this.Y),
+ (int)(this.Width * Parent.ScaleX), (int)(this.Height * Parent.ScaleY)), this.Anchor, Parent.Rotation + this.Rotation);
+ }
+ }
+ }
+ }
+
+ public virtual Rectangle AbsBounds
+ {
+ get
+ {
+ if (Parent == null)
+ {
+ if (this.Rotation == 0)
+ {
+ return new Rectangle((int)(-this.AnchorX * ScaleX + this.AbsX),
+ (int)(-this.AnchorY * ScaleY + this.AbsY),
+ this.Width, this.Height); // Do not multiply width and height by scale because their property already does that.
+ }
+ else
+ {
+ return CollisionMath.RotatedRectBounds(new Rectangle((int)(-this.AnchorX * ScaleX + this.AbsX),
+ (int)(-this.AnchorY * ScaleY + this.AbsY),
+ this.Width, this.Height), this.Anchor, Rotation);
+ }
+ }
+ else
+ {
+ if (Parent.Rotation + this.Rotation == 0)
+ {
+ return new Rectangle((int)(-this.AnchorX * ScaleX * Parent.ScaleX + this.AbsX),
+ (int)(-this.AnchorY * ScaleY * Parent.ScaleY + this.AbsY),
+ (int)(this.Width * Parent.ScaleX), (int)(this.Height * Parent.ScaleY)); // Do not multiply width and height by scale because their property already does that.
+ }
+ else
+ {
+ return CollisionMath.RotatedRectBounds(new Rectangle((int)(-this.AnchorX * ScaleX * Parent.ScaleX + this.AbsX),
+ (int)(-this.AnchorY * ScaleY * Parent.ScaleY + this.AbsY),
+ (int)(this.Width * Parent.ScaleX), (int)(this.Height * Parent.ScaleY)), this.Anchor, Parent.Rotation + this.Rotation);
+ }
+ }
+ }
+ }
+
+ private void SetTopMostParent(ObjContainer newParent)
+ {
+ if (newParent != null)
+ {
+ _topmostParent = newParent;
+ SetTopMostParent(newParent.Parent);
+ }
+ }
+
+ public ObjContainer Parent
+ {
+ set
+ {
+ _parent = value;
+ if (value == null)
+ {
+ _topmostParent = null;
+ }
+ else
+ {
+ SetTopMostParent(value);
+ }
+ }
+ get { return _parent; }
+ }
+
+ public bool ForceDraw
+ {
+ get
+ {
+ if (Parent != null)
+ return _topmostParent.ForceDraw;
+ else
+ return m_forceDraw;
+ }
+ set { m_forceDraw = value; }
+ }
+
+ private float m_opacity = 1.0f;
+ public float Opacity
+ {
+ get
+ {
+ if (Parent == null)
+ return m_opacity;
+ else
+ return m_opacity * Parent.Opacity;
+ }
+ set { m_opacity = value; }
+ }
+
+ private Color m_textureColor = Color.White;
+ public Color TextureColor
+ {
+ get
+ {
+ if (Parent == null || Parent.TextureColor == Color.White)
+ return m_textureColor;// * Opacity;
+ else
+ return Parent.TextureColor;// *Opacity;
+ }
+ set { m_textureColor = value; }
+ }
+
+ public bool UseCachedValues
+ {
+ get
+ {
+ // Not totally convinced this below thing is an optimization yet...getting conflicting data
+ //if (_topmostParent == null)
+ // return _useCachedValues;
+ //else
+ // return _topmostParent.UseCachedValues;
+ if (Parent == null)
+ return _useCachedValues;
+ return Parent.UseCachedValues;
+ }
+ set { _useCachedValues = value; }
+ }
+
+ #endregion
+
+ #region ClickHandlers
+
+ //public bool LeftJustPressed(Camera2D camera = null)
+ //{
+ // Vector2 mousePos = new Vector2(InputManager.MouseX * EngineEV.ScreenRatio.X, InputManager.MouseY * EngineEV.ScreenRatio.Y);
+
+ // if (camera == null) return InputManager.MouseLeftJustPressed() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1));
+ // else return InputManager.MouseLeftJustPressed() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)(mousePos.X + camera.X - camera.Width / 2), (int)(mousePos.Y + camera.Y - camera.Height / 2), 1, 1));
+ //}
+
+ //public bool LeftPressed(Camera2D camera = null)
+ //{
+ // Vector2 mousePos = new Vector2(InputManager.MouseX * EngineEV.ScreenRatio.X, InputManager.MouseY * EngineEV.ScreenRatio.Y);
+
+ // if (camera == null) return InputManager.MouseLeftPressed() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1));
+ // else return InputManager.MouseLeftPressed() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)(mousePos.X + camera.X - camera.Width / 2), (int)(mousePos.Y + camera.Y - camera.Height / 2), 1, 1));
+ //}
+
+ //public bool LeftJustReleased(Camera2D camera = null)
+ //{
+ // Vector2 mousePos = new Vector2(InputManager.MouseX * EngineEV.ScreenRatio.X, InputManager.MouseY * EngineEV.ScreenRatio.Y);
+
+ // if (camera == null) return InputManager.MouseLeftJustReleased() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1));
+ // else return InputManager.MouseLeftJustReleased() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)(mousePos.X + camera.X - camera.Width / 2), (int)(mousePos.Y + camera.Y - camera.Height / 2), 1, 1));
+ //}
+
+ //public bool RightJustPressed(Camera2D camera = null)
+ //{
+ // Vector2 mousePos = new Vector2(InputManager.MouseX * EngineEV.ScreenRatio.X, InputManager.MouseY * EngineEV.ScreenRatio.Y);
+
+ // if (camera == null) return InputManager.MouseRightJustPressed() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1));
+ // else return InputManager.MouseRightJustPressed() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)(mousePos.X + camera.X - camera.Width / 2), (int)(mousePos.Y + camera.Y - camera.Height / 2), 1, 1));
+ //}
+
+ //public bool RightPressed(Camera2D camera = null)
+ //{
+ // Vector2 mousePos = new Vector2(InputManager.MouseX * EngineEV.ScreenRatio.X, InputManager.MouseY * EngineEV.ScreenRatio.Y);
+
+ // if (camera == null) return InputManager.MouseRightPressed() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1));
+ // else return InputManager.MouseRightPressed() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)(mousePos.X + camera.X - camera.Width / 2), (int)(mousePos.Y + camera.Y - camera.Height / 2), 1, 1));
+ //}
+
+ //public bool RightJustReleased(Camera2D camera = null)
+ //{
+ // if (camera == null) return InputManager.MouseRightJustReleased() && CollisionMath.Intersects(m_clickRect, new Rectangle(InputManager.MouseX, InputManager.MouseY, 1, 1));
+ // else return InputManager.MouseRightJustReleased() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)(InputManager.MouseX + camera.X - camera.Width / 2), (int)(InputManager.MouseY + camera.Y - camera.Height / 2), 1, 1));
+ //}
+
+
+ //public bool MiddleJustPressed(Camera2D camera = null)
+ //{
+ // Vector2 mousePos = new Vector2(InputManager.MouseX * EngineEV.ScreenRatio.X, InputManager.MouseY * EngineEV.ScreenRatio.Y);
+
+ // if (camera == null) return InputManager.MouseMiddleJustPressed() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1));
+ // else return InputManager.MouseMiddleJustPressed() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)(mousePos.X + camera.X - camera.Width / 2), (int)(mousePos.Y + camera.Y - camera.Height / 2), 1, 1));
+ //}
+
+ //public bool MiddlePressed(Camera2D camera = null)
+ //{
+ // Vector2 mousePos = new Vector2(InputManager.MouseX * EngineEV.ScreenRatio.X, InputManager.MouseY * EngineEV.ScreenRatio.Y);
+
+ // if (camera == null) return InputManager.MouseMiddlePressed() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1));
+ // else return InputManager.MouseMiddlePressed() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)(mousePos.X + camera.X - camera.Width / 2), (int)(mousePos.Y + camera.Y - camera.Height / 2), 1, 1));
+ //}
+
+ //public bool MiddleJustReleased(Camera2D camera = null)
+ //{
+ // Vector2 mousePos = new Vector2(InputManager.MouseX * EngineEV.ScreenRatio.X, InputManager.MouseY * EngineEV.ScreenRatio.Y);
+
+ // if (camera == null) return InputManager.MouseMiddleJustReleased() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1));
+ // else return InputManager.MouseMiddleJustReleased() && CollisionMath.Intersects(m_clickRect, new Rectangle((int)(mousePos.X + camera.X - camera.Width / 2), (int)(mousePos.Y + camera.Y - camera.Height / 2), 1, 1));
+ //}
+
+ public bool LeftMouseOverJustPressed(Camera2D camera)
+ {
+ return InputManager.MouseLeftJustPressed() && MouseOver(camera) == true;
+ }
+
+ public bool LeftMouseOverPressed(Camera2D camera)
+ {
+ return InputManager.MouseLeftPressed() && MouseOver(camera) == true;
+ }
+
+ public bool RightMouseOverJustPressed(Camera2D camera)
+ {
+ return InputManager.MouseRightJustPressed() && MouseOver(camera) == true;
+ }
+
+ public bool RightMouseOverPressed(Camera2D camera)
+ {
+ return InputManager.MouseRightPressed() && MouseOver(camera) == true;
+ }
+
+ public bool MiddleMouseOverJustPressed(Camera2D camera)
+ {
+ return InputManager.MouseMiddleJustPressed() && MouseOver(camera) == true;
+ }
+
+ public bool MiddleMouseOverPressed(Camera2D camera)
+ {
+ return InputManager.MouseMiddlePressed() && MouseOver(camera) == true;
+ }
+
+ //private Vector2 m_currentMousePos;
+ //private Vector2 m_previousMousePos;
+ private bool m_previousMouseOver = false;
+ public bool MouseOver(Camera2D camera = null)
+ {
+ //Vector2 mousePos = new Vector2(InputManager.MouseX * EngineEV.ScreenRatio.X, InputManager.MouseY * EngineEV.ScreenRatio.Y);
+ //m_previousMousePos = mousePos;
+ //if (camera == null) return CollisionMath.Intersects(m_clickRect, new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1));
+ //else return (CollisionMath.Intersects(m_clickRect, new Rectangle((int)(mousePos.X + camera.X - camera.Width / 2), (int)(mousePos.Y + camera.Y - camera.Height / 2), 1, 1)));
+
+ bool mouseOver = false;
+ int mouseX = (int)(InputManager.MouseX * EngineEV.ScreenRatio.X);
+ int mouseY = (int)(InputManager.MouseY * EngineEV.ScreenRatio.Y);
+ if (camera != null)
+ {
+ mouseX += (int)(camera.X - (camera.Width / 2f)); // This compensates for camera shift.
+ float xDiff = (mouseX - camera.X) - ((mouseX - camera.X) * (1f / camera.Zoom)); // This compensates for camera zoom.
+ mouseX = (int)(mouseX - xDiff);
+
+ mouseY += (int)(camera.Y - (camera.Height / 2f)); // This compensates for camera shift.
+ float yDiff = (mouseY - camera.Y) - ((mouseY - camera.Y) * (1f / camera.Zoom)); // This compensates for camera zoom.
+ mouseY = (int)(mouseY - yDiff);
+ }
+
+ mouseOver = AbsBounds.Contains(mouseX, mouseY);
+ return mouseOver;
+ }
+
+ public bool MouseJustOver(Camera2D camera = null)
+ {
+ //Vector2 mousePos = new Vector2(InputManager.MouseX * EngineEV.ScreenRatio.X, InputManager.MouseY * EngineEV.ScreenRatio.Y);
+ //if (m_previousMousePos.X != mousePos.X || m_previousMousePos.Y != mousePos.Y)
+ //{
+ // m_previousMousePos = mousePos;
+ // if (camera == null) return CollisionMath.Intersects(m_clickRect, new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1));
+ // else return (CollisionMath.Intersects(m_clickRect, new Rectangle((int)(mousePos.X + camera.X - camera.Width / 2), (int)(mousePos.Y + camera.Y - camera.Height / 2), 1, 1)));
+ //}
+ //m_previousMousePos = mousePos;
+ //return false;
+
+ bool mouseOver = MouseOver(camera);
+ bool mouseJustOver = false;
+ if (m_previousMouseOver == false && mouseOver == true)
+ {
+ mouseJustOver = true;
+ }
+
+ m_previousMouseOver = mouseOver;
+ return mouseJustOver;
+ }
+
+ #endregion
+
+ #region Touch Handlers
+
+ public bool JustTapped(Camera2D camera, bool relativeToCamera = false, bool useAbsBounds = true)
+ {
+ return JustTapped(camera, relativeToCamera, useAbsBounds ? AbsBounds : Bounds);
+ }
+
+ public bool JustTapped(Camera2D camera, bool relativeToCamera, Rectangle bounds)
+ {
+ bool tapped = false;
+
+ foreach (GestureSample tap in InputManager.Taps)
+ {
+ int touchX, touchY;
+ TouchCoordsToViewportCoords(camera, tap.Position, out touchX, out touchY, relativeToCamera);
+ if (bounds.Contains(touchX, touchY))
+ {
+ tapped = true;
+ break;
+ }
+ }
+
+ return tapped;
+ }
+
+ public enum DragDirections
+ {
+ NONE,
+ LEFT,
+ RIGHT,
+ UP,
+ DOWN
+ }
+
+ public bool JustDragged(Camera2D camera, DragDirections direction, bool relativeToCamera = false, bool useAbsBounds = true)
+ {
+ bool dragged = false;
+
+ foreach (GestureSample drag in InputManager.Drags)
+ {
+ int touchX, touchY;
+ TouchCoordsToViewportCoords(camera, drag.Position, out touchX, out touchY, relativeToCamera);
+
+ Rectangle bounds = (useAbsBounds ? AbsBounds : Bounds);
+ if (bounds.Contains(touchX, touchY))
+ {
+ const int threshold = 5;
+ switch (direction)
+ {
+ case DragDirections.LEFT:
+ dragged = drag.Delta.X < -threshold;
+ break;
+ case DragDirections.RIGHT:
+ dragged = drag.Delta.X > threshold;
+ break;
+ case DragDirections.UP:
+ dragged = drag.Delta.Y < -threshold;
+ break;
+ case DragDirections.DOWN:
+ dragged = drag.Delta.Y > threshold;
+ break;
+ }
+ }
+ }
+
+ return dragged;
+ }
+
+ public static readonly Vector2 NoTouchPosition = new Vector2(-1, -1);
+ public Vector2 GetPositionTouched(Camera2D camera, Rectangle bounds, bool relativeToCamera = false)
+ {
+ TouchCollection touches = TouchPanel.GetState();
+ foreach (TouchLocation touch in touches)
+ {
+ int touchX, touchY;
+ TouchCoordsToViewportCoords(camera, touch.Position, out touchX, out touchY, relativeToCamera);
+ if (bounds.Contains(touchX, touchY))
+ {
+ return new Vector2(touchX, touchY);
+ }
+ }
+
+ // Nothing is touching this object. Return (-1, -1) to indicate an invalid value.
+ return NoTouchPosition;
+ }
+
+ public static void TouchCoordsToViewportCoords(Camera2D camera, Vector2 touchCoords, out int vx, out int vy, bool relativeToCamera)
+ {
+ Viewport viewport = camera.GraphicsDevice.Viewport;
+ int viewportX = (int)((touchCoords.X - viewport.X) / viewport.Width * EngineEV.ScreenWidth);
+ int viewportY = (int)((touchCoords.Y - viewport.Y) / viewport.Height * EngineEV.ScreenHeight);
+
+ if (relativeToCamera)
+ {
+ viewportX += (int)(camera.X - (camera.Width / 2f)); // This compensates for camera shift.
+ float xDiff = (viewportX - camera.X) - ((viewportX - camera.X) * (1f / camera.Zoom)); // This compensates for camera zoom.
+ viewportX = (int)(viewportX - xDiff);
+
+ viewportY += (int)(camera.Y - (camera.Height / 2f)); // This compensates for camera shift.
+ float yDiff = (viewportY - camera.Y) - ((viewportY - camera.Y) * (1f / camera.Zoom)); // This compensates for camera zoom.
+ viewportY = (int)(viewportY - yDiff);
+ }
+
+ vx = viewportX;
+ vy = viewportY;
+ }
+
+ #endregion
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/Interfaces/IAnimateableObj.cs b/DS2DEngine/src/GameObjs/Interfaces/IAnimateableObj.cs
new file mode 100644
index 0000000..615a0ca
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/Interfaces/IAnimateableObj.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public interface IAnimateableObj
+ {
+ int AnimationSpeed { get; set; }
+ bool IsAnimating { get; }
+ bool IsPaused { get; }
+ bool IsLooping { get; }
+ int TotalFrames { get; }
+ int CurrentFrame { get; }
+ float AnimationDelay { get; set; }
+ string SpriteName { get; set; }
+
+ void ChangeSprite(string spriteName);
+ void PlayAnimation(bool loopAnimation = true);
+ void PlayAnimation(int startIndex, int endIndex, bool loopAnimation);
+ void PauseAnimation();
+ void ResumeAnimation();
+ void StopAnimation();
+ void GoToNextFrame();
+ void GoToFrame(int frameIndex);
+ int FindLabelIndex(string label);
+
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/Interfaces/IClickableObj.cs b/DS2DEngine/src/GameObjs/Interfaces/IClickableObj.cs
new file mode 100644
index 0000000..7d87196
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/Interfaces/IClickableObj.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public interface IClickableObj
+ {
+ //bool LeftJustPressed(Camera2D camera);
+ //bool LeftPressed(Camera2D camera);
+ //bool LeftJustReleased(Camera2D camera);
+
+ //bool RightJustPressed(Camera2D camera);
+ //bool RightPressed(Camera2D camera);
+ //bool RightJustReleased(Camera2D camera);
+
+ //bool MiddleJustPressed(Camera2D camera);
+ //bool MiddlePressed(Camera2D camera);
+ //bool MiddleJustReleased(Camera2D camera);
+
+ bool LeftMouseOverJustPressed(Camera2D camera);
+ bool LeftMouseOverPressed(Camera2D camera);
+ //bool LeftJustReleased(Camera2D camera);
+
+ bool RightMouseOverJustPressed(Camera2D camera);
+ bool RightMouseOverPressed(Camera2D camera);
+ //bool RightJustReleased(Camera2D camera);
+
+ bool MiddleMouseOverJustPressed(Camera2D camera);
+ bool MiddleMouseOverPressed(Camera2D camera);
+ //bool MiddleJustReleased(Camera2D camera);
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/Interfaces/IDisposableObj.cs b/DS2DEngine/src/GameObjs/Interfaces/IDisposableObj.cs
new file mode 100644
index 0000000..28028e2
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/Interfaces/IDisposableObj.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public interface IDisposableObj
+ {
+ bool IsDisposed { get; }
+ void Dispose();
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/Interfaces/IDrawableObj.cs b/DS2DEngine/src/GameObjs/Interfaces/IDrawableObj.cs
new file mode 100644
index 0000000..366f94b
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/Interfaces/IDrawableObj.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public interface IDrawableObj
+ {
+ float Opacity { get; set; }
+ Color TextureColor { get; set; }
+
+ void Draw(Camera2D camera);
+ void ChangeSprite(string spriteName);
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/Interfaces/IKillableObj.cs b/DS2DEngine/src/GameObjs/Interfaces/IKillableObj.cs
new file mode 100644
index 0000000..255d8bb
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/Interfaces/IKillableObj.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public interface IKillableObj
+ {
+ bool IsKilled { get; }
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/Interfaces/IPoolableObj.cs b/DS2DEngine/src/GameObjs/Interfaces/IPoolableObj.cs
new file mode 100644
index 0000000..45d46d1
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/Interfaces/IPoolableObj.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public interface IPoolableObj
+ {
+ bool IsCheckedOut { get; set; }
+ bool IsActive { get; set; }
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/Interfaces/IStateObj.cs b/DS2DEngine/src/GameObjs/Interfaces/IStateObj.cs
new file mode 100644
index 0000000..bfcb635
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/Interfaces/IStateObj.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public interface IStateObj
+ {
+ int State { get; set; }
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/Interfaces/ITriggerableObj.cs b/DS2DEngine/src/GameObjs/Interfaces/ITriggerableObj.cs
new file mode 100644
index 0000000..f0de31f
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/Interfaces/ITriggerableObj.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DS2DEngine
+{
+ public interface ITriggerableObj
+ {
+ bool IsTriggered { get; set; }
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/ObjContainer.cs b/DS2DEngine/src/GameObjs/ObjContainer.cs
new file mode 100644
index 0000000..b38293c
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/ObjContainer.cs
@@ -0,0 +1,589 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using SpriteSystem;
+
+namespace DS2DEngine
+{
+ public class ObjContainer : GameObj, IAnimateableObj
+ {
+ protected List _objectList = null;
+ protected int _numChildren;
+ private Rectangle m_bounds = new Rectangle();
+ protected string _spriteName = "";
+
+ protected bool m_animate = false;
+ protected bool m_loop = false;
+ protected int m_currentFrameIndex;
+ protected int m_startAnimationIndex;
+ protected int m_endAnimationIndex;
+
+ protected int m_numCharData = 0; // An integer that keeps track of how many objects were added via ChangeSprite().
+ protected int m_charDataStartIndex = -1; // An integer that keeps track of the starting index of the CharData.
+
+ private bool m_wasAnimating = false; // A bool that determines whether an animation that was paused was actually animating when paused.
+
+ public float AnimationDelay { get; set; }
+ private float m_timeDelayCounter = 0;
+ private float m_totalGameTime = 0;
+
+ protected List m_spritesheetNameList;
+
+ public Color OutlineColour = Color.Black;
+ public int OutlineWidth { get; set; }
+
+ public ObjContainer()
+ {
+ AnimationSpeed = 1;
+ _objectList = new List();
+ _numChildren = 0;
+ m_spritesheetNameList = new List();
+ }
+
+ public ObjContainer(string spriteName)
+ {
+ AnimationSpeed = 1;
+ _objectList = new List();
+ m_spritesheetNameList = new List();
+
+ _numChildren = 0;
+ _spriteName = spriteName;
+
+ List charDataList = SpriteLibrary.GetCharData(spriteName);
+ foreach (CharacterData chars in charDataList)
+ {
+ SpriteObj newObj = new SpriteObj(chars.Child);
+ newObj.X = chars.ChildX; // Not sure why this is commented. Only commented it out because it was commented out for PhysicsObjContainer.
+ newObj.Y = chars.ChildY;
+ if (!m_spritesheetNameList.Contains(newObj.SpritesheetName))
+ m_spritesheetNameList.Add(newObj.SpritesheetName);
+ this.AddChild(newObj);
+ }
+
+ m_numCharData = charDataList.Count;
+ m_charDataStartIndex = 0;
+ }
+
+ public override void ChangeSprite(string spriteName)
+ {
+ _spriteName = spriteName;
+
+ if (m_charDataStartIndex == -1)
+ m_charDataStartIndex = _objectList.Count; // Sets the starting index of when ChangeSprite was called to the end of the object list if it wasn't originally set in the constructor.
+
+ List charDataList = SpriteLibrary.GetCharData(spriteName);
+
+ m_spritesheetNameList.Clear();
+ int indexCounter = 0;
+ for (int i = m_charDataStartIndex; i < charDataList.Count; i++)
+ {
+ CharacterData charData = charDataList[indexCounter];
+
+ if (i >= m_numCharData)
+ {
+ SpriteObj objToAdd = new SpriteObj(charData.Child);
+ objToAdd.X = charData.ChildX;
+ objToAdd.Y = charData.ChildY;
+ this.AddChildAt(i, objToAdd);
+ m_numCharData++;
+ }
+ else
+ {
+ SpriteObj spriteObj = _objectList[i] as SpriteObj;
+ spriteObj.Visible = true;
+ if (spriteObj != null)
+ {
+ spriteObj.ChangeSprite(charData.Child);
+ spriteObj.X = charData.ChildX;
+ spriteObj.Y = charData.ChildY;
+ if (!m_spritesheetNameList.Contains(spriteObj.SpritesheetName))
+ m_spritesheetNameList.Add(spriteObj.SpritesheetName);
+ }
+ }
+
+ indexCounter++;
+ }
+
+ if (charDataList.Count < m_numCharData)
+ {
+ //for (int i = m_charDataStartIndex + (m_numCharData - charDataList.Count); i < m_charDataStartIndex + m_numCharData; i++)
+ //for (int i = m_charDataStartIndex + 1; i < m_charDataStartIndex + m_numCharData; i++)
+ for (int i = charDataList.Count; i < m_numCharData; i++)
+ {
+ _objectList[i].Visible = false;
+ }
+ }
+
+ this.StopAnimation();
+ CalculateBounds();
+ }
+
+ public virtual void AddChild(GameObj obj)
+ {
+ _objectList.Add(obj);
+ obj.Parent = this;
+ _numChildren++;
+
+ SpriteObj spriteObj = obj as SpriteObj;
+ if (spriteObj != null && !m_spritesheetNameList.Contains(spriteObj.SpritesheetName))
+ m_spritesheetNameList.Add(spriteObj.SpritesheetName);
+
+ CalculateBounds();
+ }
+
+ public virtual void AddChildAt(int index, GameObj obj)
+ {
+ _objectList.Insert(index, obj);
+ obj.Parent = this;
+ _numChildren++;
+
+ SpriteObj spriteObj = obj as SpriteObj;
+ if (spriteObj != null && !m_spritesheetNameList.Contains(spriteObj.SpritesheetName))
+ m_spritesheetNameList.Add(spriteObj.SpritesheetName);
+
+ CalculateBounds();
+ }
+
+ public virtual void RemoveChild(GameObj obj)
+ {
+ obj.Parent = null;
+ _objectList.Remove(obj);
+ _numChildren--;
+
+ SpriteObj spriteObj = obj as SpriteObj;
+ if (spriteObj != null && m_spritesheetNameList.Contains(spriteObj.SpritesheetName))
+ m_spritesheetNameList.Remove(spriteObj.SpritesheetName);
+
+ CalculateBounds();
+ }
+
+ public virtual GameObj RemoveChildAt(int index)
+ {
+ _numChildren--;
+ GameObj objectToReturn = _objectList[index];
+ objectToReturn.Parent = null;
+ _objectList.RemoveAt(index);
+
+ CalculateBounds();
+
+ SpriteObj spriteObj = objectToReturn as SpriteObj;
+ if (spriteObj != null && m_spritesheetNameList.Contains(spriteObj.SpritesheetName))
+ m_spritesheetNameList.Remove(spriteObj.SpritesheetName);
+
+ return objectToReturn;
+ }
+
+ public virtual void RemoveAll()
+ {
+ foreach (GameObj obj in _objectList)
+ obj.Parent = null;
+ _objectList.Clear();
+ _numChildren = 0;
+
+ m_bounds = new Rectangle();
+
+ m_spritesheetNameList.Clear();
+ }
+
+ public GameObj GetChildAt(int index)
+ {
+ return _objectList[index];
+ }
+
+ public override void Draw(Camera2D camera)
+ {
+ if (this.Visible == true)
+ {
+ if (CollisionMath.Intersects(this.Bounds, camera.LogicBounds) || ForceDraw == true)
+ {
+ if (OutlineWidth > 0)
+ {
+ DrawOutline(camera);
+ }
+
+ foreach (GameObj obj in _objectList)
+ {
+ obj.Draw(camera);
+ }
+
+ float elapsedTotalSeconds = camera.ElapsedTotalSeconds;
+ if (m_animate == true && m_totalGameTime != elapsedTotalSeconds)
+ {
+ m_totalGameTime = elapsedTotalSeconds;
+
+ if (camera.GameTime != null)
+ m_timeDelayCounter += elapsedTotalSeconds;//(float)camera.GameTime.ElapsedGameTime.TotalSeconds;
+
+ if (m_timeDelayCounter >= AnimationDelay)
+ {
+ m_timeDelayCounter = 0;
+ GoToNextFrame();
+ }
+ }
+ }
+ }
+ }
+
+ //public override void DrawOutline(Camera2D camera, int width)
+ public override void DrawOutline(Camera2D camera)
+ {
+ if (this.Visible == true)
+ {
+ //if (CollisionMath.Intersects(this.Bounds, camera.Bounds) || ForceDraw == true)
+ {
+ foreach (GameObj obj in _objectList)
+ {
+ obj.DrawOutline(camera);
+ }
+ }
+ }
+ }
+
+ public void CalculateBounds()
+ {
+ int leftBound = int.MaxValue;
+ int rightBound = -int.MaxValue;
+ int topBound = int.MaxValue;
+ int bottomBound = -int.MaxValue;
+
+ // Hack to fix bounds of pre-flipped objcontainers.
+ SpriteEffects storedFlip = _flip;
+ _flip = SpriteEffects.None;
+
+ foreach (GameObj obj in _objectList)
+ {
+ if (obj.Visible == true && obj.AddToBounds == true)
+ {
+ //Rectangle boundsRect = new Rectangle((int)((-obj.AnchorX * obj.ScaleX + obj.X) * this.ScaleX), (int)(obj.Bounds.Y), obj.Width, obj.Height);
+ Rectangle objBounds = obj.Bounds;
+
+ if (objBounds.Left < leftBound)
+ leftBound = objBounds.Left;
+
+ if (objBounds.Right > rightBound)
+ rightBound = objBounds.Right;
+
+ if (objBounds.Top < topBound)
+ topBound = objBounds.Top;
+
+ if (objBounds.Bottom > bottomBound)
+ bottomBound = objBounds.Bottom;
+ }
+ }
+
+ m_bounds.X = leftBound;
+ m_bounds.Y = topBound;
+ m_bounds.Width = rightBound - leftBound;
+ m_bounds.Height = bottomBound - topBound;
+
+ // Hack to fix bounds of pre-flipped objcontainers.
+ if (storedFlip == SpriteEffects.FlipHorizontally)
+ m_bounds.X = -m_bounds.Right;
+ _flip = storedFlip;
+
+ _width = (int)(m_bounds.Width / this.ScaleX); // Divide by scale because the Width property multiplies by scale, so it would be mulitplied by scale twice when calling the Width property.
+ _height = (int)(m_bounds.Height / this.ScaleY); // Same for Height.
+ }
+
+ public void PlayAnimation(bool loopAnimation = true)
+ {
+ if (m_charDataStartIndex > -1)
+ {
+ foreach (GameObj obj in _objectList)
+ {
+ if (obj is IAnimateableObj)
+ (obj as IAnimateableObj).PlayAnimation(loopAnimation);
+ }
+ //(_objectList[m_charDataStartIndex] as IAnimateableObj).PlayAnimation(loopAnimation); // Uncomment this if you want to be able to separate the animating state
+ // of each object in this container.
+ m_loop = loopAnimation;
+ m_startAnimationIndex = 1;
+ m_endAnimationIndex = this.TotalFrames;
+ m_animate = true;
+ m_currentFrameIndex = m_startAnimationIndex;
+
+ m_timeDelayCounter = 0;
+ }
+ }
+
+ public void PlayAnimation(int startIndex, int endIndex, bool loopAnimation = false)
+ {
+ foreach (GameObj obj in _objectList)
+ {
+ IAnimateableObj animateableObj = obj as IAnimateableObj;
+ if (animateableObj != null)
+ animateableObj.PlayAnimation(startIndex, endIndex, loopAnimation);
+ }
+ m_loop = loopAnimation;
+
+ // Must be called after PlayAnimation because SpriteObj.cs also does this change.
+ startIndex = (startIndex - 1) * AnimationSpeed + 1;
+ endIndex = endIndex * AnimationSpeed;
+
+ if (startIndex < 1)
+ startIndex = 1;
+ else if (startIndex > TotalFrames)
+ startIndex = TotalFrames;
+
+ if (endIndex < 1)
+ endIndex = 1;
+ else if (endIndex > TotalFrames)
+ endIndex = TotalFrames;
+ m_startAnimationIndex = startIndex;
+ m_endAnimationIndex = endIndex;
+ m_currentFrameIndex = startIndex;
+ m_animate = true;
+ }
+
+ public void PlayAnimation(string startLabel, string endLabel, bool loopAnimation = false)
+ {
+ int startIndex = FindLabelIndex(startLabel);
+ int endIndex = FindLabelIndex(endLabel);
+ if (startIndex == -1)
+ throw new Exception("Could not find starting label " + startLabel);
+ else if (endIndex == -1)
+ throw new Exception("Could not find ending label " + endLabel);
+ else
+ PlayAnimation(startIndex, endIndex, loopAnimation);
+ }
+
+ public int FindLabelIndex(string label)
+ {
+ foreach (GameObj obj in _objectList)
+ {
+ IAnimateableObj animateableObj = obj as IAnimateableObj;
+ if (animateableObj != null)
+ {
+ int index = animateableObj.FindLabelIndex(label);
+ if (index != -1)
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ ///
+ /// For ObjContainer, GoToNextFrame() only keeps track of the CurrentFrame Index
+ ///
+ public void GoToNextFrame()
+ {
+ m_currentFrameIndex++;
+ if (m_currentFrameIndex > m_endAnimationIndex && m_loop == true)
+ m_currentFrameIndex = m_startAnimationIndex;
+ else if (m_currentFrameIndex > m_endAnimationIndex && m_loop == false)
+ m_animate = false;
+ }
+
+ public void GoToFrame(int index)
+ {
+ foreach (GameObj obj in _objectList)
+ {
+ if (obj is IAnimateableObj)
+ (obj as IAnimateableObj).GoToFrame(index);
+ }
+ //if (m_charDataStartIndex > -1)
+ // (_objectList[m_charDataStartIndex] as IAnimateableObj).GoToFrame(index);
+ }
+
+ public void ResumeAnimation()
+ {
+ if (m_charDataStartIndex > -1)
+ {
+ foreach (GameObj obj in _objectList)
+ {
+ if (obj is IAnimateableObj)
+ (obj as IAnimateableObj).ResumeAnimation();
+ }
+ //(_objectList[m_charDataStartIndex] as IAnimateableObj).ResumeAnimation();
+ if (m_wasAnimating == true)
+ m_animate = true;
+ }
+ m_wasAnimating = false;
+ }
+
+ public void PauseAnimation()
+ {
+ if (m_charDataStartIndex > -1)
+ {
+ foreach (GameObj obj in _objectList)
+ {
+ if (obj is IAnimateableObj)
+ (obj as IAnimateableObj).PauseAnimation();
+ }
+ //(_objectList[m_charDataStartIndex] as IAnimateableObj).PauseAnimation();
+ m_wasAnimating = this.IsAnimating;
+ m_animate = false;
+ }
+ }
+
+ public void StopAnimation()
+ {
+ if (m_charDataStartIndex > -1)
+ {
+ foreach (GameObj obj in _objectList)
+ {
+ if (obj is IAnimateableObj)
+ (obj as IAnimateableObj).StopAnimation();
+ }
+ //(_objectList[m_charDataStartIndex] as IAnimateableObj).StopAnimation();
+ m_animate = false;
+ }
+ m_wasAnimating = false;
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ foreach (GameObj obj in _objectList)
+ {
+ obj.Dispose();
+ }
+ RemoveAll();
+ _objectList.Clear();
+ _objectList = null;
+ base.Dispose();
+ }
+ }
+
+ protected override GameObj CreateCloneInstance()
+ {
+ if (_spriteName != "")
+ return new ObjContainer(_spriteName);
+ else
+ {
+ ObjContainer clone = new ObjContainer();
+ foreach (GameObj obj in _objectList)
+ clone.AddChild(obj.Clone() as GameObj);
+
+ return clone;
+ }
+ }
+
+ protected override void FillCloneInstance(object obj)
+ {
+ base.FillCloneInstance(obj);
+ }
+
+ public int NumChildren
+ {
+ get { return _objectList.Count; }
+ }
+
+ public override Rectangle Bounds
+ {
+ get
+ {
+ return new Rectangle((int)(m_bounds.X + this.X),
+ (int)(m_bounds.Y + this.Y),
+ m_bounds.Width, m_bounds.Height);
+ }
+ }
+
+ public Rectangle PureBounds
+ {
+ get
+ {
+ return new Rectangle((int)(m_bounds.X + this.X),
+ (int)(m_bounds.Y + this.Y),
+ m_bounds.Width, m_bounds.Height);
+ }
+ }
+
+ public Rectangle RelativeBounds
+ {
+ get { return m_bounds; }
+ }
+
+ public string SpriteName
+ {
+ get { return _spriteName; }
+ set { _spriteName = value; }
+ }
+
+ public bool IsAnimating
+ {
+ get { return m_animate; }
+ }
+
+ public bool IsLooping
+ {
+ get { return m_loop; }
+ }
+
+ public int CurrentFrame
+ {
+ get
+ {
+ if (m_currentFrameIndex > TotalFrames)
+ return TotalFrames;
+ return m_currentFrameIndex;
+ }
+ }
+
+ public int TotalFrames // Only returns the largest number of frames in the object list.
+ {
+ get
+ {
+ int totalFrames = 1;
+ foreach (GameObj obj in _objectList)
+ {
+ IAnimateableObj animatableObj = obj as IAnimateableObj;
+ if (animatableObj != null && obj.Visible == true)
+ {
+ int largestNumberOfFrames = animatableObj.TotalFrames;
+ if (largestNumberOfFrames > totalFrames)
+ totalFrames = largestNumberOfFrames;
+ }
+ }
+ return totalFrames;
+ //if (m_charDataStartIndex > -1)
+ // return (_objectList[m_charDataStartIndex] as IAnimateableObj).TotalFrames;
+ //return 0;
+ }
+ }
+
+ public int AnimationSpeed { get; set; }
+
+ public List SpritesheetNameList
+ {
+ get { return m_spritesheetNameList; }
+ }
+
+ public override float ScaleX
+ {
+ set { _scale.X = value; CalculateBounds(); }
+ get { return _scale.X; }
+ }
+
+ public override float ScaleY
+ {
+ set { _scale.Y = value; CalculateBounds(); }
+ get { return _scale.Y; }
+ }
+
+ public override Vector2 Scale
+ {
+ set { _scale = value; CalculateBounds(); }
+ get { return _scale; }
+ }
+
+ public override SpriteEffects Flip
+ {
+ get { return base.Flip; }
+ set
+ {
+ base.Flip = value;
+ CalculateBounds();
+ }
+ }
+
+ public bool IsPaused
+ {
+ get { return IsAnimating == false && m_wasAnimating == true; }
+ }
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/PhysicsObj.cs b/DS2DEngine/src/GameObjs/PhysicsObj.cs
new file mode 100644
index 0000000..82fc424
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/PhysicsObj.cs
@@ -0,0 +1,411 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+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 SpriteSystem;
+
+namespace DS2DEngine
+{
+ public class PhysicsObj : SpriteObj, IPhysicsObj
+ {
+ public LinkedListNode Node { get; set; }
+
+ public bool CollidesLeft { get; set; }
+ public bool CollidesRight { get; set; }
+ public bool CollidesTop { get; set; }
+ public bool CollidesBottom { get; set; }
+ public int CollisionTypeTag { get; set; }
+
+ private Vector2 _acceleration = Vector2.Zero;
+ public Boolean IsWeighted { get; set; }
+ public Boolean IsCollidable { get; set; }
+
+ private List _collisionBoxes = null;
+ public PhysicsManager PhysicsMngr { get; set; }
+ public bool DisableHitboxUpdating { get; set; }
+
+ public bool AccelerationXEnabled { get; set; }
+ public bool AccelerationYEnabled { get; set; }
+ public bool DisableAllWeight { get; set; }
+ public bool SameTypesCollide { get; set; }
+ public bool DisableGravity { get; set; }
+
+ private Rectangle m_terrainBounds;
+
+ #region Constructors
+ public PhysicsObj(string spriteName, PhysicsManager physicsManager = null)
+ : base(spriteName)
+ {
+ IsWeighted = true;
+ IsCollidable = true;
+ _collisionBoxes = new List();
+
+ if (physicsManager != null)
+ physicsManager.AddObject(this);
+
+ CollidesLeft = true;
+ CollidesRight = true;
+ CollidesTop = true;
+ CollidesBottom = true;
+
+ AccelerationXEnabled = true;
+ AccelerationYEnabled = true;
+
+ UpdateCollisionBoxes();
+ }
+
+ public PhysicsObj(Texture2D sprite, PhysicsManager physicsManager = null)
+ : base(sprite)
+ {
+ IsWeighted = true;
+ IsCollidable = true;
+ _collisionBoxes = new List();
+
+ if (physicsManager != null)
+ physicsManager.AddObject(this);
+
+ CollidesLeft = true;
+ CollidesRight = true;
+ CollidesTop = true;
+ CollidesBottom = true;
+
+ AccelerationXEnabled = true;
+ AccelerationYEnabled = true;
+ }
+ #endregion
+
+ public void AddCollisionBox(int xPos, int yPos, int width, int height, int hitboxType)
+ {
+ _collisionBoxes.Add(new CollisionBox((int)(xPos - AnchorX), (int)(yPos - AnchorY), width, height, hitboxType, this) { WasAdded = true, });
+ }
+
+ public void ClearCollisionBoxes()
+ {
+ _collisionBoxes.Clear();
+ }
+
+ public override void ChangeSprite(string spriteName)
+ {
+ base.ChangeSprite(spriteName);
+ UpdateCollisionBoxes();
+ }
+
+ public void UpdatePhysics(GameTime gameTime)
+ {
+ float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;
+ //if (elapsedSeconds > Consts.FRAMERATE_CAP) elapsedSeconds = Consts.FRAMERATE_CAP;
+ if (AccelerationXEnabled == true)
+ this.X += _acceleration.X * elapsedSeconds;
+
+ float yAcceleration = _acceleration.Y * elapsedSeconds;
+ if (yAcceleration < _acceleration.Y * Consts.FRAMERATE_CAP)
+ yAcceleration = _acceleration.Y * elapsedSeconds;
+ if (AccelerationYEnabled == true)
+ this.Y += yAcceleration;
+ }
+
+ public void UpdateCollisionBoxes()
+ {
+ if (Sprite.IsDisposed)
+ ReinitializeSprite();
+
+ if (m_frameDataList != null)
+ {
+ List hbList = m_frameDataList[_frameIndex].hitboxList;
+ m_terrainBounds = new Rectangle();
+ int leftBound = int.MaxValue;
+ int rightBound = -int.MaxValue;
+ int topBound = int.MaxValue;
+ int bottomBound = -int.MaxValue;
+
+ for (int i = 0; i < hbList.Count; i++)
+ {
+ if (_collisionBoxes.Count <= i)
+ {
+ CollisionBox newCollisionBox = new CollisionBox(this);
+ _collisionBoxes.Add(newCollisionBox);
+
+ //Adds the collision box to the parent if it is a PhysicsObjContainer.
+ //Because it passes by references, any changes to these collision boxes will be reflected in the PhysicsObjContainer.
+ if (Parent != null)
+ {
+ PhysicsObjContainer container = Parent as PhysicsObjContainer;
+ if (container != null)
+ container.CollisionBoxes.Add(newCollisionBox);
+ }
+ }
+
+ if (Parent == null)
+ {
+ _collisionBoxes[i].X = (int)(hbList[i].X * this.ScaleX);
+ _collisionBoxes[i].Y = (int)(hbList[i].Y * this.ScaleY);
+ _collisionBoxes[i].Width = (int)(hbList[i].Width * this.ScaleX);
+ _collisionBoxes[i].Height = (int)(hbList[i].Height * this.ScaleY);
+ }
+ else
+ {
+ _collisionBoxes[i].X = (int)(hbList[i].X * Parent.ScaleX * this.ScaleX);
+ _collisionBoxes[i].Y = (int)(hbList[i].Y * Parent.ScaleY * this.ScaleY);
+ _collisionBoxes[i].Width = (int)(hbList[i].Width * Parent.ScaleX * this.ScaleX);
+ _collisionBoxes[i].Height = (int)(hbList[i].Height * Parent.ScaleY * this.ScaleY);
+ }
+ _collisionBoxes[i].InternalRotation = hbList[i].Rotation;
+ _collisionBoxes[i].Type = hbList[i].Type;
+
+ if (_collisionBoxes[i].Type == Consts.TERRAIN_HITBOX)
+ {
+ Rectangle absRect = _collisionBoxes[i].AbsRect;
+
+ if (absRect.Left < leftBound)
+ leftBound = absRect.Left;
+
+ if (absRect.Right > rightBound)
+ rightBound = absRect.Right;
+
+ if (absRect.Top < topBound)
+ topBound = absRect.Top;
+
+ if (absRect.Bottom > bottomBound)
+ bottomBound = absRect.Bottom;
+ }
+ }
+
+ m_terrainBounds.X = leftBound;
+ m_terrainBounds.Y = topBound;
+ m_terrainBounds.Width = rightBound - leftBound;
+ m_terrainBounds.Height = bottomBound - topBound;
+
+ //Clears out any collision boxes not being used.
+ for (int k = hbList.Count; k < _collisionBoxes.Count; k++)
+ {
+ if (_collisionBoxes[k].WasAdded == false)
+ {
+ _collisionBoxes[k].X = 0;
+ _collisionBoxes[k].Y = 0;
+ _collisionBoxes[k].Width = 1;
+ _collisionBoxes[k].Height = 1;
+ _collisionBoxes[k].InternalRotation = 0;
+ _collisionBoxes[k].Type = Consts.NULL_HITBOX;
+ }
+ }
+
+ // This below optimziation didn't work. This cauese the hero's sword to
+ // go nuts animating up and down every other frame.
+ // - it seems to get an old value
+ // Optimization - Update cached values
+ //foreach (CollisionBox box in _collisionBoxes)
+ //{
+ // box.UpdateCachedValues();
+ //}
+ }
+ }
+
+ public virtual void CollisionResponse(CollisionBox thisBox, CollisionBox otherBox, int collisionResponseType)
+ {
+ if (this.IsWeighted == true && collisionResponseType == Consts.COLLISIONRESPONSE_TERRAIN)
+ {
+ //Vector2 mtdPos = CollisionMath.CalculateMTD(thisBox.AbsRect, otherBox.AbsRect);
+ Vector2 mtdPos = CollisionMath.RotatedRectIntersectsMTD(thisBox.AbsRect, thisBox.AbsRotation, Vector2.Zero, otherBox.AbsRect, otherBox.AbsRotation, Vector2.Zero);
+ IPhysicsObj otherPhysicsObj = otherBox.AbsParent as IPhysicsObj;
+ int collisionMarginOfError = 10;
+
+ if (mtdPos.Y < 0) // There is an object below this object.
+ {
+ if (this.AccelerationY > 0 && otherPhysicsObj.CollidesTop == true) // This object is falling and there is an object below, so stop this object from falling.
+ {
+ if (otherBox.AbsRotation != 0 ||
+ (otherPhysicsObj.TerrainBounds.Left <= this.TerrainBounds.Right && otherPhysicsObj.TerrainBounds.Right >= this.TerrainBounds.Right && (this.TerrainBounds.Right - otherPhysicsObj.TerrainBounds.Left > collisionMarginOfError)) ||
+ (this.TerrainBounds.Left <= otherPhysicsObj.TerrainBounds.Right && this.TerrainBounds.Right >= otherPhysicsObj.TerrainBounds.Right && (otherPhysicsObj.TerrainBounds.Right - this.TerrainBounds.Left > collisionMarginOfError)))
+ {
+ this.AccelerationY = 0;
+ this.Y += mtdPos.Y;
+ }
+ }
+ //else if (IsWeighted == false && otherPhysicsObj.CollidesTop == true) // The above code is only for weighted objects. Non-weighted objects presumably do not move by gravity, so collision detection needs to be always on.
+ // this.Y += mtdPos.Y;
+ }
+ else if (mtdPos.Y > 0) // There is an object above this object.
+ {
+ if (this.AccelerationY < 0 && otherPhysicsObj.CollidesBottom == true) // This object is going up and has hit an object above it, so stop this object from going up.
+ {
+ this.AccelerationY = 0;
+ this.Y += mtdPos.Y;
+ }
+ // else if (IsWeighted == false && otherPhysicsObj.CollidesBottom == true) // The above code is only for weighted objects. Non-weighted objects presumably do not move by gravity, so collision detection needs to be always on.
+ // this.Y += mtdPos.Y;
+ }
+
+ if (mtdPos.X != 0)
+ {
+ this.AccelerationX = 0;
+ if (((otherBox.AbsParent as IPhysicsObj).CollidesLeft == true && mtdPos.X > 0) ||
+ ((otherBox.AbsParent as IPhysicsObj).CollidesRight == true && mtdPos.X < 0))
+ this.X += mtdPos.X;
+ }
+ }
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ foreach (CollisionBox box in _collisionBoxes)
+ {
+ box.Dispose();
+ }
+ _collisionBoxes.Clear();
+ _collisionBoxes = null;
+ if (PhysicsMngr != null)
+ PhysicsMngr.RemoveObject(this);
+
+ Node = null;
+ base.Dispose();
+ }
+ }
+
+ protected override GameObj CreateCloneInstance()
+ {
+ return new PhysicsObj(_spriteName);
+ }
+
+ protected override void FillCloneInstance(object obj)
+ {
+ base.FillCloneInstance(obj);
+
+ PhysicsObj clone = obj as PhysicsObj;
+ if (this.PhysicsMngr != null)
+ this.PhysicsMngr.AddObject(clone);
+
+ clone.CollidesLeft = this.CollidesLeft;
+ clone.CollidesRight = this.CollidesRight;
+ clone.CollidesBottom = this.CollidesBottom;
+ clone.CollidesTop = this.CollidesTop;
+
+ clone.IsWeighted = this.IsWeighted;
+ clone.IsCollidable = this.IsCollidable;
+ clone.DisableHitboxUpdating = this.DisableHitboxUpdating;
+ clone.CollisionTypeTag = this.CollisionTypeTag;
+ clone.DisableAllWeight = this.DisableAllWeight;
+ clone.SameTypesCollide = this.SameTypesCollide;
+
+ clone.AccelerationX = this.AccelerationX;
+ clone.AccelerationY = this.AccelerationY;
+ clone.AccelerationXEnabled = this.AccelerationXEnabled;
+ clone.AccelerationYEnabled = this.AccelerationYEnabled;
+ clone.DisableGravity = this.DisableGravity;
+ }
+
+ public override void PopulateFromXMLReader(System.Xml.XmlReader reader, System.Globalization.CultureInfo ci)
+ {
+ base.PopulateFromXMLReader(reader, ci);
+
+ if (reader.MoveToAttribute("CollidesTop"))
+ this.CollidesTop = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("CollidesBottom"))
+ this.CollidesBottom = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("CollidesLeft"))
+ this.CollidesLeft = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("CollidesRight"))
+ this.CollidesRight = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("Collidable"))
+ this.IsCollidable = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("Weighted"))
+ this.IsWeighted = bool.Parse(reader.Value);
+ }
+
+ public virtual Rectangle TerrainBounds
+ {
+ get { return m_terrainBounds; }
+ }
+
+ public void RemoveFromPhysicsManager()
+ {
+ if (this.PhysicsMngr != null)
+ this.PhysicsMngr.RemoveObject(this);
+ }
+
+ public List CollisionBoxes
+ {
+ get { return _collisionBoxes; }
+ }
+
+ public float AccelerationX
+ {
+ set { _acceleration.X = value; }
+ get { return _acceleration.X; }
+ }
+
+ public float AccelerationY
+ {
+ set { _acceleration.Y = value; }
+ get { return _acceleration.Y; }
+ }
+
+ public override Vector2 Scale
+ {
+ get { return base.Scale; }
+ set
+ {
+ base.Scale = value;
+ if (DisableHitboxUpdating == false)
+ UpdateCollisionBoxes();
+ }
+ }
+
+ public override float ScaleX
+ {
+ get { return base.ScaleX; }
+ set
+ {
+ base.ScaleX = value;
+ if (DisableHitboxUpdating == false)
+ UpdateCollisionBoxes();
+ }
+ }
+
+ public override float ScaleY
+ {
+ get { return base.ScaleY; }
+ set
+ {
+ base.ScaleY = value;
+ if (DisableHitboxUpdating == false)
+ UpdateCollisionBoxes();
+ }
+ }
+
+ public bool HasTerrainHitBox
+ {
+ get
+ {
+ foreach (CollisionBox box in _collisionBoxes)
+ {
+ if (box.Type == Consts.TERRAIN_HITBOX)
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public bool DisableCollisionBoxRotations
+ {
+ set
+ {
+ foreach (CollisionBox box in _collisionBoxes)
+ box.DisableRotation = value;
+ }
+ get
+ {
+ foreach (CollisionBox box in _collisionBoxes)
+ return box.DisableRotation;
+ return false;
+ }
+ }
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/PhysicsObjContainer.cs b/DS2DEngine/src/GameObjs/PhysicsObjContainer.cs
new file mode 100644
index 0000000..57f9ff7
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/PhysicsObjContainer.cs
@@ -0,0 +1,481 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using SpriteSystem;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace DS2DEngine
+{
+ public class PhysicsObjContainer : ObjContainer, IPhysicsObj
+ {
+ public LinkedListNode Node { get; set; }
+
+ public bool CollidesLeft { get; set; }
+ public bool CollidesRight { get; set; }
+ public bool CollidesTop { get; set; }
+ public bool CollidesBottom { get; set; }
+ public int CollisionTypeTag { get; set; }
+
+ private Vector2 _acceleration = Vector2.Zero;
+ public Boolean IsWeighted { get; set; }
+ public Boolean IsCollidable { get; set; }
+ private List _collisionBoxes;
+ public PhysicsManager PhysicsMngr { get; set; }
+
+ public bool DisableHitboxUpdating { get; set; }
+
+ public bool AccelerationXEnabled { get; set; }
+ public bool AccelerationYEnabled { get; set; }
+ public bool DisableAllWeight { get; set; }
+ public bool SameTypesCollide { get; set; }
+ public bool DisableGravity { get; set; }
+
+ protected Rectangle m_terrainBounds;
+
+ // Calls ObjContainer() because other constructors for ObjContainer create SpriteObjs. This container needs PhysicsObjs.
+ public PhysicsObjContainer(PhysicsManager physicsManager = null)
+ {
+ IsWeighted = true;
+ IsCollidable = true;
+ _collisionBoxes = new List();
+
+ if (physicsManager != null)
+ {
+ PhysicsMngr = physicsManager;
+ PhysicsMngr.AddObject(this);
+ }
+
+ CollidesLeft = true;
+ CollidesRight = true;
+ CollidesTop = true;
+ CollidesBottom = true;
+
+ AccelerationXEnabled = true;
+ AccelerationYEnabled = true;
+ }
+
+ public PhysicsObjContainer(string spriteName, PhysicsManager physicsManager = null)
+ {
+ IsWeighted = true;
+ IsCollidable = true;
+ _collisionBoxes = new List();
+ _spriteName = spriteName; // Do not remove this line.
+
+ List charDataList = SpriteLibrary.GetCharData(spriteName);
+ foreach (CharacterData chars in charDataList)
+ {
+ PhysicsObj newObj = new PhysicsObj(chars.Child);
+
+ newObj.X = chars.ChildX;
+ newObj.Y = chars.ChildY;
+ this.AddChild(newObj);
+
+ if (!m_spritesheetNameList.Contains(newObj.SpritesheetName))
+ m_spritesheetNameList.Add(newObj.SpritesheetName);
+ }
+
+ if (physicsManager != null)
+ {
+ PhysicsMngr = physicsManager;
+ PhysicsMngr.AddObject(this);
+ }
+
+ CollidesLeft = true;
+ CollidesRight = true;
+ CollidesTop = true;
+ CollidesBottom = true;
+
+ AccelerationXEnabled = true;
+ AccelerationYEnabled = true;
+
+ m_numCharData = charDataList.Count;
+ m_charDataStartIndex = 0;
+
+ UpdateCollisionBoxes();
+ }
+
+ public override void ChangeSprite(string spriteName)
+ {
+ _spriteName = spriteName;
+
+ if (m_charDataStartIndex == -1)
+ m_charDataStartIndex = _objectList.Count; // Sets the starting index of when ChangeSprite was called to the end of the object list if it wasn't originally set in the constructor.
+
+ List charDataList = SpriteLibrary.GetCharData(spriteName);
+
+ m_spritesheetNameList.Clear();
+ int indexCounter = 0;
+ for (int i = m_charDataStartIndex; i < charDataList.Count; i++)
+ {
+ CharacterData charData = charDataList[indexCounter];
+
+ if (i >= m_numCharData)
+ {
+ PhysicsObj objToAdd = new PhysicsObj(charData.Child);
+ objToAdd.X = charData.ChildX;
+ objToAdd.Y = charData.ChildY;
+ this.AddChildAt(i, objToAdd);
+ m_numCharData++;
+ }
+ else
+ {
+ PhysicsObj physicsObj = _objectList[i] as PhysicsObj;
+ physicsObj.Visible = true;
+ if (physicsObj != null)
+ {
+ physicsObj.ChangeSprite(charData.Child);
+ physicsObj.X = charData.ChildX;
+ physicsObj.Y = charData.ChildY;
+ if (!m_spritesheetNameList.Contains(physicsObj.SpritesheetName))
+ m_spritesheetNameList.Add(physicsObj.SpritesheetName);
+ }
+ }
+
+ indexCounter++;
+ }
+
+ if (charDataList.Count < m_numCharData)
+ {
+ //for (int i = m_charDataStartIndex + (m_numCharData - charDataList.Count); i < m_charDataStartIndex + m_numCharData; i++)
+ //for (int i = m_charDataStartIndex + 1; i < m_charDataStartIndex + m_numCharData; i++)
+ for (int i = charDataList.Count; i < m_numCharData; i++)
+ {
+ _objectList[i].Visible = false;
+ }
+
+ }
+
+ this.StopAnimation();
+ CalculateBounds();
+ UpdateCollisionBoxes();
+ }
+
+ public void UpdatePhysics(GameTime gameTime)
+ {
+ float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;
+ //if (elapsedSeconds > Consts.FRAMERATE_CAP) elapsedSeconds = Consts.FRAMERATE_CAP;
+ if (this.AccelerationXEnabled == true)
+ this.X += _acceleration.X *elapsedSeconds;
+
+ float yAcceleration = _acceleration.Y * elapsedSeconds;
+ if (yAcceleration < _acceleration.Y * Consts.FRAMERATE_CAP)
+ yAcceleration = _acceleration.Y * elapsedSeconds;
+ if (this.AccelerationYEnabled == true)
+ this.Y += yAcceleration;
+ }
+
+ public void UpdateCollisionBoxes()
+ {
+ m_terrainBounds = new Rectangle();
+ int leftBound = int.MaxValue;
+ int rightBound = -int.MaxValue;
+ int topBound = int.MaxValue;
+ int bottomBound = -int.MaxValue;
+
+ foreach (GameObj obj in _objectList)
+ {
+ PhysicsObj physObj = obj as PhysicsObj;
+ if (physObj != null)
+ {
+ physObj.UpdateCollisionBoxes();
+
+ if (physObj.TerrainBounds.Left < leftBound)
+ leftBound = physObj.TerrainBounds.Left;
+
+ if (physObj.TerrainBounds.Right > rightBound)
+ rightBound = physObj.TerrainBounds.Right;
+
+ if (physObj.TerrainBounds.Top < topBound)
+ topBound = physObj.TerrainBounds.Top;
+
+ if (physObj.TerrainBounds.Bottom > bottomBound)
+ bottomBound = physObj.TerrainBounds.Bottom;
+ }
+ }
+
+ m_terrainBounds.X = leftBound;
+ m_terrainBounds.Y = topBound;
+ m_terrainBounds.Width = rightBound - leftBound;
+ m_terrainBounds.Height = bottomBound - topBound;
+ }
+
+ public virtual void CollisionResponse(CollisionBox thisBox, CollisionBox otherBox, int collisionResponseType)
+ {
+ if (collisionResponseType == Consts.COLLISIONRESPONSE_TERRAIN && IsWeighted == true)
+ {
+ Vector2 mtdPos = CollisionMath.RotatedRectIntersectsMTD(thisBox.AbsRect, thisBox.AbsRotation, Vector2.Zero, otherBox.AbsRect, otherBox.AbsRotation, Vector2.Zero);
+ IPhysicsObj otherPhysicsObj = otherBox.AbsParent as IPhysicsObj;
+ int collisionMarginOfError = 10;
+ if (mtdPos.Y < 0) // There is an object below this object.
+ {
+ // This used to be this.AccelerationY > 0. Was changed for player dashing. If it causes problems, revert it back.
+ if (this.AccelerationY >= 0 && otherPhysicsObj.CollidesTop == true && IsWeighted == true) // This object is falling and there is an object below, so stop this object from falling.
+ {
+ // This code is for dropping through blocks.
+ if (otherBox.AbsRotation != 0 ||
+
+ (otherPhysicsObj.TerrainBounds.Left <= this.TerrainBounds.Right && otherPhysicsObj.TerrainBounds.Right >= this.TerrainBounds.Right && (this.TerrainBounds.Right - otherPhysicsObj.TerrainBounds.Left > collisionMarginOfError)) ||
+ (this.TerrainBounds.Left <= otherPhysicsObj.TerrainBounds.Right && this.TerrainBounds.Right >= otherPhysicsObj.TerrainBounds.Right && (otherPhysicsObj.TerrainBounds.Right - this.TerrainBounds.Left > collisionMarginOfError)))
+ {
+ this.AccelerationY = 0;
+ this.Y += mtdPos.Y;
+ }
+ }
+ }
+ else if (mtdPos.Y > 0) // There is an object above this object.
+ {
+ if (this.AccelerationY < 0 && otherPhysicsObj.CollidesBottom == true && IsWeighted == true) // This object is going up and has hit an object above it, so stop this object from going up.
+ {
+ this.AccelerationY = 0;
+ this.Y += mtdPos.Y;
+ }
+ }
+
+ if (mtdPos.X != 0)
+ {
+ if ((otherPhysicsObj.CollidesLeft == true && mtdPos.X > 0) ||
+ (otherPhysicsObj.CollidesRight == true && mtdPos.X < 0))
+ {
+ this.AccelerationX = 0;
+ this.X += mtdPos.X;
+ }
+ }
+
+ }
+ }
+
+ public override void AddChild(GameObj obj)
+ {
+ PhysicsObj physicsObj = obj as PhysicsObj;
+ if (physicsObj != null)
+ {
+ foreach (CollisionBox box in physicsObj.CollisionBoxes)
+ {
+ if (!_collisionBoxes.Contains(box))
+ _collisionBoxes.Add(box);
+ }
+ }
+ base.AddChild(obj);
+ }
+
+ public override void AddChildAt(int index, GameObj obj)
+ {
+ PhysicsObj physicsObj = obj as PhysicsObj;
+ if (physicsObj != null)
+ {
+ foreach (CollisionBox box in physicsObj.CollisionBoxes)
+ {
+ if (!_collisionBoxes.Contains(box))
+ _collisionBoxes.Add(box);
+ }
+ }
+ base.AddChildAt(index, obj);
+ }
+
+ public override void RemoveChild(GameObj obj)
+ {
+ PhysicsObj physicsObj = obj as PhysicsObj;
+ if (physicsObj != null)
+ {
+ foreach (CollisionBox box in physicsObj.CollisionBoxes)
+ {
+ if (!_collisionBoxes.Contains(box))
+ _collisionBoxes.Remove(box);
+ }
+ }
+ base.RemoveChild(obj);
+ }
+
+ public override GameObj RemoveChildAt(int index)
+ {
+ PhysicsObj obj = null;
+
+ if (_objectList[index] is PhysicsObj)
+ obj = _objectList[index] as PhysicsObj;
+
+ if (obj != null)
+ {
+ foreach (CollisionBox box in obj.CollisionBoxes)
+ {
+ _collisionBoxes.Remove(box);
+ }
+ }
+ return base.RemoveChildAt(index);
+ }
+
+ public override void RemoveAll()
+ {
+ _collisionBoxes.Clear();
+ base.RemoveAll();
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ if (PhysicsMngr != null)
+ PhysicsMngr.RemoveObject(this);
+
+ foreach (CollisionBox box in _collisionBoxes)
+ {
+ box.Dispose();
+ }
+ _collisionBoxes.Clear();
+ Node = null;
+ base.Dispose();
+ }
+ }
+
+ public void RemoveFromPhysicsManager()
+ {
+ if (this.PhysicsMngr != null)
+ this.PhysicsMngr.RemoveObject(this);
+ }
+
+
+ protected override GameObj CreateCloneInstance()
+ {
+
+ if (_spriteName != "")
+ return new PhysicsObjContainer(_spriteName);
+ else
+ {
+ PhysicsObjContainer clone = new PhysicsObjContainer();
+
+ foreach (GameObj obj in _objectList)
+ clone.AddChild(obj.Clone() as GameObj);
+ return clone;
+ }
+ }
+
+ protected override void FillCloneInstance(object obj)
+ {
+ base.FillCloneInstance(obj);
+ PhysicsObjContainer clone = obj as PhysicsObjContainer;
+
+ if (this.PhysicsMngr != null)
+ this.PhysicsMngr.AddObject(clone);
+
+ clone.CollidesLeft = this.CollidesLeft;
+ clone.CollidesRight = this.CollidesRight;
+ clone.CollidesBottom = this.CollidesBottom;
+ clone.CollidesTop = this.CollidesTop;
+
+ clone.IsWeighted = this.IsWeighted;
+ clone.IsCollidable = this.IsCollidable;
+ clone.DisableHitboxUpdating = this.DisableHitboxUpdating;
+ clone.CollisionTypeTag = this.CollisionTypeTag;
+ clone.DisableAllWeight = this.DisableAllWeight;
+ clone.SameTypesCollide = this.SameTypesCollide;
+
+ clone.AccelerationX = this.AccelerationX;
+ clone.AccelerationY = this.AccelerationY;
+ clone.AccelerationXEnabled = this.AccelerationXEnabled;
+ clone.AccelerationYEnabled = this.AccelerationYEnabled;
+ clone.DisableGravity = this.DisableGravity;
+ }
+
+ public override void PopulateFromXMLReader(System.Xml.XmlReader reader, System.Globalization.CultureInfo ci)
+ {
+ base.PopulateFromXMLReader(reader, ci);
+
+ if (reader.MoveToAttribute("CollidesTop"))
+ this.CollidesTop = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("CollidesBottom"))
+ this.CollidesBottom = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("CollidesLeft"))
+ this.CollidesLeft = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("CollidesRight"))
+ this.CollidesRight = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("Collidable"))
+ this.IsCollidable = bool.Parse(reader.Value);
+ if (reader.MoveToAttribute("Weighted"))
+ this.IsWeighted = bool.Parse(reader.Value);
+ }
+
+ public List CollisionBoxes
+ {
+ get { return _collisionBoxes; }
+ }
+
+ public float AccelerationX
+ {
+ set { _acceleration.X = value; }
+ get { return _acceleration.X; }
+ }
+
+ public float AccelerationY
+ {
+ set { _acceleration.Y = value; }
+ get { return _acceleration.Y; }
+ }
+
+ public virtual Rectangle TerrainBounds
+ {
+ get { return m_terrainBounds; }
+ }
+
+ public override Vector2 Scale
+ {
+ get { return base.Scale; }
+ set
+ {
+ base.Scale = value;
+ if (DisableHitboxUpdating == false)
+ UpdateCollisionBoxes();
+ }
+ }
+
+ public override float ScaleX
+ {
+ get { return base.ScaleX; }
+ set
+ {
+ base.ScaleX = value;
+ if (DisableHitboxUpdating == false)
+ UpdateCollisionBoxes();
+ }
+ }
+
+ public override float ScaleY
+ {
+ get { return base.ScaleY; }
+ set
+ {
+ base.ScaleY = value;
+ if (DisableHitboxUpdating == false)
+ UpdateCollisionBoxes();
+ }
+ }
+
+ public bool HasTerrainHitBox
+ {
+ get
+ {
+ foreach (CollisionBox box in _collisionBoxes)
+ {
+ if (box.Type == Consts.TERRAIN_HITBOX)
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public bool DisableCollisionBoxRotations
+ {
+ set
+ {
+ foreach (CollisionBox box in _collisionBoxes)
+ box.DisableRotation = value;
+ }
+ get
+ {
+ foreach (CollisionBox box in _collisionBoxes)
+ return box.DisableRotation;
+ return false;
+ }
+ }
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/SpriteObj.cs b/DS2DEngine/src/GameObjs/SpriteObj.cs
new file mode 100644
index 0000000..2899fb0
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/SpriteObj.cs
@@ -0,0 +1,604 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using SpriteSystem;
+
+namespace DS2DEngine
+{
+ public class SpriteObj : GameObj, IAnimateableObj
+ {
+ protected Texture2D _sprite = null;
+ protected string _spriteName = "";
+ protected string m_spritesheetName;
+ protected Rectangle _ssRect;
+ protected int _frameDelayCounter;
+ protected int _frameDuration;
+ protected int _frameIndex; // The index of the animation if it did not take delay on frames into account.
+ protected int _frameCount;
+ protected int _currentFrameIndex = 1; // The index the animation is at if it took delays on frames into account.
+ private bool m_atEndFrame = false;
+ private float m_totalGameTime = 0;
+
+ protected int _startAnimationIndex;
+ protected int _endAnimationIndex;
+
+ protected string m_startLabel;
+ protected string m_endLabel;
+
+ protected List m_frameDataList;
+ protected List m_imageDataList;
+ protected bool _animate = false;
+ protected bool _loop = true;
+ private bool m_wasAnimating = false;
+
+ private int m_hackFrameRate = 1;
+
+ private float m_timeDelay = 0;
+ private float m_timeDelayCounter = 0;
+
+ public bool OverrideParentAnimationDelay = false;
+
+ public Vector2 DropShadow { get; set; }
+ private Color m_outlineColour = Color.Black;
+ private int m_outlineWidth = 0;
+
+ #region Constructors
+ public SpriteObj(string spriteName)
+ {
+ _spriteName = spriteName;
+
+ //Fetching sprite data from Sprite Library.
+ m_imageDataList = SpriteLibrary.GetImageDataList(spriteName);
+ ImageData imageData = m_imageDataList[0];
+ _sprite = SpriteLibrary.GetSprite(spriteName);
+ _ssRect = new Rectangle((int)(imageData.SSPos.X), (int)(imageData.SSPos.Y), imageData.Width, imageData.Height);
+ AnchorX = imageData.Anchor.X;
+ AnchorY = imageData.Anchor.Y;
+
+ m_spritesheetName = SpriteLibrary.GetSpritesheetName(spriteName);
+
+ //Fetching animation data from Sprite Library.
+ m_frameDataList = SpriteLibrary.GetFrameDataList(spriteName);
+ _frameCount = m_frameDataList.Count;
+ if (_frameCount > 1)
+ {
+ _frameIndex = 0;
+ FrameData fd = m_frameDataList[_frameIndex];
+ _frameDuration = fd.Duration * AnimationSpeed;
+ _frameDelayCounter = 1;
+ }
+
+ TextureColor = Color.White;
+ Opacity = 1;
+
+ if (SpriteLibrary.ContainsCharacter(_spriteName))
+ throw new Exception("Error: Trying to create a SpriteObj or PhysicsObj using Character Name: '" + _spriteName + "'. Character names are used for obj containers. Try using a sprite name instead.");
+ }
+
+ public SpriteObj(Texture2D sprite)
+ {
+ _sprite = sprite;
+ _ssRect = new Rectangle(0,0, _sprite.Width, _sprite.Height);
+ _frameIndex = 0;
+ _frameDuration = 1;
+ _frameDelayCounter = 1;
+ _frameCount = 0;
+
+ TextureColor = Color.White;
+ Opacity = 1;
+ }
+ #endregion
+
+ public override void ChangeSprite(string spriteName)
+ {
+ // Clear out old Sprite information.
+ //_sprite = null;
+ //m_frameDataList = null;
+ // m_imageDataList = null;
+
+ _spriteName = spriteName;
+
+ //Fetching sprite data from Sprite Library.
+ m_imageDataList = SpriteLibrary.GetImageDataList(spriteName);
+ ImageData imageData = m_imageDataList[0];
+ _sprite = SpriteLibrary.GetSprite(spriteName);
+ _ssRect = new Rectangle((int)(imageData.SSPos.X), (int)(imageData.SSPos.Y), imageData.Width, imageData.Height);
+ AnchorX = imageData.Anchor.X;
+ AnchorY = imageData.Anchor.Y;
+
+ //Fetching animation data from Sprite Library.
+ m_frameDataList = SpriteLibrary.GetFrameDataList(spriteName);
+ _frameCount = m_frameDataList.Count;
+ _frameIndex = 0;
+ if (_frameCount > 1)
+ {
+ //_frameIndex = 0;
+ FrameData fd = m_frameDataList[_frameIndex];
+ _frameDuration = fd.Duration * AnimationSpeed;
+ _frameDelayCounter = 1;
+ }
+
+ this.StopAnimation();
+ //PlayAnimation(1, 1); // What is this for and why was it not commented?
+ }
+
+ public override void Draw(Camera2D camera)
+ {
+ if (_sprite.IsDisposed)
+ ReinitializeSprite();
+
+ if (_sprite != null && this.Visible == true)
+ {
+ if (OutlineWidth > 0 && (Parent == null || Parent.OutlineWidth == 0))
+ DrawOutline(camera);
+
+ if (DropShadow != Vector2.Zero)
+ DrawDropShadow(camera);
+
+ if (Parent == null || OverrideParentScale == true)
+ {
+ if (CollisionMath.Intersects(this.Bounds, camera.LogicBounds) || this.ForceDraw == true)
+ camera.Draw(Sprite, AbsPosition, _ssRect, this.TextureColor * Opacity, MathHelper.ToRadians(Rotation), Anchor, Scale, Flip, 1);
+ }
+ else // Don't do a collision intersect test with the camera bounds here because the parent does it.
+ camera.Draw(Sprite, AbsPosition, _ssRect, this.TextureColor * Opacity, MathHelper.ToRadians(Parent.Rotation + this.Rotation), Anchor, (Parent.Scale * this.Scale), Flip, Layer);
+
+ float elapsedTotalSeconds = camera.ElapsedTotalSeconds;
+ if (_animate == true && m_totalGameTime != elapsedTotalSeconds)
+ {
+ m_totalGameTime = elapsedTotalSeconds; // Used to make sure if you call draw more than once, it doesn't keep animating.
+
+ if (camera.GameTime != null)
+ m_timeDelayCounter += elapsedTotalSeconds;// (float)camera.GameTime.ElapsedGameTime.TotalSeconds;
+
+ if (_frameCount > 1 && m_timeDelayCounter >= AnimationDelay)
+ {
+ m_timeDelayCounter = 0;
+ if (_currentFrameIndex < _endAnimationIndex || IsLooping == true)
+ GoToNextFrame();
+ else
+ {
+ if (m_atEndFrame == false)
+ m_atEndFrame = true;
+ else
+ {
+ m_atEndFrame = false;
+ _animate = false;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //public override void DrawOutline(Camera2D camera, int width)
+ public override void DrawOutline(Camera2D camera)
+ {
+ if (_sprite.IsDisposed)
+ ReinitializeSprite();
+
+ int width = OutlineWidth;
+ if (_sprite != null && this.Visible == true)
+ {
+ if (this.Opacity == 1) // Don't do a collision intersect test with the camera bounds here because the parent does it.
+ {
+ // Optimization - cache frequently referenced values
+ Vector2 absPos = AbsPosition;
+ float posX = absPos.X;
+ float posY = absPos.Y;
+ SpriteEffects flip = Flip;
+ float radianRot = MathHelper.ToRadians(this.Rotation);
+ Color outlineColor = OutlineColour * Opacity;
+ Vector2 anchor = Anchor;
+ float layer = Layer;
+ Texture2D sprite = Sprite;
+ Vector2 scale = this.Scale;
+
+ if (Parent == null || OverrideParentScale == true)
+ {
+ // Cardinal directions.
+ camera.Draw(sprite, new Vector2(posX - width, posY), _ssRect, outlineColor, radianRot, anchor, scale, flip, layer);
+ camera.Draw(sprite, new Vector2(posX + width, posY), _ssRect, outlineColor, radianRot, anchor, scale, flip, layer);
+ camera.Draw(sprite, new Vector2(posX, posY - width), _ssRect, outlineColor, radianRot, anchor, scale, flip, layer);
+ camera.Draw(sprite, new Vector2(posX, posY + width), _ssRect, outlineColor, radianRot, anchor, scale, flip, layer);
+ // The corners.
+ camera.Draw(sprite, new Vector2(posX - width, posY - width), _ssRect, outlineColor, radianRot, anchor, scale, flip, layer);
+ camera.Draw(sprite, new Vector2(posX + width, posY + width), _ssRect, outlineColor, radianRot, anchor, scale, flip, layer);
+ camera.Draw(sprite, new Vector2(posX + width, posY - width), _ssRect, outlineColor, radianRot, anchor, scale, flip, layer);
+ camera.Draw(sprite, new Vector2(posX - width, posY + width), _ssRect, outlineColor, radianRot, anchor, scale, flip, layer);
+ }
+ else
+ {
+ Vector2 parentScale = Parent.Scale * scale;
+ radianRot = MathHelper.ToRadians(Parent.Rotation + this.Rotation);
+
+ // Cardinal directions.
+ camera.Draw(sprite, new Vector2(posX - width, posY), _ssRect, outlineColor, radianRot, anchor, parentScale, flip, layer);
+ camera.Draw(sprite, new Vector2(posX + width, posY), _ssRect, outlineColor, radianRot, anchor, parentScale, flip, layer);
+ camera.Draw(sprite, new Vector2(posX, posY - width), _ssRect, outlineColor, radianRot, anchor, parentScale, flip, layer);
+ camera.Draw(sprite, new Vector2(posX, posY + width), _ssRect, outlineColor, radianRot, anchor, parentScale, flip, layer);
+ // The corners.
+ camera.Draw(sprite, new Vector2(posX - width, posY - width), _ssRect, outlineColor, radianRot, anchor, parentScale, flip, layer);
+ camera.Draw(sprite, new Vector2(posX + width, posY + width), _ssRect, outlineColor, radianRot, anchor, parentScale, flip, layer);
+ camera.Draw(sprite, new Vector2(posX + width, posY - width), _ssRect, outlineColor, radianRot, anchor, parentScale, flip, layer);
+ camera.Draw(sprite, new Vector2(posX - width, posY + width), _ssRect, outlineColor, radianRot, anchor, parentScale, flip, layer);
+ }
+ }
+ }
+
+ //base.DrawOutline(camera);
+ }
+
+
+ public void DrawDropShadow(Camera2D camera)
+ {
+ if (this.Visible == true)
+ {
+ if (Parent == null || OverrideParentScale == true)
+ camera.Draw(Sprite, this.AbsPosition + DropShadow, _ssRect, Color.Black * this.Opacity, MathHelper.ToRadians(this.Rotation), Anchor, (this.Scale), Flip, Layer);
+ else
+ camera.Draw(Sprite, this.AbsPosition + DropShadow, _ssRect, Color.Black * this.Opacity, MathHelper.ToRadians(Parent.Rotation + this.Rotation), Anchor, (Parent.Scale * this.Scale), Flip, Layer);
+ }
+ }
+
+
+ public void PlayAnimation(bool loopAnimation = true)
+ {
+ _animate = true;
+ _loop = loopAnimation;
+
+ _startAnimationIndex = 1;
+ _endAnimationIndex = TotalFrames;
+ m_timeDelayCounter = 0;
+ GoToFrame(_startAnimationIndex);
+ //_currentFrameIndex = 1;
+ }
+
+ public void PlayAnimation(int startIndex, int endIndex, bool loopAnimation = false)
+ {
+ startIndex = (startIndex - 1) * AnimationSpeed + 1;
+ endIndex = endIndex * AnimationSpeed;
+
+ if (startIndex < 1)
+ startIndex = 1;
+ else if (startIndex > TotalFrames)
+ startIndex = TotalFrames;
+
+ if (endIndex < 1)
+ endIndex = 1;
+ else if (endIndex > TotalFrames)
+ endIndex = TotalFrames;
+
+ _animate = true;
+ _loop = loopAnimation;
+
+ _startAnimationIndex = startIndex;
+ _endAnimationIndex = endIndex;
+ m_timeDelayCounter = 0;
+ GoToFrame(startIndex);
+ }
+
+ public void PlayAnimation(string startLabel, string endLabel, bool loopAnimation = false)
+ {
+ int startIndex = FindLabelIndex(startLabel);
+ int endIndex = FindLabelIndex(endLabel);
+
+ if (startIndex == -1)
+ throw new Exception("Could not find starting label " + startLabel);
+ else if (endIndex == -1)
+ throw new Exception("Could not find ending label " + endLabel);
+ else
+ PlayAnimation(startIndex, endIndex, loopAnimation);
+ }
+
+ public int FindLabelIndex(string label)
+ {
+ if (Sprite.IsDisposed)
+ this.ReinitializeSprite();
+
+ for (int i = 0; i < m_frameDataList.Count; i++)
+ {
+ if (m_frameDataList[i].Label == label)
+ return m_frameDataList[i].Frame;
+ }
+ return -1;
+ }
+
+ public void ResumeAnimation()
+ {
+ if (m_wasAnimating == true)
+ _animate = true;
+ m_wasAnimating = false;
+ }
+
+ public void PauseAnimation()
+ {
+ m_wasAnimating = this.IsAnimating;
+ _animate = false;
+ }
+
+ public void StopAnimation()
+ {
+ _animate = false;
+ m_wasAnimating = false;
+ }
+
+ public void GoToNextFrame()
+ {
+ if (_frameCount > 1) // Only animate if there is more than one frame in the animation.
+ {
+ if (_loop == true || _currentFrameIndex < _endAnimationIndex) // Only animate if it hasn't reached the end of the animation or loop is equal to true.
+ {
+ if (_frameDelayCounter < _frameDuration)
+ {
+ _frameDelayCounter++;
+ _currentFrameIndex++;
+ }
+ else
+ {
+ _frameDelayCounter = 1;
+ _frameIndex++;
+ _currentFrameIndex++;
+
+ //if (_currentFrameIndex >= _endAnimationIndex) // This was causing problems with PlayAnimation(string startLabel, string endLabel) so it was changed to > only.
+ if (_currentFrameIndex > _endAnimationIndex)
+ {
+ // If current frame goes passed endframe, go back to the starting frame.
+ GoToFrame(_startAnimationIndex);
+ }
+
+ if (Sprite.IsDisposed)
+ this.ReinitializeSprite();
+
+ FrameData fd = m_frameDataList[_frameIndex];
+ _frameDuration = fd.Duration * AnimationSpeed; // Extends the duration of each frame by 2, turning this into 30 fps animations.
+ ImageData id = m_imageDataList[_frameIndex];
+ _ssRect.X = (int)id.SSPos.X;
+ _ssRect.Y = (int)id.SSPos.Y;
+ _ssRect.Width = id.Width;
+ _ssRect.Height = id.Height;
+ AnchorX = id.Anchor.X;
+ AnchorY = id.Anchor.Y;
+ }
+ }
+ else if (IsAnimating == true)
+ {
+ _animate = false;
+ }
+ }
+ }
+
+ public void GoToFrame(int frameIndex)
+ {
+ if (Sprite.IsDisposed)
+ this.ReinitializeSprite();
+
+ int frameStart = 0;
+ int totalFrames = 0;
+ foreach (FrameData fd in m_frameDataList)
+ {
+ totalFrames += fd.Duration * AnimationSpeed; // Remove this 2 to change fps. Hack for now. DO NOT FORGET ABOUT THIS.
+ if (totalFrames >= frameIndex)
+ {
+ frameStart = fd.Index;
+ break;
+ }
+ }
+
+ //_frameDelayCounter = totalFrames - frameIndex;
+
+ _frameIndex = frameStart;
+ _currentFrameIndex = frameIndex;
+
+ FrameData fd2 = m_frameDataList[_frameIndex];
+ _frameDuration = fd2.Duration * AnimationSpeed; // Extends the duration of each frame by 2, turning this into 30 fps animations.
+
+ _frameDelayCounter = _frameDuration - (totalFrames - frameIndex);
+
+ ImageData id = m_imageDataList[_frameIndex];
+ _ssRect.X = (int)id.SSPos.X;
+ _ssRect.Y = (int)id.SSPos.Y;
+ _ssRect.Width = id.Width;
+ _ssRect.Height = id.Height;
+ AnchorX = id.Anchor.X;
+ AnchorY = id.Anchor.Y;
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ if (m_spritesheetName == null)
+ {
+ if (_sprite.IsDisposed == false)
+ _sprite.Dispose(); // Only dispose sprites that don't have a spritesheet name. Because that means they are manual Texture2Ds passed into the constructor.
+ }
+ _sprite = null;
+ m_frameDataList = null; // Do not clear out the lists either since they're also linked to the sprite library.
+ m_imageDataList = null;
+
+ base.Dispose();
+ }
+ }
+
+ protected override GameObj CreateCloneInstance()
+ {
+ return new SpriteObj(_spriteName);
+ }
+
+ protected override void FillCloneInstance(object obj)
+ {
+ base.FillCloneInstance(obj);
+
+ SpriteObj clone = obj as SpriteObj;
+ clone.OutlineColour = this.OutlineColour;
+ clone.OutlineWidth = this.OutlineWidth;
+ clone.DropShadow = this.DropShadow;
+ clone.AnimationDelay = this.AnimationDelay;
+ }
+
+ public RenderTarget2D ConvertToTexture(Camera2D camera, bool resizeToPowerOf2 = true, SamplerState samplerState = null)
+ {
+ SpriteEffects storedFlip = this.Flip;
+ this.Flip = SpriteEffects.None;
+
+ int nextPowerOf2Width = this.Width;
+ int nextPowerOf2Height = this.Height;
+ Vector2 newScale = new Vector2(1, 1);
+
+ if (resizeToPowerOf2 == true)
+ {
+ nextPowerOf2Width = CDGMath.NextPowerOf2(Width);
+ nextPowerOf2Height = CDGMath.NextPowerOf2(Height);
+ newScale = new Vector2(nextPowerOf2Width / (float)Width, nextPowerOf2Height / (float)Height);
+ }
+
+ float storedRotation = this.Rotation;
+ this.Rotation = 0;
+
+ if (Sprite.IsDisposed)
+ this.ReinitializeSprite();
+
+ RenderTarget2D texture = new RenderTarget2D(camera.GraphicsDevice, nextPowerOf2Width, nextPowerOf2Height);
+ camera.GraphicsDevice.SetRenderTarget(texture);
+ camera.GraphicsDevice.Clear(Color.Transparent);
+ camera.Begin(SpriteSortMode.Immediate, null, samplerState, null, null);
+ camera.Draw(this.Sprite, this.Anchor, this.SpriteRect, this.TextureColor, this.Rotation, Vector2.Zero, this.Scale * newScale, this.Flip, this.Layer);
+ camera.End();
+ camera.GraphicsDevice.SetRenderTarget(null);
+
+ this.Rotation = storedRotation;
+
+ this.Flip = storedFlip;
+ return texture;
+ }
+
+ public void ReinitializeSprite()
+ {
+ if (SpriteName != "")
+ {
+ m_imageDataList = SpriteLibrary.GetImageDataList(SpriteName);
+ _sprite = SpriteLibrary.GetSprite(SpriteName);
+ m_frameDataList = SpriteLibrary.GetFrameDataList(SpriteName);
+ }
+ }
+
+ public Texture2D Sprite
+ { get { return _sprite; } }
+
+ public override int Width
+ {
+ get { return (int)(_ssRect.Width * ScaleX); }
+ }
+
+ public override int Height
+ {
+ get { return (int)(_ssRect.Height * ScaleY); }
+ }
+
+ public int CurrentFrame
+ {
+ get { return (int)(_currentFrameIndex / AnimationSpeed); }
+ }
+
+ public bool IsAnimating
+ {
+ get { return _animate; }
+ }
+
+ public bool IsLooping
+ {
+ get { return _loop; }
+ }
+
+ public int TotalFrames
+ {
+ get
+ {
+ if (Sprite.IsDisposed)
+ this.ReinitializeSprite();
+
+ int totalFrames = 0;
+ foreach (FrameData fd in m_frameDataList)
+ {
+ totalFrames += fd.Duration * AnimationSpeed; // Remove this 2 to change fps. Hack for now. DO NOT FORGET ABOUT THIS.
+ }
+ return totalFrames;
+ }
+ }
+
+ public string SpriteName
+ {
+ get { return _spriteName; }
+ set { _spriteName = value; }
+ }
+
+ public int AnimationSpeed
+ {
+ get
+ {
+ if (Parent == null)
+ return m_hackFrameRate;
+ else
+ return Parent.AnimationSpeed;
+ }
+ set { m_hackFrameRate = value; }
+ }
+
+ public float AnimationDelay
+ {
+ get
+ {
+ if (Parent == null || OverrideParentAnimationDelay == true)
+ return m_timeDelay;
+ else
+ return Parent.AnimationDelay;
+ }
+ set { m_timeDelay = value; }
+ }
+
+
+ public string SpritesheetName
+ {
+ get { return m_spritesheetName; }
+ }
+
+ public Rectangle SpriteRect
+ {
+ get { return _ssRect; }
+ set { _ssRect = value; }
+ }
+
+ public Color OutlineColour
+ {
+ get
+ {
+ if (Parent == null || (Parent != null && Parent.OutlineColour == Color.Black))
+ return m_outlineColour;
+ else
+ return Parent.OutlineColour;
+ }
+ set { m_outlineColour = value; }
+ }
+
+ public int OutlineWidth
+ {
+ get
+ {
+ if (Parent == null)
+ return m_outlineWidth;
+ else
+ return Parent.OutlineWidth;
+ }
+ set { m_outlineWidth = value; }
+ }
+
+ public bool IsPaused
+ {
+ get { return IsAnimating == false && m_wasAnimating == true; }
+ }
+ }
+}
diff --git a/DS2DEngine/src/GameObjs/TextObj.cs b/DS2DEngine/src/GameObjs/TextObj.cs
new file mode 100644
index 0000000..ec610be
--- /dev/null
+++ b/DS2DEngine/src/GameObjs/TextObj.cs
@@ -0,0 +1,436 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework;
+using System.Text.RegularExpressions;
+using System.Reflection;
+
+namespace DS2DEngine
+{
+ public class TextObj : GameObj
+ {
+#region Fields
+
+ private SpriteFont m_defaultFont = null; // default font for this TextObj
+ private SpriteFont m_font = null; // current font used for drawing
+ private string m_text = "";
+ private Vector2 m_textSize = Vector2.Zero;
+ public Types.TextAlign Align = Types.TextAlign.None;
+ private float m_fontSize = 1;
+ protected Vector2 m_internalFontSizeScale = new Vector2(1, 1);
+ public Vector2 DropShadow { get; set; }
+ public int OutlineWidth { get; set; }
+
+ private string m_typewriteText = "";
+ private bool m_isTypewriting = false;
+ private float m_typewriteSpeed;
+ private float m_typewriteCounter;
+ private int m_typewriteCharLength;
+
+ private string m_tapSFX = "";
+
+ private bool m_isPaused = false;
+
+ private Color m_outlineColour = Color.Black;
+ private int m_fontIndex = -1;
+ private Texture2D m_textureValue;
+
+ public bool LimitCorners = false;
+
+#endregion
+
+#region Properties
+
+ // language stuff
+ public string locStringID { get; set; }
+
+ public SpriteFont defaultFont
+ { get { return m_defaultFont; } }
+
+ public bool isLogographic { get; set; }
+
+ public virtual string Text
+ {
+ get { return m_text; }
+ set
+ {
+ m_text = value;
+ if (Font != null)
+ m_textSize = Font.MeasureString(m_text);
+ }
+ }
+
+ public SpriteFont Font
+ {
+ get { return m_font; }
+ set
+ {
+ if (value != null)
+ {
+ m_fontIndex = SpriteFontArray.SpriteFontList.IndexOf(value);
+ if (m_fontIndex == -1) throw new Exception("Cannot find font in SpriteFontArray");
+ FieldInfo fieldInfo = value.GetType().GetField("textureValue", BindingFlags.NonPublic | BindingFlags.Instance);
+ m_textureValue = (Texture2D)fieldInfo.GetValue(value);
+ }
+ m_defaultFont = value;
+ m_font = value;
+ }
+ }
+
+ public override Vector2 Anchor
+ {
+ get
+ {
+ if (Align != Types.TextAlign.None)
+ {
+ float scaleX = 1 / (this.ScaleX * m_internalFontSizeScale.X);
+ //if (Parent != null) // Not sure why this code breaks things.
+ // scaleX = 1/(this.ScaleX * Parent.ScaleX * m_internalFontSizeScale.X);
+
+ Vector2 anchor;
+ if (Align == Types.TextAlign.Left)
+ anchor = new Vector2(0, _anchor.Y);
+ else if (Align == Types.TextAlign.Centre)
+ anchor = new Vector2(this.Width / 2 * scaleX, _anchor.Y);
+ else
+ anchor = new Vector2(this.Width * scaleX, _anchor.Y);
+
+ if (Flip == SpriteEffects.FlipHorizontally)
+ anchor.X = Width * scaleX - anchor.X;
+ return anchor;
+ }
+ else
+ return base.Anchor;
+ }
+ set { base.Anchor = value; }
+ }
+
+ public override float AnchorX
+ {
+ get { return Anchor.X; }
+ set { base.AnchorX = value; }
+ }
+
+ public override float AnchorY
+ {
+ get { return _anchor.Y; }
+ set { _anchor.Y = value; }
+ }
+
+ public override int Width
+ {
+ get { return (int)(m_textSize.X * ScaleX * m_internalFontSizeScale.X); }
+ }
+
+ public override int Height
+ {
+ get { return (int)(m_textSize.Y * ScaleY * m_internalFontSizeScale.Y); }
+ }
+
+ public virtual float FontSize
+ {
+ get { return m_fontSize; }
+ set
+ {
+ Vector2 minSize = Font.MeasureString("0");
+ float newScale = value / minSize.X;
+ //Scale = new Vector2(newScale, newScale);
+ m_internalFontSizeScale = new Vector2(newScale, newScale);
+ m_fontSize = value;
+ }
+ }
+
+ public bool IsTypewriting
+ {
+ get { return m_isTypewriting; }
+ }
+
+ public bool IsPaused
+ {
+ get { return m_isPaused; }
+ }
+
+ public Color OutlineColour
+ {
+ get
+ {
+ if (Parent == null || (Parent != null && Parent.OutlineColour == Color.Black))
+ return m_outlineColour;
+ else
+ return Parent.OutlineColour;
+ }
+ set { m_outlineColour = value; }
+ }
+
+#endregion
+
+#region Methods
+
+ public TextObj(SpriteFont font = null)
+ {
+ m_defaultFont = font;
+ m_font = font;
+ isLogographic = false;
+ if (font != null)
+ {
+ m_fontIndex = SpriteFontArray.SpriteFontList.IndexOf(font);
+ if (m_fontIndex == -1) throw new Exception("Cannot find font in SpriteFontArray");
+ FieldInfo fieldInfo = font.GetType().GetField("textureValue", BindingFlags.NonPublic | BindingFlags.Instance);
+ m_textureValue = (Texture2D)fieldInfo.GetValue(font);
+ }
+ }
+
+ // This method differs from Font set as it doesn't override default font.
+ // It is used specifically for changing languages.
+ public void ChangeFontNoDefault(SpriteFont font)
+ {
+ m_font = font;
+ this.FontSize = m_fontSize; // update font sizing
+
+ // Don't recalculate font and text sizes until next Text property set
+ }
+
+ public void BeginTypeWriting(float duration, string sound = "")
+ {
+ m_isTypewriting = true;
+ m_typewriteText = this.Text;
+ m_typewriteSpeed = duration / this.Text.Length;
+ m_typewriteCounter = m_typewriteSpeed;
+ m_typewriteCharLength = 0;
+ this.Text = "";
+
+ m_tapSFX = sound;
+ }
+
+ public void PauseTypewriting()
+ {
+ m_isPaused = true;
+ }
+
+ public void ResumeTypewriting()
+ {
+ m_isPaused = false;
+ }
+
+ public void StopTypeWriting(bool completeText)
+ {
+ m_isTypewriting = false;
+ if (completeText == true)
+ this.Text = m_typewriteText;
+ }
+
+ //public override void DrawOutline(Camera2D camera, int width)
+ public override void DrawOutline(Camera2D camera)
+ {
+ if (m_textureValue.IsDisposed)
+ m_font = SpriteFontArray.SpriteFontList[m_fontIndex];
+
+ int width = OutlineWidth;
+ if (m_font != null && this.Visible == true)
+ {
+ // Optimization - cache frequently referenced values
+ Vector2 absPos = AbsPosition;
+ float posX = absPos.X;
+ float posY = absPos.Y;
+ SpriteEffects flip = Flip;
+ float radianRot = MathHelper.ToRadians(this.Rotation);
+ Color outlineColour = OutlineColour * Opacity;
+ Vector2 anchor = Anchor;
+ float layer = Layer;
+ Vector2 scale = this.Scale * m_internalFontSizeScale;
+
+ //if (this.Opacity == 1) // Don't do a collision intersect test with the camera bounds here because the parent does it.
+ {
+ if (Parent == null || OverrideParentScale == true)
+ {
+ // Cardinal directions.
+ camera.DrawString(m_font, m_text, new Vector2(posX - width, posY), outlineColour, radianRot, anchor, scale, flip, layer);
+ camera.DrawString(m_font, m_text, new Vector2(posX + width, posY), outlineColour, radianRot, anchor, scale, flip, layer);
+ camera.DrawString(m_font, m_text, new Vector2(posX, posY - width), outlineColour, radianRot, anchor, scale, flip, layer);
+ camera.DrawString(m_font, m_text, new Vector2(posX, posY + width), outlineColour, radianRot, anchor, scale, flip, layer);
+ // The corners.
+ if (LimitCorners == false)
+ {
+ camera.DrawString(m_font, m_text, new Vector2(posX - width, posY - width), outlineColour, radianRot, anchor, scale, flip, layer);
+ camera.DrawString(m_font, m_text, new Vector2(posX + width, posY + width), outlineColour, radianRot, anchor, scale, flip, layer);
+ camera.DrawString(m_font, m_text, new Vector2(posX + width, posY - width), outlineColour, radianRot, anchor, scale, flip, layer);
+ camera.DrawString(m_font, m_text, new Vector2(posX - width, posY + width), outlineColour, radianRot, anchor, scale, flip, layer);
+ }
+ }
+ else
+ {
+ Vector2 parentScale = Parent.Scale * Scale * m_internalFontSizeScale;
+ radianRot = MathHelper.ToRadians(Parent.Rotation + this.Rotation);
+
+ // Cardinal directions.
+ camera.DrawString(m_font, m_text, new Vector2(posX - width, posY), outlineColour, radianRot, anchor, parentScale, flip, layer);
+ camera.DrawString(m_font, m_text, new Vector2(posX + width, posY), outlineColour, radianRot, anchor, parentScale, flip, layer);
+ camera.DrawString(m_font, m_text, new Vector2(posX, posY - width), outlineColour, radianRot, anchor, parentScale, flip, layer);
+ camera.DrawString(m_font, m_text, new Vector2(posX, posY + width), outlineColour, radianRot, anchor, parentScale, flip, layer);
+ // The corners.
+ if (LimitCorners == false)
+ {
+ camera.DrawString(m_font, m_text, new Vector2(posX - width, posY - width), outlineColour, radianRot, anchor, parentScale, flip, layer);
+ camera.DrawString(m_font, m_text, new Vector2(posX + width, posY + width), outlineColour, radianRot, anchor, parentScale, flip, layer);
+ camera.DrawString(m_font, m_text, new Vector2(posX + width, posY - width), outlineColour, radianRot, anchor, parentScale, flip, layer);
+ camera.DrawString(m_font, m_text, new Vector2(posX - width, posY + width), outlineColour, radianRot, anchor, parentScale, flip, layer);
+ }
+ }
+ }
+ }
+ //base.DrawOutline(camera, width);
+ }
+
+ public override void Draw(Camera2D camera)
+ {
+ if (m_textureValue.IsDisposed)
+ m_font = SpriteFontArray.SpriteFontList[m_fontIndex];
+
+ if (IsTypewriting == true && m_typewriteCounter > 0 && IsPaused == false)
+ {
+ m_typewriteCounter -= (float)camera.GameTime.ElapsedGameTime.TotalSeconds;
+ if (m_typewriteCounter <= 0 && this.Text != m_typewriteText)
+ {
+ if (m_tapSFX != "")
+ SoundManager.PlaySound(m_tapSFX);
+
+ m_typewriteCounter = m_typewriteSpeed;
+ m_typewriteCharLength++;
+ this.Text = m_typewriteText.Substring(0, m_typewriteCharLength);
+ }
+ }
+
+ if (IsTypewriting == true && this.Text == m_typewriteText)
+ m_isTypewriting = false;
+
+ if (DropShadow != Vector2.Zero)
+ DrawDropShadow(camera);
+ if (OutlineWidth > 0 && (Parent == null || Parent.OutlineWidth == 0))
+ DrawOutline(camera);
+ //DrawOutline(camera, OutlineWidth);
+
+ if (this.Visible == true)
+ {
+ if (Parent == null || OverrideParentScale == true)
+ camera.DrawString(m_font, m_text, this.AbsPosition, this.TextureColor * this.Opacity, MathHelper.ToRadians(this.Rotation), this.Anchor, this.Scale * m_internalFontSizeScale, SpriteEffects.None, this.Layer);
+ else
+ camera.DrawString(m_font, m_text, this.AbsPosition, this.TextureColor * this.Opacity, MathHelper.ToRadians(Parent.Rotation + this.Rotation), Anchor, (Parent.Scale * this.Scale * m_internalFontSizeScale), SpriteEffects.None, Layer); // Flip disabled for now.
+ }
+ }
+
+ public void DrawDropShadow(Camera2D camera)
+ {
+ if (this.Visible == true)
+ {
+ if (Parent == null || OverrideParentScale == true)
+ camera.DrawString(m_font, m_text, this.AbsPosition + DropShadow, Color.Black * this.Opacity, MathHelper.ToRadians(this.Rotation), this.Anchor, this.Scale * m_internalFontSizeScale, SpriteEffects.None, this.Layer);
+ else
+ camera.DrawString(m_font, m_text, this.AbsPosition + DropShadow, Color.Black * this.Opacity, MathHelper.ToRadians(Parent.Rotation + this.Rotation), Anchor, (Parent.Scale * this.Scale * m_internalFontSizeScale), SpriteEffects.None, Layer); // Flip disabled for now.
+ }
+ }
+
+ public virtual void WordWrap(int width)
+ {
+ if (this.Width > width)
+ {
+ String line = String.Empty;
+ String returnString = String.Empty;
+ String[] wordArray;
+
+ if (isLogographic)
+ wordArray = this.Text.Select(x => x.ToString()).ToArray();
+ else
+ wordArray = this.Text.Split(' ');
+
+ foreach (String word in wordArray)
+ {
+ if (this.Font.MeasureString(line + word).X * (ScaleX * m_internalFontSizeScale.X) > width)
+ {
+ returnString = returnString + line + '\n';
+ line = String.Empty;
+ }
+
+ if (isLogographic) line = line + word;
+ else line = line + word + ' ';
+ }
+
+ this.Text = returnString + line;
+ }
+ }
+
+ public void RandomizeSentence(bool randomizeNumbers)
+ {
+ MatchCollection matches = Regex.Matches(this.Text, @"\b[\w']*\b");
+
+ foreach (Match match in matches)
+ {
+ string word = match.Value;
+ int result = 0;
+ if (randomizeNumbers == true || (randomizeNumbers == false && int.TryParse(word, out result) == false))
+ {
+ if (word.Length > 3) // only shuffle words longer than 3 letters.
+ {
+ List charArray = word.ToList();
+ char firstLetter = charArray[0];
+ char lastLetter = charArray[charArray.Count - 1];
+
+ // Remove the first and last letters from the shuffle array.
+ charArray.RemoveAt(charArray.Count - 1);
+ charArray.RemoveAt(0);
+
+ CDGMath.Shuffle(charArray);
+ string shuffledWord = new string(charArray.ToArray());
+
+ // Add back the first and last letter to the newly shuffled word.
+ shuffledWord = firstLetter + shuffledWord + lastLetter;
+ this.Text = Text.Replace(word, shuffledWord);
+
+ //char[] charArray = word.ToArray();
+ //CDGMath.Shuffle(charArray);
+ //string shuffledWord = new string(charArray);
+ //this.Text = Text.Replace(word, shuffledWord);
+ }
+ }
+ }
+ }
+
+ protected override GameObj CreateCloneInstance()
+ {
+ TextObj clone = new TextObj(m_defaultFont);
+ clone.ChangeFontNoDefault(this.m_font); // current font may be different from default font, use it!
+ return clone;
+ }
+
+ protected override void FillCloneInstance(object obj)
+ {
+ base.FillCloneInstance(obj);
+
+ TextObj clone = obj as TextObj;
+ clone.Text = this.Text;
+ clone.FontSize = this.FontSize;
+ clone.OutlineColour = this.OutlineColour;
+ clone.OutlineWidth = this.OutlineWidth;
+ clone.DropShadow = this.DropShadow;
+ clone.Align = this.Align;
+ clone.LimitCorners = this.LimitCorners;
+ }
+
+ // A way to insert additional Dispose functionality to the engine's TextObj without having to subclass
+ public delegate void DisposeDelegate(TextObj textObj);
+ public static DisposeDelegate disposeMethod;
+
+ public override void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ m_font = null;
+ if (disposeMethod != null) disposeMethod(this);
+ base.Dispose();
+ }
+ }
+
+#endregion
+ }
+}
diff --git a/DS2DEngine/src/MoveToLogicAction.cs b/DS2DEngine/src/MoveToLogicAction.cs
new file mode 100644
index 0000000..d018b0a
--- /dev/null
+++ b/DS2DEngine/src/MoveToLogicAction.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public class MoveToLogicAction : LogicAction
+ {
+ private Vector2 m_targetPos;
+ private float m_speed;
+
+ //To stop an object from moving, call this method and set overrideSpeed to 0.
+ public MoveToLogicAction(Vector2 targetPos, float overrideSpeed = -1)
+ {
+ m_targetPos = targetPos;
+ m_speed = overrideSpeed;
+ }
+
+ public override void Execute()
+ {
+ if (ParentLogicSet != null && ParentLogicSet.IsActive == true)
+ {
+ if (m_speed == -1)
+ m_speed = this.ParentLogicSet.ParentGameObj.Speed;
+
+ this.ParentLogicSet.ParentGameObj.CurrentSpeed = m_speed;
+
+ GameObj mover = ParentLogicSet.ParentGameObj;
+ mover.Heading = new Vector2(m_targetPos.X - mover.X, m_targetPos.Y - mover.Y);
+
+ if (this.ParentLogicSet.ParentGameObj.Flip == Microsoft.Xna.Framework.Graphics.SpriteEffects.None)
+ this.ParentLogicSet.ParentGameObj.Heading = new Vector2(1, 0);
+ else
+ this.ParentLogicSet.ParentGameObj.Heading = new Vector2(-1, 0);
+ base.Execute();
+ }
+ }
+
+ public override void ExecuteNext()
+ {
+ if (CDGMath.DistanceBetweenPts(ParentLogicSet.ParentGameObj.Position, m_targetPos) <= 10)
+ {
+ ParentLogicSet.ParentGameObj.Position = m_targetPos;
+ base.ExecuteNext();
+ }
+ }
+
+
+ public override void Stop()
+ {
+ this.ParentLogicSet.ParentGameObj.CurrentSpeed = 0;
+ base.Stop();
+ }
+
+ public override object Clone()
+ {
+ return new MoveToLogicAction(m_targetPos, m_speed);
+ }
+ }
+}
diff --git a/DS2DEngine/src/ParticleObj.cs b/DS2DEngine/src/ParticleObj.cs
new file mode 100644
index 0000000..aaff177
--- /dev/null
+++ b/DS2DEngine/src/ParticleObj.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace DS2DEngine
+{
+ public class ParticleObj : GameObj
+ {
+ // Position, Velocity, and Acceleration represent exactly what their names
+ // indicate. They are public fields rather than properties so that users
+ // can directly access their .X and .Y properties.
+ public Vector2 Velocity;
+ public Vector2 Acceleration;
+
+ // how long this particle will "live"
+ private float lifetime;
+ public float Lifetime
+ {
+ get { return lifetime; }
+ set { lifetime = value; }
+ }
+
+ // how long it has been since initialize was called
+ private float timeSinceStart;
+ public float TimeSinceStart
+ {
+ get { return timeSinceStart; }
+ set { timeSinceStart = value; }
+ }
+
+ // how fast does it rotate? In radians.
+ private float rotationSpeed;
+ public float RotationSpeed
+ {
+ get { return rotationSpeed; }
+ set { rotationSpeed = value; }
+ }
+
+ private Texture2D sprite;
+ public Texture2D Sprite
+ {
+ get { return sprite; }
+ set { sprite = value; }
+ }
+
+ private float startDelay;
+ public float StartDelay
+ {
+ get { return startDelay; }
+ set { startDelay = value; }
+ }
+
+ public Vector2 Origin
+ {
+ get { return new Vector2(SpriteSourceRect.Width / 2, SpriteSourceRect.Height / 2); }
+ }
+
+ public Rectangle SpriteSourceRect = new Rectangle(0, 0, 0, 0);
+
+ // is this particle still alive? once TimeSinceStart becomes greater than
+ // Lifetime, the particle should no longer be drawn or updated.
+ public bool Active
+ {
+ get { return TimeSinceStart < Lifetime; }
+ }
+
+
+ // initialize is called by ParticleSystem to set up the particle, and prepares
+ // the particle for use.
+ public void Initialize(Vector2 position, Vector2 velocity, Vector2 acceleration,
+ float lifetime, Vector2 scale, float rotationSpeed, float startingRotation)
+ {
+ // set the values to the requested values
+ this.Position = position;
+ this.Velocity = velocity;
+ this.Acceleration = acceleration;
+ this.Lifetime = lifetime;
+ this.Scale = scale;
+ this.RotationSpeed = rotationSpeed;
+
+ // reset TimeSinceStart - we have to do this because particles will be
+ // reused.
+ this.TimeSinceStart = 0.0f;
+
+ // set rotation to some random value between 0 and 360 degrees.
+ this.Rotation = startingRotation;
+ }
+
+ // update is called by the ParticleSystem on every frame. This is where the
+ // particle's position and that kind of thing get updated.
+ public void Update(float dt)
+ {
+ if (StartDelay <= 0)
+ {
+ Velocity += Acceleration * dt;
+ Position += Velocity * dt;
+
+ Rotation += RotationSpeed * dt;
+
+ TimeSinceStart += dt;
+ }
+ else
+ StartDelay -= dt;
+ }
+
+ public void Stop()
+ {
+ TimeSinceStart = Lifetime;
+ }
+
+ protected override GameObj CreateCloneInstance()
+ {
+ return new ParticleObj();
+ }
+
+ protected override void FillCloneInstance(object obj)
+ {
+ base.FillCloneInstance(obj);
+ ParticleObj clone = obj as ParticleObj;
+ clone.Lifetime = this.Lifetime;
+ clone.Acceleration = this.Acceleration;
+ clone.Velocity = this.Velocity;
+ clone.TimeSinceStart = this.TimeSinceStart;
+ clone.StartDelay = this.StartDelay;
+ clone.RotationSpeed = this.RotationSpeed;
+ clone.Sprite = this.Sprite;
+ }
+ }
+}
diff --git a/DS2DEngine/src/ParticleSystem.cs b/DS2DEngine/src/ParticleSystem.cs
new file mode 100644
index 0000000..0100cd1
--- /dev/null
+++ b/DS2DEngine/src/ParticleSystem.cs
@@ -0,0 +1,355 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using SpriteSystem;
+
+namespace DS2DEngine
+{
+ public abstract class ParticleSystem : IDisposableObj
+ {
+
+ private bool m_isDisposed = false;
+
+ // these two values control the order that particle systems are drawn in.
+ // typically, particles that use additive blending should be drawn on top of
+ // particles that use regular alpha blending. ParticleSystems should therefore
+ // set their DrawOrder to the appropriate value in InitializeConstants, though
+ // it is possible to use other values for more advanced effects.
+ public const int AlphaBlendDrawOrder = 100;
+ public const int AdditiveDrawOrder = 200;
+
+ // the texture this particle system will use.
+ private Texture2D texture;
+
+ // the origin when we're drawing textures. this will be the middle of the
+ // texture.
+ private Vector2 origin;
+
+ // this number represents the maximum number of effects this particle system
+ // will be expected to draw at one time. this is set in the constructor and is
+ // used to calculate how many particles we will need.
+ private int howManyEffects;
+
+ // the array of particles used by this system. these are reused, so that calling
+ // AddParticles will not cause any allocations.
+ protected ParticleObj[] particles;
+
+ // the queue of free particles keeps track of particles that are not curently
+ // being used by an effect. when a new effect is requested, particles are taken
+ // from this queue. when particles are finished they are put onto this queue.
+ Queue freeParticles;
+ ///
+ /// returns the number of particles that are available for a new effect.
+ ///
+ public int FreeParticleCount
+ {
+ get { return freeParticles.Count; }
+ }
+
+
+ // This region of values control the "look" of the particle system, and should
+ // be set by deriving particle systems in the InitializeConstants method. The
+ // values are then used by the virtual function InitializeParticle. Subclasses
+ // can override InitializeParticle for further
+ // customization.
+ #region constants to be set by subclasses
+
+ ///
+ /// minNumParticles and maxNumParticles control the number of particles that are
+ /// added when AddParticles is called. The number of particles will be a random
+ /// number between minNumParticles and maxNumParticles.
+ ///
+ protected int minNumParticles;
+ protected int maxNumParticles;
+
+ ///
+ /// this controls the texture that the particle system uses. It will be used as
+ /// an argument to ContentManager.Load.
+ ///
+ protected string textureFilename;
+
+ ///
+ /// minInitialSpeed and maxInitialSpeed are used to control the initial velocity
+ /// of the particles. The particle's initial speed will be a random number
+ /// between these two. The direction is determined by the function
+ /// PickRandomDirection, which can be overriden.
+ ///
+ protected float minInitialSpeed;
+ protected float maxInitialSpeed;
+
+ ///
+ /// minAcceleration and maxAcceleration are used to control the acceleration of
+ /// the particles. The particle's acceleration will be a random number between
+ /// these two. By default, the direction of acceleration is the same as the
+ /// direction of the initial velocity.
+ ///
+ protected float minAcceleration;
+ protected float maxAcceleration;
+
+ ///
+ /// minRotationSpeed and maxRotationSpeed control the particles' angular
+ /// velocity: the speed at which particles will rotate. Each particle's rotation
+ /// speed will be a random number between minRotationSpeed and maxRotationSpeed.
+ /// Use smaller numbers to make particle systems look calm and wispy, and large
+ /// numbers for more violent effects.
+ ///
+ protected float minRotationSpeed;
+ protected float maxRotationSpeed;
+
+ ///
+ /// minLifetime and maxLifetime are used to control the lifetime. Each
+ /// particle's lifetime will be a random number between these two. Lifetime
+ /// is used to determine how long a particle "lasts." Also, in the base
+ /// implementation of Draw, lifetime is also used to calculate alpha and scale
+ /// values to avoid particles suddenly "popping" into view
+ ///
+ protected float minLifetime;
+ protected float maxLifetime;
+
+ ///
+ /// to get some additional variance in the appearance of the particles, we give
+ /// them all random scales. the scale is a value between minScale and maxScale,
+ /// and is additionally affected by the particle's lifetime to avoid particles
+ /// "popping" into view.
+ ///
+ protected float minScale;
+ protected float maxScale;
+
+ ///
+ /// different effects can use different blend states. fire and explosions work
+ /// well with additive blending, for example.
+ ///
+ protected BlendState blendState;
+
+ protected Rectangle spriteSourceRect;
+
+ #endregion
+
+ ///
+ /// Constructs a new ParticleSystem.
+ ///
+ /// The host for this particle system. The game keeps the
+ /// content manager and sprite batch for us.
+ /// the maximum number of particle effects that
+ /// are expected on screen at once.
+ /// it is tempting to set the value of howManyEffects very high.
+ /// However, this value should be set to the minimum possible, because
+ /// it has a large impact on the amount of memory required, and slows down the
+ /// Update and Draw functions.
+ protected ParticleSystem(int howManyEffects)
+ {
+ this.howManyEffects = howManyEffects;
+ }
+
+ ///
+ /// override the base class's Initialize to do some additional work; we want to
+ /// call InitializeConstants to let subclasses set the constants that we'll use.
+ ///
+ /// also, the particle array and freeParticles queue are set up here.
+ ///
+ public void Initialize()
+ {
+ InitializeConstants();
+
+ texture = SpriteLibrary.GetSprite(textureFilename);
+
+ if (spriteSourceRect == new Rectangle(0, 0, 0, 0))
+ {
+ ImageData imageData = SpriteLibrary.GetImageData(textureFilename, 0);
+ spriteSourceRect = new Rectangle((int)(imageData.SSPos.X), (int)(imageData.SSPos.Y), imageData.Width, imageData.Height);
+ }
+
+ // ... and calculate the center. this'll be used in the draw call, we
+ // always want to rotate and scale around this point.
+ origin.X = spriteSourceRect.Width / 2;
+ origin.Y = spriteSourceRect.Height / 2;
+
+ // calculate the total number of particles we will ever need, using the
+ // max number of effects and the max number of particles per effect.
+ // once these particles are allocated, they will be reused, so that
+ // we don't put any pressure on the garbage collector.
+ particles = new ParticleObj[howManyEffects * maxNumParticles];
+ freeParticles = new Queue(howManyEffects * maxNumParticles);
+ for (int i = 0; i < particles.Length; i++)
+ {
+ particles[i] = new ParticleObj();
+ freeParticles.Enqueue(particles[i]);
+ particles[i].Sprite = texture;
+ particles[i].SpriteSourceRect = spriteSourceRect;
+ }
+ }
+
+ ///
+ /// this abstract function must be overriden by subclasses of ParticleSystem.
+ /// It's here that they should set all the constants marked in the region
+ /// "constants to be set by subclasses", which give each ParticleSystem its
+ /// specific flavor.
+ ///
+ protected abstract void InitializeConstants();
+
+ ///
+ /// AddParticles's job is to add an effect somewhere on the screen. If there
+ /// aren't enough particles in the freeParticles queue, it will use as many as
+ /// it can. This means that if there not enough particles available, calling
+ /// AddParticles will have no effect.
+ ///
+ /// where the particle effect should be created
+ public virtual void StartEffect(Vector2 startingPos)
+ {
+ // the number of particles we want for this effect is a random number
+ // somewhere between the two constants specified by the subclasses.
+ int numParticles = CDGMath.RandomInt(minNumParticles, maxNumParticles);
+
+ // create that many particles, if you can.
+ for (int i = 0; i < numParticles && freeParticles.Count > 0; i++)
+ {
+ // grab a particle from the freeParticles queue, and Initialize it.
+ ParticleObj p = freeParticles.Dequeue();
+ InitializeParticle(p, startingPos);
+ }
+ }
+
+ ///
+ /// InitializeParticle randomizes some properties for a particle, then
+ /// calls initialize on it. It can be overriden by subclasses if they
+ /// want to modify the way particles are created. For example,
+ /// SmokePlumeParticleSystem overrides this function make all particles
+ /// accelerate to the right, simulating wind.
+ ///
+ /// the particle to initialize
+ /// the position on the screen that the particle should be
+ ///
+ protected virtual void InitializeParticle(ParticleObj p, Vector2 startingPos)
+ {
+ // first, call PickRandomDirection to figure out which way the particle
+ // will be moving. velocity and acceleration's values will come from this.
+ Vector2 direction = PickRandomDirection();
+
+ // pick some random values for our particle
+ float velocity = CDGMath.RandomFloat(minInitialSpeed, maxInitialSpeed);
+ float acceleration = CDGMath.RandomFloat(minAcceleration, maxAcceleration);
+ float lifetime = CDGMath.RandomFloat(minLifetime, maxLifetime);
+ float scale = CDGMath.RandomFloat(minScale, maxScale);
+ float rotationSpeed = CDGMath.RandomFloat(minRotationSpeed, maxRotationSpeed);
+ float startingRotation = CDGMath.RandomFloat(0, 360);
+
+ // then initialize it with those random values. initialize will save those,
+ // and make sure it is marked as active.
+ p.Initialize(
+ startingPos, velocity * direction, acceleration * direction,
+ lifetime, new Vector2(scale, scale), MathHelper.ToRadians(rotationSpeed), startingRotation);
+ }
+
+ ///
+ /// PickRandomDirection is used by InitializeParticles to decide which direction
+ /// particles will move. The default implementation is a random vector in a
+ /// circular pattern.
+ ///
+ protected virtual Vector2 PickRandomDirection()
+ {
+ float angle = MathHelper.ToRadians(CDGMath.RandomInt(0,360));
+ return new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
+ }
+
+ ///
+ /// overriden from DrawableGameComponent, Update will update all of the active
+ /// particles.
+ ///
+ public void Update(GameTime gameTime)
+ {
+ // calculate dt, the change in the since the last frame. the particle
+ // updates will use this value.
+ float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
+ // go through all of the particles...
+ foreach (ParticleObj p in particles)
+ {
+
+ if (p.Active)
+ {
+ // ... and if they're active, update them.
+ p.Update(dt);
+ // if that update finishes them, put them onto the free particles
+ // queue.
+ }
+ if (!p.Active)
+ {
+ freeParticles.Enqueue(p);
+ }
+ }
+ }
+
+ ///
+ /// overriden from DrawableGameComponent, Draw will use ParticleSampleGame's
+ /// sprite batch to render all of the active particles.
+ ///
+ public void Draw(Camera2D camera)
+ {
+ // tell sprite batch to begin, using the spriteBlendMode specified in
+ // initializeConstants
+ //camera.Begin(SpriteSortMode.Deferred, blendState);
+
+ foreach (ParticleObj p in particles)
+ {
+ // skip inactive particles
+ if (!p.Active)
+ continue;
+
+ // normalized lifetime is a value from 0 to 1 and represents how far
+ // a particle is through its life. 0 means it just started, .5 is half
+ // way through, and 1.0 means it's just about to be finished.
+ // this value will be used to calculate alpha and scale, to avoid
+ // having particles suddenly appear or disappear.
+ float normalizedLifetime = p.TimeSinceStart / p.Lifetime;
+
+ // we want particles to fade in and fade out, so we'll calculate alpha
+ // to be (normalizedLifetime) * (1-normalizedLifetime). this way, when
+ // normalizedLifetime is 0 or 1, alpha is 0. the maximum value is at
+ // normalizedLifetime = .5, and is
+ // (normalizedLifetime) * (1-normalizedLifetime)
+ // (.5) * (1-.5)
+ // .25
+ // since we want the maximum alpha to be 1, not .25, we'll scale the
+ // entire equation by 4.
+ float alpha = 4 * normalizedLifetime * (1 - normalizedLifetime);
+ Color color = Color.White * alpha;
+
+ // make particles grow as they age. they'll start at 75% of their size,
+ // and increase to 100% once they're finished.
+ Vector2 scale = p.Scale * (.75f + .25f * normalizedLifetime);
+
+ //camera.Draw(texture, p.Position, null, color, p.Rotation, origin, scale, SpriteEffects.None, 0.0f);
+ camera.Draw(texture, p.Position, spriteSourceRect, color, p.Rotation, origin, scale, SpriteEffects.None, 1);
+ }
+
+ //camera.End();
+ }
+
+ public void ResetSystem()
+ {
+ foreach (ParticleObj particle in particles)
+ particle.Stop();
+ }
+
+ public void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ freeParticles.Clear();
+ foreach (ParticleObj particle in particles)
+ particle.Dispose();
+ Array.Clear(particles, 0, particles.Length);
+
+ m_isDisposed = true;
+ texture = null;
+ }
+ }
+
+ public bool IsDisposed
+ {
+ get { return m_isDisposed; }
+ }
+ }
+}
diff --git a/DS2DEngine/src/Physics/IPhysicsObj.cs b/DS2DEngine/src/Physics/IPhysicsObj.cs
new file mode 100644
index 0000000..f0fffda
--- /dev/null
+++ b/DS2DEngine/src/Physics/IPhysicsObj.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public interface IPhysicsObj
+ {
+ bool CollidesLeft { get; set; }
+ bool CollidesRight { get; set; }
+ bool CollidesTop { get; set; }
+ bool CollidesBottom { get; set; }
+ bool Visible { get; set; }
+ bool SameTypesCollide { get; set; }
+ LinkedListNode Node { get; set; }
+
+ bool DisableHitboxUpdating { get; set; }
+
+ int CollisionTypeTag { get; set; } // A generic tag that developers can use to tag the type of collision object you are running into.
+ bool IsWeighted { get; set; }
+ bool AccelerationXEnabled { get; set; }
+ bool AccelerationYEnabled { get; set; }
+ bool IsCollidable { get; set; }
+ float AccelerationX { get; set; }
+ float AccelerationY { get; set; }
+ float X { get; set; }
+ float Y { get; set; }
+ float Rotation { get; set; }
+ int Width { get; }
+ int Height { get; }
+
+ Rectangle Bounds { get; }
+ Rectangle TerrainBounds { get; }
+
+ PhysicsManager PhysicsMngr { get; set; }
+
+ List CollisionBoxes { get; }
+
+ void UpdatePhysics(GameTime gameTime);
+ void UpdateCollisionBoxes();
+ void CollisionResponse(CollisionBox thisBox, CollisionBox otherBox, int collisionResponseType);
+ void RemoveFromPhysicsManager();
+
+ bool DisableAllWeight { get; set; }
+
+ bool HasTerrainHitBox { get; }
+ bool DisableCollisionBoxRotations { get; set; }
+ bool DisableGravity { get; set; }
+ bool UseCachedValues { get; set; }
+ }
+}
diff --git a/DS2DEngine/src/Physics/PhysicsManager.cs b/DS2DEngine/src/Physics/PhysicsManager.cs
new file mode 100644
index 0000000..4818d0f
--- /dev/null
+++ b/DS2DEngine/src/Physics/PhysicsManager.cs
@@ -0,0 +1,280 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace DS2DEngine
+{
+ public class PhysicsManager: IDisposable
+ {
+ private LinkedList _poList;
+ public int TerminalVelocity { get; set; }
+
+ private Vector2 _gravity = Vector2.Zero; // In metres per second.
+
+ private Color m_damageHitboxColor = new Color(255, 0, 0, 100);
+ private Color m_attackHitboxColor = new Color(0, 0, 155, 100);
+ private Color m_collisionHitboxColor = new Color(0, 53, 0, 100);
+ private Camera2D m_camera;
+
+ private bool m_isDisposed = false;
+
+ public PhysicsManager()
+ {
+ _poList = new LinkedList();
+ TerminalVelocity = 1000;
+ }
+
+ public void Initialize(Camera2D camera)
+ {
+ m_camera = camera;
+ }
+
+ public void AddObject(IPhysicsObj obj)
+ {
+ if (obj.Node == null)
+ {
+ obj.Node = _poList.AddLast(obj);
+ obj.PhysicsMngr = this;
+ }
+ else
+ {
+ _poList.AddLast(obj.Node);
+ obj.PhysicsMngr = this;
+ }
+
+ // Optimization - cache the physics values for each collision box
+ // Calling UpdateCachedValues in the constructor of CollisionBoxes alone
+ // did not work. Without this cache update, the character would
+ // fall through the world on the first frame and end up below the ground.
+ foreach (CollisionBox box in obj.CollisionBoxes)
+ {
+ box.UpdateCachedValues();
+ }
+ }
+
+ public void RemoveObject(IPhysicsObj obj)
+ {
+ if (obj.Node != null && obj.PhysicsMngr != null)
+ {
+ // try
+ {
+ _poList.Remove(obj.Node);
+ obj.PhysicsMngr = null;
+ }
+ // catch { Console.WriteLine("Could not remove " + obj + " from Physics Manager as it was not found."); }
+ }
+ }
+
+ public void RemoveAllObjects()
+ {
+ int counter = _poList.Count;
+ while (counter > 0)
+ {
+ RemoveObject(_poList.Last.Value);
+ counter--;
+ }
+ }
+
+ public void Update(GameTime gameTime)
+ {
+ float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
+
+ foreach (IPhysicsObj weightedObj in _poList)
+ {
+ if (weightedObj.Visible == true)// && CollisionMath.Intersects(weightedObj.Bounds, m_camera.LogicBounds))
+ {
+ if (weightedObj.DisableAllWeight == false)
+ {
+ if (weightedObj.IsWeighted == true)
+ {
+ Vector2 gravity = new Vector2(_gravity.X * elapsedTime, _gravity.Y * elapsedTime);
+
+ if (weightedObj.AccelerationXEnabled == true && weightedObj.DisableGravity == false)
+ weightedObj.AccelerationX += gravity.X;
+ if (weightedObj.AccelerationYEnabled == true && weightedObj.DisableGravity == false)
+ weightedObj.AccelerationY += gravity.Y;
+
+ if (weightedObj.AccelerationY > TerminalVelocity)
+ weightedObj.AccelerationY = TerminalVelocity;
+ }
+
+ weightedObj.UpdatePhysics(gameTime);
+ }
+ }
+ // Update code, physics, and collision code goes here. Only update collision boxes if the flag isn't disabled. (Needed in case you manually add collision boxes, in which case this method
+ // would constantly override those hitboxes.
+ if (weightedObj.DisableHitboxUpdating == false)
+ weightedObj.UpdateCollisionBoxes();
+
+ weightedObj.UseCachedValues = true;
+ foreach (CollisionBox box in weightedObj.CollisionBoxes)
+ box.UpdateCachedValues();
+ }
+
+ //Converted to linked list, and had IPhysicsObjs hold a reference to their linkedlist node, so that their addition and removal from the Physics Manager is quick and painless.
+ LinkedListNode firstNode = _poList.First;
+ LinkedListNode secondNode = null;
+ if (firstNode != null) secondNode = firstNode.Next;
+
+ while (firstNode != null)
+ {
+ IPhysicsObj firstObj = firstNode.Value;
+ int firstBoxHBCount = firstObj.CollisionBoxes.Count;
+
+ if (firstObj.IsCollidable == true && firstObj.Visible == true)// && CollisionMath.Intersects(firstObj.Bounds, m_camera.LogicBounds))
+ {
+ while (secondNode != null)
+ {
+ IPhysicsObj secondObj = secondNode.Value;
+ int secondBoxHBCount = secondObj.CollisionBoxes.Count;
+ secondNode = secondNode.Next;
+
+ // If both objects are the same type (i.e. enemies) & SameType flag is false for BOTH, move to next iteration.
+ if (firstObj.CollisionTypeTag == secondObj.CollisionTypeTag && (firstObj.SameTypesCollide == false && secondObj.SameTypesCollide == false))
+ continue;
+
+ if (secondObj.IsCollidable == true && secondObj.Visible == true)// && CollisionMath.Intersects(secondObj.Bounds, m_camera.LogicBounds))
+ {
+ for (int l = 0; l < firstBoxHBCount; l++)
+ {
+ CollisionBox firstBox = firstObj.CollisionBoxes[l];
+
+ Rectangle firstBoxAbsRect = firstBox.AbsRect; // Optimization so we don't keep pinging it, despite being cached.
+ float firstBoxAbsRotation = firstBox.AbsRotation; // Optimization so we don't keep pinging it, despite being cached.
+
+ for (int m = 0; m < secondBoxHBCount; m++)
+ {
+ CollisionBox secondBox = secondObj.CollisionBoxes[m];
+
+ Rectangle secondBoxAbsRect = secondBox.AbsRect;
+ float secondBoxAbsRotation = secondBox.AbsRotation;
+
+ if (firstBox.Type != Consts.NULL_HITBOX && secondBox.Type != Consts.NULL_HITBOX && firstBox.Parent.Visible == true && secondBox.Parent.Visible == true)
+ {
+ bool runHitDetection = false;
+ int collisionResponseType = 0;
+ // Perform terrain hit detection.
+ if (firstBox.Type == Consts.TERRAIN_HITBOX && secondBox.Type == Consts.TERRAIN_HITBOX )//&& (firstObj.IsWeighted == false || secondObj.IsWeighted == false))
+ {
+ runHitDetection = true;
+ collisionResponseType = Consts.COLLISIONRESPONSE_TERRAIN;
+ }
+ // Perform weapon/body body/weapon hit detection.
+ else if ((firstBox.Type == Consts.WEAPON_HITBOX && secondBox.Type == Consts.BODY_HITBOX) || (firstBox.Type == Consts.BODY_HITBOX && secondBox.Type == Consts.WEAPON_HITBOX))
+ {
+ runHitDetection = true;
+ if (firstBox.Type == Consts.WEAPON_HITBOX)
+ collisionResponseType = Consts.COLLISIONRESPONSE_SECONDBOXHIT;
+ else
+ collisionResponseType = Consts.COLLISIONRESPONSE_FIRSTBOXHIT;
+ }
+
+ if (runHitDetection == true)
+ {
+ if (CollisionMath.RotatedRectIntersects(firstBoxAbsRect, firstBoxAbsRotation, Vector2.Zero,
+ secondBoxAbsRect, secondBoxAbsRotation, Vector2.Zero) == true)
+ {
+ if (collisionResponseType == Consts.COLLISIONRESPONSE_FIRSTBOXHIT)
+ {
+ firstObj.CollisionResponse(firstBox, secondBox, Consts.COLLISIONRESPONSE_FIRSTBOXHIT);
+ secondObj.CollisionResponse(secondBox, firstBox, Consts.COLLISIONRESPONSE_SECONDBOXHIT);
+ }
+ else if (collisionResponseType == Consts.COLLISIONRESPONSE_SECONDBOXHIT)
+ {
+ firstObj.CollisionResponse(firstBox, secondBox, Consts.COLLISIONRESPONSE_SECONDBOXHIT);
+ secondObj.CollisionResponse(secondBox, firstBox, Consts.COLLISIONRESPONSE_FIRSTBOXHIT);
+ }
+ else
+ {
+ firstObj.CollisionResponse(firstBox, secondBox, collisionResponseType);
+ secondObj.CollisionResponse(secondBox, firstBox, collisionResponseType);
+ }
+
+ firstBox.UpdateCachedValues();
+ secondBox.UpdateCachedValues();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ firstNode = firstNode.Next;
+ if (firstNode != null)
+ secondNode = firstNode.Next;
+ }
+
+ foreach (IPhysicsObj weightedObj in _poList)
+ {
+ weightedObj.UseCachedValues = false;
+ }
+ }
+
+ public void SetGravity(float xGrav, float yGrav)
+ {
+ _gravity.X = xGrav;
+ _gravity.Y = yGrav;
+ }
+
+ public Vector2 GetGravity()
+ {
+ return _gravity;
+ }
+
+ public LinkedList ObjectList
+ {
+ get {return _poList;}
+ }
+
+ public void DrawAllCollisionBoxes(SpriteBatch spriteBatch, Texture2D texture, int collBoxType)
+ {
+ foreach (IPhysicsObj po in _poList)
+ {
+ foreach (CollisionBox cb in po.CollisionBoxes)
+ {
+ if (cb.Type == collBoxType && cb.Parent.Visible == true && cb.AbsParent.Visible == true)
+ {
+ switch (cb.Type)
+ {
+ case (Consts.WEAPON_HITBOX):
+ //spriteBatch.Draw(texture, cb.AbsRect, m_attackHitboxColor);
+ spriteBatch.Draw(texture, cb.AbsRect, null, m_attackHitboxColor, MathHelper.ToRadians(cb.AbsRotation), Vector2.Zero, SpriteEffects.None, 1);
+ break;
+ case (Consts.BODY_HITBOX):
+ //spriteBatch.Draw(texture, cb.AbsRect, m_damageHitboxColor);
+ spriteBatch.Draw(texture, cb.AbsRect, null, m_damageHitboxColor, MathHelper.ToRadians(cb.AbsRotation), Vector2.Zero, SpriteEffects.None, 1);
+ break;
+ case (Consts.TERRAIN_HITBOX):
+ //spriteBatch.Draw(texture, cb.AbsRect, m_collisionHitboxColor);
+ spriteBatch.Draw(texture, cb.AbsRect, null, m_collisionHitboxColor, MathHelper.ToRadians(cb.AbsRotation), Vector2.Zero, SpriteEffects.None, 1);
+ break;
+
+ }
+ }
+ }
+ }
+ }
+
+ public bool IsDisposed
+ {
+ get { return m_isDisposed;}
+ }
+
+ public void Dispose()
+ {
+ if (IsDisposed == false)
+ {
+ Console.WriteLine("Disposing Physics Manager");
+
+ _poList.Clear();
+ _poList = null;
+ m_isDisposed = true;
+ }
+ }
+ }
+}
diff --git a/DS2DEngine/src/RectangleTree.cs b/DS2DEngine/src/RectangleTree.cs
new file mode 100644
index 0000000..76f8012
--- /dev/null
+++ b/DS2DEngine/src/RectangleTree.cs
@@ -0,0 +1,343 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace DS2DEngine
+{
+ public class RectangleTree
+ {
+ public Dictionary AllItems;
+ protected Func PositionDelegate;
+ protected RectangleTreeNode Root;
+
+ public class RectangleTreeItem
+ {
+ public RectangleTreeNode Parent;
+ public Rectangle Position;
+ public T Value;
+ }
+
+ public class RectangleTreeNode
+ {
+ public RectangleTreeNode Parent;
+ public RectangleTreeNode[] Children;
+ public List Items;
+ public Rectangle Position;
+ public int ItemCount;
+
+ public RectangleTreeNode(Rectangle dimensions, RectangleTreeNode parent)
+ {
+ Items = new List();
+ Position = dimensions;
+ Parent = parent;
+ }
+ }
+
+ public RectangleTree(Func positionDelegate, Rectangle dimensions) {
+ PositionDelegate = positionDelegate;
+ AllItems = new Dictionary();
+ Root = new RectangleTreeNode(dimensions, null);
+ }
+
+ public void Add(T item) {
+ RectangleTreeItem i = new RectangleTreeItem();
+ i.Value = item;
+ i.Position = PositionDelegate(item);
+ AllItems.Add(item, i);
+ Add(Root, i);
+ }
+
+ private bool Add(RectangleTreeNode node, RectangleTreeItem item)
+ {
+ Rectangle nodePosition = node.Position;
+ //check if the item is entirely contained in this node
+ if (nodePosition.Contains(item.Position))
+ {
+ if (node.Children == null)
+ {
+ //Bucket has not been split yet
+ if (node.Items.Count < 5)
+ {
+ InnerAdd(node, item);
+ return true;
+ }
+
+ //Bucket needs to be split
+ CreateChildren(node);
+
+ //Move all nodes down
+ for (int i = 0; i < node.Items.Count; i++)
+ {
+ if (MoveDown(node.Items[i]))
+ {
+ i--;
+ }
+ }
+ }
+ //bucket has been split
+ //try to add the item to each child
+ foreach (RectangleTreeNode child in node.Children)
+ {
+ if (Add(child, item))
+ {
+ node.ItemCount++;
+ return true;
+ }
+ }
+
+ //couldn't add to any children, add to this node
+ InnerAdd(node, item);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void CreateChildren(RectangleTreeNode node)
+ {
+ Rectangle nodePosition = node.Position;
+ int w = nodePosition.Width / 2, h = nodePosition.Height / 2;
+ node.Children = new RectangleTreeNode[4] {
+ new RectangleTreeNode(new Rectangle(nodePosition.X, nodePosition.Y, w, h), node),
+ new RectangleTreeNode(new Rectangle(nodePosition.X + w, nodePosition.Y, w,h), node),
+ new RectangleTreeNode(new Rectangle(nodePosition.X, nodePosition.Y + h, w, h), node),
+ new RectangleTreeNode(new Rectangle(nodePosition.X + w, nodePosition.Y + h, w, h), node)
+ };
+ }
+
+ private bool MoveDown(RectangleTreeItem item)
+ {
+ foreach (RectangleTreeNode child in item.Parent.Children)
+ {
+ if (Add(child, item))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void InnerAdd(RectangleTreeNode node, RectangleTreeItem item)
+ {
+ if (item.Parent != null)
+ {
+ item.Parent.Items.Remove(item);
+ }
+ node.Items.Add(item);
+ node.ItemCount++;
+ item.Parent = node;
+ }
+
+ public void Remove(T item) {
+ Remove(AllItems[item].Parent, AllItems[item]);
+ AllItems.Remove(item);
+ }
+
+ private void Remove(RectangleTreeNode node, RectangleTreeItem item)
+ {
+ node.Items.Remove(item);
+ item.Parent = null;
+
+ while (node != null)
+ {
+ node.ItemCount--;
+ if (node.ItemCount < 6)
+ {
+ CollapseChildren(node);
+ }
+ node = node.Parent;
+ }
+ }
+
+ private void CollapseChildren(RectangleTreeNode node)
+ {
+ foreach (RectangleTreeNode child in node.Children)
+ {
+ while (child.Items.Count > 0)
+ {
+ MoveUp(child.Items[0]);
+ }
+ }
+
+ node.Children = null;
+ }
+
+ private void MoveUp(RectangleTreeItem item)
+ {
+ item.Parent.Items.Remove(item);
+ item.Parent.ItemCount--;
+ if (item.Parent.Children != null && item.Parent.ItemCount < 6)
+ {
+ CollapseChildren(item.Parent);
+ }
+ item.Parent = item.Parent.Parent;
+
+
+ if (item.Parent == null)
+ {
+ AllItems.Remove(item.Value);
+ }
+ else
+ {
+ item.Parent.Items.Add(item);
+ }
+ }
+
+ public void UpdatePosition(T item){
+ RectangleTreeItem ri = AllItems[item];
+ Rectangle newPosition = PositionDelegate(item);
+
+ if (newPosition == ri.Position)
+ {
+ return;
+ }
+
+ ri.Position = newPosition;
+ if (ri.Parent.Position.Contains(newPosition))
+ { //Step Into
+ if (ri.Parent.Children != null)
+ MoveDown(ri);
+ }
+ else
+ {
+ do
+ { //Step Out Of
+ MoveUp(ri);
+ } while (ri.Parent != null && !ri.Parent.Position.Contains(newPosition));
+
+ if (ri.Parent != null)
+ {
+ Add(ri.Parent, ri);
+ }
+ }
+ }
+
+ public T[] GetItems(Rectangle area) {
+ return GetItems(Root, area);
+ }
+
+ private T[] GetItems(RectangleTreeNode node, Rectangle area)
+ {
+ List items = new List();
+ foreach (RectangleTreeItem item in node.Items)
+ {
+ if (item.Position.Intersects(area) || item.Position.Contains(area) || area.Contains(item.Position))
+ {
+ items.Add(item.Value);
+ }
+ }
+
+ if (node.Children != null)
+ {
+ foreach (RectangleTreeNode child in node.Children)
+ {
+ if (area.Contains(child.Position))
+ {
+ items.AddRange(GetAllItems(node));
+ }
+ else if (child.Position.Contains(area))
+ {
+ items.AddRange(GetItems(child, area));
+ break;
+ }
+ else if (child.Position.Intersects(area))
+ {
+ items.AddRange(GetItems(child, area));
+ }
+ }
+ }
+
+ return items.ToArray();
+ }
+
+ private T[] GetAllItems(RectangleTreeNode node)
+ {
+ T[] items = new T[node.ItemCount];
+ int i = 0;
+ foreach (RectangleTreeItem item in node.Items)
+ {
+ items[i++] = item.Value;
+ }
+
+ if (node.Children != null)
+ {
+ int q;
+ foreach (RectangleTreeNode child in node.Children)
+ {
+ T[] tmp = GetAllItems(child);
+ for (q = i; q < tmp.Length + i; q++)
+ {
+ items[q] = tmp[q - i];
+ }
+ i = q;
+ }
+ }
+
+ return items;
+ }
+
+ public T[] GetItems(Point point) {
+ return GetItems(Root, point);
+ }
+
+ private T[] GetItems(RectangleTreeNode node, Point point)
+ {
+ List items = new List();
+ foreach (RectangleTreeItem item in node.Items)
+ {
+ if (item.Position.Contains(point))
+ {
+ items.Add(item.Value);
+ }
+ }
+
+ if (node.Children != null)
+ {
+ foreach (RectangleTreeNode child in node.Children)
+ {
+ if(child.Position.Contains(point)) {
+ items.AddRange(GetItems(child, point));
+ break;
+ }
+ }
+ }
+ return items.ToArray();
+ }
+ }
+
+ public class RectangleDrawer : RectangleTree
+ {
+ Texture2D pixel;
+
+ public RectangleDrawer(GraphicsDevice graphicsDevice, Func positionDelegate, Rectangle dimensions)
+ : base(positionDelegate, dimensions)
+ {
+ pixel = new Texture2D(graphicsDevice, 1, 1);
+ pixel.SetData(new Color[] { Color.White });
+ }
+ public void Draw(SpriteBatch spriteBatch)
+ {
+ Draw(Root, spriteBatch);
+ }
+
+ private void Draw(RectangleTreeNode Root, SpriteBatch spriteBatch)
+ {
+ spriteBatch.Draw(pixel, new Rectangle(Root.Position.X, Root.Position.Y, 1, Root.Position.Height), Color.White);
+ spriteBatch.Draw(pixel, new Rectangle(Root.Position.X, Root.Position.Y, Root.Position.Width, 1), Color.White);
+ spriteBatch.Draw(pixel, new Rectangle(Root.Position.X, Root.Position.Bottom, Root.Position.Width, 1), Color.White);
+ spriteBatch.Draw(pixel, new Rectangle(Root.Position.Right, Root.Position.Y, 1, Root.Position.Height), Color.White);
+
+ if (Root.Children != null)
+ {
+ foreach (RectangleTreeNode child in Root.Children)
+ {
+ Draw(child, spriteBatch);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/DS2DEngine/src/SafeArea.cs b/DS2DEngine/src/SafeArea.cs
new file mode 100644
index 0000000..a9e0cd5
--- /dev/null
+++ b/DS2DEngine/src/SafeArea.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace DS2DEngine
+{
+ ///
+ /// Credit goes to Manders (on internet) for the basis of this code.
+ ///
+ /// A helper class to indicate the "safe area" for rendering to a
+ /// television. The inner 90% of the viewport is the "action safe" area,
+ /// meaning all important "action" should be shown within this area. The
+ /// inner 80% of the viewport is the "title safe area", meaning all text
+ /// and other key information should be shown within in this area. This
+ /// class shows the area that is not "title safe" in yellow, and the area
+ /// that is not "action safe" in red.
+ ///
+ public class SafeArea : DrawableGameComponent
+ {
+ SpriteBatch spriteBatch;
+ Texture2D tex; // Holds a 1x1 texture containing a single white texel
+ int width; // Viewport width
+ int height; // Viewport height
+ int dx; // 5% of width
+ int dy; // 5% of height
+ Color notActionSafeColor = new Color(255, 0, 0, 127); // Red, 50% opacity
+ Color notTitleSafeColor = new Color(255, 255, 0, 127); // Yellow, 50% opacity
+
+ public SafeArea(Game game)
+ : base(game)
+ {
+ //this.DrawOrder = (int)(DrawLevel.SafeArea);
+ game.Components.Add(this);
+ }
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ this.Enabled = false; // No need to call update for this
+ this.Visible = true;
+ }
+
+ protected override void LoadContent()
+ {
+ base.LoadContent();
+
+ spriteBatch = new SpriteBatch(base.GraphicsDevice);
+ tex = new Texture2D(base.GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
+ Color[] texData = new Color[1];
+ texData[0] = Color.White;
+ tex.SetData(texData);
+ width = base.GraphicsDevice.Viewport.Width;
+ height = base.GraphicsDevice.Viewport.Height;
+ dx = (int)(width * 0.05);
+ dy = (int)(height * 0.05);
+ }
+
+ public override void Draw(GameTime gameTime)
+ {
+ base.Draw(gameTime);
+
+ if (Consts.DEBUG_ShowSafeZones == true)
+ {
+ spriteBatch.Begin();
+
+ // Tint the non-action-safe area red
+ spriteBatch.Draw(tex, new Rectangle(0, 0, width, dy), notActionSafeColor);
+ spriteBatch.Draw(tex, new Rectangle(0, height - dy, width, dy), notActionSafeColor);
+ spriteBatch.Draw(tex, new Rectangle(0, dy, dx, height - 2 * dy), notActionSafeColor);
+ spriteBatch.Draw(tex, new Rectangle(width - dx, dy, dx, height - 2 * dy), notActionSafeColor);
+
+ // Tint the non-title-safe area yellow
+ spriteBatch.Draw(tex, new Rectangle(dx, dy, width - 2 * dx, dy), notTitleSafeColor);
+ spriteBatch.Draw(tex, new Rectangle(dx, height - 2 * dy, width - 2 * dx, dy), notTitleSafeColor);
+ spriteBatch.Draw(tex, new Rectangle(dx, 2 * dy, dx, height - 4 * dy), notTitleSafeColor);
+ spriteBatch.Draw(tex, new Rectangle(width - 2 * dx, 2 * dy, dx, height - 4 * dy), notTitleSafeColor);
+ spriteBatch.End();
+ }
+ }
+ }
+}
diff --git a/DS2DEngine/src/Screens/Screen.cs b/DS2DEngine/src/Screens/Screen.cs
new file mode 100644
index 0000000..dad0dd0
--- /dev/null
+++ b/DS2DEngine/src/Screens/Screen.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+
+namespace DS2DEngine
+{
+ public abstract class Screen : IDisposableObj
+ {
+ public bool UpdateIfCovered { get; set; }
+ public bool DrawIfCovered { get; set; }
+ public bool HandleInputIfCovered { get; set; }
+ public bool HasFocus {get;set;}
+ public PlayerIndex? ControllingPlayer { get; set; }
+ public ScreenManager ScreenManager { get; set; }
+ private bool m_isInitialized = false;
+ private bool m_isPaused = false;
+ private bool m_isContentLoaded = false;
+ private bool m_isDisposed = false;
+
+ public Screen()
+ {
+ UpdateIfCovered = false;
+ DrawIfCovered = false;
+ HasFocus = false;
+ }
+
+ public virtual void Initialize()
+ {
+ m_isInitialized = true;
+ }
+
+ public virtual void ReinitializeRTs()
+ {
+ }
+
+ public virtual void DisposeRTs()
+ {
+ }
+
+ public virtual void RefreshTextObjs() {}
+
+ public virtual void LoadContent()
+ {
+ m_isContentLoaded = true;
+ }
+
+ ///
+ /// Allows the screen to handle user input. Unlike Update, this method
+ /// is only called when the screen is active, and not when some other
+ /// screen has taken the focus.
+ ///
+ public virtual void HandleInput() { }
+
+ ///
+ /// Allows the screen to accept data from other screens.
+ ///
+ /// The list of data to be passed in.
+ public virtual void PassInData(List