Spritebatch drawing sprite with jagged borders
- by Mutoh
Alright, I've been on the making of a sprite class and a sprite sheet manager, but have come across this problem. Pretty much, the project is acting like so; for example:
Let's take this .png image, with a transparent background. Note how it has alpha-transparent pixels around it in the lineart.
Now, in the latter link's image, in the left (with CornflowerBlue background) it is shown the image drawn in another project (let's call it "Project1") with a simpler sprite class - there, it works. The right (with Purple background for differentiating) shows it drawn with a different class in "Project2" - where the problem manifests itself.
This is the Sprite class of Project1:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace WindowsGame2
{
class Sprite
{
Vector2 pos = new Vector2(0, 0);
Texture2D image;
Rectangle size;
float scale = 1.0f;
// ---
public float X
{
get
{
return pos.X;
}
set
{
pos.X = value;
}
}
public float Y
{
get
{
return pos.Y;
}
set
{
pos.Y = value;
}
}
public float Width
{
get
{
return size.Width;
}
}
public float Height
{
get
{
return size.Height;
}
}
public float Scale
{
get
{
return scale;
}
set
{
if (value < 0) value = 0;
scale = value;
if (image != null)
{
size.Width = (int)(image.Width * scale);
size.Height = (int)(image.Height * scale);
}
}
}
// ---
public void Load(ContentManager Man, string filename)
{
image = Man.Load<Texture2D>(filename);
size = new Rectangle(
0, 0,
(int)(image.Width * scale), (int)(image.Height * scale)
);
}
public void Become(Texture2D frame)
{
image = frame;
size = new Rectangle(
0, 0,
(int)(image.Width * scale), (int)(image.Height * scale)
);
}
public void Draw(SpriteBatch Desenhista)
{
// Desenhista.Draw(image, pos, Color.White);
Desenhista.Draw(
image, pos,
new Rectangle(
0, 0,
image.Width, image.Height
),
Color.White,
0.0f,
Vector2.Zero,
scale,
SpriteEffects.None, 0
);
}
}
}
And this is the code in Project2, a rewritten, pretty much, version of the previous class. In this one I added sprite sheet managing and, in particular, removed Load and Become, to allow for static resources and only actual Sprites to be instantiated.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace Mobby_s_Adventure
{
// Actually, I might desconsider this, and instead use static AnimationLocation[] and instanciated ID and Frame;
// For determining the starting frame of an animation in a sheet and being able to iterate through
// the Rectangles vector of the Sheet;
class AnimationLocation
{
public int Location;
public int FrameCount;
// ---
public AnimationLocation(int StartingRow, int StartingColumn, int SheetWidth, int NumberOfFrames)
{
Location = (StartingRow * SheetWidth) + StartingColumn;
FrameCount = NumberOfFrames;
}
public AnimationLocation(int PositionInSheet, int NumberOfFrames)
{
Location = PositionInSheet;
FrameCount = NumberOfFrames;
}
public static int CalculatePosition(int StartingRow, int StartingColumn, SheetManager Sheet)
{
return ((StartingRow * Sheet.Width) + StartingColumn);
}
}
class Sprite
{
// The general stuff;
protected SheetManager Sheet;
protected Vector2 Position;
public Vector2 Axis;
protected Color _Tint;
public float Angle;
public float Scale;
protected SpriteEffects _Effect;
// ---
// protected AnimationManager Animation;
// For managing the animations;
protected AnimationLocation[] Animation;
public int AnimationID;
protected int Frame;
// ---
// Properties for easy accessing of the position of the sprite;
public float X
{
get { return Position.X; }
set { Position.X = Axis.X + value; }
}
public float Y
{
get { return Position.Y; }
set { Position.Y = Axis.Y + value; }
}
// ---
// Properties for knowing the size of the sprite's frames
public float Width
{
get { return Sheet.FrameWidth * Scale; }
}
public float Height
{
get { return Sheet.FrameHeight * Scale; }
}
// ---
// Properties for more stuff;
public Color Tint
{
set { _Tint = value; }
}
public SpriteEffects Effect
{
set { _Effect = value; }
}
public int FrameID
{
get { return Frame; }
set
{
if (value >= (Animation[AnimationID].FrameCount)) value = 0;
Frame = value;
}
}
// ---
// The only things that will be constantly modified will be AnimationID and FrameID, anything else only
// occasionally;
public Sprite(SheetManager SpriteSheet, AnimationLocation[] Animations, Vector2 Location, Nullable<Vector2> Origin = null)
{
// Assign the sprite's sprite sheet;
// (Passed by reference! To allow STATIC sheets!)
Sheet = SpriteSheet;
// Define the animations that the sprite has available;
// (Passed by reference! To allow STATIC animation boundaries!)
Animation = Animations;
// Defaulting some numerical values;
Angle = 0.0f;
Scale = 1.0f;
_Tint = Color.White;
_Effect = SpriteEffects.None;
// If the user wants a default Axis, it is set in the middle of the frame;
if (Origin != null) Axis = Origin.Value;
else Axis = new Vector2(
Sheet.FrameWidth / 2,
Sheet.FrameHeight / 2
);
// Now that we have the axis, we can set the position with no worries;
X = Location.X;
Y = Location.Y;
}
// Simply put, draw the sprite with all its characteristics;
public void Draw(SpriteBatch Drafter)
{
Drafter.Draw(
Sheet.Texture,
Position,
Sheet.Rectangles[Animation[AnimationID].Location + FrameID], // Find the rectangle which frames the wanted image;
_Tint,
Angle,
Axis,
Scale,
_Effect,
0.0f
);
}
}
}
And, in any case, this is the SheetManager class found in the previous code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace Mobby_s_Adventure
{
class SheetManager {
protected Texture2D SpriteSheet; // For storing the sprite sheet;
// Number of rows and frames in each row in the SpriteSheet;
protected int NumberOfRows;
protected int NumberOfColumns;
// Size of a single frame;
protected int _FrameWidth;
protected int _FrameHeight;
public Rectangle[] Rectangles; // For storing each frame;
// ---
public int Width {
get { return NumberOfColumns; }
}
public int Height {
get { return NumberOfRows; }
}
// ---
public int FrameWidth {
get { return _FrameWidth; }
}
public int FrameHeight {
get { return _FrameHeight; }
}
// ---
public Texture2D Texture {
get { return SpriteSheet; }
}
// ---
public SheetManager (Texture2D Texture, int Rows, int FramesInEachRow) {
// Normal assigning
SpriteSheet = Texture;
NumberOfRows = Rows;
NumberOfColumns = FramesInEachRow;
_FrameHeight = Texture.Height / NumberOfRows;
_FrameWidth = Texture.Width / NumberOfColumns;
// Framing everything
Rectangles = new Rectangle[NumberOfRows * NumberOfColumns];
int ID = 0;
for (int i = 0; i < NumberOfRows; i++) {
for (int j = 0; j < NumberOfColumns; j++) {
Rectangles[ID] = new Rectangle (
_FrameWidth * j, _FrameHeight * i,
_FrameWidth, _FrameHeight
);
ID++;
}
}
}
public SheetManager (Texture2D Texture, int NumberOfFrames): this(Texture, 1, NumberOfFrames)
{ }
}
}
For even more comprehending, if needed, here is how the main code looks like (it's just messing with the class' capacities, nothing actually; the result is a disembodied feet walking in place animation on the top-left of the screen and a static axe nearby):
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Threading;
namespace Mobby_s_Adventure
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
static List<Sprite> ToDraw;
static Texture2D AxeSheet;
static Texture2D FeetSheet;
static SheetManager Axe;
static Sprite Jojora;
static AnimationLocation[] Hack = new AnimationLocation[1];
static SheetManager Feet;
static Sprite Mutoh;
static AnimationLocation[] FeetAnimations = new AnimationLocation[2];
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
this.TargetElapsedTime = TimeSpan.FromMilliseconds(100);
this.IsFixedTimeStep = true;
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// Loading logic
ToDraw = new List<Sprite>();
AxeSheet = Content.Load<Texture2D>("Sheet");
FeetSheet = Content.Load<Texture2D>("Feet Sheet");
Axe = new SheetManager(AxeSheet, 1);
Hack[0] = new AnimationLocation(0, 1);
Jojora = new Sprite(Axe, Hack, new Vector2(100, 100), new Vector2(5, 55));
Jojora.AnimationID = 0;
Jojora.FrameID = 0;
Feet = new SheetManager(FeetSheet, 8);
FeetAnimations[0] = new AnimationLocation(1, 7);
FeetAnimations[1] = new AnimationLocation(0, 1);
Mutoh = new Sprite(Feet, FeetAnimations, new Vector2(0, 0));
Mutoh.AnimationID = 0;
Mutoh.FrameID = 0;
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// all content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// Update logic
Mutoh.FrameID++;
ToDraw.Add(Mutoh);
ToDraw.Add(Jojora);
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Purple);
// Drawing logic
spriteBatch.Begin();
foreach (Sprite Element in ToDraw)
{
Element.Draw(spriteBatch);
}
spriteBatch.Draw(Content.Load<Texture2D>("Sheet"), new Rectangle(50, 50, 55, 60), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
}
Please help me find out what I'm overlooking!
One thing that I have noticed and could aid is that, if inserted the equivalent of this code
spriteBatch.Draw(
Content.Load<Texture2D>("Image Location"),
new Rectangle(X, Y, images width, height),
Color.White
);
in Project2's Draw(GameTime) of the main loop, it works.
EDIT
Ok, even if the matter remains unsolved, I have made some more progress!
As you see, I managed to get the two kinds of rendering in the same project (the aforementioned Project2, with the more complex Sprite class).
This was achieved by adding the following code to Draw(GameTime):
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Purple);
// Drawing logic
spriteBatch.Begin();
foreach (Sprite Element in ToDraw)
{
Element.Draw(spriteBatch);
}
// Starting here
spriteBatch.Draw(
Axe.Texture,
new Vector2(65, 100),
new Rectangle (
0, 0,
Axe.FrameWidth, Axe.FrameHeight
),
Color.White,
0.0f,
new Vector2(0, 0),
1.0f,
SpriteEffects.None,
0.0f
);
// Ending here
spriteBatch.End();
base.Draw(gameTime);
}
(Supposing that Axe is the SheetManager containing the texture, sorry if the "jargons" of my code confuse you :s)
Thus, I have noticed that the problem is within the Sprite class. But I only get more clueless, because even after modifying its Draw function to this:
public void Draw(SpriteBatch Drafter)
{
/*Drafter.Draw(
Sheet.Texture,
Position,
Sheet.Rectangles[Animation[AnimationID].Location + FrameID], // Find the rectangle which frames the wanted image;
_Tint,
Angle,
Axis,
Scale,
_Effect,
0.0f
);*/
Drafter.Draw(
Sheet.Texture, Position,
new Rectangle(
0, 0,
Sheet.FrameWidth, Sheet.FrameHeight
),
Color.White,
0.0f,
Vector2.Zero,
Scale,
SpriteEffects.None, 0
);
}
to make it as simple as the patch of code that works, it still draws the sprite jaggedly!