Exploderande sprite

Inledning

Ibland vill man ha någon cool effekt i sitt spel när t.ex. någon fiende dör, eller när något exploderar. I denna artikel ska vi kika på hur man kan få alla pixlar i en bild att explodera!

För flera år sedan då datorer knappt kunde köra 3D så var det såklart vanligare med enkla pixel-effekter. Idén kommer just från sådana effekter där man t.ex. kunde få pixlar att ”snöa” bort eller ”blåsa” bort från en bild.

En liten förhandsvisning om vad slutresultatet kommer att bli ser du i videoklippet nedan. Ett färdigt projekt hittar du längre ned i artikeln.

Partiklar

Först av allt så behöver vi en klass som kan beskriva en enskild pixel, dess position, hastighet och ursprungliga poistion i den bild som ska explodera.

Det vi behöver är egentligen ett partikelsystem. En klass för en sådan partikel kan se ut såhär:

Particle.cs
   class Particle
    {
        public Vector2 Position { get; set; }
        public Vector2 Speed { get; set; }
        public int ImageOffsetX { get; set; }
        public int ImageOffsetY { get; set; }
    }

Logiken

En exploderande bild kan vi baka in i en enda klass. Det som klassen behöver kunna hantera är:

  • Skapa partiklar utifrån de pixlar som finns i bilden.
  • Undvika genomskinliga pixlar, de syns ändå inte.
  • Räkna ut en hastighet/riktning på varje pixel.
  • Rita ut alla pixlar.
  • Uppdatera alla pixlar så att de rör på sig.
  • Ha någon slags TTL (= Time To Live) så att vi vet när explosionen är klar.

En sådan klass skulle kunna se ut såhär:

Explode.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ExplodingImage
{
    class Explode
    {
        public Texture2D Texture { get; set; }
        public Vector2 Position { get; set; }
        public bool IsExploding { get; set; }
        public int TTL { get; set; }

        List<Particle> _particles = new List<Particle>();

        public Explode(Texture2D texture, Vector2 position)
        {
            Texture = texture;
            Position = position;
            Reset();
        }

        private void Reset()
        {
            _particles.Clear();
            TTL = 180;
            IsExploding = false;

            Random rnd = new Random();
            var pixels = new Color[Texture.Width * Texture.Height];
            Texture.GetData(pixels);
            var center = Position + new Vector2(Texture.Width / 2.0f, Texture.Height / 2.0f);

            for (int y = 0; y < Texture.Height; y++)
            {
                for (int x = 0; x < Texture.Width; x++)
                {
                    if(pixels[x + y*Texture.Width] == Color.Transparent)
                        continue;
                    
                    var p = new Particle()
                    {
                        ImageOffsetX = x,
                        ImageOffsetY = y,
                        Position = Position + new Vector2(x, y)
                    };
                    p.Speed = (p.Position - center) / 50.0f;
                    p.Speed += new Vector2((float)rnd.NextDouble(), (float)rnd.NextDouble());
                    _particles.Add(p);
                }
            }
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            if(!IsExploding)
                spriteBatch.Draw(Texture, Position, Color.White);
            else
            {
                //var off = 120 - TTL;
                //var blend = new Color(255 - off, 255 - off, 255 - off);
                foreach (var particle in _particles)
                {
                    spriteBatch.Draw(Texture, particle.Position, 
                        new Rectangle(particle.ImageOffsetX, particle.ImageOffsetY, 1, 1), Color.White);
                }
            }
        }

        public void Update()
        {
            if (IsExploding)
            {
                foreach (var particle in _particles)
                {
                    particle.Position += particle.Speed;
                    particle.Speed += new Vector2(0, 0.02f);
                }

                TTL--;

                if(TTL <= 0)
                    Reset();
            }
        }
    }
}

Klassen tar en Texture2D och en Vector2 som inparameter i konstruktorn. Du måste alltså ha laddat texturen innan du skapar ett objekt av klassen Explode.

Klassen håller redan på om den håller på att explodera och hur lände den ska hålla på med egenskaperna TTL och IsExploding.

Metoden Reset räknar ut alla partiklar och lägger den i en privat lista. Med hjälp av metoden GetData från Texture2D-klassen kan man få all pixel-data i from av Color. Detta är mycket användbart om man t.ex. vill manipulera en bild eller i vårt fall läsa av bilden.
En traditionell nästlad loop följer som itererar över bildens bredd och höjd. För varje pixel skapas en Particle som också får en hastighet i motsatt rktning från bildens centrum.

Magin i det hela ligger faktiskt i att ge partikeln en liten extra slumpvis vald hastighet också. Utan detta på rad 53 så kommer allt att se mycket tråkigt ut. Det kan du testa själv om du vill.

Klassens Draw-metod är väldigt enkel. Den förlitar sig på att den får en SpriteBatch så att den kan rita ut alla partiklar (om bilden exploderar).

Klassens Update-metod är också väldigt enkel. Här har jag valt att lägga på en konstant hastighet nedåt för att få effekten av gravitation.

Prova

Ladda hem exemplet nedan och testa själv! Kräver att du har MonoGame installerat såklart.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *

Scroll to top