This site requires JavaScript, please enable it in your browser!
Greenfoot back
shindayoni
shindayoni wrote ...

2022/6/9

How to add a collision to an object?

shindayoni shindayoni

2022/6/9

#
I have trees on the map. How to realize the player's inability to pass through them? I've never actually developed games before, so I don't quite understand how to do it. So, i think that code would be in this block that I have now:
public boolean canMove()
    {
        World myWorld = getWorld();
        int x = getX();
        int y = getY();
        
        // test for outside border
        if (x >= myWorld.getWidth() || y >= myWorld.getHeight()) {
            return false;
        }
        else if (x < 0 || y < 0) {
            return false;
        }
        return true;
    }
Here my move function if this would be helpful:
public void move()
    {
        if (!canMove()) {
            return;
        }
        
        if (Greenfoot.isKeyDown("down")){
            setImage("player.png");
            setLocation(getX(), getY() + speed);
        }
        if (Greenfoot.isKeyDown("up")){
            setImage("player.png");
            setLocation(getX(), getY() - speed);
        }
        if (Greenfoot.isKeyDown("right")){
            setImage("player.png");
            setLocation(getX() + speed, getY());
        }
        if (Greenfoot.isKeyDown("left")){
            setImage("playerLeft.png");
            setLocation(getX() - speed, getY());
        }
        
    }
Help please..
Spock47 Spock47

2022/6/9

#
Greenfoot already provides methods to check whether a collision happened, e.g. isTouching(Tree.class) checks whether the actor collided with a tree. The catch is that in your case you have to know about the collision before the collision happens. But one can use a trick for that by 1. just doing the movement, 2. but then reverting the movement if it caused a collision.
public class Player extends Actor
{    
    @Override
    public void move(final int distance) {
        super.move(distance);
        if (isTouching(Tree.class)) {
            // Collided with a tree, so revert to previous position.
            super.move(-distance);
        }
    }
}
shindayoni shindayoni

2022/6/9

#
Spock47 wrote...
Greenfoot already provides methods to check whether a collision happened, e.g. isTouching(Tree.class) checks whether the actor collided with a tree. The catch is that in your case you have to know about the collision before the collision happens. But one can use a trick for that by 1. just doing the movement, 2. but then reverting the movement if it caused a collision.
public class Player extends Actor
{    
    @Override
    public void move(final int distance) {
        super.move(distance);
        if (isTouching(Tree.class)) {
            // Collided with a tree, so revert to previous position.
            super.move(-distance);
        }
    }
}
Can I make this a boolean method that will return me a value based on which to invert the speed(distance) value? I also noticed that the first time the character enters the tree and then gets stuck in it..
Spock47 Spock47

2022/6/9

#
I see that you used setLocation instead of move to move the actor. In that case, it makes sense to override the setLocation method instead of the move method for the collision check:
public class Player extends Actor
{
    // whether the addedToWorld method has already been called.
    private boolean isAddedToWorld = false;
    
    @Override
    public void addedToWorld(final World world) {
        isAddedToWorld = true;
    }
    
    @Override
    public void setLocation(final int x, final int y) {
        final int oldX = getX();
        final int oldY = getY();
        super.setLocation(x, y);
        // isTouching can only be used after addedToWorld has been called.
        if (isAddedToWorld && isTouching(Tree.class)) {
            // Collided with a tree, so revert to previous position.
            super.setLocation(oldX, oldY);
        }
    }
}
With this your source code should already work. Note (technical background): Here an additional isAddedToWorld boolean is needed, since the isTouching method must not be called before the addedToWorld method has been called the first time. (just checking getWorld() != null is not sufficient).
shindayoni wrote...
Can I make this a boolean method that will return me a value based on which to invert the speed(distance) value?
In principle, this is possible - with a catch. The example code I provided above overrides an existing method. This has two consequences: 1. You can call the normal methods for moving (move, setLocation) and it will work, but 2. it needs to have the same signature (parameters and return type) as the original method. Therefore, the return type cannot be changed to boolean in my example code. However, the basic idea (moving, and reverting when colliding) can also be done without overriding an existing method. You can write your own method for it. This again has two consequences: 1. You have to call this new method everywhere where you want to move the actor (the original setLocation and move method should not be called then, since they won't check the collision), but 2. you can change the signature to whatever you want. Example code for this solution:
public class Player extends Actor
{
    public void act()
    {
        customSetLocation(getX() + 1, getY()); // example call
    }

    // Returns whether the move was executed (return `false` means that position is unchanged)
    public boolean customSetLocation(final int x, final int y) {
        final int oldX = getX();
        final int oldY = getY();
        setLocation(x, y);
        if (isTouching(Tree.class)) {
            // Collided with a tree, so revert to previous position.
            setLocation(oldX, oldY);
            return false; // move was not allowed!
        }
        return true; // move was allowed!
    }
}
shindayoni shindayoni

2022/6/13

#
Spock47 wrote...
I see that you used setLocation instead of move to move the actor. In that case, it makes sense to override the setLocation method instead of the move method for the collision check:
public class Player extends Actor
{
    // whether the addedToWorld method has already been called.
    private boolean isAddedToWorld = false;
    
    @Override
    public void addedToWorld(final World world) {
        isAddedToWorld = true;
    }
    
    @Override
    public void setLocation(final int x, final int y) {
        final int oldX = getX();
        final int oldY = getY();
        super.setLocation(x, y);
        // isTouching can only be used after addedToWorld has been called.
        if (isAddedToWorld && isTouching(Tree.class)) {
            // Collided with a tree, so revert to previous position.
            super.setLocation(oldX, oldY);
        }
    }
}
With this your source code should already work. Note (technical background): Here an additional isAddedToWorld boolean is needed, since the isTouching method must not be called before the addedToWorld method has been called the first time. (just checking getWorld() != null is not sufficient).
shindayoni wrote...
Can I make this a boolean method that will return me a value based on which to invert the speed(distance) value?
In principle, this is possible - with a catch. The example code I provided above overrides an existing method. This has two consequences: 1. You can call the normal methods for moving (move, setLocation) and it will work, but 2. it needs to have the same signature (parameters and return type) as the original method. Therefore, the return type cannot be changed to boolean in my example code. However, the basic idea (moving, and reverting when colliding) can also be done without overriding an existing method. You can write your own method for it. This again has two consequences: 1. You have to call this new method everywhere where you want to move the actor (the original setLocation and move method should not be called then, since they won't check the collision), but 2. you can change the signature to whatever you want. Example code for this solution:
public class Player extends Actor
{
    public void act()
    {
        customSetLocation(getX() + 1, getY()); // example call
    }

    // Returns whether the move was executed (return `false` means that position is unchanged)
    public boolean customSetLocation(final int x, final int y) {
        final int oldX = getX();
        final int oldY = getY();
        setLocation(x, y);
        if (isTouching(Tree.class)) {
            // Collided with a tree, so revert to previous position.
            setLocation(oldX, oldY);
            return false; // move was not allowed!
        }
        return true; // move was allowed!
    }
}
Thank you for reply, I will try to make it work, cuz using isTouching just immediately inverts my movement, I understand that this is due to the location of my trees
danpost danpost

2022/6/13

#
To be more precise on the movement, the following should be considered:. Keep the horizontal movement with its collision checking and the vertical movement with its collision checking separate so that you can make use of the obstacle's location and size as well as the size of the player to relocate the player more precisely. This also help prevent the player from getting "stuck" on an obstacle.
Spock47 Spock47

2022/6/13

#
shindayoni wrote...
Thank you for reply, I will try to make it work, cuz using isTouching just immediately inverts my movement, I understand that this is due to the location of my trees
In the image the knight is already colliding with the tree. So if you try to move, it will correctly identify that there is a collision and revert the movement. In other words: The problem is that the position of the knight was already invalid before the movement. The knight has to start in a position where it does not touch a tree.
Spock47 Spock47

2022/6/14

#
Like said: The solution is that the knight has to start in a free position and not already in the trees. However, here is a version that will work even if the player was stuck before the movement. Note that it is not a very efficient implementation and if there is not enough space for the player in the direction in which the player tries to unstuck, it will just stay at the edge of the world. Example: If the given image actually shows the bottom of the world, then there is not enough space for the Knight below the trees, therefore he cannot be unstuck. You need to make more space for her/him.
public class Player extends Actor
{    
    // Returns whether the move was executed (return `false` indicates that position is unchanged)
    public boolean customSetLocation(final int x, final int y) {
        final int oldX = getX();
        final int oldY = getY();
        setLocation(x, y);
        if (isTouching(Tree.class)) { // Collided with a tree, so revert to previous position.
            unstuck();
            return false;
        }
        return true;
    }

    private void unstuck() {
        final Actor collidingTree = getOneIntersectingObject(Tree.class);
        if (collidingTree != null) {
            final int oldRotation = getRotation();
            turnAwayFrom(collidingTree);
            while (isTouching(Tree.class)) {
                move(1);
                if (isAtEdge()) {
                    break;
                }
            }
            setRotation(oldRotation);
        }
    }
    
    public void turnAwayFrom(final Actor actor) {
        turnTowards(actor.getX(), actor.getY());
        turn(180);
    }
}
For a more efficient unstuck implementation, Math is our friend.
You need to login to post a reply.