mercredi 23 avril 2014

c# - ne peut pas dessiner image plus que carré dans XNA - Stack Overflow


I'm making a pong game. I've been using a simple 44 x 44 .png of a red square as my ball while I've been debugging. The game works fine with this square.


When I try to replace the texture with anything other than a square, I don't see the ball drawn on screen, and I can't figure out why. I'm making my images the exact same size in photoshop, and using either .PNG or .JPG and having the same result, but I can't figure it out for the life of me. What do you think could be causing this issue?


I've left the code for my ball in the text below. My ball's update and draw methods are being called by the GameplayScreen (using MS' GSM sample).


    public class Ball : IGameEntity
{
#region Fields

private Random rand; // Random var
private Texture2D texture; // Texture for the ball
private double direction; // Directon the ball is traveling in
private bool isVisible;
private bool hasHitLeftBat; // Checked to see if the ball and bat have just collided
private bool hasHitRightBat; // Checked to see if the ball and bat have just collided
private Vector2 ballPosition, resetBallPos, oldBallPos;
private Rectangle ballRect;
public float Speed;
private SpriteBatch spriteBatch; // Spritebatch
private bool isBallStopped;
private Vector2 origin; // Locate the mid-point of the ball
public float RotationAngle;
private AIBat rightBat; // Player's Bad
private Bat leftBat; // AI Bat
private float ballRelativePos;
private Rectangle rectangle3; // Used to draw the collison rectangle
private Texture2D blank; // Texture to be drawn on the collision rectangle

GameplayScreen gameplayScreen; // Creates an instance of the GameplayScreen
Game1 gameInstance; // Creates an instance of the Game1 class
int selectedStage; // Pass this into GameplayScreen for selecting easy, medium, or hard


#endregion

#region Constructors and Destructors

/// <summary>
/// Constructor for the ball
/// </summary>
public Ball(ContentManager contentManager, Vector2 ScreenSize, Bat bat, AIBat aiBat)
{
Speed = 15f;
texture = contentManager.Load<Texture2D>(@"gfx/balls/redBall");
direction = 0;
ballRect = new Rectangle(0, 0, texture.Width /2, texture.Height /2);
resetBallPos = new Vector2(ScreenSize.X / 2 + origin.X, ScreenSize.Y / 2 + origin.Y);
ballPosition = resetBallPos;
rand = new Random();
isVisible = true;
origin = new Vector2(texture.Width / 2, texture.Height / 2);
leftBat = bat; // Creates a new instance of leftBat so that I can access Position.X/Y for LeftBatPatcicles()
rightBat = aiBat;// Creates a new instance of leftBat so that can access Position.X/Y for RightBatPatcicles()
gameplayScreen = new GameplayScreen(null, selectedStage);
gameInstance = new Game1();
Rectangle rectangle3 = new Rectangle();
blank = contentManager.Load<Texture2D>(@"gfx/blank");

// pes = new ParticleEmitterService(game);
}

public Ball(Bat myBat)
{
leftBat = myBat; // this assigns and instantiates the member bat
// with myBat which was passed from the constructor
}

#endregion

#region Methods

/// <summary>
/// Draws the ball on the screen
/// </summary>
public void Draw(SpriteBatch spriteBatch)
{
if (isVisible)
{
// Draws the rotaing ball
spriteBatch.Draw(texture, ballPosition, ballRect, Color.White,
RotationAngle, origin, .0f, SpriteEffects.None, 0);

spriteBatch.Draw(blank, rectangle3, Color.LightCoral);
}
}

/// <summary>
/// Updates position of the ball. Used in Update() for GameplayScreen.
/// </summary>
public void UpdatePosition(GameTime gameTime)
{
ballRect.X = (int)ballPosition.X;
ballRect.Y = (int)ballPosition.Y;
oldBallPos.X = ballPosition.X;
oldBallPos.Y = ballPosition.Y;

ballPosition.X += Speed * ((float)Math.Cos(direction));

ballPosition.Y += Speed * ((float)Math.Sin(direction));
bool collided = CheckWallHit();


// Stops the issue where ball was oscillating on the ceiling or floor
if (collided)
{
ballPosition.X = oldBallPos.X + Speed * (float)1.5 * (float)Math.Cos(direction);
ballPosition.Y = oldBallPos.Y + Speed * (float)Math.Sin(direction);
}

// As long as the ball is to the right of the back, check for an update
if (ballPosition.X > leftBat.BatPosition.X)
{
// When the ball and bat collide, draw the rectangle where they intersect
BatCollisionRectLeft();
}

// As longas the ball is to the left of the back, check for an update
if (ballPosition.X < rightBat.BatPosition.X)
{ // When the ball and bat collide, draw the rectangle where they intersec
BatCollisionRectRight();
}

// The time since Update was called last.
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

// Rotation for the ball
RotationAngle += elapsed;
float circle = MathHelper.Pi * 2;
RotationAngle = RotationAngle % circle;

// base.Update(gameTime);
gameInstance.update();

}


/// <summary>
/// Checks for the current direction of the ball
/// </summary>
public double GetDirection()
{
return direction;
}

/// <summary>
/// Checks for the current position of the ball
/// </summary>
public Vector2 GetPosition()
{
return ballPosition;
}

/// <summary>
/// Checks for the current size of the ball (for the powerups)
/// </summary>
public Rectangle GetSize()
{
return ballRect;
}



/// <summary>
/// Checks to see if ball went out of bounds, and triggers warp sfx. Used in GameplayScreen.
/// </summary>
public void OutOfBounds()
{
AudioManager.Instance.PlaySoundEffect("Muzzle_shot");
}

/// <summary>
/// Speed for the ball when Speedball powerup is activated
/// </summary>
public void PowerupSpeed()
{
Speed += 20.0f;
}

/// <summary>
/// Check for where to reset the ball after each point is scored
/// </summary>
public void Reset(bool left)
{
if (left)
{
direction = 0;
}
else
{
direction = Math.PI;
}

ballPosition = resetBallPos; // Resets the ball to the center of the screen
isVisible = true;
Speed = 15f; // Returns the ball back to the default speed, in case the speedBall was active
if (rand.Next(2) == 0)
{
direction += MathHelper.ToRadians(rand.Next(30));
}
else
{
direction -= MathHelper.ToRadians(rand.Next(30));
}
}

/// <summary>
/// Shrinks the ball when the ShrinkBall powerup is activated
/// </summary>
public void ShrinkBall()
{
ballRect = new Rectangle(0, 0, texture.Width / 2, texture.Height / 2);
}

/// <summary>
/// Stops the ball each time it is reset. Ex: Between points / rounds
/// </summary>
public void Stop()
{
isVisible = true;
Speed = 0;
isBallStopped = true;
}

/// <summary>
/// Checks for collision with the ceiling or floor. 2*Math.pi = 360 degrees
/// </summary>
private bool CheckWallHit()
{
while (direction > 2 * Math.PI)
{
direction -= 2 * Math.PI;
return true;
}

while (direction < 0)
{
direction += 2 * Math.PI;
return true;
}

if (ballPosition.Y <= 0 || (ballPosition.Y > resetBallPos.Y * 2 - ballRect.Height))
{
direction = 2 * Math.PI - direction;
return true;
}
return true;
}

/// <summary>
/// Used to determine the location where the particles will initialize when the ball and bat collide
/// </summary>
private void BatCollisionRectLeft()
{
// For the left bat
if (ballRect.Intersects(leftBat.batRect))
{
rectangle3 = Rectangle.Intersect(ballRect, leftBat.batRect);
}
}

/// <summary>
///Checks for collision of Right Bat
/// </summary>
private void BatCollisionRectRight()
{
// for the right bat
if (ballRect.Intersects(rightBat.batRect))
{
rectangle3 = Rectangle.Intersect(ballRect, rightBat.batRect); ;
}
}



You shouldn't be passing anything as your SourceRect parameter in your draw call unless you don't wan't to draw the entire image.


The way you have it set up now is you're passing 'ballRect' as your SourceRect when trying to draw your ball, and that ballRect parameter is being updated according to the ball's position so you're trying to draw a portion of your image that is way outside of the size of the texture.


If you want to draw the entire ball, just use:


spriteBatch.Draw(texture, ballPosition, null, Color.White,
RotationAngle, origin, .0f, SpriteEffects.None, 0);

If you only wanted to draw the top-left quadrant of the ball, you could pass in the following rectangle as your SourceRect:


Rectangle sourceRect = new Rectangle(0, 0, texture.Width / 2, texture.Height / 2);

Then you could use that in your Draw call:


spriteBatch.Draw(texture, ballPosition, sourceRect, Color.White,
RotationAngle, origin, .0f, SpriteEffects.None, 0);

EDIT: You're also passing .0f as your "scale" parameter so when it does draw your ball it will be 0 pixels in size, which I'm guessing isn't the intended behavior. Using 1f for your scale will draw it at it's default size.




2 things that i'm noticing, I don't think you want to halve your rectangle dimentions when you call


ballRect = new Rectangle(0, 0, texture.Width /2, texture.Height /2);

also, you're drawing this blank sprite on top of your ball, so that if they overlap, you won't see the ball, but if that's the case I don't know why it worked with the square.




You're passing ballRect into Draw() as the source rectangle, which specifies where the sprite exists on the texture. If you want to use the entire texture, specify null instead. Otherwise, make sure that the value of ballRect always falls within the texture.


You seem to be using it to keep track of the sprite's position on the screen. From your UpdatePosition function:


ballRect.X = (int)ballPosition.X;
ballRect.Y = (int)ballPosition.Y;

This is producing texture coordinates outside of the bounds of the texture, which are being clamped by the sampler. With a texture that consists entirely of red pixels, this appears to work, because the pixels around the edge of the texture are all red. In a texture with a transparent border, the sprite is going to be clamped to that transparent color instead.




Something I noticed is that in the line


resetBallPos = new Vector2(ScreenSize.X / 2 + origin.X, ScreenSize.Y / 2 + origin.Y);

inside your constructor is accessing the properties of the variable 'origin' before you actually assign any value to it; however, you do assign a value to it, just a few lines below this value. I'd move this line:


origin = new Vector2(texture.Width / 2, texture.Height / 2);

to be above it. I would think that should fix your issue; in the transition from red block to proper image, you may have changed more then you thought.


Also, as a side note, when you're working with XNA, try to use mainly PNG images. They're small and load very quickly; additionally, they support transparent background images.



I'm making a pong game. I've been using a simple 44 x 44 .png of a red square as my ball while I've been debugging. The game works fine with this square.


When I try to replace the texture with anything other than a square, I don't see the ball drawn on screen, and I can't figure out why. I'm making my images the exact same size in photoshop, and using either .PNG or .JPG and having the same result, but I can't figure it out for the life of me. What do you think could be causing this issue?


I've left the code for my ball in the text below. My ball's update and draw methods are being called by the GameplayScreen (using MS' GSM sample).


    public class Ball : IGameEntity
{
#region Fields

private Random rand; // Random var
private Texture2D texture; // Texture for the ball
private double direction; // Directon the ball is traveling in
private bool isVisible;
private bool hasHitLeftBat; // Checked to see if the ball and bat have just collided
private bool hasHitRightBat; // Checked to see if the ball and bat have just collided
private Vector2 ballPosition, resetBallPos, oldBallPos;
private Rectangle ballRect;
public float Speed;
private SpriteBatch spriteBatch; // Spritebatch
private bool isBallStopped;
private Vector2 origin; // Locate the mid-point of the ball
public float RotationAngle;
private AIBat rightBat; // Player's Bad
private Bat leftBat; // AI Bat
private float ballRelativePos;
private Rectangle rectangle3; // Used to draw the collison rectangle
private Texture2D blank; // Texture to be drawn on the collision rectangle

GameplayScreen gameplayScreen; // Creates an instance of the GameplayScreen
Game1 gameInstance; // Creates an instance of the Game1 class
int selectedStage; // Pass this into GameplayScreen for selecting easy, medium, or hard


#endregion

#region Constructors and Destructors

/// <summary>
/// Constructor for the ball
/// </summary>
public Ball(ContentManager contentManager, Vector2 ScreenSize, Bat bat, AIBat aiBat)
{
Speed = 15f;
texture = contentManager.Load<Texture2D>(@"gfx/balls/redBall");
direction = 0;
ballRect = new Rectangle(0, 0, texture.Width /2, texture.Height /2);
resetBallPos = new Vector2(ScreenSize.X / 2 + origin.X, ScreenSize.Y / 2 + origin.Y);
ballPosition = resetBallPos;
rand = new Random();
isVisible = true;
origin = new Vector2(texture.Width / 2, texture.Height / 2);
leftBat = bat; // Creates a new instance of leftBat so that I can access Position.X/Y for LeftBatPatcicles()
rightBat = aiBat;// Creates a new instance of leftBat so that can access Position.X/Y for RightBatPatcicles()
gameplayScreen = new GameplayScreen(null, selectedStage);
gameInstance = new Game1();
Rectangle rectangle3 = new Rectangle();
blank = contentManager.Load<Texture2D>(@"gfx/blank");

// pes = new ParticleEmitterService(game);
}

public Ball(Bat myBat)
{
leftBat = myBat; // this assigns and instantiates the member bat
// with myBat which was passed from the constructor
}

#endregion

#region Methods

/// <summary>
/// Draws the ball on the screen
/// </summary>
public void Draw(SpriteBatch spriteBatch)
{
if (isVisible)
{
// Draws the rotaing ball
spriteBatch.Draw(texture, ballPosition, ballRect, Color.White,
RotationAngle, origin, .0f, SpriteEffects.None, 0);

spriteBatch.Draw(blank, rectangle3, Color.LightCoral);
}
}

/// <summary>
/// Updates position of the ball. Used in Update() for GameplayScreen.
/// </summary>
public void UpdatePosition(GameTime gameTime)
{
ballRect.X = (int)ballPosition.X;
ballRect.Y = (int)ballPosition.Y;
oldBallPos.X = ballPosition.X;
oldBallPos.Y = ballPosition.Y;

ballPosition.X += Speed * ((float)Math.Cos(direction));

ballPosition.Y += Speed * ((float)Math.Sin(direction));
bool collided = CheckWallHit();


// Stops the issue where ball was oscillating on the ceiling or floor
if (collided)
{
ballPosition.X = oldBallPos.X + Speed * (float)1.5 * (float)Math.Cos(direction);
ballPosition.Y = oldBallPos.Y + Speed * (float)Math.Sin(direction);
}

// As long as the ball is to the right of the back, check for an update
if (ballPosition.X > leftBat.BatPosition.X)
{
// When the ball and bat collide, draw the rectangle where they intersect
BatCollisionRectLeft();
}

// As longas the ball is to the left of the back, check for an update
if (ballPosition.X < rightBat.BatPosition.X)
{ // When the ball and bat collide, draw the rectangle where they intersec
BatCollisionRectRight();
}

// The time since Update was called last.
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

// Rotation for the ball
RotationAngle += elapsed;
float circle = MathHelper.Pi * 2;
RotationAngle = RotationAngle % circle;

// base.Update(gameTime);
gameInstance.update();

}


/// <summary>
/// Checks for the current direction of the ball
/// </summary>
public double GetDirection()
{
return direction;
}

/// <summary>
/// Checks for the current position of the ball
/// </summary>
public Vector2 GetPosition()
{
return ballPosition;
}

/// <summary>
/// Checks for the current size of the ball (for the powerups)
/// </summary>
public Rectangle GetSize()
{
return ballRect;
}



/// <summary>
/// Checks to see if ball went out of bounds, and triggers warp sfx. Used in GameplayScreen.
/// </summary>
public void OutOfBounds()
{
AudioManager.Instance.PlaySoundEffect("Muzzle_shot");
}

/// <summary>
/// Speed for the ball when Speedball powerup is activated
/// </summary>
public void PowerupSpeed()
{
Speed += 20.0f;
}

/// <summary>
/// Check for where to reset the ball after each point is scored
/// </summary>
public void Reset(bool left)
{
if (left)
{
direction = 0;
}
else
{
direction = Math.PI;
}

ballPosition = resetBallPos; // Resets the ball to the center of the screen
isVisible = true;
Speed = 15f; // Returns the ball back to the default speed, in case the speedBall was active
if (rand.Next(2) == 0)
{
direction += MathHelper.ToRadians(rand.Next(30));
}
else
{
direction -= MathHelper.ToRadians(rand.Next(30));
}
}

/// <summary>
/// Shrinks the ball when the ShrinkBall powerup is activated
/// </summary>
public void ShrinkBall()
{
ballRect = new Rectangle(0, 0, texture.Width / 2, texture.Height / 2);
}

/// <summary>
/// Stops the ball each time it is reset. Ex: Between points / rounds
/// </summary>
public void Stop()
{
isVisible = true;
Speed = 0;
isBallStopped = true;
}

/// <summary>
/// Checks for collision with the ceiling or floor. 2*Math.pi = 360 degrees
/// </summary>
private bool CheckWallHit()
{
while (direction > 2 * Math.PI)
{
direction -= 2 * Math.PI;
return true;
}

while (direction < 0)
{
direction += 2 * Math.PI;
return true;
}

if (ballPosition.Y <= 0 || (ballPosition.Y > resetBallPos.Y * 2 - ballRect.Height))
{
direction = 2 * Math.PI - direction;
return true;
}
return true;
}

/// <summary>
/// Used to determine the location where the particles will initialize when the ball and bat collide
/// </summary>
private void BatCollisionRectLeft()
{
// For the left bat
if (ballRect.Intersects(leftBat.batRect))
{
rectangle3 = Rectangle.Intersect(ballRect, leftBat.batRect);
}
}

/// <summary>
///Checks for collision of Right Bat
/// </summary>
private void BatCollisionRectRight()
{
// for the right bat
if (ballRect.Intersects(rightBat.batRect))
{
rectangle3 = Rectangle.Intersect(ballRect, rightBat.batRect); ;
}
}


You shouldn't be passing anything as your SourceRect parameter in your draw call unless you don't wan't to draw the entire image.


The way you have it set up now is you're passing 'ballRect' as your SourceRect when trying to draw your ball, and that ballRect parameter is being updated according to the ball's position so you're trying to draw a portion of your image that is way outside of the size of the texture.


If you want to draw the entire ball, just use:


spriteBatch.Draw(texture, ballPosition, null, Color.White,
RotationAngle, origin, .0f, SpriteEffects.None, 0);

If you only wanted to draw the top-left quadrant of the ball, you could pass in the following rectangle as your SourceRect:


Rectangle sourceRect = new Rectangle(0, 0, texture.Width / 2, texture.Height / 2);

Then you could use that in your Draw call:


spriteBatch.Draw(texture, ballPosition, sourceRect, Color.White,
RotationAngle, origin, .0f, SpriteEffects.None, 0);

EDIT: You're also passing .0f as your "scale" parameter so when it does draw your ball it will be 0 pixels in size, which I'm guessing isn't the intended behavior. Using 1f for your scale will draw it at it's default size.



2 things that i'm noticing, I don't think you want to halve your rectangle dimentions when you call


ballRect = new Rectangle(0, 0, texture.Width /2, texture.Height /2);

also, you're drawing this blank sprite on top of your ball, so that if they overlap, you won't see the ball, but if that's the case I don't know why it worked with the square.



You're passing ballRect into Draw() as the source rectangle, which specifies where the sprite exists on the texture. If you want to use the entire texture, specify null instead. Otherwise, make sure that the value of ballRect always falls within the texture.


You seem to be using it to keep track of the sprite's position on the screen. From your UpdatePosition function:


ballRect.X = (int)ballPosition.X;
ballRect.Y = (int)ballPosition.Y;

This is producing texture coordinates outside of the bounds of the texture, which are being clamped by the sampler. With a texture that consists entirely of red pixels, this appears to work, because the pixels around the edge of the texture are all red. In a texture with a transparent border, the sprite is going to be clamped to that transparent color instead.



Something I noticed is that in the line


resetBallPos = new Vector2(ScreenSize.X / 2 + origin.X, ScreenSize.Y / 2 + origin.Y);

inside your constructor is accessing the properties of the variable 'origin' before you actually assign any value to it; however, you do assign a value to it, just a few lines below this value. I'd move this line:


origin = new Vector2(texture.Width / 2, texture.Height / 2);

to be above it. I would think that should fix your issue; in the transition from red block to proper image, you may have changed more then you thought.


Also, as a side note, when you're working with XNA, try to use mainly PNG images. They're small and load very quickly; additionally, they support transparent background images.


0 commentaires:

Enregistrer un commentaire