Java-Gaming.org    
Featured games (79)
games approved by the League of Dukes
Games in Showcase (477)
Games in Android Showcase (106)
games submitted by our members
Games in WIP (533)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
   Home   Help   Search   Login   Register   
  Show Posts
Pages: [1]
1  Game Development / Newbie & Debugging Questions / Re: Game Loops on: 2014-05-20 09:39:35
Thank you very much for your help philfrei. I've added a bit of code to account for extended delays, though I'm fairly certain that if the updates/renders take too long the animation will simply hang. I've also separated the update and render operations into their own executor processes. Changed the animation from a cube to something a bit more complex so as to create a pseudo 3d effect with a little lighting play. Includes mouse and keyboard interactivity.
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.swing.SwingUtilities;

public class GameLoop {
   
   public static final long FRAME_INTERVAL = 41;   // (milliseconds) Equivalent to 24 frames per second
  public static final long UPDATE_INTERVAL = 21;   // (milliseconds) Equivalent to 48 Hz
 
   public static final int POOL_SIZE = 3;
   
   public static long updateRepetitions;
   public static long updateCount;
   
   public static ScheduledFuture <?> updateFuture;
   public static ScheduledFuture<?> frameFuture;
   
   public static void main(String[] args) {
     
      SwingUtilities.invokeLater(new Runnable() {

         @Override
         public void run() {
           
            final GameCanvas game = new GameFrame().getCanvas();
           
            final ScheduledThreadPoolExecutor updateExecutor = new ScheduledThreadPoolExecutor(POOL_SIZE);
            final ScheduledThreadPoolExecutor frameExecutor = new ScheduledThreadPoolExecutor(POOL_SIZE);
           
            updateFuture = updateExecutor.scheduleAtFixedRate(new Runnable() {
               
               @Override
               public void run() {
                 
                  for(long i = updateRepetitions; i < 1; i++) {
                     game.update();
                  }
                  updateRepetitions = updateFuture.getDelay(TimeUnit.MILLISECONDS) / UPDATE_INTERVAL;
               }
            }, 0L, UPDATE_INTERVAL, TimeUnit.MILLISECONDS);
           
            frameFuture = frameExecutor.scheduleAtFixedRate(new Runnable(){
               
               private long priorFrameLength = 0;   // Elapsed time of prior frame's render sequence

               @Override
               public void run() {

                  if(priorFrameLength < FRAME_INTERVAL) {
                     game.render();
                     priorFrameLength = -frameFuture.getDelay(TimeUnit.MILLISECONDS);
                  } else {
                     priorFrameLength -= FRAME_INTERVAL + frameFuture.getDelay(TimeUnit.MILLISECONDS);
                  }
               }
            }, UPDATE_INTERVAL, FRAME_INTERVAL, TimeUnit.MILLISECONDS);
         }
         
      });
   }
}
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
import java.awt.GraphicsEnvironment;

import javax.swing.JComponent;
import javax.swing.JFrame;

@SuppressWarnings("serial")
public class GameFrame extends JFrame {

   private final GameCanvas canvas;
   
   public GameFrame() {
     
      super(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration());
     
      if(!getGraphicsConfiguration().getDevice().isFullScreenSupported()) {
         
         System.out.println("No full screen support.");
         System.exit(1);
      }
     
      if(!getGraphicsConfiguration().getBufferCapabilities().isPageFlipping()) {
         
         System.out.println("No page flipping support.");
         System.exit(1);
      }
     
      setUndecorated(true);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      getContentPane().add(canvas = new GameCanvas(getGraphicsConfiguration()));
      pack();
      getGraphicsConfiguration().getDevice().setFullScreenWindow(this);
      canvas.createBufferStrategy(2);
     
      getRootPane().setActionMap(canvas.getActionMap());
      getRootPane().setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, canvas.getInputMap());
     
      canvas.requestFocus();
   }
   
   public GameCanvas getCanvas() {
     
      return canvas;
   }
   
   @Override
   public void dispose() {
     
      getGraphicsConfiguration().getDevice().setFullScreenWindow(null);
      super.dispose();
   }
}
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
114  
115  
116  
117  
118  
119  
120  
121  
122  
123  
124  
125  
126  
127  
128  
129  
130  
131  
132  
133  
134  
135  
136  
137  
138  
139  
140  
141  
142  
143  
144  
145  
146  
147  
148  
149  
150  
151  
152  
153  
154  
155  
156  
157  
158  
159  
160  
161  
162  
163  
164  
165  
166  
167  
168  
169  
170  
171  
172  
173  
174  
175  
176  
177  
178  
179  
180  
181  
182  
183  
184  
185  
186  
187  
188  
189  
190  
191  
192  
193  
194  
195  
196  
197  
198  
199  
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.KeyStroke;

@SuppressWarnings("serial")
public class GameCanvas extends Canvas {
   
   private static final double MAX_THETA = Math.PI * 4.0 / 180.0;
   private double deltaTheta;
   private Point canvasCenter;
   private Sphere[] sphere = new Sphere[9];
   
   private ActionMap actionMap = new ActionMap();
   private InputMap inputMap = new InputMap();
   
   public GameCanvas(GraphicsConfiguration configuration) {
     
      super(configuration);
      setBounds(configuration.getBounds());
      setIgnoreRepaint(true);
     
      canvasCenter = new Point(getWidth() / 2, getHeight() / 2);
      for(int i = 0; i < sphere.length; i++) {
         
         sphere[i] = new Sphere(i * 360.0 / sphere.length * Math.PI / 180.0);
      }
     
      this.addMouseMotionListener(new MouseMotionListener() {

         @Override
         public void mouseDragged(MouseEvent e) {}

         @Override
         public void mouseMoved(MouseEvent ev) {
           
            deltaTheta = MAX_THETA * (canvasCenter.getY() - ev.getY()) / canvasCenter.getY();
         }
         
      });
   }
   
   public ActionMap getActionMap() {
     
      Action up = new AbstractAction("up") {

         @Override
         public void actionPerformed(ActionEvent ev) {
           
            canvasCenter.y -= 5;
         }
         
      };
     
      Action down = new AbstractAction("down") {
         
         @Override
         public void actionPerformed(ActionEvent ev) {
           
            canvasCenter.y += 5;
         }
      };
     
      Action left = new AbstractAction("left") {
         
         @Override
         public void actionPerformed(ActionEvent ev) {
           
            canvasCenter.x -= 5;
         }
      };
     
      Action right = new AbstractAction("right") {
         
         @Override
         public void actionPerformed(ActionEvent ev) {
           
            canvasCenter.x += 5;
         }
      };
     
      actionMap.put("up", up);
      actionMap.put("down", down);
      actionMap.put("left", left);
      actionMap.put("right", right);
     
      return actionMap;
   }
   
   public InputMap getInputMap() {
     
      inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "up");
      inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "down");
      inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left");
      inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right");
     
      return inputMap;
   }
   
   public void render() {
     
      Graphics2D g2d = (Graphics2D)getBufferStrategy().getDrawGraphics();
      g2d.clearRect(0, 0, getWidth(), getHeight());
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      render(g2d);
      g2d.dispose();
      getBufferStrategy().show();
      getToolkit().sync();
   }
   
   private void render(Graphics2D g2d) {
     
      for(Sphere current : sphere) {
         g2d.setPaint(current.getSpecular());
         g2d.fill(current);
      }
   }
   
   public void update() {
     
      for(Sphere current : sphere) {
         current.rotate(deltaTheta);
      }
   }
   
   @Override
   public boolean isDoubleBuffered() {
     
      return true;
   }
   
   private class Sphere extends Ellipse2D.Double {
     
      private final double diameter = 40.0;
      private double x = 0;
      private double y = 0;
      private double centerX = 0;
      private double centerY = 0;
      private double travelRadius = 180.0;
      private double theta;
     
      public Sphere(double theta) {
         
         super();
         
         this.theta = theta;
         rotate(0.0);
      }
     
      public void rotate(double deltaTheta) {
         
         theta += deltaTheta;
         
         double xcomponent = Math.cos(theta);
         double ycomponent = Math.sin(theta);
         
         centerX = xcomponent * travelRadius / 2.0 + canvasCenter.getX();
         centerY = canvasCenter.getY() - ycomponent * travelRadius;
         
         double scaledDiameter = Math.abs(xcomponent - 1.0) / 2.0;
         scaledDiameter *= diameter;
         scaledDiameter += diameter;
         
         x = centerX - scaledDiameter / 2.0;
         y = centerY - scaledDiameter / 2.0;
         
         this.setFrame(x, y, scaledDiameter, scaledDiameter);
      }
     
      public RadialGradientPaint getSpecular() {
         double scaledDiameter = Math.abs(Math.cos(theta) - 1.0) / 2.0;
         int channel = (int)(255.0 * scaledDiameter);
         scaledDiameter *= getWidth();
         scaledDiameter += getWidth();
         scaledDiameter /= 2.0;
         
         return new RadialGradientPaint(
               (float) (getX() + 0.3 * getWidth()),
               (float) (getY() + 0.3 * getHeight()),
               (float) scaledDiameter,
               new float[]{0.0f, 1.0f},
               new Color[]{new Color(channel, channel, channel), Color.BLACK});
      }
   }
}
2  Game Development / Newbie & Debugging Questions / Re: Game Loops on: 2014-05-17 21:40:40
I'm not concerned with the Timers, and I realize that inaccuracy will occur with a GUI toolkit. I don't think I stated anywhere that I was too concerned with hyper accuracy. I realize that can't or isn't readily provided by the JVM. My post is concerning the concurrency libraries. Sure there are tried and true methods (I stipulated this in my post). However, the concurrency objects provide certain convenience over managing a System nanotimer. For instance, I modded the GameLoop class as follows:
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
package loop.game.test;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javax.swing.SwingUtilities;

public class GameLoop {
   
   public static final long FRAME_INTERVAL = 41;   // Equivalent to 24 frames per second
  public static ScheduledFuture<?> future;
   public static long frameRemainingTime;
   
   public static void main(String[] args) {
     
      SwingUtilities.invokeLater(new Runnable() {

         @Override
         public void run() {
           
            final GameWindow window = new GameWindow();
           
            future = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
                  new Runnable(){
                     
                     @Override
                     public void run() {
                       
                        if(frameRemainingTime > 0) {
                           window.update();   // Perform update
                          window.repaint();
                        }
                        frameRemainingTime = FRAME_INTERVAL + future.getDelay(TimeUnit.MILLISECONDS);
                     }
                  }, 0L, FRAME_INTERVAL, TimeUnit.MILLISECONDS);
         }
         
      });
   }
}

The getDelay() method yields the result of computations that people do with System nano. This seems a bit more user-friendly is all. However, I have yet to test it against heavy computation.
3  Game Development / Newbie & Debugging Questions / Game Loops on: 2014-05-17 06:12:06
This subject has been touched on in other threads, particularly in the Articles & Tutorials sections, which I've looked through (and based my code on in fact). From what I understood, Timer (either util or Swing) is a bad idea for animation due to their lack of reliabile updating mechanisms. The seemingly "de facto" way is to use System.nanoTime() and compute your update and render time, accounting for the difference (whether fast or slow). It's a sound method, and I'm understanding it the more I read.

I have two classes, GameLoop and GameWindow. I didn't bother with a Canvas because java.awt.Window also implements a bufferstrategy and decided to forego adding an extra object/class to my code. Here is the relevant source:
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javax.swing.SwingUtilities;

public class GameLoop {
   
   public static final long FRAME_INTERVAL = 41;   // Equivalent to 24 frames per second
 
   public static void main(String[] args) {
     
      SwingUtilities.invokeLater(new Runnable() {

         @Override
         public void run() {
           
            final GameWindow window = new GameWindow();
           
            Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(
                  new Runnable(){
                     
                     @Override
                     public void run() {
                       
                        window.update();   // Perform update
                       window.repaint();
                     }
                  }, 0L, FRAME_INTERVAL, TimeUnit.MILLISECONDS);
         }
         
      });
   }
}

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

@SuppressWarnings("serial")
public class GameWindow extends Window {
   
   private int x = 0;
   
   GameWindow() throws HeadlessException {
     
      super(null, GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration());
     
      if(!getGraphicsConfiguration().getDevice().isFullScreenSupported()) {
         
         System.out.println("Your device does not support full screen exclusive mode");
         System.exit(0);
      }
     
      addWindowListener(new WindowListener() {

         @Override
         public void windowOpened(WindowEvent e) {}

         @Override
         public void windowClosing(WindowEvent e) {
           
            getGraphicsConfiguration().getDevice().setFullScreenWindow(null);
            dispose();
         }

         @Override
         public void windowClosed(WindowEvent e) {}

         @Override
         public void windowIconified(WindowEvent e) {}

         @Override
         public void windowDeiconified(WindowEvent e) {}

         @Override
         public void windowActivated(WindowEvent e) {
           
            requestFocus();
         }

         @Override
         public void windowDeactivated(WindowEvent e) {}
         
         
      });
     
      setBounds(getGraphicsConfiguration().getBounds());
      setLayout(null);
      setIgnoreRepaint(true);
      getGraphicsConfiguration().getDevice().setFullScreenWindow(this);   // Show full screen window
     createBufferStrategy(2);
   }
   
   @Override
   public void repaint() {
     
      Graphics g = getBufferStrategy().getDrawGraphics();
      g.clearRect(0, 0, getWidth(), getHeight());
      paint(g);
      g.dispose();
      getBufferStrategy().show();   // Display the new graphics onto the canvas
     getToolkit().sync();
   }
   
   @Override
   public void paint(Graphics g) {
     
      g.fillRect(x, 0, 20, 20);
   }
   
   public void update(){
     
      x += x < 500 ? 5 : -500;
   }
}

This works and all it does is paint a simple rectangle that moves along the upper portion of the screen. Nothing spectacular really, just something to help me get a grasp on thing.

My questions are the following:
Is it "wrong" to use an executor in this manner?
Is the executor a reliable timing mechanism?
As the next frame only executes once the prior frame has ended (due to the single thread scheduled execution) am I correct in assuming my frame rate is variable?
If the window.update() and window.render() finish prematurely, does the scheduler perform the equivalent waiting of Thread.sleep automatically?

The purpose of this is to develop some sort of reliable skeleton framework for animated games, that I understand and that isn't just a copy/paste of someone else's code. Use of an Executor in his fashion may obviously not be the best practice or an extremely obtuse or preposterous way of doing things as I haven't seen it elsewhere (and there's likely a reason for that). Any help or critiques about this code are welcome. Thanks for your input.
4  Games Center / WIP games, tools & toy projects / Re: Battleship Solitaire on: 2013-06-18 00:18:16
I use 7-zip on Windows which, among many other compressed formats, can extract tars. I do most of my Java coding on Linux, thus my usage of tars.
5  Games Center / WIP games, tools & toy projects / Battleship Solitaire on: 2013-06-17 01:57:40


Been working on this for some time now and it can hardly be called finished to be honest. However, it's got all the button logic working for the one puzzle it currently has hard-coded into it.

For an explanation on this game see here.

I decided to make it as my first complete Java game since it seemed fairly feasible given my skill set. Also, I really enjoy the game and I found there weren't many implementations available online.

Latest Version

Game can be executed with (once loaded press F1 for help):
1  
java -jar battle.jar


6 July 2013

Implemented keyboard control. The selected tile will have a yellow border around it. You can trigger its state with the SPACE key. Select any of the tiles on the board using the arrow keys. To flood fill a row or column use the key combinations Alt + Left/Right or Alt + Up/Down, respectively.

10 July 2013

Had a problem in Arch Linux where the hotspot of the default Xorg cursor was shifted by (+1, +1), resulting in problems when determining cursor position over a tile. Solved the discrepancy by implementing a custom Cursor object.

11 July 2013

Added a panel on the left that shows the ships and their quantities for the given puzzle.

20 July 2013

The left panel no longer solely indicates which ships are being searched for. Rather when a vessel is located it will gray out the respective vessel on the left panel. This is not to say that it also indicates that the vessel's location is correct. It solely indicates whether the player has located a ship of that length somewhere on the board.

25 July 2013

Seamless changes to improve performance and code modularity.

4 Aug 2013

Implemented a Save/Load function to facilitate the user's ability to arrive at the puzzle's solution thereby eliminating starting from scratch. The Help Dialog now includes a tab labeled Solution where a solved image of the puzzle appears.

8 Aug 2013

Expanded on the Save function to display a small image of the previously saved board on the left panel. Also added a timer to keep track of solution time.
6  Discussions / Miscellaneous Topics / Re: Best Linux Screen Recorder? on: 2013-05-09 03:27:58
ffmpeg will do the job if you know how to mess with its parameters.
Pages: [1]
 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

pw (26 views)
2014-07-24 01:59:36

Riven (25 views)
2014-07-23 21:16:32

Riven (19 views)
2014-07-23 21:07:15

Riven (22 views)
2014-07-23 20:56:16

ctomni231 (51 views)
2014-07-18 06:55:21

Zero Volt (46 views)
2014-07-17 23:47:54

danieldean (37 views)
2014-07-17 23:41:23

MustardPeter (40 views)
2014-07-16 23:30:00

Cero (56 views)
2014-07-16 00:42:17

Riven (55 views)
2014-07-14 18:02:53
HotSpot Options
by dleskov
2014-07-08 03:59:08

Java and Game Development Tutorials
by SwordsMiner
2014-06-14 00:58:24

Java and Game Development Tutorials
by SwordsMiner
2014-06-14 00:47:22

How do I start Java Game Development?
by ra4king
2014-05-17 11:13:37

HotSpot Options
by Roquen
2014-05-15 09:59:54

HotSpot Options
by Roquen
2014-05-06 15:03:10

Escape Analysis
by Roquen
2014-04-29 22:16:43

Experimental Toys
by Roquen
2014-04-28 13:24:22
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!