NOOBIE Question... please help! I have a tile-based game I'm building where I'm moving an icon up down left and right. The dimensions of the game is 15 tiles x 15 tiles. When I'm approaching the boundaries, how can I prevent the icon from moving off the "map". I'm using W (up) A (left) S (down) and D (right) for directional input.
What would be a good way to prevent that icon from going off the map once it's reached the maximum point on the X or Y axis? I defined PlayerY and PlayerX as points where the player exists on the map, but this is the code i currently have for movement.
if (choice.equals("w")){
playerY--;
}
else if (choice.equals("s")){
playerY++;
}
else if (choice.equals("d")){
playerX++;
}
else if (choice.equals("a")){
playerX--;
}
Would you put something here where you say something like player is at max value and d inputted, do nothing? But I don't know how you'd say "do nothing"...
if (player Y == 15 && choice.equals("d")){
________;
}
Sorry again for the dumb question... I'm relatively new to Java and still trying to get my bearings
I suggest maintaining some constants for the maximum x and y size. Then, add logic which checks the boundaries before making any move:
final int X_DIM = 15; // assuming the board be represented by an array, then 14 is the
final int Y_DIM = 15; // maximum index
if (choice.equals("w")) {
if (playerY > 0) {
playerY--;
}
else {
System.out.println("Error: Cannot make an off-board move");
}
}
else if (choice.equals("s")) {
if (playerY < Y_DIM - 1) {
playerY++;
}
else {
System.out.println("Error: Cannot make an off-board move");
}
}
else if (choice.equals("d")) {
if (playerX < Y_DIM - 1) {
playerX++;
}
else {
System.out.println("Error: Cannot make an off-board move");
}
}
else if (choice.equals("a")) {
if (playerX > 0) {
playerX--;
}
else {
System.out.println("Error: Cannot make an off-board move");
}
}
Before make an move, you have to check whether the move is valid or not. If the move is valid, move it. If not, don't make the move and optionally show a warning for the player.
boolean isValidMove(choice){
int nextX, nextY;
if (choice.equals("w")){
nextY = playerY-1;
}
else if (choice.equals("s")){
nextY = playerY+1;
}
else if (choice.equals("d")){
nextX = playerX+1;
}
else if (choice.equals("a")){
nextX = playerX-1;
}
//immediately return false if X or Y out of board
if(nextX<0||nextX>=15) return false;
if(nextY<0||nextY>=15) return false;
return true; //return true if nextX and nextY is in the board
}
In main method where run the game:
choice = //user input
if(isValidMove(choice)){
//do the move request and represent board game
} else{
//optionally show a warning to players
}
I suggest starting with a helper method to determine whether or not any given position is on the map or not:
private boolean isOnMap(int x, int y) {
return x >= 0 && x < 15
&& y >= 0 && y < 15;
}
Then, you can use that helper method to decide whether or not the move the user requested is valid, and only make the move if it is. I'd split the logic into two steps to make this easier to work with: first step is to naively calculate the new position that the user wants to move to, and then the second step is to decide if that new position is a valid move or not
// Step 1: calculate new position from requested move
int newX = playerX;
int newY = playerY;
if (choice.equals("w")){
newY--;
}
else if (choice.equals("s")){
newY++;
}
else if (choice.equals("d")){
newX++;
}
else if (choice.equals("a")){
newY--;
}
// Step 2: apply the new position, but only if the move is valid
if (isOnMap(newX, newY)) {
playerX = newX;
playerY = newY;
}
Just check if it is on the last or first tiles or not, like this:
if (choice.equals("w")){
if ( playerY - 1 >= 0 ) { // if the next player move in Y axis is positive, then move
playerY--;
}
}
else if (choice.equals("s")){
if ( playerY + 1 < 15 ) { // if the next player move in Y axis is less than 15 (because you have 15 tiles), then move
playerY++;
}
}
else if (choice.equals("d")){
if ( playerX + 1 < 15 ) { // if the next player move in X axis is less than 15 (because you have 15 tiles), then move
playerX++;
}
}
else if (choice.equals("a")){
if ( playerX - 1 >= 0 ) { // if the next player move in X axis is positive, then move
playerX--;
}
}
Related
Synopsis
Well, I'm making a little top-down JRPG and today I was like 'Yeah, I'm gonna bust out this whole map collision thing!'. I failed.
Problem
So I went on the internet and looked up 'LibGDX Tiled Map Collision Detection' and found a really neat post about Map Objects so I added in a map object layer and did all that biz and came out with this little method to ensure the player can move freely around the map but at the same time can't exit it but each time I've tried it ends up with a horrible result such as the player moving off the screen. The latest error is that the player gets stuck doing a walk animation and can't move anywhere else!
Code
package com.darkbyte.games.tfa.game.entity.entities;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.maps.objects.RectangleMapObject;
import com.badlogic.gdx.math.Rectangle;
import com.darkbyte.games.tfa.game.entity.Entity;
import com.darkbyte.games.tfa.game.entity.SpriteSheet;
import com.darkbyte.games.tfa.game.world.map.MapManager;
import com.darkbyte.games.tfa.render.Batch;
import com.darkbyte.games.tfa.render.Camera;
public class Player extends Entity {
// The constructor for the player class
public Player(String name, SpriteSheet spriteSheet) {
super(name, spriteSheet);
direction = Direction.DOWN;
collisionBox = new Rectangle(x, y, 64, 64);
}
// A flag to see if the player is moving
private boolean isMoving;
// The variable that holds the state time
private float stateTime;
// The player's walking animations
private Animation[] walkAnimations = {
spriteSheet.getAnimation(8, 8, 1 / 16f),
spriteSheet.getAnimation(9, 8, 1 / 16f),
spriteSheet.getAnimation(10, 8, 1 / 16f),
spriteSheet.getAnimation(11, 8, 1 / 16f) };
// The player's static frames
private TextureRegion[] staticFrames = {
spriteSheet.getTexture(8, 0),
spriteSheet.getTexture(9, 0),
spriteSheet.getTexture(10, 0),
spriteSheet.getTexture(11, 0) };
// The render code for the player
#Override
public void render() {
// Makes the camera follow the player
Camera.setCameraPosition(x, y);
Batch.getGameBatch().setProjectionMatrix(Camera.getCamera().combined);
// Updates the state time
stateTime += Gdx.graphics.getDeltaTime();
// Gets the player's direction, if the player's moving, it sets the
// current frame to the frame that would be played at the current moment
// based on the state time
// If the player isn't moving, it sets the current frame to the static
// frame associated to the direction
switch (direction) {
case UP:
if(isMoving) {
currentFrame = walkAnimations[0].getKeyFrame(stateTime, true);
} else
currentFrame = staticFrames[0];
break;
case LEFT:
if(isMoving) {
currentFrame = walkAnimations[1].getKeyFrame(stateTime, true);
} else
currentFrame = staticFrames[1];
break;
case DOWN:
if(isMoving) {
currentFrame = walkAnimations[2].getKeyFrame(stateTime, true);
} else
currentFrame = staticFrames[2];
break;
case RIGHT:
if(isMoving) {
currentFrame = walkAnimations[3].getKeyFrame(stateTime, true);
} else
currentFrame = staticFrames[3];
break;
}
}
// The tick code for the player
#Override
public void tick() {
// The object to represent the bounds of the land on the map
RectangleMapObject land = (RectangleMapObject) MapManager.getCurrentMap().getMap().getLayers().get("collision").getObjects().get("land");
// Checks if the player is within the bounds of the map
if(land.getRectangle().contains(collisionBox)) {
// If the player is moving but the arrow keys aren't pressed, sets isMoving to false
isMoving = (isMoving && (Gdx.input.isKeyPressed(Keys.W) || Gdx.input.isKeyPressed(Keys.UP)
|| Gdx.input.isKeyPressed(Keys.A) || Gdx.input.isKeyPressed(Keys.LEFT)
|| Gdx.input.isKeyPressed(Keys.S) || Gdx.input.isKeyPressed(Keys.DOWN)
|| Gdx.input.isKeyPressed(Keys.D) || Gdx.input.isKeyPressed(Keys.RIGHT)));
// Checks to see if the arrow / WASD keys are pressed and moves the
// player in the correct direction at the speed of 1.5 pixels/tick
// (45/second)
// It also sets the players state to moving and corresponds it's
// direction to the key pressed
// Doesn't move if opposing keys are pressed
if(Gdx.input.isKeyPressed(Keys.W) || Gdx.input.isKeyPressed(Keys.UP)) {
if(!(Gdx.input.isKeyPressed(Keys.S) || Gdx.input.isKeyPressed(Keys.DOWN))) {
direction = Direction.UP;
y += 1.5f;
isMoving = true;
}
}
if(Gdx.input.isKeyPressed(Keys.A) || Gdx.input.isKeyPressed(Keys.LEFT)) {
if(!(Gdx.input.isKeyPressed(Keys.D) || Gdx.input.isKeyPressed(Keys.RIGHT))) {
direction = Direction.LEFT;
isMoving = true;
x -= 1.5f;
}
}
if(Gdx.input.isKeyPressed(Keys.S) || Gdx.input.isKeyPressed(Keys.DOWN)) {
if(!(Gdx.input.isKeyPressed(Keys.W) || Gdx.input.isKeyPressed(Keys.UP))) {
direction = Direction.DOWN;
y -= 1.5f;
isMoving = true;
}
}
if(Gdx.input.isKeyPressed(Keys.D) || Gdx.input.isKeyPressed(Keys.RIGHT)) {
if(!(Gdx.input.isKeyPressed(Keys.A) || Gdx.input.isKeyPressed(Keys.LEFT))) {
direction = Direction.RIGHT;
x += 1.5f;
isMoving = true;
}
}
} else {
if(!isMoving) {
// If the player's just spawned puts the player to the map's spawn point
x = MapManager.getCurrentMap().getPlayerSpawnX();
y = MapManager.getCurrentMap().getPlayerSpawnY();
} else { // If not, it just moves them back till they're no longer out of the map
if(x > (land.getRectangle().getX() + land.getRectangle().getWidth())) x -= 1.5;
if(y > (land.getRectangle().getY() + land.getRectangle().getHeight())) y -= 1.5;
}
}
// Synchronises the collision box with the player's x and y position
collisionBox.x = x;
collisionBox.y = y;
}
// Returns if the player is moving
public boolean isMoving() {
return isMoving;
}
}
Can you guys make it so that when he reaches the border that he stops but he can still keep moving in other directions instead of staying static!
Thanks for reading!
At the moment it sounds you just copy/pasted it and you need to familiarize yourself with it first. If you don't know what it does then you should learn or stop the project imho.
Anyway, from what I can tell it's just a player class that handles the animation frames based on which direction it is moving. Nothing to do with collision detection at all. It does update some kind of collisionBox but functionality for this is handled elsewhere, perhaps in the parent class Entity?
My guess is that this is a tile map and units are restricted to the grid. It's pretty easy to detect if A tile exists or not.
private boolean tileExists(int tileX, int tileY, tile[][] map)
{
return tileX >= 0 && tileY >= 0 &&
tileX < map.length && tileY < map[0].length;
}
Now whenever a entity requests a move you should check if the destination is within the map bounds.
private void moveRequest(int destinationX, int destinationY, Tile[][] map)
{
//Just return if the tile is outside of the map
if (!tileExists(destinationX, destinationY, map) return;
//Same goes for your other checks...
//Return if the tile is not walkable
if (!tileIsWalkable(destinationX, destinationY, map) return;
//Return if the tile is already occupied
if (tileIsOccupied(destinationX, destinationY, otherEntities) return;
//etc..
//Now the move is valid and you can set it's state to moving in that direction.
}
Tile maps are not very hard to understand. I will make an attempt to give you some better insight into tile maps. You have a 2D array where you store your tiles in. Tiles have a width and a height and from that you can make your own tile engine:
//Find out which tiles to draw based on the camera position and viewport size.
int startX = (int)(camera.position.x - camera.viewportWidth / 2) / tileWidth;
int startY = (int)(camera.position.y - camera.viewportHeight / 2) / tileHeight;
int endX = (int)(startX + camera.viewportWidth / tileWidth) + 1;
int endY = (int)(startY + camera.viewportHeight / tileHeight) + 1;
//Loop using this data as boundaries
for (int y = startY; y < endY; y++)
{
for (int x = startX; x < endX; x++)
{
//If out of bounds continue to next tile.
if (!tileExists(x, y, map) continue;
//Now all we need to draw the on screen tiles properly:
//x == tile position x in array
//y == tile position y in array
//World position of this tile:
//worldX = x * tileWidth;
//worldY = y * tileHeight;
//Let's draw:
batch.draw(map[x][y].getTexture, worldX, worldY,
tileWidth, tileHeight)
}
}
There really is no magic involved here at all. Drawing only what is on screen like in the above example is very important for larger maps. Other then that you should draw thing in the back first. You have several options to do this, the easiest but least versatile is just a separate the ground from the objects that can obscure things and draw this later.
Characters, creatures or other entities can just use a world position and be easily converted back to tile position.
tileX = worldX / tileWidth;
tileY = worldY / tileHeight;
So if you want to move something with the world position calculate it's tile position first using the aforementioned method. Then lookup if this tile is valid to move to. Then block that tile for other and move to it.
Assuming that the below method is hooked up to a main method that creates a circle in a window and that I want this circle to move left about 100 pixels and then move right 100 pixels and so on.
I can't figure out the code to do it.
private void moveBall()
{
boolean moveRight = true;
if(moveRight == true)
{
x = x + 1;
}
else
{
x = x - 1;
}
if(x == 300)
{
moveRight = false;
}
}
The reason the ball is continually moving right is because when it hits the if statement to set moveRight to false it resets it back to true at the start of the method. You need to pull moveRight to be a class variable if you want it to work like you think it should.
What about trying it like this?
//set the moveRight variable as a class variable
private boolean moveRight = true;
private void moveBall() {
//move right or left accordingly
x = moveRight ? x + 1 : x - 1;
//if x == 300 we want to move left, else if x == 100 im assuming you want to move right again
if (x == 300) {
moveRight = false;
} else if(x == 100){
moveRight = true;
}
}
I'm making a 2D platformer and just added gravity & jumping.
It works like it should, however, if the space bar is still held down after the player finishes a jump, the player just keeps jumping while in mid-air.
I know I need to check for whether or not the player is actually on the ground, but when I do it always returns as 'false', as described in my jumping method:
// Controls both falling and the jumping action.
// The MapObject list is a collection of every object the player can
// collide with on that map. Currently it only contains a single 'ground' object.
public void fall(ArrayList<MapObject> objects)
{
int distance = 0;
if (jumpTicks > 0)
{
float jumpHeight = jumpSpeed * (jumpTicks / maxJumpTicks);
System.out.println(jumpTicks + "/" + maxJumpTicks + " = " + jumpHeight);
y -= jumpHeight;
jumpTicks--;
}
else
{
for (MapObject obj : objects)
{
if (this.y + this.height >= obj.y)
{
// This cancels falling if the player's feet are on top
// of the ground, but for some reason setting an 'isOnGround'
// boolean to 'true' here and checking for it in the 'jump()'
// method does not work, it's always 'false'.
return;
}
distance = obj.y - (this.y + this.height);
}
if (distance > fallSpeed)
{
y += fallSpeed;
}
else
{
y += distance;
}
}
}
// This doesn't make the player jump, it just adds jump time to the player
// if it's not already jumping.
public void jump()
{
if (jumpTicks > 0)
{
return;
}
this.jumpTicks = maxJumpTicks;
this.jumpSpeed = 10;
}
Before you change his speed, check his y value for the floor
function jump() {
if (jumpTicks > 0) {
return;
}
if (this.y === floorY) {
this.jumpTicks = maxJumpTicks;
this.jumpSpeed = 10;
}
}
In my game, the player navigates a maze. I can't figure out how to do proper collision detection with the walls. It is easy to do collision detection for staying in a certain area:
if (x > rightWallX - playerWidth) x = rightWallX - playerWidth;
if (x < leftWallX) x = leftWallX;
//...
But how would I do collision detection for many walls?
I can do plain collision detection without correction (like if (intersecting) return true;), but I can't correct this correctly. If I just store the old x and y and reset them, then
The object never actually touches the wall
If the object can go up but is blocked to the right, it won't go up, it will just not move.
How is collision detection in a maze done?
The easiest way, once you have solved collision detection, to fix the collision is to move the actor to the closest valid position to where the actor would be were it not for the object it collides with. This assumes no inertia, but it is sufficient for maze-like games or top-down map-crawling games.
If you want to simplify your calculations further, you can limit yourself to detecting if changing the actor's x or y coordinate would be better. If your actor has an axis-aligned rectangular hit-box and all obstacles are axis-aligned rectangular as well (the simplest case), this assumption is indeed correct. However, the results might not be satisfactory in some other cases (potential artifact: speed boost from gliding diagonal walls - not the case in most maze games).
Keep in mind multiple collisions could happen concurrently (pushing against two walls). If there are no sharp angles between two walls that an actor could both intersect (say, if all your obstacles are axis aligned and sufficiently spaced), fixing each collision in turn will suffice - just don't stop after the first collision.
You can use Rectangle.intersects() method:
public Rectangle Player(){
return new Rectangle(PlayerX,PlayerY,PlayerWidth,PlayerHeight);
//we do this for getting players x and y values every tick
}
if(Player().intersects(new Rectangle(0,0,100,50)))//if(player touching wall)
new Rectangle(0,0,100,50) is just an example you can change it.
Ok so i'm currently making a 2D top down view game and I'm not sure how you created your maze. However, in my game my Level is created from a Tile[][] tiles = new Tile[levelWidth][levelHeight]; array. The way i handled collision detection was by checking the surrounding tiles to see if they were solid.
This is my getTile method.
public Tile[][] getTile(int x, int y) {
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
return new VoidTile();
} else {
return tiles[x][y];
}
}
In my Tile.java class i have a isSolid() method which returns whether the tile is solid or not. All of my tiles extend my Tile.java so they inherit this method and I override it in their constructor. As i said previously, I am not sure whether or not you use the same style of level implementation as i do. However, It is good practice to do it this way :)
Personally, I am not a big fan of using the .intersects() and .contains() methods for Sprite collision detection. I mainly use them for buttons and alike.
Ok so,
In my player.java class i have a checkBlockedDirection(int x, int y) method and it looks like this.
public void checkBlockedDirection(int x, int y) {
boolean u = map.getTile(x, y - 1).isSolid();
boolean d = map.getTile(x, y + 1).isSolid();
boolean l = map.getTile(x - 1, y).isSolid();
boolean r = map.getTile(x + 1, y).isSolid();
if (u) {
uBlocked = true;
System.out.println("up tile blocked");
} else {
uBlocked = false;
}
if (d) {
dBlocked = true;
System.out.println("down tile blocked");
} else {
dBlocked = false;
}
if (l) {
lBlocked = true;
System.out.println("left tile blocked");
} else {
lBlocked = false;
}
if (r) {
rBlocked = true;
System.out.println("right tile blocked");
} else {
rBlocked = false;
}
}
Then in my player update method i have this
public void tick() {
float dx = 0;
float dy = 0;
if (input.up.isPressed()) {
direction = 0;
} else if (input.down.isPressed()) {
direction = 2;
} else if (input.left.isPressed()) {
direction = 3;
} else if (input.right.isPressed()) {
direction = 1;
} else {
direction = 4; // standing
}
checkBlockedDirection((int)x, (int)y);
if (input.up.isPressed() && y > 0 && !uBlocked) {
dy += -speed;
} else if (input.down.isPressed() && y < map.getHeight() - 1 && !dBlocked) {
dy += speed;
} else if (input.left.isPressed() && x > 0 && !lBlocked) {
dx += -speed;
} else if (input.right.isPressed() && x < map.getWidth() - 1 && !rBlocked) {
dx += speed;
}
x += dx;
y += dy;
}
Basically it just checks whether or not the blocks up, down, left, or right are solid. If they are solid then it wont move and if they arent solid then you can move in the desired direction.
Not sure if this helps or not but it's just my take on this kind of grid collision detection :)
Hope this helps :)
Enjoy
So basically i'm currently working on a small project where the player will be able to fight stuff through move but the gravity is giving me kind of a problem. So basically, i got this method, checkGravity() which will check if isGravityApplicable() return true (does the entity's bottom collider(a Line2D) collide with one of the foothold?(another line2D))
If isGravityApplicable() returns true, then the gravity should be applied but the problem that i have is that anything above 1 pixel Y is way too quickly and even, moving the character by one pixel is extremely fast. I'm not sure whether i should fix my game loop or what?
public boolean isGravityApplicable() {
for (Line2D line : frame.getMap().getFootholds()) {
/*Does the bottom collider intersect the foothold?*/
/*v THIS CHECK DOESN'T WORK CORRECTLY FOR SOME REASONS v*/
if (!GraphicHelper.getLineCameraRelative(getBottomCollider(), frame.getCam()).intersectsLine(line)) {
return true;
}
/*Above check returned false, if the velocity.y is above 1, then it might
skip the line since it would be skipping 5 pixels for example at once.
this check should resolve that.*/
if (velocity.y > 0) {
Line2D collider = getPositionToVelocityCollider();
if (!collider.intersectsLine(line)) {
position.y = (int) line.getY1(); //Y1 or Y2, same sh*t.
return true;
}
}
}
return false;
}
public void checkGravity() {
if (isGravityApplicable()) {
if (isJumping) { //Player has jumped
velocity.y += 1;
velocity.y = velocity.y > TERMINAL_ACCELERATION ? (int) TERMINAL_ACCELERATION : velocity.y;
if (velocity.y < 0) {
isFalling = true;
}
} else if (isFalling) { //Player is currently falling but not from jumping. Most likely just spawned
velocity.y = 1;
/*Anything higher than 1 is WAY too quickly...*/
//velocity.y += 1;
//velocity.y = velocity.y > TERMINAL_VELOCITY ? (int) TERMINAL_VELOCITY : velocity.y;
} else if (isJumping && isFalling) { //Player has jumped and has reached its highest point, falling back down
velocity.y = 1;
/*Anything higher than 1 is WAY too quickly...*/
//velocity.y += 1;
//velocity.y = velocity.y > TERMINAL_VELOCITY ? (int) TERMINAL_VELOCITY : velocity.y;
} else { //Player hasn't jumped and has not started falling, he most likely just spawned
isFalling = true;
velocity.y = 1;
}
} else {
isFalling = false;
isJumping = false;
}
}
Thanks in advance. Oh and if there is anything i could of done better like the collisions which consist of simple lines all around the entities, please tell me. :) Thanks.
Nevermind guys, for some reason the update/tick method was called twice in the loop for some reasons. I guess i had a brain fart or something.