2012-12-06 3 views
3

Je développe un jeu en utilisant le framework Java Swing. Est-ce que quelqu'un connaît un bon cadre basé sur Swing? Surtout, je me soucie de la performance de redessiner.Java cadre de jeu basé sur Swing. Aucun conseil?

+0

jetez un coup d'oeil à Play Framework et lisez les FAQ de ce site –

+1

@NikolayKuznetsov Le cadre de jeu est un cadre pour les webapps, pas pour les applications Swing, et n'a rien à voir avec les jeux, malgré le nom "Play". – Jesper

+0

@Jesper, vous avez raison, désolé. Swing est une sorte de cadre lui-même –

Répondre

3

Cette simple boucle de jeu à temps fixe (je me suis adapté du crédit de référence à l'auteur) ne m'a jamais laissé tomber.

Il permet de tracer à 60 fps (ou peu importe), le hertz peut également être modifié, il active l'anti-aliasing via Graphics2D et quelques autres effets. L'exemple des auteurs originaux comprenait une vérification d'interpolation, mais je l'ai trouvé en me donnant quelques problèmes dans mes jeux comme des images scintillant dans et hors de leurs positions, donc j'ai gardé cela inclus, mais si vous rencontrez des problèmes au moins, vous saurez faisant):

enter image description here

import java.awt.*; 
import java.awt.event.*; 
import javax.swing.*; 

public class GameLoopTest implements ActionListener { 

    private GamePanel gamePanel; 
    private JButton startButton; 
    private JButton quitButton; 
    private JButton pauseButton; 
    private boolean running = false; 
    private boolean paused = false; 

    public GameLoopTest() { 
     JFrame frame = new JFrame("Fixed Timestep Game Loop Test"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

     gamePanel = new GamePanel(500, 500); 
     startButton = new JButton("Start"); 
     quitButton = new JButton("Quit"); 
     pauseButton = new JButton("Pause"); 
     pauseButton.setEnabled(false); 

     JPanel buttonPanel = new JPanel(); 
     buttonPanel.setLayout(new GridLayout(1, 2)); 

     startButton.addActionListener(this); 
     quitButton.addActionListener(this); 
     pauseButton.addActionListener(this); 

     buttonPanel.add(startButton); 
     buttonPanel.add(pauseButton); 
     buttonPanel.add(quitButton); 
     frame.add(gamePanel); 
     frame.add(buttonPanel, BorderLayout.SOUTH); 

     frame.pack(); 
     frame.setVisible(true); 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       new GameLoopTest(); 
      } 
     }); 
    } 

    @Override 
    public void actionPerformed(ActionEvent e) { 
     Object s = e.getSource(); 
     if (s == startButton) { 
      running = !running; 
      if (running) { 
       startButton.setText("Stop"); 
       pauseButton.setEnabled(true); 
       runGameLoop(); 
      } else { 
       startButton.setText("Start"); 
       pauseButton.setEnabled(false); 
      } 
     } else if (s == pauseButton) { 
      paused = !paused; 
      if (paused) { 
       pauseButton.setText("Unpause"); 
      } else { 
       pauseButton.setText("Pause"); 
      } 
     } else if (s == quitButton) { 
      System.exit(0); 
     } 
    } 

    //Starts a new thread and runs the game loop in it. 
    public void runGameLoop() { 
     Thread loop = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       gameLoop(); 
      } 
     }); 
     loop.start(); 
    } 

    //Only run this in another Thread! 
    private void gameLoop() { 
     //This value would probably be stored elsewhere. 
     final double GAME_HERTZ = 30.0; 
     //Calculate how many ns each frame should take for our target game hertz. 
     final double TIME_BETWEEN_UPDATES = 1000000000/GAME_HERTZ; 
     //At the very most we will update the game this many times before a new render. 
     //If you're worried about visual hitches more than perfect timing, set this to 1. 
     final int MAX_UPDATES_BEFORE_RENDER = 5; 
     //We will need the last update time. 
     double lastUpdateTime = System.nanoTime(); 
     //Store the last time we rendered. 
     double lastRenderTime = System.nanoTime(); 

     //If we are able to get as high as this FPS, don't render again. 
     final double TARGET_FPS = 60; 
     final double TARGET_TIME_BETWEEN_RENDERS = 1000000000/TARGET_FPS; 

     //Simple way of finding FPS. 
     int lastSecondTime = (int) (lastUpdateTime/1000000000); 

     while (running) { 
      double now = System.nanoTime(); 
      int updateCount = 0; 

      if (!paused) { 
       //Do as many game updates as we need to, potentially playing catchup. 
       while (now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER) { 
        updateGame(); 
        lastUpdateTime += TIME_BETWEEN_UPDATES; 
        updateCount++; 
       } 

       //If for some reason an update takes forever, we don't want to do an insane number of catchups. 
       //If you were doing some sort of game that needed to keep EXACT time, you would get rid of this. 
       if (now - lastUpdateTime > TIME_BETWEEN_UPDATES) { 
        lastUpdateTime = now - TIME_BETWEEN_UPDATES; 
       } 

       //Render. To do so, we need to calculate interpolation for a smooth render. 
       float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime)/TIME_BETWEEN_UPDATES)); 
       drawGame(interpolation); 
       lastRenderTime = now; 

       //Update the frames we got. 
       int thisSecond = (int) (lastUpdateTime/1000000000); 
       int frameCount = gamePanel.getFrameCount(); 
       if (thisSecond > lastSecondTime) { 
        System.out.println("NEW SECOND " + thisSecond + " " + frameCount); 
        gamePanel.setFps(frameCount); 
        frameCount = 0; 
        lastSecondTime = thisSecond; 
       } 

       //Yield until it has been at least the target time between renders. This saves the CPU from hogging. 
       while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) { 
        //allow the threading system to play threads that are waiting to run. 
        Thread.yield(); 

        //This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it. 
        //You can remove this line and it will still work (better), your CPU just climbs on certain OSes. 
        //FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this. 
        //On my OS it does not unpuase the game if i take this away 
        try { 
         Thread.sleep(1); 
        } catch (Exception e) { 
        } 

        now = System.nanoTime(); 
       } 
      } 
     } 
    } 

    private void updateGame() { 
     gamePanel.update(); 
    } 

    private void drawGame(float interpolation) { 
     gamePanel.setInterpolation(interpolation); 

     SwingUtilities.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       gamePanel.repaint(); 
      } 
     }); 
    } 
} 

class GamePanel extends JPanel { 

    float interpolation; 
    float ballX, ballY, lastBallX, lastBallY; 
    int ballWidth, ballHeight; 
    float ballXVel, ballYVel; 
    float ballSpeed; 
    int lastDrawX, lastDrawY; 
    private int frameCount = 0; 
    private int fps = 0; 
    int width, height; 

    public GamePanel(int width, int height) { 
     super(true); 
     ballX = lastBallX = 100; 
     ballY = lastBallY = 100; 
     ballWidth = 25; 
     ballHeight = 25; 
     ballSpeed = 25; 
     ballXVel = (float) Math.random() * ballSpeed * 2 - ballSpeed; 
     ballYVel = (float) Math.random() * ballSpeed * 2 - ballSpeed; 
     this.width = width; 
     this.height = height; 
    } 

    public void setInterpolation(float interp) { 
     interpolation = interp; 
    } 

    public void update() { 
     lastBallX = ballX; 
     lastBallY = ballY; 

     ballX += ballXVel; 
     ballY += ballYVel; 

     if (ballX + ballWidth/2 >= getWidth()) { 
      ballXVel *= -1; 
      ballX = getWidth() - ballWidth/2; 
      ballYVel = (float) Math.random() * ballSpeed * 2 - ballSpeed; 
     } else if (ballX - ballWidth/2 <= 0) { 
      ballXVel *= -1; 
      ballX = ballWidth/2; 
     } 

     if (ballY + ballHeight/2 >= getHeight()) { 
      ballYVel *= -1; 
      ballY = getHeight() - ballHeight/2; 
      ballXVel = (float) Math.random() * ballSpeed * 2 - ballSpeed; 
     } else if (ballY - ballHeight/2 <= 0) { 
      ballYVel *= -1; 
      ballY = ballHeight/2; 
     } 
    } 

    public int getFrameCount() { 
     return frameCount; 
    } 

    public void setFrameCount(int frameCount) { 
     this.frameCount = frameCount; 
    } 

    void setFps(int fps) { 
     this.fps = fps; 
    } 
    private final static RenderingHints textRenderHints = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 
    private final static RenderingHints imageRenderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 
    private final static RenderingHints colorRenderHints = new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); 
    private final static RenderingHints interpolationRenderHints = new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 
    private final static RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 

    public void applyRenderHints(Graphics2D g2d) { 
     g2d.setRenderingHints(textRenderHints); 
     g2d.setRenderingHints(imageRenderHints); 
     g2d.setRenderingHints(colorRenderHints); 
     g2d.setRenderingHints(interpolationRenderHints); 
     g2d.setRenderingHints(renderHints); 
    } 

    @Override 
    public void paintComponent(Graphics g) { 
     super.paintComponent(g); 

     Graphics2D g2d = (Graphics2D) g; 

     //applys effects like anti alising for images and tetx, as well as sets the renderinf value to quality etc 
     applyRenderHints(g2d); 

     g2d.setColor(Color.RED); 
     int drawX = (int) ((ballX - lastBallX) + lastBallX - ballWidth/2); 
     int drawY = (int) ((ballY - lastBallY) + lastBallY - ballHeight/2); 
     g2d.fillOval(drawX, drawY, ballWidth, ballHeight); 

     lastDrawX = drawX; 
     lastDrawY = drawY; 

     g2d.setColor(Color.BLACK); 
     g2d.drawString("FPS: " + fps, 5, 10); 

     frameCount++; 
    } 

    @Override 
    public Dimension getPreferredSize() { 
     return new Dimension(width, height); 
    } 
} 

Référence:

+1

Je ne peux pas croire combien de temps je viens de regarder ce point rouge impressionnant ... +1 – moeTi

+0

@FakeMan voir la dernière mise à jour, il devrait fonctionner comme un charme –

4

Swing est très bien pour des jeux simples, mais si vous tenez vraiment à la performance de redessiner, vous devriez probablement jeter un oeil à l'un des cadres basés sur OpenGL. Exemples:

En particulier, si vous voulez faire des effets plus complexes (beaucoup de couleurs, ombrage, effets de transparence par exemple) alors vous aurez probablement besoin d'OpenGL.

+0

+1 oui ce sont quelques cadres agréables –

+0

slick2d a été déplacé à http://slick.ninjacave.com – ajb