Negative scaling with spritebatch in XNA vs Primitives - c#

I am using both primitives and sprites in an XNA project. I draw my primitives using this code (a summary, not verbatim from my project):
transmatrix = Matrix.CreateTranslation(v23(-pos)) * Matrix.CreateScale(scale, -scale, 1f) * Matrix.CreateTranslation(v23(offset));
basicEffect.World = transmatrix;
basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up);
basicEffect.Projection = Matrix.CreateOrthographicOffCenter(0, (float)GraphicsDevice.Viewport.Width, (float)GraphicsDevice.Viewport.Height, 0, 1.0f, 1000.0f);
basicEffect.Begin();
//draw primitives blah blah
basicEffect.End();
spritebatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None, transmatrix);
//draw sprites blah blah
spritebatch.end()
if I have
transmatrix = Matrix.CreateTranslation(v23(-pos)) * Matrix.CreateScale(scale, scale, 1f) * Matrix.CreateTranslation(v23(offset));
both the primitives and the sprites draw (but y is the inverse of what I want).
if I have
transmatrix = Matrix.CreateTranslation(v23(-pos)) * Matrix.CreateScale(scale, -scale, 1f) * Matrix.CreateTranslation(v23(offset));
The primitives draw correctly, but the sprites don't draw at all. What am I missing? I have tried messing around with lots of different things but nothing seems to work.

It turns it was another problem that was causing the issue. I have to scale all the sprites with negative y to get them to draw correctly as mentioned in a msdn community content "Something that is not obvious when using this form of the method to map SpriteBatch to a desired coordinate system is the effect of negative scaling on the results. If you scale by a single negative number in the X or Y axes (if, for instance, you want to flip the Y-axis to match your game coordinates) the polygon that the sprite gets drawn on gets flipped in the process and gets backface-culled. It is necessary to also scale the texture by a negative in the same direction to get the results you expect."
http://msdn.microsoft.com/en-us/library/ff433701.aspx

SpriteBatch sets up its own "camera" using its own parameters. To scale your camera, do this:
basicEffect.View = Matrix.CreateScale(1.0f, -1.0, 1.0f) * Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up);
Or you could just create a different matrix for the spritebatch.
Alternatively, you could pass in your BasicEffect to SpriteBatch:
http://msdn.microsoft.com/en-us/library/ff433700.aspx

Related

Scale texture to fit a circular body

I have the following code in my MonoGame/Farseer Physics project:
_ball = BodyFactory.CreateCircle(World, 1f, 400f);
_ball.BodyType = BodyType.Dynamic;
_ballSprite = new Sprite(ScreenManager.Content.Load<Texture2D>("Common/ball"));
In my Draw method:
ScreenManager.SpriteBatch.Draw(_ballSprite.Texture, ConvertUnits.ToDisplayUnits(_ball.Position), null, Color.White, _ball.Rotation, _ballSprite.Origin, 1f, SpriteEffects.None, 0f);
The problem is that my texture is 120px by 120px, but when it renders on screen, the _ball body is larger than it in size. What can I do to resize the texture to fit exactly the width and height of the _ball body?
I think I have solved this mystery at last. I needed to compute the scale (_ballTextureScale that is, which was the part that I was stuck on):
var radius = 1f;
_ball = BodyFactory.CreateCircle(World, radius, 400f);
_ball.BodyType = BodyType.Dynamic;
var rectangleTexture = ScreenManager.Content.Load<Texture2D>("Common/ball");
_ballTextureScale = ConvertUnits.ToDisplayUnits(radius * 2) / rectangleTexture.Width;
This converts the radius * 2 (total width of the circle) to display units which it seems the rectangle texture is already in. Then when I go to draw the body, I just needed to make use of this _ballTextureScale property:
ScreenManager.SpriteBatch.Draw(_ballSprite.Texture, ConvertUnits.ToDisplayUnits(_ball.Position), null, Color.White, _ball.Rotation, _ballSprite.Origin, _ballTextureScale, SpriteEffects.None, 0f);
If this is an incorrect approach, I am all ears to a better method for accomplishing this task, however it seems to work very well for my needs.

Translation on a X,Z plane using variable axis relative to camera orientation

I have a board on the world X,Z axis.
I have a camera that can rotate to look downward or upward and rotate left to right using the world Y axis.
Here's the camera rotation code:
float angle = (mouseX_Current - mouseX_ActionStart) * camRotationSpeedMod;
Camera.main.transform.Rotate(0.0f, angle, 0.0f, Space.World);
angle = (mouseY_Current - mouseY_ActionStart) * camRotationSpeedMod;
Camera.main.transform.Rotate(angle, 0.0f, 0.0f, Space.Self);
Using mouse button down and a mouse position input(drag&Drop) I translate the camera in the world. It receive a 2D vector that contain the X and Y value of the mouse(drag&drop) operation. Here's the code of the method I use :
private void CameraOnPlaneTranslation(Vector2 myVector)
{
float cameraYPos = Camera.main.transform.position.y;
Camera.main.transform.Translate(new Vector3(myVector[0], 0.0f, myVector[1]));
Camera.main.transform.position = new Vector3(Camera.main.transform.position.x, cameraYPos, Camera.main.transform.position.z);
}
Problem : When my camera is not oriented directly toward the Z Axis(0.0f, Z, 0.0f) the translation vector I compute is incorrect. I need to change the Y value of the vector so the camera don't move on the world Y axis.
Thanks
I am not familiar with Unity, but supposing it is similar to other 3D systems on these points, I will attempt an answer and hopefully not embarrass myself.
It looks like you /translate/ the camera position, then immediately /set/ the camera position, thus overwriting the work done by translate. If this is the case, you should try setting camera.main.transform.position once (in the line after you create the Camera). Then only apply myVector by the Translate call (as you have it). Thus you should eliminate the third line of the method Camera.main.transform.position = new Vector3(Camera.main.transform.position.x, cameraYPos, Camera.main.transform.position.z);
After trying this, if it still does not do what you expect, see if it needs you to Update the view or Flush or ApplyTransform . . . anything that will make it understand you are done altering the transform and you are ready for it to process again with the updated transform.
I've solved this by adding another GameObject that is affected only by the camera right/left rotations(world up vector) and the camera translations.
In the Awake method
cameraOrientation = new GameObject("cameraOrientation");
cameraOrientation.transform.position = Camera.main.transform.position;
cameraOrientation.transform.localEulerAngles = new Vector3(0.0f, Camera.main.transform.localEulerAngles.y, 0.0f);
In the rotation method
float angle = (mouseX_Current - mouseX_ActionStart) * camRotationSpeedMod;
Camera.main.transform.Rotate(0.0f, angle, 0.0f, Space.World);
cameraOrientation.transform.Rotate(0.0f, angle, 0.0f, Space.World);
angle = (mouseY_Current - mouseY_ActionStart) * camRotationSpeedMod;
Camera.main.transform.Rotate(angle, 0.0f, 0.0f, Space.Self);
And finally the translation method
cameraOrientation.transform.Translate(new Vector3(myVector[0], 0.0f, myVector[1]));
Camera.main.transform.position = cameraOrientation.transform.position;

XNA 3.5 Firing a tank cannon

I have a 2D image of a tanks body (top down) that can be moved left-right across the screen.
On top, there's a second image of the tanks turret. This turret can be rotated across the screen edge following the users mouse movement along the Y axis.
When the user presses'Enter' a bullet appears and moves across the screen at the angle of the turret. However, whilst the angle is fine, the bullet's position seems to vary a lot. At times, it sites where it should (in the centre of the cannon) however, as you move the mouse it seems to get offsetted.
Edit: For some strange reason, my pictures don't seem to be showing up - so here is a direct link: http://img824.imageshack.us/img824/7093/khte.png
Edited Code:
Tank Fire
if (keyBoardState.IsKeyDown(Keys.Enter))
{
shell.Initialize(rotation, new Vector2(location.X + 25, location.Y - 15));
shell.makeAlive();
}
(It is initialized with the location of the tank (+25, -25) so it appears at the end of the turrent. having this set at the tanks location (shell.Initialize(rotation, location);) seems to make no difference to the offset.)
Bullet/Shell:
public void Update(GameTime gameTime)
{
if (alive)
{
movement.X -= speed * (float)Math.Cos(rotation);
movement.Y -= speed * (float)Math.Sin(rotation);
location.X += (int)movement.X;
location.Y += (int)movement.Y;
}
}
public void Draw(SpriteBatch spriteBatch)
{
if (alive)
{
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(texture, location, null, Color.White, rotation - MathHelper.PiOver2, new Vector2(texture.Width / 2, texture.Height / 2), 1.0f, SpriteEffects.None, 0f);
spriteBatch.End();
}
}
If you're trying to get your bullet to rotate about your tank, than I personally draw the bullet on top of the tank do the exact same thing that you do after you've fired your bullet(but with a greater magnitude) to displace it,
But if you want to use the origin parameter of SpriteBatch.Draw, than I'd imagine that it would look something like this:
given the following datapoints:
this.origin = new Vector2(texture.Width / 2, texture.Height / 2);
tank.origin = new Vector2(texture.Width / 2, texture.Height / 2);
// and this is what you'd pass into your sprite-batch.draw method
originToDraw = (tank.position + tank.origin)-(this.position + this.origin)
And you also have to initialize your bullet's position to the appropriate starting point Since I don't actually know where rotation comes from, I can't know for sure, but if it's the angle between your tank and the mouse, than I'd imagine it'd be the following
this.startingPosition = tank.position + tank.origin - this.origin
I asume that shell is a Bullet instance if yes your problem is the origin in Bullet class. It should have set this value in LoadTextures method
public void LoadTextures(Texture2D texture)
{
this.texture = texture;
this.origin = new Vector2(texture.Width, texture.Height);
}
And a little extra in Bullet.Draw method:
spriteBatch.Draw(texture, location, null, Color.White, rotation - 1.5f, origin, 1.0f, SpriteEffects.None, 0f);
change 1.5f to MathHelper.PiOver2 it is more accure when you want to rotate something 90 degrees
You have a nasty roundoff problem here.
You compute the velocity in floating point but switch to integers for the bullet's location. The problem is that while ON AVERAGE the errors even out any given bullet is going to either round up or round down every time.
Lets take an extreme case: The bullet is fired 25 degrees off the vertical axis and it's moving at two pixels per update cycle. Strangely enough the bullet flies directly down the axis.
Or an even more extreme case. The speed is 1/3 pixel per cycle. The bullet stands still.

Having the Background or Camera "Scroll" based on charcter position

I'm working on an RPG game that has a Top-Down view. I want to load a picture into the background which is what the character is walking on, but so far I haven't figured out how to correctly have the background redraw so that it's "scrolling". Most of the examples I find are auto scrolling.
I want the camera to remained centered at the character until you the background image reaches its boundaries, then the character will move without the image re-drawing in another position.
Your question is a bit unclear, but I think I get the gist of it. Let's look at your requirements.
You have an overhead camera that's looking directly down onto a two-dimensional plane. We can represent this as a simple {x, y} coordinate pair, corresponding to the point on the plane at which the camera is looking.
The camera can track the movement of some object, probably the player, but more generally anything within the game world.
The camera must remain within the finite bounds of the game world.
Which is simple enough to implement. In broad terms, somewhere inside your Update() method you need to carry out steps to fulfill each of those requirements:
if (cameraTarget != null)
{
camera.Position = cameraTarget.Position;
ClampCameraToWorldBounds();
}
In other words: if we have a target object, lock our position to its position; but make sure that we don't go out of bounds.
ClampCameraToBounds() is also simple to implement. Assuming that you have some object, world, which contains a Bounds property that represents the world's extent in pixels:
private void ClampCameraToWorldBounds()
{
var screenWidth = graphicsDevice.PresentationParameters.BackBufferWidth;
var screenHeight = graphicsDevice.PresentationParameters.BackBufferHeight;
var minimumX = (screenWidth / 2);
var minimumY = (screnHeight / 2);
var maximumX = world.Bounds.Width - (screenWidth / 2);
var maximumY = world.Bounds.Height - (screenHeight / 2);
var maximumPos = new Vector2(maximumX, maximumY);
camera.Position = Vector2.Clamp(camera.Position, minimumPos, maximumPos);
}
This makes sure that the camera is never closer than half of a screen to the edge of the world. Why half a screen? Because we've defined the camera's {x, y} as the point that the camera is looking at, which means that it should always be centered on the screen.
This should give you a camera with the behavior that you specified in your question. From here, it's just a matter of implementing your terrain renderer such that your background is drawn relative to the {x, y} coordinate specified by the camera object.
Given an object's position in game-world coordinates, we can translate that position into camera space:
var worldPosition = new Vector2(x, y);
var cameraSpace = camera.Position - world.Postion;
And then from camera space into screen space:
var screenSpaceX = (screenWidth / 2) - cameraSpace.X;
var screenSpaceY = (screenHeight / 2) - cameraSpace.Y;
You can then use an object's screen space coordinates to render it.
Your can represent the position in a simple Vector2 and move it towards any entity.
public Vector2 cameraPosition;
When you load your level, you will need to set the camera position to your player (Or the object it should be at)
You will need a matrix and some other stuff, As seen in the code below. It is explained in the comments. Doing it this way will prevent you from having to add cameraPosition to everything you draw.
//This will move our camera
ScrollCamera(spriteBatch.GraphicsDevice.Viewport);
//We now must get the center of the screen
Vector2 Origin = new Vector2(spriteBatch.GraphicsDevice.Viewport.Width / 2.0f, spriteBatch.GraphicsDevice.Viewport.Height / 2.0f);
//Now the matrix, It will hold the position, and Rotation/Zoom for advanced features
Matrix cameraTransform = Matrix.CreateTranslation(new Vector3(-cameraPosition, 0.0f)) *
Matrix.CreateTranslation(new Vector3(-Origin, 0.0f)) *
Matrix.CreateRotationZ(rot) * //Add Rotation
Matrix.CreateScale(zoom, zoom, 1) * //Add Zoom
Matrix.CreateTranslation(new Vector3(Origin, 0.0f)); //Add Origin
//Now we can start to draw with our camera, using the Matrix overload
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default,
RasterizerState.CullCounterClockwise, null, cameraTransform);
DrawTiles(spriteBatch); //Or whatever method you have for drawing tiles
spriteBatch.End(); //End the camera spritebatch
// After this you can make another spritebatch without a camera to draw UI and things that will not move
I added the zoom and rotation if you want to add anything fancy, Just replace the variables.
That should get you started on it.
However, You will want to make sure the camera is in bounds, and make it follow.
Ill show you how to add smooth scrolling, However if you want simple scrolling see this sample.
private void ScrollCamera(Viewport viewport)
{
//Add to the camera positon, So we can see the origin
cameraPosition.X = cameraPosition.X + (viewport.Width / 2);
cameraPosition.Y = cameraPosition.Y + (viewport.Height / 2);
//Smoothly move the camera towards the player
cameraPosition.X = MathHelper.Lerp(cameraPosition.X , Player.Position.X, 0.1f);
cameraPosition.Y = MathHelper.Lerp(cameraPosition.Y, Player.Position.Y, 0.1f);
//Undo the origin because it will be calculated with the Matrix (I know this isnt the best way but its what I had real quick)
cameraPosition.X = cameraPosition.X -( viewport.Width / 2);
cameraPosition.Y = cameraPosition.Y - (viewport.Height / 2);
//Shake the camera, Use the mouse to scroll or anything like that, add it here (Ex, Earthquakes)
//Round it, So it dosent try to draw in between 2 pixels
cameraPosition.Y= (float)Math.Round(cameraPosition.Y);
cameraPosition.X = (float)Math.Round(cameraPosition.X);
//Clamp it off, So it stops scrolling near the edges
cameraPosition.X = MathHelper.Clamp(cameraPosition.X, 1f, Width * Tile.Width);
cameraPosition.Y = MathHelper.Clamp(cameraPosition.Y, 1f, Height * Tile.Height);
}
Hope this helps!

Managed DirectX: Specifying the Depth (Z-Order) of Sprites containing Texture and Text

I am using managed DirectX to try and draw a texture and a piece of text to the screen using a Sprite. Unfortunately, if I place the text and the texture in the same sprite, the texture overwrites (overdraws?) the text regardless of the order I do the draw commands.
Since I will eventually want to intersperse textures and text, how do I specify a Z-order for these sprites. Does each layer have to be in a separate sprite?
Here is the current code:
m_device.BeginScene();
m_device.Clear(ClearFlags.Target, Color.Black, 1.0f, 0);
m_sprite.Begin(SpriteFlags.SortTexture | SpriteFlags.AlphaBlend);
// Switching the order of following two statements doesn't change the Z-Order!
m_sprite.Draw(m_texture, Vector3.Empty, new Vector3(0, 0, 0),
Color.White.ToArgb());
m_d3dFont.DrawText(m_sprite, m_text, x, y, color);
m_sprite.End();
m_device.EndScene();
m_device.Present();
Note: Using the SpriteFlags.SortDepthBackToFront or SpriteFlags.SortDepthBackToFront does not change the behaviour.
This is probably a conceptual misunderstanding on my part, but if code is useful, I'll gratefully accept samples in unmanaged DirectX using C++ or whatever language.
Many thanks in advance!
If you want to change the Z-Order of rendering then you have to set the Z value in the Draw command. If you set the all to 0 you will get all sorts of weirdness. Your bigger issue is that DrawText doesn't allow you to set a Z-Depth which is all kinds of rubbish.
Thus your only chance is to use ID3DXSprite::SetTransform. You need to shift only the Z-Coordinate back with it for the relevant z ordering position. So you can set your transforms (assuming you are using identity world matrices) as follows (C++ example)
D3DXMATRIX mat( 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, zOrder, 1.0f );
m_Sprite->SetTransform( &mat );
You then carry on passing a position of (0, 0, 0) for rendering and text will also gain the correct depth for z-ordering.
Hope that helps.
Here is the answer I (the poster) went with:
Goz provided most of the information needed to solve the problem. Essentially, for textures, you can specify the z-order using the third parameter of the second Verctor3. Experimentation has made it clear that (1) z order goes from 0.0 to 1.0, with 0.0 being closest and 1.0 being the the furthest away. Anything out of that range doesn't appear at all.
For text, because there's no opportunity to specify the z in the call, you need to use Goz's suggestion of a transform matrix.
In the end, here's roughly the code I used.
m_device.BeginScene();
m_device.Clear(ClearFlags.Target, Color.Black, 1.0f, 0);
m_sprite.Begin(SpriteFlags.SortDepthFrontToBack | SpriteFlags.SortTexture | SpriteFlags.AlphaBlend);
// The "1.0f" is the z-order of texture1. This places it at the very back.
m_sprite.Draw(m_texture1, Vector3.Empty, new Vector3(0, 0, 1.0f),
Color.White.ToArgb());
// The text1 is placed at z-order 0.8f, in order to place it in front of texture1
Microsoft.DirectX.Matrix t = Microsoft.DirectX.Matrix.Identity;
t.Translate(new Vector3(0, 0, 0.8f));
m_sprite.Transform = t;
m_d3dFont.DrawText(m_sprite, m_text1, 200, 200, color1);
m_sprite.Transform = Microsoft.DirectX.Matrix.Identity;
// The "0.6f" is the z-order of texture2. This places it at the very back.
m_sprite.Draw(m_texture2, Vector3.Empty, new Vector3(220, 220, 0.6f),
Color.White.ToArgb());
// The text2 is placed at z-order 0.4f, in order to place it in front of texture2
t = Microsoft.DirectX.Matrix.Identity;
t.Translate(new Vector3(0, 0, 0.4f));
m_sprite.Transform = t;
m_d3dFont.DrawText(m_sprite, m_text2, 240, 240, color2);
m_sprite.Transform = Microsoft.DirectX.Matrix.Identity;
m_sprite.End();
m_device.EndScene();
m_device.Present();

Categories