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);
}
}
