XNA 2D Camera - How to make it properly follow sprite - c#

Hello there wonderful people!
Let's just cut to the chase.
I've made a tile engine that draws my map relative to my camera (The tiles that are drawn are the ones visible in the camera window), and i've made a sprite (my character) that is centered within the camera.
Whenever i move the camera my character follows accordingly, the only problem is when i reach the borders of my map. Programing the way i did has constrained my camera from moving beyond the borders, resulting in limiting my character from moving closer to the borders(since it's always centered within the camera).
How do i free my camera from the restraints of my evil map?
p.s. I've made a picture to illustrate what i want to do.
And here is the guide i followed.
Here's my code where i draw the tiles and place the camera:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
Vector2 firstSquare = new Vector2(Camera.Location.X / Tile.TileWidth, Camera.Location.Y / Tile.TileHeight);
int firstX = (int)firstSquare.X;
int firstY = (int)firstSquare.Y;
Vector2 squareOffset = new Vector2(Camera.Location.X % Tile.TileWidth, Camera.Location.Y % Tile.TileHeight);
int offsetX = (int)squareOffset.X;
int offsetY = (int)squareOffset.Y;
for (int y = 0; y < squaresDown; y++)
{
for (int x = 0; x < squaresAcross; x++)
{
foreach (int tileID in myMap.Rows[y + firstY].Columns[x + firstX].BaseTiles)
{
spriteBatch.Draw(
Tile.TileSetTexture,
new Rectangle(
(x * Tile.TileWidth) - offsetX, (y * Tile.TileHeight) - offsetY,
Tile.TileWidth, Tile.TileHeight),
Tile.GetSourceRectangle(tileID),
Color.White);
}
}
}
spriteBatch.End();
// TODO: Add your drawing code here
base.Draw(gameTime);
}
And here is the code where i move the camera:
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
IsMouseVisible = true;
KeyboardState ks = Keyboard.GetState();
if (ks.IsKeyDown(Keys.A))
{
Camera.Location.X = MathHelper.Clamp(Camera.Location.X - 2, 0, (myMap.MapWidth - squaresAcross) * Tile.TileWidth);
}
if (ks.IsKeyDown(Keys.D))
{
Camera.Location.X = MathHelper.Clamp(Camera.Location.X + 2, 0, (myMap.MapWidth - squaresAcross) * Tile.TileWidth);
}
if (ks.IsKeyDown(Keys.W))
{
Camera.Location.Y = MathHelper.Clamp(Camera.Location.Y - 2, 0, (myMap.MapHeight - squaresDown) * Tile.TileHeight);
}
if (ks.IsKeyDown(Keys.S))
{
Camera.Location.Y = MathHelper.Clamp(Camera.Location.Y + 2, 0, (myMap.MapHeight - squaresDown) * Tile.TileHeight);
}
// TODO: Add your update logic here
base.Update(gameTime);
}

Instead of making your player follow the camera, you need to make your camera follow the player. That way you can set restrictions on the camera, instead of attempting to hack the camera system to make the character do things.
On every update, you would have something like:
Player.Update(gametime);
Camera.Update(Player.Position);

Related

Monogame - Loading multiple tiles

I'm having some issues loading multiple tiles to my game. My game world currently has a pixel size of 770x450. I have loaded a single tile at position (0, 330), and wanted to make a loop that copies and loads the tile along the x axis until it reaches (770, 330).
I have been able to make this loop, however upon every loop, the next tile doesn't load, it just moves to the next position, here's the loop:
for (int i = 0; i < 770; i += 31)
{
position = new Vector2(i, 330);
// Some sort of draw method here!
if (i == 744)
{
i = i + 26;
// or here...
position = new Vector2(i, 330);
// or maybe here?
}
}
And if this helps, here's my current Draw() method:
spriteBatch.Begin();
spriteBatch.Draw(gameTile, position, Color.White);
spriteBatch.End();
You're only drawing the tile once. You can tell because you only have one spriteBatch.Draw() call. It doesn't do enough to just update the position inside the loop. You have to draw it at each location as well.
public void Draw()
{
spriteBatch.Begin();
for (int i = 0; i < 770; i += 31)
{
position = new Vector2(i, 330);
if (i == 744)
{
i = i + 26;
position = new Vector2(i, 330);
}
spriteBatch.Draw(gameTile, position, Color.White);
}
spriteBatch.End();
}
Of course, you want to avoid all that loopy logic in the Draw() method. The only way around that is to create a tile for every position you want it drawn at in your Update() method. Then the Draw() method could just loop through all of your gameTiles and draw them at their corresponding position.

Move a sprite between two points [back and forth continuously]

I'm pretty new to XNA|C# and what I want to do is simply move a sprite between two points and have it move back and forth continuously.
Let's say I want the sprite to move between the y-coordinate 100 and 0.
How would I accomplish this?
Your question has nothing to do with XNA itself. You are asking how to move an object in a straight line and that is something that is usually learnt in first year geometry.
I'll assume you are drawing your texture like this:
SpriteTexture sprite;
Vector2 position;
...
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(sprite, position, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
A straight line is an extremely simple path - it is easily defined by two points. Let the points be P1 and P2. The straight line is then defined as the function (1 - t) * P1 + t * P2 where 0 <= t <= 1. To move the sprite, start from t = 0 and increment t in each update cycle. Computing the function with the given t gives you the position of the sprite. When t >= 1 you've reached P2, this means you should start decrementing t back to 0 and so on and so forth. Here's how to use that fact to move the sprite:
SpriteTexture sprite;
Vector2 position;
Vector2 p1 = new Vector2(0, 100),
p2 = new Vector2(0, 0);
double currentTime = 0, timestep = 0.01;
...
protected override void Update(GameTime gameTime)
{
position = currentTime * p1 + (1 - currentTime) * p2;
currentTime += timestep;
if (currentTime >= 1 || currentTime <= 0)
{
timestep *= -1;
}
}
Here is how XNA works, every frame it calls the methods update and draw in your game's main page. We need to keep track of which direction your sprite is moving and its position so lets add to your main game file:
public Vector2 rectanglePosition = new Vector2(0,0);
public bool moveRight = true;
Now what you want to do is every frame update the position, and use it to draw an object. So in the update method you would have something like
if (moveRight)
rectanglePosition.Y += 10;
else
rectanglePosition.Y -= 10;
if(rectanglePosition.Y>100 || rectanglePosition.Y<0)
moveRight = !moveright;
Then in the draw method just draw the sprite based on the position (you can start by just drawing a rectangle), which you can look up how to do easily.
I can further help you if you don't get the code.

Having problems drawing animations in monogame

I am trying to create a spaceship with multiple animations (idle, attack, damaged, etc)
But when I'm trying to change the animations I see the first frame of the current animation for a split second on the "old" position (where the animation was changed the last time)
Picture for explanation: http://i.imgur.com/L3O8rsc.png
The red rectangle is supposed to be a health bar.
The class that runs my animations:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace Test
{
class Animation
{
Texture2D spriteStrip;
float scale;
int elapsedTime;
int frameTime;
int frameCount;
int currentFrame;
Color color;
// The area of the image strip we want to display
Rectangle sourceRect = new Rectangle();
// The area where we want to display the image strip in the game
Rectangle destinationRect = new Rectangle();
public int FrameWidth;
public int FrameHeight;
public bool Active;
public bool Looping;
public Vector2 Position;
public void Initialize(Texture2D texture, Vector2 position, int frameWidth, int frameHeight, int frameCount, int frametime, Color color, float scale, bool looping)
{
this.color = color;
this.FrameWidth = frameWidth;
this.FrameHeight = frameHeight;
this.frameCount = frameCount;
this.frameTime = frametime;
this.scale = scale;
Looping = looping;
Position = position;
spriteStrip = texture;
elapsedTime = 0;
currentFrame = 0;
Active = true;
}
public void Update(GameTime gameTime)
{
if (Active == false) return;
elapsedTime += (int)gameTime.ElapsedGameTime.TotalMilliseconds;
if (elapsedTime > frameTime)
{
currentFrame++;
if (currentFrame == frameCount)
{
currentFrame = 0;
if (Looping == false)
Active = false;
}
elapsedTime = 0;
}
sourceRect = new Rectangle(currentFrame * FrameWidth, 0, FrameWidth, FrameHeight);
destinationRect = new Rectangle((int)Position.X - (int)(FrameWidth * scale) / 2, (int)Position.Y - (int)(FrameHeight * scale) / 2, (int)(FrameWidth * scale), (int)(FrameHeight * scale));
}
public void Draw(SpriteBatch spriteBatch)
{
if (Active)
{
spriteBatch.Draw(spriteStrip, destinationRect, sourceRect, color);
}
}
}
}
The reason that I wrote the whole code is because I don't know where the problem is.
In my Game1.cs class I have:
Global Variables:
Texture2D playerTexture;
Texture2D playerShootingTexure;
Animation playerAnimation = new Animation();
Animation ShootingAnimation = new Animation();
...
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
playerTexture = Content.Load<Texture2D>("Graphics/Player/PlayerShip");
playerShootingTexure = Content.Load<Texture2D>("Graphics/Player/Player_Shooting");
playerAnimation.Initialize(playerTexture, Vector2.Zero, playerTexture.Width / 5, playerTexture.Height, 5, 50, Color.Wheat, 1f, true);
ShootingAnimation.Initialize(playerShootingTexure, Vector2.Zero, playerShootingTexure.Width / 5, playerShootingTexure.Height, 5, 50, Color.WhiteSmoke, 0.75f, true);
}
Where I change the animations:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// Start drawing
spriteBatch.Begin();
if (Player_Attacking == false)
{
player.PlayerAnimation = playerAnimation;
}
else
{
player.PlayerAnimation = ShootingAnimation;
}
player.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
}
Note: Player_Attacking is a boolean variable. When 'Space' is pressed, the value of the variable changes to TRUE, when 'Space' is released, value is FALSE
Please help!
Thanks in advance!
And sorry for huge wall of text
I'm a little unclear on the exact issue, so maybe you can help clarify if I'm not understanding.
But based on your picture, it seems when you press space to fire, the player animation should change to light up his gun with blue flames (or w/e it is :)) And it looks like it is doing that, but it's in the wrong position (above the health bar - I see the laser it is shooting is in the right location :))
But then it corrects itself once the shooting is done, as shown by the second picture (no more blue flames).
So this implies the Animation for the Player Shooting is using the wrong position.
Are you:
Updating your Player Shooting Animation's Position in your Player Update?
Calling your Player Shooting Animation's Update in your Player Update?
so you should have this somewhere in your Player Update
PlayerAnimation.Position = Position;
PlayerAnimation.Update(gameTime);
Also, try moving the check for which Player Animation to use in the Update of your Game1.cs:
protected override void Update(GameTime gameTime)
{
if (Player_Attacking == false)
{
player.PlayerAnimation = playerAnimation;
}
else
{
player.PlayerAnimation = ShootingAnimation;
}
player.Update(gameTime);
}

xna sprite to mouse position

In XNA 4.0 how do I get a sprite to move to the mouses coordinates. I know that this would be possible to do like this:
if (ms.LeftButton == ButtonState.Pressed)
{
Vector2 shipPos = new Vector2(ms.X,ms.Y);
}
but because I'm using a camera that follows the ship this does not work properly. The reason for this is that the position of the mouse is relative to the screen and if the ship has been moved to let say (500,500) when I click in the top left corner of the window the ship goes back to (0,0), even tough I want the ship to move from the ships position up towards the corner. Here's the code for my matrix:
class Camera
{
public Matrix transform;
Viewport view;
Vector2 centre;
public Camera(Viewport newView)
{
view = newView;
}
public void Update(GameTime gameTime, Game1 ship)
{
int w = Game1.width;
int h = Game1.height;
centre = new Vector2(ship.FSpos.X - (w / 2 - 189/2),
ship.FSpos.Y - (h / 2-128/2));
transform = Matrix.CreateScale(new Vector3(1, 1, 0)) *
Matrix.CreateTranslation(new Vector3(-centre.X, -centre.Y, 0));
}
}
You could do something like this:
if (ms.LeftButton == ButtonState.Pressed)
{
Vector2 shipPos = new Vector2(
ms.X-screenWidth/2 + previousShipPosition.X,
ms.Y-screenHeight/2 + previousShipPosition.Y
);
}

Sprite vibrates. What is wrong?

My enemy sprite vibrates all the time after it has reached the variable endposition. What is wrong? Why is it vibrating?
In addition, the variable next_position is never true. But why? I want that the enemy sprite moves to a new random endposition after it reached the current endposition.
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D enemy;
Vector2 position, endposition;
bool next_position = false;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 1280;
graphics.PreferredBackBufferHeight = 720;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
enemy = Content.Load<Texture2D>("zombie");
Random randomstart = new Random();
position = new Vector2(randomstart.Next(100, 200), randomstart.Next(100, 200));
endposition = new Vector2(randomstart.Next(100, 600), randomstart.Next(100, 400));
}
protected override void Update(GameTime gameTime)
{
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
Random random = new Random();
position.X += 5 * delta;
position.Y += 3 * delta;
if (next_position == true)
{
endposition = new Vector2(random.Next(100, 600), random.Next(100, 400));
next_position = false;
}
if (Vector2.Dot(endposition - position, endposition - position) > 0 || Vector2.Dot(endposition - position, endposition - position) < 0)
{
Vector2 enemyDirection = Vector2.Normalize(endposition - position) * 100f;
position += enemyDirection * delta;
}
else
{
next_position = true;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(enemy, position, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
I am slightly confused by what the line
if (Vector2.Dot(endposition - position, endposition - position) > 0 || Vector2.Dot(endposition - position, endposition - position) < 0)
is supposed to do exactly.
From the rest of the code what you are trying to achieve with it, is checking whether the sprite's position is the enemy's position.
What the line actually does is calculating dot products of vectors in opposite directions, which will never be zero, unless the vectors are zero. So in theory this might actually work, but only if the position equals the enemy's position precisely.
However, you are moving the sprite a fixed distance per unit of time, and so it actually does not reach the enemy but overshoots it. Finding itself then on the other side it moves back, where it came from, and overshoots it again. This continues and results in the vibration you describe.
Now, the way the behaviour you want to implement is usually done is calculating the distance between the two objects(sprite and enemy), using Pythagoras' theorem, and then accepting the arrival of the object when the distance is either below a certain threshold(easy but prone to error), or shorter than the distance the object could travel this particular update(in your case, since you move by a normalized vector times 100f * delta, this distance is equal to 100f * delta).
I hope my explanation is helpful.
Of course, feel free to ask for clarifications.
Edit(response to comment):
The limit vector you are creating is relatively meaningless, as it is just the position offset by max_distance in both x and y direction. There is no need for anything like that.
However, the other if() includes pretty much the code you want, check this out:
// first you calculate the distance we can move this update
float max_distance = 100f * delta;
// now, we compare the actual distance to the end position to that maximum
// if the actual distance is larger, then we are not there yet
// otherwise, (if the actual distance is smaller) we will overshoot the target,
// thus we are in fact there! (=close enough to continue)
// (note: we compare the distances squared to avoid a square root,
// which makes this check much faster, which is good practice,
// in case we ever scale this up to more objects)
if ((position - endposition).LengthSquared() > max_distance * max_distance)
{
/* we are not yet there, continue moving! */
}
else
{
/* we are there! */
}
It really is this simple and no other check is needed. Let me know if this works out for you!
It’s better now, but not perfect. The enemy sprite doesn’t vibrate anymore, but it always moves some pixels down right if it has reached the end position. Why does it always move some pixels down right?
The rest works fine.
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D enemy;
Vector2 position, endposition;
bool next_position = false;
int count_nextposition;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 1280;
graphics.PreferredBackBufferHeight = 720;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
enemy = Content.Load<Texture2D>("zombie");
Random randomstart = new Random();
position = new Vector2(randomstart.Next(100, 200), randomstart.Next(100, 200));
endposition = new Vector2(randomstart.Next(100, 600), randomstart.Next(100, 400));
count_nextposition = randomstart.Next(90, 121);
}
protected override void Update(GameTime gameTime)
{
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
count_nextposition -= 1;
Random random = new Random();
position.X += 5 * delta;
position.Y += 3 * delta;
if (count_nextposition <= 0)
{
if (next_position == true)
{
endposition = new Vector2(random.Next(100, 600), random.Next(100, 400));
next_position = false;
}
float max_distance = 100f * delta;
if ((position - endposition).LengthSquared() > max_distance * max_distance)
{
Vector2 enemyDirection = Vector2.Normalize(endposition - position) * 100f;
position += enemyDirection * delta;
}
else
{
next_position = true;
count_nextposition = random.Next(90, 121);
}
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(enemy, position, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}

Categories

Resources