Advertisement
George_E_2

GameLoop (bouncing ball example)

Oct 19th, 2019
304
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 11.18 KB | None | 0 0
  1. // ###################### App.java ######################
  2. package app;
  3.  
  4.  
  5. public class App {
  6.     public static void main(String[] args) throws Exception {
  7.         Window window = new Window();
  8.         System.out.println(window);
  9.     }
  10. }
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17. // ###################### Window.java ######################
  18. package app;
  19.  
  20. import java.awt.BorderLayout;
  21. import java.awt.Container;
  22. import java.awt.GridLayout;
  23. import java.awt.event.ActionEvent;
  24. import java.awt.event.ActionListener;
  25.  
  26. import javax.swing.JButton;
  27. import javax.swing.JFrame;
  28. import javax.swing.JPanel;
  29.  
  30. import app.game.logic.Game;
  31. import app.game.loop.GameLoop;
  32.  
  33.  
  34. public class Window extends JFrame implements ActionListener {
  35.  
  36.     private static final long serialVersionUID = -6500357792634135845L;
  37.    
  38.     private GameLoop gameLoop = new GameLoop(new Game());
  39.     private JButton startButton = new JButton("Start");
  40.     private JButton quitButton = new JButton("Quit");
  41.    
  42.  
  43.     public Window() {
  44.         super("Game Title");
  45.         setVisible(true);
  46.         setSize(500, 500);
  47.         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  48.  
  49.         JPanel p = new JPanel();
  50.         p.setLayout(new GridLayout());
  51.         p.add(startButton);
  52.         p.add(quitButton);
  53.         startButton.addActionListener(this);
  54.         quitButton.addActionListener(this);
  55.  
  56.         Container cp = getContentPane();
  57.         cp.setLayout(new BorderLayout());
  58.         cp.add(gameLoop.gamePanel, BorderLayout.CENTER);
  59.         cp.add(p, BorderLayout.SOUTH);
  60.     }
  61.    
  62.     public void actionPerformed(ActionEvent e) {
  63.         Object s = e.getSource();
  64.  
  65.         if (s == startButton) {
  66.             gameLoop.toggleRunning();
  67.             if (gameLoop.isRunning) {
  68.                 startButton.setText("Stop");
  69.                 gameLoop.runGameLoop();
  70.             } else {
  71.                 startButton.setText("Start");
  72.             }
  73.         } else if (s == quitButton) {
  74.             System.exit(0);
  75.         }
  76.     }
  77. }
  78.  
  79.  
  80.  
  81.  
  82.  
  83.  
  84. // ###################### GameLoop.java ######################
  85. package app.game.loop;
  86.  
  87.  
  88. // The game loop controls all the timing of the game. The
  89. // game refreshes at 30Hz and the frames refresh at 60Hz.
  90. public class GameLoop {
  91.  
  92.     // Holds the game's details (which links to its logic).
  93.     final public app.game.loop.GamePanel gamePanel;
  94.     // Running state of the game.
  95.     public boolean isRunning = false;
  96.  
  97.  
  98.     public GameLoop(app.game.loop.GamePanel game) {
  99.         this.gamePanel = game;
  100.     }
  101.    
  102.  
  103.     // Turn the game loop on or off by toggling.
  104.     public void toggleRunning() {
  105.         isRunning = !isRunning;
  106.     }
  107.    
  108.     // Starts a game loop on a new thread
  109.     public void runGameLoop() {
  110.         Thread loop = new Thread() {
  111.             public void run() {
  112.                 gameLoop();
  113.             }
  114.         };
  115.         loop.start();
  116.     }
  117.    
  118.     // Main game loop
  119.     private void gameLoop() {
  120.         // Number of nanoseconds in a second (1 billion).
  121.         final int NANOSECONDS_IN_SECOND = 1_000_000_000;
  122.         // Allocated frame time, in nanoseconds (ns).
  123.         final double TIME_BETWEEN_UPDATES = NANOSECONDS_IN_SECOND / 30.0;
  124.         // Maximum number of times to update the game before a new render.
  125.         final int MAX_UPDATES_BEFORE_RENDER = 5;
  126.  
  127.         // Time since last frame update.
  128.         double lastUpdateTime = System.nanoTime();
  129.         // Last time the frame was rendered.
  130.         double lastRenderTime = System.nanoTime();
  131.        
  132.         // If the target FPS is reached, don't render again.
  133.         final double TARGET_FPS = 60;
  134.         final double TARGET_TIME_BETWEEN_RENDERS = NANOSECONDS_IN_SECOND / TARGET_FPS;
  135.        
  136.         // Last second the FPS was updated. Used to calculate the FPS.
  137.         int lastSecondTime = (int) (lastUpdateTime / NANOSECONDS_IN_SECOND);
  138.        
  139.         while (isRunning) {
  140.             double now = System.nanoTime();
  141.             int updateCount = 0;
  142.            
  143.             // As long as it's been at least as long as the set frame duration
  144.             // but still able to update again, try update the game again. This
  145.             // can be used to catch up on game updates.
  146.             while (now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER) {
  147.                 updateGame();
  148.                 lastUpdateTime += TIME_BETWEEN_UPDATES;
  149.                 updateCount++;
  150.             }
  151.  
  152.             // If for some reason a game update takes too long, we don't want the
  153.             // frames to catch up. This is used to skip a frame if needed.
  154.             if ( now - lastUpdateTime > TIME_BETWEEN_UPDATES) {
  155.                 lastUpdateTime = now - TIME_BETWEEN_UPDATES;
  156.             }
  157.        
  158.             // To make a smooth render, interpolation is used. This means that
  159.             // the object appears to move at a more constant rate, as the
  160.             // position is determined by how far into a frame the rendering is.
  161.             float proportion = (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES);
  162.             float interpolation = Math.min(1.0f, proportion);
  163.             drawGame(interpolation);
  164.             lastRenderTime = now;
  165.        
  166.             // Update the FPS we got with the frames counted over the second.
  167.             int thisSecond = (int) (lastUpdateTime / NANOSECONDS_IN_SECOND);
  168.             if (thisSecond > lastSecondTime) {
  169.                 gamePanel.fps = gamePanel.frameCount;
  170.                 gamePanel.frameCount = 0;
  171.                 lastSecondTime = thisSecond;
  172.             }
  173.        
  174.             // Yield until it has been at least the target time between renders. This saves the CPU from hogging.
  175.             while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
  176.                 Thread.yield();
  177.                
  178.                 // This stops the app from consuming all the CPU. It makes
  179.                 // this slightly less accurate, but it is worth it.
  180.                 try {Thread.sleep(1);} catch(Exception e) {}
  181.                
  182.                 now = System.nanoTime();
  183.             }
  184.         }
  185.     }
  186.    
  187.     // Handles game updates.
  188.     private void updateGame() {
  189.         gamePanel.update();
  190.     }
  191.    
  192.     // Set the interpolation value, and then render the game graphics.
  193.     private void drawGame(float interpolation) {
  194.         gamePanel.interpolation = interpolation;
  195.         gamePanel.repaint();
  196.     }
  197. }
  198.  
  199.  
  200.  
  201.  
  202.  
  203.  
  204. // ###################### GamePanel.java ######################
  205. package app.game.loop;
  206.  
  207. import java.awt.Color;
  208. import java.awt.Graphics;
  209. import java.awt.geom.Point2D;
  210.  
  211. import javax.swing.JPanel;
  212.  
  213.  
  214. // Subclass to add properties and methods. This will hold the main game logic.
  215. public class GamePanel extends JPanel {
  216.  
  217.     private static final long serialVersionUID = 6503338914360911369L;
  218.  
  219.     // Whether the FPS is shown.
  220.     public boolean isShowingFPS = true;
  221.  
  222.     // Value of interpolation, as a proportion from `0.0` to `1.0`.
  223.     float interpolation;
  224.     // Number of frames so far this second.
  225.     int frameCount;
  226.     // Current FPS the game is running at.
  227.     int fps;
  228.  
  229.  
  230.     // Calculates an interpolated point. This takes in the original point
  231.     // and the new point to calculate the point in between.
  232.     public Point2D.Double interpolatedPoint(Point2D.Double fromPoint, Point2D.Double toPoint) {
  233.         double xp = fromPoint.x + (toPoint.x - fromPoint.x) * interpolation;
  234.         double yp = fromPoint.y + (toPoint.y - fromPoint.y) * interpolation;
  235.         return new Point2D.Double(xp, yp);
  236.     }
  237.     // Calculates an interpolated point. This takes in the original point
  238.     // and the delta x and y to calculate the point in between.
  239.     public Point2D.Double interpolatedPoint(Point2D.Double fromPoint, double dx, double dy) {
  240.         return interpolatedPoint(fromPoint, new Point2D.Double(fromPoint.x + dx, fromPoint.y + dy));
  241.     }
  242.  
  243.     // Creates a `Point2D` object.
  244.     public Point2D.Double createPoint(double x, double y) {
  245.         return new Point2D.Double(x, y);
  246.     }
  247.  
  248.    
  249.     // Override to make a custom update. This is called every frame.
  250.     public void update() {}
  251.     // Override to write the rendering code. This is called every frame.
  252.     public void render(Graphics G) {}
  253.    
  254.  
  255.     // This method should never be called by the subclass. Refer to using
  256.     // `render()` to write rendering code.
  257.     final protected void paintComponent(Graphics g) {
  258.         g.setColor(getBackground());
  259.  
  260.         // Show FPS if wanted
  261.         if (isShowingFPS) {
  262.             g.setColor(Color.BLACK);
  263.             g.drawString("FPS: " + fps, 5, 10);
  264.         }
  265.        
  266.         // Render game graphics according to subclass's implementation.
  267.         render(g);
  268.  
  269.         // Increment the frame count, as `paintComponent()` is called once per frame.
  270.         frameCount++;
  271.     }
  272. }
  273.  
  274.  
  275.  
  276.  
  277.  
  278.  
  279. // ###################### Game.java ######################
  280. package app.game.logic;
  281.  
  282. import java.awt.Color;
  283. import java.awt.Graphics;
  284. import java.awt.geom.Point2D;
  285.  
  286. import app.game.loop.GamePanel;
  287.  
  288.  
  289. // Subclass to add properties and methods. This will hold the main game logic.
  290. public class Game extends GamePanel {
  291.  
  292.     private static final long serialVersionUID = -3768316870032287321L;
  293.  
  294.     float interpolation;
  295.     float ballX, ballY, lastBallX, lastBallY;
  296.     int ballWidth, ballHeight;
  297.     float ballXVel, ballYVel;
  298.     float ballSpeed;
  299.    
  300.     int lastDrawX, lastDrawY;
  301.    
  302.    
  303.     public Game() {
  304.         ballX = lastBallX = 100;
  305.         ballY = lastBallY = 100;
  306.         ballWidth = 25;
  307.         ballHeight = 25;
  308.         ballSpeed = 25;
  309.         ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
  310.         ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
  311.     }
  312.  
  313.    
  314.     @Override
  315.     public void update() {
  316.         super.update();
  317.  
  318.         lastBallX = ballX;
  319.         lastBallY = ballY;
  320.        
  321.         ballX += ballXVel;
  322.         ballY += ballYVel;
  323.        
  324.         if (ballX + ballWidth/2 >= getWidth()) {
  325.             ballXVel *= -1;
  326.             ballX = getWidth() - ballWidth/2;
  327.             ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
  328.         } else if (ballX - ballWidth/2 <= 0) {
  329.             ballXVel *= -1;
  330.             ballX = ballWidth/2;
  331.         }
  332.        
  333.         if (ballY + ballHeight/2 >= getHeight()) {
  334.             ballYVel *= -1;
  335.             ballY = getHeight() - ballHeight/2;
  336.             ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
  337.         } else if (ballY - ballHeight/2 <= 0) {
  338.             ballYVel *= -1;
  339.             ballY = ballHeight/2;
  340.         }
  341.     }
  342.    
  343.     @Override
  344.     public void render(Graphics g) {
  345.         super.render(g);
  346.  
  347.         g.setColor(Color.RED);
  348.         Point2D.Double lastPoint = createPoint(lastBallX, lastBallY);
  349.         Point2D.Double newPoint = createPoint(ballX, ballY);
  350.         Point2D.Double interpolatedPoint = interpolatedPoint(lastPoint, newPoint);
  351.         int drawX = (int) (interpolatedPoint.x - ballWidth/2);
  352.         int drawY = (int) (interpolatedPoint.y - ballHeight/2);
  353.         g.fillOval(drawX, drawY, ballWidth, ballHeight);
  354.        
  355.         lastDrawX = drawX;
  356.         lastDrawY = drawY;
  357.     }
  358. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement