1898 lines
95 KiB
C#
1898 lines
95 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
|
|
namespace SpriteLibrary
|
|
{
|
|
/// <summary>
|
|
/// An EventArgs that contains information about Sprites. Most of the Sprite events use
|
|
/// this SpriteEventArgs.
|
|
/// </summary>
|
|
public class SpriteEventArgs : EventArgs
|
|
{
|
|
/// <summary>
|
|
/// If another Sprite is involved in the event (Collision), than that Sprite is included here.
|
|
/// It will be null if no other Sprite is involved.
|
|
/// </summary>
|
|
public Sprite TargetSprite=null;
|
|
/// <summary>
|
|
/// The CollisionMethod used in the event. Currently, only rectangle collisions are used
|
|
/// </summary>
|
|
public SpriteCollisionMethod CollisionMethod = SpriteCollisionMethod.rectangle;
|
|
/// <summary>
|
|
/// For the CheckBeforeMove event, newlocation will be the location the sprite is trying
|
|
/// to move to. You can adjust the point (move it left, right, up, down) and it will affect
|
|
/// the placement of the sprite.
|
|
/// </summary>
|
|
public Point NewLocation = new Point(-1,-1);
|
|
/// <summary>
|
|
/// Used primarily in the CheckBeforeMove event. If you set cancel to true, then the move fails.
|
|
/// You can use this to keep a Sprite from going places where it ought not to go.
|
|
/// </summary>
|
|
public bool Cancel = false;
|
|
}
|
|
/// <summary>
|
|
/// A Sprite is an animated image that has a size, position, rotation, and possible vector
|
|
/// It tracks where in the animation sequence it is, can report colisions, etc. This SpriteController
|
|
/// draws, moves, and deals with most graphical aspects of the sprites for you.
|
|
/// </summary>
|
|
public class Sprite
|
|
{
|
|
int _ID = -1;
|
|
/// <summary>
|
|
/// The Sprite ID as specified by the sprite controller.
|
|
/// </summary>
|
|
public int ID { get { return _ID; } private set { _ID = value; } }
|
|
private bool SpriteHasInitialized = false;
|
|
/// <summary>
|
|
/// The name of the sprite. Use SetSpriteName(Name) to change this name. Most Named sprites
|
|
/// are used to define what a sprite is. Once you have created a named sprite, you usually use
|
|
/// <see cref="SpriteController.DuplicateSprite(String)"/> to clone the sprite for use. The basic rule of thumb is
|
|
/// to load your sprites from images once, and name the initial sprites. Then, when you go to use
|
|
/// those sprites, get duplicates of them. The reason for this is because it takes more processing time to initially
|
|
/// create the sprites than it takes to duplicate them.
|
|
/// </summary>
|
|
public string SpriteName { get; private set; }
|
|
/// <summary>
|
|
/// Set the opacity of the sprite. The value should be between 0 and 1. 1 is solid, 0 is transparent.
|
|
/// Sometimes you want to drag a sprite around the map, or show a sprite that "could be there." Setting
|
|
/// the sprite opacity is usually how you do that. One warning, however. The opacity value takes effect the
|
|
/// next time it is drawn. If the sprite is animating rapidly, it will take effect nearly emmediately. If
|
|
/// it is not animating, not moving, or just sitting there, then it may not take effect for quite some time.
|
|
/// </summary>
|
|
public float Opacity { get { return _opacity; } set { if (value <= 1 && value >= 0) _opacity = value; } }
|
|
private float _opacity = 1;
|
|
|
|
/// <summary>
|
|
/// Return the name of the sprite that this was duplicated from. A duplicated sprite will have
|
|
/// no name, but will have a SpriteOriginName.
|
|
/// </summary>
|
|
public string SpriteOriginName { get; private set; }
|
|
SmartImage MyImage;
|
|
private double SpeedAdjust = 1;
|
|
DateTime LastResetImage = DateTime.UtcNow;
|
|
int _FrameIndex = -1;
|
|
/// <summary>
|
|
/// This is the frame of the current animation sequence. You can use this if you need to figure out what frame index
|
|
/// to resume something at, or something like that.
|
|
/// </summary>
|
|
public int FrameIndex
|
|
{
|
|
get { return _FrameIndex; }
|
|
private set { _FrameIndex = value; }
|
|
} //We start out with -1 so we know we need to draw from the begining.
|
|
int _AnimationIndex = 0;
|
|
int AnimationNumberCount = 0;
|
|
|
|
/// <summary>
|
|
/// The final frame is the one that gets displayed once the animation has finished.
|
|
/// </summary>
|
|
private int _FinalFrameAfterAnimation = -1;
|
|
private bool SetToAnimateOnce = false;
|
|
private bool _AnimationDone = false;
|
|
/// <summary>
|
|
/// Report whether or not the animation has been completed. When you tell a Sprite to AnimateOnce,
|
|
/// this will report "false" until the animation sequence has been finished. At that time, the value
|
|
/// will be "True." The tricky bit is that this is a boolean. If you have not told a sprite to
|
|
/// animate once, it will always return "false." If a sprite is paused, this returns "false." The only
|
|
/// time this returns "true" is when you tell a sprite to animate once, or animate a few times, and those
|
|
/// times have completed. At that time, this will report "True". If you have a sprite with only one frame,
|
|
/// it may not look like it is "animating", but it is. It is simply animating that one frame over and over.
|
|
/// So, AnimationDone reports false, unless you have told it to animate_once.
|
|
/// </summary>
|
|
public bool AnimationDone { get { return _AnimationDone; } private set { _AnimationDone = value; } }
|
|
private bool ForceRedraw = false;
|
|
private bool NeedsDrawingAtEndOfTick = false; //We use this to say that we need to draw at end
|
|
private System.Windows.Vector SpriteVector;
|
|
/// <summary>
|
|
/// The movement speed of the sprite. To make a Sprite move, you need to set the MovementSpeed,
|
|
/// the direction (using
|
|
/// <see cref="SpriteLibrary.Sprite.SetSpriteDirection(System.Windows.Vector)"/>,
|
|
/// <see cref="SpriteLibrary.Sprite.SetSpriteDirectionToPoint(Point)"/>,
|
|
/// <see cref="SpriteLibrary.Sprite.SetSpriteDirectionRadians(double)"/>,
|
|
/// or <see cref="SpriteLibrary.Sprite.SetSpriteDirectionDegrees(double)"/>), and the
|
|
/// <see cref="SpriteLibrary.Sprite.AutomaticallyMoves"/> property.
|
|
/// The speed is calculated in pixels per amount of time. A higher number is faster than a lower number.
|
|
/// </summary>
|
|
public int MovementSpeed = 0; //Used in calculating when to move next.
|
|
private DateTime LastMovement = DateTime.UtcNow;
|
|
|
|
//For tracking if we are moving along a set of points
|
|
private List<Point> MovementDestinations = new List<Point>();
|
|
/// <summary>
|
|
/// Tells us if we are in the process of doing a MoveTo operation. This boolean should be the
|
|
/// opposite of SpriteReachedEndpoint, but that boolean is poorly named. This is usually the easier
|
|
/// one to use.
|
|
/// </summary>
|
|
public bool MovingToPoint { get { return _MovingToPoint; } private set { _MovingToPoint = value; } }
|
|
private bool _MovingToPoint = false;
|
|
|
|
/// <summary>
|
|
/// If we are trying to collide with a sprite, we store that sprite here.
|
|
/// </summary>
|
|
private Sprite MovingToSprite = null;
|
|
|
|
private bool _AutomaticallyMoves = false; //Does the sprite move in the direction it has been set?
|
|
/// <summary>
|
|
/// Determine if the sprite automatically moves (you need to give it a direction [using one of the
|
|
/// SetSpriteDirection functions] and speed [MovementSpeed = X] also)
|
|
/// </summary>
|
|
public bool AutomaticallyMoves
|
|
{
|
|
get { return _AutomaticallyMoves; }
|
|
set
|
|
{
|
|
_AutomaticallyMoves = value;
|
|
LastMovement = DateTime.UtcNow;
|
|
}
|
|
}
|
|
private int _Zvalue = 50;
|
|
/// <summary>
|
|
/// A number from 0 to 100. Default = 50. Higher numbers print on top of lower numbers. If you want a sprite to
|
|
/// always be drawn on top of other sprites, give it a number higher than 50. If you want a sprite to go under
|
|
/// other sprites, make its number lower than 50.
|
|
/// </summary>
|
|
public int Zvalue
|
|
{
|
|
get { return _Zvalue; }
|
|
set { _Zvalue = value; if (_Zvalue < 0) _Zvalue = 0; if (_Zvalue > 100) _Zvalue = 100; MySpriteController.SortSprites(); }
|
|
}
|
|
private bool PausedAnimation = false;
|
|
private bool PausedMovement = false;
|
|
private bool PausedEvents = false;
|
|
private DateTime PausedAnimationTime = DateTime.UtcNow;
|
|
private DateTime PausedMovementTime = DateTime.UtcNow;
|
|
/// <summary>
|
|
/// Determine if the sprite will automatically move outside the box. If not, it will hit the side of the box and stick
|
|
/// </summary>
|
|
public bool CannotMoveOutsideBox = false; //If set to true, it will not automatically move outside the picture box.
|
|
|
|
/// <summary>
|
|
/// Get or set the animation nimber. It is best to change the animation using ChangeAnimation.
|
|
/// It is safer.
|
|
/// </summary>
|
|
public int AnimationIndex
|
|
{
|
|
get { return _AnimationIndex; }
|
|
set { if (value > -1 && value < MyImage.AnimationCount) _AnimationIndex = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number of animations this sprite has
|
|
/// </summary>
|
|
public int AnimationCount
|
|
{
|
|
get { return MyImage.AnimationCount; }
|
|
}
|
|
|
|
//The location and size of the sprite
|
|
int xPositionOnImage = -1;
|
|
int yPositionOnImage = -1;
|
|
int xPositionOnPictureBox = -1;
|
|
int yPositionOnPictureBox = -1;
|
|
double xOnVector = -1;
|
|
double yOnVector = -1;
|
|
int Width = -1;
|
|
int Height = -1;
|
|
Rectangle MyRectangle { get { return new Rectangle(xPositionOnImage, yPositionOnImage, Width, Height); } }
|
|
bool _HasBeenDrawn = false;
|
|
/// <summary>
|
|
/// Report whether or not this Sprite has been drawn. If it has, then it needs to be erased at
|
|
/// some point in time.
|
|
/// </summary>
|
|
public bool HasBeenDrawn { get { return _HasBeenDrawn; } private set { _HasBeenDrawn = value; } }
|
|
/// <summary>
|
|
/// The sprite location as found on the base image. This is usually the easiest location to use.
|
|
/// </summary>
|
|
public Point BaseImageLocation { get { return new Point(xPositionOnImage, yPositionOnImage); } }
|
|
/// <summary>
|
|
/// The sprite location as found on the picture-box that this sprite is associated with. Used when dealing with mouse-clicks
|
|
/// </summary>
|
|
public Point PictureBoxLocation { get { return new Point(xPositionOnPictureBox, yPositionOnPictureBox); } }
|
|
/// <summary>
|
|
/// Return the size of the sprite in reference to the image on which it is drawn. To get the
|
|
/// size of the Sprite in relation to the PictureBox, use GetVisibleSize
|
|
/// </summary>
|
|
public Size GetSize { get { return new Size(Width, Height); } }
|
|
/// <summary>
|
|
/// Return the relative size of the Sprite in relation to the PictureBox. If the box has been
|
|
/// stretched or shrunk, that affects the visible size of the sprite.
|
|
/// </summary>
|
|
public Size GetVisibleSize { get { return new Size(VisibleWidth, VisibleHeight); } }
|
|
|
|
//This is the rotation of the item. If we change this, we will draw the sprite rotated.
|
|
int _Rotation = 0;
|
|
|
|
/// <summary>
|
|
/// Change the rotation of the sprite, using degrees. 0 degrees is to the right. 90 is up.
|
|
/// 180 left, 270 down. But, if your sprite was drawn facing up, then rotating it 90 degrees
|
|
/// will have it pointing left. The angle goes counter-clockwise. The image will be scaled
|
|
/// such that it continues to fit within the rectangle that it was originally in. This results
|
|
/// in a little bit of shrinking at times, but you should rarely notice that.
|
|
/// </summary>
|
|
public int Rotation { set { if (value >= 0 && value <= 360) _Rotation = value; } get { return _Rotation; } }
|
|
|
|
/// <summary>
|
|
/// Flip the image when it gets printed. If your sprite is walking left, flipping it will
|
|
/// make it look like it is going right.
|
|
/// This works great for many things. But, if your program is gobbling memory or CPU, you may need to
|
|
/// consider using <see cref="SpriteLibrary.Sprite.AddAnimation(int, bool, bool)">Sprite.AddAnimation</see>
|
|
/// </summary>
|
|
public bool MirrorHorizontally = false;
|
|
|
|
/// <summary>
|
|
/// Flip the image when it gets printed. If your sprite looks like it is facing up, doing
|
|
/// this will make it look like it faces down.
|
|
/// This works great for many things. But, if your program is gobbling memory or CPU, you may need to
|
|
/// consider using <see cref="SpriteLibrary.Sprite.AddAnimation(int, bool, bool)">Sprite.AddAnimation</see>
|
|
/// </summary>
|
|
public bool MirrorVertically = false;
|
|
|
|
internal SpriteController MySpriteController;
|
|
private bool _Destroying = false;
|
|
/// <summary>
|
|
/// If the Sprite is in the middle of being Destroyed, this is set to true. When a Sprite is
|
|
/// Destroyed, it needs to erase itself and do some house-cleaning before it actually vanishes.
|
|
/// During this time, you may not want to use it. It is always a good thing to verify a Sprite
|
|
/// is not in the middle of being destroyed before you do something important with it. To Destroy
|
|
/// a Sprite, use the Sprite.Destroy() function.
|
|
/// </summary>
|
|
public bool Destroying { get { return _Destroying; } }
|
|
|
|
/// <summary>
|
|
/// This is true unless we are using MoveTo(point) or MoveTo(list of points) to tell the sprite to move
|
|
/// from one place to the next. This boolean tells us if it has finished or not.
|
|
/// </summary>
|
|
public bool SpriteReachedEndPoint { get { return _SpriteReachedEndPoint; } internal set { _SpriteReachedEndPoint = value; } }
|
|
bool _SpriteReachedEndPoint = true;
|
|
|
|
/// <summary>
|
|
/// The visible Height as seen in the PictureBox. It may be stretched, or shrunk from the actual
|
|
/// image size.
|
|
/// </summary>
|
|
public int VisibleHeight { get { return MySpriteController.ReturnPictureBoxAdjustedHeight(Height); } }
|
|
/// <summary>
|
|
/// The visible width as seen in the PictureBox. The Sprite may be stretched or shrunk from the
|
|
/// actual image size.
|
|
/// </summary>
|
|
public int VisibleWidth { get { return MySpriteController.ReturnPictureBoxAdjustedWidth(Width); } }
|
|
|
|
/// <summary>
|
|
/// A Sprite can hold a payload. Use this to store extra information about the various Sprites. Health, Armor,
|
|
/// Shoot time, etc. But, to store information in the payload, you need to make a new class of SpritePayload. The syntax
|
|
/// for doing so is:
|
|
/// <code Lang="C#">
|
|
/// public class TankPayload : SpritePayload { public int Armor; public int Speed; }
|
|
/// </code>
|
|
/// You can access the payload and retrieve the various values.
|
|
/// </summary>
|
|
public SpritePayload payload = null; //The user can put anything they want here.
|
|
//We changed the payload from being an object. The Object was too vague. By making it a defined type,
|
|
//It helps with some level of type protection.
|
|
//public object payload = null; //The user can put anything they want here.
|
|
|
|
private List<Sprite> CollisionSprites = new List<Sprite>(); //The list of sprites that are colliding with this one
|
|
|
|
//*********************************
|
|
//**** Add event stubs **********
|
|
/// <summary>
|
|
/// A delegate that has a SpriteEventArgs instead of EventArgs. Used for most
|
|
/// of the Sprite events. This allows us to pass more information from sprite events than
|
|
/// a basic EventArgs allows for
|
|
/// </summary>
|
|
/// <param name="sender">The Sprite that triggers the event</param>
|
|
/// <param name="e">A SpriteEventArgs class which contains Sprite Event values</param>
|
|
public delegate void SpriteEventHandler(object sender, SpriteEventArgs e);
|
|
/// <summary>
|
|
/// This event happens right after the sprite is created. Use this to immediately set a
|
|
/// sprite to animate once or something like that.
|
|
/// </summary>
|
|
public event SpriteEventHandler SpriteInitializes = delegate { };
|
|
/// <summary>
|
|
/// This happens when the sprite hits the border of the picture-box.
|
|
/// Useful for when you want to have shots explode when they hit the side.
|
|
/// </summary>
|
|
public event SpriteEventHandler SpriteHitsPictureBox = delegate { };
|
|
/// <summary>
|
|
/// This happens when the sprite has exited the picture box. Useful when you want to
|
|
/// keep sprites from traveling on forever after exiting.
|
|
/// </summary>
|
|
public event SpriteEventHandler SpriteExitsPictureBox = delegate { };
|
|
/// <summary>
|
|
/// Only used when you tell an animation to animate once. At the end of the animation,
|
|
/// this function fires off.
|
|
/// </summary>
|
|
public event SpriteEventHandler SpriteAnimationComplete = delegate { };
|
|
/// <summary>
|
|
/// This happens when two sprites hit each-other. The SpriteEventArgs that is returned
|
|
/// contains the sprite that this sprite hits.
|
|
/// </summary>
|
|
public event SpriteEventHandler SpriteHitsSprite = delegate { };
|
|
/// <summary>
|
|
/// This event fires off before a sprite is drawn. Use it if you have constraints. You
|
|
/// can change the location or cancel the move entirely.
|
|
/// </summary>
|
|
public event SpriteEventHandler CheckBeforeMove = delegate { };
|
|
/// <summary>
|
|
/// This event happens when someone clicks on the sprite (on the rectangle in which the sprite is).
|
|
/// If you want the event to fire off only when someone clicks on the visible part of the sprite,
|
|
/// use ClickTransparent instead.
|
|
/// </summary>
|
|
public event SpriteEventHandler Click = delegate { };
|
|
/// <summary>
|
|
/// This event happens when someone clicks on the sprite (on the sprite image itself).
|
|
/// If the sprite is sometimes hidden, but you want the click to work even if it is not
|
|
/// visible at that instant, use Click instead.
|
|
/// </summary>
|
|
public event SpriteEventHandler ClickTransparent = delegate { };
|
|
/// <summary>
|
|
/// This event happens when the mouse moves over the sprite, and then pauses. We use the hover timing from the
|
|
/// parent form.
|
|
/// </summary>
|
|
public event SpriteEventHandler MouseHover = delegate { };
|
|
/// <summary>
|
|
/// When the mouse moves over the sprite. Use this for a menu, when you want the menu item to glow when the
|
|
/// mouse is over the menu item sprite.
|
|
/// </summary>
|
|
public event SpriteEventHandler MouseEnter = delegate { };
|
|
/// <summary>
|
|
/// When the mouse moves off the sprite. Use this for a menu, when you want the menu item to stop glowing when
|
|
/// the mouse moves away from the menu item sprite.
|
|
/// </summary>
|
|
public event SpriteEventHandler MouseLeave = delegate { };
|
|
/// <summary>
|
|
/// This event happens when the mouse moves over a non-transparent portion of the sprite, and then pauses.
|
|
/// We use the hover timing from the parent form.
|
|
/// </summary>
|
|
public event SpriteEventHandler MouseHoverTransparent = delegate { };
|
|
/// <summary>
|
|
/// When the mouse moves over a non-transparent portoin of the sprite. Use this for a menu, when you want the
|
|
/// menu item to glow when the mouse is over the menu item sprite.
|
|
/// </summary>
|
|
public event SpriteEventHandler MouseEnterTransparent = delegate { };
|
|
/// <summary>
|
|
/// When the mouse moves off the non-transparent portion of the sprite. Use this for a menu, when you want the
|
|
/// menu item to stop glowing when
|
|
/// the mouse moves away from the menu item sprite.
|
|
/// </summary>
|
|
public event SpriteEventHandler MouseLeaveTransparent = delegate { };
|
|
/// <summary>
|
|
/// When the frame of an animation changes. If you want to have something happen every time
|
|
/// the foot of your monster comes down, when the swing of your sword is at certain points, etc.
|
|
/// Check to see that the Animaton and FrameIndex are what you expect them to be.
|
|
/// </summary>
|
|
public event SpriteEventHandler SpriteChangesAnimationFrames = delegate { };
|
|
/// <summary>
|
|
/// An event for when you tell a Sprite to MoveTo(Point) a specific point, or, when you
|
|
/// tell the Sprite to MoveTo(list of points). When the Sprite has reached the final destination,
|
|
/// the Sprite fires off this event.
|
|
/// </summary>
|
|
public event SpriteEventHandler SpriteArrivedAtEndPoint = delegate { };
|
|
/// <summary>
|
|
/// When you tell a sprite to MoveTo(list of points), this fires off every time it gets to
|
|
/// one of the points. When it gets to the final point, only the SpriteAtEndPoint event fires off.
|
|
/// </summary>
|
|
public event SpriteEventHandler SpriteArrivedAtWaypoint = delegate { };
|
|
|
|
/// <summary>
|
|
/// The Sprite has just been told to be destroyed. You might want to do some cleanup.
|
|
/// If you need to destroy some payload data, or tell something to cleanup after the sprite
|
|
/// this is where to do that.
|
|
/// </summary>
|
|
public event SpriteEventHandler SpriteBeingDestroyed = delegate { };
|
|
|
|
|
|
// **********************************************************
|
|
// ************* Start of Sprite Code **********
|
|
// *************************************
|
|
|
|
|
|
/// <summary>
|
|
/// Generate a new sprite. It takes the image and the width and height. If there are multiple images of that width
|
|
/// and height in the image, an animation is created.
|
|
/// </summary>
|
|
/// <param name="Controller">The sprite controller that manages this sprite</param>
|
|
/// <param name="SpriteImage">The image we pull the animation from</param>
|
|
/// <param name="width">The width of one animation frame</param>
|
|
/// <param name="height">The height of one animation frame</param>
|
|
public Sprite(SpriteController Controller, Image SpriteImage, int width, int height)
|
|
{
|
|
MySpriteController = Controller;
|
|
ID = MySpriteController.SpriteCount;
|
|
Width = width;
|
|
Height = height;
|
|
MyImage = new SmartImage(MySpriteController, SpriteImage);
|
|
MySpriteController.AddSprite(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate a new sprite. It takes the image and the width and height. If there are multiple images of that width
|
|
/// and height in the image, an animation is created.
|
|
/// </summary>
|
|
/// <param name="Controller">The sprite controller that manages this sprite</param>
|
|
/// <param name="SpriteImage">The image we pull the animation from</param>
|
|
/// <param name="SpriteSize">The size of the animation frame</param>
|
|
public Sprite(SpriteController Controller, Image SpriteImage,Size SpriteSize)
|
|
{
|
|
MySpriteController = Controller;
|
|
ID = MySpriteController.SpriteCount;
|
|
Width = SpriteSize.Width;
|
|
Height = SpriteSize.Height;
|
|
MyImage = new SmartImage(MySpriteController, SpriteImage);
|
|
MySpriteController.AddSprite(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate a new single-frame sprite from the specified image.
|
|
/// </summary>
|
|
/// <param name="Controller">The sprite controller that manages this sprite</param>
|
|
/// <param name="SpriteImage">The image we pull the animation from</param>
|
|
public Sprite(SpriteController Controller, Image SpriteImage)
|
|
{
|
|
MySpriteController = Controller;
|
|
ID = MySpriteController.SpriteCount;
|
|
Width = SpriteImage.Width;
|
|
Height = SpriteImage.Height;
|
|
MyImage = new SmartImage(MySpriteController, SpriteImage);
|
|
MySpriteController.AddSprite(this);
|
|
}
|
|
/// <summary>
|
|
/// Generate a new sprite. It takes a width, height, and the duration in Milliseconds for each frame
|
|
/// </summary>
|
|
/// <param name="Controller">The sprite controller</param>
|
|
/// <param name="SpriteImage">The image we pull the animations from</param>
|
|
/// <param name="width">The width of one animation frame</param>
|
|
/// <param name="height">the height of one animation frame</param>
|
|
/// <param name="durationInMilliseconds">The number of milliseconds each frame is shown for as it animates.</param>
|
|
public Sprite(SpriteController Controller, Image SpriteImage, int width, int height, int durationInMilliseconds)
|
|
{
|
|
MySpriteController = Controller;
|
|
ID = MySpriteController.SpriteCount;
|
|
Width = width;
|
|
Height = height;
|
|
MyImage = new SmartImage(MySpriteController, SpriteImage, width, height, durationInMilliseconds);
|
|
MySpriteController.AddSprite(this);
|
|
}
|
|
/// <summary>
|
|
/// Create a Sprite from an animation image, specifying the number of consecutive
|
|
/// frames to grab.
|
|
/// </summary>
|
|
/// <param name="Start">A point on the specified image where we begin grabbing frames</param>
|
|
/// <param name="Controller">The Sprite controller we are associating the sprite with</param>
|
|
/// <param name="SpriteImage">An image that we grab the frames from</param>
|
|
/// <param name="width">The width of one frame</param>
|
|
/// <param name="height">The height of one frame</param>
|
|
/// <param name="duration">The number of milliseconds each frame is displayed for</param>
|
|
/// <param name="Count">The number of frames to grab as a part of this animation</param>
|
|
public Sprite(Point Start, SpriteController Controller, Image SpriteImage, int width, int height, int duration, int Count)
|
|
{
|
|
MySpriteController = Controller;
|
|
ID = MySpriteController.SpriteCount;
|
|
Width = width;
|
|
Height = height;
|
|
MyImage = new SmartImage(Start, MySpriteController, SpriteImage, width, height, duration, Count);
|
|
MySpriteController.AddSprite(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a Sprite that is based off of the specified sprite. Clone the Sprite except that
|
|
/// we set SpriteName = "" and OrigSpriteName = the OldSprite.SpriteName. That way we know that
|
|
/// the sprite was duplicated from the original, and we can still distinguish the original from
|
|
/// the duplicate.
|
|
/// </summary>
|
|
/// <param name="OldSprite">The Sprite to make a copy of</param>
|
|
/// <param name="RetainName">If we want to set this sprite name to be that of the original. This is a terrible idea. Never do it.</param>
|
|
public Sprite(Sprite OldSprite, bool RetainName = false)
|
|
{
|
|
MySpriteController = OldSprite.MySpriteController;
|
|
ID = MySpriteController.SpriteCount;
|
|
Width = OldSprite.Width;
|
|
Height = OldSprite.Height;
|
|
MyImage = OldSprite.MyImage;
|
|
MovementSpeed = OldSprite.MovementSpeed;
|
|
SpriteOriginName = OldSprite.SpriteName;
|
|
|
|
//duplicate any eventhandlers we may have added to the one being cloned.
|
|
SpriteHitsPictureBox += OldSprite.SpriteHitsPictureBox;
|
|
SpriteExitsPictureBox += OldSprite.SpriteExitsPictureBox;
|
|
SpriteHitsSprite += OldSprite.SpriteHitsSprite;
|
|
SpriteAnimationComplete += OldSprite.SpriteAnimationComplete;
|
|
SpriteInitializes += OldSprite.SpriteInitializes;
|
|
CheckBeforeMove += OldSprite.CheckBeforeMove;
|
|
Click += OldSprite.Click;
|
|
ClickTransparent += OldSprite.ClickTransparent;
|
|
SpriteChangesAnimationFrames += OldSprite.SpriteChangesAnimationFrames;
|
|
SpriteArrivedAtEndPoint += OldSprite.SpriteArrivedAtEndPoint;
|
|
SpriteArrivedAtWaypoint += OldSprite.SpriteArrivedAtWaypoint;
|
|
SpriteBeingDestroyed += OldSprite.SpriteBeingDestroyed;
|
|
MouseEnter += OldSprite.MouseEnter;
|
|
MouseHover += OldSprite.MouseHover;
|
|
MouseLeave += OldSprite.MouseLeave;
|
|
|
|
if (RetainName)
|
|
SpriteName = OldSprite.SpriteName;
|
|
MySpriteController.AddSprite(this);
|
|
}
|
|
|
|
|
|
// *******************
|
|
|
|
/// <summary>
|
|
/// Give this sprite a name. This way we can make a duplicate of it by specifying the name
|
|
/// </summary>
|
|
/// <param name="Name">A string that represents the new name of the sprite</param>
|
|
public void SetName(string Name)
|
|
{
|
|
SpriteName = Name;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add another animation to an existing Sprite. After you add animations, you can use
|
|
/// ChangeAnimation to select which animation you want the specified sprite to show.
|
|
/// For example, you may want to have Animation 0 be a guy walking left, and animation 1 is
|
|
/// that same guy walking right. Because we do not specify the number of frames, it starts
|
|
/// at the top-left corner and grabs as many frames as it can from the image.
|
|
/// </summary>
|
|
/// <param name="SpriteImage">The animation image to grab the frames from</param>
|
|
/// <param name="width">The width of each frame</param>
|
|
/// <param name="height">The height of each frame</param>
|
|
public void AddAnimation(Image SpriteImage, int width, int height)
|
|
{
|
|
int duration = GetAnimationSpeed(0);
|
|
MyImage.AddAnimation(SpriteImage, width, height, duration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add another animation to an existing Sprite. After you add animations, you can use
|
|
/// ChangeAnimation to select which animation you want the specified sprite to show.
|
|
/// For example, you may want to have Animation 0 be a guy walking left, and animation 1 is
|
|
/// that same guy walking right. Because we do not specify the number of frames, it starts
|
|
/// at the top-left corner and grabs as many frames as it can from the image.
|
|
/// </summary>
|
|
/// <param name="SpriteImage">The animation image to grab the frames from</param>
|
|
/// <param name="SpriteSize">The size of each frame</param>
|
|
public void AddAnimation(Image SpriteImage, Size SpriteSize)
|
|
{
|
|
int duration = GetAnimationSpeed(0);
|
|
MyImage.AddAnimation(SpriteImage, SpriteSize.Width, SpriteSize.Height, duration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add another animation to an existing Sprite. After you add animations, you can use
|
|
/// ChangeAnimation to select which animation you want the specified sprite to show.
|
|
/// For example, you may want to have Animation 0 be a guy walking left, and animation 1 is
|
|
/// that same guy walking right. Because we do not specify the number of frames, it starts
|
|
/// at the top-left corner and grabs as many frames as it can from the image.
|
|
/// </summary>
|
|
/// <param name="SpriteImage">The animation image to grab the frames from</param>
|
|
public void AddAnimation(Image SpriteImage)
|
|
{
|
|
int duration = GetAnimationSpeed(0);
|
|
MyImage.AddAnimation(SpriteImage, SpriteImage.Width, SpriteImage.Height, duration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add another animation to an existing Sprite. After you add animations, you can use
|
|
/// ChangeAnimation to select which animation you want the specified sprite to show.
|
|
/// For example, you may want to have Animation 0 be a guy walking left, and animation 1 is
|
|
/// that same guy walking right. Because we do not specify the number of frames, it starts
|
|
/// at the top-left corner and grabs as many frames as it can from the image.
|
|
/// </summary>
|
|
/// <param name="SpriteImage">The animation image to grab the frames from</param>
|
|
/// <param name="duration">The duration the single frame uses before refreshing. 1000 is a good number.</param>
|
|
public void AddAnimation(Image SpriteImage, int duration)
|
|
{
|
|
MyImage.AddAnimation(SpriteImage, SpriteImage.Width, SpriteImage.Height, duration);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Add another animation to an existing Sprite. After you add animations, you can use
|
|
/// ChangeAnimation to select which animation you want the specified sprite to show.
|
|
/// For example, you may want to have Animation 0 be a guy walking left, and animation 1 is
|
|
/// that same guy walking right. Because we do not specify the number of frames, it starts
|
|
/// at the top-left corner and grabs as many frames as it can from the image.
|
|
/// </summary>
|
|
/// <param name="SpriteImage">The animation image to grab the frames from</param>
|
|
/// <param name="width">The width of each frame</param>
|
|
/// <param name="height">The height of each frame</param>
|
|
/// <param name="duration">The time in milliseconds we use for each frame</param>
|
|
public void AddAnimation(Image SpriteImage, int width, int height, int duration)
|
|
{
|
|
MyImage.AddAnimation(SpriteImage, width, height, duration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add another animation to an existing Sprite. After you add animations, you can use
|
|
/// ChangeAnimation to select which animation you want the specified sprite to show.
|
|
/// For example, you may want to have Animation 0 be a guy walking left, and animation 1 is
|
|
/// that same guy walking right. Because we do not specify the number of frames, it starts
|
|
/// at the top-left corner and grabs as many frames as it can from the image.
|
|
/// </summary>
|
|
/// <param name="SpriteImage">The animation image to grab the frames from</param>
|
|
/// <param name="width">The width of each frame</param>
|
|
/// <param name="height">The height of each frame</param>
|
|
/// <param name="duration">The time in milliseconds we use for each frame</param>
|
|
/// <param name="Count">The number of frames we grab from the image</param>
|
|
/// <param name="Start">The starting position on the Image where we grab the first frame</param>
|
|
public void AddAnimation(Point Start, Image SpriteImage, int width, int height, int duration, int Count)
|
|
{
|
|
MyImage.AddAnimation(Start, SpriteImage, width, height, duration, Count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Duplicate an animation, except rotated by the specified number of degrees. For example, if you have
|
|
/// a single animation (0), and you want to rotate it by 90 degrees, it will create animation 1 with that
|
|
/// rotation to it. In the long haul, generating a few rotated animations is less memory intensive than
|
|
/// rotating it on demand.
|
|
/// </summary>
|
|
/// <param name="AnimationToCopy">An integer value specifying the animation to duplicate</param>
|
|
/// <param name="RotationDegrees">The amount of counter-clockwise rotation to add</param>
|
|
public void AddAnimation(int AnimationToCopy, int RotationDegrees)
|
|
{
|
|
if (AnimationToCopy < 0) return;
|
|
if (AnimationToCopy > MyImage.AnimationCount) return;
|
|
MyImage.AddAnimation(AnimationToCopy, RotationDegrees);
|
|
}
|
|
/// <summary>
|
|
/// Duplicate an animation, except rotated by the specified number of degrees. For example, if you have
|
|
/// a single animation (0), and you want to rotate it by 90 degrees, it will create animation 1 with that
|
|
/// rotation to it. In the long haul, generating a few rotated animations is less memory intensive than
|
|
/// rotating it on demand using the <see cref="Sprite.MirrorHorizontally"/> or <see cref="Sprite.MirrorVertically"/> booleans.
|
|
/// </summary>
|
|
/// <param name="AnimationToCopy">An integer value specifying the animation to duplicate</param>
|
|
/// <param name="MirrorHorizontal">A boolean, stating if we should mirror horizontally</param>
|
|
/// <param name="MirrorVertical">A boolean, stating if we should mirror vertically</param>
|
|
public void AddAnimation(int AnimationToCopy, bool MirrorHorizontal, bool MirrorVertical)
|
|
{
|
|
if (AnimationToCopy < 0) return;
|
|
if (AnimationToCopy > MyImage.AnimationCount) return;
|
|
MyImage.AddAnimation(AnimationToCopy, MirrorHorizontal, MirrorVertical);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a new animation, but do it just once. You can use AnimateJustAFewTimes(1) to the same effect.
|
|
/// Or, you can use AnimateJustAFewTimes with a different number. The SpriteAnimationComplete event will
|
|
/// fire off when the animation completes. The variable, Sprite.AnimationDone will be true once the
|
|
/// animation finishes animating.
|
|
/// </summary>
|
|
/// <param name="AnimationFrameToEndOn">Once the animation has finished, display this animation frame.
|
|
/// -1, or any number that is not an actual frame, will show the last frame of the animation.</param>
|
|
/// <param name="WhichAnimation">The animation index you want to use</param>
|
|
public void AnimateOnce(int WhichAnimation, int AnimationFrameToEndOn = -1)
|
|
{
|
|
AnimateJustAFewTimes(WhichAnimation, 1);
|
|
_FinalFrameAfterAnimation = AnimationFrameToEndOn;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a new animation. It will complete the animation the number of times you specify.
|
|
/// For example, if your sprite is walking, and one animation is one step, specifying 4 here
|
|
/// will result in your sprite taking 4 steps and then the animation stops. You will want
|
|
/// to make sure you are checking for when the animation stops, using the SpriteAnimationComplete event,
|
|
/// checking the Sprite.AnimationDone flag.
|
|
/// </summary>
|
|
/// <param name="WhichAnimation">The animation index you want to use</param>
|
|
/// <param name="HowManyAnimations">The number of animations to do before it stops</param>
|
|
/// <param name="AnimationFrameToEndOn">Once the animation has finished, display this animation frame.
|
|
/// -1, or any number that is not an actual frame, will show the last frame of the animation.</param>
|
|
public void AnimateJustAFewTimes(int WhichAnimation, int HowManyAnimations, int AnimationFrameToEndOn = -1)
|
|
{
|
|
if (WhichAnimation > -1 && WhichAnimation < MyImage.AnimationCount)
|
|
{ //We have a valid animation. Do it once
|
|
SetToAnimateOnce = true;
|
|
AnimationNumberCount = HowManyAnimations;
|
|
//Console.WriteLine("Setting to animate: " + HowManyAnimations);
|
|
AnimationDone = false;
|
|
AnimationIndex = WhichAnimation;
|
|
FrameIndex = 0;
|
|
ForceRedraw = true;
|
|
LastResetImage = DateTime.UtcNow;
|
|
_FinalFrameAfterAnimation = AnimationFrameToEndOn;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Start a new animation index from scratch
|
|
/// </summary>
|
|
/// <param name="WhichAnimation">The animation index you want to use</param>
|
|
/// <param name="StartFrame">The first frame you want to start the animation at.</param>
|
|
public void ChangeAnimation(int WhichAnimation, int StartFrame = 0)
|
|
{
|
|
if (WhichAnimation > -1 && WhichAnimation < MyImage.AnimationCount)
|
|
{ //We have a valid animation. Do it once
|
|
SetToAnimateOnce = false;
|
|
AnimationDone = false;
|
|
AnimationIndex = WhichAnimation;
|
|
FrameIndex = 0;
|
|
int NumFrames = MyImage.AnimationFrameCount(WhichAnimation);
|
|
if (StartFrame >= 0 && StartFrame <= NumFrames)
|
|
FrameIndex = StartFrame;
|
|
ForceRedraw = true;
|
|
LastResetImage = DateTime.UtcNow; //start from this second.
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Change the animation speed of a particular animation. This looks at the first frame
|
|
/// and compares that frame to the speed specified. It adjusts all the animations by the
|
|
/// same percentage.
|
|
/// </summary>
|
|
/// <param name="WhichAnimation">The integer representing the animation to change</param>
|
|
/// <param name="newSpeed">The speed in milliseconds for the new animation</param>
|
|
public void ChangeAnimationSpeed(int WhichAnimation, int newSpeed)
|
|
{
|
|
if (WhichAnimation > -1 && WhichAnimation < MyImage.AnimationCount)
|
|
{ //We have a valid animation
|
|
TimeSpan CurrentTS = MyImage.GetCurrentDuration(WhichAnimation, 0);
|
|
double Current = CurrentTS.TotalMilliseconds;
|
|
double New = 1; //This is 1/2 times slower. 1 is the actual speed...
|
|
if ((int)Current != newSpeed)
|
|
{
|
|
//We have a different speed
|
|
if (Current == 0) Current = 10000; //A duration of zero should not rotate
|
|
if (newSpeed == 0)
|
|
New = 1;
|
|
else
|
|
{
|
|
New = Current / newSpeed; //We have a ratio.
|
|
}
|
|
}
|
|
//Console.WriteLine("New SpeedAdjust: " + New.ToString());
|
|
SpeedAdjust = New;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Change the animation speed of a specific frame. Beware. This affects every sprite using this frame
|
|
/// </summary>
|
|
/// <param name="WhichAnimation">The index of the animation</param>
|
|
/// <param name="WhichFrame">The index of the frame within the animation</param>
|
|
/// <param name="newSpeed">The new frame duration in milliseconds</param>
|
|
public void ChangeFrameAnimationSpeed(int WhichAnimation, int WhichFrame, int newSpeed)
|
|
{
|
|
if (WhichAnimation > -1 && WhichAnimation < MyImage.AnimationCount)
|
|
{
|
|
Animation tAnimation = MyImage.getAnimation(WhichAnimation);
|
|
if (WhichFrame < 0 || WhichFrame >= tAnimation.Frames.Count) return;
|
|
tAnimation.Frames[WhichFrame].Duration = TimeSpan.FromMilliseconds(newSpeed);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the animation speed of a single frame.
|
|
/// </summary>
|
|
/// <param name="WhichAnimation">The animation we are looking at</param>
|
|
/// <param name="WhichFrame">The index of the frame we wish to get the speed of</param>
|
|
/// <returns>-1 if either index is out of range. Otherwise, return the total milliseconds of the specified frame.</returns>
|
|
public int GetFrameAnimationSpeed(int WhichAnimation, int WhichFrame)
|
|
{
|
|
if (WhichAnimation > -1 && WhichAnimation < MyImage.AnimationCount)
|
|
{
|
|
Animation tAnimation = MyImage.getAnimation(WhichAnimation);
|
|
if (WhichFrame < 0 || WhichFrame >= tAnimation.Frames.Count) return -1;
|
|
return (int)tAnimation.Frames[WhichFrame].Duration.TotalMilliseconds;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the animation speed of this particualar animation of the sprite.
|
|
/// </summary>
|
|
/// <param name="WhichAnimation">The animation we are looking at</param>
|
|
/// <returns>The speed which was set. The speed is calculated in pixels per amount of time. A higher number is faster than a lower number</returns>
|
|
public int GetAnimationSpeed(int WhichAnimation)
|
|
{
|
|
if (WhichAnimation > -1 && WhichAnimation < MyImage.AnimationCount)
|
|
{
|
|
TimeSpan CurrentTS = MyImage.GetCurrentDuration(WhichAnimation, 0);
|
|
return (int)(CurrentTS.TotalMilliseconds / SpeedAdjust);
|
|
}
|
|
return -1;
|
|
}
|
|
private void EraseMe(bool SkipInvalidate = false)
|
|
{
|
|
EraseMe(MyRectangle.Location, SkipInvalidate);
|
|
}
|
|
private void EraseMe(Point tLocation, bool SkipInvalidate = false)
|
|
{
|
|
Image ChangedImage = MySpriteController.BackgroundImage; //This is the image itself. Changes to this affect what is displayed
|
|
Image OriginalImage = MySpriteController.OriginalImage;
|
|
System.Drawing.Rectangle oldPlace = new System.Drawing.Rectangle(tLocation.X, tLocation.Y, Width, Height);
|
|
|
|
lock(ChangedImage) lock(OriginalImage)
|
|
{
|
|
using (Graphics gx = Graphics.FromImage(ChangedImage))
|
|
{
|
|
gx.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
|
|
gx.DrawImage(OriginalImage, oldPlace, oldPlace, GraphicsUnit.Pixel);
|
|
}
|
|
}
|
|
if (!SkipInvalidate)
|
|
MySpriteController.Invalidate(oldPlace);
|
|
//Tell any sprite we overlap with that it needs to redraw
|
|
foreach (Sprite CollidesWith in CollisionSprites.ToList())
|
|
{
|
|
CollidesWith.RedrawMeAndAllICollideWith();
|
|
}
|
|
}
|
|
|
|
private void RedrawMeAndAllICollideWith()
|
|
{
|
|
if (NeedsDrawingAtEndOfTick) return; //we are already doing this
|
|
NeedsDrawingAtEndOfTick = true;
|
|
//if (Opacity != 1)
|
|
EraseMe(true);
|
|
//foreach (Sprite CollidesWith in CollisionSprites)
|
|
//{
|
|
// CollidesWith.RedrawMeAndAllICollideWith();
|
|
//}
|
|
}
|
|
|
|
private void DrawMe(bool SkipInvalidate = false, bool ActuallyDraw = false)
|
|
{
|
|
DrawMe(MyRectangle.Location, SkipInvalidate, ActuallyDraw);
|
|
}
|
|
private void DrawMe(Point tLocation, bool SkipInvalidate = false, bool ActuallyDraw = false)
|
|
{
|
|
if (ActuallyDraw)
|
|
{
|
|
lock(MySpriteController.BackgroundImage)
|
|
{
|
|
Image ChangedImage = MySpriteController.BackgroundImage; //This is the image itself. Changes to this affect what is displayed
|
|
System.Drawing.Rectangle ThePlace = new System.Drawing.Rectangle(tLocation.X, tLocation.Y, MyRectangle.Width, MyRectangle.Height);
|
|
|
|
ColorMatrix matrix = new ColorMatrix();
|
|
matrix.Matrix33 = Opacity;
|
|
ImageAttributes attributes = new ImageAttributes();
|
|
attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
|
|
|
|
Image MeImage = MyImage.GetImage(AnimationIndex, FrameIndex, GetVisibleSize);
|
|
if (MirrorHorizontally || MirrorVertically)
|
|
{
|
|
MeImage = (Bitmap)MeImage.Clone();
|
|
if (MirrorHorizontally) MeImage.RotateFlip(RotateFlipType.RotateNoneFlipX);
|
|
if (MirrorVertically) MeImage.RotateFlip(RotateFlipType.RotateNoneFlipY);
|
|
}
|
|
GraphicsUnit tUnit = GraphicsUnit.Pixel;
|
|
if (Rotation != 0 && MeImage != null)
|
|
{
|
|
Image rotatedImage = new Bitmap(MeImage.Width, MeImage.Height);
|
|
using (Graphics g = Graphics.FromImage(rotatedImage))
|
|
{
|
|
g.TranslateTransform(MeImage.Width / 2, MeImage.Height / 2); //set the rotation point as the center into the matrix
|
|
g.RotateTransform(Rotation); //rotate
|
|
g.TranslateTransform(-MeImage.Width / 2, -MeImage.Height / 2); //restore rotation point into the matrix
|
|
g.DrawImage(MeImage, MeImage.GetBounds(ref tUnit), rotatedImage.GetBounds(ref tUnit), tUnit); //draw the image on the new bitmap
|
|
|
|
}
|
|
//GC.Collect();
|
|
MeImage = rotatedImage;
|
|
}
|
|
if (MeImage != null)
|
|
{
|
|
//g.DrawImage(MeImage, new Rectangle(0, 0, MeImage.Width, MeImage.Height), 0, 0, rotatedImage.Width, rotatedImage.Height, tUnit, attributes); //draw the image on the new bitmap
|
|
//Graphics.FromImage(ChangedImage).DrawImage(MeImage, ThePlace, MeImage.GetBounds(ref tUnit), tUnit);
|
|
Graphics.FromImage(ChangedImage).DrawImage(MeImage, ThePlace, 0, 0, MeImage.Width, MeImage.Height, tUnit, attributes); //draw the image on the new bitmap
|
|
//Pen tmpPen = new Pen(Color.Black);
|
|
//Graphics.FromImage(ChangedImage).DrawRectangle(tmpPen, MyRectangle);
|
|
MySpriteController.Invalidate(MyRectangle);
|
|
}
|
|
NeedsDrawingAtEndOfTick = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NeedsDrawingAtEndOfTick = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Actually draw the Sprite. Never use this. It is used by the SpriteController
|
|
/// </summary>
|
|
internal void ActuallyDraw()
|
|
{
|
|
if (NeedsDrawingAtEndOfTick)
|
|
DrawMe(false, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Put the Sprite at a specified location, using the dimentions of the BackgroundImage.
|
|
/// Unless you are using coordinates you have gotten from a mouse-click, this is how you want
|
|
/// to place a Sprite somewhere. It is the easiest way to track things. But, if you are
|
|
/// doing something using mouse-click coordinates, you want to use PutPictureBoxLocation
|
|
/// </summary>
|
|
/// <param name="NewLocationOnImage">The new point on the Image</param>
|
|
public void PutBaseImageLocation(Point NewLocationOnImage)
|
|
{
|
|
Point PointOnPictureBox = MySpriteController.ReturnPictureBoxAdjustedPoint(NewLocationOnImage);
|
|
if (MyRectangle.Location != NewLocationOnImage || !HasBeenDrawn)
|
|
{
|
|
if (HasBeenDrawn)
|
|
EraseMe();
|
|
xPositionOnImage = NewLocationOnImage.X;
|
|
yPositionOnImage = NewLocationOnImage.Y;
|
|
xPositionOnPictureBox = PointOnPictureBox.X;
|
|
yPositionOnPictureBox = PointOnPictureBox.Y;
|
|
xOnVector = NewLocationOnImage.X;
|
|
yOnVector = NewLocationOnImage.Y;
|
|
DrawMe(NewLocationOnImage);
|
|
HasBeenDrawn = true;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Put the Sprite at a specified location, using the dimentions of the BackgroundImage.
|
|
/// Unless you are using coordinates you have gotten from a mouse-click, this is how you want
|
|
/// to place a Sprite somewhere. It is the easiest way to track things. But, if you are
|
|
/// doing something using mouse-click coordinates, you want to use PutPictureBoxLocation
|
|
/// </summary>
|
|
/// <param name="X">The X location on the background image</param>
|
|
/// <param name="Y">the Y location on the background image</param>
|
|
public void PutBaseImageLocation(double X, double Y)
|
|
{
|
|
Point NewLocation = new Point((int)X, (int)Y);
|
|
Point actualPoint = MySpriteController.ReturnPictureBoxAdjustedPoint(NewLocation);
|
|
if (MyRectangle.Location != NewLocation || !HasBeenDrawn)
|
|
{
|
|
if (HasBeenDrawn)
|
|
EraseMe();
|
|
xPositionOnImage = NewLocation.X;
|
|
yPositionOnImage = NewLocation.Y;
|
|
xPositionOnPictureBox = actualPoint.X;
|
|
yPositionOnPictureBox = actualPoint.Y;
|
|
xOnVector = X;
|
|
yOnVector = Y;
|
|
DrawMe();
|
|
HasBeenDrawn = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Put the Sprite at a specified location, using the dimentions of the PictureBox.
|
|
/// You want to use this if you got your X/Y position from a mouse-click. Otherwise,
|
|
/// this is the harder way to track things, particularly if your window can resize. Use
|
|
/// PutBaseImageLocation instead.
|
|
/// </summary>
|
|
/// <param name="NewLocationOnPictureBox">A point on the PictureBox</param>
|
|
public void PutPictureBoxLocation(Point NewLocationOnPictureBox)
|
|
{
|
|
//We adjust to the location
|
|
Point actualPoint = MySpriteController.ReturnPointAdjustedForImage(NewLocationOnPictureBox);
|
|
PutBaseImageLocation(actualPoint);
|
|
xPositionOnPictureBox = NewLocationOnPictureBox.X;
|
|
yPositionOnPictureBox = NewLocationOnPictureBox.Y;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Done when the box resizes. We need to recompute the picturebox location. The resize function
|
|
/// automatically calls this. You should never need to do so.
|
|
/// </summary>
|
|
public void RecalcPictureBoxLocation()
|
|
{
|
|
Point nPoint = MySpriteController.ReturnPictureBoxAdjustedPoint(new Point(xPositionOnImage, yPositionOnImage));
|
|
xPositionOnPictureBox = nPoint.X;
|
|
yPositionOnPictureBox = nPoint.Y;
|
|
}
|
|
|
|
private void SetupForDrawing()
|
|
{
|
|
//We make sure we have the correct index for the image
|
|
TimeSpan duration = DateTime.UtcNow - LastResetImage;
|
|
if (PausedAnimation)
|
|
{
|
|
TimeSpan newduration = DateTime.UtcNow - PausedAnimationTime;
|
|
LastResetImage = LastResetImage + newduration;
|
|
PausedAnimationTime = DateTime.UtcNow;
|
|
duration = DateTime.UtcNow - LastResetImage;
|
|
}
|
|
double orig = duration.TotalMilliseconds;
|
|
if (SpeedAdjust != 1)
|
|
{
|
|
double Adjust = duration.TotalMilliseconds * SpeedAdjust;
|
|
//Console.WriteLine(SpriteName + " " + SpriteOriginName + " " + orig.ToString() + " " + Adjust.ToString());
|
|
duration = TimeSpan.FromMilliseconds(Adjust);
|
|
}
|
|
if (MyImage.NeedsNewImage(AnimationIndex, FrameIndex, duration) || ForceRedraw)
|
|
{
|
|
EraseMe();
|
|
bool AtEnd = false;
|
|
int oldindex = FrameIndex;
|
|
if (SetToAnimateOnce && AnimationNumberCount <= 1) AtEnd = true;
|
|
//Console.Write("Changing Animation Frame: " + FrameIndex);
|
|
TimeSpan difference = MyImage.ChangeIndex(AnimationIndex, ref _FrameIndex, duration, AtEnd);
|
|
//Console.WriteLine(" To: " + FrameIndex);
|
|
SpriteChangesAnimationFrames(this, new SpriteEventArgs());
|
|
LastResetImage = DateTime.UtcNow - difference;
|
|
if (SetToAnimateOnce)
|
|
{
|
|
if ((FrameIndex == 0 && oldindex != FrameIndex) || MyImage.AnimationDone(AnimationIndex, FrameIndex, SetToAnimateOnce))
|
|
{
|
|
//Console.WriteLine("We are at the end frame:");
|
|
TimeSpan HowLong = MyImage.GetCurrentDuration(AnimationIndex, FrameIndex);
|
|
if (difference > HowLong || FrameIndex == 0)
|
|
{
|
|
AnimationNumberCount--;
|
|
//Console.WriteLine("One animation done. Now we have: " + AnimationNumberCount);
|
|
if (AnimationNumberCount < 1)
|
|
{
|
|
AnimationDone = true;
|
|
if (_FinalFrameAfterAnimation >= 0 && _FinalFrameAfterAnimation < AnimationCount)
|
|
{
|
|
AnimationIndex = _FinalFrameAfterAnimation;
|
|
_FinalFrameAfterAnimation = -1;
|
|
}
|
|
if (!PausedEvents)
|
|
SpriteAnimationComplete(this, new SpriteEventArgs());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DrawMe();
|
|
ForceRedraw = false; //We have done a redraw. No need to do it every time
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is run from the sprite controller every 10 miliseconds. You should never
|
|
/// need to call this yourself.
|
|
/// </summary>
|
|
internal void Tick()
|
|
{
|
|
if (!SpriteHasInitialized)
|
|
{
|
|
SpriteHasInitialized = true;
|
|
SpriteInitializes(this, new SpriteEventArgs());
|
|
}
|
|
if (HasBeenDrawn)
|
|
{
|
|
SetupForDrawing();
|
|
TryToMove();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the point that this sprite needs to be shooting for, for the center of this sprite to
|
|
/// hit the center of the destination sprite.
|
|
/// </summary>
|
|
/// <param name="destination">The sprite we are shooting for trying to hit</param>
|
|
/// <returns>A point which allows the moving sprite to collide with the destination sprite.</returns>
|
|
private Point GetMoveToSpritePoint(Sprite destination)
|
|
{
|
|
if (destination.Destroying) return new Point(-1, -1);
|
|
Point DestCenter = destination.GetSpriteBaseImageCenter();
|
|
Point targetPoint = new Point(DestCenter.X - Width / 2, DestCenter.Y - Height / 2);
|
|
return targetPoint;
|
|
}
|
|
|
|
private void TryToMove()
|
|
{
|
|
if (AutomaticallyMoves && MovementSpeed > 1)
|
|
{
|
|
//Now, we move it.
|
|
//Take 1 sec and divide it by the speed. That is how many ms we need before we move
|
|
|
|
double MS = 50 / MovementSpeed;
|
|
if (MS < 2) MS = 2;
|
|
TimeSpan Elapsed = DateTime.UtcNow - LastMovement;
|
|
if (PausedMovement)
|
|
{
|
|
TimeSpan newduration = DateTime.UtcNow - PausedMovementTime;
|
|
LastMovement = LastMovement + newduration;
|
|
PausedMovementTime = DateTime.UtcNow;
|
|
Elapsed = DateTime.UtcNow - LastMovement;
|
|
}
|
|
if (Elapsed.TotalMilliseconds > MS)
|
|
{
|
|
if(MovingToSprite != null && !MovingToSprite.Destroying)
|
|
{
|
|
MovementDestinations.Clear();
|
|
Point TargetPoint = GetMoveToSpritePoint(MovingToSprite);
|
|
MovementDestinations.Add(TargetPoint);
|
|
}
|
|
else if(MovingToSprite != null && MovingToSprite.Destroying)
|
|
{
|
|
MovingToSprite = null; //We stop shooting for the location of the sprite.
|
|
//We are still shooting for where the sprite used to be.
|
|
}
|
|
if (MovingToPoint && MovementDestinations.Count > 0)
|
|
SetSpriteDirectionToPoint(MovementDestinations[0]); //recalculate to decrease error
|
|
double MovementDelta = Elapsed.TotalMilliseconds / MS;
|
|
double newX = xOnVector + (MovementDelta * SpriteVector.X);
|
|
double newY = yOnVector + (MovementDelta * SpriteVector.Y);
|
|
//if(double.IsNaN(newX) || newX > 1000 || newX < -1000)
|
|
//{
|
|
// Console.WriteLine("Major issue. NAN: xOnVecor:" + xOnVector.ToString() + " MovementDelta:" + MovementDelta.ToString() + " SpriteVec:" + SpriteVector.X.ToString());
|
|
//}
|
|
//if (double.IsNaN(newY) || newY > 1000 || newY < -1000)
|
|
//{
|
|
// Console.WriteLine("Major issue. NAN: yOnVecor:" + yOnVector.ToString() + " MovementDelta:" + MovementDelta.ToString() + " SpriteVec:" + SpriteVector.Y.ToString());
|
|
//}
|
|
if (CannotMoveOutsideBox)
|
|
{
|
|
double tNewx = newX;
|
|
double tNewy = newY;
|
|
Image tImage = MySpriteController.BackgroundImage;
|
|
if (newX < -1) newX = -1;
|
|
if (newY < -1) newY = -1;
|
|
if (newX > tImage.Width - Width) newX = (tImage.Width - Width) + 1;
|
|
if (newY > tImage.Height - Height) newY = (tImage.Height - Height) + 1;
|
|
if (tNewx != newX || tNewy != newY && MovingToPoint)
|
|
{
|
|
//We are not allowed to go outside the box, but our point is outside the box. Cancel
|
|
CancelMoveTo();
|
|
}
|
|
}
|
|
SpriteEventArgs e = new SpriteEventArgs();
|
|
e.NewLocation = new Point((int)Math.Round(newX), (int)Math.Round(newY));
|
|
if (MovingToPoint && MovementDestinations.Count > 0)
|
|
{
|
|
//do not go past the destination point
|
|
if (SpriteVector.X < 0 && newX < MovementDestinations[0].X) newX = MovementDestinations[0].X;
|
|
if (SpriteVector.X > 0 && newX > MovementDestinations[0].X) newX = MovementDestinations[0].X;
|
|
if (SpriteVector.X == 0) newX = MovementDestinations[0].X;
|
|
if (SpriteVector.Y < 0 && newY < MovementDestinations[0].Y) newY = MovementDestinations[0].Y;
|
|
if (SpriteVector.Y > 0 && newY > MovementDestinations[0].Y) newY = MovementDestinations[0].Y;
|
|
if (SpriteVector.Y == 0) newY = MovementDestinations[0].Y;
|
|
//Check to see if we have hit the waypoint
|
|
//Console.WriteLine("Heading to: " + MovementDestinations[0].ToString() + " At: " + newX.ToString() + " " + newY.ToString());
|
|
if ((int)newX == MovementDestinations[0].X && (int)newY == MovementDestinations[0].Y)
|
|
{
|
|
//check to see if we have hit the endpoint
|
|
MovementDestinations.RemoveAt(0); //Yank the destination
|
|
if (MovementDestinations.Count == 0)
|
|
{
|
|
CancelMoveTo();
|
|
SpriteArrivedAtEndPoint(this, e);
|
|
}
|
|
else
|
|
{
|
|
SpriteArrivedAtWaypoint(this, e);
|
|
SetSpriteDirectionToPoint(MovementDestinations[0]);
|
|
}
|
|
}
|
|
}
|
|
else if (MovingToPoint) {
|
|
CancelMoveTo();
|
|
}
|
|
if (!PausedEvents)
|
|
CheckBeforeMove(this, e); //See if there is any code to let us go or not go
|
|
//if(e.Cancel)
|
|
//{
|
|
// Console.WriteLine("canceled. " + newX.ToString() + " " + newY.ToString() + " " + MovementDelta.ToString());
|
|
//}
|
|
if (!e.Cancel)
|
|
{
|
|
//This allows our 'check before move'function to adjust the destination.
|
|
PutBaseImageLocation(e.NewLocation);
|
|
}
|
|
LastMovement = DateTime.UtcNow;
|
|
CheckForEvents();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resize the sprite using the base image coordinates. The width and height specified
|
|
/// are relative to the size of the background image, not the picturebox.
|
|
/// </summary>
|
|
/// <param name="NewSize">The size (width, height) to make the sprite</param>
|
|
public void SetSize(Size NewSize)
|
|
{
|
|
if (NewSize.Width == MyRectangle.Width && NewSize.Height == MyRectangle.Height)
|
|
return; //No need to do anything if we are not making changes
|
|
if (HasBeenDrawn)
|
|
{
|
|
EraseMe(); //Erase ourselves
|
|
}
|
|
Width = NewSize.Width;
|
|
Height = NewSize.Height;
|
|
if (HasBeenDrawn)
|
|
{
|
|
DrawMe();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tell the sprite to kill itself. It will erase itself and then
|
|
/// be removed from the spritelist. Then it will be gone forever.
|
|
/// </summary>
|
|
public void Destroy()
|
|
{
|
|
//If we are not already destroying ourselves
|
|
if (!_Destroying)
|
|
{
|
|
//Mark ourselves as being destroyed
|
|
_Destroying = true;
|
|
//Erase ourselves
|
|
EraseMe();
|
|
//Remove ourselves from the controller (and the tick process)
|
|
SpriteBeingDestroyed(this, new SpriteEventArgs());
|
|
MySpriteController.DestroySprite(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove the sprite from the field. This does not destroy the sprite. It simply removes it from action.
|
|
/// Use UnhideSprite to show it again.
|
|
/// </summary>
|
|
public void HideSprite()
|
|
{
|
|
EraseMe();
|
|
HasBeenDrawn = false;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Make the sprite reappear. If you have not positioned it yet, it will show up at the top corner. It is best to only
|
|
/// use this when you have hidden it using HideSprite
|
|
/// </summary>
|
|
public void UnhideSprite()
|
|
{
|
|
DrawMe();
|
|
HasBeenDrawn = true;
|
|
}
|
|
/// <summary>
|
|
/// Return true or false, asking if the specifiec sprite is at the point on the picturebox.
|
|
/// You can use this with a mouse-click to see if you are clicking on a sprite. Use the
|
|
/// SpriteCollisionMethod "transparent" to see if you have clicked on an actual pixel of the
|
|
/// sprite instead of just within the sprite rectangle.
|
|
/// </summary>
|
|
/// <param name="location">The x and y location in ImageBox coordinates.</param>
|
|
/// <param name="method">The method of determining if the sprite is at that position</param>
|
|
/// <returns>True if the sprite is at the specified location, false if it is not</returns>
|
|
public bool SpriteAtPictureBoxPoint(Point location, SpriteCollisionMethod method = SpriteCollisionMethod.rectangle)
|
|
{
|
|
//Translate the position to a position on the drawing pane
|
|
SpriteAdjustmentRatio PictureBoxRatio = MySpriteController.ReturnAdjustmentRatio();
|
|
int x = (int)(location.X / PictureBoxRatio.width_ratio);
|
|
int y = (int)(location.Y / PictureBoxRatio.height_ratio);
|
|
|
|
//The x,y is now the pixel in the sprite.
|
|
return SpriteAtImagePoint(new Point(x, y), method);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Because sprites are scaled (shrunk or stretched), this function finds the point
|
|
/// within the sprite that is specified by the location. this function is used by
|
|
/// a number of internal processes, but may be useful to you. But probably not.
|
|
/// </summary>
|
|
/// <param name="location">A point given in Image coordinates</param>
|
|
/// <returns>A point within the pixel that can be used to find a particular pixel in a sprite.</returns>
|
|
public Point SpriteAdjustedPoint(Point location)
|
|
{
|
|
Point internalPoint = new Point(location.X - MyRectangle.Location.X, location.Y - MyRectangle.Location.Y);
|
|
SpriteAdjustmentRatio Ratio = ReturnAdjustmentRatio();
|
|
Point AdjustedPoint = new Point((int)(internalPoint.X / Ratio.width_ratio), (int)(internalPoint.Y / Ratio.height_ratio));
|
|
return AdjustedPoint;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check to see if the sprite exists at the point specified. The point given is
|
|
/// in coordinates used by the image (not the PictureBox, use SpriteAtPictureBox for that)
|
|
/// </summary>
|
|
/// <param name="location">An imagebox location</param>
|
|
/// <param name="method">the method to use to determine if the image is there</param>
|
|
/// <returns>true if the sprite is at that position, false if it is not</returns>
|
|
public bool SpriteAtImagePoint(Point location, SpriteCollisionMethod method = SpriteCollisionMethod.rectangle)
|
|
{
|
|
if (location.X < MyRectangle.Location.X) return false;
|
|
if (location.X > MyRectangle.Location.X + MyRectangle.Width) return false;
|
|
if (location.Y < MyRectangle.Location.Y) return false;
|
|
if (location.Y > MyRectangle.Location.Y + MyRectangle.Height) return false;
|
|
|
|
|
|
//We need to adjust to the sprite being stretched or shrunk
|
|
Point internalPoint = new Point(location.X - MyRectangle.Location.X, location.Y - MyRectangle.Location.Y);
|
|
SpriteAdjustmentRatio Ratio = ReturnAdjustmentRatio();
|
|
Point AdjustedPoint = SpriteAdjustedPoint(location);
|
|
|
|
if (method == SpriteCollisionMethod.transparency)
|
|
{
|
|
Bitmap tImage = (Bitmap)GetImage();
|
|
//Check the point within the sprite
|
|
if (AdjustedPoint.X < 0 || AdjustedPoint.X >= tImage.Width) return false;
|
|
if (AdjustedPoint.Y < 0 || AdjustedPoint.Y >= tImage.Height) return false;
|
|
Color OneSpace = tImage.GetPixel(AdjustedPoint.X, AdjustedPoint.Y);
|
|
if (OneSpace.A == 0)
|
|
return false; //It is transparent. No collision at this point. 255 is solid
|
|
return true;
|
|
}
|
|
if (method == SpriteCollisionMethod.circle)
|
|
{
|
|
|
|
Point center = new Point(
|
|
MyRectangle.Width / 2,
|
|
MyRectangle.Height / 2);
|
|
|
|
double _xRadius = MyRectangle.Width / 2;
|
|
double _yRadius = MyRectangle.Height / 2;
|
|
|
|
|
|
if (_xRadius <= 0.0 || _yRadius <= 0.0)
|
|
return false;
|
|
/* This is a more general form of the circle equation
|
|
*
|
|
* X^2/a^2 + Y^2/b^2 <= 1
|
|
*/
|
|
|
|
Point normalized = new Point(location.X - center.X,
|
|
location.Y - center.Y);
|
|
|
|
return ((double)(normalized.X * normalized.X)
|
|
/ (_xRadius * _xRadius)) + ((double)(normalized.Y * normalized.Y) / (_yRadius * _yRadius))
|
|
<= 1.0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// return the current image frame. Warning: If you write to this image, it will
|
|
/// affect all sprites using this frame.
|
|
/// </summary>
|
|
/// <returns>An image that is the current sprite frame for the current animation</returns>
|
|
public Image GetImage()
|
|
{
|
|
if (AnimationIndex < 0) AnimationIndex = 0;
|
|
if (FrameIndex < 0) FrameIndex = 0;
|
|
return MyImage.GetImage(AnimationIndex, FrameIndex, GetVisibleSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// return the frame for the given index. Warning: If you write to this image, it will
|
|
/// affect all sprites using this frame.
|
|
/// </summary>
|
|
/// <param name="Animation_Index">The Animation index we are trying to find</param>
|
|
/// <param name="Frame_Index">The Frame index we are trying to find</param>
|
|
/// <returns>An image that is the current sprite frame for the current animation</returns>
|
|
public Image GetImage(int Animation_Index, int Frame_Index)
|
|
{
|
|
if (Animation_Index < 0) Animation_Index = 0;
|
|
if (Animation_Index > MyImage.AnimationCount) return null;
|
|
if (Frame_Index < 0) Frame_Index = 0;
|
|
if (Frame_Index > MyImage.AnimationFrameCount(Animation_Index)) return null;
|
|
return MyImage.GetImage(Animation_Index, Frame_Index, GetVisibleSize);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Replace a sprite image. It will replace the current frame unless you specify both an animation
|
|
/// and the frame within the animation you wish to replace. Warning: This replaces the image_frame
|
|
/// for every sprite that uses that is based off the same image.
|
|
/// </summary>
|
|
/// <param name="newimage">The new image to use</param>
|
|
/// <param name="animation">The animation you want to change</param>
|
|
/// <param name="frame">The frame within the animation you want to change</param>
|
|
public void ReplaceImage(Image newimage, int animation = -1, int frame = -1)
|
|
{
|
|
if (newimage == null) return; //do not replace it with nothing
|
|
if (AnimationIndex < 0) AnimationIndex = 0;
|
|
if (FrameIndex < 0) FrameIndex = 0;
|
|
if (animation == -1) animation = AnimationIndex;
|
|
if (frame == -1) frame = FrameIndex;
|
|
MyImage.ReplaceImage(newimage, animation, frame);
|
|
RedrawMeAndAllICollideWith();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Taking into consideration how the sprite is stretched or shrunk, it
|
|
/// returns a SpriteAdjustmentRatio that can be used to work with the sprite
|
|
/// itself.
|
|
/// </summary>
|
|
/// <returns>The current SpriteAdjustmentRatio used to display this sprite</returns>
|
|
public SpriteAdjustmentRatio ReturnAdjustmentRatio()
|
|
{
|
|
SpriteAdjustmentRatio Ratio = new SpriteAdjustmentRatio();
|
|
|
|
Image tImage = GetImage();
|
|
//if(tImage == null) Console.WriteLine(this.ID.ToString());
|
|
Ratio.width_ratio = (double)MyRectangle.Width / (double)tImage.Width;
|
|
Ratio.height_ratio = (double)MyRectangle.Height / (double)tImage.Height;
|
|
return Ratio;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return true if the sprite can go to this point and still be on the drawing-board.
|
|
/// </summary>
|
|
/// <param name="newpoint">The point, given in pixels and corresponding to pixels on the picturebox</param>
|
|
/// <returns>true or false</returns>
|
|
public bool SpriteCanMoveOnPictureBox(Point newpoint)
|
|
{
|
|
return SpriteCanMoveOnImage(MySpriteController.ReturnPointAdjustedForImage(newpoint));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return true if the sprite can go to this point and still be on the drawing-board.
|
|
/// </summary>
|
|
/// <param name="newpoint">The point, given in pixels and corresponding to pixels on the background image</param>
|
|
/// <returns>true or false</returns>
|
|
public bool SpriteCanMoveOnImage(Point newpoint)
|
|
{
|
|
Image tImage = MySpriteController.BackgroundImage;
|
|
if (newpoint.X < 0) return false;
|
|
if (newpoint.X + Width > tImage.Width) return false;
|
|
if (newpoint.Y < 0) return false;
|
|
if (newpoint.Y + Height > tImage.Height) return false;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move to where the destination sprite currently is at. This is a dumb move. It does not take into
|
|
/// consideration the movement direction of the destination sprite. So the moving sprite does need to be
|
|
/// moving a bit faster than the sprite you are trying to hit for it to do so.
|
|
/// </summary>
|
|
/// <param name="Destination">The sprite we are trying to hit</param>
|
|
public void MoveTo(Sprite Destination)
|
|
{
|
|
MovingToSprite = Destination;
|
|
LastMovement = DateTime.UtcNow;
|
|
MovementDestinations.Clear();
|
|
Point TargetPoint = GetMoveToSpritePoint(Destination);
|
|
MovementDestinations.Add(TargetPoint);
|
|
MovingToPoint = true;
|
|
SpriteReachedEndPoint = false;
|
|
SetSpriteDirectionToPoint(TargetPoint);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tell the Sprite to move towards a destination. You need to give the sprite a MovementSpeed
|
|
/// and tell the sprite that it can automatically move. But the sprite will begin a journey towards
|
|
/// that point at the MovementSpeed you have set. When it gets to the point, the SpriteArrivedAtEndPoint event
|
|
/// will fire off. Also, the SpriteReachedEnd bool will be true.
|
|
/// </summary>
|
|
/// <param name="Destination">An image-point that the sprite will move to.</param>
|
|
public void MoveTo(Point Destination)
|
|
{
|
|
List<Point> tList = new List<Point>();
|
|
tList.Add(Destination);
|
|
MoveTo(tList); //This way we only have one function to make.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tell the sprite to move towards each point in turn. The sprite will move in a straight line until the first point.
|
|
/// From there it moves to the next point, until it has reached the last point. Every time it reaches a point, the
|
|
/// SpriteArrivedAtWaypoint event is triggered. When it reaches the final point in the list, the SpriteArrivedAtEndPoint
|
|
/// event is triggered. While the sprite is moving, the SpriteReachedEndPoint attribute is set to false. When it has
|
|
/// arrived, it is set to true.
|
|
/// </summary>
|
|
/// <param name="DestinationList">A list of Image-Points that the sprite will follow, one after the other</param>
|
|
public void MoveTo(List<Point> DestinationList)
|
|
{
|
|
MovingToSprite = null; //If we were trying to hit something, we are no longer trying to hit it.
|
|
LastMovement = DateTime.UtcNow;
|
|
if (DestinationList.Count == 0) CancelMoveTo(); //If we tell it to move nowhere, cancel it
|
|
MovementDestinations.Clear();
|
|
foreach(Point one in DestinationList)
|
|
MovementDestinations.Add(one);
|
|
MovingToPoint = true;
|
|
SpriteReachedEndPoint = false;
|
|
SetSpriteDirectionToPoint(DestinationList[0]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the Sprite Moving towards a given point. You are responsible to do something with it once it gets there.
|
|
/// If you want it to automatically stop upon reaching it, use MoveTo instead. Actually, the MoveTo function works
|
|
/// a lot better than this one. Because of integer rounding and a few other things, this function is a little
|
|
/// bit imprecise. If you send it towards a point, it will go in that general direction. The MoveTo function
|
|
/// will perpetually recalculate its way to the destination point and actually reach that point. SetSpriteDirectionToPoint
|
|
/// will sort-of head in the direction of the point. But MoveTo will go to that point.
|
|
/// </summary>
|
|
/// <param name="ImagePointDestination">The destination, based off a point on the background image, that we send the sprite towards.</param>
|
|
public void SetSpriteDirectionToPoint(Point ImagePointDestination)
|
|
{
|
|
double x = ImagePointDestination.X - xPositionOnImage;
|
|
double y = ImagePointDestination.Y - yPositionOnImage;
|
|
double distance = Math.Sqrt(x *x + y * y);
|
|
if (distance == 0) return; //No need to go anywhere.
|
|
System.Windows.Vector newVec = new System.Windows.Vector(x / distance, y / distance);
|
|
//if(double.IsNaN(newVec.X) || double.IsNaN(newVec.Y))
|
|
//{
|
|
// Console.WriteLine("SetSpriteDirectionToPoint: Creating invalid vector " + distance);
|
|
//}
|
|
SetSpriteDirection(newVec);
|
|
//Console.WriteLine("SetDirectionToPoint " + ImagePointDestination.ToString());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancel a MoveTo command. The sprite will stop moving, and all the waypoints will be removed.
|
|
/// </summary>
|
|
public void CancelMoveTo()
|
|
{
|
|
SpriteReachedEndPoint = true;
|
|
MovementDestinations.Clear();
|
|
MovingToSprite = null; //If we were heading towards a sprite. Stop doing so.
|
|
if (!MovingToPoint) return; //We do not do anything if we are not moving.
|
|
MovementSpeed = 0;
|
|
//AutomaticallyMoves = false;
|
|
SetSpriteDirectionDegrees(0);//Basically reset it to nothing
|
|
MovingToPoint = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given a "degree" (from 0 to 360, set the direction
|
|
/// that the sprite moves automatically. 0 is right, 90 is up, 180 is left
|
|
/// and 270 is down.
|
|
/// </summary>
|
|
/// <param name="AngleInDegrees">the degrees to use</param>
|
|
public void SetSpriteDirectionDegrees(double AngleInDegrees)
|
|
{
|
|
//convert to Radians and set it with that
|
|
double Radians = ConvertDegreesToRadians(AngleInDegrees);
|
|
SetSpriteDirectionRadians(Radians);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the sprite direction using Radians. Most people do not want to use this.
|
|
/// Use SetSpriteDirectionDegrees instead unless you like math and know what you
|
|
/// are doing with Radians.
|
|
/// </summary>
|
|
/// <param name="AngleInRadians">The angle in radians</param>
|
|
public void SetSpriteDirectionRadians(double AngleInRadians)
|
|
{
|
|
//Turn it into a vector
|
|
System.Windows.Vector newVector = new System.Windows.Vector((float)Math.Cos(AngleInRadians),
|
|
-(float)Math.Sin(AngleInRadians));
|
|
SetSpriteDirection(newVector);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the sprite direction using a vector. The vector may contain
|
|
/// a speed as well as the movement delta (amount of x shift, and amount
|
|
/// of y shift.) If so, this function may also affect the movement speed
|
|
/// Most people prefer to use SetSpriteDirectionDegrees instead of using
|
|
/// vectors.
|
|
/// </summary>
|
|
/// <param name="newVector">A vector</param>
|
|
public void SetSpriteDirection(System.Windows.Vector newVector)
|
|
{
|
|
System.Windows.Vector oldVector = newVector;
|
|
//use the specified vector
|
|
if (Math.Round(newVector.Length) != 1)
|
|
{
|
|
double NewSpeed = Math.Round(newVector.Length);
|
|
MovementSpeed = (int)NewSpeed;
|
|
}
|
|
newVector.Normalize();
|
|
if (double.IsNaN(newVector.X) || double.IsNaN(newVector.Y))
|
|
{
|
|
//Console.WriteLine("SetSpriteDirection: Error setting direction. " + oldVector.ToString());
|
|
newVector = oldVector;
|
|
}
|
|
SpriteVector = newVector;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a number from degrees to radians.
|
|
/// </summary>
|
|
/// <param name="Degrees">The number from 0 to 360 in degrees</param>
|
|
/// <returns>The corresponding number converted to radians</returns>
|
|
public double ConvertDegreesToRadians(double Degrees)
|
|
{
|
|
return (Math.PI / 180.0) * Degrees;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a number from radians to degrees.
|
|
/// </summary>
|
|
/// <param name="Radians">The number of radians</param>
|
|
/// <returns>The corresponding number in degrees</returns>
|
|
public double ConvertRadiansToDegrees(double Radians)
|
|
{
|
|
double degrees = (180.0 / Math.PI) * Radians;
|
|
if (Radians < 0) degrees += 360;
|
|
return degrees;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the current vector that the sprite is moving along
|
|
/// </summary>
|
|
/// <returns>The current sprite vector</returns>
|
|
public System.Windows.Vector GetSpriteVector()
|
|
{
|
|
return SpriteVector;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the direction the sprite is currently traveling, using Radians.
|
|
/// </summary>
|
|
/// <returns>The direction in radians that the sprite is traveling in</returns>
|
|
public double GetSpriteRadans()
|
|
{
|
|
//double radians = Math.Atan2((double)SpriteVector.Y, (double)SpriteVector.X);
|
|
double radians;
|
|
if(SpriteVector.Y > 0 && SpriteVector.X > 0)
|
|
{
|
|
//calculate it in the other quadrant and then adjust.
|
|
radians = Math.Atan2((double)SpriteVector.Y, (double)SpriteVector.X);
|
|
radians = (2 * Math.PI) - radians;
|
|
}
|
|
else
|
|
radians = Math.Atan2(-(double)SpriteVector.Y, (double)SpriteVector.X);
|
|
return radians;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the direction that the sprite is traveling in in degrees. You may want to
|
|
/// use Math.Round on the results. The value returned is usually just a tiny bit off
|
|
/// from what you set it with. For example, if you set the sprite movement direction
|
|
/// to be 270 degrees (down), this function may return it as 269.999992. Rounding the
|
|
/// number will give it back to you at probably the same direction you set it as.
|
|
/// </summary>
|
|
/// <returns>A double (it has a decimal place) that represents the direction in degrees</returns>
|
|
public double GetSpriteDegrees()
|
|
{
|
|
double radians = GetSpriteRadans();
|
|
double degrees = ConvertRadiansToDegrees(radians);
|
|
if (degrees < 0) degrees = 360 + degrees;
|
|
return degrees;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the centerpoint of the sprite, as found on the background image
|
|
/// </summary>
|
|
/// <returns>a point with the x and y based off the background image location</returns>
|
|
public Point GetSpriteBaseImageCenter()
|
|
{
|
|
Point corner = BaseImageLocation;
|
|
return new Point(corner.X + Width / 2, corner.Y + Height / 2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the centerpoint of the sprite, as found on the picturebox
|
|
/// </summary>
|
|
/// <returns>A point with the x and y found on the picturebox</returns>
|
|
public Point GetSpritePictureboxCenter()
|
|
{
|
|
Point corner = PictureBoxLocation;
|
|
return new Point(corner.X + VisibleWidth / 2, corner.Y + VisibleHeight / 2);
|
|
}
|
|
|
|
// ***************** Events ***********
|
|
private bool CheckForHittingEdgeOfImage()
|
|
{
|
|
Image tImage = MySpriteController.BackgroundImage;
|
|
bool outOfBounds = false;
|
|
if (xPositionOnImage < 0) outOfBounds = true;
|
|
if (xPositionOnImage + Width > tImage.Width) outOfBounds = true;
|
|
if (yPositionOnImage < 0) outOfBounds = true;
|
|
if (yPositionOnImage + Height > tImage.Height) outOfBounds = true;
|
|
if (outOfBounds)
|
|
{
|
|
if (!PausedEvents)
|
|
SpriteHitsPictureBox(this, new SpriteEventArgs());
|
|
}
|
|
return outOfBounds;
|
|
}
|
|
private bool CheckForExitingImage()
|
|
{
|
|
Image tImage = MySpriteController.BackgroundImage;
|
|
bool outOfBounds = false;
|
|
if (xPositionOnImage + Width < 0) outOfBounds = true;
|
|
if (xPositionOnImage > tImage.Width) outOfBounds = true;
|
|
if (yPositionOnImage + Width < 0) outOfBounds = true;
|
|
if (yPositionOnImage > tImage.Height) outOfBounds = true;
|
|
if (outOfBounds)
|
|
{
|
|
if (!PausedEvents)
|
|
SpriteExitsPictureBox(this, new SpriteEventArgs());
|
|
}
|
|
return outOfBounds;
|
|
}
|
|
|
|
private bool CheckToSeeIfSpriteHitsSprite(Sprite target, SpriteCollisionMethod how)
|
|
{
|
|
if (target == this) return false; //We do not collide with ourselves.
|
|
if (target.xPositionOnImage + target.Width < xPositionOnImage) return false;
|
|
if (target.xPositionOnImage > xPositionOnImage + Width) return false;
|
|
if (target.yPositionOnImage + target.Height < yPositionOnImage) return false;
|
|
if (target.yPositionOnImage > yPositionOnImage + Height) return false;
|
|
//If we get here, we have two rectangles ovelapping.
|
|
if (how == SpriteCollisionMethod.circle)
|
|
{
|
|
|
|
}
|
|
if (how == SpriteCollisionMethod.transparency)
|
|
{
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check to see if the specified rectangle overlaps with the sprite.
|
|
/// </summary>
|
|
/// <param name="target">The rectangle we are looking to see if we hit</param>
|
|
/// <returns>True if the rectangle overlaps the sprite rectabgle</returns>
|
|
public bool SpriteIntersectsRectangle(Rectangle target)
|
|
{
|
|
if (target.X + target.Width < xPositionOnImage) return false;
|
|
if (target.X > xPositionOnImage + Width) return false;
|
|
if (target.Y + target.Height < yPositionOnImage) return false;
|
|
if (target.Y > yPositionOnImage + Height) return false;
|
|
//If we get here, we have two rectangles ovelapping.
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check to see if two sprites hit each-other. The sprite collision methods are
|
|
/// not all programmed in.
|
|
/// </summary>
|
|
/// <param name="target">The Sprite we are checking to see if we hit</param>
|
|
/// <param name="how">The method we use to determine if they hit</param>
|
|
public void CheckSpriteHitsSprite(Sprite target, SpriteCollisionMethod how)
|
|
{
|
|
if (target == this) return;
|
|
if (CheckToSeeIfSpriteHitsSprite(target, how))
|
|
{
|
|
target.NoteSpriteHitsSprite(this, how);
|
|
NoteSpriteHitsSprite(target, how);
|
|
CollisionSprites.Add(target);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is used when two sprites hit each-other.
|
|
/// </summary>
|
|
/// <param name="target">The sprite it hits</param>
|
|
/// <param name="how">the method for checking</param>
|
|
internal void NoteSpriteHitsSprite(Sprite target, SpriteCollisionMethod how)
|
|
{
|
|
if (target == this) return;
|
|
SpriteEventArgs newArgs = new SpriteEventArgs();
|
|
newArgs.TargetSprite = target;
|
|
newArgs.CollisionMethod = how;
|
|
if (SpriteHitsSprite != null)
|
|
{
|
|
if (!PausedEvents)
|
|
SpriteHitsSprite(this, newArgs);
|
|
}
|
|
}
|
|
|
|
internal void CheckForEvents()
|
|
{
|
|
if (!CheckForExitingImage())
|
|
{
|
|
CheckForHittingEdgeOfImage();
|
|
}
|
|
}
|
|
|
|
internal void ClearCollisionList()
|
|
{
|
|
CollisionSprites.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Make the sprite show up in front of all other sprites.
|
|
/// </summary>
|
|
public void SendToFront()
|
|
{
|
|
MySpriteController.SpriteToFront(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Make the sprite go behind all other sprites
|
|
/// </summary>
|
|
public void SendToBack()
|
|
{
|
|
MySpriteController.SpriteToBack(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pause the sprite. We can pause just the animation (and still let it move), pause movement (and let it animate), or pause everything.
|
|
/// </summary>
|
|
/// <param name="What">Which aspects of the sprite you want to pause.</param>
|
|
public void Pause(SpritePauseType What = SpritePauseType.PauseAll)
|
|
{
|
|
if(!PausedAnimation && (What == SpritePauseType.PauseAnimation || What == SpritePauseType.PauseAll))
|
|
{
|
|
PausedAnimationTime = DateTime.UtcNow;
|
|
PausedAnimation = true;
|
|
}
|
|
if (!PausedMovement && ( What == SpritePauseType.PauseMovement || What == SpritePauseType.PauseAll))
|
|
{
|
|
PausedMovementTime = DateTime.UtcNow;
|
|
PausedMovement = true;
|
|
}
|
|
if (What == SpritePauseType.PauseEvents || What == SpritePauseType.PauseAll)
|
|
{
|
|
PausedEvents = true;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// unpause the sprite.
|
|
/// </summary>
|
|
/// <param name="What">Which aspects of the sprite you want to unpause.</param>
|
|
public void UnPause(SpritePauseType What = SpritePauseType.PauseAll)
|
|
{
|
|
TimeSpan duration;
|
|
if (PausedAnimation && What == SpritePauseType.PauseAnimation || What == SpritePauseType.PauseAll)
|
|
{
|
|
duration = DateTime.UtcNow - PausedAnimationTime;
|
|
LastResetImage = LastResetImage + duration;
|
|
PausedAnimation = false;
|
|
}
|
|
if (PausedMovement && What == SpritePauseType.PauseMovement || What == SpritePauseType.PauseAll)
|
|
{
|
|
duration = DateTime.UtcNow - PausedMovementTime;
|
|
LastMovement = LastResetImage + duration;
|
|
PausedMovement = false;
|
|
}
|
|
if (PausedEvents && What == SpritePauseType.PauseEvents || What == SpritePauseType.PauseAll)
|
|
{
|
|
PausedEvents = false;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Ask if the sprite is paused using the specified sprite type (default is PauseAll)
|
|
/// </summary>
|
|
/// <param name="What">The spritePauseType to see if the sprite is paused with</param>
|
|
/// <returns>True if the sprite is set to pause the specified item, false if not</returns>
|
|
public bool IsPaused(SpritePauseType What = SpritePauseType.PauseAll)
|
|
{
|
|
if (What == SpritePauseType.PauseAnimation && PausedAnimation) return true;
|
|
if (What == SpritePauseType.PauseMovement && PausedMovement) return true;
|
|
if (What == SpritePauseType.PauseEvents && PausedMovement) return true;
|
|
if (What == SpritePauseType.PauseAll && PausedAnimation && PausedMovement && PausedEvents) return true;
|
|
return false;
|
|
}
|
|
|
|
internal void ClickedOn(SpriteCollisionMethod how)
|
|
{
|
|
if (how == SpriteCollisionMethod.rectangle)
|
|
Click(this, new SpriteEventArgs());
|
|
if (how == SpriteCollisionMethod.transparency)
|
|
ClickTransparent(this, new SpriteEventArgs());
|
|
}
|
|
|
|
internal void HoverOver()
|
|
{
|
|
MouseHover(this, new SpriteEventArgs());
|
|
}
|
|
internal void Enter()
|
|
{
|
|
MouseEnter(this, new SpriteEventArgs());
|
|
}
|
|
internal void Leave()
|
|
{
|
|
MouseLeave(this, new SpriteEventArgs());
|
|
}
|
|
|
|
internal void HoverOverTransparent()
|
|
{
|
|
MouseHoverTransparent(this, new SpriteEventArgs());
|
|
}
|
|
internal void EnterTransparent()
|
|
{
|
|
MouseEnterTransparent(this, new SpriteEventArgs());
|
|
}
|
|
internal void LeaveTransparent()
|
|
{
|
|
MouseLeaveTransparent(this, new SpriteEventArgs());
|
|
}
|
|
}
|
|
}
|