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

2023/3/27

My Demon class has a NullPointerException when spawning into the world

KCee KCee

2023/3/27

#
In my game I have an actor called Demon who spawns every time a "blood moon" event occurs (which occurs about every 7-10 seconds or so). Upon spawning i attempted to code it so he would move towards the closest villager to eat it, and then retarget to the next closest and so on. However, for some reason when it spawns in it returns a NullPointerException. Blood Demon Code:
import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.util.ArrayList;

/**
 * The Ambulance subclass
 */
public class BloodDemon extends Vehicle
{
    
    private ArrayList <Villager> villagers;
    private Villager targetVillager;
    
    public BloodDemon(VehicleSpawner origin){
        super(origin); // call the superclass' constructor
        GreenfootImage image = getImage();
        image.scale(image.getWidth() - 200, image.getHeight() - 250);
        setImage(image);
        maxSpeed = 1.5 + ((Math.random() * 30)/5);
        speed = maxSpeed;
        yOffset = 0;
        targetClosestVillager();
    }

    /**
     * Act - do whatever the Ambulance wants to do. This method is called whenever
     * the 'Act' or 'Run' button gets pressed in the environment.
     */
    public void act()
    {
        if (targetVillager != null && targetVillager.getWorld() != null)
        {
            checkHitPedestrian();
            moveTowardVillager();
        }
        else
        {
            moveRandomly();
        }
        /*drive();
        checkHitPedestrian();
        if (checkEdge()){
            getWorld().removeObject(this);
        }*/
    }
    
    private void targetClosestVillager ()
    {
        double closestTargetDistance = 0;
        double distanceToActor;
        int numVil;
        // Get a list of all Villagers in the World, cast it to ArrayList
        // for easy management

        numVil = getWorld().getObjects(Villager.class).size();

        // If any villagers are found
        if (numVil > 50) // If lots of villagers are found, search small area
        {
            villagers = (ArrayList)getObjectsInRange(80, Villager.class);
        }
        else if (numVil > 20) // If less villagers are found, search wider radius
        {
            villagers= (ArrayList)getObjectsInRange(180, Villager.class);
        }
        else    // If even fewer villagers are found, search the whole World
            villagers = (ArrayList)getWorld().getObjects(Villager.class);

        if (villagers.size() > 0)
        {
            // set the first one as my target
            targetVillager = villagers.get(0);
            // Use method to get distance to target. This will be used
            // to check if any other targets are closer
            closestTargetDistance = VehicleWorld.getDistance (this, targetVillager);

            // Loop through the objects in the ArrayList to find the closest target
            for (Villager o : villagers)
            {
                // Cast for use in generic method
                //Actor a = (Actor) o;
                // Measure distance from me
                distanceToActor = VehicleWorld.getDistance(this, o);
                // If I find a villager closer than my current target, I will change
                // targets
                if (distanceToActor < closestTargetDistance)
                {
                    targetVillager = o;
                    closestTargetDistance = distanceToActor;
                }
            }
        }
    }
    
    private void moveRandomly ()
    {
        if (Greenfoot.getRandomNumber (100) == 50)
        {
            turn (Greenfoot.getRandomNumber(360));
        }
        else
            drive();
    }
    
    private void moveTowardVillager()
    {
        turnTowards(targetVillager.getX(), targetVillager.getY());
        drive();
    }
    
    public boolean checkHitPedestrian () {
        Pedestrian p = (Pedestrian)getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Pedestrian.class);
        
        if (p != null){
            getWorld().removeObject(p);
            return true;
        }
        return false;
    }
}
World code (The part related to the Blood Moon event is in the last part of the spawn method):
public class VehicleWorld extends World
{
    private GreenfootImage background;

    
    // Color Constants
    public static Color GREY_BORDER = new Color (108, 108, 108);
    public static Color GREY_STREET = new Color (88, 88, 88);
    public static Color YELLOW_LINE = new Color (255, 216, 0);
    public static Color GREEN_STREET = new Color (114, 135, 0);
    public static Color BROWN_LINE = new Color (101, 67, 33);
    public static Color BROWN_BORDER = new Color (101, 67, 33);
    
    

    // Instance variables / Objects
    private boolean twoWayTraffic, splitAtCenter;
    private int laneHeight, laneCount, spaceBetweenLanes;
    private int[] lanePositionsY;
    private VehicleSpawner[] laneSpawners;
    private GreenfootSound worldSound;
    //Static Variables
    private static boolean bloodMooning;

    /**
     * Constructor for objects of class MyWorld.
     * 
     */
    public VehicleWorld()
    {    
        // Create a new world with 600x400 cells with a cell size of 1x1 pixels.
        super(800, 600, 1, false); 

        setPaintOrder (Villager.class, EvilWitch.class, GoodWitch.class, Acidmon.class, BloodDemon.class, Prowler.class);
        
        bloodMooning = false;
        worldSound = new GreenfootSound ("backgroundsound.wav");
        // set up background
        GreenfootImage background = new GreenfootImage("background.png"); 
        background.scale(getWidth(), getHeight());
        
        setBackground(background);

        // Set critical variables
        laneCount = 6;
        laneHeight = 48;
        spaceBetweenLanes = 6;
        splitAtCenter = false;
        twoWayTraffic = false;

        setActOrder(Pedestrian.class);
        // Init lane spawner objects 
        laneSpawners = new VehicleSpawner[laneCount];

        // Prepare lanes method - draws the lanes
        lanePositionsY = prepareLanes (this, background, laneSpawners, 222, laneHeight, laneCount, spaceBetweenLanes, twoWayTraffic, splitAtCenter);

    }

    public void act () {
        spawn();
    }
    
    public void started()
    {
        worldSound.setVolume(80);
        worldSound.play();
    }
    
    public boolean isGrabbed(final Villager villager) 
    {
        for (final Grabber grabber : getObjects(Grabber.class)) {
            if (villager.equals(grabber.getGrabbed())) {
                return true;
            }
        }
        return false;
    }
    
    public static float getDistance (Actor a, Actor b)
    {
        double distance;
        double xLength = a.getX() - b.getX();
        double yLength = a.getY() - b.getY();
        distance = Math.sqrt(Math.pow(xLength, 2) + Math.pow(yLength, 2));
        return (float)distance;
    }
    
    private void spawn () {
        // Chance to spawn a vehicle
        if (Greenfoot.getRandomNumber (60) == 0){
            int lane = Greenfoot.getRandomNumber(laneCount);
            if (!laneSpawners[lane].isTouchingVehicle()){
                int vehicleType = Greenfoot.getRandomNumber(4);
                if (vehicleType == 0){
                    addObject(new Acidmon(laneSpawners[lane]), 0, 0);
                //} else if (vehicleType == 2){
                    //addObject(new BloodDemon(laneSpawners[lane]), 0, 0);
                } else if (vehicleType == 1){
                    addObject(new Prowler(laneSpawners[lane]), 0, 0);
                } else if (vehicleType == 2){
                    addObject(new RootedCorpse(laneSpawners[lane]), 0, 0);
                } 
            }
        }

        // Chance to spawn a Pedestrian
        if (Greenfoot.getRandomNumber (80) == 0){
            int xSpawnLocation = Greenfoot.getRandomNumber (600) + 100; // random between 99 and 699, so not near edges
            boolean spawnAtTop = true;//Greenfoot.getRandomNumber(2) == 0 ? true : false;
            if (spawnAtTop){
                addObject (new Villager (1), xSpawnLocation, 50);
            } else {
                addObject (new Villager (-1), xSpawnLocation, 550);
            }
        }
        else if (Greenfoot.getRandomNumber (160) == 0){
            int xSpawnLocation = Greenfoot.getRandomNumber (600) + 100; // random between 99 and 699, so not near edges
            boolean spawnAtTop = true;//Greenfoot.getRandomNumber(2) == 0 ? true : false;
            if (spawnAtTop){
                addObject (new EvilWitch (1), xSpawnLocation, 50);
            } else {
                addObject (new EvilWitch (-1), xSpawnLocation, 550);
            }
        }
        else if (Greenfoot.getRandomNumber (160) == 0){
            int xSpawnLocation = Greenfoot.getRandomNumber (600) + 100; // random between 99 and 699, so not near edges
            boolean spawnAtTop = true;//Greenfoot.getRandomNumber(2) == 0 ? true : false;
            if (spawnAtTop){
                addObject (new GoodWitch (1), xSpawnLocation, 50);
            } else {
                addObject (new GoodWitch (-1), xSpawnLocation, 550);
            }
        }
        
        //Blood moon event
        if (!bloodMooning && Greenfoot.getRandomNumber(200) == 0){
            int lane = Greenfoot.getRandomNumber(laneCount);
            addObject (new BloodMoon(240), 400, 300);
            addObject(new BloodDemon(laneSpawners[lane]), 0, 0);
            bloodMooning = true;
        }
        if (bloodMooning && getObjects(BloodMoon.class).size() == 0){
            bloodMooning = false;
        }
    }
    
    public static boolean isBloodMooning()
    {
        return bloodMooning;
    }

    /**
     *  Given a lane number (zero-indexed), return the y position
     *  in the centre of the lane. (doesn't factor offset, so 
     *  watch your offset, i.e. with Bus).
     *  
     *  @param lane the lane number (zero-indexed)
     *  @return int the y position of the lane's center, or -1 if invalid
     */
    public int getLaneY (int lane){
        if (lane < lanePositionsY.length){
            return lanePositionsY[lane];
        } 
        return -1;
    }
    
    /**
     * Given a y-position, return the lane number (zero-indexed).
     * Note that the y-position must be valid, and you should 
     * include the offset in your calculations before calling this method.
     * For example, if a Bus is in a lane at y=100, but is offset by -20,
     * it is actually in the lane located at y=80, so you should send
     * 80 to this method, not 100.
     * 
     * @param y - the y position of the lane the Vehicle is in
     * @return int the lane number, zero-indexed
     * 
     */
    public int getLane (int y){
        for (int i = 0; i < lanePositionsY.length; i++){
            if (y == lanePositionsY[i]){
                return i;
            }
        }
        return -1;
    }
}
Blood Moon Event Code:
import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.util.ArrayList;
/**
 * Write a description of class BloodMoon here.
 * 
 * @author (your name) 
 * @version (a version number or a date)
 */
public class BloodMoon extends Effect
{
    private int duration;
    private GreenfootSound eventSound;
    
    public BloodMoon (int duration){
        this.duration = duration;
        eventSound = new GreenfootSound ("BloodMoonSound.wav");
    }
    public void addedToWorld (World w){
        image = drawBlood (w.getWidth(), w.getHeight(), 200);
        setImage(image);
        eventSound.setVolume(80);
        eventSound.play();
        ArrayList<Vehicle> vehicles =  (ArrayList<Vehicle>) w.getObjects(Vehicle.class);
        for (Vehicle v : vehicles){
            v.speedUp();
        }
    }
    
    /**
     * Act - do whatever the Snowstorm wants to do. This method is called whenever
     * the 'Act' or 'Run' button gets pressed in the environment.
     */
    public void act()
    {
        if (duration == 0){
            getWorld().removeObject(this);
        } else if (duration <= 180){
            fade (duration, 180);
        } 
        duration--;
    }

    
    
    /**
     * density should be 1-100. 100 will be almost completely white
     */
    public static GreenfootImage drawBlood (int width, int height, int density){

        Color[] swatch = new Color [64];
        int red = 128;
        int blue = 192;

              
        for (int i = 0; i < swatch.length/2; i++){ 
            swatch[i] = new Color (255, 0, 0);
        }
        for (int i = swatch.length/2; i < swatch.length; i++){ 
            swatch[i] = new Color (255, 0, 0);
        }

        // The temporary image, my canvas for drawing
        GreenfootImage temp = new GreenfootImage (width, height);
        //temp.setColor (Color.BLACK);
        //temp.fill();

        // Run this loop one time per "density"

        for (int i = 0; i < density; i++){
            for (int j = 0; j < 150; j++){ // draw 100 circles
                int randSize;
                // Choose a random colour from my swatch, and set its tranparency randomly
                int randColor = Greenfoot.getRandomNumber(swatch.length);;
                int randTrans = Greenfoot.getRandomNumber(220) + 35; // around half transparent
                temp.setColor (swatch[randColor]);

                //setTransparency(randTrans);
                // random locations for our dot
                int randX = Greenfoot.getRandomNumber (width);
                int randY = Greenfoot.getRandomNumber (height);

                int tempVal = Greenfoot.getRandomNumber(250);
                if (tempVal >= 1){
                    //randSize = 2;
                    temp.drawRect (randX, randY, 0, 0);
                }else{
                    randSize = Greenfoot.getRandomNumber (2) + 2;
                    temp.fillOval (randX, randY, randSize, randSize);
                }
                // silly way to draw a dot..
            }
        }

        return temp;
    }
}
Spock47 Spock47

2023/3/27

#
I assume that the NullPointerException is thrown in BloodDemon, line 54. When the BloodDemon is created, it is not in a world yet. Therefore, if targetClosestVillager is called from within the constructor, getWorld() will be null, resulting in the NullPointerException. The solution is to move the call to targetClosestVillager out of the constructor. You can move it into the act method (line 37) which will also make sure that the demon searches a new target when the previous one is eaten:
    public void act() {
        if (targetVillager != null && targetVillager.getWorld() != null) {
            checkHitPedestrian();
            moveTowardVillager();
        } else  {
            moveRandomly();
            targetClosestVillager();
        }
Don't forget to remove the call from the constructor (line 21).
KCee KCee

2023/3/27

#
Thanks that worked! Theres a small problem now the blood demon isnt removing the villager from the world on contact, instead its just going up to it, then spinning in circles?
Spock47 Spock47

2023/3/27

#
KCee wrote...
Thanks that worked! Theres a small problem now the blood demon isnt removing the villager from the world on contact, instead its just going up to it, then spinning in circles?
Seems to me that "getOneObjectAtOffset((int)speed + getImage().getWidth()/2, 0, Pedestrian.class);" is only working correctly if the demon meets the pedestrian's center exactly? Try with "getOneIntersectingObject(Pedestrian.class);":
    public boolean checkHitPedestrian () {
        Actor p = getOneIntersectingObject(Pedestrian.class);
        if (p != null) {
            getWorld().removeObject(p);
            return true;
        }
        return false;
    }
Additionally, I think it is a good idea to first move and then to hit (to prevent null pointer problems):
    public void act() {
        if (targetVillager != null && targetVillager.getWorld() != null) {
            moveTowardVillager();
            checkHitPedestrian();
        } else {
            targetClosestVillager();
            moveRandomly();
        }
    }
You need to login to post a reply.