I have a bunch of actors under a superclass called "Vehicle". All of them move from the left side of the screen to the right and then disappear when they hit the edge of the world. There are 6 lanes in the road that these actors travel on. What I want to do is when there are 2 actors in the same lane, and one actor is right behind another actor, I want it to check if the lane to its right is empty, and if so, move into that lane. How would i check if the lane is empty? and how would i make it change lanes?
World code:
Vehicle Superclass code:
Also this is the code used to make the vehicle spawners, returns if a vehicle is currently inside the spawn zone, and if so, prevents vehicles from spawning in that lane until the spawn zone is free. Not sure if this will help:
import greenfoot.*; // (World, Actor, GreenfootImage, Greenfoot and MouseInfo) /** * <h1>The new and vastly improved 2022 Vehicle Simulation Assignment.</h1> * <p> This is the first redo of the 8 year old project. Lanes are now drawn dynamically, allowing for * much greater customization. Pedestrians can now move in two directions. The graphics are better * and the interactions smoother.</p> * <p> The Pedestrians are not as dumb as before (they don't want straight into Vehicles) and the Vehicles * do a somewhat better job detecting Pedestrians.</p> * * Version Notes - Feb 2023 * --> Includes grid <--> lane conversion method * --> Now starts with 1-way, 5 lane setup (easier) */ /** * Music Used * No Sleep for Busy Minds – sombre, melancholic piece, piano and small guitar elements BIIANSU * */ 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, Bus.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(); } 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 == 1){ addObject(new Bus(laneSpawners[lane]), 0, 0); //} else if (vehicleType == 2){ //addObject(new BloodDemon(laneSpawners[lane]), 0, 0); } else if (vehicleType == 2){ addObject(new Prowler(laneSpawners[lane]), 0, 0); } else if (vehicleType == 3){ 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); } } 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; } public static int[] prepareLanes (World world, GreenfootImage target, VehicleSpawner[] spawners, int startY, int heightPerLane, int lanes, int spacing, boolean twoWay, boolean centreSplit, int centreSpacing) { // Declare an array to store the y values as I calculate them int[] lanePositions = new int[lanes]; // Pre-calculate half of the lane height, as this will frequently be used for drawing. // To help make it clear, the heightOffset is the distance from the centre of the lane (it's y position) // to the outer edge of the lane. int heightOffset = heightPerLane / 2; // draw top border target.setColor (BROWN_BORDER); target.fillRect (0, startY, target.getWidth(), spacing); // Main Loop to Calculate Positions and draw lanes for (int i = 0; i < lanes; i++){ // calculate the position for the lane lanePositions[i] = startY + spacing + (i * (heightPerLane+spacing)) + heightOffset ; // draw lane target.setColor(GREEN_STREET); // the lane body target.fillRect (0, lanePositions[i] - heightOffset, target.getWidth(), heightPerLane); // the lane spacing - where the white or yellow lines will get drawn target.fillRect(0, lanePositions[i] + heightOffset, target.getWidth(), spacing); // Place spawners and draw lines depending on whether its 2 way and centre split if (twoWay && centreSplit){ // first half of the lanes go rightward (no option for left-hand drive, sorry UK students .. ?) if ( i < lanes / 2){ spawners[i] = new VehicleSpawner(false, heightPerLane); world.addObject(spawners[i], target.getWidth(), lanePositions[i]); } else { // second half of the lanes go leftward spawners[i] = new VehicleSpawner(true, heightPerLane); world.addObject(spawners[i], 0, lanePositions[i]); } // draw yellow lines if middle if (i == lanes / 2){ target.setColor(BROWN_LINE); target.fillRect(0, lanePositions[i] - heightOffset - spacing, target.getWidth(), spacing); } /*else if (i > 0){ // draw white lines if not first lane for (int j = 0; j < target.getWidth(); j += 120){ target.setColor (Color.WHITE); target.fillRect (j, lanePositions[i] - heightOffset - spacing, 60, spacing); } }*/ } else if (twoWay){ // not center split if ( i % 2 == 0){ spawners[i] = new VehicleSpawner(false, heightPerLane); world.addObject(spawners[i], target.getWidth(), lanePositions[i]); } else { spawners[i] = new VehicleSpawner(true, heightPerLane); world.addObject(spawners[i], 0, lanePositions[i]); } // draw Grey Border if between two "Streets" if (i > 0){ // but not in first position if (i % 2 == 0){ target.setColor(BROWN_BORDER); target.fillRect(0, lanePositions[i] - heightOffset - spacing, target.getWidth(), spacing); } else { // draw dotted lines for (int j = 0; j < target.getWidth(); j += 120){ target.setColor (BROWN_LINE); target.fillRect (j, lanePositions[i] - heightOffset - spacing, 60, spacing); } } } } else { // One way traffic spawners[i] = new VehicleSpawner(true, heightPerLane); world.addObject(spawners[i], 0, lanePositions[i]); /*if (i > 0){ for (int j = 0; j < target.getWidth(); j += 120){ target.setColor (Color.WHITE); target.fillRect (j, lanePositions[i] - heightOffset - spacing, 60, spacing); } }*/ } } // draws bottom border target.setColor (BROWN_BORDER); target.fillRect (0, lanePositions[lanes-1] + heightOffset, target.getWidth(), spacing); return lanePositions; } /** * <p>The prepareLanes method is a static (standalone) method that takes a list of parameters about the desired roadway and then builds it.</p> * * <p><b>Note:</b> So far, Centre-split is the only option, regardless of what values you send for that parameters.</p> * * <p>This method does three things:</p> * <ul> * <li> Determines the Y coordinate for each lane (each lane is centered vertically around the position)</li> * <li> Draws lanes onto the GreenfootImage target that is passed in at the specified / calculated positions. * (Nothing is returned, it just manipulates the object which affects the original).</li> * <li> Places the VehicleSpawners (passed in via the array parameter spawners) into the World (also passed in via parameters).</li> * </ul> * * <p> After this method is run, there is a visual road as well as the objects needed to spawn Vehicles. Examine the table below for an * in-depth description of what the roadway will look like and what each parameter/component represents.</p> * * <pre> * <=== Start Y * |||||||||||||| <=== Top Border * /------------\ * | | * | Y[0] | <=== Lane Position (Y) is the middle of the lane * | | * \------------/ * [##] [##] [##| <== spacing ( where the lane lines or borders are ) * /------------\ * | | * | Y[1] | * | | * \------------/ * |||||||||||||| <== Bottom Border * </pre> * * @param world The World that the VehicleSpawners will be added to * @param target The GreenfootImage that the lanes will be drawn on, usually but not necessarily the background of the World. * @param spawners An array of VehicleSpawner to be added to the World * @param startY The top Y position where lanes (drawing) should start * @param heightPerLane The height of the desired lanes * @param lanes The total number of lanes desired * @param spacing The distance, in pixels, between each lane * @param twoWay Should traffic flow both ways? Leave false for a one-way street (Not Yet Implemented) * @param centreSplit Should the whole road be split in the middle? Or lots of parallel two-way streets? Must also be two-way street (twoWay == true) or else NO EFFECT * */ public static int[] prepareLanes (World world, GreenfootImage target, VehicleSpawner[] spawners, int startY, int heightPerLane, int lanes, int spacing, boolean twoWay, boolean centreSplit){ return prepareLanes (world, target, spawners, startY, heightPerLane, lanes, spacing, twoWay, centreSplit, spacing); } }
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() { boolean bloodMooning = VehicleWorld.isBloodMooning(); // 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(); } if (bloodMooning) { speed = maxSpeed*2; move (speed * direction); } else { speed = maxSpeed; 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; } public void speedUp () { speed = maxSpeed*3; } }
import greenfoot.*; // (World, Actor, GreenfootImage, Greenfoot and MouseInfo) /** * A Spawner object is a place where a Vehicle can spawn. Each spawner is * able to check if there is already a Vehicle in the spot to avoid spawning * multiple Vehicles on top of each other. * * @author (your name) * @version (a version number or a date) */ public class VehicleSpawner extends Actor { public static final Color TRANSPARENT_RED = new Color (255, 0, 0, 128); public static final int DIST_BETWEEN_CARS = 128; private GreenfootImage image; private boolean rightward; private boolean visible; private int height, width; public VehicleSpawner (boolean rightward, int laneHeight) { this.rightward = rightward; this.height = (int)(laneHeight * 0.75); width = DIST_BETWEEN_CARS; // set this to true to see the Spawners - might help with understanding of how this works: visible = false; image = new GreenfootImage (width, height); if(visible){ image.setColor(TRANSPARENT_RED); image.fillRect(0, 0, width-1, height - 1); } setImage(image); } public boolean facesRightward (){ return rightward; } /*public boolean isTouchingVehicleOld () { return this.isTouching (Lurker.class) || this.isTouching (Bus.class) || this.isTouching (Ambulance.class); }*/ public boolean isTouchingVehicle () { return this.isTouching(Vehicle.class); } }