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

2023/3/26

I want my actor to let go of another actor after a certain time frame

1
2
KCee KCee

2023/3/26

#
I have a monster in my simulation that upon contact with a villager, I want it to grab it, stop moving for 2 seconds, and then replace the villagers image with a poison cloud signifying it killed it. In order to do this I want to have the image of the villager object replaced with the poison cloud after the 2-second mark and then have the monster actor let go of the new image of the poison cloud and keep moving. How would I do something like this? This is what i have so far but the monster isnt stopping at all on contact with the villager
import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)

/**
 * The Car subclass
 */
public class Acidmon extends Vehicle
{
    private Actor grabbed;
    int timer = 200;
    public Acidmon(VehicleSpawner origin) {
        super(origin); // call the superclass' constructor
        GreenfootImage image = getImage();
        image.scale(image.getWidth() - 120, image.getHeight() - 350);
        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 () {
        if (grabbed == null) 
        {
            grabbed = getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Villager.class);
        }
        
        if (grabbed != null) {
            grabbed.setLocation(getX()+15, getY());//adjust '30' value as needed
            for (int i = 0; i < timer; i++)
            {
                timer--;
                speed = 0;
            }
            speed = maxSpeed;
            if (grabbed.isAtEdge()) {
                getWorld().removeObject(grabbed);
                grabbed = null;
            }
        }
        return grabbed != null;
    }
}
danpost danpost

2023/3/26

#
KCee wrote...
I have a monster in my simulation that upon contact with a villager, I want it to grab it, stop moving for 2 seconds, and then replace the villagers image with a poison cloud signifying it killed it. In order to do this I want to have the image of the villager object replaced with the poison cloud after the 2-second mark and then have the monster actor let go of the new image of the poison cloud and keep moving. How would I do something like this? This is what i have so far but the monster isnt stopping at all on contact with the villager
I would not change the speed value of the monster; I would just use the value of the timer. Instead of starting the timer at 200, start it at zero:
int timer = 0;// line 9
And, as long as it is zero, the monster can move:
if (timer == 0) drive(); // line 22
The for loop is useless. it will execute in one act step and not have any apparent effect on the monster. Set the timer to 200 when catching a villager. Then the checkHitPedestrian can be:
public boolean checkHitPedestrian()
{
    if (grabbed == null) {
        grabbed = getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Villager.class);
        if (grabbed != null) timer = 200; // starting timer when grabbing pedestrian
    }
    if (grabbed == null || timer == 0) return; // both these conditions mean the same thing;
        // either one could be removed without changing anything;
        // both used for conciseness of code (preventing any possible errors in lines to come)
    if (grabbed.getWorld() != null) grabbed.setLocation(getX()+15, getY()); // again conditioned to avoid any possible error
    if (--timer == 0) { // running timer and checking time expired
        getWorld().addObject(new PoisonCloud(), grabbed.getX(), grabbed.getY());
        getWorld().removeObject(grabbed);
        grabbed = null;
    }
    return grabbed != null; // or:  return timer > 0;
}
envxity envxity

2023/3/26

#
dan could u help me with the reload?
danpost danpost

2023/3/26

#
envxity wrote...
dan could u help me with the reload?
Done.
KCee KCee

2023/3/26

#
danpost wrote...
KCee wrote...
I have a monster in my simulation that upon contact with a villager, I want it to grab it, stop moving for 2 seconds, and then replace the villagers image with a poison cloud signifying it killed it. In order to do this I want to have the image of the villager object replaced with the poison cloud after the 2-second mark and then have the monster actor let go of the new image of the poison cloud and keep moving. How would I do something like this? This is what i have so far but the monster isnt stopping at all on contact with the villager
I would not change the speed value of the monster; I would just use the value of the timer. Instead of starting the timer at 200, start it at zero:
int timer = 0;// line 9
And, as long as it is zero, the monster can move:
if (timer == 0) drive(); // line 22
The for loop is useless. it will execute in one act step and not have any apparent effect on the monster. Set the timer to 200 when catching a villager. Then the checkHitPedestrian can be:
public boolean checkHitPedestrian()
{
    if (grabbed == null) {
        grabbed = getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Villager.class);
        if (grabbed != null) timer = 200; // starting timer when grabbing pedestrian
    }
    if (grabbed == null || timer == 0) return; // both these conditions mean the same thing;
        // either one could be removed without changing anything;
        // both used for conciseness of code (preventing any possible errors in lines to come)
    if (grabbed.getWorld() != null) grabbed.setLocation(getX()+15, getY()); // again conditioned to avoid any possible error
    if (--timer == 0) { // running timer and checking time expired
        getWorld().addObject(new PoisonCloud(), grabbed.getX(), grabbed.getY());
        getWorld().removeObject(grabbed);
        grabbed = null;
    }
    return grabbed != null; // or:  return timer > 0;
}
in line 7 the "return;" gives me an error that says incompatible types: missing return value
danpost danpost

2023/3/26

#
KCee wrote...
in line 7 the "return;" gives me an error that says incompatible types: missing return value
Sorry. Use: "return false;"
KCee KCee

2023/3/26

#
thanks! also some errors are occuring because while the monster is grabbing onto the villager, other monsters can still grab said villagers and drag them away. how would i make it so if the villager is currently being held, no one else can grab it or interfere with it?
danpost danpost

2023/3/26

#
KCee wrote...
thanks! also some errors are occuring because while the monster is grabbing onto the villager, other monsters can still grab said villagers and drag them away. how would i make it so if the villager is currently being held, no one else can grab it or interfere with it?
You may need to use a static List object to track grabbed actors and use it to determine if an actor can be grabbed. You must be diligent to keep the list updated (adding and removing grabbed actors as needed).
Spock47 Spock47

2023/3/26

#
Before showing a solution for your question, I suggest to extract the grabbing part into a new superclass Grabber (so that you don't have to repeat the code for grabbing in each class):
public abstract class Grabber extends Vehicle {
    private Villager grabbed;
    
    public Grabber(final VehicleSpawner origin) {
        super(origin);
    }
 
    public void act() {
        drive();
        checkHitPedestrian();
    }
    
    public boolean checkHitPedestrian() {
        final boolean isNewlyGrabbed = tryGrab();
        if (grabbed != null) {
            actionWithGrabbed(grabbed, isNewlyGrabbed);
        }
        return grabbed != null;
    }
    
    private boolean tryGrab() {
        if (grabbed == null) {
            grabbed = (Villager)getOneObjectAtOffset((int)speed + getImage().getWidth() / 2, 0, Villager.class);
            return grabbed != null;
        }
        return false;
    }
    
    /**
     * Do whatever has to be done with the grabbed villager.
     * 
     * This method is automatically called after checkHitPedestrian has grabbed onto a villager.
     * 
     * @param grabbed the grabbed villager, must not be null.
     * @param isNewlyGrabbed whether the villager has just been grabbed in this act.
     */
    protected abstract void actionWithGrabbed(final Villager grabbed, final boolean isNewlyGrabbed);
    
    public void releaseGrabbed() {
        grabbed = null;
    }
}
Simplifying the prowler to this:
public class Prowler extends Grabber {
    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;
    }
    
    protected void actionWithGrabbed(final Villager grabbed, final boolean isNewlyGrabbed) {
        if (isNewlyGrabbed) {
            turn(Greenfoot.getRandomNumber(360));
        }
        grabbed.setLocation(getX() + 30, getY());//adjust '30' value as needed
        if (grabbed.isAtEdge()) {
            getWorld().removeObject(grabbed);
            releaseGrabbed();
        }
    }
}
Now, for any new grabbing monster, you can just make it a subclass of Grabber and write any necessary action that should be done with the grabbed villager into actionWithGrabbed method.
KCee wrote...
thanks! also some errors are occuring because while the monster is grabbing onto the villager, other monsters can still grab said villagers and drag them away. how would i make it so if the villager is currently being held, no one else can grab it or interfere with it?
You could either have the villager know that it is been grabbed (option A) or the world compute it when needed (option B) or danpost's proposal (option C). Option A: Villager knows whether (s)he is grabbed.
public class Villager extends SuperSmoothMover {
    ...
    private boolean isGrabbed = false;
    
    public void grab() {
        isGrabbed = true;
    }
    
    public void release() {
        isGrabbed = false;
    }
    
    public boolean isGrabbed() {
        return isGrabbed;
    }
    ....    
}
In Grabber, replace tryGrab and releaseGrabbed:
    private boolean tryGrab() {
        if (grabbed == null) {
            for (final Villager villager : getObjectsAtOffset((int)speed + getImage().getWidth() / 2, 0, Villager.class)) {
                if (!villager.isGrabbed()) {
                    grabbed = villager;
                    villager.grab();
                    return true;
                }
            }
        }
        return false;
    }
    
    public void releaseGrabbed() {
        grabbed.release();
        grabbed = null;
    }
or Option B: World computes whether villager is grabbed. In your world class (I will call it MyWorld here, change accordingly if your world class has a different name)
    public boolean isGrabbed(final Villager villager) {
        for (final Grabber grabber : getObjects(Grabber.class)) {
            if (villager.equals(grabber.getGrabbed())) {
                return true;
            }
        }
        return false;
    }
Add method getGrabbed to Grabber and replace tryGrab:
    public Villager getGrabbed() {
        return grabbed;
    }

    private boolean tryGrab() {
        if (grabbed == null) {
            final MyWorld world = (MyWorld)getWorld();
            for (final Villager villager : getObjectsAtOffset((int)speed + getImage().getWidth() / 2, 0, Villager.class)) {
                if (!world.isGrabbed(villager)) {
                    grabbed = villager;
                    return true;
                }
            }
        }
        return false;
    }
KCee KCee

2023/3/26

#
Thanks! I have another issue with the poison cloud where it can kill villagers being grabbed and dragged away by other monsters. How would i have it only kills villagers that arent grabbed by any monsters? Poison cloud code:
import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)

/**
 * Write a description of class PoisonCloud here.
 * 
 * @author (your name) 
 * @version (a version number or a date)
 */
public class PoisonCloud extends Actor
{
    /**
     * Act - do whatever the PoisonCloud wants to do. This method is called whenever
     * the 'Act' or 'Run' button gets pressed in the environment.
     */
    public PoisonCloud ()
    {
        this.getImage().setTransparency(250);
        GreenfootImage image = getImage();
        image.scale(image.getWidth() - 150, image.getHeight() - 150);
        setImage(image);
    }
    
    public void act()
    {
        this.getImage().setTransparency(getImage().getTransparency() - 1);
        if (isTouching(Pedestrian.class))
        {
            removeTouching(Pedestrian.class);
        }
        
        if (this.getImage().getTransparency() < 5)
        {
            getWorld().removeObject(this);
        }
    }
}
Spock47 Spock47

2023/3/26

#
KCee wrote...
Thanks! I have another issue with the poison cloud where it can kill villagers being grabbed and dragged away by other monsters. How would i have it only kills villagers that arent grabbed by any monsters?
I assume that class Villager extends Pedestrian. For any pedestrian that touches the poison cloud, you can check whether it is a villager and if so, ask it whether it is grabbed. E.g. replace lines 26 through 29 by:
        for (final Pedestrian touched : getIntersectingObjects(Pedestrian.class)) {
            final Villager villager = (Villager)touched;
            if (villager != null && villager.isGrabbed()) {
                continue;
            }
            getWorld().removeObject(touched);
        }
(Given that you implemented option A from above. If you implemented option B, replace the if-condition accordingly by calling the isGrabbed(villager) method of your world):
if (((MyWorld)getWorld()).isGrabbed(villager)) {
danpost danpost

2023/3/27

#
Spock47 wrote...
I assume that class Villager extends Pedestrian. For any pedestrian that touches the poison cloud, you can check whether it is a villager and if so, ask it whether it is grabbed. E.g. replace lines 26 through 29 by: << Code Omitted >>
Would not an error occur if the Pedestrian instance was not a Villager instance at line 2?
Spock47 Spock47

2023/3/27

#
danpost wrote...
Spock47 wrote...
Would not an error occur if the Pedestrian instance was not a Villager instance at line 2?
That's right, I forgot the instanceof check there:
        for (final Pedestrian touched : getIntersectingObjects(Pedestrian.class)) {
            if (touched instanceof Villager) {
                final Villager villager = (Villager)touched;
                if (villager.isGrabbed()) {
                    continue;
                }
            }
            getWorld().removeObject(touched);
        }
danpost danpost

2023/3/27

#
Spock47 wrote...
<< Quote Omitted >> That's right, I forgot the instanceof check there This would be more to the point:
for (Object obj : getIntersectingObjects(Villager.class)) {
    Villager villager = (Villager)obj;
    if ( ! villager.isGrabbed()) {
        getWorld().removeObject(villager);
        break;
    }
}
Spock47 Spock47

2023/3/27

#
danpost wrote...
This would be more to the point:
for (Object obj : getIntersectingObjects(Villager.class)) {
    Villager villager = (Villager)obj;
    if ( ! villager.isGrabbed()) {
        getWorld().removeObject(villager);
        break;
    }
}
That would be a possibility, too. But in that case, the poison cloud would only go after villagers, but not other pedestrians.
There are more replies on the next page.
1
2