10 Replies Latest reply: Jan 26, 2010 2:52 AM by gimbal2 RSS

    Implementing Jump the proper way

    843853
      Hello
      I am trying to make my spirit jump in a "normal" fashion. If I just do a jump it's pretty normal but when i use the arrow keys to move him in mid air, he flies all over the place. I think i am doing the threading wrong, not sure.
      Also, am i doing the the key bindings the right way?
      Here is the image needed for the main char.
      [http://i.imgur.com/WIpwY.png]

      Here is the code:
      import java.awt.Color;
      import java.awt.Graphics;
      import java.awt.event.ActionEvent;
      import java.awt.event.ActionListener;
      import java.awt.image.BufferedImage;
      import java.io.File;
      import java.io.IOException;
      
      import javax.imageio.ImageIO;
      import javax.swing.AbstractAction;
      import javax.swing.Action;
      import javax.swing.JButton;
      import javax.swing.JFrame;
      import javax.swing.JPanel;
      import javax.swing.KeyStroke;
      import javax.swing.Timer;
      import javax.swing.UIManager;
      import javax.swing.UnsupportedLookAndFeelException;
      
      public class GamePractice {
      
           final int SPEED;
           DrawingPanel mainPane;
           int xPos; //= 192;
           int yPos; //= 420;
           
           Action actionJumpKey;
      
           int xV;
           int yV;
      
           int jumpLength;
           
           boolean moveRight;
           boolean moveLeft;
           boolean jumpUp;
           
           public GamePractice(){
                SPEED = 5;
                jumpLength = 200;
           
           }
      
           public static void main(String[] args) {
                GamePractice gp = new GamePractice();
                gp.go();
      
           }
      
           public void go(){
      
                try {
                     UIManager.setLookAndFeel(
                               UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException e1) {
                     // TODO Auto-generated catch block
                     e1.printStackTrace();
                } catch (InstantiationException e1) {
                     // TODO Auto-generated catch block
                     e1.printStackTrace();
                } catch (IllegalAccessException e1) {
                     // TODO Auto-generated catch block
                     e1.printStackTrace();
                } catch (UnsupportedLookAndFeelException e1) {
                     // TODO Auto-generated catch block
                     e1.printStackTrace();
                }
      
                JFrame frame = new JFrame();
                frame.setFocusTraversalKeysEnabled(false);
                mainPane = new DrawingPanel();
                mainPane.setFocusTraversalKeysEnabled(false);
                mainPane.setDoubleBuffered(true);
                mainPane.setBackground(Color.black);
                mainPane.setOpaque(true);
                mainPane.setFocusable(true);
      
                ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
                actionJumpKey = new ActionKeys("jump");
                mainPane.getInputMap(JButton.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("pressed SPACE"),"jump");
                mainPane.getActionMap().put("jump",actionJumpKey/*new ActionKeys("jump")*/);
                
                mainPane.getInputMap(JButton.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("pressed LEFT"),"moveLeft");
                mainPane.getActionMap().put("moveLeft",new ActionKeys("moveLeft"));
           
                mainPane.getInputMap(JButton.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("pressed RIGHT"),"moveRight");
                mainPane.getActionMap().put("moveRight",new ActionKeys("moveRight"));
                //*************************************************************************************************************
                //*************************************************************************************************************
                
                mainPane.getInputMap(JButton.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released SPACE"),"stopJump");
                mainPane.getActionMap().put("stopJump",new ActionKeys("stopJump"));
      
                mainPane.getInputMap(JButton.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released LEFT"),"stopLeft");
                mainPane.getActionMap().put("stopLeft",new ActionKeys("stopLeft"));
      
                mainPane.getInputMap(JButton.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released RIGHT"),"stopRight");
                mainPane.getActionMap().put("stopRight",new ActionKeys("stopRight"));
                ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
      
                frame.add(mainPane);
                frame.setSize(900,600);
                frame.setVisible(true);
      
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      
                Timer animationTimerLoop = new Timer(0,new TimerLoop());
                animationTimerLoop.start();
      
                Thread loop = new Thread( new MainLoop());
                loop.start();
      
           }
      
      
           class TimerLoop implements ActionListener{     
                public void actionPerformed(ActionEvent arg0) {
                     mainPane.reDraw();
                }
      
           }
      
           class DrawingPanel extends JPanel{
      
                Player mainPlayer;
                BufferedImage image = null;
      
                public DrawingPanel(){
      
                     try {
                          image = ImageIO.read(new File("Super Paper Mario_128.png"));
                          
                     } catch (IOException e) {
                          e.printStackTrace();
                     }
                     mainPlayer = new Player(192, 420, image);
                }
      
                protected void paintComponent(Graphics g) {
                     super.paintComponent(g);
      
                     mainPlayer.paintImage(g);
                }
      
                private void reDraw(){
                     final int POSITION_X = mainPlayer.getX();
                     final int POSITION_Y = mainPlayer.getY();
                     final int IMAGE_W = mainPlayer.getWidth();
                     final int IMAGE_H = mainPlayer.getHeight();
                     xPos = POSITION_X;
                     yPos = POSITION_Y;
                     
                     if (POSITION_X >= 756){
                          
                          mainPlayer.setX(755);          
                     } else if (POSITION_X <= 0){
                          
                          mainPlayer.setX(1);
                     } else {
                          if (!(xV == 0 & yV == 0)){
                               mainPane.repaint(POSITION_X - 1, POSITION_Y, IMAGE_W + 2, IMAGE_H);
                               mainPlayer.setX(POSITION_X + xV);
                               mainPlayer.setY(POSITION_Y + yV);
                               mainPane.repaint(mainPlayer.getX(), mainPlayer.getY(), IMAGE_W, IMAGE_H);
                               
                          }
                          
                     }
      
                }
      
                private void paintJump(){
                     int x = 0;
                     while (x < 1 && jumpUp){
                               
                          for (int i = 1; i <= jumpLength / 2; i++){
                               
                               try {
                                    Thread.sleep(5);
                               } catch (InterruptedException e) {
                                    // TODO Auto-generated catch block
                                    e.printStackTrace();
                               }
      
                               yPos -= 1;
                               mainPlayer.setY(yPos);
                               mainPane.repaint(xPos - 1, yPos, 128 + 2, 128);
                               mainPane.repaint(mainPlayer.getX(), mainPlayer.getY(),128 ,128);
                          }
                          
                          for (int i = 1; i <= jumpLength / 2; i++){
                               try {
                                    Thread.sleep(3);
                               } catch (InterruptedException e) {
                                    // TODO Auto-generated catch block
                                    e.printStackTrace();
                               }
                               
                               yPos += 1;
                               mainPlayer.setY(yPos);
                               mainPane.repaint(xPos - 1, yPos, 128 + 2, 128);
                               mainPane.repaint(mainPlayer.getX(), mainPlayer.getY(),128 ,128);
                          }
                     
                          x++;
                          jumpUp = false;
                          actionJumpKey.setEnabled(true);
                     }
                }
                
                
           }
        • 1. Re: Implementing Jump the proper way
          843853
          rest of the code:
          class ActionKeys extends AbstractAction{
          
                    private String description;
          
                    public ActionKeys(String desC){
                         description = desC;
                    }
          
                    public void changeDescription(String chngedDes){
                         description = chngedDes;
                    }
          
                    private void update() {
                         xV = 0;
                         yV = 0;
                         if(moveLeft) xV = -SPEED;
                         if(moveRight) xV = SPEED;
                    }
          
                    public void actionPerformed(ActionEvent e) {
                         
                         if (description.equals("jump")){
                              jumpUp = true;
                              
                              actionJumpKey.setEnabled(false);
                              
                         } else if (description.equals("moveDown")){
          
                         } else if (description.equals("moveLeft")){
          
                              
                              moveLeft = true;
                              update();
                         } else if (description.equals("moveRight")){
          
                              
                              moveRight = true;
                              update();
                         } else if (description.equals("stopJump")){
                              
          
                         } else if (description.equals("stopDown")){
          
               
                         } else if (description.equals("stopLeft")){
          
                              
                              moveLeft = false;
                              update();
                         } else if (description.equals("stopRight")){
          
                              moveRight = false;
                              update();
                         }
          
                    }
          
               }
          
               class MainLoop implements Runnable{
          
                    public void run() {
                    
                         while (true){
                              mainPane.paintJump();
                              
                         }     
                         
                    }
               }
          }
          I am assuming starting a thread just for a jump simulation is bad idea and waste of resources right? Is there a better way of doing this?
          Thank you

          Edited by: oplead on Jan 5, 2010 4:53 PM

          Edited by: oplead on Jan 5, 2010 4:55 PM
          • 2. Re: Implementing Jump the proper way
            gimbal2
            Thread.sleep(5);
            Create a simple test program where you do this about a hundred times, printing out the difference between the previous timestamp and the current timestamp. I think you'll find that it is in no way what you pass to sleep(), on windows at least it will be much higher.

            You shouldn't need sleep at any time to control speed during movement actions. One correct way of dealing with movements is to lock the entire game to some update rate (I tend to update when I draw, 60 times a second); that will give the application a certain steady "pulse" that you can rely on. To get this, you would need to perform updates every 1000/60 = 17 milliseconds. If your game is not that active, you could also opt for 50 frames per second.

            Now you need to match your movement speed to this pulse so you can move EVERY pulse. To make this as accurate as possible, I would use float values to hold your current position, and when you draw to the screen translate the floats to integer values. This allows you to move 1.5 pixels per pulse for example. This allows you to go even slower than 1 pixel per pulse.

            I hope you get the idea.
            • 3. Re: Implementing Jump the proper way
              843853
              I kind of get the idea. This stuff is all new to me, I guess I should be better at it by the end of the term. Is this why games like Mario or Braid have very responsive controls? I knew something was wrong when i was trying to change speeds with threads, hehe...
              I think i will read the book everyone here recommends; "Killer Game Programming in Java".
              Thank you for your help gimbal2
              • 4. Re: Implementing Jump the proper way
                843853
                Ok so i removed the jump portion out and removed the thread. And then changed the ints to floats. I also set the delay of the swing timer to 17ms so it would repaint the screen every 17 ms. However my character's movement is not smooth at all, it's like he is taking very very small steps rather than sliding smoothly. Am I missing some point from your advice?
                • 5. Re: Implementing Jump the proper way
                  843853
                  Looks like the problem was with SwingTimer, it gives you bad FPS, the book i am reading also confirmed it. I switched to just using a thread and the picture glides smoothly most of the time.
                  • 6. Re: Implementing Jump the proper way
                    gimbal2
                    are you running on windows by any chance?

                    On windows the timing resolution, even for threads, is very poor. It could be higher than 17ms, so you get slightly jumpy updates. Not much to be done about that really, other than running on MacOS or Linux where the timer resolution is of 1 or 2ms precision. You might try to lower the FPS to 50 (update every 20ms) to see if that smooths it out a little more.
                    • 7. Re: Implementing Jump the proper way
                      843853
                      Yeah I am using windows 7, maybe that would even make it worst somehow. It's kind of weird since the character slides smoothly for couple of seconds and then you feel a little "bump" in speed. Updating every 20 ms made it worst.This book will go into more detailed though how to improve sleeping and what not.
                      Well at least this gives me an excuse to try out that Linux distro that I have been wanting to try : P or maybe even unix..

                      Thank you again for the help.
                      • 8. Re: Implementing Jump the proper way
                        gimbal2
                        If you are running Windows 7 you probably have a fast machine, so I would try to install Linux into VirtualBox if I were you; I installed the latest Gentoo Linux into it and it works flawlessly and quite fast, even on my older core2duo setup.
                        • 9. Re: Implementing Jump the proper way
                          796280
                          I'm working on a 2d platform game. I figured out a way to implement jumping and it works fairly well. It's not perfect, but good enough.

                          First, check out this thread:
                          http://stackoverflow.com/questions/98628/2d-game-physics
                          They talk a bit about gravity and jumping.

                          Here's how I implemented jumping. I've extracted the relevant code from a class. Hopefully it makes sense.

                          It doesn't involve any separate threads.
                          // this will give us a jump height of about 260 or 280 pixels... I don't know how much off the top of my head.
                          // this method doesn't allow for precise jump heights
                          public static final int INITIAL_JUMP_Y_VELOCITY = -33;
                          
                          private void jump() {
                                  // first check and see if we are already jumping
                                  if (this.isJumping) {
                                      // if we are, don't allow another jump.
                                      return;
                                  }
                                  // only allow jumping if we are on ground
                                  if (this.isOnGround()) {
                                      this.isJumping = true;
                                      this.setOnGround(false);
                                      // set the initial jump velocity
                                      this.setVy(this.getVy() + INITIAL_JUMP_Y_VELOCITY);
                                  }
                              }
                          This sets the the y velocity (which is normally 0).

                          For each frame, move() is called to update the location of the object. Here is the relevant code from that function:
                          // gravity and terminal_velocity are just arbitrary values which work well for my game.
                          // tweak the numbers to suit you
                          public static final int GRAVITY = 2; // pixels per frame
                          
                          public static final int TERMINAL_VELOCITY = GRAVITY * 12; // pixels per frame
                          
                          public void move() {
                              /* SNIP */
                              // if the object is jumping, this call to applyGravity() will eventually slow it down and it will fall
                              // in a somewhat 'smooth' fashion
                              this.applyGravity();
                              this.setY(this.getY() + this.getVy());
                              // TODO: here is where you need to insert your collision detection and response so you know when you've hit a 'floor'
                              /* SNIP */
                          }
                          
                          
                          private void applyGravity() {
                                  // make sure the object doesn't accelerate beyond our
                                  // arbitrary 'terminal velocity'
                                  if (this.getVy() < TERMINAL_VELOCITY) {
                                      this.setVy(this.getVy() + GRAVITY);
                                  }
                          }
                          Let me know if you have questions.

                          -Thok

                          Edited by: Thok on Jan 25, 2010 7:46 PM
                          • 10. Re: Implementing Jump the proper way
                            gimbal2
                            I do something similar, only my values aren't static. Jump/fall works such that there is a gravitational pull on the character - when he jumps the force is strong enough to propel him upwards, but the weight will grow with each cycle that passes, decreasing the force until it becomes negative, which will make the character fall in stead. Because it keeps increasing (up to a certain limit), the fall continues to speed up.