Reputation: 213
I am trying to make an RPG game in XNA and I am having problems with the movement. I want to create movement similar to in RPG maker, where you smoothly walk from tile to tile, and snap to the middle of a tile when you stop walking. How would I go about doing this? I have this, but it moves instantly from square to square with nothing between.
if (aCurrentKeyboardState.IsKeyDown(Keys.Right) == true && mPreviousKeyboardState != aCurrentKeyboardState)
{
Position.X += 30;
}
else if (aCurrentKeyboardState.IsKeyDown(Keys.Left) == true && mPreviousKeyboardState != aCurrentKeyboardState)
{
Position.X -= 30;
}
else if (aCurrentKeyboardState.IsKeyDown(Keys.Up) == true && mPreviousKeyboardState != aCurrentKeyboardState)
{
Position.Y -= 30;
}
else if (aCurrentKeyboardState.IsKeyDown(Keys.Down) == true && mPreviousKeyboardState != aCurrentKeyboardState)
{
Position.Y += 30;
}
Upvotes: 1
Views: 2083
Reputation: 168
Edit: Scott solution is very similar, but I personally prefer animating between a source and a destination than incrementing the position every frame. So here is my version:
You need to only check for keys when the character is on a tile (Middle), and not in movement. Then when a key is detected, you start a movement animation from tile1 to tile2, and wait for the animation to complete to accept inputs again.
private vars:
float m_animPercent = 1;
float m_animSpeed = 1.0f / .5f; // half a second to perform the movement. might want to try faster, like .25f (250 ms)
Vector2 m_from;
Vector2 m_to;
Update:
if (m_animPercent == 1)
{
// Can check for inputs
if (aCurrentKeyboardState.IsKeyDown(Keys.Right)) // Check for pressed only, so the player can hold the key down.
{
m_from = Position;
m_to = Position + new Vector2(30, 0); // Set the destination tile position
m_animPercent = 0;
}
[... do other movement checks... ]
}
else
{
// Animate to the new position
m_animPercent += m_animSpeed * ElapsedTime;
if (m_animPercent >= 1) m_animPercent = 1;
Position = m_from + (m_to - m_from) * m_animPercent;
}
Upvotes: 0
Reputation:
You need quite a bit more in place than you have now.
First off, as you already realized, setting the position directly is instant. The solution?
Instead of directly setting Position
, set up a velocity
:
Vector2 velocity = Vector2.Zero;
Also define some sort of movement speed:
const float speed = 10.0f;
Modify velocity
instead in your keypresses:
if (aCurrentKeyboardState.IsKeyDown(Keys.Right) == true && mPreviousKeyboardState != aCurrentKeyboardState)
{
velocity.X = new Vector2(speed, 0.0f);
}
else if (aCurrentKeyboardState.IsKeyDown(Keys.Left) == true && mPreviousKeyboardState != aCurrentKeyboardState)
{
velocity.X = new Vector2(-speed, 0.0f);
}
else if (aCurrentKeyboardState.IsKeyDown(Keys.Up) == true && mPreviousKeyboardState != aCurrentKeyboardState)
{
velocity.Y = new Vector2(0.0f, -speed);
}
else if (aCurrentKeyboardState.IsKeyDown(Keys.Down) == true && mPreviousKeyboardState != aCurrentKeyboardState)
{
velocity.Y = new Vector2(0.0f, speed);
}
Now, every frame, just update Position
to make the velocity work actively on it:
//assuming you're using GameTime gameTime for the timing values
Position += velocity * (float)gameTime.ElapsedGameTime.TotalSeconds;
(If you're using fixed game time, you don't really need the elapsed time multiply, but I typically put it in there because I prefer to measure in units per second.)
This is a completely separate issue from the "smooth" motion. Now that you've got basic velocity in, all you need now is to STOP after one full tile has been moved. There are many many ways to solve this, but I'll sketch out a simple one here.
Keep a "distance" variable to represent how far the character has travelled so far in his motion, independent of direction/axis:
float distance = 0.0f;
Increment it along with your Position
change, and do the "travelled far enough" test right after:
//assuming you're using GameTime gameTime for the timing values
Position += velocity * (float)gameTime.ElapsedGameTime.TotalSeconds;
//increment total distance indepedent of direction/axis
distance += Math.Abs(velocity.X) + Math.Abs(velocity.Y);
//test if we've travelled far enough
if (distance >= tileSize)
{
//reset distance
distance = 0.0f;
//stop
velocity = Vector2.Zero;
//TODO: SNAP TO TILE
}
This will be a good start, but you will still need to do two more things:
Snap to the nearest tile when you're done moving (the one you just travelled to). If you don't, the character will start ever so slowly getting "off" from the tile grid. To do this I suggest taking the center coordinates (coordinates + tile size / 2) of your character and converting them to tile coordinates and then convert that back to "real" coordinate floats. There are many other methods for this too.
Make sure keypresses are disabled while in motion. If you don't, the character can freely travel "off" the tile grid by interrupting motion mid-progress. You could keep a boolean inMotion
or, more succinctly, test if velocity
is not Vector2.Zero
(then you know you're in motion).
It gets a bit more advanced if you'd like to allow interrupting motion mid-way through and still stay synced to the grid, but this should give you a good start.
Upvotes: 10
Reputation: 12218
Leaving aside the questions about appropriateness:
You're adding units to the position on every key press. The right amount to move on the press is going to depend on the speed of your main loop: here you're moving 30 units on every game update, which might be a lot or a little (hard to tell without more context).
For the sake of argument, let's say your tiles are 100 units across, and we'll keep the move speed to 30 units per tick (totally made up numbers - just for illustration). So a key hold will give you something like this:
turn |0| 1| 2| 3| 4| 5| 6| 7|
units |0|30|60|90|120|150|180|210|
square|0| 0| 1| 1| 1| 2| 2| 2|
While you have the key down, you just draw the character at absolute position (60, 90, etc). When the key comes up, it becomes (in long form to make it clear):
float total_squares_moved = position / square_size;
int rounded_up = ((int) (total_square_size + .5f));
position = (rounded_up + .5f) * square_size;
which will snap to the last tile you were on. You could bias forwards or back by changing the .5f in the 'rounded_up' line to a lower number (towards the last time) or a higher one (towards the next).
Upvotes: 2