Canvas double buffered graphics not working - java

I tried to make double buffered graphics for my canvas but it always disappears right after rendering, and sometimes it doesn't even render, Here is the code:
package initilizer;
import java.awt.AWTException;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import input.Keyboard;
public class Main extends Canvas{
static int width = 800;
static int height = 600;
int cx = width/2;
int cy = height/2;
boolean initilized = false;
double FOV = 0.5 * Math.PI;
Camera cam = new Camera(1.0, 5.0, 3.0);
Camera cam1 = new Camera(10.0, 50.0, 30.0);
long lastFpsCheck = System.currentTimeMillis();
public static JFrame frame = new JFrame("3D Engine");
Robot robot;
static Keyboard keyboard = new Keyboard();
Image img;
public static void main(String[] args) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Canvas canvas = new Main();
canvas.setSize(width, height);
canvas.addKeyListener(keyboard);
canvas.setFocusable(true);
canvas.setBackground(Color.black);
frame.add(canvas);
frame.pack();
frame.setVisible(true);
BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
// Create a new blank cursor.
Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
cursorImg, new Point(0, 0), "blank cursor");
// Set the blank cursor to the JFrame.
canvas.setCursor(blankCursor);
}
void init() {
try {
robot = new Robot();
} catch (AWTException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
double[] rotate2D(double[] pos,double[] rot) {
double x = pos[0];
double y = pos[1];
double s = rot[0];
double c = rot[1];
double[] result = {(x * c) - (y * s), (y * c) + (x * s)};
return result;
}
public void paint(Graphics MainGraphics) {
Point startMousePos = MouseInfo.getPointerInfo().getLocation();
double startMouseX = startMousePos.getX();
double startMouseY = startMousePos.getY();
if(img == null)
{
img = createImage(width, height);
}
Graphics g = img.getGraphics();;
// First run initialization
if (initilized == false) {
initilized = true;
init();
}
// Storing start time for FPS Counting
long startTime = System.currentTimeMillis();
// Clearing Last Frame
//g.clearRect(0, 0, width, height);
// Drawing Crosshair
g.setColor(Color.white);
g.fillRect(cx - 8, cy - 1, 16, 2);
g.fillRect(cx - 1, cy - 8, 2, 16);
// Drawing Debugger Menu
g.drawString("HI WASSUp", 0, 16);
g.dispose();
if (frame.isFocused() == true) {
robot.mouseMove(cx, cy);
Point endMousePos = MouseInfo.getPointerInfo().getLocation();
double endMouseX = endMousePos.getX();
double endMouseY = endMousePos.getY();
double[] rel = {startMouseX - endMouseX, startMouseY - endMouseY};
cam.mouseMotion(rel);
}
// Limiting FPS
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Calculating FPS
long endTime = System.currentTimeMillis();
double delta_time = (endTime - startTime);
if ((lastFpsCheck + 1000) < endTime) {
lastFpsCheck = endTime;
frame.setTitle("3D Engine - FPS: " + (int) (1000/delta_time));
}
// Controlling camera movement
if (keyboard.getW() == true) {
cam.update(delta_time, "W");
}
if (keyboard.getA() == true) {
cam.update(delta_time, "A");
}
if (keyboard.getS() == true) {
cam.update(delta_time, "S");
}
if (keyboard.getD() == true) {
cam.update(delta_time, "D");
}
if (keyboard.getE() == true) {
cam.update(delta_time, "E");
}
if (keyboard.getQ() == true) {
cam.update(delta_time, "Q");
}
// Draw rendered frame
MainGraphics.drawImage(img, 0,0, null);
// Draw next frame
repaint();
}
}
I posted a question recently about this code, You could check keyboard java from that last post if you wanted to, But please help me with this I'm new to java programming (I still have some programming experience tho), Thank you

The answer to your question is complicated.
Java Swing JPanel (or JComponent) are double buffered by default
Swing already has a painting mechanism, which you don't control, so you need to work within it's functionality.
The only real reason you would use a java.awt.Canvas is if you want to take complete control over the painting process
The first thing I would suggest you do is take a look at Performing Custom Painting and Painting in AWT and Swing to get a better idea of how painting works in Swing/AWT. This will provide you a better understanding of the API and whether you want to work with it or define your own.
Some other areas of concern:
Don't do Thread.sleep(1000); in the paint method, nothing will be rendered until AFTER the paint method returns. You want to seperate the "update pass" from the "paint pass". Painting does nothing else by paints. All you decision making should be done as part of your "update pass" from your "main loop", which should be executed (in this case) off the Event Dispatching Thread, so as to prevent possible issues, but which then raises a bunch of other issues
MouseInfo.getPointerInfo().getLocation() is NOT how you should be tracking the position of the mouse. Swing already provides a number of mechanisms for tracking the mouse events. See How to write a Mouse Listener and How to Write a Mouse-Motion Listener for more details.
Based on every thing, I'd also be concerned about how you're tracking keyboard input and would highly recommend that you take a look at How to Use Key Bindings for the most commonly recommended method for managing key board input from the user.
The following example simply uses a JPanel as it's primary rendering surface. It takes advantage of the pre-existing painting process and uses a Swing Timer as the "main loop" mechanism, which is responsible for processing the user input and update the state before scheduling a new paint pass.
Remember, Swing is NOT thread safe and you should not update the UI or anything the UI might depend on, from outside the context of the Event Dispatching Thread.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.time.Duration;
import java.time.Instant;
import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;
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;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
Main main = new Main();
frame.add(main);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
main.start();
}
});
}
// Decouple the input from the implementation
enum Input {
UP, DOWN, LEFT, RIGHT
}
public class Main extends JPanel {
boolean initilized = false;
double FOV = 0.5 * Math.PI;
private Instant lastFpsCheck = Instant.now();
private Point mousePosition;
private Timer timer;
private Set<Input> input = new HashSet<>();
public Main() {
MouseAdapter mouseHandler = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
// This is within the components coordinate space
mousePosition = e.getPoint();
}
#Override
public void mouseEntered(MouseEvent e) {
mousePosition = e.getPoint();
}
#Override
public void mouseExited(MouseEvent e) {
mousePosition = null;
}
};
addMouseMotionListener(mouseHandler);
addMouseListener(mouseHandler);
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Pressed.up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Released.up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Pressed.down");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Released.down");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Pressed.left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Released.left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Pressed.right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Released.right");
actionMap.put("Pressed.up", new InputAction(input, Input.UP, true));
actionMap.put("Released.up", new InputAction(input, Input.UP, false));
actionMap.put("Pressed.down", new InputAction(input, Input.DOWN, true));
actionMap.put("Released.down", new InputAction(input, Input.DOWN, false));
actionMap.put("Pressed.left", new InputAction(input, Input.LEFT, true));
actionMap.put("Released.left", new InputAction(input, Input.LEFT, false));
actionMap.put("Pressed.right", new InputAction(input, Input.RIGHT, true));
actionMap.put("Released.right", new InputAction(input, Input.RIGHT, false));
timer = new Timer(15, new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
update();
}
});
}
public void start() {
startTime = Instant.now();
timer.start();
}
public void stop() {
timer.stop();
}
// The start time of a given cycle
private Instant startTime;
// The estimated number of frames per second
private double fps = 0;
// The number of acutal updates performed
// within a given cycle
private int updates = 0;
protected void update() {
if (startTime == null) {
startTime = Instant.now();
}
if (input.contains(Input.UP)) {
//cam.update(delta_time, "W");
}
if (input.contains(Input.LEFT)) {
//cam.update(delta_time, "A");
}
if (input.contains(Input.DOWN)) {
//cam.update(delta_time, "S");
}
if (input.contains(Input.RIGHT)) {
//cam.update(delta_time, "D");
}
// Don't know what these do, so you will need to devices
// your own action
//if (input.contains(Input.UP)) {
//cam.update(delta_time, "E");
//}
//if (input.contains(Input.UP)) {
//cam.update(delta_time, "Q");
//}
Instant endTime = Instant.now();
Duration deltaTime = Duration.between(startTime, endTime);
if (lastFpsCheck.plusSeconds(1).isBefore(endTime)) {
System.out.println(deltaTime.toMillis());
lastFpsCheck = endTime;
fps = updates;
updates = 0;
startTime = Instant.now();
}
updates++;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
double[] rotate2D(double[] pos, double[] rot) {
double x = pos[0];
double y = pos[1];
double s = rot[0];
double c = rot[1];
double[] result = {(x * c) - (y * s), (y * c) + (x * s)};
return result;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Point startMousePos = MouseInfo.getPointerInfo().getLocation();
//double startMouseX = startMousePos.getX();
//double startMouseY = startMousePos.getY();
Graphics2D g2d = (Graphics2D) g.create();
// Drawing Debugger Menu
g2d.drawString("HI WASSUp", 0, 20);
if (mousePosition != null) {
g2d.drawString(mousePosition.x + "x" + mousePosition.y, 0, 40);
// Your old code is broken, because MouseInfo.getPointerInfo
// doesn't give you the position of the mouse from within
// the components coordinate space, but in the screen space
// instead
//robot.mouseMove(cx, cy);
//Point endMousePos = MouseInfo.getPointerInfo().getLocation();
//double endMouseX = endMousePos.getX();
//double endMouseY = endMousePos.getY();
//double[] rel = {startMouseX - endMouseX, startMouseY - endMouseY};
//cam.mouseMotion(rel);
}
g2d.drawString(Double.toString(fps), 0, 60);
StringJoiner sj = new StringJoiner(", ");
for (Input item : input) {
switch (item) {
case DOWN:
sj.add("down");
break;
case UP:
sj.add("up");
break;
case LEFT:
sj.add("left");
break;
case RIGHT:
sj.add("right");
break;
}
}
g2d.drawString(sj.toString(), 0, 80);
g2d.dispose();
}
public class InputAction extends AbstractAction {
private final Set<Input> input;
private final Input direction;
private final boolean add;
public InputAction(Set<Input> input, Input direction, boolean add) {
this.input = input;
this.direction = direction;
this.add = add;
}
#Override
public void actionPerformed(ActionEvent evt) {
if (add) {
input.add(direction);
} else {
input.remove(direction);
}
}
}
}
}
Now, because of the way Swing's paint process works, the FPS is at best a "guesstimate" and I'd personally no rely to heavy on it. I might consider setting the Timer to use a 5 millisecond delay instead and just go as fast as you can.
Now, if you absolutely, positively must have, without question, complete control over the painting process, then you will need to start with a java.awt.Canvas and make use of the BufferStrategy API.
This will give you complete control over the painting process. It's more complicated and will require you to take into consideration more edge cases, but will provide you with complete control over scheduling when a paint pass occurs and thus, better control over the FPS.
I would recommend having a look at the JavaDocs as the example is better.
I used the Thread.sleep(1000); to limit FPS only, It was 1000/60 but I changed it to this because I thought the problem may be in the speed of rendering
This is, to be frank, is a naive approach and demonstrates a lack of understanding with how the painting process works - no offensive, you've got to start somewhere. But a better place to start would be by reading the available documentation, which I've provided above so you can gain a better understanding of how the API actually works and make better decisions about whether you want to use it (ie JPanel) or roll your own (ie Canvas)

Related

KeyListener method keyPressed not working

I am trying to make a little game using java swing and have gotten pretty far but now my KeyListener is not using the method keyPressed.
Here is some of my code
public class Screen extends JPanel implements Runnable{
private static final int WIDTH = 300, HEIGHT = 300, RIGHT = 0, LEFT = 1, UP = 2, DOWN = 3, STILL = 4;
private Thread thread;
private boolean running = false;
private int direction = DOWN;
public Screen() {
setFocusable(true);
addKeyListener(new Key());
setPreferredSize(new Dimension(WIDTH, HEIGHT));
start();
}
public void tick(){
System.out.println(direction)
}
public void start() {
running = true;
thread = new Thread(this, "Game Loop");
thread.start();
}
public void run() {
while (running) {
tick();
repaint();
}
}
private class Key implements KeyListener{
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_D) {
direction = RIGHT;
}
if (key == KeyEvent.VK_A) {
direction = LEFT;
}
if (key == KeyEvent.VK_W) {
direction = UP;
}
if (key == KeyEvent.VK_S) {
direction = DOWN;
}
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
}
}
I am looking at the console and expecting the output to change from 3 (which is DOWN) whatever button I press but it never does. After some time I realized that something was wrong with the keyPressed method, I just don't know what.
You don't need a separate "Game Loop" thread. Swing is event driven. Your key listener code will be called when the user presses a key on the computer keyboard.
As MadProgrammer mentioned in his comment to your question
modifying he state of the UI from outside the context of the EDT may cause painting issues
In other words, Swing is single threaded and that thread is referred to as the Event Dispatch Thread (EDT) and all your code that changes the GUI should be executed on the EDT only.
The below code is a stand-alone, Swing application that displays a focussable JPanel. When you press one of the relevant keyboard "direction" keys, i.e. A or D or S or W, the console displays the direction. Pressing any other key causes the direction STILL to be written to the console. Note that I used a enum for the various directions, rather than integer constants.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class Screen01 extends JPanel implements Runnable {
public enum Directions {RIGHT, LEFT, UP, DOWN, STILL};
private static final int WIDTH = 300, HEIGHT = 300;
private Directions direction;
public Screen01() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setFocusable(true);
addKeyListener(new Key());
}
public void run() {
createAndDisplayGui();
}
private void createAndDisplayGui() {
JFrame frame = new JFrame("Screen");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(this, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private class Key implements KeyListener {
#Override
public void keyTyped(KeyEvent e) {
// Do nothing.
}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch (key) {
case KeyEvent.VK_D:
direction = Directions.RIGHT;
break;
case KeyEvent.VK_A:
direction = Directions.LEFT;
break;
case KeyEvent.VK_W:
direction = Directions.UP;
break;
case KeyEvent.VK_S:
direction = Directions.DOWN;
break;
default:
direction = Directions.STILL;
}
System.out.println("Direction = " + direction);
}
#Override
public void keyReleased(KeyEvent e) {
// Do nothing.
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Screen01());
}
}
Make use of the key bindings API, it will solve this, and all other, KeyListener related issues without a lot of hacking and head scratching over why it "sometimes" works
Swing is single threaded. This means you should be careful about updating the state of the UI (directly or indirectly) from outside of the context of the Event Dispatching Thread, this also includes any state the UI might be dependent on.
Have a look at Concurrency in Swing for more details.
Without more details, one of the simplest solutions would be to use a Swing Timer to act as the primary "tick" action. Each time it "ticks" you'd inspect the state of the input and update the state of the model and trigger a repaint to update the UI
For example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
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.util.HashSet;
import java.util.Set;
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;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
enum Input {
UP, DOWN, LEFT, RIGHT
}
private Set<Input> input = new HashSet<>();
public TestPane() {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Left.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Left.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Right.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Right.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Up.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Up.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Down.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Down.released");
am.put("Left.pressed", new InputAction(Input.LEFT, input, false));
am.put("Left.released", new InputAction(Input.LEFT, input, true));
am.put("Right.pressed", new InputAction(Input.RIGHT, input, false));
am.put("Right.released", new InputAction(Input.RIGHT, input, true));
am.put("Down.pressed", new InputAction(Input.DOWN, input, false));
am.put("Down.released", new InputAction(Input.DOWN, input, true));
am.put("Up.pressed", new InputAction(Input.UP, input, false));
am.put("Up.released", new InputAction(Input.UP, input, true));
input.add(Input.UP);
input.add(Input.DOWN);
input.add(Input.LEFT);
input.add(Input.RIGHT);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Check what's currently been pressed
// and update the state accordingly...
if (input.contains(Input.UP)) {
//..
}
if (input.contains(Input.DOWN)) {
//..
}
if (input.contains(Input.LEFT)) {
//..
}
if (input.contains(Input.RIGHT)) {
//..
}
repaint();
}
});
timer.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 midX = getWidth() / 2;
int midY = getHeight() / 2;
FontMetrics fm = g2d.getFontMetrics();
int spaceWidth = fm.stringWidth("M");
int spaceHeight = fm.getHeight();
if (input.contains(Input.UP)) {
String text = "UP";
g2d.drawString(text,
midX - (fm.stringWidth(text)) / 2,
(midY - (spaceHeight * 2) + fm.getAscent()));
}
if (input.contains(Input.DOWN)) {
String text = "DOWN";
g2d.drawString(text,
midX - (fm.stringWidth(text)) / 2,
(midY + spaceHeight + fm.getAscent()));
}
if (input.contains(Input.LEFT)) {
String text = "LEFT";
g2d.drawString(text,
(midX - spaceWidth - fm.stringWidth(text)),
(midY + (fm.getAscent() / 2)));
}
if (input.contains(Input.RIGHT)) {
String text = "RIGHT";
g2d.drawString(text,
(midX + spaceWidth),
(midY + (fm.getAscent() / 2)));
}
g2d.dispose();
}
public class InputAction extends AbstractAction {
private Input input;
private Set<Input> inputSet;
private boolean onRelease;
public InputAction(Input input, Set<Input> inputSet, boolean onRelease) {
this.input = input;
this.inputSet = inputSet;
this.onRelease = onRelease;
}
#Override
public void actionPerformed(ActionEvent e) {
if (onRelease) {
inputSet.remove(input);
} else {
inputSet.add(input);
}
}
}
}
}
This is a basic example and prints the state of the key inputs directly within the paintComponent method. A more realistic implementation would use the Timer's ActionListener to inspect the input state and make changes to the desired model accordingly, which is hinted at in the example.
If you require more direct control, then you need to avoid the Swing painting system and take control yourself. For this, you'd need to use a BufferStrategy. This is a more complicated solution, but it is also more powerful, as you gain complete control over when the UI is updated

Java Swing KeyBindings stop working only on Mac

I'm working on a game using Swing and I'm using KeyBindings for input from the keyboard.
I'm having issues where the KeyBindings stop responding. It happens every time I run the application, but as far as I can tell, it's not when a certain chain of events occurs. The KeyBindings just stop receiving input from the keyboard. I also use mouse input, which continues to work, so I know it's not related to input in general.
Some things I've tried:
made sure my object was not garbage collected
looked for a certain reason the issue occurs (ex: after a certain amount of time, a certain key combination pressed), to which I could find none
tried to use a KeyListener instead
None of these worked.
I then copied the project (without changing any code) to a windows machine, and after testing, I could not get the problem to occur once. I didn't paste code here because I'm 99% positive this is not related to my code, but related to the operating system it's running on.
Is this a problem with the macOS, or the JDK for Mac, or something else I don't know about?
I'm using JDK version 8 update 112, the computer is running macOS Sierra version 10.12.2.
It seems someone else had the same problem I now do, but they never got an answer.
EDIT
Here's a code example where the problem occurs:
import java.awt.Dimension;
import java.awt.Graphics;
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.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
//an example of the problem where the keyboard stops receiving input randomly
public class ProblemExample extends JPanel {
private static final long serialVersionUID = 1L;
private int xPos = 200, yPos = 200;
private boolean wKey, aKey, sKey, dKey;
private BufferedImage image; //sprite
public ProblemExample() {
JFrame frame = new JFrame();
frame.add(this);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
//makes the sprite a red square
image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
int[] redPixels = new int[50 * 50];
for (int i = 0; i < redPixels.length; i++) {
redPixels[i] = 0xffff0000;
}
image.setRGB(0, 0, 50, 50, redPixels, 0, 50);
initializeKeys();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
//sets up Key Bindings
private void initializeKeys() {
final String W = "W",
A = "A",
S = "S",
D = "D",
PRESSED = "PRESSED",
RELEASED = "RELEASED";
InputMap inputMap = this.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = this.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), W + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), A + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), S + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), D + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), W + RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), A + RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), S + RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), D + RELEASED);
Action wActionPressed = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
wKey = true;
}
};
Action aActionPressed = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
aKey = true;
}
};
Action sActionPressed = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
sKey = true;
}
};
Action dActionPressed = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
dKey = true;
}
};
Action wActionReleased = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
wKey = false;
}
};
Action aActionReleased = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
aKey = false;
}
};
Action sActionReleased = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
sKey = false;
}
};
Action dActionReleased = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
dKey = false;
}
};
actionMap.put(W + PRESSED, wActionPressed);
actionMap.put(A + PRESSED, aActionPressed);
actionMap.put(S + PRESSED, sActionPressed);
actionMap.put(D + PRESSED, dActionPressed);
actionMap.put(W + RELEASED, wActionReleased);
actionMap.put(A + RELEASED, aActionReleased);
actionMap.put(S + RELEASED, sActionReleased);
actionMap.put(D + RELEASED, dActionReleased);
}
public void loop() {
if (wKey) yPos -= 5;
if (aKey) xPos -= 5;
if (sKey) yPos += 5;
if (dKey) xPos += 5;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, xPos, yPos, null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
ProblemExample example = new ProblemExample();
Timer timer = new Timer(60, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
example.loop();
}
});
timer.start();
}
});
}
}
You might think this is a bug in MAC, while it is not Because MAC has the feature of secondary keys. As you are the user of MAC, I hope you are aware of this feature. This feature might be the the cause of your problem.
The Secondary key feature of MAC : is used where holding down a letter key will display variations of that letter, like holding down “u” to get “ü.” This comes in handy when spelling non-English words.
There’s a simple way to take control and change the behavior of long key presses to accommodate your needs.
Open the Terminal app and write:
defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false
Then restart any open app in which you want this setting to activate.
REVERTING BACK: Just,simply add true in place of false to the previous command like this:
defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool true
UPDATE:
You can speed up the rate of key repeats or decrease the delay before a held key starts repeating, by going to the System Preferences, and make changes under the keyboard header.
Besides what Tahir Hussain Mir suggest about changing the Mac key settings, I find the problem lies with the looping, it'd be more efficient to implement it in a event-driven way, I took the liberty to change your code a bit. Together with Hussain Mir suggest 's solution, it should solve your problem.
You could also handle key-repeating yourself, by e.g., starting a timer on key press and stoping it on key release, but then the key pressing behavior will differ between Windows and Mac, which isn't really the way you want to go.
package swing;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
//an example of the problem where the keyboard stops receiving input randomly
public class SOMacKeyBindings extends JPanel
{
private BufferedImage image; //sprite
private Point point = new Point(200, 200);
private int steps = 5;
private class KeyAction extends AbstractAction
{
private Runnable runnable;
public KeyAction(Runnable runnable)
{
this.runnable = runnable;
}
#Override
public void actionPerformed(ActionEvent e)
{
runnable.run();
}
}
public SOMacKeyBindings()
{
JFrame frame = new JFrame();
frame.add(this);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
//makes the sprite a red square
image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
int[] redPixels = new int[50 * 50];
for (int i = 0; i < redPixels.length; i++)
{
redPixels[i] = 0xffff0000;
}
image.setRGB(0, 0, 50, 50, redPixels, 0, 50);
initializeKeys();
}
#Override
public Dimension getPreferredSize()
{
return new Dimension(800, 600);
}
//sets up Key Bindings
private void initializeKeys()
{
final String W = "W",
A = "A",
S = "S",
D = "D",
PRESSED = "PRESSED";
InputMap inputMap = this.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = this.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), W + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), A + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), S + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), D + PRESSED);
actionMap.put(W + PRESSED, new KeyAction(() -> { point.y -= steps; repaint(); }));
actionMap.put(A + PRESSED, new KeyAction(() -> { point.x -= steps; repaint(); }));
actionMap.put(S + PRESSED, new KeyAction(() -> { point.y += steps; repaint(); }));
actionMap.put(D + PRESSED, new KeyAction(() -> { point.x += steps; repaint(); }));
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, point.x, point.y, null);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> new SOMacKeyBindings());
}
}

Move image in a spiral fashion in java

I am trying to make a simple animated intro. I have an image I am trying to move from the bottom left of the screen to the center of the screen in a clockwise spiral motion. This is the code that I am using for now. It just moves the image upward to the center:
static ImageLoader il = new ImageLoader();
private static BufferedImage logo = il.load("/logoNew.png");
private static Image power = il.gif("http://i.stack.imgur.com/KSnus.gif");
static double y = 1024.0;
public static void render(Graphics g){
if(y>(486/2)-128){
y = y-0.25;
}
if(draw){
g.drawImage(logo,(864/2)-128,(int)y,null);
g.setColor(Color.WHITE);
g.drawImage(power,10,10,null);
}
}
The if(draw) statement is activated by something else.
How do I go about moving the image. Do I just increment the x and the y differently at different points?
** EDIT **
I didn't make it clear on the motion. Its going from the bottom left to the top left to the top right to the bottom right to the bottom center (centre) to the center (centre) of the screen
Animation is the illusion of movement over time. Normally I would use something like the Timing Framework (or Trident or Universal Tween Engine) as the base of the animation, these provide better support for things like ease-in and ease-out.
The following example just makes uses of a simple javax.swing.Timer. I use this because it's safer to use with Swing, as it allows me to update the state of the UI from within the context of the Event Dispatching Thread, but doesn't block it (preventing it from updating the screen).
The following example uses a concept of a timeline and key frames. That is, at some point in time, something must happen. The timeline then provides the means for blending between those "key" points in time.
I, personally, like to work in abstract concepts, so the timeline is simply measured in a percentage from 0-1, which allows me to provide a variable time span. This allows me to adjust the speed of the animation without the need to change anything.
As you (should) be able to see, the last two legs only need to move half the distance, so they are slower than the other three legs, so, technically, they only need half the time to complete...but I'll leave it up to you to nut out the maths for that ;)
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
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 TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
protected static final int PLAY_TIME = 6000;
private Timeline timeline;
private long startTime;
private Point imgPoint;
private BufferedImage img;
public TestPane() {
try {
img = ImageIO.read(new File("C:/Neko.png"));
imgPoint = new Point(0, 200 - img.getHeight());
timeline = new Timeline();
timeline.add(0f, imgPoint);
timeline.add(0.2f, new Point(0, 0));
timeline.add(0.4f, new Point(200 - img.getWidth(), 0));
timeline.add(0.6f, new Point(200 - img.getWidth(), 200 - img.getHeight()));
timeline.add(0.8f, new Point(100 - (img.getWidth() / 2), 200 - img.getHeight()));
timeline.add(1f, new Point(100 - (img.getWidth() / 2), 100 - (img.getHeight() / 2)));
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long duration = System.currentTimeMillis() - startTime;
float progress = (float) duration / (float) PLAY_TIME;
if (progress > 1f) {
startTime = System.currentTimeMillis();
progress = 0;
((Timer) (e.getSource())).stop();
}
System.out.println(progress);
imgPoint = timeline.getPointAt(progress);
repaint();
}
});
startTime = System.currentTimeMillis();
timer.start();
} catch (IOException exp) {
exp.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null && imgPoint != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(img, imgPoint.x, imgPoint.y, this);
g2d.dispose();
}
}
}
public static class Timeline {
private Map<Float, KeyFrame> mapEvents;
public Timeline() {
mapEvents = new TreeMap<>();
}
public void add(float progress, Point p) {
mapEvents.put(progress, new KeyFrame(progress, p));
}
public Point getPointAt(float progress) {
if (progress < 0) {
progress = 0;
} else if (progress > 1) {
progress = 1;
}
KeyFrame[] keyFrames = getKeyFramesBetween(progress);
float max = keyFrames[1].progress - keyFrames[0].progress;
float value = progress - keyFrames[0].progress;
float weight = value / max;
return blend(keyFrames[0].getPoint(), keyFrames[1].getPoint(), 1f - weight);
}
public KeyFrame[] getKeyFramesBetween(float progress) {
KeyFrame[] frames = new KeyFrame[2];
int startAt = 0;
Float[] keyFrames = mapEvents.keySet().toArray(new Float[mapEvents.size()]);
while (startAt < keyFrames.length && keyFrames[startAt] <= progress) {
startAt++;
}
if (startAt >= keyFrames.length) {
startAt = keyFrames.length - 1;
}
frames[0] = mapEvents.get(keyFrames[startAt - 1]);
frames[1] = mapEvents.get(keyFrames[startAt]);
return frames;
}
protected Point blend(Point start, Point end, float ratio) {
Point blend = new Point();
float ir = (float) 1.0 - ratio;
blend.x = (int) (start.x * ratio + end.x * ir);
blend.y = (int) (start.y * ratio + end.y * ir);
return blend;
}
public class KeyFrame {
private float progress;
private Point point;
public KeyFrame(float progress, Point point) {
this.progress = progress;
this.point = point;
}
public float getProgress() {
return progress;
}
public Point getPoint() {
return point;
}
}
}
}

Java - Pac-Man - GUI - Drawing Graphics issue, and general tips for an aspiring programmer

I am making Pac-Man and I'm having trouble with drawing graphics on a frame, when i draw my point image it looks like a game of snake, i tried putting my drawing methods for background and char both in the render method, but than my point image flickers
What it currently looks like, feel free to ignore the random face it was an inside joke.
Also this is my very first game so any tips on structure, pointers on what I am doing right (if anything) and what I'm doing wrong, and general tips would be extremely helpful!
Also I am aware that i have a couple unused methods
Code:
package game;
import graphics.map;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
public class main extends Canvas implements Runnable{
private static final long serialVersionUID = 1L; //not sure why it wanted me to do this, maybe ask bender, or just google it later
public static boolean running = false;
public static int HEIGHT = 800;
public static int WIDTH = 600;
public static int posX = 50;
public static int posY = 50;
public static final String name = "Pac Man Alpha 1.4";
private static final double speed = 1.2;
public input input;
static BufferedImage background = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage pacman = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage settingsBackground = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage level1 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage level2 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage points = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage point = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static JFrame frame;
private input keypress = new input();
private map map;
private static boolean charLoaded = false;
public static boolean MAIN_MENU = true;
public static boolean GAME = false;
public static boolean level1test = true;
public static boolean level2test = false;
public static boolean level3test = false;
public static boolean level4test = false;
static boolean drawn = false;
public static boolean key_down;
public static boolean key_up;
public static boolean key_right;
public static boolean key_left;
//private Screen screen;
JButton startButton = new JButton("Start"); //Start
JButton settingsButton = new JButton("Settings"); //Settings
JButton exitButton = new JButton("Exit"); //Exit
public main()
{
setMinimumSize(new Dimension(WIDTH , HEIGHT ));
setMaximumSize(new Dimension(WIDTH , HEIGHT )); // keeps the canvas same size
setPreferredSize(new Dimension(WIDTH, HEIGHT));
frame = new JFrame(name);
if(MAIN_MENU == true && GAME == false){
buttons(frame.getContentPane());
}
frame.setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // ends program on
// close
frame.addKeyListener(new input() );
frame.add(this, BorderLayout.CENTER);
frame.pack(); // keeps size correct
frame.setResizable(false);
frame.setVisible(true);
this.addKeyListener(keypress);
}
public static void main(String[] args)
{
try {
background = ImageIO.read(new File("res\\Background.png"));
pacman = ImageIO.read(new File("res\\pacmansprites.png"));
settingsBackground = ImageIO.read(new File("res\\Background.png"));
level1 = ImageIO.read(new File("res\\level1.png"));
//level2 = ImageIO.read(new File("res\\level2.png"));
point = ImageIO.read(new File("res\\Points for pacman.png"));
} catch (IOException e) {
}
running = true;
new main().start();
}
public void run()
{
long lastTime = System.nanoTime();
double nsPerTick = 1000000000 / 60D;
long lastTimer = System.currentTimeMillis();
double delta = 0;
int frames = 0;
int ticks = 0;
while (running == true) {
long now = System.nanoTime();
delta += (now - lastTime) / nsPerTick;
lastTime = now;
boolean render = false;
while (delta >= 1) {
ticks++;
tick();
delta -= 1;
render = true;
}
try {
Thread.sleep(3); //keep the Frames from going to high
} catch (InterruptedException e) {
e.printStackTrace();
}
if(render == true){
frames++;
render();
}
if (System.currentTimeMillis() - lastTimer >= 1000) {
lastTimer +=1000;
//System.out.println("Frames: " + frames + " Ticks: " + ticks);
frames = 0;
ticks = 0;
}
}
}
public synchronized void start()
{
new Thread(this).start();
run();
}
public synchronized void stop()
{
running = false;
}
public void tick()
{
if (key_up) posY -= speed / 2;
if (key_down) posY += speed;
if (key_left) posX -= speed / 2;
if (key_right) posX += speed;
}
public void render()
{
drawn = false;
if(MAIN_MENU == false && GAME == true)
{
drawMap();
drawChar();
}
else if(MAIN_MENU == false && GAME == false) {
Graphics g = getGraphics();
{
g.drawImage(settingsBackground,0,0,getWidth(),getHeight(),null);
g.dispose();
}
} else {
Graphics g = getGraphics();{
g.drawImage(background,0,0,getWidth(), getHeight(),null);
g.dispose(); //kill it
}
}
}
public void drawMap(){
if(level1test == true){
Graphics g = getGraphics();
{
g.drawImage(level1,0,0,getWidth(),getHeight(),null);
g.dispose();
}
}
if(level2test == true && drawn == false){
Graphics g = getGraphics();
{
g.drawImage(level2,0,0,getWidth(),getHeight(),null);
}
g.dispose();
}
drawn = true;
}
public void drawChar(){
//drawMap();
Graphics g = getGraphics();{
g.drawImage(point,posX,posY,20, 20,null);
g.dispose();
revalidate();
}
}
public void begin() {
if (key_up) System.out.println("up");
if (key_down) System.out.println("down");
if (key_left) System.out.println("left");
if (key_right) System.out.println("right");
}
public void loadMap(){
if(!drawn && level1test){
}else if(!drawn && level2test){
//draw 2nd map here
}else if(!drawn && level3test){
//draw 3rd map here
}
}
public void buttons(Container pane)
{
pane.setLayout(null);
startButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
MAIN_MENU = false;
GAME = true;
frame.remove(startButton);
frame.remove(settingsButton);
frame.remove(exitButton);
frame.revalidate();
drawMap();
System.out.println("Start Button Clicked");
}
} );
settingsButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
MAIN_MENU = false;
GAME = false;
frame.remove(startButton);
frame.remove(settingsButton);
frame.remove(exitButton);
frame.revalidate();
frame.repaint();
System.out.println("Settings Button Clicked");
}
} );
exitButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
System.out.println("Exit Button Clicked");
System.exit(0);
}
} );
pane.add(startButton);
pane.add(settingsButton);
pane.add(exitButton);
Insets insets = pane.getInsets();
Dimension size = startButton.getPreferredSize();
startButton.setBackground(new Color(0, 0, 0));
startButton.setForeground(Color.CYAN);
startButton.setFocusPainted(false);
startButton.setFont(new Font("Calabri", Font.BOLD, 16));
settingsButton.setBackground(new Color(0, 0, 0));
settingsButton.setForeground(Color.RED);
settingsButton.setFocusPainted(false);
settingsButton.setFont(new Font("Calabri", Font.BOLD, 16));
exitButton.setBackground(new Color(0, 0, 0));
exitButton.setForeground(Color.YELLOW);
exitButton.setFocusPainted(false);
exitButton.setFont(new Font("Calabri", Font.BOLD, 16));
startButton.setBounds((WIDTH - 125) + insets.left, 10 + insets.top,
size.width + 50, size.height + 10);
settingsButton.setBounds((WIDTH - 125) + insets.left, 55 + insets.top,
size.width + 50, size.height + 10);
exitButton.setBounds((WIDTH - 125) + insets.left, 100 + insets.top,
size.width + 50, size.height + 10);
}
}
getGraphics is not how custom painting is done. You should, in your case, override the paint method, and make sure you call super.paint before doing any custom painting.
getGraphics returns the Graphics context last used to paint the component, which could be discarded on the next paint cycle, be null or no longer used by the component
Remember, painting uses the "painters canvas" approach, that is, just like painting in a physical canvas, when you paint into it, you paint over what was previously there, but not erasing it.
Now, if you override paint, you will find that you will have a flickering problem. This is because Canvas
is not double buffered
To solve this, you should consider user a BufferStrategy, which allows you to not only generate multiple buffers to paint to, but also to take control of the paint process itself
Just don't forget to clear each buffer before you paint to it...
Double buffering is the trick that allows you to have flicker-free animation. Basically you have two representations of your canvas, one that's currently being displayed and one that you can draw on. If you're finished with drawing, you copy the draw-canvas over the display-canvas. Depending on system and hardware there are more elegant ways where you can just tell the hardware to switch canvases (page flipping).
Without double buffering or a similar techniques, it is almost impossible to have flicker-free animation.
With double buffering you can afford to draw the background and then the foreground sprites. It is possibly more efficient to draw only those parts of the background that have been destroyed by the foreground sprites (there are various techniques for that as well, including of taking a snapshot image of the affected areas before you paint the sprites).
You can find a simple example for Java double buffering here. Java's BufferStrategy is a more complex solution that can use hardware features for page flipping.
I think the problem is that you only draw onto the image background, never erasing the old drawing from your image. You will need to clear the area and then start drawing in order to get your desired results.
I have never attempted to make a game but when I do simple animations I usually will do them on a JFrame or JPanel. With a JFrame you can Override the paint() method and with a JPanel, the paintComponent() method. It helps to keep everything that I'm drawing centralized, which makes it much easier for me to modify my code. When you call the respective super method in your overridden method, it will start you off with a clean slate, meaning you will have to paint the (image) background and your characters all over again. Calling the super method is also necessary to paint that component's children if you decide to add anything onto the JFrame/JPanel.
If you chose to use one of the above then I would recommend a JPanel due to it offering double buffering, which will help make your animations look smooth/fluid. Also, do not forget to call repaint();.
Here is a quick example, which can be used to duplicate your issue if you comment out super.paintComponent(g);.
*Note that I am extending and using a JPanel for this example.
Code:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Trial extends JPanel{
public static void main(String[] args)
{
new Trial();
}
int x = 5; // will represent the x axis position for our crude animation.
javax.swing.Timer timer = new javax.swing.Timer( 500, new ActionListener(){
// Timer used to control the animation and
// the listener is used to update x position and tell it to paint every .5 seconds.
#Override
public void actionPerformed(ActionEvent e) {
x += 5;
if ( x > 250)
timer.stop();
repaint(); // will call the paintComponent method.
}
});
Trial()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.add(this);
frame.setSize(300, 200);
frame.setVisible(true);
timer.start();
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g); // calling the super paintComponent method to paint children. can comment it
// out to duplicate your error, which happens when the frame isn't "refreshed".
Graphics2D g2d = (Graphics2D) g.create(); // creating a copy of the graphics object. // I do this to not alter the original
// Good practice and also Graphics2D
// offers a bit more to work with.
g2d.drawString("Graphics", x, 60); // painting a string.
g2d.drawRect(x, 80, 10, 10); // painting a rectangle.
}
}
Edit:
If you have a bunch of stuff to do and don't want to add it all into your paintComponent(); method you could create a method for various things and call them from inside your Overriden paint method, you could also pass them your graphics object. It will help you keep things relatively simple.

How to draw disappearing rectangles in Java [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Closed 9 years ago.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Questions concerning problems with code you've written must describe the specific problem — and include valid code to reproduce it — in the question itself. See SSCCE.org for guidance.
Improve this question
I want to make a program in Java. What I am trying to do is, when the user click the screen a small square is drawn. One by one ten more squares are displayed with the previous square in the center. When the sixth square is drawn the first disappears, when the seventh square is drawn the second square disappears etc. until all the squares are gone.
This is the MainActivity:
import java.awt.Color;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MainActivity {
public static int width = 900;
public static int height = width / 16 * 9;
static HandlerClass handler = new HandlerClass();
static JFrame window;
static JPanel windowInner;
public static void main(String[] args) {
window = new JFrame("Squarmony 1.0");
window.setSize(width, height);
window.setVisible(true);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
windowInner = new JPanel();
windowInner.setSize(width, height);
windowInner.setBackground(Color.BLACK);
window.add(windowInner);
windowInner.addMouseListener(handler);
}
private static class HandlerClass implements MouseListener {
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
GraphicsActivity g = new GraphicsActivity();
g.drawRectRipple(window.getGraphics(), e);
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
}
}
And here is the GraphicsActivity:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
public class GraphicsActivity {
public void drawRectRipple(Graphics g, MouseEvent e) {
g.setColor(Color.WHITE);
for (int i = 0; i <= 10; i++) {
for (int j = 0; j <= 250; j += 10) {
g.drawRect(e.getX() + j / 2, e.getY() - j / 2, j - j / 2, j + j / 2);
try {
Thread.sleep(250);
} catch (InterruptedException e1) {
}
}
}
}
}
How could I draw the rectangles one by one (like ripples) to the screen?
Thanks,
John
So there are probably a number of ways to achieve this, some easier, some harder.
Backgound:
Animation is the illusion of change over time. This means over a time period, some kind of change is expected to happen. In your case, some squares get painted, some don't.
Now, this suggests that we need some way to update our UI on a regular bases. Now you could use a Thread, but to be frank, there are easier ways.
Swing provides a Timer class that provides events on a regular time interval, this allows us to make changes to the UI safely from within the context of the Event Dispatching Thread.
This is important, because it is expected that all updates and modifications to the UI occur within the context of the EDT. Also, any action which blocks the EDT will prevent the UI from being updated. Check out Concurrency in Swing for more details.
Now, you could set a timer to tick at a predefined interval that meets your needs. Ie, you need 10 updates over a period of n seconds, but this begins to put you at a slight disadvantage, as all you calculations would be based on some known, concert value...While you can certainly do this, I prefer a more flexible solution.
So, instead, I've chosen to use a "percentage" based approach. That is, all my animation is based on knowing that at given percentage of the way through the cycle, something needs to happen. This makes it possible to have a variable time frame without adversely effecting the paint algorithm.
To achieve this, we need to know when the cycle started and how long the cycle is. This way we can calculate the duration a cycle has being running and the percentile of the cycle that is completed, for example
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Check to see if the cycle has being completed...
if (cycleStartedAt == -1) {
// The time that this cycle started
cycleStartedAt = System.currentTimeMillis();
}
// The amount of time this cycle has being running
long duration = System.currentTimeMillis() - cycleStartedAt;
// The progress through this cycle...
progress = (double)duration / (double)runningTime;
// Check for the completion of this cycle...
if (progress > 1.0d) {
// Reset..
cycleStartedAt = -1;
progress = 0;
}
repaint();
}
});
timer.setRepeats(true);
timer.start();
What does this actually provide us? The provides us the ability to model the animation based on percentage of time. For example...
private double[][] squares = {
{0, 0.30},
{0.075, 0.375},
{0.15, 0.45},
{0.225, 0.525},
{0.30, 0.60},
{0.30, 0.675},
{0.375, 0.75},
{0.45, 0.825},
{0.525, 0.90},
{0.6, 0.975},
};
This is a list of the 10 squares, each with a start and end percentage, indicating when each square should be visible.
And finally, a runnable example...
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.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Squares {
public static void main(String[] args) {
new Squares();
}
public Squares() {
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 TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int runningTime = 5000;
private long cycleStartedAt = -1;
private double progress = 0;
private double[][] squares = {
{0, 0.30},
{0.075, 0.375},
{0.15, 0.45},
{0.225, 0.525},
{0.30, 0.60},
{0.30, 0.675},
{0.375, 0.75},
{0.45, 0.825},
{0.525, 0.90},
{0.6, 0.975},
};
public TestPane() {
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (cycleStartedAt == -1) {
cycleStartedAt = System.currentTimeMillis();
}
long duration = System.currentTimeMillis() - cycleStartedAt;
progress = (double)duration / (double)runningTime;
if (progress > 1.0d) {
cycleStartedAt = -1;
progress = 0;
}
repaint();
}
});
timer.setRepeats(true);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics g2d = (Graphics2D) g.create();
int width = getWidth() - 1;
int height = getHeight() - 1;
int hGap = width / squares.length;
int vGap = height / squares.length;
int index = 0;
for (double[] square : squares) {
if (progress >= square[0] && progress <= square[1]) {
int sWidth = hGap * (index + 1);
int sHeight = vGap * (index + 1);
int x = (width - sWidth) / 2;
int y = (height - sHeight) / 2;
g2d.drawRect(x, y, sWidth, sHeight);
}
index++;
}
g2d.dispose();
}
}
}
Check out Performing custom painting and 2D Graphics for more details

Categories