# Simple Game Physics & Animation (Air Hockey)

• October 31st, 2011, 08:40 AM
Spaces Allowed?
Simple Game Physics & Animation (Air Hockey)
Hey.

What I have here is a crude animation setup using labels and a timer. It is just an oblique collision test, eventually to become something similar to Air Hockey.

Every 1000/FPS miliseconds it updates the positions of the labels according to the velocities as specified by the Puck class. This class contains the data to be later used by the CollisionHandler class such as mass and CoR and also functions to update positions and velocity.

I don't suppose any function leaving 'update' is important:

Code java:

```//The Puck class is associated with a label, velocites in x and y directions are velx, vely public void update() { if (Math.abs(velx) > velrest) //velrest is an arbitrarily decided value to cease updates for low velocities { posx += velx/FPS; if (frictionEnabled) velx -= Math.signum(velx) * mu / FPS; label.setBounds((int)posx, (int)posy, width, height); }   if (Math.abs(vely) > velrest) { posy += vely/FPS; if (frictionEnabled) vely -= Math.signum(vely) * mu / FPS; label.setBounds((int)posx, (int)posy, width, height); }   }```

The CollisionHandler Class runs a continuous loop to check if any puck is in contact with any other puck and if so, makes calculations to redirect each puck's velocity.

The 'update' function of the CollisionHandler:

Code java:

```public void update() { for(int i = 0; i < counter; i++) //Counter = Total number of pucks { Puck t = plist[i]; //Array of pucks added to the Handler       /*Boundary checks omitted, inter-puck collisions:*/   for (int j = i + 1; j < counter; j++) //Checks remaining pucks { Puck t2 = plist[j]; int coll = (int)distance(t, t2) - (t.width + t2.width)/ 2; //this is where I talked about messing with distance parameters     if (coll == 0) //setting coll = 0, or defining limits like -1 < coll < 1 etc. { /*Calculations snipped, new velocities applied*/   } }   }   }```

The timer event handler just executes the aforementioned update functions for each puck and one common CollisionHandler instance.

Most of it works fine, butI have a few questions. Pardon me if some of them are generic but I'm new to this:
• Right off the bat, my collision detector is screwed up. I've tried several ways; at first I simply checked whether the distance between said pucks was less than zero and updated it as required. That caused the pucks to stick together because often (especially for high velocities) a single collision was read as 40 continuous ones.

Then I tried equating it to zero, but obviously that failed whenever the distance was not exactly that much and caused them to pass through each other most of the time.

I then made a small contact-distance limit, trying various values from 1 pixel difference to 10 but encountered the same problem of passing through half the time, and reacting too soon the other half.

I thought maybe I could set it up so as to ignore any collisions for a small time period after each collision, but that would cause it to also ignore any collisions happening between other pucks at the same time.

How should I go about doing this? I'm completely baffled.

• I'm concerned that it tends to slow down for FPS above 80 -I understand that my methods might be inefficient, but surely most modern games have to do many more complicated calculations and update a lot more in far less time - how do they manage this?

• Regarding the above problem, would it be wise/feasible to work with Event Listeners to notify the appropriate functions for contact between two pucks? I'm not sure if that'd help my case; I'm guessing they run the same way, i.e. a continuous loop to check given condition?

• For when I get around to the coding for the mouse - how do I lock the mouse from exiting the specified area, like in most games? Google led me to the Robot class, but that appears cumbersome. Is there some easier way?

• Lastly, is my particular method of animating the labels also inconvenient/inefficient? Most examples I saw on the internet ran separate threads for animating (as far as I could tell) but I don't have much experience with threads. I need to know whether to invest my time looking it up and implementing it.

Thanks.
• October 31st, 2011, 10:45 AM
KevinWorkman
Re: Simple Game Physics & Animation (Air Hockey)
Quote:

Originally Posted by Spaces Allowed?
//The Puck class is associated with a label, velocites in x and y directions are velx, vely

Why are you using a JLabel instead of simply drawing the puck to a JPanel directly? Recommended reading: Lesson: Performing Custom Painting (The Java™ Tutorials > Creating a GUI With JFC/Swing)

Quote:

Originally Posted by Spaces Allowed?
The CollisionHandler Class runs a continuous loop to check if any puck is in contact with any other puck and if so, makes calculations to redirect each puck's velocity.

A continuous loop seems like a bad idea. It should only check collision once per frame.

Quote:

Originally Posted by Spaces Allowed?
Right off the bat, my collision detector is screwed up. I've tried several ways; at first I simply checked whether the distance between said pucks was less than zero and updated it as required. That caused the pucks to stick together because often (especially for high velocities) a single collision was read as 40 continuous ones.

Then I tried equating it to zero, but obviously that failed whenever the distance was not exactly that much and caused them to pass through each other most of the time.

I then made a small contact-distance limit, trying various values from 1 pixel difference to 10 but encountered the same problem of passing through half the time, and reacting too soon the other half.

I thought maybe I could set it up so as to ignore any collisions for a small time period after each collision, but that would cause it to also ignore any collisions happening between other pucks at the same time.

How should I go about doing this? I'm completely baffled.

I can't really tell without seeing your code, and I can't really look at anything except an SSCCE- something simple, just a few lines, without anything extra.

Quote:

Originally Posted by Spaces Allowed?
I'm concerned that it tends to slow down for FPS above 80 -I understand that my methods might be inefficient, but surely most modern games have to do many more complicated calculations and update a lot more in far less time - how do they manage this?

80 fps is huge! Are you sure you need something that high? For simple games, you can usually get away with 30 fps, let alone something as high as 60 fps. And there are a ton of ways to gain efficiency, all depending on the game, the system, etc.

Quote:

Originally Posted by Spaces Allowed?
Regarding the above problem, would it be wise/feasible to work with Event Listeners to notify the appropriate functions for contact between two pucks? I'm not sure if that'd help my case; I'm guessing they run the same way, i.e. a continuous loop to check given condition?

Again, this all really depends on a lot of things. At first, keep everything on the EDT- use a Timer that updates everything periodically (30 fps or so) and calls repaint. That means you can have some kind of Event Listener dealing with the collisions. But as things get more complicated, you'll want to take more and more stuff off the EDT, making sure to handle updates and painting correctly. I honestly don't really see the point of using a handler for collisions though, to be honest.

Quote:

Originally Posted by Spaces Allowed?
For when I get around to the coding for the mouse - how do I lock the mouse from exiting the specified area, like in most games? Google led me to the Robot class, but that appears cumbersome. Is there some easier way?

The Robot class is probably the way to go. Or you could consider using a fullscreen application. But really, you should consider not doing it at all. Restricting what the user can do usually annoys the user more than it enhances the program.

Quote:

Originally Posted by Spaces Allowed?
Lastly, is my particular method of animating the labels also inconvenient/inefficient? Most examples I saw on the internet ran separate threads for animating (as far as I could tell) but I don't have much experience with threads. I need to know whether to invest my time looking it up and implementing it.

How did they use threads? Like I said, at first, keep everything on the EDT using a Swing Timer. But yeah, I don't know why you're using JLabels at all. Just do the painting yourself, and save yourself the overhead.
• October 31st, 2011, 12:48 PM
Spaces Allowed?
Re: Simple Game Physics & Animation (Air Hockey)
Quote:

Why are you using a JLabel instead of simply drawing the puck to a JPanel directly?
While I could use the experience, I need to put in images in place of pucks and labels appear to be the only option.

Quote:

A continuous loop seems like a bad idea. It should only check collision once per frame.
I did mean every frame.

Quote:

I can't really tell without seeing your code, and I can't really look at anything except an SSCCE- something simple, just a few lines, without anything extra.
Well, what I posted up there in the first post was pretty much stripped of anything else. All the pertinent code was up there too. What I need is some implementable way to check for collisions, and I just need some ideas, but my code is up there. I've snipped the calculations too (check first post), so those are just 10 lines each (and the first one has 5 almost repeated anyway).

Quote:

80 fps is huge...
I guess you're right. I did realize that it didn't matter much, and I set it to 40 FPS.

Quote:

...But really, you should consider not doing it at all...
I agree, it might seem to be an annoyance, but I want to leave paddle control to the mouse - so you can see why I wouldn't want them to be able to access all the parts of the screen. I'm leaning towards dealing with it internally now, i.e. just disable paddle movement when it leaves the area. That should work well.

Thanks a lot for your help and interest by the way. I'd appreciate it if you'd look at the coding above again now, I've stripped it to bare minimum.
• October 31st, 2011, 01:09 PM
KevinWorkman
Re: Simple Game Physics & Animation (Air Hockey)
Quote:

Originally Posted by Spaces Allowed?
While I could use the experience, I need to put in images in place of pucks and labels appear to be the only option.

Nah, there are Graphics.drawImage() functions you might want to check out.

Quote:

Originally Posted by Spaces Allowed?
Well, what I posted up there in the first post was pretty much stripped of anything else. All the pertinent code was up there too. What I need is some implementable way to check for collisions, and I just need some ideas, but my code is up there. I've snipped the calculations too (check first post), so those are just 10 lines each (and the first one has 5 almost repeated anyway).

I'd love to check it out, if you wouldn't mind throwing together an SSCCE. You've got a good length, but it should also be runnable if we just copy and paste it- just a simple program with two pucks. Maybe move one with the arrow keys and either a print statement or a color change when you detect a collision? There are a bunch of ways to do collision detection, and it really depends on your setup.

Quote:

Originally Posted by Spaces Allowed?
I agree, it might seem to be an annoyance, but I want to leave paddle control to the mouse - so you can see why I wouldn't want them to be able to access all the parts of the screen. I'm leaning towards dealing with it internally now, i.e. just disable paddle movement when it leaves the area. That should work well.

That seems reasonable. Or you could make the paddle move slower than the mouse, so it's playing catch up (that might be a fun thing to play with). So if you leave the screen and reenter the screen somewhere else, the paddle will still have to catch up to the mouse.
• October 31st, 2011, 01:49 PM
Spaces Allowed?
Re: Simple Game Physics & Animation (Air Hockey)
Quote:

Or you could make the paddle move slower than the mouse, so it's playing catch up
Wow, that sounds brilliant. I do have something similar to that already implemented (was doing a 'magnetism powerup' :P) and should be easy to implement, yet fun.

Quote:

Nah, there are Graphics.drawImage() functions you might want to check out.
Will that make much difference, as compared to moving labels around I mean? I'll still check it out I guess.

Quote:

SSCCE
About that - I use Netbeans (yeah, you hate it I know) so is it okay if I just copy that auto-generated stuff?

I'm working on making a compilable version now, will post soon.

EDIT: Also, I'm guessing you don't want it in separate classes, i.e., want a single file? It would save some time for me if I could give you the three files (with non-vital stuff omitted, ofc), and would be neater, but whatever you're more comfortable with.

EDIT2: What about the images? This is hard work :/
Could you just take a look at my code and see what we can do from there?
Just throw in the "bunch of ways" of detection. All the relevant code (to understand my 'system') is already in the first post. and it is incredibly difficult for me assembling code I do not understand (the GUI part).
• October 31st, 2011, 05:39 PM
KevinWorkman
Re: Simple Game Physics & Animation (Air Hockey)
I appreciate the work you're putting into it, but I'm not that much of a nazi! Just throw together something I can copy and paste (if it's multiple classes, that's not the end of the world). Don't worry about the images (honestly most games I write start out as a single circle drawn using Graphics.fillOval() that's controlled by the arrow keys), and just focus on collision between two pucks. Don't make it more work than it has to be!
• November 2nd, 2011, 04:48 AM
Spaces Allowed?
Re: Simple Game Physics & Animation (Air Hockey)
I don't get it - what would you do with a compilable version? My code doesn't need debugging, I just need a suggestion of how to do it. I don't see how running the actual program might help your case, but here is something you can copy and paste:

Main:
Code Java:

```import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.Timer;   /* * To change this template, choose Tools | Templates * and open the template in the editor. */     public class NewJFrame extends javax.swing.JFrame {   /** Creates new form NewJFrame */ public NewJFrame() { initComponents(); myinit(); getContentPane().setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); //myinit();   }     double speed = 1; double globalFPS = 60;   boolean magnetism = false;     Timer t = new Timer((int)(1000/(speed*globalFPS)), new ActionListener() {public void actionPerformed(ActionEvent e) {timerfunc3();}}); void timerfunc3() { ch.update();   puck1.update(); //puck2.update(); puck3.update(); puck4.update();     if (magnetism) { double dist = ch.distance(puck1, puck4); puck1.velx += 200/globalFPS * (puck4.posx - puck1.posx)/dist; puck1.vely += 200/globalFPS * (puck4.posy - puck1.posy)/dist; } }         /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() {   l1 = new javax.swing.JLabel(); l2 = new javax.swing.JLabel(); jButton1 = new javax.swing.JButton(); jButton2 = new javax.swing.JButton(); jButton3 = new javax.swing.JButton(); l3 = new javax.swing.JLabel(); l4 = new javax.swing.JLabel();   setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); setMinimumSize(new java.awt.Dimension(400, 500)); setResizable(false); addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { formMouseClicked(evt); } public void mouseEntered(java.awt.event.MouseEvent evt) { formMouseEntered(evt); } public void mousePressed(java.awt.event.MouseEvent evt) { formMousePressed(evt); } public void mouseReleased(java.awt.event.MouseEvent evt) { formMouseReleased(evt); } }); addMouseMotionListener(new java.awt.event.MouseMotionAdapter() { public void mouseMoved(java.awt.event.MouseEvent evt) { formMouseMoved(evt); } }); addKeyListener(new java.awt.event.KeyAdapter() { public void keyPressed(java.awt.event.KeyEvent evt) { formKeyPressed(evt); } public void keyReleased(java.awt.event.KeyEvent evt) { formKeyReleased(evt); } }); getContentPane().setLayout(null);   l1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/transp.png"))); // NOI18N l1.setText("jLabel1"); getContentPane().add(l1); l1.setBounds(160, 130, 40, 40);   l2.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/transp.png"))); // NOI18N l2.setText("jLabel1"); getContentPane().add(l2); l2.setBounds(340, 10, 40, 40);   jButton1.setText("Start Tmer"); jButton1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton1ActionPerformed(evt); } }); getContentPane().add(jButton1); jButton1.setBounds(40, 20, 83, 23);   jButton2.setText("Stop Timer"); jButton2.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton2ActionPerformed(evt); } }); getContentPane().add(jButton2); jButton2.setBounds(130, 20, 90, 23);   jButton3.setText("Debug"); jButton3.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton3ActionPerformed(evt); } }); getContentPane().add(jButton3); jButton3.setBounds(240, 20, 63, 23);   l3.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/30ps.png"))); // NOI18N l3.setText("jLabel1"); getContentPane().add(l3); l3.setBounds(60, 260, 30, 30);   l4.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/transp.png"))); // NOI18N l4.setText("jLabel1"); getContentPane().add(l4); l4.setBounds(160, 250, 40, 40);   pack(); }// </editor-fold>//GEN-END:initComponents     private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed // TODO add your handling code here: t.start();   }//GEN-LAST:event_jButton1ActionPerformed   private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed // TODO add your handling code here: t.stop(); }//GEN-LAST:event_jButton2ActionPerformed   private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton3ActionPerformed // TODO add your handling code here: }//GEN-LAST:event_jButton3ActionPerformed           private void formMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMousePressed magnetism = true; }//GEN-LAST:event_formMousePressed       private void formMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseReleased // TODO add your handling code here: magnetism = false; }//GEN-LAST:event_formMouseReleased   /** * @param args the command line arguments */ public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new NewJFrame().setVisible(true); } }); }   void myinit() { puck1 = new Puck(l1, 100, 0, globalFPS); puck2 = new Puck(l2, 120, 80, globalFPS); puck3 = new Puck(l3, -120, 0, globalFPS); puck4 = new Puck(l4, 0, 0, globalFPS);   puck1.frictionEnabled = false; puck2.frictionEnabled = false; puck3.frictionEnabled = false; puck4.frictionEnabled = false;   ch = new CollisionHandler(0, 0, 400, 470);   ch.add(puck1); //ch.add(puck2); ch.add(puck3); ch.add(puck4); }   // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton jButton1; private javax.swing.JButton jButton2; private javax.swing.JButton jButton3; private javax.swing.JLabel l1; private javax.swing.JLabel l2; private javax.swing.JLabel l3; private javax.swing.JLabel l4; // End of variables declaration//GEN-END:variables   Puck puck1; Puck puck2; Puck puck3; Puck puck4; CollisionHandler ch; }```

The CH class:

Code java:

```/* * To change this template, choose Tools | Templates * and open the template in the editor. */   public class CollisionHandler {   int minx; int miny;   int maxx; int maxy; int plimit = 10;   //double velrest = 0.7; //double ebound = 0.8; double epuck = 0.9;   private Puck[] plist = new Puck[10]; private int counter = 0;   public CollisionHandler(int lx, int ly, int ux, int uy) { minx = lx; miny = ly; maxx = ux; maxy = uy; }   public boolean add(Puck p) { if (counter > plimit) return false;   for (int i = 0; i < counter; i++) { if (plist[i] == p) return false; }   plist[counter] = p; counter++; return true; }   public double distance(Puck a, Puck b) { return Math.sqrt( (a.getCentreX()- b.getCentreX()) * (a.getCentreX()- b.getCentreX()) + (a.getCentreY()- b.getCentreY()) * (a.getCentreY()- b.getCentreY()) ); }   //If colls get jittery with top and left, change to: posx = minx + some_offset   public void update() { for(int i = 0; i < counter; i++) { Puck t = plist[i];   if (Math.abs(t.velx) > t.velrest) { if(t.posx > maxx - t.width) { t.posx = maxx - t.width; t.velx *= -t.ebound; }   if (t.posx < minx) { t.posx = minx; t.velx *= -t.ebound; } }   if (Math.abs(t.vely) > t.velrest) { if(t.posy > maxy - t.height) { t.posy = maxy - t.height; t.vely *= -t.ebound; }   if (t.posy < miny) { t.posy = miny; t.vely *= -t.ebound; } }   /*inter-puck collisions: TODO*/ if (counter <= 1) return; //No collisions for less than 1   for (int j = i + 1; j < counter; j++) { Puck t2 = plist[j]; int coll = (int)distance(t, t2) - (t.width + t2.width) / 2;     if (coll < 0) //&& !(t.velx < t.velrest && t2.velx < t2.velrest && t.vely < t.velrest && t2.vely < t2.velrest)) { double cos = Math.abs(t.getCentreX() - t2.getCentreX())/((t.width + t2.width) / 2); double sin = Math.abs(t.getCentreY() - t2.getCentreY())/((t.width + t2.width) / 2);   double u1 = t.velx*cos + t.vely*sin; double u2 = t2.velx*cos + t2.vely*sin;   double v1 = (t.mass*u1 + t2.mass*u2 + epuck * t2.mass * (u2 - u1) ) / (t.mass + t2.mass); double v2 = (t.mass*u1 + t2.mass*u2 + epuck * t.mass * (u1 - u2) ) / (t.mass + t2.mass);   t.velx = v1*cos + t.velx*sin*sin - t.vely*cos*sin; t.vely = v1*sin + t.vely*cos*cos - t.velx*sin*cos;   t2.velx = v2*cos + t2.velx*sin*sin - t2.vely*cos*sin; t2.vely = v2*sin + t2.vely*cos*cos - t2.velx*sin*cos;   System.out.println("Bam! " + i + j);// + distance(t, t2) + ", And width- " + (t.width + t2.width) / 2);   } }   //End outer for*/ }   } }```

And the Puck class:

Code java:

```/* * To change this template, choose Tools | Templates * and open the template in the editor. */   public class Puck {   double mass = 1;   double posx = 0; double posy = 0;   double velx; double vely; double velrest = 0.7;   double ebound = 0.9; double mu = 3; //Actually, mu*g boolean frictionEnabled = true;   double FPS;   javax.swing.JLabel label; int width = 40; int height = 40;     /*public Puck(javax.swing.JLabel a) {   }*/   public Puck(javax.swing.JLabel lbl, double vx, double vy, double f) { label = lbl;   posx = label.getX(); posy = label.getY();   width = label.getWidth(); height = label.getHeight();   velx = vx; vely = vy;   FPS = f;   }   public double getCentreX() { return posx + width/2; }   public double getCentreY() { return posy + width/2; }   public void update() { if (Math.abs(velx) > velrest) { posx += velx/FPS; if (frictionEnabled) velx -= Math.signum(velx) * mu / FPS; label.setBounds((int)posx, (int)posy, width, height); }   if (Math.abs(vely) > velrest) { posy += vely/FPS; if (frictionEnabled) vely -= Math.signum(vely) * mu / FPS; label.setBounds((int)posx, (int)posy, width, height); }   }   public void setpos(int x, int y) { posx = x; posy = y; label.setBounds((int)posx, (int)posy, width, height); } }```

Note - You will have to change the image stuff in initComponents() in the main NewJFrame class, add some random image if you want.

Also, you can set initial velocities in the myInit() function towards the end in the main NewJFrame class. (The second and third arguments while defining Pucks).