Take setVisible(true);
and move it it to the last of the Game
constructor. Also you should make sure you're working within the context of the EDT when building your UI's, see Initial Threads for more details
import java.awt.Color;
import java.awt.EventQueue;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Game extends JFrame {
public final static int WIDTH = 700, HEIGHT = 450;
public GamePanel panel;
public Game() {
setTitle("Game");
setBackground(Color.WHITE);
setDefaultCloseOperation(EXIT_ON_CLOSE);
panel = new GamePanel(this);
add(panel);
setResizable(false);
setSize(WIDTH, HEIGHT);
setVisible(true);
}
public GamePanel getPanel() {
return panel;
}
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();
}
new Game();
}
});
}
}
Basically, Swing's layout management is lazy, it won't try to update the container hierarchy until you tell it (revalidate
) or it becomes realised or resized, this is a good thing, as the operation can be expensive.
Next, take a look at player = new Player(game, KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, game.getWidth() - 36);
You're setting the position of the player to be 36
pixels less than the components width
, but when this is called, the width
will be 0
.
Basically, you need to allow the UI to "settle" before making this types of calls. This is actually not that easy. You could use a ComponentListener
and monitor for the componentResized
event, but a window can be resized a number of times when it's first initialized, to this end, you need to "wait" until the size is "settled", for example...
public Game() {
addComponentListener(new ComponentAdapter() {
private boolean initalised = false;
private Timer timer = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
initalised = true;
panel.init();
timer.stop();
}
});
@Override
public void componentResized(ComponentEvent e) {
if (!initalised) {
timer.restart();
}
}
});
Then you would need to update GamePanel
to provide an init
method...
public class GamePanel extends JPanel implements ActionListener, KeyListener {
public Game game;
public Player player;
public GamePanel(Game game) {
setBackground(Color.GREEN);
this.game = game;
addKeyListener(this);
setFocusable(true);
}
public void init() {
player = new Player(game, KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, game.getWidth() - 36);
repaint();
}
//...
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (player != null) {
player.paint(g);
}
}
}
You have a wide range of "magic" numbers, which don't equate to what you're actually trying to do, for example...
player = new Player(game, KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, game.getWidth() - 36);
shouldn't that be game.getWidth() - Player.WIDTH
?
And then in Player
...
public Player(Game game, int left, int right, int x) {
this.game = game;
this.x = x;
y = game.getHeight() - 20;
//...
this.y = y;
x = game.getWidth() - 36;
}
You assign the parameter x
to the field x
, but then change it at the end of the constructor? Also, this.y = y
makes no sense!?
Now, let's talk about what a bad idea setSize(WIDTH, HEIGHT);
, which relates to why your player doesn't appear where you want it to...
Windows have decorations, so your viewable area will ALWAYS be smaller than the actual window size, what's worse, the size of the decorations are variable. Instead, you should use JFrame#pack
to pack the window around it's contents and have the contents provide hints about big it wants to be.
Have a look at How can I set in the midst? for more details.
Instead of setting the size of the frame, you should be using pack
...
public class Game extends JFrame {
public GamePanel panel;
public Game() {
addComponentListener(new ComponentAdapter() {
private boolean initalised = false;
private Timer timer = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
initalised = true;
panel.init();
timer.stop();
}
});
@Override
public void componentResized(ComponentEvent e) {
if (!initalised) {
timer.restart();
}
}
});
setTitle("Game");
setBackground(Color.WHITE);
setDefaultCloseOperation(EXIT_ON_CLOSE);
panel = new GamePanel(this);
add(panel);
setResizable(false);
pack();
setVisible(true);
}
And allowing GamePanel
to make decisions about how big it wants to be...
public static class GamePanel extends JPanel implements ActionListener, KeyListener {
public final static int WIDTH = 700, HEIGHT = 450;
public Game game;
public Player player;
public GamePanel(Game game) {
setBackground(Color.GREEN);
this.game = game;
addKeyListener(this);
setFocusable(true);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(WIDTH, HEIGHT);
}
This also means that instead of passing Game
to your objects, you should be passing GamePanel
to them and using its dimensions, for example...
public static class Player {
//...
public Player(GamePanel game, int left, int right, int x) {
this.game = game;
this.left = left;
this.right = right;
this.x = x;
y = game.getHeight() - HEIGHT;
}
You should also not be using KeyListener
and instead should be using the Key Bindings API instead, which will solve the focus related issue and make it easier to configure the key strokes
Basically, you're missing some core understandings about how the Swing framework works, which is causing no end of issues, I'd take the time to learn more about how the framework works generally before diving into something as complex as game development
Runnable example...
Now, because I've basically butchered your code to get it to work...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Game extends JFrame {
public GamePanel panel;
public Game() {
addComponentListener(new ComponentAdapter() {
private boolean initalised = false;
private Timer timer = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
initalised = true;
panel.init();
timer.stop();
}
});
@Override
public void componentResized(ComponentEvent e) {
if (!initalised) {
timer.restart();
}
}
});
setTitle("Game");
setBackground(Color.WHITE);
setDefaultCloseOperation(EXIT_ON_CLOSE);
panel = new GamePanel(this);
add(panel);
setResizable(false);
pack();
setVisible(true);
}
public GamePanel getPanel() {
return panel;
}
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();
}
new Game();
}
});
}
public static class GamePanel extends JPanel implements ActionListener, KeyListener {
public final static int WIDTH = 700, HEIGHT = 450;
public Game game;
public Player player;
public GamePanel(Game game) {
setBackground(Color.GREEN);
this.game = game;
addKeyListener(this);
setFocusable(true);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(WIDTH, HEIGHT);
}
public void init() {
System.out.println("!!");
player = new Player(this, KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, getWidth() - 50);
repaint();
}
public void update() {
player.update();
}
public void actionPerformed(ActionEvent e) {
update();
repaint();
}
public Player getPlayer(int playerNo) {
return player;
}
public void keyPressed(KeyEvent e) {
player.pressed(e.getKeyCode());
}
public void keyReleased(KeyEvent e) {
player.released(e.getKeyCode());
}
public void keyTyped(KeyEvent e) {
;
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (player != null) {
player.paint(g);
}
}
}
public static class Player {
public static final int WIDTH = 50, HEIGHT = 50;
public GamePanel game;
public int left, right;
public int y;
public int x, xa;
public Player(GamePanel game, int left, int right, int x) {
this.game = game;
this.left = left;
this.right = right;
this.x = x;
y = game.getHeight() - HEIGHT;
}
public void update() {
if (x > 0 && x < game.getWidth() - WIDTH - 36) {
x += xa;
} else if (x == 0) {
x++;
} else if (x == game.getWidth() - WIDTH - 36) {
x--;
}
}
public void pressed(int keyCode) {
if (keyCode == left) {
xa = -1;
} else if (keyCode == right) {
xa = 1;
}
}
public void released(int keyCode) {
if (keyCode == left || keyCode == right) {
xa = 0;
}
}
public Rectangle getBounds() {
return new Rectangle(x, y, WIDTH, HEIGHT);
}
public void paint(Graphics g) {
System.out.println(x + "x" + y);
g.fillRect(x, y, WIDTH, HEIGHT);
g.setColor(Color.ORANGE);
}
}
}