3
\$\begingroup\$

I am making a Pacman game using XNA. The game is tile based, with each tile being 32 pixels. As the player moves, I need to know whenever it is perfectly on a tile (ie position of 32, 64, etc...) so that I can check to see if the next tile is free. I am using the following logic to test this.

if (position.X % 32 == 0 && position.Y %32 == 0)
{
    onTile = true;
}

I figure that I need to make the player's speed evenly divide 32. Everything works fine if I make the player's speed an integer such as 4 or 8. But if I make the speed something like 6.4, I end up with positions such as 64.00001, and my if statement no longer works correctly.

How can I keep the player aligned with the grid, while allowing a wider range of player speeds than 1, 2, 4, 8, 16, and 32? Or is there some better way to go about this?

Thanks

\$\endgroup\$

3 Answers 3

2
\$\begingroup\$

I've used the modulus operator method before while I was trying to emulate the movement in classic JRPG games, and also ran into the same limitation as you did. In order to be able to use any speed value, you need to approach the problem in a very different way.

The method I suggest is to treat movement as if you were following a path, moving your character between each node in the path at whatever speed you want, but making sure to keep the path nodes aligned to the grid. Because if all of the end points of your movement are aligned to the grid, then your character will also remain aligned, no matter what speed he's moving at.

For this answer I'll just give an example of how it can be implemented, although there are many other ways to do it. I've based the implementation for this answer on another answer I've given before, which I suggest you to check if you have any questions about the method below:

The core of that answer is the following method which moves the position towards a goal, snaps to the goal if the movement happens to overshoot, and lets the user know when the movement has finished:

private bool MoveTowardsPoint(Vector2 goal, float elapsed)
{
    // If we're already at the goal return immediatly
    if (_position == goal) return true;

    // Find direction from current position to goal
    Vector2 direction = Vector2.Normalize(goal - _position);

    // Move in that direction
    _position += direction * Speed * elapsed;

    // If we moved PAST the goal, move it back to the goal
    if (Math.Abs(Vector2.Dot(direction, Vector2.Normalize(goal - _position)) + 1) < 0.1f)
        _position = goal;

    // Return whether we've reached the goal or not
    return _position == goal;
}

Using this method, it's pretty simple to move your character around. But unlike the other answer, this time instead of using a list of positions as the path for the character to follow, I decided to only record what the next target is, and compute a new target whenever the old one is reached.

I created these variables for the pacman, that specify its position, speed, direction, and a nullable Vector2 to store the next target in its path:

private Vector2 _position = new Vector2(32, 32);
private const float Speed = 156;
private Vector2 _direction = new Vector2(0, 1);
private Vector2? _target;

And here's the bulk of the Update method:

// Detect user input and set direction
if (Keyboard.GetState().IsKeyDown(Keys.Down)) _direction = new Vector2(0, 1);
if (Keyboard.GetState().IsKeyDown(Keys.Right)) _direction = new Vector2(1, 0);
if (Keyboard.GetState().IsKeyDown(Keys.Up)) _direction = new Vector2(0, -1);
if (Keyboard.GetState().IsKeyDown(Keys.Left)) _direction = new Vector2(-1, 0);

// If there's no target and we can move in the chosen direction, set new target to be the next tile
if (_target == null)
    if (CanMoveInDirection(_position, _direction))
        _target = _position + _direction * 32;

// If there's a target, then move pacman towards that location, and clear target when destination is reached
if (_target != null)
    if (MoveTowardsPoint(_target.Value, (float) gameTime.ElapsedGameTime.TotalSeconds))
        _target = null;

The comments should be self explanatory, but I suggest you to try the sample below and see for yourself how it works. Just copy the code into a new XNA project, it does not require any assets to run.

PS: I know some parts of the movement are not behaving exactly like pacman, such as stopping when changing direction towards a wall. The point of this answer is really just the grid aligned movement.

\$\endgroup\$
2
  • \$\begingroup\$ The sample you provided works really well. I like how truly any speed is allowed, not just speeds that evenly divide the tile size. Thank you very much for your answer. \$\endgroup\$
    – Marc
    Commented Jul 1, 2012 at 13:26
  • \$\begingroup\$ @Marc Glad it helped :) By the way, I've noticed that this is your first post on stackexchange so I'll inform you in case you don't know. When you ask a question and one of the answers is the solution to your problem, you should mark it as an Answer with the third button on the left side of the post. This turns the symbol of the question into green, and lets other users know that the answer worked. \$\endgroup\$ Commented Jul 1, 2012 at 16:57
2
\$\begingroup\$

I would keep the position in units of tiles, not pixels, in an integer. Then I'd Hold the direction the player is moving, it could take values like, NORTH, SOUTH, EAST, etc. Then I would have two offsets as floats. On each update, I would then use the direction to figure out which offset to add to. If I was moving NORTH, I would subtract speed variable from the yOffset. If I was going SOUTH, I would add speed to yOffset. If I was going WEST, I would subtract speed from xOffset. And so on. Finally I would check to see if the yOffset is greater than the tile size and if it is, I'd add 1 to it's yPosition. If it's less, I'd subtract. I would do the same for the x. When rendering I would always do xPosition + xOffset. Which I'd do for the y too.

\$\endgroup\$
4
  • \$\begingroup\$ I might add code later \$\endgroup\$
    – Lysol
    Commented Jun 30, 2012 at 15:07
  • \$\begingroup\$ I like this approach, but it seems to have a similar problem to my original approach because it will not work correctly if the xOffset is something such as 31.99999. Is there a good way to deal with this? Or is the best way to just round the player's position if it is close to a whole number? \$\endgroup\$
    – Marc
    Commented Jun 30, 2012 at 16:35
  • \$\begingroup\$ @Marc it will still work if the offset is a float. Float(34.001) is still > Int(32). Rounding was the first thing I thought of when I saw your question and may work, depending on your exact application. \$\endgroup\$
    – Wackidev
    Commented Jul 1, 2012 at 0:20
  • \$\begingroup\$ Im surprised I got up votes. There are some bugs with this and I think I'll rethink this. \$\endgroup\$
    – Lysol
    Commented Jul 1, 2012 at 2:42
1
\$\begingroup\$

What I've done is previous projects is not match the player exactly to a particular tile but more like if they are mostly overlapping a tile.

Vector2 tilePosition = new Vector2(position.X % 32, position.Y % 32);
if(tilePosition.X >= 16)
    tilePosition.X = position.X + 32 - tilePosition.X;
else
    tilePosition.X = position.X - tilePosition.X;

... repeat for Y ...

Obviously there are cleaner ways of writing that but the idea is that if half of the player is overlapping the next tile, assume they are on said tile. And since you are checking if they can intersect the next tile before hand you can always assume that they are never half on an open tile and half on a collidable tile.

EDIT: The idea behind this approach is that unless you use exact multiple of your tile size as your movement speed then you can never assume that the player exactly occupies a single tile.

Instead, when a player is colliding with at least 50% of a tile, consider them to be occupying that tile.

Tile collision example

In the image above, we would consider the player to be occupying the green tile instead of the blue tile.

\$\endgroup\$
4
  • \$\begingroup\$ I am having some trouble figuring out how to apply your answer to my problem. If the player is currently between two tiles, I allow the player to continue in the direction he is moving until he is perfectly on a single tile, and then I can check surrounding tiles for which direction the player is next allowed to move. I am interested in a check I can do so that I know when the player is perfectly on the tile, which I am not sure that code answers. Am I misunderstanding your answer? Thanks \$\endgroup\$
    – Marc
    Commented Jun 30, 2012 at 16:29
  • \$\begingroup\$ You're right, this doesn't let you know when they're perfectly aligned with a tile but that's the point. The idea is that if you are going to have variable positioning (i.e. not a multiple of 32) then you can never assume a perfect alignment with a tile. So instead of looking for perfect alignment, consider them to be on a tile when they are at least halfway on the tile. \$\endgroup\$
    – Mike Cluck
    Commented Jun 30, 2012 at 17:37
  • \$\begingroup\$ It seems like a good technique to assign a tile to the player when the player is between two tiles. But the problem I have with something like Pacman is that if the player is moving left and is halfway between two tiles, I do not want to allow the player to change directions at that moment, as it will be a big jump to go from something like position (15, 0) to position (0, 4). \$\endgroup\$
    – Marc
    Commented Jul 1, 2012 at 13:35
  • \$\begingroup\$ Ohhhh I see. My mistake, I misunderstood the question. \$\endgroup\$
    – Mike Cluck
    Commented Jul 1, 2012 at 16:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .