Gradually speeding a sprite - java

I'm trying to make the sprite speed-up gradually on button press and not to move constant speed only. Also set a max-speed limit. I hope you understand what i mean.
timer = new Timer(5, this);
timer.start();
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D)g;
g2d.drawImage(image, x, y, this); //x,y = position
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
private class TAdapter extends KeyAdapter {
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_LEFT) {
dx = -1;
}
if (key == KeyEvent.VK_RIGHT) {
dx = 1;
}
if (key == KeyEvent.VK_UP) {
dy = -1;
}
if (key == KeyEvent.VK_DOWN) {
dy = 1;
}
}
}
public void actionPerformed(ActionEvent e) {
x += dx;
y += dy;
repaint();
}

There are several things (initially) wrong with your example code...
You are overriding the paint method. It is recommend that you override the paintComponent method instead. If you are overriding the paint method of a top level container, like JFrame, then it is recommended that you don't. Instead, use something like JPanel as the bases for your custom painting...
You are disposing of the Graphics context that is past to you. This is VERY dangerous, as this will prevent anything else from been painted. The Graphics context is a shared resources, everything that needs to be updated during this repaint cycle will using the same Graphics context.
You are using a KeyListener. KeyListener suffers from focus issues. This can easily be remedied through the use of the Key Binding API. Key bindings are also more flexible, as they separate the physical key from the action, allowing you to associate the action with different keys with little effort and/or reuse the underlying action (such as with buttons).
So. For your question. You need to know...
The current speed...
The minimum allowable speed...
The maximum allowable speed...
You will also want to maintain the current position of the object you are altering.
This example doesn't actually move the "player" so much as it moves the background. The background position is altered by the xDelta, which is the speed of change...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestSpeed {
public static void main(String[] args) {
new TestSpeed();
}
public TestSpeed() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage background;
// The current position of the background
private int xPos = 0;
// The speed/delta that the xPos is changed...
private int xDelta = 0;
public TestPane() {
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (xPos < -(getWidth())) {
xPos = 0;
}
xPos -= xDelta;
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "slower");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "faster");
ActionMap am = getActionMap();
am.put("slower", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
setSpeed(-1);
}
});
am.put("faster", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
setSpeed(1);
}
});
}
protected void setSpeed(int delta) {
xDelta += delta;
// Check the change in speed to ensure it's within the appropriate range
if (xDelta < 0) {
xDelta = 0;
} else if (xDelta > 9) {
xDelta = 9;
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void invalidate() {
background = null;
super.invalidate();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int x = xPos;
g.setColor(Color.DARK_GRAY);
while (x < getWidth()) {
g.drawLine(x, 0, x, getHeight());
x += 15;
}
int width = getWidth();
int height = getHeight();
x = (width / 2) - 5;
int y = (height / 2) - 5;
g.setColor(Color.RED);
g.fillOval(x, y, 10, 10);
}
}
}

You need to define and store the values for max speed, actual speed and the speed increment.
The simplest way to define the speed increment, and should try it first, is to define a constant speed increment. Based on the provided code:
int maxspeed = 5;
int speed = 1;
int acceleration = 1;
timer = new Timer(5, this);
timer.start();
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D)g;
g2d.drawImage(image, x, y, this); //x,y = position
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
private class TAdapter extends KeyAdapter {
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_LEFT) {
dx = -acceleration;
}
if (key == KeyEvent.VK_RIGHT) {
dx = acceleration;
}
if (key == KeyEvent.VK_UP) {
dy = -acceleration;
}
if (key == KeyEvent.VK_DOWN) {
dy = acceleration;
}
}
}
public void actionPerformed(ActionEvent e) {
if (speed < maxspeed) {
speed += acceleration;
}
x += dx * speed;
y += dy * speed;
repaint();
}
As I don't really know the context of the problem, or the goal to achieve, I didn't include any way to slow the sprite down again, once maxspeed is hit.
Some interval for speed gains may also be considered. With the above code, the updates to speed would be fast enough that you probably wouldn't notice them.

Related

creating many objects with collision properties. JAVA

I have a simple program with three rectangles: one that can move with the push of the arrow keys, and two that are already moving back and forth on their own.
When the 'player' rectangle and top red collide, the player driven rectangle gets put back to (0,0). When I try to collide the player rectangle with the bottom red rectangle, it does not have those collision properties and I have no idea why.
What am I missing?
import java.awt.*;//needed for graphics
import javax.swing.*;//needed for JFrame window
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class javaapplication23 extends JFrame implements KeyListener, ActionListener {
public static int x = 0;
public static int y = 0;
public static int x2 = 100;
public static int y2 = 100;
public javaapplication23() {//constructor for JPanel
add(new JP());
}//close Jpanel Contructor
public static void main(String[] args) {
javaapplication23 w = new javaapplication23();
w.setTitle("MIKE IS AWESOME");
w.setSize(Toolkit.getDefaultToolkit().getScreenSize());
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
w.setVisible(true);
w.addKeyListener(w);
}
public class JP extends JPanel {//start JPanel CLass
public JP() {
Container c = getContentPane();
c.setBackground(Color.white);//backgraund color can be changed
}
public void paint(Graphics g) {//opens paint method
super.paint(g);
player(g, x, y);
g.setColor(Color.RED);
enemylevel1(g, x2, y2);
Rectangle enemyblocks = new Rectangle(x2, y2, 25, 25);
Rectangle player = new Rectangle(x, y, 25, 25);
enemyblocks.contains(x2, y2);
player.contains(x, y);
if (player.getBounds().intersects(enemyblocks.getBounds())) {
x = 0;
y = 0;
}
pause(1);
repaint();
}//close paint method
}//close JPanel Class
public static void pause(int time) {
try //opens an exception handling statement
{
Thread.sleep(time);
} catch (InterruptedException e) {
} //captures the exception
}
public void actionPerformed(ActionEvent e) {
}
public void keyTyped(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == e.VK_RIGHT) {
x += 20;//global variable controlling right movement
repaint();
}
if (e.getKeyCode() == e.VK_LEFT) {
x -= 20;//global variable controlling left movement
repaint();
}
if (e.getKeyCode() == e.VK_UP) {
y -= 20;//global variable controlling up movement
repaint();
}
if (e.getKeyCode() == e.VK_DOWN) {
y += 20;//global variable controlling down movement
repaint();
}
}
public void player(Graphics g, int x, int y) {
g.fillRect(x, y, 30, 30);
}
public void enemylevel1(Graphics g, int x, int y) {
g.fillRect(x2, y2, 25, 25);
g.fillRect(x2, y2 + 100, 25, 25);
if (x2 < 200 && y2 == 100) {
x2 += 1;
}
if (x2 == 200 && y2 >= 100) {
y2 += 1;
}
if (x2 <= 200 && y2 >= 101) {
x2 -= 1;
}
if (x2 == 100 && y2 <= 101) {
y2 -= 1;
}
pause(10);
repaint();
}
}
Start by having a look at Working with Geometry, this will allow you to reduce much of the code complexity.
Basically, a enemy is just a Rectangle, Graphics2D can paint these without to much of an issue. What you need to do is create an instance which can also update it's position based on your needs
public class Enemy extends Rectangle {
private int xDelta;
public Enemy(int x, int y) {
super(x, y, 20, 20);
if (x == 0) {
xDelta = 1;
} else {
xDelta = -1;
}
}
public void update(Rectangle bounds) {
x += xDelta;
if (x < bounds.x) {
x = bounds.x;
xDelta *= -1;
} else if (x > bounds.x + bounds.width - width) {
x = bounds.x + bounds.width - width;
xDelta *= -1;
}
}
}
So, this creates a single unit of work, which is isolated from everything else and carries it's own logic with it. This makes updating it, painting and generally working with much simpler.
Next, you need to create a List of these
public class Bounce extends JPanel implements KeyListener, ActionListener {
private List<Enemy> enemies;
//...
public Bounce() {
enemies = new ArrayList<>(5);
int y = 100;
for (int index = 0; index < 5; index++) {
int x = (index % 2 == 0) ? 0 : 200;
Enemy enemy = new Enemy(x, y);
enemies.add(enemy);
y += 60;
}
This creates a List of Enemys which are distributed evenly within the container.
Now, we need to paint them....
#Override
protected void paintComponent(Graphics g) {//opens paint method
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g.create();
g2d.setColor(Color.RED);
for (Enemy enemy : enemies) {
g2d.fill(enemy);
}
}//close paint method
nb: General convention suggests that you should override paintComponent when you want to perform custom painting
But they don't move, that kind of sucks. So we need a way to, on a regular bases, update the position of the enemies...
First, we create a simple method which we can call to update the enemies, remember, they are capable of updating themselves, we just need to tell them when
public void updateState() {
Rectangle bounds = new Rectangle(20, 20, 200, 200);
for (Enemy enemy : enemies) {
enemy.update(bounds);
}
}
Remember, the Enemy is self contained, it knows how to update itself based on the constraints you have provided.
And now, we need to call this method on a regular bases...
javax.swing.Timer timer = new javax.swing.Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
updateState();
repaint();
}
});
timer.start();
Okay, this will schedule a callback every 40 milliseconds which will allow us to call the updateState method and repaint the component. This is neat because it won't block the Event Dispatching Thread (making our program look like it's hung) but which notifies us within the context of the EDT, making it safe to update the UI from within - WIN/WIN :)
Take a look at Concurrency in Swing and How to use Swing Timers for more details.
Okay, but that doesn't solve the collision...
The player is also a Rectangle, so why not use the same concept we have with the enemies...
public class Bounce extends JPanel implements KeyListener, ActionListener {
private List<Enemy> enemies;
private Rectangle player;
//...
public Bounce() {
player = new Rectangle(0, 0, 30, 30);
enemies = new ArrayList<>(5);
//...
}
#Override
protected void paintComponent(Graphics g) {//opens paint method
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
drawPlayer(g2d);
g2d.setColor(Color.RED);
for (Enemy enemy : enemies) {
g2d.fill(enemy);
if (player.intersects(enemy)) {
player.x = 0;
player.y = 0;
}
}
}//close paint method
public void drawPlayer(Graphics2D g) {
g.fill(player);
}
Which ends up with something like...
This allows you to add/remove enemies as you want and also change the way in which the enemies move, simply and easily
An my "awesome" test code...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Bounce extends JPanel implements KeyListener, ActionListener {
private List<Enemy> enemies;
private Rectangle player;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new Bounce());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public Bounce() {
player = new Rectangle(0, 0, 30, 30);
enemies = new ArrayList<>(5);
int y = 100;
for (int index = 0; index < 5; index++) {
int x = (index % 2 == 0) ? 0 : 200;
Enemy enemy = new Enemy(x, y);
enemies.add(enemy);
y += 60;
}
setBackground(Color.white);//backgraund color can be changed
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
updateState();
repaint();
}
});
timer.start();
setFocusable(true);
requestFocusInWindow();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
requestFocusInWindow();
}
});
addKeyListener(this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(240, 400);
}
#Override
protected void paintComponent(Graphics g) {//opens paint method
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
drawPlayer(g2d);
g2d.setColor(Color.RED);
for (Enemy enemy : enemies) {
g2d.fill(enemy);
if (player.intersects(enemy)) {
player.x = 0;
player.y = 0;
}
}
}//close paint method
public void actionPerformed(ActionEvent e) {
}
public void keyTyped(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == e.VK_RIGHT) {
player.x += 20;//global variable controlling right movement
}
if (e.getKeyCode() == e.VK_LEFT) {
player.x -= 20;//global variable controlling left movement
}
if (e.getKeyCode() == e.VK_UP) {
player.y -= 20;//global variable controlling up movement
}
if (e.getKeyCode() == e.VK_DOWN) {
player.y += 20;//global variable controlling down movement
}
}
public void drawPlayer(Graphics2D g) {
g.fill(player);
}
public void updateState() {
Rectangle bounds = new Rectangle(20, 20, 200, 200);
for (Enemy enemy : enemies) {
enemy.update(bounds);
}
}
public class Enemy extends Rectangle {
private int xDelta;
public Enemy(int x, int y) {
super(x, y, 20, 20);
if (x == 0) {
xDelta = 1;
} else {
xDelta = -1;
}
}
public void update(Rectangle bounds) {
x += xDelta;
if (x < bounds.x) {
x = bounds.x;
xDelta *= -1;
} else if (x > bounds.x + bounds.width - width) {
x = bounds.x + bounds.width - width;
xDelta *= -1;
}
}
}
}

How to move graphics based on input from arrow keys? [duplicate]

I'm trying to make a program in java that involves making an object move constantly from a single key press. Think Pacman, where you press up once and Pacman continues to go up until you press another key. I want to keep the code simple if possible. My original movement (one keypress = one movement) is like this:
public class AL extends KeyAdapter {
public void keyPressed(KeyEvent e){
int keyCode = e.getKeyCode();
if(keyCode == e.VK_A){
x -= 5;
}
if(keyCode == e.VK_D){
x += 5;
}
if(keyCode == e.VK_W){
y -= 5;
}
if(keyCode == e.VK_S){
y += 5;
}
}
The x and y in values are the position of an oval. This works perfectly, but I want it to keep moving after I press the key only once, instead of having to hold it to keep the movement going. I tried a while loop with a boolean parameter that moves while true and doesn't while false, but as soon as I activate the loop, it freezes the program. Here's an example of that bit of code:
public void keyPressed(KeyEvent e){
int keyCode = e.getKeyCode();
if(keyCode == e.VK_LEFT && moveL==false){
moveL=true;
moveR=false;
moveU=false;
moveD=false;
while(moveL){
x--;
}
}
Please help me figure this out, I've been trying and looking around for days now. I appreciate any help you guys can give. Thanks.
The basic concept revolves around this idea of a "delta" or "change" value. This value is then applied to the state you want to change by either incrementing or decrementing the state value by it.
Because of the nature of Swing, you can't block the Event Dispatching Thread, otherwise you end up preventing from processing incoming events (such as paint and key events).
Equally, you should never try and update any UI component (or state variable that might effect the UI) from any thread other then the EDT.
While there are tricks you can apply to facilitate these requirements, the simplest is to use a javax.swing.Timer, which triggers a actionPerformed event on a regular bases within the EDT.
When this occurs you "update" all the elements by the prescribed amount and repaint the screen.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class PacManTest {
public static void main(String[] args) {
new PacManTest();
}
public PacManTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new MazePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class PacMan {
private int x;
private int y;
private int deltaX;
private int deltaY;
private BufferedImage sprite;
public PacMan() {
try {
sprite = ImageIO.read(new File("PacMan.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void move(int x, int y) {
deltaX = x;
deltaY = y;
}
public void update(MazePane pane) {
x += deltaX;
y += deltaY;
if (x + sprite.getWidth() > pane.getWidth()) {
x = pane.getWidth() - sprite.getWidth();
} else if (x < 0) {
x = 0;
}
if (y + sprite.getHeight() > pane.getHeight()) {
y = pane.getHeight() - sprite.getHeight();
} else if (y < 0) {
y = 0;
}
}
public void paint(MazePane pane, Graphics2D g2d) {
Graphics2D g = (Graphics2D) g2d.create();
float angle = 0;
if (deltaX != 0) {
angle = deltaX > 0 ? 0 : 180;
} else if (deltaY != 0) {
angle = deltaY > 0 ? 90 : 270;
}
AffineTransform t = new AffineTransform();
t.translate(x, y);
t.rotate(Math.toRadians(angle), sprite.getWidth() / 2, sprite.getHeight() / 2);
g.setTransform(t);
g.drawImage(sprite, 0, 0, pane);
g.dispose();
}
}
public class MazePane extends JPanel {
private PacMan pacMan;
public MazePane() {
pacMan = new PacMan();
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
pacMan.update(MazePane.this);
repaint();
}
});
timer.start();
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "up");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "down");
am.put("left", new MoveAction(pacMan, -4, 0));
am.put("right", new MoveAction(pacMan, 4, 0));
am.put("up", new MoveAction(pacMan, 0, -4));
am.put("down", new MoveAction(pacMan, 0, 4));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
pacMan.paint(this, g2d);
g2d.dispose();
}
public class MoveAction extends AbstractAction {
private int deltaX;
private int deltaY;
private PacMan pacMan;
public MoveAction(PacMan pacMan, int deltaX, int deltaY) {
this.deltaX = deltaX;
this.deltaY = deltaY;
this.pacMan = pacMan;
}
#Override
public void actionPerformed(ActionEvent e) {
pacMan.move(deltaX, deltaY);
}
}
}
}
I would also recommend that you take the time to learn about Key Bindings, KeyListener suffer from focus issues, which key bindings are capable of addressing...
You need to process the move in a separate thread. I.e.:
public class Pacman implements Runnable
{
public void run(){
//moving code, i.e. in a while loop
//every move will notify the EDT:
SwingUtilities.invokeLater(new Runnable(){
public void run(){
//update the Swing here - i.e. move Pacman
}
}
}
public void startMoving(){
new Thread(this).start();
}
//more methods to set speed, direction, etc...
}
Then you keep a reference to an instance of Pacman class in your Gui class and respond to various key presses by changing pacman's parameters:
public void keyPressed(KeyEvent e){
int keyCode = e.getKeyCode();
if(keyCode == e.VK_LEFT){
pacman.newDirection(LEFT); //for exmaple, enum with direction LEFT, RIGHT, UP, DOWN...
}
//etc... more logic
}

repaint() paints slower than paintComponent()?

I am painting vehicle objects that I defined using the paintComponent().
Because the vehicles can move, I implement ActionListener and set a Timer() to trigger.
As a result, my vehicles can move. But it is kind of "shaking". When I keep resizing the window to call the paintComponent(), the movement becomes smooth. When I do not resize the window (not calling paintComponent), it gets skaking again. Why? How to fix it?
public class VehiclesComponent extends JComponent implements ActionListener{
private Vehicle[] vehicles;
private Timer timer;
public VehiclesComponent(int n){
vehicles = Vehicle.generateVehicle(n);
timer = new Timer(5,this);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
for (int i=0; i<vehicles.length; i++) {
vehicles[i].draw(g2);
}
// may change later
timer.start();
}
#Override
public void actionPerformed(ActionEvent e){
//check collision in here
for (Vehicle v : vehicles) {
if (Vehicle.intersectsOther(v, vehicles)) {
v.collisionSideEffect();
}
}
//move all in here
for (Vehicle v : vehicles ) {
v.move();
}
repaint();
//?? repaint slower than paintComponent
}
}
Start by taking a look at Painting in AWT and Swing. Remember, repaint is only a suggest made to the RepaintManager, the RepaintManager may choose to consolidate multiple repaint calls into a smaller number of actual paint events.
Make sure you are calling super.paintComponent, otherwise you will end up with no end of strange paint artifacts.
Don't, directly or indirectly, modify the state of the component or ant other components from within any paint method, this will result in a new repaint request been made, which could lead to a cycle of paint events which could consume your CPU cycles. This means, don't call timer.start()!
Without a runable example to go by, I hobbled this together. Now this is animating 10, 000 individual Vehicles (rectangles), so it's massively over kill, but it should provide the point...
(the gif is only running at 7fps, not your 200fps)
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new VehiclesComponent(10000));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class VehiclesComponent extends JComponent implements ActionListener {
private Vehicle[] vehicles;
private Timer timer;
public VehiclesComponent(int n) {
vehicles = Vehicle.generateVehicle(n, getPreferredSize());
timer = new Timer(5, this);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
for (int i = 0; i < vehicles.length; i++) {
vehicles[i].draw(g2);
}
}
#Override
public void actionPerformed(ActionEvent e) {
//check collision in here
// for (Vehicle v : vehicles) {
// if (Vehicle.intersectsOther(v, vehicles)) {
// v.collisionSideEffect();
// }
// }
//move all in here
for (Vehicle v : vehicles) {
v.move(this.getSize());
}
repaint();
//?? repaint slower than paintComponent
}
}
public static class Vehicle {
protected static final int SIZE = 5;
protected static final Color[] COLORS = new Color[]{
Color.BLACK,
Color.BLUE,
Color.CYAN,
Color.DARK_GRAY,
Color.GREEN,
Color.MAGENTA,
Color.ORANGE,
Color.PINK,
Color.RED,
Color.WHITE,
Color.YELLOW
};
private int x = 0;
private int y = 0;
private int xDelta;
private int yDelta;
private Shape car;
private Color color;
public static Vehicle[] generateVehicle(int count, Dimension bounds) {
Vehicle[] vehicles = new Vehicle[count];
for (int index = 0; index < vehicles.length; index++) {
vehicles[index] = new Vehicle(bounds);
}
return vehicles;
}
public Vehicle(Dimension size) {
x = (int)(Math.random() * (size.width - SIZE));
y = (int)(Math.random() * (size.height - SIZE));
xDelta = (int)(Math.random() * 3) + 1;
yDelta = (int)(Math.random() * 3) + 1;
car = new Rectangle(SIZE, SIZE);
color = COLORS[(int)(Math.random() * COLORS.length)];
}
public void move(Dimension size) {
x += xDelta;
y += yDelta;
if (x < 0) {
x = 0;
xDelta *= -1;
} else if (x + SIZE > size.width) {
x = size.width - SIZE;
xDelta *= -1;
}
if (y < 0) {
y = 0;
yDelta *= -1;
} else if (y + SIZE > size.height) {
y = size.height - SIZE;
yDelta *= -1;
}
}
public void draw(Graphics2D g2) {
g2.translate(x, y);
g2.setColor(color);
g2.fill(car);
g2.translate(-x, -y);
}
}
}
You could also take a look at this example which renders upwards of 4500 images in random directions and demonstrates some optimisation techniques.
You can also take a look at this example which is capable of animating both in direction and rotation, upwards of 10, 000 images

JApplet creates a ball that bounces and gets progressively less high in Java

public class Circle extends JApplet {
public void paint(Graphics g) {
int x=100;
int y=100;
int diameter=50;
int xResize=500;
int yResize=500;
super.paint(g);
resize(xResize,yResize);
g.drawOval(x, y, diameter, diameter);
}
}
So I am trying to create a ball that bounces up and down and progressively gets smaller. I need to use the following code as a class that will set up my next class that will actually carry out the action. I know that I need to set up the current code that I have into constructors, instance variables and methods to create objects from but I can't seem to figure out how to do that.
Also how would I make the drawn image move up and down across the JApplet?
This is a basic example of the idea
Basically, when you press the spacebar, it generates a vertical movement which slows over time till it runs out of energy, it will then fall and rebound until it has run out of "bounce"
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class JumpingSprite {
public static void main(String[] args) {
new JumpingSprite();
}
public JumpingSprite() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
protected static final int SPRITE_HEIGHT = 10;
protected static final int SPRITE_WIDTH = 10;
private float vDelta; // The vertical detla...
private float rbDelta; // Rebound delta...
private float rbDegDelta; // The amount the rebound is degregated...
private int yPos; // The vertical position...
private float gDelta; // Gravity, how much the vDelta will be reduced by over time...
private Timer engine;
private boolean bounce = false;
public TestPane() {
yPos = getPreferredSize().height - SPRITE_HEIGHT;
vDelta = 0;
gDelta = 0.25f;
// This is how much the re-bound will degrade on each cycle...
rbDegDelta = 2.5f;
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "jump");
am.put("jump", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// Can only bound when we're actually on the ground...
// You might want to add fudge factor here so that the
// sprite can be within a given number of pixels in order to
// jump again...
if (yPos + SPRITE_HEIGHT == getHeight()) {
vDelta = -8;
rbDelta = vDelta;
bounce = true;
}
}
});
engine = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int height = getHeight();
// No point if we've not been sized...
if (height > 0) {
// Are we bouncing...
if (bounce) {
// Add the vDelta to the yPos
// vDelta may be postive or negative, allowing
// for both up and down movement...
yPos += vDelta;
// Add the gravity to the vDelta, this will slow down
// the upward movement and speed up the downward movement...
// You may wish to place a max speed to this
vDelta += gDelta;
// If the sprite is not on the ground...
if (yPos + SPRITE_HEIGHT >= height) {
// Seat the sprite on the ground
yPos = height - SPRITE_HEIGHT;
// If the re-bound delta is 0 or more then we've stopped
// bouncing...
if (rbDelta >= 0) {
// Stop bouncing...
bounce = false;
} else {
// Add the re-bound degregation delta to the re-bound delta
rbDelta += rbDegDelta;
// Set the vDelta...
vDelta = rbDelta;
}
}
}
}
repaint();
}
});
engine.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth() - 1;
int xPos = (width - SPRITE_WIDTH) / 2;
g2d.drawOval(xPos, yPos, SPRITE_WIDTH, SPRITE_HEIGHT);
g2d.dispose();
}
}
}
The example introduces concepts of Key Bindings, Swing Timer and basic custom painting

Continuous movement with a single key press?

I'm trying to make a program in java that involves making an object move constantly from a single key press. Think Pacman, where you press up once and Pacman continues to go up until you press another key. I want to keep the code simple if possible. My original movement (one keypress = one movement) is like this:
public class AL extends KeyAdapter {
public void keyPressed(KeyEvent e){
int keyCode = e.getKeyCode();
if(keyCode == e.VK_A){
x -= 5;
}
if(keyCode == e.VK_D){
x += 5;
}
if(keyCode == e.VK_W){
y -= 5;
}
if(keyCode == e.VK_S){
y += 5;
}
}
The x and y in values are the position of an oval. This works perfectly, but I want it to keep moving after I press the key only once, instead of having to hold it to keep the movement going. I tried a while loop with a boolean parameter that moves while true and doesn't while false, but as soon as I activate the loop, it freezes the program. Here's an example of that bit of code:
public void keyPressed(KeyEvent e){
int keyCode = e.getKeyCode();
if(keyCode == e.VK_LEFT && moveL==false){
moveL=true;
moveR=false;
moveU=false;
moveD=false;
while(moveL){
x--;
}
}
Please help me figure this out, I've been trying and looking around for days now. I appreciate any help you guys can give. Thanks.
The basic concept revolves around this idea of a "delta" or "change" value. This value is then applied to the state you want to change by either incrementing or decrementing the state value by it.
Because of the nature of Swing, you can't block the Event Dispatching Thread, otherwise you end up preventing from processing incoming events (such as paint and key events).
Equally, you should never try and update any UI component (or state variable that might effect the UI) from any thread other then the EDT.
While there are tricks you can apply to facilitate these requirements, the simplest is to use a javax.swing.Timer, which triggers a actionPerformed event on a regular bases within the EDT.
When this occurs you "update" all the elements by the prescribed amount and repaint the screen.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class PacManTest {
public static void main(String[] args) {
new PacManTest();
}
public PacManTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new MazePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class PacMan {
private int x;
private int y;
private int deltaX;
private int deltaY;
private BufferedImage sprite;
public PacMan() {
try {
sprite = ImageIO.read(new File("PacMan.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void move(int x, int y) {
deltaX = x;
deltaY = y;
}
public void update(MazePane pane) {
x += deltaX;
y += deltaY;
if (x + sprite.getWidth() > pane.getWidth()) {
x = pane.getWidth() - sprite.getWidth();
} else if (x < 0) {
x = 0;
}
if (y + sprite.getHeight() > pane.getHeight()) {
y = pane.getHeight() - sprite.getHeight();
} else if (y < 0) {
y = 0;
}
}
public void paint(MazePane pane, Graphics2D g2d) {
Graphics2D g = (Graphics2D) g2d.create();
float angle = 0;
if (deltaX != 0) {
angle = deltaX > 0 ? 0 : 180;
} else if (deltaY != 0) {
angle = deltaY > 0 ? 90 : 270;
}
AffineTransform t = new AffineTransform();
t.translate(x, y);
t.rotate(Math.toRadians(angle), sprite.getWidth() / 2, sprite.getHeight() / 2);
g.setTransform(t);
g.drawImage(sprite, 0, 0, pane);
g.dispose();
}
}
public class MazePane extends JPanel {
private PacMan pacMan;
public MazePane() {
pacMan = new PacMan();
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
pacMan.update(MazePane.this);
repaint();
}
});
timer.start();
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "up");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "down");
am.put("left", new MoveAction(pacMan, -4, 0));
am.put("right", new MoveAction(pacMan, 4, 0));
am.put("up", new MoveAction(pacMan, 0, -4));
am.put("down", new MoveAction(pacMan, 0, 4));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
pacMan.paint(this, g2d);
g2d.dispose();
}
public class MoveAction extends AbstractAction {
private int deltaX;
private int deltaY;
private PacMan pacMan;
public MoveAction(PacMan pacMan, int deltaX, int deltaY) {
this.deltaX = deltaX;
this.deltaY = deltaY;
this.pacMan = pacMan;
}
#Override
public void actionPerformed(ActionEvent e) {
pacMan.move(deltaX, deltaY);
}
}
}
}
I would also recommend that you take the time to learn about Key Bindings, KeyListener suffer from focus issues, which key bindings are capable of addressing...
You need to process the move in a separate thread. I.e.:
public class Pacman implements Runnable
{
public void run(){
//moving code, i.e. in a while loop
//every move will notify the EDT:
SwingUtilities.invokeLater(new Runnable(){
public void run(){
//update the Swing here - i.e. move Pacman
}
}
}
public void startMoving(){
new Thread(this).start();
}
//more methods to set speed, direction, etc...
}
Then you keep a reference to an instance of Pacman class in your Gui class and respond to various key presses by changing pacman's parameters:
public void keyPressed(KeyEvent e){
int keyCode = e.getKeyCode();
if(keyCode == e.VK_LEFT){
pacman.newDirection(LEFT); //for exmaple, enum with direction LEFT, RIGHT, UP, DOWN...
}
//etc... more logic
}

Categories

Resources