Download Cyotek.ArcadeExplosionMaker.zip version 1.0.0.1, last updated 05/06/2012 (123.69 KB)

Download
  • md5: bd412009442f7923a555ba5b0f508fe0
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;

namespace Cyotek.ArcadeExplosionMaker
{
  // Cyotek Arcade Explosion Maker
  // Copyright (c) 2012 Cyotek. All Rights Reserved.
  // http://cyotek.com

  // http://cyotek.com/blog/arcade-explosion-generator

  // If you use this code or output in your applications, attribution or donations are welcome.

  public class ExplosionGenerator : IDisposable
  {
    #region  Private Member Declarations

    private List<Image> _frames;
    private Random _random;
    private Bitmap _spriteSheet;

    #endregion  Private Member Declarations

    #region  Public Constructors

    public ExplosionGenerator()
    {
      _random = new Random();
    }

    #endregion  Public Constructors

    #region  Public Methods

    public void Clear()
    {
      if (_frames != null)
      {
        while (_frames.Count != 0)
        {
          _frames[0].Dispose();
          _frames.RemoveAt(0);
        }
      }

      if (_spriteSheet != null)
        _spriteSheet.Dispose();
    }

    public void Dispose()
    {
      this.Clear();
      _frames = null;
      _spriteSheet = null;
    }

    public void Generate()
    {
      this.Clear();
      _frames = new List<Image>();

      if (this.Parameters.Seed < 0)
        this.Seed = new Random().Next(); // Could use Environment.Ticks but this produces a wider range of numbers
      else
        this.Seed = this.Parameters.Seed;

      _random = new Random(this.Seed);

      this.CreateFrames();
      this.CreateSpriteSheet();
    }

    #endregion  Public Methods

    #region  Public Properties

    public ReadOnlyCollection<Image> Frames
    { get { return _frames != null ? _frames.AsReadOnly() : null; } }

    public ExplosionParameters Parameters { get; set; }

    public int Seed { get; protected set; }

    public Bitmap SpriteSheet
    { get { return _spriteSheet; } }

    public Size SpriteSheetLayoutSize { get; protected set; }

    #endregion  Public Properties

    #region  Private Methods

    private ExplosionData CreateExplosion(Rectangle bounds)
    {
      int x;
      int y;
      int diameter;
      int radius;
      int growthSize;
      int defaultRadius;

      radius = _random.Next(this.Parameters.MinimumExplosionRadius, this.Parameters.MaximumExplosionRadius + 1);
      diameter = radius * 2;

      if (this.Parameters.FitToBounds && diameter < this.Parameters.FrameSize.Width && diameter < this.Parameters.FrameSize.Height)
      {
        x = radius + _random.Next(0, this.Parameters.FrameSize.Width - diameter);
        y = radius + _random.Next(0, this.Parameters.FrameSize.Height - diameter);
      }
      else
      {
        x = _random.Next((this.Parameters.FrameSize.Width / 2), (this.Parameters.FrameSize.Width * 2) + 1);
        y = _random.Next((this.Parameters.FrameSize.Height / 2), (this.Parameters.FrameSize.Height * 2) + 1);
      }

      if (this.Parameters.GrowthIncrement != 0)
        growthSize = (int)Math.Max(2, (diameter / 100F) * this.Parameters.GrowthIncrement);
      else
        growthSize = 0;

      if (this.Parameters.GrowthStages == 0)
        defaultRadius = 0;
      else
        defaultRadius = Math.Max(0, radius - (growthSize * this.Parameters.GrowthStages));

      return new ExplosionData()
      {
        Center = new Point(x, y),
        Radius = defaultRadius,
        RadiusGrowth = growthSize,
        MaximumRadius = radius,
        MinimumRadius = defaultRadius,
        BorderGrowth = this.Parameters.BorderSize,
        BorderSize = this.Parameters.GrowBorders ? 0 : this.Parameters.BorderSize,
        GrowthSpeed = 1
      };

    }

    private void CreateFrames()
    {
      List<ExplosionData> explosions;

      explosions = new List<ExplosionData>();

      for (int frameIndex = 0; frameIndex < this.Parameters.Frames; frameIndex++)
      {
        Bitmap image;

        image = new Bitmap(this.Parameters.FrameSize.Width, this.Parameters.FrameSize.Height);

        using (Graphics g = Graphics.FromImage(image))
        {
          Rectangle bounds;

          g.SmoothingMode = this.Parameters.AntiAlias ? SmoothingMode.AntiAlias : SmoothingMode.None;
          bounds = new Rectangle(Point.Empty, this.Parameters.FrameSize);

          g.Clear(this.Parameters.BackColor);

          if (explosions.Count < this.Parameters.MaximumExplosions)
          {
            ExplosionData explosion;
            int zorder;

            if (this.Parameters.RandomOrder)
              zorder = _random.Next(0, explosions.Count);
            else
              zorder = 0;

            explosion = this.CreateExplosion(bounds);

            explosions.Insert(zorder, explosion);
          }

          this.RenderFrame(g, explosions);
        }

        _frames.Add(image);
      }
    }

    private void CreateSpriteSheet()
    {
      int rows;
      int columns;
      int x;
      int y;

      columns = this.Parameters.SpriteSheetColumns;
      if (columns < 1)
        columns = (int)Math.Round(Math.Sqrt(_frames.Count));

      rows = (int)Math.Round((float)_frames.Count / columns);
      if (columns * rows < _frames.Count)
        rows++;

      this.SpriteSheetLayoutSize = new Size(columns, rows);

      _spriteSheet = new Bitmap(columns * this.Parameters.FrameSize.Width, rows * this.Parameters.FrameSize.Height);

      using (Graphics g = Graphics.FromImage(_spriteSheet))
      {
        for (int r = 0; r < rows; r++)
        {
          for (int c = 0; c < columns; c++)
          {
            int cell;

            cell = r * columns + c;

            if (cell < _frames.Count)
            {
              x = (c * this.Parameters.FrameSize.Width);
              y = (r * this.Parameters.FrameSize.Height);

              g.DrawImage(_frames[cell], new Point(x, y));
            }
          }
        }
      }
    }

    private Region FindCircleIntersections(PointF[] centers, float[] radii)
    {
      // http://blog.csharphelper.com/2010/08/23/find-the-area-where-two-or-more-circles-overlap-in-c.aspx

      if (centers.Length < 1)
        return null;

      // Make a region.
      Region result_region = new Region();

      // Intersect the region with the circles.
      for (int i = 0; i < centers.Length; i++)
      {
        using (GraphicsPath circle_path = new GraphicsPath())
        {
          circle_path.AddEllipse(
              centers[i].X - radii[i], centers[i].Y - radii[i],
              2 * radii[i], 2 * radii[i]);
          result_region.Intersect(circle_path);
        }
      }

      return result_region;
    }

    private void RenderCircle(Graphics g, PointF point, float radius, Color color)
    {
      this.RenderCircle(g, point, radius, color, 0, 0);
    }

    private void RenderCircle(Graphics g, PointF point, float radius, Color color, float offset, float lineWidth)
    {
      RectangleF bounds;

      bounds = new RectangleF((point.X + offset) - radius, (point.Y + offset) - radius, (radius - offset) * 2, (radius - offset) * 2);

      if (lineWidth == 0)
      {
        using (Brush brush = new SolidBrush(color))
          g.FillEllipse(brush, bounds);
      }
      else
      {
        using (Pen pen = new Pen(color, lineWidth))
          g.DrawEllipse(pen, bounds);
      }
    }

    private void RenderExplosion(Graphics g, ExplosionData explosion)
    {
      if (explosion.Radius != 0)
      {
        float borderSize;
        float diameter;

        borderSize = explosion.BorderSize;
        diameter = explosion.Radius * 2;

        this.RenderCircle(g, explosion.Center, explosion.Radius, this.Parameters.Color3);

        if (borderSize > 0)
        {
          if (diameter > borderSize)
            this.RenderCircle(g, explosion.Center, explosion.Radius, this.Parameters.Color1, 0, borderSize + 1);
          if (diameter > (borderSize * 2))
            this.RenderCircle(g, explosion.Center, explosion.Radius, this.Parameters.Color2, borderSize, borderSize);
        }
      }
    }

    private void RenderExplosionBlackout(Graphics g, ExplosionData explosion)
    {
      if (explosion.MinimumRadius != 0)
      {
        this.RenderCircle(g, explosion.Center, explosion.MinimumRadius, this.Parameters.BlackoutColor);
        this.RenderCircle(g, explosion.Center, explosion.MinimumRadius + (this.Parameters.BorderSize * 2), this.Parameters.BlackoutColor, 0, this.Parameters.BorderSize);
      }
    }

    private void RenderExplosionMask(Graphics g, PointF[] centers, float[] radii)
    {
      using (Region region = this.FindCircleIntersections(centers, radii))
      {
        if (region != null)
        {
          using (Brush brush = new SolidBrush(this.Parameters.BlackoutColor))
            g.FillRegion(brush, region);
        }
      }
    }

    private void RenderFrame(Graphics g, List<ExplosionData> explosions)
    {
      for (int explosionIndex = explosions.Count; explosionIndex > 0; explosionIndex--)
      {
        ExplosionData explosion;

        explosion = explosions[explosionIndex - 1];

        explosion.Radius += (explosion.RadiusGrowth * explosion.GrowthSpeed);

        if (this.Parameters.GrowBorders)
          explosion.BorderSize += explosion.BorderGrowth;

        if (explosion.Radius >= explosion.MaximumRadius)
        {
          explosion.Radius = explosion.MaximumRadius;
          explosion.RadiusGrowth = -explosion.RadiusGrowth;
          explosion.BorderGrowth = -explosion.BorderGrowth;
        }

        if (explosion.Radius > explosion.MinimumRadius)
          this.RenderExplosion(g, explosion);
        else if (this.Parameters.RemoveExpiredExplosions)
          explosions.Remove(explosion);
        else if (this.Parameters.BlackOutExpiredExplosions)
          this.RenderExplosionBlackout(g, explosion);
      }

      // mask out the overlapping area
      if (explosions.Count > 1)
      {
        switch (this.Parameters.ExplosionMaskMode)
        {
          case ExplosionMaskMode.Previous:
            this.RenderPreviousExplosionMask(g, explosions);
            break;
          case ExplosionMaskMode.All:
            this.RenderExplosionMask(g, explosions.Select(e => e.Center).ToArray(), explosions.Select(e => e.Radius).ToArray());
            break;
        }
      }
    }

    private void RenderPreviousExplosionMask(Graphics g, List<ExplosionData> explosions)
    {
      for (int i = 1; i < explosions.Count; i++)
      {
        ExplosionData explosion1;
        ExplosionData explosion2;

        explosion1 = explosions[i];
        explosion2 = explosions[i - 1];

        this.RenderExplosionMask(g, new PointF[] { explosion1.Center, explosion2.Center }, new float[] { explosion1.Radius, explosion2.Radius });
      }
    }

    #endregion  Private Methods
  }
}

Donate

Donate