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

2023/3/24

How would I make an actor grab another actor and drag them off the screen?

KCee KCee

2023/3/24

#
Im making a simulation and one of the actors in my simulation is a monster that drags villagers off the screen. How would i make the villager follow the monster actor so it looks like its being grabbed? villager code:
import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)

/**
 * Write a description of class Villager here.
 * 
 * @author (your name) 
 * @version (a version number or a date)
 */
public class Villager extends Pedestrian
{
    private GreenfootImage image;
    public Villager(int dir)
    {
        super(dir);
        image = getImage();
        image.scale(image.getWidth() - 340, image.getHeight() - 340);
        setImage(image);
    }
    
    /**
     * Act - do whatever the Villager wants to do. This method is called whenever
     * the 'Act' or 'Run' button gets pressed in the environment.
     */
    public void act()
    {
        move();
    }
    
    public void rooted()
    {
        speed = maxSpeed/2;
        image = new GreenfootImage("rootedvillager.png");
        
        image.scale(image.getWidth() - 340, image.getHeight() - 340);
        setImage(image);
    }
    
    public void frogged()
    {
        speed = maxSpeed/3;
        
        image = new GreenfootImage("frog.png");
        image.scale(image.getWidth() - 65, image.getHeight() - 65);
        setImage(image);
        
    }
    
    public void grabbed()
    {
        
    }
}                       
import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)

/**
 * Write a description of class motorcycle here.
 * 
 * @author (your name) 
 * @version (a version number or a date)
 */
public class Prowler extends Vehicle
{
    /**
     * Act - do whatever the motorcycle wants to do. This method is called whenever
     * the 'Act' or 'Run' button gets pressed in the environment.
     */
    public Prowler(VehicleSpawner origin) {
        super(origin); // call the superclass' constructor
        GreenfootImage image = getImage();
        image.scale(image.getWidth() - 200, image.getHeight() - 200);
        setImage(image);
        maxSpeed = 1.5 + ((Math.random() * 30)/5);
        speed = maxSpeed;
        yOffset = 0;
    }

    public void act()
    {
        drive(); 
        checkHitPedestrian();
        if (checkEdge()){
            getWorld().removeObject(this);
        }
    }
    
    /**
     * When a Car hit's a Pedestrian, it should knock it over
     */
    public boolean checkHitPedestrian () {
        Pedestrian p = (Pedestrian)getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Pedestrian.class);
        
        if (p != null){
            p.grabbed();
            return true;
        }
        return false;
    }
}
danpost danpost

2023/3/24

#
KCee wrote...
Im making a simulation and one of the actors in my simulation is a monster that drags villagers off the screen. How would i make the villager follow the monster actor so it looks like its being grabbed? << Codes Omitted >>
Best would be to have the monster retain a reference to the villager and (have the monster) keep it (the villager) in front of him while moving off the screen. You can set the act order so the the monster acts after the village; that way, any movement by the villager will be of no consequence. In class of monster:
private Actor grabbed;

private void checkHitPedestrian() {
    if (grabbed == null) {
        grabbed = getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Pedestrian.class);
    }
    if (grabbed != null) {
        grabbed.setLocation(getX()+30, getY()); //adjust '30' value as needed
        if (grabbed.isAtEdge()) getWorld().removeObject(grabbed);
    }
}
In world constructor:
setActOrder(Pedestrian.class); // pedestrians act first
KCee KCee

2023/3/24

#
I cant make it "private void checkHitPedestrian()" because the checkHitPedestrian method is in my vehicle superclass as a boolean which my monster class is under. vehicle class code:
import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)

/**
 * This is the superclass for Vehicles.
 * 
 */
public abstract class Vehicle extends SuperSmoothMover
{
    protected double maxSpeed;
    protected double speed;
    protected int direction; // 1 = right, -1 = left
    protected boolean moving;
    protected int yOffset;
    protected VehicleSpawner origin;
    
    protected abstract boolean checkHitPedestrian ();

    public Vehicle (VehicleSpawner origin) {
        this.origin = origin;
        moving = true;
        
        if (origin.facesRightward()){
            direction = 1;
            
        } else {
            direction = -1;
            getImage().mirrorHorizontally();
        }
    }
    
    public void addedToWorld (World w){
        setLocation (origin.getX() - (direction * 100), origin.getY() - yOffset);
    }

    /**
     * A method used by all Vehicles to check if they are at the edge.
     * 
     * Note that this World is set to unbounded (The World's super class is (int, int, int, FALSE) which means
     * that objects should not be stopped from leaving the World. However, this introduces a challenge as there
     * is the potential for objects to disappear off-screen but still be fully acting and thus wasting resources
     * and affecting the simulation even though they are not visible.
     */
    protected boolean checkEdge() {
        if (direction == 1)
        { // if moving right, check 200 pixels to the right (above max X)
            if (getX() > getWorld().getWidth() + 200){
                return true;
            }
        } 
        else 
        { // if moving left, check 200 pixels to the left (negative values)
            if (getX() < -200){
                return true;
            }
        }
        return false;
    }

    /**
     * Method that deals with movement. Speed can be set by individual subclasses in their constructors
     */
    public void drive() 
    {
        // Ahead is a generic vehicle - we don't know what type BUT
        // since every Vehicle "promises" to have a getSpeed() method,
        // we can call that on any vehicle to find out it's speed
        Vehicle ahead = (Vehicle) getOneObjectAtOffset (direction * (int)(speed + getImage().getWidth()/2 + 4), 0, Vehicle.class);
        if (ahead == null)
        {
            speed = maxSpeed;

        } else {
            speed = ahead.getSpeed();
        }
        move (speed * direction);
    }   

    /**
     * An accessor that can be used to get this Vehicle's speed. Used, for example, when a vehicle wants to see
     * if a faster vehicle is ahead in the lane.
     */
    public double getSpeed(){
        return speed;
    }
}
Prowler (monster) class code:
import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)

/**
 * Write a description of class motorcycle here.
 * 
 * @author (your name) 
 * @version (a version number or a date)
 */
public class Prowler extends Vehicle
{
    /**
     * Act - do whatever the motorcycle wants to do. This method is called whenever
     * the 'Act' or 'Run' button gets pressed in the environment.
     */
    private Actor grabbed;
    public Prowler(VehicleSpawner origin) {
        super(origin); // call the superclass' constructor
        GreenfootImage image = getImage();
        image.scale(image.getWidth() - 200, image.getHeight() - 200);
        setImage(image);
        maxSpeed = 1.5 + ((Math.random() * 30)/5);
        speed = maxSpeed;
        yOffset = 0;
    }

    public void act()
    {
        drive(); 
        checkHitPedestrian();
        if (checkEdge()){
            getWorld().removeObject(this);
        }
    }
    
    /**
     * When a Car hit's a Pedestrian, it should knock it over
     */
    public void checkHitPedestrian () {
        if (grabbed == null) {
            grabbed = getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Villager.class);
        }
        if (grabbed != null) {
            grabbed.setLocation(getX()+30, getY()); //adjust '30' value as needed
            if (grabbed.isAtEdge()) getWorld().removeObject(grabbed);
        }
        return false;
    }
}
Spock47 Spock47

2023/3/25

#
Cool idea! I have some small additions to the implementation: 1. I suggest to free-up the grabbed variable after the villager has been removed from the world (line 44). Otherwise, it will be readded (and re-removed) in the next step.
    public void checkHitPedestrian() {
        if (grabbed == null) {
            grabbed = getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Villager.class);
        }
        if (grabbed != null) {
            grabbed.setLocation(getX()+30, getY()); //adjust '30' value as needed
            if (grabbed.isAtEdge()) {
                getWorld().removeObject(grabbed);
                grabbed = null;
            }
        }
        return false;
    }
2. Lines 30 through 32 remove the prowler from the world if it is at the edge. If the prowler reaches the edge before the villager it is carrying, then the prowler will get removed, but the villager stays in the world. Therefore, I suggest to either: A. just remove lines (30 through 32), which means the prowler just stays in the world, only the villager gets removed from the world. or B. remove lines 30 through 32, but add "getWorld().removeObject(this);" at line 45 (within the if; make sure the if has braces {...}). This way, the prowler will be removed from the world together with the villager it had grabbed. 3. Currently, the checkHitPedestrian method seems not to be used in/for the Vehicle, only for the Prawler (or is it called from somewhere else?). Is there a plan to use it for vehicles, too? Otherwise the "protected abstract boolean checkHitPedestrian();" line in Vehicle could be removed and the method could be private in Prawler class. 4. The return value of the checkHitPedestrian method should be repaired (currently it always returns false). Again, there are multiple possibilities depending on how it is meant, e.g.: A. Return true, if a "new" pedestrian is hit:
    public void checkHitPedestrian() {
        boolean result = false;
        if (grabbed == null) {
            grabbed = getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Villager.class);
            result = true;
        }
        if (grabbed != null) {
            grabbed.setLocation(getX()+30, getY()); //adjust '30' value as needed
            if (grabbed.isAtEdge()) {
                getWorld().removeObject(grabbed);
                grabbed = null;
            }
        }
        return result;
    }
or B. Return true as long as a pedestrian is carried:
    public void checkHitPedestrian() {
        if (grabbed == null) {
            grabbed = getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Villager.class);
        }
        if (grabbed != null) {
            grabbed.setLocation(getX()+30, getY()); //adjust '30' value as needed
            if (grabbed.isAtEdge()) {
                getWorld().removeObject(grabbed);
                grabbed = null;
            }
        }
        return grabbed != null;
    }
KCee KCee

2023/3/25

#
Thanks! i also want the monster to turn a random direction after grabbing onto the villager. I tried using "turn (Greenfoot.getRandomNumber(360));"
public boolean checkHitPedestrian () {
        if (grabbed == null) 
        {
            grabbed = getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Villager.class);
        }
        
        if (grabbed != null) {
            grabbed.setLocation(getX()+30, getY());//adjust '30' value as needed
            turn(Greenfoot.getRandomNumber(360));
            if (grabbed.isAtEdge()) {
                getWorld().removeObject(grabbed);
                grabbed = null;
            }
        }
        return grabbed != null;
    }
but it didnt seem to work
Spock47 Spock47

2023/3/25

#
Try moving the turn-call into the first if-block:
public boolean checkHitPedestrian () {
        if (grabbed == null) {
            grabbed = getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Villager.class);
            turn(Greenfoot.getRandomNumber(360));
        }
        
        if (grabbed != null) {
            grabbed.setLocation(getX()+30, getY());//adjust '30' value as needed
            if (grabbed.isAtEdge()) {
                getWorld().removeObject(grabbed);
                grabbed = null;
            }
        }
        return grabbed != null;
    }
KCee KCee

2023/3/26

#
tried it, it doesnt seem to work
Spock47 Spock47

2023/3/26

#
KCee wrote...
tried it, it doesnt seem to work
There is a bug in SuperSmoothMover class: the turn methods do not pass the new rotation value to the super class; change the turn methods accordingly:
    /**
     * Turn a specified number of degrees.
     * 
     * @param angle     the number of degrees to turn.
     */
    @Override
    public void turn(int angle) {
        turn((double)angle);
    }
 
    /**
     * Turn a specified number of degrees with precision.
     * 
     * @param angle     the precise number of degrees to turn
     */
    public void turn(double angle) {
        rotation += angle;
        setRotation(rotation);
    }
To ensure that the prowler only changes direction when it grabs a new(!) villager, call turn in checkHitPedestrian method within an additional if:
    public boolean checkHitPedestrian () {
        if (grabbed == null) {
            grabbed = getOneObjectAtOffset((int)speed + getImage().getWidth() / 2, 0, Villager.class);
            if (grabbed != null) {
                turn(Greenfoot.getRandomNumber(360));
            }
        }
        if (grabbed != null) {
            grabbed.setLocation(getX() + 30, getY()); //adjust '30' value as needed
            if (grabbed.isAtEdge()) {
                getWorld().removeObject(grabbed);
                grabbed = null;
            }
        }
        return grabbed != null;
    }
Spock47 Spock47

2023/3/26

#
Small additional note: The checkEdge method of the Vehicle works under the assumption that all movement happens on the basis of the "direction" system; however the turn/move call works with the "rotation" system. Therefore, it is possible that the checkEdge method will not work as expected with the prowler now moving to an edge that is not necessarily in the direction the vehicleSpawner gave to the prowler. Solution: isAtEdge works with the rotation system like turn/move, so you can probably use isAtEdge instead of checkEdge if any problem arises.
danpost danpost

2023/3/26

#
KCee wrote...
I cant make it "private void checkHitPedestrian()" because the checkHitPedestrian method is in my vehicle superclass as a boolean which my monster class is under. << Codes Omitted >>
Then use this for the method:
public boolean checkHitPedestrian() {
    if (grabbed == null) {
        grabbed = getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Pedestrian.class);
        if (grabbed != null) turn((double)Math.random()*360)
    }
    if (grabbed != null && grabbed.getWorld() != null) {
        grabbed.setLocation(getX(), getY());
        grabbed.setRotation(getRotation();
        grabbed.move(30); // adjust value as needed
        if (grabbed.isAtEdge()) getWorld().removeObject(grabbed);
    }
    return grabbed != null;
}
You need to login to post a reply.