Advertisement

Smooth Tile movement.

Started by December 29, 2014 02:22 AM
3 comments, last by Zaphyk 10 years ago

Tiles are 32x32 pixels, if i move the character by 32 pixels like in this piece of code the characters moves too roughly but if i move him by less pixels he can stay in middle of 2 tiles. Can someone give some pseudo code to orientate me? Thanks


    	int TILESIZE = 32;
        Input input = container.getInput();
    	if (input.isKeyDown(Input.KEY_W) && Y != 0) {
    		Y -= Game.TILESIZE;// moves the sprite by 32 pixel up
    		Movement.update(delta);//updates animation
    	}else if (input.isKeyDown(Input.KEY_A) && X != 0) {
    		X -= Game.TILESIZE;
    		Movement.update(delta);
    	}else if (input.isKeyDown(Input.KEY_D) && X != Game.MapLimitX) {
    		X += Game.TILESIZE;
    		Movement.update(delta);
    	}else if (input.isKeyDown(Input.KEY_S) && Y != Game.MapLimitY) {
    		Y += Game.TILESIZE;
    		Movement.update(delta);
    	}

Hello Zaphyk,

if you want a smooth transition, just add the 32 px pixel by pixel each update and not all at once.

Regards

Henry

Advertisement

Assuming you want movement system where the character can only walk a single tile per time (a lot of games from the 16 bit era use this kind of movement, e.g.: shinning force), you will need to have a "state" variable. You set the state variable to the direction you want and in your main loop you update the position smoothly, not accepting other inputs meanwhile.

Something like that:


enum movement_states {
  IDLE, MOVING_UP, MOVING_DOWN, MOVING_LEFT, MOVING_RIGHT;
}

void process_input(input, character, delta){
  if (character.getState() != IDLE){
    update_position(character, delta);
    return;
  }
  if (input.isKeyDown(Input.KEY_A && x != 0){
    character.setState(MOVING_LEFT);
    character.setTargetX(X - Game.TILE_SIZE);
    update_position(character, delta);
  }
  else if ... // same logic for UP, DOWN and RIGHT
}

void update_position (character, delta){
  if (character.getState() == MOVING_LEFT){
    if (character.getX() == character.getTargetX()){
      return;
    }
    newX = character.getX() - (character.getMovespeed() * delta);
    if (newX <= character.getTargetX()){
      newX = charcter.getTargetX();
      character.setState(IDLE);
    }
    charater.setX(newX);
  }
  else if ... // same logic for UP, DOWN and RIGHT
}

Very important notes:

- This code can be made much simpler, but it wouldn't be as straight foward.

- This code assumes that you call process_input every frame.

- This code updates position on the process_input, this will not lead to clear code, it would be better to call update_position in the main loop (adding the necessary restrictions/parameters), but the pseudo-code would get messy.

Hope this helps.

Currently working on a scene editor for ORX (http://orx-project.org), using kivy (http://kivy.org).

There are two main ways to do it really.

First you could follow as glportal said above, and basically give your character a "transitioning" flag or something(a bool perhaps) and ignore input during that state as you advance the character by coordinates until they reach the desired space. The problem with that comes to when you get to gameplay topics like if there is tile based combat, if you can hit characters in other tiles then you would have to base hitting them on the bounding box of their sprite.

What I did in a game was I had each character have a current tile position they were logically at, this was used to say, check if the tile was blocked, if you could hit them in that space, etc. When they tried to move between tiles I would set their state to transitioning, and had a constant that was the amount of time it took to move between tiles. I also instantly advanced their "logical" position to the tile they were moving to. Essentially if it took a quarter of a second to move and someone swung at me in the new tile as I was beginning to move, it would hit me. This actually tends to work out better for gameplay in practice since your character will more often than not, be in the new tile by the time you would see the hit anyway, or more into it than the previous one.

As for the transition, if you keep the previous tile position that you were in you can do a simple linear interpolation between the two positions using the current time vs how long the transition takes. Essentially: Check if we can move, move the character and set their state to transitioning, add delta time to an accumulator that acts as a timer, if the time is larger than the time it takes to transition in total, we're there, and can just reset the state to let the character move again. If we aren't there yet then you can divide the current time by the total time to move and get a float value like 0.647 or something. Then you take the tile positions(converted to screen positions) and do a LERP between them. You'll end up getting a draw position roughly where the character should be if it takes that long to transition tiles.

Of course this method works in the opposite way as well, you could keep their logical position at the previous tile and only move the character when the transition finishes, that means attacks and such would affect from where you were moving from rather than to where you were going, but then you have problems like another NPC or something trying to move into the tile you are moving into. You would likely need some way to mark the tile as "blocked" or something, until your character actually gets there.

Assuming you want movement system where the character can only walk a single tile per time (a lot of games from the 16 bit era use this kind of movement, e.g.: shinning force), you will need to have a "state" variable. You set the state variable to the direction you want and in your main loop you update the position smoothly, not accepting other inputs meanwhile.

Something like that:


enum movement_states {
  IDLE, MOVING_UP, MOVING_DOWN, MOVING_LEFT, MOVING_RIGHT;
}

void process_input(input, character, delta){
  if (character.getState() != IDLE){
    update_position(character, delta);
    return;
  }
  if (input.isKeyDown(Input.KEY_A && x != 0){
    character.setState(MOVING_LEFT);
    character.setTargetX(X - Game.TILE_SIZE);
    update_position(character, delta);
  }
  else if ... // same logic for UP, DOWN and RIGHT
}

void update_position (character, delta){
  if (character.getState() == MOVING_LEFT){
    if (character.getX() == character.getTargetX()){
      return;
    }
    newX = character.getX() - (character.getMovespeed() * delta);
    if (newX <= character.getTargetX()){
      newX = charcter.getTargetX();
      character.setState(IDLE);
    }
    charater.setX(newX);
  }
  else if ... // same logic for UP, DOWN and RIGHT
}

Very important notes:

- This code can be made much simpler, but it wouldn't be as straight foward.

- This code assumes that you call process_input every frame.

- This code updates position on the process_input, this will not lead to clear code, it would be better to call update_position in the main loop (adding the necessary restrictions/parameters), but the pseudo-code would get messy.

Hope this helps.

I tried your code and it has the same problem as the on i put above, the sprite can end in the middle of two tiles. If you want to review the adaption of the code you gave me i am gonna leave it here btw it is in Java.


	public void processInput(GameContainer gc, int delta){
		  Input input = gc.getInput();
		  if (!MovingState.equals(State.IDLE)){
			    update_position(delta);
			    return;
		  }
		  if (input.isKeyDown(Input.KEY_A) && X != 0){
				MovingState = State.MOVING_LEFT;
			    TargetX = X - Game.TILESIZE;
			    update_position(delta);
		  }
		  if (input.isKeyDown(Input.KEY_D) && X != Game.MapLimitX){
				MovingState = State.MOVING_RIGHT;
			    TargetX = X + Game.TILESIZE;
			    update_position(delta);
		  }
		  if (input.isKeyDown(Input.KEY_W) && Y != 0){
				MovingState = State.MOVING_UP;
			    TargetY = Y - Game.TILESIZE;
			    update_position(delta);
		  }
		  if (input.isKeyDown(Input.KEY_S) && Y != Game.MapLimitY){
				MovingState = State.MOVING_DOWN;
			    TargetY = Y + Game.TILESIZE;
			    update_position(delta);
		  }
		  System.out.println(MovingState.name());

		}

		void update_position (int delta){
		  if (MovingState.equals(State.MOVING_LEFT)){
		    if (X == TargetX){
		      return;
		    }
		    newX = X - (int) (WALKSPEED * delta);
		    if (newX <= TargetX){
		      newX = TargetX;
		      MovingState = State.IDLE;
		    }
		    X = newX;
		  }else
			  
		  if(MovingState.equals(State.MOVING_RIGHT)){
		    if (X == TargetX){
			      return;
			    }
		    newX = X + (int) (WALKSPEED * delta);
		    if (newX >= TargetX){
		      newX = TargetX;
		      MovingState = State.IDLE;
		    }
		    X = newX;  
		  }else
			  
		  if(MovingState.equals(State.MOVING_UP)){
		    if (Y == TargetY){
			      return;
			    }
		    newY = Y - (int) (WALKSPEED * delta);
		    if (newY <= TargetY){
		      newY = TargetY;
		      MovingState = State.IDLE;
		    }
		    Y = newY;  
		  } else
			  
		  if(MovingState.equals(State.MOVING_DOWN)){
		    if (Y == TargetY){
			      return;
			    }
		    newY = Y + (int) (WALKSPEED * delta);
		    if (newY >= TargetY){
		      newY = TargetY;
		      MovingState = State.IDLE;
		    }
		    Y = newY;  
		  }   

		}

Now i am gonna try Satharis solution because with glportal solution the chracter could end in the middle of a tile, thanks anyway.

This topic is closed to new replies.

Advertisement