I'm working on a pacman clone in java using eclipse and sometimes it appears laggy more specifically the movement of pacman/ghosts is slow, sometimes its fine. Once it has happened while I was running it so it wasn't after I added code and it doesn't seem to be after any specific event in game. I can't find any trigger or produce the lag on purpose
The resource manager shows the same cpu usage(only around 50%)/memory usage . Aswell the FPS seems to be around 200 consistently through lag and during the periods where it is working well.
Does anyone know what this could be?
Is there any information I left out that could be of use?
edit - I am basing movement on a timer is that bad? I will post the movement relevant code below is there a good way of posting the whole code on here?
Timer movement = new Timer(20, new ActionListener()//500 is time in milliseconds between
//calls to actionPerformed as far as I know.
{
public void actionPerformed(ActionEvent arg0)
{
if(movingUp == true)
{
moveUp();
}
else if(movingDown == true)
{
moveDown();
}
else if(movingRight == true)
{
moveRight();
}
else if(movingLeft == true)
{
moveLeft();
}
}
});
public void moveUp()
{
yPos -= 1;
this.rect.y -= 1;
}
public void setDirUp()
{
movingUp = true;
movingDown = false;
movingRight = false;
movingLeft = false;
}
in the main class in public void keyPressed:
if(keyCode == KeyEvent.VK_W)
{
if(pacMan.isUpHittingWall == false)
{
pacMan.setDirUp();
pacMan.isDownHittingWall = false;
pacMan.isRightHittingWall = false;
pacMan.isLeftHittingWall = false;
}
}
edit 2 -Thanks for the help guys. I have the movement using System time now and it seems to have fixed the issue because I implemented it only for pacman at first and the ghosts were still slow. Now there is an issue where moving right and down are much slower than moving left or up The only difference I see is that right and down are both adding and left and up are subtracting. What can I do about this?
The updated code is below.
//updated movement code
public void moveUp(long timePassed)
{
yPos -= vy * timePassed;
this.rect.y -= vy * timePassed;
}
public void moveDown(long timePassed)
{
yPos += vy * timePassed;
this.rect.y += vy * timePassed;
}
public void moveRight(long timePassed)
{
xPos += vx * timePassed;
this.rect.x += vx * timePassed;
}
public void moveLeft(long timePassed)
{
xPos -= vx * timePassed;
this.rect.x -= vx * timePassed;
}
//I passed timePassed through a globalInputObject because my input is handled in public //void keyPressed(KeyEvent e) and I didnt know how else to get timePassed in to the //movement method
//Here is the code in gameLoop()
globalInputObject.isPacManMovingUp(timePassed);
globalInputObject.isPacManMovingDown(timePassed);
globalInputObject.isPacManMovingRight(timePassed);
globalInputObject.isPacManMovingLeft(timePassed);
//This is inside the GlobalInputObject
public void isPacManMovingUp(long timePassed)
{
if(pacMan.movingUp == true)
{
pacMan.moveUp(timePassed);
}
}
public void isPacManMovingDown(long timePassed)
{
if(pacMan.movingDown == true)
{
pacMan.moveDown(timePassed);
}
}
public void isPacManMovingRight(long timePassed)
{
if(pacMan.movingRight == true)
{
pacMan.moveRight(timePassed);
}
}
public void isPacManMovingLeft(long timePassed)
{
if(pacMan.movingLeft == true)
{
pacMan.moveLeft(timePassed);
}
}
Rather than always moving the pacman by a constant distance (1 pixel, it appears) each time the timer runs, you should:
Set the timer to run as fast as possible (e.g. once every millisecond or less). Edit: if you set it too fast, the game may end up actually running slower, you'll have to experiment.
Calculate how much time has passed between each frame using the system clock and move the pacman by an amount proportional to that.
Doing the above will mean that if the system is "laggy," it will simply show fewer frames per second, rather than actually moving everything slower.
As I feared, you're basing the distance moved on the time chunk from the Timer. You shouldn't do this as all timers can be variable and unreliable, especially with small time chunks. Better to base movement on difference in system time. So yes, use a Timer or something to run your "game loop", but know the sprite's position and velocity using doubles, and calculate the distance to move based on velocity vector (math vector not Java Vector) * difference in system time. That way if the timer is delayed by say garbage collection, making the time chunk larger, the distance moved will be correspondingly greater and will look smoother.
You should look into creating a proper "main loop" or "game loop" as some call it. Take a look at the game structure part of this wikipedia article.. Basically those input events are happening\invoked from a separate thread than the main thread and they are directly modifying geometry of in game objects. Instead consider something like this for a main loop:
loop:
process collision detection
process animation (alters geometry of game objects)
process input (more on this later)
any other game specific logic
render screen
your process input could be something like this
if (globalInputObject.movingUp==true) {
hero.y -= 10;
}
if (globalInputObject.movingDown==true) {
hero.y += 10;
}
if (globalInputObject.movingLeft==true) {
hero.x -= 10;
}
if (globalInputObject.movingRight==true) {
hero.x += 10;
}
and your input handler would look something like this:
public void actionPerformed(ActionEvent evt) {
if (evt.button==UP_BUTTON) {
globalInputObject.movingUp=true;
}
if (evt.button==DOWN_BUTTON) {
globalInputObject.movingDown=true;
}
if (evt.button==LEFT_BUTTON) {
globalInputObject.movingLeft=true;
}
if (evt.button==RIGHT_BUTTON) {
globalInputObject.movingRight=true;
}
}
Basically the processing that you're doing in your "extra" threads (input thread) is minimal and therefore doesn't interfere with your main thread. Also, this method has the benefied of easily supporting multiple directions simultaneously (ie: UP+RIGHT = diagonal).
Only super high end games have more than a single thread (if they even need it at all). Dealing with synchronisation in a game is not good for performance.
Related
Im trying to get my movement smoother. At the moment when I press the up key it will go up and then when I press the left key it stops and then turns left. I want to make it so when i press right it will do it straight away and have no delay. How could I with my code
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
import java.awt.Image;
import javax.swing.*;
class MyFrame extends JFrame implements KeyListener {
JLabel label;
MyFrame(){
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addKeyListener(this);
ImageIcon imageIcon = new ImageIcon("C:\\Users\\jacob\\Downloads\\player.png");
Image image = imageIcon.getImage();
Image newimg = image.getScaledInstance(150, 150, java.awt.Image.SCALE_SMOOTH);
imageIcon = new ImageIcon(newimg);
label = new JLabel(imageIcon);
this.add(label);
this.setSize(500, 500);
this.setVisible(true);
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP)
label.setLocation(label.getX(), label.getY()-10);
else if (e.getKeyCode() == KeyEvent.VK_DOWN)
label.setLocation(label.getX(), label.getY()+10);
else if (e.getKeyCode() == KeyEvent.VK_LEFT)
label.setLocation(label.getX()-10, label.getY());
else if (e.getKeyCode() == KeyEvent.VK_RIGHT)
label.setLocation(label.getX()+10, label.getY());
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
}
The main problem here is that different operating systems have different times for key-repeat functionality (how often, and how much to accelerate, repeated key press deliveries).
The way to solve this is to maintain state for which keys are currently down or not, and in some timer thread, perform actions if the state is true or not.
For example, (in very rough Java, this won't compile without fixing I think) you want something like this
// mark that the key is down
public void keyPressed(KeyEvent e) {
keyboardState[e.getKeyCode()] = true;
}
// mark that the key has been released
public void keyReleased(KeyEvent e) {
keyboardState[e.getKeyCode()] = false;
}
// this function should be called at some repeatable interval, fast enough where it looks "smooth"
public void calledAtFixedInterval(int delta) {
// we can query definitively if the key is currently down or up, rather than waiting on some event to be delivered at an arbitrary interval
if (keyboardState[KeyEvent.VK_LEFT] == true) {
// your logic here
}
}
This architecture is roughly how most game engines work - they will handle user input using an event-driven architecture, but just maintain a giant map of which keys are currently down or not - that way it's easy and fast to query if a key is pressed deep within the render loop.
A solution I commonly use these days is to separate the game animation thread from the input events (mouse and keyboard), employing loose coupling to communicate between the two.
The game loop can be driven by a java.util.Timer operating at 60 fps. I use this rate as it is pretty common for monitors to refresh at 60 fps, so there are diminishing returns for going at a faster rate.
Some prefer to use a javax.swing.Timer but IDK if that is such a good idea. The EDT (event dispatch thread) can become something of a bottle neck. (The classic Java game programming book: Killer Game Programming in Java compares the two timers and the util Timer comes out the winner.) Arguably, a ScheduledThreadPoolExecutor is more robust than either, though it requires a bit of a learning curve if you haven't used Executors for multi-threading yet. (I prefer using JavaFX, so I use its AnimationTimer.)
The class holding the game loop should have public methods where the keyboard or mouse events that are triggered can simply set a variable or toggle a boolean (or add a variable to the top of a queue). The public input method for the keyboard can have a bit of extra processing to compare the incoming key event with current key state (e.g., a maintained map of key states as described elsewhere on this thread) and set a field for the game-loop thread to read and act upon.
This sort of architecture for a game program adds a great deal of predictability and control, as well as clarifying the logic of the various layers. It also takes good advantage of the multi-threading capabilities of today's processors.
My guess would be to probably make a velocityX and velocityY.
And also, if you want smooth movements. You need a tick method.
Basic tick method for Java Game Development:
public synchronized void start() {
thread = new Thread(this);
thread.start();
running = true;
}
public synchronized void stop() {
try {
thread.join();
}catch(Exception e){
e.printStackTrace();
}
}
public void run() {
long lastTime = System.nanoTime();
double ns =1000000000 / 60.0;
double delta = 0;
long timer = System.currentTimeMillis();
int ticks = 0;
int frames = 0;
while(running) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while(delta >= 1) {
tick();
ticks++;
delta--;
}
if(running) {
render();
frames++;
}
if(System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println(ticks + " tps" + " | " + frames + " fps");
frame.setTitle(name + " | " + ticks + " tps" + " " + frames + " fps");
frames = 0;
ticks = 0;
//meanings
//ns = nanoseconds
//amountOfTicks = ticks per second
}
}
stop();
}
The start and stop methods are for the Thread.
You can find easy tutorials on how to do the tick method and fully understand it.
When you have your ticks working properly, you can make your x and y += the velX and velY, just like this:
public void tick() {
y += velY;
x += velX;
}
Making the player update the x and y each tick
And the velX and velY need to react to your KeyInputs so they, y'know, have a value, something like this:
if(key == KeyEvent.VK_W) { tempObject.setVelY(-10); keyDown[0] = true; }
if(key == KeyEvent.VK_S) { tempObject.setVelY(10); keyDown[1] =
true; }
if(key == KeyEvent.VK_D) { tempObject.setVelX(10); keyDown[2] =
true; }
if(key == KeyEvent.VK_A) { tempObject.setVelX(-10); keyDown[3] =
true; }
This code, don't copy and paste it. It has different methods because I am basing this code off a game I made a while ago. So methods are different.
And so when you get that, you are basically done.
setVelY and setVelX look something like this:
public void setVelY(int velY){
this.velY = velY;
}
public void setVelY(int velY){
this.velX = velX;
}
So, conclusion.
If you want smoother animations, you need a tick method, so the x and y update every second.
My code will basically serve nothing if you copy and paste it, but it's just the concept of how to make the movements smooth.
So x and y need to update every second. Making a Runnable tick method is your only way.
I really hope this helps you just a little bit.
Good luck!
:D
I am creating a 2D tank game and I want my tank to be able to shoot immediately once the fire button is pressed and then again every half second, while the fire button is held. Currently In my game, my first bullet shoots immediately after the fire button is pressed, then there is a delay (I presume half second) until my tank starts shooting a stream of bullets. I'm wondering why the initial delay after the first bullet works, but the successive ones fail. Below I have included the method which creates bullets which is called every frame in my 144Hz main game loop:
public void addBullets(ArrayList<Animate> animates, ArrayList<Drawable> drawables){
if (this.ShootPressed) {
if( firstShot || (System.currentTimeMillis() - timeSinceLastShot) >= 500) {
Bullet newBullet = this.addBullet();
animates.add(newBullet);
drawables.add(newBullet);
firstShot = false;
timeSinceLastShot = System.currentTimeMillis();
}
}
}
Here are the associated methods in my KeyListener class:
public void keyPressed(KeyEvent key) {
int keyPressed = key.getKeyCode();
if (keyPressed == up) {
this.t1.toggleUpPressed();
}
if (keyPressed == down) {
this.t1.toggleDownPressed();
}
if (keyPressed == left) {
this.t1.toggleLeftPressed();
}
if (keyPressed == right) {
this.t1.toggleRightPressed();
}
if(keyPressed == shoot) {
this.t1.toggleShootPressed();
this.t1.setFirstShot(true);
}
I am including this for additional information even though the bug happens before the key is released:
public void keyReleased(KeyEvent ke) {
int keyReleased = ke.getKeyCode();
if (keyReleased == up) {
this.t1.unToggleUpPressed();
}
if (keyReleased == down) {
this.t1.unToggleDownPressed();
}
if (keyReleased == left) {
this.t1.unToggleLeftPressed();
}
if (keyReleased == right) {
this.t1.unToggleRightPressed();
}
if (keyReleased == shoot) {
this.t1.unToggleShootPressed();
}
}
I suspect it's an issue with key-repeats which will repeatedly call KeyPressed while the key is held. It is usually setup to behave as you describe. Consequently, "firstShot" will be repeatedly set to true and a shot will be fired.
I advise restricting your event code, mainly as it's continuous in nature, to only toggling actions, rather than performing any logic. You can determine whether a shot is first or not in your game loop, with the help of some messages from your events.
However, the firstShot variable is not really necessary at all as the time delta will account for it. Removing it would also prevent firing faster than every 500ms by pressing the fire key rapidly, which you may or may not want.
I'm trying to make a simple action in a Java FX "game" in which an image of a pig 'jumps' upwards every time the spacebar is pressed. Here is the code for the key event handlers and the Animation Timer that I'm using to actually carry out the action.
Key Handlers:
ArrayList<String> in = new ArrayList<String>();
s.setOnKeyPressed(
new EventHandler<KeyEvent>()
{
public void handle(KeyEvent e)
{
String code = e.getCode().toString();
if ( !in.contains(code) ){
in.add( code );
}
}
});
s.setOnKeyReleased(
new EventHandler<KeyEvent>()
{
public void handle(KeyEvent e)
{
String code = e.getCode().toString();
in.remove( code );
}
});
Animation timer:
new AnimationTimer()
{
double q = 200;
public void handle(long currentNanoTime)
{
double t = (currentNanoTime - startNanoTime) / 4000000.0;
if(in.contains("SPACE")){
q -= 20;
}
double y = q + t;
if(y >= 520){
gc.drawImage(background1, 0, 0, 1160, 740);
gc.drawImage(pig, 90, 520, 125, 100);
}else{
gc.drawImage(background1, 0, 0, 1160, 740);
gc.drawImage(pig, 90, y, 125, 100);
}
}
}.start();
So as you can see I'm having the animation timer simply cause the 'pig' to gradually fall down the y-axis, and when the spacebar is pressed, it is given a slight boost upwards.
The problem is that if the spacebar is held down, the pig just flies continuously upwards without stopping. I want this to be prevented so that the spacebar must be repeatedly tapped and not just held down. So I want only one 'jump' per spacebar press. Nothing that I've tried to workaround it has worked. How can I do this?
EDIT: I reworked the answer. The original solution used a counter which prevented the pressed key from having any impact for a certain period of time. Unfortunately, this was not what this question was about. :) The current solution is more straight forward and uses only a simple boolean lock.
Before answering the question, here are some annoying tips: I would suggest to use Map<KeyCode, Boolean> instead of List<String> to store information about what keys are currently pressed. It will simplify your code in terms of readability and give it a performance boost at the same time. Next, creating a dedicated object to store information about the pig (haha!) might be a good idea. Finally, using constants instead of hard coded literal values is a good practice.
Also, note that you don't actually need to store information about whether the spacebar is pressed or not and then refer to it from the timer thread. This would only be necessary if you WANTED the pig to be controlled by HOLDING the spacebar. But since you want it to jump only when the spacebar is pressed, you could tell the pig to switch into "jump" state directly from the handler. Of course, this won't solve your problem, because the onKeyPressed handler is invoked repeatedly when holding a key for a longer period of time. But I thought it was worth mentioning. :)
Now, to answer the question. If you want to quickfix your current solution and ignore all the "good practice" crap, focus only on the jumpLock field of the Pig class. The trick is to keep telling the pig to jump repeatedly as you are currently doing, BUT making sure that the pig will obey only when the jumpLock allows it to do so.
NOTE: The following solution assumes you will update the state of your game using a fixed interval like every 30 milliseconds. But as noted at the end, this solution can be easily modified to use FPS based timer.
The following class contains constants which you may want to change when tweaking your game in the future:
public final class Settings {
private Settings() {
}
public static final double BOOST_VELOCITY = 10.0;
public static final double GRAVITY = 0.3;
}
This class represents the pig. The x and y fields store information about current position of the pig. velocityX and velocityY are vectors containing information about the direction and "speed" of the pig in X and Y axis, respectively. jumpLock is a simple boolean flag which is actually a solution to your problem. Whenever user makes a jump, this lock is set to true. And it will remain so until it will be told to release the lock, which will happen when user releases the spacebar.
public final class Pig {
private double x;
private double y;
private double velocityX;
private double velocityY;
private boolean jumpLock;
public Pig() {
// ...
}
public void timeChanged() {
x += velocityX;
y += velocityY;
velocityY -= Settings.GRAVITY;
}
public void jumpBoost() {
if (!jumpLock) {
velocityY = Settings.BOOST_VELOCITY;
jumpLock = true;
}
}
public void releaseLock() {
jumpLock = false;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
}
Your handlers could look like this. Notice that Map<KeyCode, Boolean> is used to store information about currently pressed keys. It performs better than List<String> in this situation. Also adding the #Override annotation is a good practice even when overriding methods which are abstract:
final Map<KeyCode, Boolean> keyboard = new HashMap<>();
keyboard.put(KeyCode.SPACE, false);
scene.setOnKeyPressed(
new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent e) {
if (e.getCode() == KeyCode.SPACE) {
keyboard.put(e.getCode(), true);
// You could alternately call pig.jumpBoost()
// directly from this handler and not having to
// deal with the 'keyboard' map at all
// as illustrated with by pig.releaseLock()
// in the next handler
}
}
});
scene.setOnKeyReleased(
new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent e) {
if (e.getCode() == KeyCode.SPACE) {
keyboard.put(e.getCode(), false);
pig.releaseLock(); // IMPORTANT!!!
}
}
});
Finally, the following snippet of code must be executed repeatedly. This solution assumes this code will be executed in a fixed interval like every 30 milliseconds. If you are using FPS based timer (meaning there will be irregular interval between executions), you should pass the time which elapsed from the previous update as a parameter to the timeChanged() method, and multiply with it whatever necessary inside that method.
pig.timeChanged();
if (keyboard.get(KeyCode.SPACE)) {
pig.jumpBoost();
}
// Note that pig.releaseLock() could be called in else
// branch here and not in the onKeyReleased handler.
// Choose whatever solution suits you best.
// + draw image of the pig on pig.getX() and pig.getY() coordinates
Hope I got this right. I was almost asleep when writing this post and misunderstood the question at first. But I really need to earn some reputation points to be allowed to comment on an issue which is currently important to me. Haha! :D:D
I'm working on a game in java, based on the Atari game adventure. I got the basic KeyListener part working fine, but then I added another if statement, using another class, to test if if the player was going to hit a wall, and stopping movement if that was the case. The method I used also used if statements, and when I ran the code, it had MAJOR lag. I tried a while loop first, but that made it lag even worse. Anyway to make this not lag so much? It doesn't seem that complex a program to run, and I still have to add yet another if statement to make be able to move into another room, so I have to do something to massively cut down on the lag.
Here is the class:
class Player extends JPanel implements KeyListener{
private char c = 'e';
int x = 400;
int y = 400;
int mapX = 0;
int mapY = 0;
public Player() {
this.setPreferredSize(new Dimension(800, 500));
addKeyListener(this);
}
public void addNotify() {
super.addNotify();
requestFocus();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Environment Layout = new Environment();
Layout.drawRoom(mapX,mapY,g);
g.fillRect(x , y , 20, 20);
}
public void keyPressed(KeyEvent e) { }
public void keyReleased(KeyEvent e) { }
public void keyTyped(KeyEvent e) {
c = e.getKeyChar();
repaint();
Environment Layout = new Environment();
if(Layout.isWall(x,y,c)){}
else{
if (c == 'a'){
x = x - 3;
}
else if (c == 'w'){
y = y - 3;
}
else if (c == 's'){
y = y + 3;
}
else if (c == 'd'){
x = x + 3;
}
}
}
public static void main(String[] s) throws IOException{
JFrame f = new JFrame();
f.getContentPane().add(new Player());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
}
The draw room method I used in this was just to put the background of the room into place.
Here is the isWall method from the Environment class:
public boolean isWall(int moveX, int moveY, char let){
BufferedImage room = null;
try {
room = ImageIO.read(new File(xNum + "," + yNum + ".png"));
}
catch (IOException e) {
}
int[][] walls = convertImage(room);
boolean blocked = false;
if(let == 'w') {
if(walls[moveY-8][moveX] == -3584){blocked = true;}
}
else if(let == 's') {
if(walls[moveY+20][moveX] == -3584){blocked = true;}
}
else if(let == 'a') {
if(walls[moveY][moveX-5] == -3584){blocked = true;}
}
else if(let == 'd') {
if(walls[moveY][moveX+20] == -3584){blocked = true;}
}
return blocked;
}
the convertImage method just converts the image of the room into an int array, for the value of the colors. -3584 is the color of the walls. It's possible this is what's lagging it, but this seemed like the best way for each room to have the walls done automatically.
I also tried a timer, but either I did that wrong, or it just didn't help.
I can give more of my code if that's needed, but help with this would be much appreciated. I'm relatively new to this kind of stuff, so it's likely I'm missing something big. Thanks.
The lag here is almost certainly not from the if statements. Those are really fast. I think the bigger issue is in isWall. Notice that any time you want to check for whether a wall is present, you
Open a file,
read the file contents,
convert the file contents from an image to a grid of pixels, and
read exactly one pixel.
Reading files from disk is extremely slow compared to looking at values in memory. For example, a regular magnetic hard drive works at around 7200 RPM, so the seek time is measured in milliseconds. On the other hand, your processor can do about a billion operations per second, so other operations take nanoseconds. That means that a disk read is roughly a million times slower than other operations, which is almost certainly where you're getting the lag from!
To fix this, consider rewriting your isWall code so that you only read the file and do the conversion once and, having done that, then just look up the part of the image you need. This converts doing tons of (glacially slow) file reads to one single (slow but inevitable) file read followed by tons of fast memory reads.
You appear to be moving your walls further than you are moving your player.
Is it possible that your player object is getting stuck in a wall there by producing "blocked = true" continuously?
Your character gets +- 3 in every direction, however your walls seem inconsistent and range from 8 up to 20 down to 5 left to 20 right.
This is an extension to #templatetypedef's answer.
Instead of loading the image files upon calling the isWall method, you might want to consider caching all of the walls on game start.
So I am thinking;
have a HashMap data structure keyed by <String, Integer>. Where String is your coordinates. E.g. coordinate string = "100,238"
parse all the .png image files in the directories and store the coordinates as key and the value can just be any dummy value like 1 or 2.
Then when isWall() is invoked. Given the X and Y coordinate, build the coordinate string as mentioned in point 1 and check if the key exists. If it does then we know it is a piece of wall else not.
This should drastically reduce the I/O disk contention.
In future, if you would like to extend the solution to incorporate APIs like isTreasureChest() or isMonster(). It can be extended by building a immutable class call "Room" or "Tile" to represent the object. Then modify the HashMap to take in <String, Room>.
Given a greenLightDuration and a yellowLightDuration, I need to switch lights (including red Light) based on duration. This all happens inside a run method for the traffic light. The reason for the run method is because it is used when running the entire simulation (trafficLight is an Agent).
//Run method for agents in Model
public void run(double duration) {
if (disposed)
throw new IllegalStateException();
for (int i=0; i<duration; i++) {
time++;
for (Agent a : agents.toArray(new Agent[0])) {
a.run(time);
}
super.setChanged();
super.notifyObservers();
}
}
In Light I have the run method...
public void run(double runTime){
double check_time = runtime - time;
if(check_time >= yellowLightDuration&& color == Color.RED){
color = Color.GREEN;
time = runtime;
}else if(check_time >= greenLightDuration&& color == Color.GREEN){
color = Color.RED;
time = runtime;
}
...but it was just something silly I did to get the lights switching from red/green and obviously does not work for yellow or is not proportionate to green/yellow light duration(I don't think).
For color I use
Color.RED, Color.YELLOW, Color.GREEN from java.awt.Color.
public void run(double runtime){
if(this.color == Color.GREEN){
if(time%greenLightDuration==0){
color = Color.YELLOW;
}
}
if(this.color == Color.YELLOW){
if(time%yellowLightDuration == 0){
color = Color.RED;
}
}
else
color = Color.GREEN;
}
Tried this but the three colors are blinking furiously. I set green to 200 and yellow to 40.
Since this is a cycle, you want to get the current phase of the cycle using the modulus operator. Something like: double phase = (runTime - startTime) % (greenDuration + yellowDuration + redDuration). You can take the modulus of a floating-point number in Java.
the three colors are blinking furiously.
The first/main cause of this is the simulator/model, and not necessarily the traffic light (though your traffic light my still be a problem). The issue is that your current model for time is just some sketchy for-loop, which will run however fast it runs... and it clearly runs a lot faster than your eyes would like.
What you can do is attempt to control the time in your simulation, using Thread.sleep() to delay the program momentarily. The following version will run an iteration roughly once per millisecond...
//Run method for agents in Model
public void run(double duration) {
if (disposed)
throw new IllegalStateException();
for (int i=0; i<duration; i++) {
time++;
try{ Thread.sleep(1); } //wait 1ms
catch(Exception e){} //just resume after interruption
for (Agent a : agents.toArray(new Agent[0])) {
a.run(time);
}
super.setChanged();
super.notifyObservers();
}
}
Now a duration of 300 for green lights takes roughly 0.3 seconds, which is brisk but not unobservable.