how i can separate the logic from the EDT,because as you can see logic and render are related into loop
and how i can separate code in a pretty way, i' m getting in trouble with this,trying to find out how to do.
Any suggestion/helps/advices?
main class
public class MainFrame {
private static MainFrame mainFrame = null;
private final JFrame frame;
private MainFrame() {
frame = new JFrame();
frame.setUndecorated(true);
frame.add(new GamePanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static final MainFrame getMainFrameInstance() {
if (mainFrame == null) {
mainFrame = new MainFrame();
}
return mainFrame;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {// set look and feel to nimbus
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
MainFrame.getMainFrameInstance();
}
});
}
}
gamePanel class on which i start the game loop
public class MyPanel extends JPanel implements Runnable {
private static final long serialVersionUID = 1L;
// thread and loop
private Thread thread;
private boolean running;
private int FPS = 60;
private long targetTime = 1000 / FPS;
private long start;
private long elapsed;
private long wait;
// image
public BufferedImage image;
// foo
private Foo foo;
private Render render = Render.getRenderManagerInstance();
public MyPanel() {
setPreferredSize(new Dimension(700, 700));
setFocusable(true);
requestFocus();
}
public void addNotify() {
super.addNotify();
if (thread == null) {
thread = new Thread(this);
thread.start();
}
}
private void initGraphic() {
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
foo = new Foo();
running = true;
}
public void run() {
initGraphic();
// loop
while (running) {
start = System.nanoTime();
foo.update();
repaint();
elapsed = System.nanoTime() - start;
wait = (targetTime - elapsed / 1000000) - 8;
if (wait <= 0)
wait = 6;
try {
Thread.sleep(wait);
} catch (Exception e) {
e.printStackTrace();
}
}
}
#Override
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
graphics = (Graphics2D) image.getGraphics();
((Graphics2D) graphics).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
((Graphics2D) graphics).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
render.setRenderState((Graphics2D) graphics);
}
Your gamePanel should not enter an infinite loop. It should initialize itself and define a method to be called periodically by a Swing timer. More generally, whenever you do something periodically, you should not try to keep track of time, but instead define what should be done during a single step and let a timer call you. This makes a much better use of system resources and is a good engineering practice.
Related
I have a problem where my JFrame is constantly flashing white and black, but I only set the colour to black. I think it has to do with the while (running) {} bit.
It just turns white and black forever, until I close it. I really don't know what is going on.. I only just started to use JFrame so I'm sure I have just put some wrong code.
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = 1L;
public static int width = 300;
public static int height = width / 16 * 9;
public static int scale = 3;
public static boolean running = false;
private Thread thread;
private JFrame frame;
public Game() {
Dimension window = new Dimension(width * scale, height * scale);
setPreferredSize(window);
frame = new JFrame();
}
public synchronized void start() {
running = true;
thread = new Thread(this, "Display");
thread.start();
}
public synchronized void stop() {
try {
running = false;
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void run() {
while (running) {
render();
}
}
public void update() {
}
public void render() {
BufferStrategy buffer = getBufferStrategy();
if (buffer == null) {
createBufferStrategy(3);
return;
}
Graphics g = buffer.getDrawGraphics();
g.setColor(Color.BLACK);
g.drawRect(0, 0, getWidth(), getHeight());
g.dispose();
buffer.show();
}
public static void main(String[] args) {
Game game = new Game();
game.frame.setResizable(false);
game.frame.setTitle("Game");
game.frame.add(game);
game.frame.pack();
game.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
game.frame.setLocationRelativeTo(null);
game.frame.setVisible(true);
game.start();
}
}
Solved it myself... I used the method drawRect() And I read the documentation page and it says it only draws the outline of the rectangle.. So I just did drawRect()
Also I changed the buffer to 2.
Sorry for wasting your time.
In more detail, I have a componentResized event attached to the JFrame (which contains the canvas, and nothing else), and in that event a call a method which sets the bounds of the canvas accordingly. This works fine, except that while I'm resizing the canvas, it doesn't show anything. I just see the back of the JFrame. Once I've stopped resizing the JFrame, the canvas paints fine again.
public class MyCanvas implements ComponentListener {
public static void main(String[] args) {
new MyCanvas("MyCanvas",new Dimension(300,300));
}
private static final int frameRate = 30;
private JFrame frame;
private JPanel panel;
private Canvas canvas;
private BufferStrategy strategy;
private int delta;
private boolean running = false;
private int frameCount = 0;
public MyCanvas(String name, Dimension size) {
frame = new JFrame(name);
panel = (JPanel) frame.getContentPane();
canvas = new Canvas();
frame.setSize(size);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel.setPreferredSize(size);
panel.setLayout(null);
canvas.setBounds(0,0,size.width,size.height);
panel.add(canvas);
canvas.setIgnoreRepaint(true);
frame.setResizable(true);
frame.pack();
frame.addComponentListener(this);
canvas.createBufferStrategy(2);
strategy = canvas.getBufferStrategy();
running = true;
frame.setVisible(true);
long lastLoopTime = 0;
while (running) {
frameCount++;
delta = (int) (System.currentTimeMillis() - lastLoopTime);
lastLoopTime = System.currentTimeMillis();
Graphics2D graphics = (Graphics2D) strategy.getDrawGraphics();
graphics.setColor(Color.black);
graphics.fillRect(0,0,getSize().width,getSize().height);
graphics.dispose();
strategy.show();
try {
Thread.sleep(1000/frameRate);
} catch (InterruptedException e) {}
}
}
public final Dimension getSize() {
return frame.getSize();
}
public final void setSize(Dimension size) {
frame.setSize(size);
canvas.setBounds(0,0,size.width,size.height);
}
public synchronized void componentResized(ComponentEvent e) {
setSize(frame.getSize());
}
public synchronized void componentHidden(ComponentEvent e) {
// unused
}
public synchronized void componentShown(ComponentEvent e) {
// unused
}
public synchronized void componentMoved(ComponentEvent e) {
// unused
}
}
Edit
After tweaking with the code for a while, I have come up with a solution:
public class MyCanvas {
public static void main(String[] args) {
new MyCanvas("MyCanvas",new Dimension(400,400));
}
private static final int frameRate = 1000 / 30;
private JFrame frame;
private JPanel panel;
private int delta;
private long lastLoopTime;
private boolean running = false;
private int frameCount = 0;
private BufferedImage backBuffer = null;
private int lastPaintFrame = -1;
public MyCanvas(String name, Dimension size) {
frame = new JFrame(name);
panel = new JPanel() {
protected void paintComponent(Graphics g) {
super.paintComponent(g);
redraw(g);
}
};
frame.setContentPane(panel);
frame.setSize(size);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel.setPreferredSize(size);
panel.setLayout(null);
frame.setResizable(true);
running = true;
frame.setVisible(true);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
backBuffer = new BufferedImage(screenSize.width,screenSize.height,BufferedImage.TYPE_INT_ARGB);
lastLoopTime = System.nanoTime();
while (running) {
long thisLoopTime = System.nanoTime();
delta = (int) ((thisLoopTime - lastLoopTime) / 1000000);
draw(backBuffer.getGraphics());
frameCount++;
lastLoopTime = thisLoopTime;
redraw(panel.getGraphics());
try {
Thread.sleep(1000/30);
} catch (InterruptedException e) {}
}
}
private final void redraw(Graphics g) {
if (g != null && backBuffer != null) {
g.drawImage(backBuffer,0,0,null);
}
}
int x = 30;
public final void draw(Graphics g) {
g.setColor(Color.darkGray);
g.fillRect(0,0,getSize().width,getSize().height);
g.setColor(Color.gray);
g.fillRect(0,0,500,500);
g.setColor(Color.blue);
g.fillRect(x,30,300,300);
x++;
}
public final Dimension getSize() {
return frame.getSize();
}
public final void setSize(Dimension size) {
frame.setSize(size);
}
}
However, this is not quite solved yet, because it still has odd little graphical glitches which only seem to appear when redraw is called from panel's paintComponent method, though not consistently. Those glitches manifest themselves as odd rectangles of color (usually black or grey) which promptly disappear again. I'm really not sure of what it could be... maybe problems with the double buffering? BTW, if I threw a runtime exception in paintComponent, it worked perfectly.
If this should be moved to a new question, please let me know.
I found the solution: different loops for draw and update:
public class MyCanvas {
public static void main(String[] args) {
new MyCanvas("MyCanvas",new Dimension(400,400));
}
private static final int frameRate = 1000 / 30;
private JFrame frame;
private JPanel panel;
private int delta;
private long lastLoopTime;
private volatile boolean running = false;
private int frameCount = 0;
public MyCanvas(String name, Dimension size) {
frame = new JFrame(name);
panel = new JPanel() {
protected void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
if (running) repaint();
}
};
frame.setContentPane(panel);
frame.setSize(size);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel.setPreferredSize(size);
panel.setLayout(null);
frame.setResizable(true);
running = true;
frame.setVisible(true);
lastLoopTime = System.nanoTime();
new Thread(()->{
while (running) {
update();
frameCount++;
try {
Thread.sleep(frameRate);
} catch (InterruptedException e) {}
}
},"Game Loop").start();
}
int x = 30;
public final void update() {
x++;
}
public final void draw(Graphics g) {
g.setColor(Color.darkGray);
g.fillRect(0,0,getSize().width,getSize().height);
g.setColor(Color.gray);
g.fillRect(0,0,500,500);
g.setColor(Color.blue);
g.fillRect(x,30,300,300);
}
public final Dimension getSize() {
return frame.getSize();
}
public final void setSize(Dimension size) {
frame.setSize(size);
}
}
I'm making a game, and I have the following main classes
Main controller class:
public class Manager implements Runnable {
private boolean running;
private Thread thread;
private MasterRenderer window;
private PlayerCharacter player;
private Map map;
public Manager() {
window = new MasterRenderer();
player = new PlayerCharacter(0, 0, Entity.FACING_DOWN, "Pepe", 0, 1, 2);
map = new Map();
}
public void initialize() {
window.setPlayerRenderer(new PlayerRenderer(player));
window.setMapRenderer(new MapRenderer(map));
window.setTerrainRenderer(new TerrainRenderer(new Terrain(0, 0, Entity.FACING_DOWN, "arbol", false)));
running = true;
thread = new Thread(this);
thread.start();
}
public void run() {
long timer = System.currentTimeMillis();
long lastTime = System.nanoTime();
final double ns = 1000000000.0 / 60.0;
double delta = 0;
int frames = 0;
int ticks = 0;
while (running) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 1) {
tick(delta);
ticks++;
delta--;
frames++;
render();
}
if (System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println("Frames: " + frames + "\tTicks: " + ticks);
frames = ticks = 0;
}
}
}
public void tick(double delta) {
}
public void render() {
window.render();
}
public static void main(String[] args) {
Manager manager = new Manager();
manager.initialize();
}
}
Main view class:
public class MasterRenderer extends Canvas implements KeyListener {
private static final long serialVersionUID = 1L;
public static final int WIDTH = 640;
public static final int HEIGHT = WIDTH * 3 / 4;
public static final String TITLE = "The legend of Finn";
private JFrame frame;
private BufferStrategy bs;
private Graphics g;
private PlayerRenderer playerR;
private MapRenderer mapR;
private TerrainRenderer terrainR;
public MasterRenderer() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
frame = new JFrame();
addKeyListener(this);
frame.setTitle(TITLE);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this);
frame.setResizable(false);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
requestFocus();
}
public void render() {
bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
g = bs.getDrawGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
playerR.render(g);
mapR.render(g);
terrainR.render(g);
bs.show();
g.dispose();
}
public void setMapRenderer(MapRenderer mapR) {
this.mapR = mapR;
}
public void setPlayerRenderer(PlayerRenderer playerR) {
this.playerR = playerR;
}
public void setTerrainRenderer(TerrainRenderer terrainR) {
this.terrainR = terrainR;
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
int i=e.getKeyCode();
System.out.println("Key pressed!!");
}
#Override
public void keyReleased(KeyEvent e) {
}
}
I've organize the code this way, because I'm trying to really stick to MVC (besides, putting the keyListener in the manager class now is a bit of a pain in the butt). Is there any simple way to notify Manager when MasterRenderer registers a key press?
Your View has no Control reference within it, and no methods for registering listeners, and without this, I don't see how the view will communicate with the control. I see you have two possible solutions:
Pass the Control reference into the view, and have the view call methods of the control when it wants to notify it of a state change, or
set up a mechanism, such as a SwingPropertyChangeSupport mechanism, that will allow the control to listener for state changes. If you go this route, then you have to call the approprate fireXxx(...) methods of the support.
Luckily many Swing and AWT components already have mechanisms for number 2 above, and I suggest that you do just this: have your control register a PropertyChangeListener with your Canvas object (better if it were a JPanel!), and then in your KeyListener a firePropertyChange(...) method notifying the control of the state change.
For example:
import java.awt.Dimension;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
public class MyControl implements Runnable, PropertyChangeListener {
public MyControl() {
// TODO finish
}
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
// this is only one of potentially many properties that the control can listen to
if (MyView.KEY_PRESSED.equals(pcEvt.getPropertyName())) {
// respond to the key pressed code
System.out.println("KeyCode is: " + pcEvt.getNewValue());
}
}
#Override
public void run() {
// TODO Finish!
}
private static void createAndShowGui() {
// create both view and control
MyControl control = new MyControl();
MyView view = new MyView();
// now wire the control to listen to the view
view.addPropertyChangeListener(MyView.KEY_PRESSED, control);
JFrame frame = new JFrame("MyControl");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(view);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MyView extends JPanel {
// constant for PropertyChangeListener use:
public static final String KEY_PRESSED = "key pressed";
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
public MyView() {
setFocusable(true);
requestFocusInWindow();
addKeyListener(new MyKeyListener());
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class MyKeyListener extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
// get the keycode pressed
int newValue = e.getKeyCode();
// ** notify any listeners that this key has been pressed **
firePropertyChange(KEY_PRESSED, null, newValue);
}
}
}
I am currently using a JPanel to draw the elements from my game, however, there is occasional stuttering with the graphics, after some research I learnt about screen tearing and double buffering, which I believe is the solution to my problem, however, currently with my code I am finding it extremely difficult to implement without tearing out a lot of code.
I am wondering if anyone could possibly provide either a simpler way to fix my screen tearing on a small 2D tile based game, or how to do double buffering with my current code, thank you very much!
Frame.java
public class Frame extends JPanel implements KeyListener {
public static Game game;
public static boolean[] mouseDown = new boolean[4];
public static int width, height;
public static Font font = new Font("Arial", Font.BOLD, 16);
public static JFrame frame;
public Frame()
{
setFocusable(true);
requestFocus();
setOpaque(true);
addKeyListener((KeyListener) this);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
});
addMouseMotionListener(new MouseMotionListener() {
#Override
public void mouseMoved(MouseEvent e) {
}
#Override
public void mouseDragged(MouseEvent e) {
}
});
}
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setBackground(Color.BLACK);
g2d.setFont(font);
super.setBackground(Color.BLACK);
super.paintComponent(g2d);
//This is where I use g2d to draw onto the JPanel
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
frame = new JFrame("Game");
frame.add(new Frame());
frame.setSize(1000, 750);
frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}});
}
Game.java
public void gameStart() {
Thread gameThread = new Thread() {
// Override run() to provide the running behavior of this thread.
#Override
public void run() {
gameLoop();
}
};
// Start the thread. start() calls run(), which in turn calls gameLoop().
gameThread.start();
}
public void gameLoop() {
while (!isGameFinished) {
beginTime = System.nanoTime();
gameUpdate();
frame.repaint();
// Delay timer to provide the necessary delay to meet the target rate
timeTaken = System.nanoTime() - beginTime;
timeLeft = (UPDATE_PERIOD_NSEC - timeTaken) / 1000000L; // in milliseconds
if (timeLeft < 10) timeLeft = 10; // set a minimum
try { // Provides the necessary delay and also yields control so that other thread can do work.
Thread.sleep(timeLeft);
} catch (InterruptedException ex) { }
}
}
public void gameStart() {
Thread gameThread = new Thread() {
// Override run() to provide the running behavior of this thread.
#Override
public void run() {
gameLoop();
}
};
// Start the thread. start() calls run(), which in turn calls gameLoop().
gameThread.start();
}
public void gameLoop() {
while (!isGameFinished) {
beginTime = System.nanoTime();
gameUpdate();
frame.repaint();
timeTaken = System.nanoTime() - beginTime;
timeLeft = (UPDATE_PERIOD_NSEC - timeTaken) / 1000000L;
if (timeLeft < 10) timeLeft = 10; // set a minimum
try {
Thread.sleep(timeLeft);
} catch (InterruptedException ex) { }
}
}
As shown in the code, the Game class contains the Game loop, which will constantly repaint the Frame class. How can I tweak this code to allow for double buffering to remove screen tearing? Thanks!
Double buffering is the best way to fix it. Try calling Toolkit#sync (Toolkit.getDefaultToolkit().sync()) , which sometimes removes the issue. You won't be tearing down much code if you switch to double buffering, just move the code in paint to a separate method.
After execution, the loop completes its circles and then the repaint method is being called,
Is there an alternative way of drawing the image?
public class GameCanvas extends JPanel implements KeyListener {
private Tank tank1;
private Tank tank2;
private Rocket rocket;
public Graphics2D g2;
private Image img;
public boolean fire;
public GameCanvas() {
tank1 = new Tank(0, 0);
tank2 = new Tank(500, 500);
rocket = new Rocket(50, tank1);
init();
fire = false;
}
public void init() {
JFrame frame = new JFrame("Topak Tank");
frame.setLayout(new FlowLayout());
frame.setSize(1300, 740);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
this.setPreferredSize(new Dimension(1300, 740));
frame.add(this);
frame.addKeyListener(tank1);
frame.addKeyListener(this);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
tank1.draw(g);
// rocket.draw(g);
g2 = (Graphics2D) g;
if (fire == true) {
try {
img = ImageIO.read(getClass().getResource("rocket.png"));
System.out.println(rocket.getYPos());
g2.drawImage(img, rocket.getXPos(), rocket.getYPos(), null);
} catch (Exception e) {
}
}
repaint();
}
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_F: {
try {
int xPos = tank1.getXAxis();
int yPos = tank1.getYAxis();
fire = true;
int delay = 10000;
// img = ImageIO.read(getClass().getResource("rocket.png"));
int count = 0;
while (true) {
System.out.println(xPos);
System.out.println(yPos);
if (count == 5) {
break;
}
count++;
// g2.drawImage(img,xPos,yPos,null);
rocket.setYPos(yPos);
System.out.println("loop");
Timer t = new Timer(delay, null);
t.start();
yPos++;
this.repaint();// here i want to excute and draw image after every lop cycle
Thread.sleep(100);
}
System.out.println("Fired");
fire = false;
} catch (Exception io) {
JOptionPane.showMessageDialog(null, "Could not found Rocket image");
}
}
break;
}
}
public void keyReleased(KeyEvent ke) {
}
public void keyTyped(KeyEvent ke) {
}
public static void main(String[] arg) {
GameCanvas gc = new GameCanvas();
}
}
Calling repaint() just tells the component to repaint itself whenever the EDT is available again. Since that entire loop is on the EDT, the component can't repaint itself until it's done.
You should probably be using a Timer or a Thread instead of tying up the EDT with that loop. You should NOT be calling sleep() on the EDT.
More info here: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/