I realize that a major component missing from my game is a sense of time, ticks, or FPS. I am planning on implementing this soon, but wanted to get some feedback on how to set up my "bullets" instead. I want the user to press the space bar and have a bullet fired across the screen. Now I have gotten close without a sense of FPS; however, it just draws as one giant line, meaning the bullet "trail" never clears. I am wondering why, when I call repaint, the rectangle can move around the screen via my keyboard, but whenever I have something set automatically to move then it just leaves a "trail", even though I am calling repaint(); in each method.
Also, how could i create my bullet from another class?
public class drawingComponent extends JComponent implements KeyListener {
public Rectangle hello = new Rectangle(300, 100, 50, 50);
Rectangle bullet = new Rectangle(310,75, 10,10);
boolean goingon = false;
public drawingComponent(){
addKeyListener(this);
}
public void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.setColor(new Color(255,25,0));
g2.setFont(new Font("monospace", Font.BOLD+Font.ITALIC, 30));
g2.drawString("nothing yet",300,320);
g2.fill(hello);
setFocusable(true);
requestFocus();
g2.setColor(new Color(0,25,0));
if (goingon == true){
while (bullet.y < 1000){
bullet.y=bullet.y+10;
g2.fill(bullet);
}
bullet.y=300;
}
}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_W){
hello.y=hello.y-1;
hello.setLocation(hello.x,hello.y);
repaint();
System.out.println(hello.y);
}
if(e.getKeyCode() == KeyEvent.VK_S){
hello.y=hello.y+1;
hello.setLocation(hello.x,hello.y);
repaint();
}
if(e.getKeyCode() == KeyEvent.VK_A){
hello.x=hello.x-1;
hello.setLocation(hello.x,hello.y);
repaint();
}
if(e.getKeyCode() == KeyEvent.VK_D){
hello.x=hello.x+1;
hello.setLocation(hello.x,hello.y);
repaint();
}
if(e.getKeyCode() == KeyEvent.VK_SPACE){
goingon = true;
repaint();
}
}
#Override
public void keyReleased(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_SPACE){
goingon = false;
repaint();
}
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
}
Start by repairing the paint chain...
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(new Color(255,25,0));
g2.setFont(new Font("monospace", Font.BOLD+Font.ITALIC, 30));
g2.drawString("nothing yet",300,320);
g2.fill(hello);
Next, stop changing the state of the component from within the paint method...
//setFocusable(true);
//requestFocus();
g2.setColor(new Color(0,25,0));
if (goingon == true){
while (bullet.y < 1000){
bullet.y=bullet.y+10;
g2.fill(bullet);
}
bullet.y=300;
}
Painting should simply paint the current state of the component, it should never attempt to modify the state of the component, doing so could trigger another paint request which will put your code into an infinite loop of painting and consume your CPU cycles.
Swing uses a passive rendering algorithm, this means, painting is carried out only when the repaint manager thinks it needs to be done, meaning that painting can be done at random and mostly without your intervention...
Based on the fact that you are trying to force focus to the component, I assume you are trying to overcome issues related to KeyListener. Instead, you should use the key bindings API. Take a look at How to Use Key Bindings for more details
Take a look at Painting in AWT and Swing and Performing Custom Painting for more details.
Don't forget to call super.paintComponent() in overridden paintComponent() method that clears the previews view.
Read more...
Related
I know there's no direct replacement for java.awt.Canvas in swing, and I know I'm supposed to use a JPanel and override paintComponent, for example like so:
public void paintComponent(Graphics g) {
g.setColor(Color.black);
g.drawOval(0, 0, 100, 100);
}
And this would draw a black circle on the JPanel when it is created. The problem I have is that I want a dynamic canvas: I want to be able to draw things in response to user input, and redraw continuously, not just once when the application starts. An example would be having a moving object on a canvas, that would need to be redrawn at a rate of say 60 frames per second. How could I achieve this without using AWT components?
EDIT: what I mean is, in an actual canvas, I'd be able to arbitrarily call, say, drawOval anywhere in my code, and that would draw an oval on the canvas; is this doable with JPanel?
Store the information to be drawn (e.g. a Shape or a group of them) and call repaint() from a Swing Timer. Each time the paintComponent(..) method is called, first call the super(..) method to erase the previous drawings, then iterate the list of shapes, move them if necessary, and draw each one.
Here's one way to do it:
public class Renderer extends JComponent implements ActionListener {
private int x;
public Renderer() {
Timer timer = new Timer(1000/60, this);
timer.start();
x = 0;
}
#Override
public void paintComponent(Graphics g) {
super.paint(g);
// drawing code
g.setColor(Color.black);
g.drawOval(x, 0, 100, 100);
}
private void update() {
this.x++;
}
#Override
public void actionPerformed(ActionEvent e) {
update();
repaint();
}
}
Now just add this to your component (JPanel or whatever):
comp.add(new Renderer());
I tried to make a simple game in Java and ended up with this code
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
ball.paint(g2d);
}
...
while (true) {
repaint();
Thread.sleep(10);
}
It redraws not frequently enough. But if I move my mouse on top of the window it starts to repaint much more frequently. Pressing buttons on keyboard speeds up too.
I'm using Arch with i3wm.
Don't trust guides on the internet. Think yourself sometimes.
It was the guide with mistake. The problem is that the algorithm is wrong. We just have to draw more frequently than update our world.
Here is a stupid implementation of this. It should may be really wrong in terms of concurrency.
Timer timer1 = new Timer(1, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
game.repaint();
}
});
timer1.start();
Timer timer2 = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
game.move();
}
});
timer2.start();
I had exactly the same problem. Two timers didn't work for me. I managed to move an object smoothly by adding rendering hints and drawing an empty rectangle before my main object:
public void paintComponent(Graphics g) {
g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.WHITE)
g2d.fillRect(0, 0, getWidth, getHeight)
// paint your object here
}
Let me be honest, I'm not really know what I'm doing.
I just moved from python to Java, and i'm still trying to get used to all the classes and the types things.
I decide to make a break with java concepts tutorials and start to get my hands dirty. According to my understanding, I'm using swing to paint a ball on the screen and make it move.
I tried to design a ball object that handle the ball position and the screen bumping, but the ball doesn't moved at all. When I turn on the debug I noticed that the paint() function get called only at creation, but not get called with repaint().
I got a feeling that I'm using a bad tutorial to do this stuff, its look like there is a better way to do it.
Anyway, I will be glad to hear what you guys thinking.
Edit: After I saw your comments I notice that paint actually get called when I put sysout there. Its seems that the debugger doesn't jump to there before I put sysout in paint(). My guess is that I'm not really changing the position of the ball.
#SuppressWarnings("serial")
public class Tennis extends JPanel {
Ball ball = new Ball(50,50);
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int[] position = ball.getPosition();
g2d.fillOval(position[0],position[1], 30, 30);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Mini Tennis");
Tennis game = new Tennis();
frame.add(game);
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
while (true) {
// just change the position and check for bump
game.ball.move(game.getHeight(), game.getWidth());
game.repaint();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Change paint() to paintComponent(), for an explanation of the differences therein see this.
#Override
public void paintComponent(Graphics g){ //CHANGE HERE
super.paintComponent(g); //AND HERE
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int[] position = ball.getPosition();
g2d.fillOval(position[0],position[1], 30, 30);
}
So I am working on a project for a class. And I've been struggling with this issue for a while now. The code below is a start screen, and when the enter key is pressed (when atTitle turns to false) I would like it to draw the next image. The problem with that is I can not think of a way for it to draw the next image when it turns to false. I've tried using ifs and whiles. Mainly the problem is that you obviously put another public void paintComponent in an if statement. And I can't carry the Graphics g variable to the KeyPressed method.
I'm stuck.
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
AffineTransform at = new AffineTransform();
g2.setTransform(at);
if (atTitle == true) {
g.drawImage(titlescreen, 0, 0, this);
if (start_visible == true) {
g.drawImage(start_symbol, -70, 30, this);
jf.addKeyListener(this);
}
}
}
public void keyTyped(KeyEvent e) {}
public void keyReleased(KeyEvent e) {}
public void keyPressed(KeyEvent e) {
//int keyCode = e.getKeyCode();
if (atTitle == true) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
atTitle = false;
System.out.println("It Works.");
}
}
}
you need to call repaint and wait system calls to paint function. you should not keep graphic object and use it in a function is not child function of paint
as a simple canvas component, you can use this one
class Game extends Canvas implements Runnable {
private Thread thread;
public Game(){
thread = new Thread(this);
thread.start();
}
#Override
public void paint(Graphics g) {
// paint your game
}
public void stop(){
thread = null;
}
#Override
public void run() {
while (thread == Thread.currentThread()){
long ti = System.currentTimeMillis();
repaint();
long ti2 = System.currentTimeMillis();
long waitTime = 60 - (ti2-ti);
if (waitTime > 0){
try {
Thread.sleep(waitTime);
} catch (Exception e){
}
}
}
}
}
If you are using JPanel you should:
panel.setFocusable(true);
panel.requestFocusInWindow();
Referenced here
Also you can call panel.revalidate() and/or panel.repaint() in the key press.
Maybe try to stay away from JPanel and Swing altogether (not suited for gaming, but for forms) and just use the graphics2D functionality with a Window, Frame and Canvas, as suggested in previous comment.
public void paintComponent(Graphics g) {
g.setColor(Color.red);
g.fillRect(ball.getX(), ball.getY(), ball.getWidth(), ball.getHeight());
}
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if(keyCode == KeyEvent.VK_DOWN) {
System.out.println("down");
ball.moveY(5);
}
if(keyCode == KeyEvent.VK_UP) {
System.out.println("up");
ball.moveY(-5);
}
if(keyCode == KeyEvent.VK_LEFT) {
System.out.println("left");
ball.moveX(-5);
}
if(keyCode == KeyEvent.VK_RIGHT) {
System.out.println("right");
ball.moveX(5);
}
System.out.println("X: " +ball.getX() +", Y: " +ball.getY());
repaint();
}
When I press an arrow key and move the ball, why doesn't the repaint() method erase the ball's location from before? It's creating a tail thing.
Thanks
You are forgetting to call the super's paintComponent. i.e.,
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.fillRect(ball.getX(), ball.getY(), ball.getWidth(), ball.getHeight());
}
Note
that paintComponent should be protected, not public. Also, don't forget the #Override annotation.
KeyListeners should be avoided for Swing applications. Key Bindings are in general preferred since they are "higher level" concepts.
Because you've broken the paint chain.
One of the jobs that paintComponent does is to clear the Graphics context of what ever was painted to it before.
Make sure you call super.paintComponent first
Generally, the Graphics context is a shared resource, this means that everything that was painted during a paint cycle will share the same Graphics context. It also means that it's possible that the same Graphics context will be used for a single native peer (as is the case for you). You must always make best efforts to clean the context before use (transparency being a special case)
Take a look at Painting in AWT and Swing for more details about how painting is done in Swing
As has already being suggested, it is recommended that you use the Key Bindings API over KeyListener, the most significant reasons is because the key bindings API gives you greater control of the level of focus required before a key event is triggered
You are forget to call super.paintComponent(g). Have a look at PaintComponent
Try to replace
public void paintComponent(Graphics g) {
g.setColor(Color.red);
g.fillRect(ball.getX(), ball.getY(), ball.getWidth(), ball.getHeight());
}
By
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.fillRect(ball.getX(), ball.getY(), ball.getWidth(), ball.getHeight());
}