2017-09-03 03:35:32 +02:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.Threading.Tasks ;
using System.Threading ;
using System.ComponentModel ;
using System.Drawing ;
using System.Windows.Forms ;
using System.IO ;
using System.IO.Packaging ;
namespace SpriteLibrary
{
/// <summary>
/// SpriteLibrary is a .net graphical library for creating and controlling sprites on a PictureBox.
///
/// A sprite is an animated image that can be moved around on a
/// picturebox. You can give the sprite an initial location, and either move it around manually or give it
/// specific movement controls.
///
/// To use this library, you will need to add a reference to it in your project. You will also need a reference to
/// "Windows Base."
/// In the solution explorer, if you right-click your project and go to "add", and then "reference" and click
/// "WindowsBase" towards the bottom.
/// On that same window, on the left, click "browse." Then, click the "Browse..." button and find the sprite-library dll.
/// The main places to find the SpriteLibrary and sample programs using this SpriteLibrary are here:
/// <see href="http://www.codeproject.com/Articles/1085446/Using-Sprites-Inside-Windows-Forms"/>
/// and
/// <see href="http://tyounglightsys.ddns.info/SpriteLibrary"/>
/// </summary>
internal class NamespaceDoc
{
}
/// <summary>
/// The various types of collisions a sprite can have. Currently only rectangle works. The other types were added when I
/// thought the different types of collision types were needed. Someday we may add these if we find they are useful, or if
/// someone else decides they want to help program the SpriteLibrary. These values are primarily used in Sprite Events
/// </summary>
public enum SpriteCollisionMethod {
/// <summary>
/// Checks if the two rectangles that contain the sprites overlap. Each rectangle is the starting location of the sprite
/// (top left) with the sprite width, and height marking the other sides of the rectangle.
/// </summary>
rectangle ,
/// <summary>
/// Draws a circle (ellipse) inside the sprite rectangles and see if those ellipses overlap
/// </summary>
circle ,
/// <summary>
/// Check to see if nontransparent portions of a sprite collide. Not working.
/// </summary>
transparency }
/// <summary>
/// A structure that contains the width and height adjustment ratio. Use this if you need to manually calculate positions
/// between the PictureBox that the sprite is in, and the Background Image itself.
/// </summary>
public struct SpriteAdjustmentRatio {
/// <summary>
/// Divide a picturebox ratio by this to get the image location. Multiply an image location by this to get the picturebox location.
/// </summary>
public double width_ratio ;
/// <summary>
/// Divide a picturebox ratio by this to get the image location. Multiply an image location by this to get the picturebox location.
/// </summary>
public double height_ratio ; }
/// <summary>
/// The type of pause signals you can give a sprite or the sprite controller
/// </summary>
public enum SpritePauseType {
/// <summary>
/// Pause the animating. Animation resumes from the current frame when we unpause. A paused animation will continue
/// to display the same image frame until it is unpaused.
/// </summary>
PauseAnimation ,
/// <summary>
/// Pause any automatic movement. Movement resumes where it was left off if you unpause. The sprite will
/// just sit there until unpaused.
/// </summary>
PauseMovement ,
/// <summary>
/// Pause events. Sprite collisions, movement checks, etc are stopped until the unpause.
/// </summary>
PauseEvents ,
/// <summary>
/// All pausable things are paused. PauseAnimation, PauseMovement, and PauseEvents.
/// </summary>
PauseAll }
/// <summary>
/// A sprite controller is the main heart of the sprite class. Each SpriteController manages one picturebox.
/// If at all possible, try to keep each game in one picturebox, and try to avoid making and destroying
/// new forms with SpriteController/pictureboxes in them. It is hard to destroy them completely.
/// </summary>
/// <example>
/// A sprite controller controls animations and
/// can help you check for <see cref="SpriteController.IsKeyPressed(Keys)">key-presses.</see> To make a sprite controller,
/// you need to have one defined for your main form:
/// <code language="C#">
/// SpriteController MySpriteController;
/// </code>
/// And then, when the form is created, after the InitializeComponents() function, you
/// need to configure the drawing area and create the sprite controller:
/// <code language="C#">
/// MainDrawingArea.BackgroundImage = Properties.Resources.Background;
/// MainDrawingArea.BackgroundImageLayout = ImageLayout.Stretch;
/// MySpriteController = new SpriteController(MainDrawingArea);
/// </code>
/// In this case, MainDrawingArea is the picturebox where all the sprites will be displayed.
/// </example>
public class SpriteController
{
Image MyOriginalImage ; //The untainted background
PictureBox DrawingArea ; //The PictureBox we draw ourselves on
List < Sprite > Sprites = new List < Sprite > ( ) ;
2017-09-10 23:57:31 +02:00
List < SpriteController > LinkedControllers = new List < SpriteController > ( ) ; //Other sprite controllers that we share sprites with
2017-09-03 03:35:32 +02:00
/// <summary>
/// Since everything needs a random number generator, we make one that should be accessible throughout your program.
/// </summary>
public Random RandomNumberGenerator = new Random ( ) ;
/// <summary>
/// This is only used by the SpriteController. It allows us to queue up invalidation requests.
/// </summary>
private List < Rectangle > InvalidateList = new List < Rectangle > ( ) ;
private KeyMessageFilter MessageFilter = new KeyMessageFilter ( ) ;
/// <summary>
/// The count of all the sprites the controller knows about. This includes named
/// sprites, which may not be visible.
/// </summary>
public int SpriteCount { get { return Sprites . Count ; } }
System . Windows . Forms . Timer MyTimer = new System . Windows . Forms . Timer ( ) ;
//private bool lockObject=false;
//System.Threading.Timer ThreadTimer;
//These two are used for tracking mouse-leave, and mouse enter functions.
private Point MousePoint = Point . Empty ;
private List < Sprite > SpritesUnderMouse = new List < Sprite > ( ) ;
private List < Sprite > SpritesUnderMouseTransparent = new List < Sprite > ( ) ;
private SpriteAdjustmentRatio _AdjustmentRatio ;
/// <summary>
/// If your sprite images need substantial growing or shrinking when displayed, you can try setting this to "true"
/// to see if it makes it run any faster. What it does is to resize the image once, and keep a cached copy of that
/// image at that size. If you use the same sprite, but with different sizes, setting this to "True" may actually slow
/// down the game instead of speeding it up.
/// </summary>
public bool OptimizeForLargeSpriteImages = false ;
/// <summary>
/// Create a sprite controller, specifying the picturebox on which the sprites
/// will be displayed. You want to have the PictureBox already defined, and a background image
/// already set for the PictureBox.
/// </summary>
/// <example>
/// This is an example of a Form class that defines a SpriteController. The MainDrawingArea is a
/// <see cref="System.Windows.Forms.PictureBox">PictureBox.</see>
/// <code lang="C#">
/// public partial class ShootingFieldForm : Form
/// {
/// public ShootingFieldForm()
/// {
/// InitializeComponent();
/// MainDrawingArea.BackgroundImage = Properties.Resources.Background;
/// MainDrawingArea.BackgroundImageLayout = ImageLayout.Stretch;
/// MySpriteController = new SpriteController(MainDrawingArea);
/// }
/// }
/// </code>
/// </example>
/// <param name="Area">The <see cref="System.Windows.Forms.PictureBox">PictureBox.</see>
/// that the sprites will be drawn in</param>
public SpriteController ( PictureBox Area )
{
DrawingArea = Area ;
Local_Setup ( ) ;
}
/// <summary>
/// Create a sprite controller, specifying the picturebox on which the sprites
/// will be displayed.
/// </summary>
/// <example>
/// This is an example of a Form class that defines a SpriteController. The MainDrawingArea is a
/// <see cref="System.Windows.Forms.PictureBox">PictureBox.</see> While defining the SpriteController, we
/// are also setting a function used for the <see cref="SpriteLibrary.SpriteController.DoTick">DoTick.</see> event.
/// <code lang="C#">
/// public partial class ShootingFieldForm : Form
/// {
/// public ShootingFieldForm()
/// {
/// InitializeComponent();
/// MainDrawingArea.BackgroundImage = Properties.Resources.Background;
/// MainDrawingArea.BackgroundImageLayout = ImageLayout.Stretch;
/// MySpriteController = new SpriteController(MainDrawingArea, CheckForKeyPress);
/// }
///
/// private void CheckForKeyPress(object sender, EventArgs e)
/// {
/// //Do stuff here
/// }
/// }
///
/// </code>
/// </example>
/// <param name="Area">The picturebox that the sprites will be drawn in</param>
/// <param name="TimerTickMethod">A function on the form that you want to have run every tick</param>
public SpriteController ( PictureBox Area , System . EventHandler TimerTickMethod )
{
DrawingArea = Area ;
Local_Setup ( ) ;
DoTick + = new System . EventHandler ( TimerTickMethod ) ;
}
/// <summary>
/// Define some things and set up some things that need defining at instantiation
/// </summary>
private void Local_Setup ( )
{
//Make a clone of the background image. We take images from the "original image"
//when we want to erase a sprite and re-draw it elsewhere.
if ( DrawingArea . BackgroundImage = = null )
{
DrawingArea . BackgroundImage = new Bitmap ( 800 , 600 ) ;
Graphics . FromImage ( DrawingArea . BackgroundImage ) . FillRectangle ( new SolidBrush ( Form . DefaultBackColor ) ,
new Rectangle ( 0 , 0 , 800 , 600 ) ) ; //Fill it with the default background color.
}
MyOriginalImage = ( Image ) DrawingArea . BackgroundImage . Clone ( ) ; //Duplicate it and store it
//The messagefilter allows us to check for keypresses.
Application . AddMessageFilter ( MessageFilter ) ;
//Set up the timer.
MyTimer . Interval = 10 ;
MyTimer . Tick + = TimerTick ;
MyTimer . Start ( ) ;
//ThreadTimer = new System.Threading.Timer(TryTimer, null, 0, 10);
//Add a mouseclick event
DrawingArea . MouseClick + = MouseClickOnBox ;
DrawingArea . MouseHover + = MouseHover ;
DrawingArea . MouseMove + = MouseMove ;
//Add a function to be called when the parent form is resized. This keeps things from
//Misbehaving immediately after a resize.
Form tParent = ( Form ) DrawingArea . FindForm ( ) ;
//tParent.ResizeEnd += ProcessImageResize;
tParent . SizeChanged + = ProcessImageResize ;
}
/// <summary>
/// Change the Tick Interval. By default, the spritecontroller does a tick every 10ms, which
/// is very fast. Some people may prefer it to happen less regularly. Must be > 5, and less than 1001
/// </summary>
/// <param name="newTickMilliseconds">The new tick interval</param>
public void ChangeTickInterval ( int newTickMilliseconds )
{
if ( newTickMilliseconds < 5 ) return ;
if ( newTickMilliseconds > 1000 ) return ;
MyTimer . Interval = newTickMilliseconds ;
}
//private void TryTimer(object state)
//{
// if (System.Threading.Monitor.TryEnter(lockObject,10))
// {
// try
// {
// // Work here
// DoTick(null,null);
// }
// finally
// {
// System.Threading.Monitor.Exit(lockObject);
// }
// }
//}
/// <summary>
/// Allow the sprite sort-method to be overridden.
/// </summary>
/// <example>
/// The default sprite sort method is:
/// <code lang="C#">
/// SpriteComparisonDelegate = delegate (Sprite first, Sprite second) { return first.Zvalue.CompareTo(second.Zvalue); };
/// </code>
/// Which compares just the Zvalues of the two sprites. Often you will want to have a more refined sort. The sort
/// order determines which sprites appear on top of other sprites. In the default state, if two sprites have the
/// same Zvalue, it is very uncleaer which one will draw on top of the other one. By overridding this sort function,
/// you can specify a very precise order of which sprite is on top and which is behind.
/// </example>
public Comparison < Sprite > SpriteComparisonDelegate = null ;
/// <summary>
/// This is what happens when someone clicks on the PictureBox. We want to pass any Click events to the Sprite
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MouseClickOnBox ( object sender , MouseEventArgs e )
{
List < Sprite > SpritesHere = SpritesAtPoint ( e . Location ) ;
foreach ( Sprite one in SpritesHere . ToList ( ) )
{
one . ClickedOn ( SpriteCollisionMethod . rectangle ) ;
if ( one . SpriteAtPictureBoxPoint ( e . Location , SpriteCollisionMethod . transparency ) )
one . ClickedOn ( SpriteCollisionMethod . transparency ) ;
}
}
/// <summary>
/// Check to see if we are hovering over anything
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MouseHover ( object sender , EventArgs e )
{
if ( MousePoint = = Point . Empty ) return ;
List < Sprite > SpritesHere = SpritesAtPoint ( MousePoint ) ;
Point Place = DrawingArea . PointToClient ( Cursor . Position ) ;
foreach ( Sprite one in SpritesHere . ToList ( ) )
{
one . HoverOver ( ) ;
if ( one . SpriteAtPictureBoxPoint ( Place , SpriteCollisionMethod . transparency ) )
one . HoverOverTransparent ( ) ;
}
}
//Track when we move over a new sprite, and when we leave a sprite. Check to see if the sprite
//Is transparent at that spot to determine if we are moving over transparent or just in the rectangle
private void MouseMove ( object sender , MouseEventArgs e )
{
try {
MousePoint = e . Location ;
List < Sprite > SpritesHere = SpritesAtPoint ( e . Location ) ;
List < Sprite > OldSprites = new List < Sprite > ( ) ;
List < Sprite > OldSpritesTransparent = new List < Sprite > ( ) ;
List < Sprite > NewSpritesTransparent = new List < Sprite > ( ) ;
OldSprites . AddRange ( SpritesUnderMouse ) ;
OldSpritesTransparent . AddRange ( SpritesUnderMouseTransparent ) ;
SpritesUnderMouse . Clear ( ) ;
SpritesUnderMouseTransparent . Clear ( ) ;
bool IsTransparent = false ;
Point Place = MousePoint ;
foreach ( Sprite one in SpritesHere . ToList ( ) )
{
IsTransparent = false ;
//Console.WriteLine("Testing mouseover");
if ( one . SpriteAtPictureBoxPoint ( Place , SpriteCollisionMethod . transparency ) )
{
// Console.WriteLine("Is Transparent!");
IsTransparent = true ;
NewSpritesTransparent . Add ( one ) ;
}
if ( ! OldSprites . Contains ( one ) )
{
//This is the first time we have run into it
one . Enter ( ) ;
}
if ( IsTransparent & & ! OldSpritesTransparent . Contains ( one ) )
{
//Console.WriteLine("Calling EnterTransparent");
one . EnterTransparent ( ) ;
}
if ( IsTransparent )
{
OldSpritesTransparent . Remove ( one ) ;
}
OldSprites . Remove ( one ) ;
}
//Now, anything we have not "removed" is a sprite we are no longer over.
foreach ( Sprite donewith in OldSprites . ToList ( ) )
{
donewith . Leave ( ) ;
}
foreach ( Sprite donewith in OldSpritesTransparent . ToList ( ) )
{
//Console.WriteLine("Calling LeaveTransparent");
donewith . LeaveTransparent ( ) ;
}
SpritesUnderMouse . AddRange ( SpritesHere ) ;
SpritesUnderMouseTransparent . AddRange ( NewSpritesTransparent ) ;
}
catch ( AggregateException ee )
{
Console . WriteLine ( ee . ToString ( ) ) ;
}
}
/// <summary>
/// Replace the image on which the sprites are drawn. Use this when you move to a new playing field,
/// or want to have a different background
/// <example>
/// Replacing the background image is actually a lot more complex than you might imagine. Once you use the
/// below code, it can be done without any problem. But you need to do it this way, or it just goofs up in
/// a number of small ways.
/// You need to tell the sprite controller that you are replacing the background image,
/// and you need to change the image to that image as well.Because the Images are actually
/// pointers to memory where the image sets, changes to one image will affect the other image.This goofs
/// things up, so what we do is duplicate the image twice, and tell the sprite controller to use one of the
/// copies and then set the background to be the other one of the two copies.Finally, we tell the picturebox
/// to invalidate itself.That does everything that is needed.
/// <code lang="C#">
/// void ReplaceBackground(Image NewBackground)
///{
/// if (MyController == null) return;
/// if (NewBackground == null) return;
///
/// Image OneImage = new Bitmap(NewBackground);
/// MyController.ReplaceOriginalImage(OneImage);
///
/// Image TwoImage = new Bitmap(NewBackground);
/// pb_map.BackgroundImage = TwoImage;
/// pb_map.Invalidate();
///}
/// </code>
/// </example>
/// </summary>
/// <param name="tImage">The new image that all sprites will be drawn on</param>
public void ReplaceOriginalImage ( Image tImage )
{
if ( MyOriginalImage = = null )
{
MyOriginalImage = ( Image ) DrawingArea . BackgroundImage . Clone ( ) ;
}
else
{
Graphics . FromImage ( MyOriginalImage ) . Clear ( Color . Transparent ) ; //erase the old image
Graphics . FromImage ( MyOriginalImage ) . DrawImage ( tImage , new Rectangle ( 0 , 0 , MyOriginalImage . Width , MyOriginalImage . Height ) ) ;
Graphics . FromImage ( DrawingArea . BackgroundImage ) . Clear ( Color . Transparent ) ; //erase the old image
Graphics . FromImage ( DrawingArea . BackgroundImage ) . DrawImage ( tImage , new Rectangle ( 0 , 0 , MyOriginalImage . Width , MyOriginalImage . Height ) ) ;
}
DrawingArea . Invalidate ( ) ;
}
/// <summary>
/// Notify the sprite controller that you have changed the background image on the
/// PictureBox. Whatever background is on the picturebox is now used to draw all the sprites on.
/// </summary>
public void ReplaceOriginalImage ( )
{
if ( MyOriginalImage = = null )
{
MyOriginalImage = ( Image ) DrawingArea . BackgroundImage . Clone ( ) ;
}
else
{
Graphics . FromImage ( MyOriginalImage ) . DrawImage ( DrawingArea . BackgroundImage , new Rectangle ( 0 , 0 , MyOriginalImage . Width , MyOriginalImage . Height ) ) ;
}
}
/// <summary>
/// The function called by the timer every 10 millisecods We also call do_tick, which
/// is the function defined by the user. This is usually where they will do the majority of the work.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TimerTick ( object sender , EventArgs e )
{
//If we have added a function to call on the timer, do it.
DoTick ( sender , e ) ;
Tick ( ) ;
}
/// <summary>
/// The function called by the timer every 10 millisecods This is usually where you will do the majority of the work.
/// You can define this manually, or when you <see cref="SpriteLibrary.SpriteController.SpriteController(PictureBox, EventHandler)">instantiate the SpriteController</see>
/// </summary>
/// <example>
/// The Sprite controller uses a <see cref="System.Windows.Forms.Timer">System.Windows.Forms.Timer.</see> This timer is notoriously un-precise, but it is very
/// easy to set up initially. It tries to fire off every 10 milliseconds, but it can fire off incredibly
/// slowly if you have long pieces of code; the DoTick function needs to finish before it can start again. You want all your
/// functions to run as quickly as possible to avoid things looking jerky.
/// Most programs you will make using the sprite library will begin by tapping into the DoTick Event.
/// Every time the sprite controller is ready to pass control back to your program, it will call
/// the DoTick event. You want to see if you should be doing anything, and then exiting the do-tick function.
/// <code lang = "C#">
/// public partial class ShootingFieldForm : Form
/// {
/// public ShootingFieldForm()
/// {
/// InitializeComponent();
/// MainDrawingArea.BackgroundImage = Properties.Resources.Background;
/// MainDrawingArea.BackgroundImageLayout = ImageLayout.Stretch;
/// MySpriteController = new SpriteController(MainDrawingArea, CheckForKeyPress);
/// }
///
/// private void CheckForKeyPress(object sender, EventArgs e)
/// {
/// bool left = false;
/// bool right = false;
/// bool space = false;
/// bool didsomething = false;
/// TimeSpan duration = DateTime.Now - LastMovement;
/// if (duration.TotalMilliseconds < 100)
/// return;
/// LastMovement = DateTime.Now;
/// if (MySpriteController.IsKeyPressed(Keys.A) || MySpriteController.IsKeyPressed(Keys.Left))
/// {
/// left = true;
/// }
/// if (MySpriteController.IsKeyPressed(Keys.D)||MySpriteController.IsKeyPressed(Keys.Right))
/// {
/// right = true;
/// }
/// if (left && right) return; //do nothing if we conflict
/// if (left)
/// {
/// if (LastDirection != MyDir.left)
/// {
/// Spaceship.SetSpriteDirectionDegrees(180);
/// //We want to only change animation once. Every time we change
/// //the animation, it starts at the first frame again.
/// Spaceship.ChangeAnimation(0);
/// LastDirection = MyDir.left;
/// }
/// didsomething = true;
/// Spaceship.MovementSpeed = 15;
/// Spaceship.AutomaticallyMoves = true;
/// }
/// if (right)
/// {
/// if (LastDirection != MyDir.right)
/// {
/// Spaceship.SetSpriteDirectionDegrees(0);
/// Spaceship.ChangeAnimation(0);
/// LastDirection = MyDir.right;
/// }
/// didsomething = true;
/// Spaceship.AutomaticallyMoves = true;
/// Spaceship.MovementSpeed = 15;
/// }
/// if(!didsomething)
/// {
/// LastDirection = MyDir.stopped;
/// //No keys pressed. Stop moving
/// Spaceship.MovementSpeed = 0;
/// }
/// }
/// </code>
/// </example>
public event EventHandler DoTick = delegate { } ;
/// <summary>
/// Process a form resize by recalculating all the picturebox locations for all sprites.
/// </summary>
/// <param name="sender">The form</param>
/// <param name="e">Form event args</param>
internal void ProcessImageResize ( object sender , EventArgs e )
{
//Go through all sprites and recalculate the Ratio.
foreach ( Sprite oneSprite in Sprites )
{
oneSprite . RecalcPictureBoxLocation ( ) ;
}
}
/// <summary>
/// Count the number of sprites that were duplicated from the sprite with the specified name. When you use a
/// <see cref="SpriteLibrary.SpriteController.DuplicateSprite(string)">SpriteController.DuplicateSprite(string)</see>
/// command, it creates a new sprite that is based off the named sprite. This function will count those duplicated sprites.
/// </summary>
/// <param name="Name">The name to look for</param>
/// <returns>The count of sprites that are duplicates of the specified name</returns>
public int CountSpritesBasedOff ( string Name )
{
int count = 0 ;
foreach ( Sprite OneSprite in Sprites )
{
if ( OneSprite . SpriteOriginName = = Name )
count + + ;
}
return count ;
}
/// <summary>
/// Return a list of all sprites
/// </summary>
/// <returns>A list of all sprites</returns>
public List < Sprite > AllSprites ( )
{
return Sprites ;
}
/// <summary>
/// Return all sprites that were based off a particular sprite name.
/// When you use a
/// <see cref="SpriteLibrary.SpriteController.DuplicateSprite(string)">SpriteController.DuplicateSprite(string)</see>
/// command, it creates a new sprite that is based off the named sprite. This function returns a list of those
/// duplicated sprites.
/// </summary>
/// <param name="SpriteName">The sprite name to find</param>
/// <returns>A list of sprites that were based off the named sprite</returns>
public List < Sprite > SpritesBasedOff ( string SpriteName )
{
List < Sprite > newList = new List < Sprite > ( ) ;
foreach ( Sprite one in Sprites )
{
if ( one . SpriteOriginName = = SpriteName )
newList . Add ( one ) ;
}
return newList ;
}
/// <summary>
/// Return a list of all sprites which have been drawn on the image
/// </summary>
/// <returns>A list of sprites that have been drawn</returns>
public List < Sprite > SpritesThatHaveBeenDrawn ( )
{
List < Sprite > newList = new List < Sprite > ( ) ;
foreach ( Sprite one in Sprites )
{
if ( one . HasBeenDrawn )
newList . Add ( one ) ;
}
return newList ;
}
/// <summary>
/// Return a list of all sprites which are not master sprites (which are duplicates of something)
/// </summary>
/// <returns>A list of sprites</returns>
public List < Sprite > SpritesBasedOffAnything ( )
{
List < Sprite > newList = new List < Sprite > ( ) ;
foreach ( Sprite one in Sprites )
{
if ( one . SpriteOriginName ! = "" & & one . SpriteOriginName ! = null )
newList . Add ( one ) ;
}
return newList ;
}
2017-09-10 23:57:31 +02:00
/// <summary>
/// Get a list of all your named sprites. These should just be your template sprites.
/// </summary>
/// <returns>A list containing all the named sprites</returns>
public List < Sprite > AllNamedSprites ( )
{
List < Sprite > tList = new List < Sprite > ( ) ;
foreach ( Sprite one in Sprites )
{
if ( one . SpriteName ! = "" )
tList . Add ( one ) ;
}
return tList ;
}
2017-09-03 03:35:32 +02:00
/// <summary>
/// Return an adjustment ratio. This is the image-size to picture-box ratio.
/// It is used for calculating precise pixels or picture-box locations.
/// </summary>
/// <returns>A SpriteAdjustmentRatio containing the current ratio of picture-box pixels to image-box pixels</returns>
public SpriteAdjustmentRatio ReturnAdjustmentRatio ( )
{
//if (_AdjustmentRatio.height_ratio != 0 && _AdjustmentRatio.width_ratio != 0)
// return _AdjustmentRatio;
//default to stretch
lock ( DrawingArea ) lock ( MyOriginalImage )
{
SpriteAdjustmentRatio Ratio = new SpriteAdjustmentRatio ( ) ;
switch ( DrawingArea . BackgroundImageLayout )
{
case ImageLayout . Center :
case ImageLayout . None :
case ImageLayout . Tile :
case ImageLayout . Zoom :
case ImageLayout . Stretch :
default :
//This is the code for Stretch.
double CRW = DrawingArea . ClientRectangle . Width ;
double MOIW = MyOriginalImage . Width ;
Ratio . width_ratio = CRW / MOIW ;
double CRH = DrawingArea . ClientRectangle . Height ;
double MOIH = MyOriginalImage . Height ;
Ratio . height_ratio = CRH / MOIH ;
break ;
}
_AdjustmentRatio = Ratio ;
return Ratio ;
}
}
/// <summary>
/// This takes a point, the location on a picturebox, and returns the corresponding point on the BackgroundImage.
/// Picturebox locations are "sloppy"; the background image locations are very precise. Since this takes a "sloppy"
/// number and returns a precise number, it does some rounding to figure out where the specified location is.
/// </summary>
/// <param name="LocationOnPicturebox">A point on the picturebox that you want the corresponding image pixel location for.</param>
/// <returns>A point (x,y) on the background image which corresponds to the picture-box coordinates you sent into the function.</returns>
public Point ReturnPointAdjustedForImage ( Point LocationOnPicturebox )
{
SpriteAdjustmentRatio Ratio = ReturnAdjustmentRatio ( ) ;
Point returnedPoint = new Point ( ( int ) ( LocationOnPicturebox . X / Ratio . width_ratio ) , ( int ) ( LocationOnPicturebox . Y / Ratio . height_ratio ) ) ;
return returnedPoint ;
}
/// <summary>
/// Return the height of an object in picture-box terms. It is basically the virtual height
/// of the sprite or other item.
/// </summary>
/// <param name="Height">The image-box heigh (or sprite height)</param>
/// <returns>An integer that corresponds to the hight as displayed in the picturebox</returns>
public int ReturnPictureBoxAdjustedHeight ( int Height )
{
SpriteAdjustmentRatio Ratio = ReturnAdjustmentRatio ( ) ;
int returnedAmount = ( int ) ( Height * Ratio . height_ratio ) ;
return returnedAmount ;
}
/// <summary>
/// Return the width of an object in picture-box terms. It takes the width of a sprite or other
/// item that is being displayed on the screen, and calculates the width as displayed in the
/// picture-box (taking into consideration stretching or shrinking)
/// </summary>
/// <param name="Width">An integer width of the drawn item</param>
/// <returns>An integer that contains the number of pixels wide it is on the picturebox</returns>
public int ReturnPictureBoxAdjustedWidth ( int Width )
{
SpriteAdjustmentRatio Ratio = ReturnAdjustmentRatio ( ) ;
int returnedAmount = ( int ) ( Width * Ratio . width_ratio ) ;
return returnedAmount ;
}
/// <summary>
/// This does the reverse of an adjusted point. It takes a point on the image and
/// transforms it to one on the PictureBox
/// </summary>
/// <param name="LocationOnImage">A point on the image, using the x and y pixels on the image</param>
/// <returns>A location that can be used on the picture-box, taking into consideration the image being stretched.</returns>
public Point ReturnPictureBoxAdjustedPoint ( Point LocationOnImage )
{
SpriteAdjustmentRatio Ratio = ReturnAdjustmentRatio ( ) ;
Point returnedPoint = new Point ( ( int ) ( LocationOnImage . X * Ratio . width_ratio ) , ( int ) ( LocationOnImage . Y * Ratio . height_ratio ) ) ;
return returnedPoint ;
}
/// <summary>
/// Adjust a rectangle that is based on the image, according to the stretch of the picturebox
/// </summary>
/// <param name="ImageRectangle">A rectangle using coordinates from the image</param>
/// <returns>a rectangle that is adjusted for the PictureBox</returns>
public Rectangle AdjustRectangle ( Rectangle ImageRectangle )
{
if ( DrawingArea . BackgroundImageLayout = = ImageLayout . Stretch )
{
SpriteAdjustmentRatio Ratio = ReturnAdjustmentRatio ( ) ;
double width_ratio = Ratio . width_ratio ;
double height_ratio = Ratio . height_ratio ;
int x , y , width , height ;
x = ( int ) ( ImageRectangle . X * width_ratio ) ;
y = ( int ) ( ImageRectangle . Y * height_ratio ) ;
width = ( int ) ( ImageRectangle . Width * width_ratio ) ;
height = ( int ) ( ImageRectangle . Height * height_ratio ) ;
Rectangle newRec = new Rectangle ( x , y , width , height ) ;
return newRec ;
}
return ImageRectangle ; //If we do not know what it is, return the curent
}
/// <summary>
/// Adjust an image point so that it conforms to the picturebox.
/// </summary>
/// <param name="LocationOnImage">The image location</param>
/// <returns>the corresponding point on the PictuerBox</returns>
public Point AdjustPoint ( Point LocationOnImage )
{
SpriteAdjustmentRatio Ratio = ReturnAdjustmentRatio ( ) ;
double width_ratio = Ratio . width_ratio ;
double height_ratio = Ratio . height_ratio ;
int x , y ;
x = ( int ) ( LocationOnImage . X * width_ratio ) ;
y = ( int ) ( LocationOnImage . Y * height_ratio ) ;
return new Point ( x , y ) ;
}
/// <summary>
/// Invalidate a rectangle that is specified in image coordinates
/// </summary>
/// <param name="ImageRectangle">A rectangle based on the image coordinates</param>
/// <param name="QueueUpInvalidation">Whether to do it now, or to queue it up for another time.</param>
public void Invalidate ( Rectangle ImageRectangle , bool QueueUpInvalidation = true )
{
if ( QueueUpInvalidation )
{
InvalidateList . Add ( ImageRectangle ) ;
}
else
{
//Figure out the area we are looking at
if ( DrawingArea . BackgroundImageLayout = = ImageLayout . Stretch )
{
Rectangle newRec = AdjustRectangle ( ImageRectangle ) ;
newRec = new Rectangle ( newRec . Location . X , newRec . Location . Y , newRec . Width + 2 , newRec . Height + 2 ) ;
//Now we invalidate the adjusted rectangle
DrawingArea . Invalidate ( newRec ) ;
}
}
}
/// <summary>
/// Invalidate the entire image on which the sprites are drawn
/// </summary>
/// <param name="QueueUpInvalidation">Whether to do it now, or to queue it up for another time.</param>
public void Invalidate ( bool QueueUpInvalidation = true )
{
Invalidate ( DrawingArea . ClientRectangle ) ;
}
/// <summary>
/// The Background Image on which the sprites are drawn. This image ends up having
/// sprite parts on it. The OriginalImage is the version that is clean. Use
/// ReplaceOriginalImage to replace the background Image.
/// </summary>
public Image BackgroundImage { get { return DrawingArea . BackgroundImage ; } }
/// <summary>
/// The Image from which the background is taken when we erase sprites. The BackgroundImage
/// is the image that contains images of the sprites as well as the background image. Use
/// ReplaceOriginalImage to replace this and the BackgroundImage.
/// </summary>
public Image OriginalImage { get { return MyOriginalImage ; } }
//void Tick()
//{
// BackgroundWorker bw = new BackgroundWorker();
// // this allows our worker to report progress during work
// bw.WorkerReportsProgress = true;
// // what to do in the background thread
// bw.DoWork += new DoWorkEventHandler(
// delegate (object o, DoWorkEventArgs args)
// {
// ThreadTick();
// });
// bw.RunWorkerAsync();
//}
void Tick ( )
{
try
{
//We check for collisions.
for ( int looper = 0 ; looper < Sprites . Count ; looper + + )
{
if ( Sprites [ looper ] ! = null & & ! Sprites [ looper ] . Destroying & & Sprites [ looper ] . HasBeenDrawn )
{
for ( int checkloop = 0 ; checkloop < Sprites . Count ; checkloop + + )
{
if ( Sprites [ checkloop ] ! = null & & ! Sprites [ checkloop ] . Destroying & & Sprites [ checkloop ] . HasBeenDrawn )
{
//Check to see if they have hit
Sprites [ looper ] . CheckSpriteHitsSprite ( Sprites [ checkloop ] , SpriteCollisionMethod . rectangle ) ;
}
}
}
}
//We do a tick for each sprite
//Parallel.ForEach(Sprites.ToList(), tSprite =>
//{
// if (!tSprite.Destroying)
// {
// tSprite.Tick();
// }
//});
foreach ( Sprite tSprite in Sprites . ToList ( ) )
{
if ( ! tSprite . Destroying )
{
tSprite . Tick ( ) ;
}
} ;
foreach ( Sprite tSprite in Sprites . ToList ( ) )
{
if ( ! tSprite . Destroying )
{
tSprite . ActuallyDraw ( ) ;
}
}
foreach ( Rectangle rec in InvalidateList )
{
Invalidate ( rec , false ) ;
}
InvalidateList . Clear ( ) ;
}
catch ( AggregateException e )
{
Console . WriteLine ( e . ToString ( ) ) ;
}
}
/// <summary>
/// Make a duplicate of the specified sprite. The duplicate does not yet have a location.
/// </summary>
/// <param name="What">The sprite to duplicate</param>
/// <returns>A new sprite. If What is null, returns null</returns>
public Sprite DuplicateSprite ( Sprite What )
{
//Make a new sprite that is based off of the old one
if ( What = = null ) return null ;
return new Sprite ( What ) ;
}
/// <summary>
/// Find a sprite that has been named with the specified name. Then duplicate that sprite
/// </summary>
/// <example>
/// Below is a function that creates a sprite based off a name, and puts it at the designated coordinates.
/// <code lang="C#">
/// public void AddSprite(string name, int startx, int starty)
/// {
/// Sprite NewSprite = MySpriteController.DuplicateSprite(What.ToString());
/// if(NewSprite != null)
/// {
/// NewSprite.AutomaticallyMoves = true;
/// NewSprite.CannotMoveOutsideBox = true;
/// NewSprite.SetSpriteDirectionDegrees(180); //left
/// NewSprite.PutBaseImageLocation(new Point(startx, starty));
/// NewSprite.MovementSpeed = 5;
/// }
/// }
/// </code>
/// </example>
/// <param name="Name">The name of a sprite</param>
/// <returns>A duplicate of the specified sprite. It has no location, and does not retain the sprite name.</returns>
public Sprite DuplicateSprite ( string Name )
{
Sprite tSprite = SpriteFromName ( Name ) ;
if ( tSprite = = null ) return null ;
return new Sprite ( tSprite ) ; //Make a new sprite that is based off the original
}
/// <summary>
/// Find a sprite that has a specified name. This returns the actual sprite with that name.
/// You usually want to use DuplicateSprite(Name) to clone the sprite and get one you can
/// destroy. If you destroy a named sprite without duplicating it, you may end up losing
/// it for the remainder of the program.
/// </summary>
/// <param name="Name">A string that matches something added to a sprite with Sprite.SetName</param>
/// <returns>A sprite that has the specified name, or null if no such sprite exists.</returns>
public Sprite SpriteFromName ( string Name )
{
foreach ( Sprite OneSprite in Sprites )
{
if ( OneSprite . SpriteName = = Name )
{ return OneSprite ; }
}
2017-09-11 00:00:59 +02:00
//If we have not found one on this controller, get it from another controller
foreach ( SpriteController SC in LinkedControllers )
{
Sprite Found = SC . SpriteFromNameInternal ( Name ) ;
if ( Found ! = null )
{
//If we get here, we do not have it in our list. Add it to this controller and then return it
AddSprite ( Found ) ;
return Found ;
}
}
2017-09-03 03:35:32 +02:00
return null ;
}
2017-09-11 00:00:59 +02:00
/// <summary>
/// The internal SpriteFromName does not check the linked controllers. Keeps us from entering into an endless loop
/// </summary>
/// <param name="Name"></param>
/// <returns></returns>
internal Sprite SpriteFromNameInternal ( string Name )
{
foreach ( Sprite OneSprite in Sprites )
{
if ( OneSprite . SpriteName = = Name )
{ return OneSprite ; }
}
return null ;
}
2017-09-03 03:35:32 +02:00
/// <summary>
/// Add the specified sprite to the list of sprites we know about. You usually do not need to do this.
/// Sprites add themselves to the controller when you create a new sprite.
/// </summary>
/// <param name="SpriteToAdd">The sprite to add to the sprite-controller</param>
public void AddSprite ( Sprite SpriteToAdd )
{
Sprites . Add ( SpriteToAdd ) ;
2017-09-10 23:57:31 +02:00
AddSpriteToLinkedControllers ( SpriteToAdd ) ;
2017-09-03 03:35:32 +02:00
SortSprites ( ) ;
}
2017-09-10 23:57:31 +02:00
/// <summary>
/// This internal function is for adding named sprites from other controllers to keep them in sync
/// </summary>
/// <param name="SpriteToAdd">The sprite to add if it does not exist yet on this controller</param>
internal void AddSpriteIfNotExists ( Sprite SpriteToAdd )
{
if ( SpriteToAdd . SpriteName = = "" ) return ; //We only add named sprites
Sprite found = SpriteFromName ( SpriteToAdd . SpriteName ) ;
if ( found = = null )
Sprites . Add ( SpriteToAdd ) ;
}
/// <summary>
/// If we are linked to other controllers, add this sprite template to the other controllers also
/// </summary>
/// <param name="SpriteToAdd">The sprite we are trying to add</param>
internal void AddSpriteToLinkedControllers ( Sprite SpriteToAdd )
{
if ( SpriteToAdd . SpriteName = = "" ) return ; //We only add named sprites
foreach ( SpriteController one in LinkedControllers )
{
one . AddSpriteIfNotExists ( SpriteToAdd ) ;
}
}
2017-09-03 03:35:32 +02:00
/// <summary>
/// Tell a sprite to destroy itself. The sprite will have Destroying property set to true from
/// the time you destroy it until it vanishes. Whe you destroy a sprite, it will erase itself
/// and remove itself from the controller. After it is destroyed, it is completely gone.
/// </summary>
/// <param name="what">The Sprite to destroy</param>
public void DestroySprite ( Sprite what )
{
if ( what = = null ) return ;
Sprites . Remove ( what ) ;
if ( ! what . Destroying )
{
what . Destroy ( ) ;
}
}
/// <summary>
/// Remove all sprites (even named sprites that have not yet been displayed)
/// </summary>
public void DestroyAllSprites ( )
{
for ( int i = Sprites . Count - 1 ; i > = 0 ; i - - )
{
Sprites [ i ] . Destroy ( ) ;
}
}
/// <summary>
/// Find the specified Sprite in the controller and change its name to the specified string.
/// You can do the same thing with <see cref="SpriteLibrary.Sprite.SetName(string)">Sprite.SetName(Name)</see>
/// </summary>
/// <param name="What">The Sprite to find</param>
/// <param name="Name">The string to change the name to</param>
public void NameSprite ( Sprite What , string Name )
{
What . SetName ( Name ) ;
}
2017-09-10 23:57:31 +02:00
/// <summary>
/// Link up a sprite controller so that it shares sprites with this other sprite controller. If one sprite controller
/// does not have the named sprite, it will query any linked controllers for that named sprite and copy it to the
/// controller that did not have it. This means you only need to create a sprite once, and you can use it on multiple
/// sprite controllers. In many games, you will want to have a sprite appear on different PictureBoxes, and this is
/// a way to do that. For example, you may want to have a bad-guy running around on the screen, but also have his sprite
/// appear in a bad-guy summary, along with his stats, on the side. Loading sprites can be slow, so this makes things a bit
/// faster by only needing to load them once.
/// </summary>
/// <param name="ControllerToLinkToThis">The sprite-controller to link. You only need to link it one direction,
/// the sprite controller will automatically create a bi-directional link</param>
public void LinkControllersForSpriteTemplateSharing ( SpriteController ControllerToLinkToThis )