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 { /// /// 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: /// /// and /// /// internal class NamespaceDoc { } /// /// 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 /// public enum SpriteCollisionMethod { /// /// 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. /// rectangle, /// /// Draws a circle (ellipse) inside the sprite rectangles and see if those ellipses overlap /// circle, /// /// Check to see if nontransparent portions of a sprite collide. Not working. /// transparency } /// /// 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. /// public struct SpriteAdjustmentRatio { /// /// Divide a picturebox ratio by this to get the image location. Multiply an image location by this to get the picturebox location. /// public double width_ratio; /// /// Divide a picturebox ratio by this to get the image location. Multiply an image location by this to get the picturebox location. /// public double height_ratio; } /// /// The type of pause signals you can give a sprite or the sprite controller /// public enum SpritePauseType { /// /// 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. /// PauseAnimation, /// /// Pause any automatic movement. Movement resumes where it was left off if you unpause. The sprite will /// just sit there until unpaused. /// PauseMovement, /// /// Pause events. Sprite collisions, movement checks, etc are stopped until the unpause. /// PauseEvents, /// /// All pausable things are paused. PauseAnimation, PauseMovement, and PauseEvents. /// PauseAll } /// /// 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. /// /// /// A sprite controller controls animations and /// can help you check for key-presses. To make a sprite controller, /// you need to have one defined for your main form: /// /// SpriteController MySpriteController; /// /// And then, when the form is created, after the InitializeComponents() function, you /// need to configure the drawing area and create the sprite controller: /// /// MainDrawingArea.BackgroundImage = Properties.Resources.Background; /// MainDrawingArea.BackgroundImageLayout = ImageLayout.Stretch; /// MySpriteController = new SpriteController(MainDrawingArea); /// /// In this case, MainDrawingArea is the picturebox where all the sprites will be displayed. /// public class SpriteController { Image MyOriginalImage; //The untainted background PictureBox DrawingArea; //The PictureBox we draw ourselves on List Sprites = new List(); List LinkedControllers = new List(); //Other sprite controllers that we share sprites with /// /// Since everything needs a random number generator, we make one that should be accessible throughout your program. /// public Random RandomNumberGenerator = new Random(); /// /// This is only used by the SpriteController. It allows us to queue up invalidation requests. /// private List InvalidateList = new List(); private KeyMessageFilter MessageFilter = new KeyMessageFilter(); /// /// The count of all the sprites the controller knows about. This includes named /// sprites, which may not be visible. /// 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 SpritesUnderMouse = new List(); private List SpritesUnderMouseTransparent = new List(); private SpriteAdjustmentRatio _AdjustmentRatio; /// /// 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. /// public bool OptimizeForLargeSpriteImages = false; /// /// 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. /// /// /// This is an example of a Form class that defines a SpriteController. The MainDrawingArea is a /// PictureBox. /// /// public partial class ShootingFieldForm : Form /// { /// public ShootingFieldForm() /// { /// InitializeComponent(); /// MainDrawingArea.BackgroundImage = Properties.Resources.Background; /// MainDrawingArea.BackgroundImageLayout = ImageLayout.Stretch; /// MySpriteController = new SpriteController(MainDrawingArea); /// } /// } /// /// /// The PictureBox. /// that the sprites will be drawn in public SpriteController(PictureBox Area) { DrawingArea = Area; Local_Setup(); } /// /// Create a sprite controller, specifying the picturebox on which the sprites /// will be displayed. /// /// /// This is an example of a Form class that defines a SpriteController. The MainDrawingArea is a /// PictureBox. While defining the SpriteController, we /// are also setting a function used for the DoTick. event. /// /// 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 /// } /// } /// /// /// /// The picturebox that the sprites will be drawn in /// A function on the form that you want to have run every tick public SpriteController(PictureBox Area, System.EventHandler TimerTickMethod) { DrawingArea = Area; Local_Setup(); DoTick += new System.EventHandler(TimerTickMethod); } /// /// Define some things and set up some things that need defining at instantiation /// 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; } /// /// 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 /// /// The new tick interval 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); // } // } //} /// /// Allow the sprite sort-method to be overridden. /// /// /// The default sprite sort method is: /// /// SpriteComparisonDelegate = delegate (Sprite first, Sprite second) { return first.Zvalue.CompareTo(second.Zvalue); }; /// /// 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. /// public Comparison SpriteComparisonDelegate = null; /// /// This is what happens when someone clicks on the PictureBox. We want to pass any Click events to the Sprite /// /// /// private void MouseClickOnBox(object sender, MouseEventArgs e) { List 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); } } /// /// Check to see if we are hovering over anything /// /// /// private void MouseHover(object sender, EventArgs e) { if (MousePoint == Point.Empty) return; List 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 SpritesHere = SpritesAtPoint(e.Location); List OldSprites = new List(); List OldSpritesTransparent = new List(); List NewSpritesTransparent = new List(); 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()); } } /// /// 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 /// /// 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. /// /// 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(); ///} /// /// /// /// The new image that all sprites will be drawn on 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(); } /// /// 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. /// 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)); } } /// /// 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. /// /// /// private void TimerTick(object sender, EventArgs e) { //If we have added a function to call on the timer, do it. DoTick(sender, e); Tick(); } /// /// 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 instantiate the SpriteController /// /// /// The Sprite controller uses a System.Windows.Forms.Timer. 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. /// /// 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; /// } /// } /// /// public event EventHandler DoTick = delegate { }; /// /// Process a form resize by recalculating all the picturebox locations for all sprites. /// /// The form /// Form event args internal void ProcessImageResize(object sender, EventArgs e) { //Go through all sprites and recalculate the Ratio. foreach (Sprite oneSprite in Sprites) { oneSprite.RecalcPictureBoxLocation(); } } /// /// Count the number of sprites that were duplicated from the sprite with the specified name. When you use a /// SpriteController.DuplicateSprite(string) /// command, it creates a new sprite that is based off the named sprite. This function will count those duplicated sprites. /// /// The name to look for /// The count of sprites that are duplicates of the specified name public int CountSpritesBasedOff(string Name) { int count = 0; foreach (Sprite OneSprite in Sprites) { if (OneSprite.SpriteOriginName == Name) count++; } return count; } /// /// Return a list of all sprites /// /// A list of all sprites public List AllSprites() { return Sprites; } /// /// Return all sprites that were based off a particular sprite name. /// When you use a /// SpriteController.DuplicateSprite(string) /// command, it creates a new sprite that is based off the named sprite. This function returns a list of those /// duplicated sprites. /// /// The sprite name to find /// A list of sprites that were based off the named sprite public List SpritesBasedOff(string SpriteName) { List newList = new List(); foreach(Sprite one in Sprites) { if (one.SpriteOriginName == SpriteName) newList.Add(one); } return newList; } /// /// Return a list of all sprites which have been drawn on the image /// /// A list of sprites that have been drawn public List SpritesThatHaveBeenDrawn() { List newList = new List(); foreach (Sprite one in Sprites) { if (one.HasBeenDrawn) newList.Add(one); } return newList; } /// /// Return a list of all sprites which are not master sprites (which are duplicates of something) /// /// A list of sprites public List SpritesBasedOffAnything() { List newList = new List(); foreach (Sprite one in Sprites) { if (one.SpriteOriginName != "" && one.SpriteOriginName != null) newList.Add(one); } return newList; } /// /// Get a list of all your named sprites. These should just be your template sprites. /// /// A list containing all the named sprites public List AllNamedSprites() { List tList = new List(); foreach(Sprite one in Sprites) { if (one.SpriteName != "") tList.Add(one); } return tList; } /// /// Return an adjustment ratio. This is the image-size to picture-box ratio. /// It is used for calculating precise pixels or picture-box locations. /// /// A SpriteAdjustmentRatio containing the current ratio of picture-box pixels to image-box pixels 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; } } /// /// 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. /// /// A point on the picturebox that you want the corresponding image pixel location for. /// A point (x,y) on the background image which corresponds to the picture-box coordinates you sent into the function. 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; } /// /// Return the height of an object in picture-box terms. It is basically the virtual height /// of the sprite or other item. /// /// The image-box heigh (or sprite height) /// An integer that corresponds to the hight as displayed in the picturebox public int ReturnPictureBoxAdjustedHeight(int Height) { SpriteAdjustmentRatio Ratio = ReturnAdjustmentRatio(); int returnedAmount = (int)(Height * Ratio.height_ratio); return returnedAmount; } /// /// 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) /// /// An integer width of the drawn item /// An integer that contains the number of pixels wide it is on the picturebox public int ReturnPictureBoxAdjustedWidth(int Width) { SpriteAdjustmentRatio Ratio = ReturnAdjustmentRatio(); int returnedAmount = (int)(Width * Ratio.width_ratio); return returnedAmount; } /// /// This does the reverse of an adjusted point. It takes a point on the image and /// transforms it to one on the PictureBox /// /// A point on the image, using the x and y pixels on the image /// A location that can be used on the picture-box, taking into consideration the image being stretched. 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; } /// /// Adjust a rectangle that is based on the image, according to the stretch of the picturebox /// /// A rectangle using coordinates from the image /// a rectangle that is adjusted for the PictureBox 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 } /// /// Adjust an image point so that it conforms to the picturebox. /// /// The image location /// the corresponding point on the PictuerBox 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); } /// /// Invalidate a rectangle that is specified in image coordinates /// /// A rectangle based on the image coordinates /// Whether to do it now, or to queue it up for another time. 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); } } } /// /// Invalidate the entire image on which the sprites are drawn /// /// Whether to do it now, or to queue it up for another time. public void Invalidate(bool QueueUpInvalidation = true) { Invalidate(DrawingArea.ClientRectangle); } /// /// 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. /// public Image BackgroundImage { get { return DrawingArea.BackgroundImage; } } /// /// 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. /// 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()); } } /// /// Make a duplicate of the specified sprite. The duplicate does not yet have a location. /// /// The sprite to duplicate /// A new sprite. If What is null, returns null 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); } /// /// Find a sprite that has been named with the specified name. Then duplicate that sprite /// /// /// Below is a function that creates a sprite based off a name, and puts it at the designated coordinates. /// /// 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; /// } /// } /// /// /// The name of a sprite /// A duplicate of the specified sprite. It has no location, and does not retain the sprite name. 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 } /// /// 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. /// /// A string that matches something added to a sprite with Sprite.SetName /// A sprite that has the specified name, or null if no such sprite exists. public Sprite SpriteFromName(string Name) { foreach (Sprite OneSprite in Sprites) { if (OneSprite.SpriteName == Name) { return OneSprite; } } //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; } } return null; } /// /// The internal SpriteFromName does not check the linked controllers. Keeps us from entering into an endless loop /// /// /// internal Sprite SpriteFromNameInternal(string Name) { foreach (Sprite OneSprite in Sprites) { if (OneSprite.SpriteName == Name) { return OneSprite; } } return null; } /// /// 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. /// /// The sprite to add to the sprite-controller public void AddSprite(Sprite SpriteToAdd) { Sprites.Add(SpriteToAdd); AddSpriteToLinkedControllers(SpriteToAdd); SortSprites(); } /// /// This internal function is for adding named sprites from other controllers to keep them in sync /// /// The sprite to add if it does not exist yet on this controller 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); } /// /// If we are linked to other controllers, add this sprite template to the other controllers also /// /// The sprite we are trying to add internal void AddSpriteToLinkedControllers(Sprite SpriteToAdd) { if (SpriteToAdd.SpriteName == "") return; //We only add named sprites foreach (SpriteController one in LinkedControllers) { one.AddSpriteIfNotExists(SpriteToAdd); } } /// /// 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. /// /// The Sprite to destroy public void DestroySprite(Sprite what) { if (what == null) return; Sprites.Remove(what); if (!what.Destroying) { what.Destroy(); } } /// /// Remove all sprites (even named sprites that have not yet been displayed) /// public void DestroyAllSprites() { for(int i= Sprites.Count -1; i>=0; i--) { Sprites[i].Destroy(); } } /// /// Find the specified Sprite in the controller and change its name to the specified string. /// You can do the same thing with Sprite.SetName(Name) /// /// The Sprite to find /// The string to change the name to public void NameSprite(Sprite What, string Name) { What.SetName(Name); } /// /// 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. /// /// The sprite-controller to link. You only need to link it one direction, /// the sprite controller will automatically create a bi-directional link public void LinkControllersForSpriteTemplateSharing(SpriteController ControllerToLinkToThis) { if (ControllerToLinkToThis == null) return; if(!LinkedControllers.Contains(ControllerToLinkToThis)) { LinkedControllers.Add(ControllerToLinkToThis); } ControllerToLinkToThis.LinkControllersForSpriteTemplateSharing(this); //link the other direction also } /// /// Unlink a previously linked controller. If you have linked a controller from a different window and are trying to /// kill off the controller in a window you are closing, you want to unlink them as the window closes. We take a brief /// moment to copy over any templates that have not yet been copied over. /// /// The public void UnlinkControllersForSpriteTemplateSharing(SpriteController ControllerToUnlink) { if (ControllerToUnlink == null) return; //nothing to do. if (LinkedControllers.Contains(ControllerToUnlink)) { LinkedControllers.Remove(ControllerToUnlink); } ControllerToUnlink.UnlinkControllersForSpriteTemplateSharingInternal(this); List MySpriteTemplates = AllNamedSprites(); List TheirSpriteTemplates = ControllerToUnlink.AllNamedSprites(); foreach (Sprite one in MySpriteTemplates) ControllerToUnlink.AddSpriteIfNotExists(one); foreach (Sprite one in TheirSpriteTemplates) AddSpriteIfNotExists(one); } /// /// This unlinks the second half. This is an internal function so people using SpriteController cannot accidentally /// unlink half a controller. /// /// internal void UnlinkControllersForSpriteTemplateSharingInternal(SpriteController ControllerToUnlink) { if (ControllerToUnlink == null) return; //nothing to do. if (LinkedControllers.Contains(ControllerToUnlink)) { LinkedControllers.Remove(ControllerToUnlink); } } /// /// This takes a point, as given by the mouse-click args, and returns the sprites at that point. Different /// functions use different coordinates, whether based off the background image, or based off the picturebox. /// This one uses the picturebox coordinates. So you can use this directly from a MouseDown or MouseUp function. /// /// The picture-box point being clicked on /// A list of sprites that are all at the specified point. public List SpritesAtPoint(Point Location) { List tList = new List(); foreach (Sprite OneSprite in Sprites) { if (OneSprite.HasBeenDrawn && OneSprite.SpriteAtPictureBoxPoint(Location)) { tList.Add(OneSprite); } } return tList; } /// /// This takes a point, as as specified on the image, and returns the sprites at that point. Different /// functions use different coordinates, whether based off the background image, or based off the picturebox. /// This one uses the background image coordinates. Use SpritesAdPoint() if you are doing something based off /// a MouseUp or MouseDown function. This is used for functions based on sprite location or based off the absoloute /// location (using the background image location is much more precise than the visible location in the picturebox) /// /// The point being looked at /// A list of sprites that are all at the specified image point public List SpritesAtImagePoint(Point Location) { List tList = new List(); foreach (Sprite OneSprite in Sprites) { if (OneSprite.HasBeenDrawn && OneSprite.SpriteAtImagePoint(Location)) { tList.Add(OneSprite); } } return tList; } /// /// Return a list of all the sprites that intersect with the given background-image-based rectangle /// /// The rectangle on the image we are trying to find /// A list of the sprites that have any portion of it inside the rectangle public List SpritesInImageRectangle(Rectangle Location) { List tList = new List(); foreach (Sprite OneSprite in Sprites) { if (OneSprite.HasBeenDrawn && OneSprite.SpriteIntersectsRectangle(Location)) { tList.Add(OneSprite); } } return tList; } /// /// Check to see if any keys are pressed. There is a small glitch with the /// key-pressed system. If the form loses focus, and someone releases a key, the key-up is never /// triggered. It is a good thing to ResetKeypressState() occasionally if you think your form may have /// lost focus. /// /// True if a key is pressed, false if no keys are pressed. public bool IsKeyPressed() { return MessageFilter.IsKeyPressed(); } /// /// Return a list of all the keys that are currently pressed. There is a small glitch with the /// key-pressed system. If the form loses focus, and someone releases a key, the key-up is never /// triggered. It is a good thing to ResetKeypressState() occasionally if you think your form may have /// lost focus. /// /// A List of Keys which are currently considered to be pressed. public List KeysPressed() { return MessageFilter.KeysPressed(); } /// /// Check to see if the given key is pressed. There is a small glitch with the /// key-pressed system. If the form loses focus, and someone releases a key, the key-up is never /// triggered. It is a good thing to ResetKeypressState() occasionally if you think your form may have /// lost focus. /// /// The key to check to see if it is pressed /// True if the key is pressed, false if that key is not pressed public bool IsKeyPressed(Keys k) { return MessageFilter.IsKeyPressed(k); } /// /// If you want to have a KeyDown function that is triggered by a keypress function, add the event here. /// The event should have the parameters (object sender, KeyEventArgs e) /// /// The function to set public void RegisterKeyDownFunction(SpriteKeyEventHandler Func) { MessageFilter.KeyDown += Func; } /// /// If you want to have a KeyUp function that is triggered by a keypress function, add the event here. /// The event should have the parameters (object sender, KeyEventArgs e) /// /// The function to set public void RegisterKeyUpFunction(SpriteKeyEventHandler Func) { MessageFilter.KeyUp += Func; } /// /// Reset the keypress status. Sometimes the sprite controller misses a key being released (usually /// because a window has taken priority, or something has changed). Calling this function will reset /// the stored memory of whether a key has been pressed. /// public void ResetKeypressState() { MessageFilter.ResetState(); } /// /// Change the display order of the specified sprite so it goes in front of all other sprites. /// /// The sprite we want to show up in front public void SpriteToFront(Sprite What) { What.Zvalue = 100; } /// /// Change the display order of the specified sprite so it goes behind all other sprites. /// /// The sprite to send behind all other sprites public void SpriteToBack(Sprite What) { What.Zvalue = 0; } /// /// Change the display order of the specified sprite so it is more likely to go behind all other sprites. /// /// The sprite to send behind all other sprites public void SpriteBackwards(Sprite What) { What.Zvalue--; } /// /// Change the display order of the specified sprite so it is more likely to go in front of other sprites /// /// The sprite to send behind all other sprites public void SpriteForwards(Sprite What) { What.Zvalue++; } /// /// Change the display order of the sprites such that the specified sprite appears behind the other sprite. /// /// The sprite we are changing the display order of /// The sprite we want to go behind public void PlaceSpriteBehind(Sprite WhatToSend, Sprite ToGoBehind) { if (WhatToSend == ToGoBehind) return; if (WhatToSend == null) return; if (ToGoBehind == null) return; WhatToSend.Zvalue = ToGoBehind.Zvalue - 1; } /// /// Make the sprite go in front of the specified sprite. /// /// The sprite to change the display order of /// The sprite we want to make sure we display in front of public void PlaceSpriteInFrontOf(Sprite WhatToSend, Sprite ToGoInFrontOf) { if (WhatToSend == ToGoInFrontOf) return; if (WhatToSend == null) return; if (ToGoInFrontOf == null) return; WhatToSend.Zvalue = ToGoInFrontOf.Zvalue + 1; } //****************************// //******* SOUND Stuff *******// private struct SoundEntry { public string SoundName; public bool HasBeenPlayed; } List MySounds = new List(); /// /// Play a sound that we can check to see if it has completed. /// /// The sound to play /// The name, which we can use to determine if it has finished. public void SoundPlay(System.IO.Stream ToPlay, string Name) { if (SoundIsFinished(Name)) { PlayAsync(ToPlay, Name, SoundIsDone); RegisterSound(Name); } } /// /// Play a sound bit in a separate thread. When the thread is done, set a bool saying that /// /// The sound to play /// The string that we can use to track the status of the sound /// A function that gets called when the sound is complete private void PlayAsync(System.IO.Stream ToPlay, string RegisterName, EventHandler WhenDone) { ToPlay.Position = 0; System.Threading.ThreadPool.QueueUserWorkItem(delegate { using (System.Media.SoundPlayer player = new System.Media.SoundPlayer(ToPlay)) { player.PlaySync(); } if (WhenDone != null) WhenDone(RegisterName, EventArgs.Empty); }); //new System.Threading.Thread(() => { // var c = new System.Windows.Media.MediaPlayer(); // byte[] buffer = new byte[ToPlay.Length]; // int totalBytesCopied; // for (totalBytesCopied = 0; totalBytesCopied < ToPlay.Length;) // totalBytesCopied += ToPlay.Read(buffer, totalBytesCopied, Convert.ToInt32(ToPlay.Length) - totalBytesCopied); // Uri tURI = Create_Memory_Resource_Uri(buffer, totalBytesCopied); // c.Open(tURI); // c.Play(); // if (WhenDone != null) WhenDone(RegisterName, EventArgs.Empty); //}).Start(); } //Uri Create_Memory_Resource_Uri(byte[] in_memory_resource, int Size) //{ // MemoryStream packStream = new MemoryStream(); // using ( // Package pack = // Package.Open(packStream, FileMode.Create, FileAccess.ReadWrite)) // { // Uri packUri = new Uri("AnyBeforeColon:"); // PackageStore.AddPackage(packUri, pack); // Uri packPartUri = new Uri("/AnyAfterSlash", UriKind.Relative); // //PackagePart packPart = // // pack.CreatePart(packPartUri, "application/vnd.ms-opentype"); // PackagePart packPart = // pack.CreatePart(packPartUri, "AnyBeforeSlash/AnyAfterSlash"); // //MemoryStream resourceStream = new MemoryStream(in_memory_resource); // //CopyStream(resourceStream, packPart.GetStream()); // packPart.GetStream().Write(in_memory_resource, 0, Size); // Uri memory_resource_uri = // PackUriHelper.Create(packUri, packPart.Uri); // return memory_resource_uri; // } //} private void SoundIsDone(object sender, EventArgs e) { string Name = (string)sender; RemoveEntry(Name); SoundEntry newsound = new SoundEntry(); newsound.SoundName = Name; newsound.HasBeenPlayed = true; //Mark it as done MySounds.Add(newsound); } private void RemoveEntry(string Name) { if (Name == null || Name == "") return; for (int count = MySounds.Count - 1; count >= 0; count--) { if (MySounds[count].SoundName == Name) { MySounds.RemoveAt(count); } } } private void RegisterSound(string Name) { if (Name == null || Name == "") return; RemoveEntry(Name); SoundEntry newsound = new SoundEntry(); newsound.SoundName = Name; newsound.HasBeenPlayed = false; MySounds.Add(newsound); } private void RegisterSoundDone(string Name) { if (Name == null || Name == "") return; RemoveEntry(Name); SoundEntry newsound = new SoundEntry(); newsound.SoundName = Name; newsound.HasBeenPlayed = true; MySounds.Add(newsound); } /// /// Check to see if the specified sound has finished playing /// /// The name of the sound /// True if the sound is not currently playing. False if it is currently playing. public bool SoundIsFinished(string Name) { foreach(SoundEntry one in MySounds.ToList()) { if (one.SoundName == Name) return one.HasBeenPlayed; } return true; //It does not exist, therefore it is not playing } /// /// Pause everything. It loops through all the sprites in the SpriteController and sends the specified /// SpritePauseType to each one. Look at the documentation for SpritePauseType to determine which pause /// type to use. /// /// The SpritePauseType to send all sprites public void Pause(SpritePauseType What = SpritePauseType.PauseAll) { for(int i=0; i< Sprites.Count; i++) { Sprites[i].Pause(What); } } /// /// un-Pause everything. This will send the specified SpritePauseType unpause command /// to all sprites. /// /// The SpritePauseType to unpause for all sprites public void UnPause(SpritePauseType What = SpritePauseType.PauseAll) { for (int i = 0; i < Sprites.Count; i++) { Sprites[i].UnPause(What); } } internal void SortSprites() { //Define the sort we use if we do not have another one specified Comparison G = null; //G = delegate (Sprite first, Sprite second) { // if(first.Zvalue != second.Zvalue) return first.Zvalue.CompareTo(second.Zvalue); // if (first.BaseImageLocation.Y != second.BaseImageLocation.Y) return first.BaseImageLocation.Y.CompareTo(second.BaseImageLocation.Y); // return first.BaseImageLocation.X.CompareTo(second.BaseImageLocation.X); //}; G = delegate (Sprite first, Sprite second) { return first.Zvalue.CompareTo(second.Zvalue); }; if (SpriteComparisonDelegate != null) G = SpriteComparisonDelegate; //Sprites.Sort((x, y) => x.Zvalue.CompareTo(y.Zvalue)); Sprites.Sort(G); } } }