that looks good, are you using an effect for the colour fading? random colours too?
Nop Just straight alpha blending scaling and rotating. The image itself has some transparency in it and the colors im passing sometimes have its alpha set lower.
This is a updated version of the earlier code. The shader is the same empty one used before to bypass spritebatch altogether.
The method AddSpriteToBatch(…) is basically a spriteBatch.Draw(…) call. It does it all without any matrices either. Just straight linear math, translations, rotations and projection. It bypasses everything and does it directly, but it only works on a single texture per buffer at least right now.
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace MyDirectSpriteBatcher
{
public class LittleGpuSpriteBatcher
{
private Vector2 uvLT = new Vector2(0f, 0f);
private Vector2 uvLB = new Vector2(0f, 1f);
private Vector2 uvRT = new Vector2(1f, 0f);
private Vector2 uvRB = new Vector2(1f, 1f);
public Effect currentEffect;
public Texture2D currentTexture;
private static int windowWidth = 0;
private static int windowHeight = 0;
private int quadCapacity = 60;
private int currentQuads = 0;
private int ti_pointer = 0;
private int vi_pointer = 0;
private int[] triangleIndexList = new int[6];
private VertexPositionColorTexture[] spriteVertices = new VertexPositionColorTexture[4];
public int QuadCapacity
{
get { return quadCapacity; }
}
public LittleGpuSpriteBatcher(Rectangle windowClientBounds, int quadCapacity, Effect effectToUse)
{
windowWidth = windowClientBounds.Width;
windowHeight = windowClientBounds.Height;
this.quadCapacity = quadCapacity;
triangleIndexList = new int[6 * quadCapacity];
spriteVertices = new VertexPositionColorTexture[4 * quadCapacity];
currentEffect = effectToUse;
}
public void ClearAll()
{
currentQuads = 0;
vi_pointer = 0;
ti_pointer = 0;
}
public void AddSpriteToBatch(Rectangle destinationRectangle)
{
AddSpriteToBatch(destinationRectangle, Color.White, 0f, Vector2.One, 0f);
}
public void AddSpriteToBatch(Rectangle destinationRectangle, Color color, float rotation, Vector2 scale, float depth)
{
// The 2x coefficent which is a mulitpiplier to put the rectangle in the 0 to 2 range.
// With respect to the size of the window width and height.
float cw = 2f / windowWidth;
float ch = 2f / windowHeight;
// Determine the vertice origin for rotation and scaling which i will do in place instead.
Vector2 origin = new Vector2((destinationRectangle.Left + destinationRectangle.Right) * .5f, (destinationRectangle.Top + destinationRectangle.Bottom) * .5f);
// Determines the vertice positions from the rectangle, translates and then scales
// If we really want to just scale from top left to bottom right.
// Then below we may find RB = (...(...) - origin) * scale; then set RT.X = RB.X; and LB.Y = RB.Y;
var LT = (new Vector2(destinationRectangle.Left, destinationRectangle.Top) - origin) * scale;
var LB = (new Vector2(destinationRectangle.Left, destinationRectangle.Bottom) - origin) * scale;
var RT = (new Vector2(destinationRectangle.Right, destinationRectangle.Top) - origin) * scale;
var RB = (new Vector2(destinationRectangle.Right, destinationRectangle.Bottom) - origin) * scale;
// If rotation is specified we will perform that now!
if (rotation != 0)
{
// Sin Cos of rotation
Vector2 q = new Vector2((float)Math.Sin(rotation), (float)Math.Cos(rotation));
// Rotates the vertices from the origin here we make it the center as opposed to how spritebatch does it.
// We could pass a offset here to further translate the origin like spritebatch.
// However it is no longer necessary, this way we can pre transform the position of the rectangle itself.
LT = GetRotatedVector(LT, q);
LB = GetRotatedVector(LB, q);
RT = GetRotatedVector(RT, q);
RB = GetRotatedVector(RB, q);
}
// Translate the vertices from the origin local space back to world space
// Then transform the vertices by projecting them to screen space
var _LT = Project(LT + origin, cw, ch, depth);
var _LB = Project(LB + origin, cw, ch, depth);
var _RT = Project(RT + origin, cw, ch, depth);
var _RB = Project(RB + origin, cw, ch, depth);
// create the vertice quad
spriteVertices[vi_pointer + 0] = CreateVPCTstruct(_LT, color, uvLT);
spriteVertices[vi_pointer + 1] = CreateVPCTstruct(_LB, color, uvLB);
spriteVertices[vi_pointer + 2] = CreateVPCTstruct(_RT, color, uvRT);
spriteVertices[vi_pointer + 3] = CreateVPCTstruct(_RB, color, uvRB);
// create the indexs im not sure this parts not simply redundant.
triangleIndexList[ti_pointer + 0] = 0 + vi_pointer;
triangleIndexList[ti_pointer + 1] = 1 + vi_pointer;
triangleIndexList[ti_pointer + 2] = 2 + vi_pointer;
triangleIndexList[ti_pointer + 3] = 2 + vi_pointer;
triangleIndexList[ti_pointer + 4] = 1 + vi_pointer;
triangleIndexList[ti_pointer + 5] = 3 + vi_pointer;
currentQuads += 1;
vi_pointer += 4;
ti_pointer += 6;
}
public Vector3 Project(Vector2 v, float cw, float ch, float depth)
{
// invert the Y positions upon the X axis
return new Vector3(v.X * cw - 1f, v.Y * -ch + 1f, depth);
}
public Vector2 GetRotatedVector(Vector2 v, Vector2 q)
{
return new Vector2(v.X * q.Y - v.Y * q.X, v.X * q.X + v.Y * q.Y);
}
public Vector3 GetRotatedVector(Vector3 v, Vector2 q)
{
return new Vector3
(
v.X * q.Y - v.Y * q.X,
v.X * q.X + v.Y * q.Y,
v.Z
);
}
/// <summary>
/// simplest version
/// </summary>
public void DrawAll(GraphicsDevice gd)
{
if (TriangleDrawCount() > 0)
{
foreach (EffectPass pass in currentEffect.CurrentTechnique.Passes)
{
pass.Apply();
gd.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, spriteVertices, 0, VerticesPerQuad(), triangleIndexList, 0, TriangleDrawCount());
}
}
}
public void SetCurrentEffect(Effect effectToUse)
{
currentEffect = effectToUse;
}
public void SetCurrentTexture(Texture2D t)
{
currentTexture = t;
currentEffect.Parameters["Texture"].SetValue(currentTexture);
}
public int TotalQuads()
{
return currentQuads;
}
public int VerticesPerQuad()
{
return 4;
}
public int TriangleDrawCount()
{
return currentQuads * 2;
}
public static void OnResizeUpdateWindowWidthHeight(Rectangle windowClientBounds)
{
windowWidth = windowClientBounds.Width;
windowHeight = windowClientBounds.Height;
}
private VertexPositionColorTexture CreateVPCTstruct(float vx, float vy, float vz, Color c, Vector2 uv)
{
return new VertexPositionColorTexture(new Vector3(vx, vy, vz), c, uv);
}
private VertexPositionColorTexture CreateVPCTstruct(Vector3 position, Color c, Vector2 uv)
{
return new VertexPositionColorTexture(position, c, uv);
}
}
}
The image itself is solid alpha at the center and fades as it goes outwards. Its in pure white, to work with any color i pass to the method as a parameter.
Check this out exact same lone image as before drawn a ton of times moving.
Same as above but with variably dependent decreasing alpha in the color. That is passed to the method.
It actually looks much cooler when you see it. Because all the soft colors are flashing like soft lightning in the background.
The class that moves the particles is just a footnote to the actual functional methods and classes.
For instance the particles are in a non sorted buffer that basically only moves things on death or creation of a particle. It only really moves the pointer most of the time so no huge re-sort ever occurs but that is more of my own pattern then a class. The class itself is full of junk to make the particles behave like in the picture but… the buffer pattern it uses is the cool part.
public class DeadAliveBuffer
{
// This is my own little invention or pattern if you like quite proud of this one
public static int width = 0;
public static int height = 0;
// technically this is pointless in most cases it can just grow forever.
// it still will function optimally, its always gonna sort O(n) or below.
public int bufferLimit = 32; // default
// we could use a array here or a list if done properly i didnt create a proper swap
/// <summary>
/// Ideally a class that Implements either a base class with a isAlive bool or interface
/// However we can just copy paste the entire class and just use a object of our own type
/// </summary>
public List<IsAliveOrDeadBaseObjectItem> itemList = new List<IsAliveOrDeadBaseObjectItem>();
private int aliveMarker = 0;
public int DeadMarker { get { return aliveMarker + 1; } }
public int AliveMarker { get { return aliveMarker; } }
public DeadAliveBuffer(int bufferlimit)
{
bufferLimit = bufferlimit;
// initialize all the possible items in the buffer
for (int i = 0; i < bufferlimit; i++)
{
itemList.Add(new IsAliveOrDeadBaseObjectItem());
}
aliveMarker = -1;
}
public void CreateDefaultItem(GameTime gameTime)
{
if (DeadMarker < bufferLimit)
{
int index = MakeAliveItemReturnIndex();
// re use a dead item basically overwrite old data mark it alive
itemList[index].ReUse(gameTime);
}
}
public void MakeItemDead(int deadItemIndex)
{
// This is basically the entire sort it is simple.
if (deadItemIndex == aliveMarker)
{
aliveMarker--;
itemList[DeadMarker].isAlive = false;
}
else
{
// should redo this swap its not a very good way to swap it probably makes garbage
IsAliveOrDeadBaseObjectItem A = itemList[aliveMarker];
IsAliveOrDeadBaseObjectItem B = itemList[deadItemIndex];
itemList[deadItemIndex] = A;
itemList[aliveMarker] = B;
aliveMarker--;
itemList[DeadMarker].isAlive = false;
}
}
public void MakeAliveItem()
{
if (DeadMarker < bufferLimit)
{
itemList[DeadMarker].isAlive = true;
aliveMarker++;
}
else
{
// the buffer is completely filled with live objects
// we have two choices expand the buffer or do nothing
}
}
public int MakeAliveItemReturnIndex()
{
if (DeadMarker < bufferLimit)
{
itemList[DeadMarker].isAlive = true;
aliveMarker++;
}
else
{
// the buffer is completely filled with live objects
// we have two choices expand the buffer or do nothing
}
return aliveMarker;
}
public void Update()
{
for (int i = 0; i < DeadMarker; i++)
{
if (itemList[i].isAlive)
{
if (itemList[i].duration < 1 || itemList[i].positionRect.Y > height)
{
MakeItemDead(i);
}
}
if (itemList[i].isAlive)
{
//itemList[i].velocity.Y += .0010f; // gravitasity
//itemList[i].positionRect.X += itemList[i].velocity.X;
//itemList[i].positionRect.Y += itemList[i].velocity.Y;
}
}
}
}