I am quite new to the work with JFrames and Graphics in java. My longterm goal is to create a RayCast Game-World. The following code is my first approach of a rectangle moving in a jframe at its coordinates. The coordinates change when the user presses the arrow keys. However something seems to be wrong, because when i use the program the rectangle just gets drawn and creates a path. I want the rectangle just to be drawn at the position of the coordinates.
import javax.swing.JFrame;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
import java.awt.event.ActionEvent;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JMenu;
public class Raycast_World_2 extends JFrame implements ActionListener, KeyListener, Runnable
{
public int px,py;
int velx = 0, vely = 0;
Graphics f;
public Raycast_World_2()
{
// Instanzvariable initialisieren
px = 100;
py = 100;
setSize(1280,960);
setVisible(true);
setDefaultCloseOperation(3);
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
setTitle("Raycast_World-Try4_Version 3.0");
JMenuBar menuBar = new JMenuBar();
JMenu menuFile = new JMenu("File");
JMenuItem menuItemExit = new JMenuItem("Exit");
menuFile.add(menuItemExit);
menuBar.add(menuFile);
// adds menu bar to the frame
setJMenuBar(menuBar);
}
public void actionPerformed(ActionEvent e){
update(getGraphics());
repaint();
}
public void paint(java.awt.Graphics g) {
g.setColor(Color.red);
g.fillRect(px,py,20,20);
g.dispose();
repaint();
}
public void run(){
}
public void keyPressed(KeyEvent e){
int c = e.getKeyCode();
if(c == KeyEvent.VK_LEFT){
px = px-10;
}
if(c == KeyEvent.VK_UP){
py = py -10;
}
if(c == KeyEvent.VK_RIGHT){
px = px 10;
}
if(c == KeyEvent.VK_DOWN){
py = py 10;
}
}
public void keyTyped(KeyEvent e){
}
public void keyReleased(KeyEvent e){}
}
Please explain me what im doing wrong. Thanks in advance, for helping me!
CodePudding user response:
Unfortunately, you are not painting correctly.
- don't extend
JFrame
. Top level classes are rarely subclassed. All you need is an instance ofJFrame
to which you will add aJPanel
in which to do your painting. Frames are meant for holding components. - extend
JPanel
and add that to the frame instance. - don't override
paint()
, overridepaintComponent
in the JPanel subclass. - then invoke
super.paintComponent(g)
to call the parent method to do such things as clear the background. If you don't do this, the rectangle will leave a trail when you move it. - and never call repaint from within
paintComponent
. It will continue to invoke paintComponent in the Event Dispatch Thread and may lockup or overflow the stack. - In the keylistener, where you modify the coordinates is where to call
repaint
.
Here is a modified version of your code that does the above.
import java.awt.Color;
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.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Raycast_World_2 extends JPanel implements KeyListener{
public int px, py;
int velx = 0, vely = 0;
JFrame frame = new JFrame();
public Raycast_World_2() {
// Instanzvariable initialisieren
px = 100;
py = 100;
// okay here but in some situations getPreferredSize() should be
// overridden.
setPreferredSize(new Dimension(600,500));
setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
frame.setTitle("Raycast_World-Try4_Version 3.0");
frame.add(this);
JMenuBar menuBar = new JMenuBar();
JMenu menuFile = new JMenu("File");
JMenuItem menuItemExit = new JMenuItem("Exit");
menuFile.add(menuItemExit);
menuBar.add(menuFile);
frame.setJMenuBar(menuBar);
frame.pack(); // size and layout components.
frame.setLocationRelativeTo(null); // center on screen
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(()->new Raycast_World_2());
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics gg = g.create();
gg.setColor(Color.red);
gg.fillRect(px, py, 20, 20);
gg.dispose();
}
}
public void keyPressed(KeyEvent e) {
int c = e.getKeyCode();
if (c == KeyEvent.VK_LEFT) {
px = px - 10;
}
if (c == KeyEvent.VK_UP) {
py = py - 10;
}
if (c == KeyEvent.VK_RIGHT) {
px = px 10;
}
if (c == KeyEvent.VK_DOWN) {
py = py 10;
}
repaint();
}
public void keyTyped(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
}
}
Check out how to paint in the Java Tutorials.
There are also many excellent examples of properly painting on StackOverflow.
Here is another suggestion but not required. Use an inner class
to create your keyListener
. By subclassing KeyAdapter
you do not not have to add unimplemented methods yourself. KeyAdapter
is simply a class with empty methods. MouseAdapter
is another one. Note that inner classes have access to the fields of the containing class. So you can do the following:
public class MyKeyListener extends KeyAdapter {
public void keyPressed(KeyEvent e) {
int c = e.getKeyCode();
if (c == KeyEvent.VK_LEFT) {
px = px - 10;
} else
if (c == KeyEvent.VK_UP) {
py = py - 10;
} else
if (c == KeyEvent.VK_RIGHT) {
px = px 10;
} else
if (c == KeyEvent.VK_DOWN) {
py = py 10;
}
repaint();
}
Then you can do the following.
addKeyListener(new MyKeyListener());
You may also want to consider using Key Bindings
which are also covered in the tutorials.
CodePudding user response:
Your problem seems to be that you are not reseting the screen every time you call the 'paint' method. So what happens is that every time you run this method it takes the previous state of the screen and draws whatever you want OVER this existing state. To be more clear, it just overloads paint on the screen. All you have to do in order to fix it is to add the following line of code at the start of your paint method:
super.paint(g);
This method, called from the superclass JFrame, will make sure that all the things that were previously painted onto the screen will be erased. So your screen after this line of code will be empty and will now be painted only with whatever you paint onto it after this line of code.