让这个立方体的运动更平滑?

时间:2015-04-04 11:26:35

标签: java jframe graphics2d

我一直在研究这个立方体的运动,然而,它的运动非常丑陋和突然,所以无论如何我能使它“平滑”和“干净”吗?

这是我的代码:

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

public class Main extends JPanel implements KeyListener
{
  Environment environment = new Environment ();

  Cube cube = new Cube ();

  JFrame frame = new JFrame ();

  int cubeX = cube.cube.x;
  int cubeY = cube.cube.y;

  // Paint method used to repaint the cube's location
  public void paint (Graphics g)
  {
    Graphics2D g2d = (Graphics2D) g;
    g2d.setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    environment.createBox (g2d);

    cube.createCube (g2d);
  }

  // Getting pressed keys to move cube
  @Override
  public void keyPressed (KeyEvent e)
  {
    if (e.getKeyCode () == KeyEvent.VK_UP)
    {
      try
      {
        cube.isCubeMoving = true;
        cube.moveCube ();
        Thread.sleep (10);
        frame.repaint ();
      }
      catch (InterruptedException ie)
      {
        ie.printStackTrace ();
      }
    }
    else if (e.getKeyCode () == KeyEvent.VK_DOWN)
    {
      cube.cube.y = cube.cube.y + 100;

      if (cube.cube.y > 620)
      {
        cube.cube.y = 620;
      }

      try
      {
        Thread.sleep (10);
        frame.repaint ();
      }
      catch (InterruptedException e1)
      {
        e1.printStackTrace ();
      }
    }
  }

  @Override
  public void keyReleased (KeyEvent arg0)
  {

  }

  @Override
  public void keyTyped (KeyEvent arg0)
  {

  }

  // Main method
  public static void main (String[] args) throws InterruptedException
  {
    Main m = new Main ();

    m.frame.add (m);
    m.frame.addKeyListener (m);

    m.frame.setSize (700, 1000);
    m.frame.setVisible (true);
    m.frame.setTitle ("The Cube");
    m.frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
    m.frame.setResizable (true);
    m.frame.setLocationRelativeTo (null);

    m.frame.setBackground (new Color (240, 84, 84));

    while (true)
    {
      m.frame.repaint ();
      Thread.sleep (3);
    }
  }
}

这是Cube类:

import java.awt.*;

public class Cube extends Thread
{
  public int x = 200;
  public int y = 620;

  public boolean isCubeMoving = true;
  int whereCubeStops = 440;

  Runnable r = new Runnable ()
  {
    public void run ()
    {
      while (isCubeMoving == true)
      {
        cube.setLocation (x, y -= 10);
        System.out.println (y);

        if (y == whereCubeStops)
        {
          try
          {
            isCubeMoving = false;
            cube.setLocation (x, y = 620);
            Thread.sleep (100);
          }
          catch (InterruptedException e)
          {
            e.printStackTrace ();
          }
        }

        try
        {
          Thread.sleep (10);
        }
        catch (InterruptedException e)
        {
          e.printStackTrace ();
        }
      }
    }
  };

  Rectangle cube = new Rectangle (x, y, 80, 80);

  public void createCube (Graphics2D g2d)
  {
    g2d.setColor (new Color (148, 235, 148));

    g2d.fill (cube);
  }

  public void moveCube ()
  {
    new Thread (r).start ();
  }
}

非常感谢你的帮助! :)

1 个答案:

答案 0 :(得分:0)

嗯,为此,我们必须编写一个合适的游戏循环。让我们一个接一个地开始:

这是我的GameFrame

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

public abstract class GameFrame extends JFrame
{
  private GamePanel gamePanel;

  public GameFrame (String gameTitle, GamePanel gamePanel)
  {
    super (gameTitle);

    this.gamePanel = gamePanel;

    setDefaultCloseOperation (WindowConstants.EXIT_ON_CLOSE);

    addWindowListener (new FrameListener ());

    getContentPane ().setLayout (new GridBagLayout ());
    getContentPane ().add (gamePanel);

    pack ();
    setLocationRelativeTo (null);
    setResizable (false);
    setVisible (true);
  }

  public class FrameListener extends WindowAdapter
  {
    public void windowActivated (WindowEvent event)
    {
      gamePanel.setWindowPaused (false);
    }

    public void windowDeactivated (WindowEvent event)
    {
      gamePanel.setWindowPaused (true);
    }

    public void windowDeiconified (WindowEvent event)
    {
      gamePanel.setWindowPaused (false);
    }

    public void windowIconified (WindowEvent event)
    {
      gamePanel.setWindowPaused (true);
    }

    public void windowClosing (WindowEvent event)
    {
      gamePanel.stopGame ();
    }
  }
}

它是一个抽象类,它所做的就是在其中放置GamePanel,并在初始化时使其自身可见。

这是我实现游戏循环的GamePanel

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

public abstract class GamePanel extends JPanel implements Runnable
{
  private int panelWidth;
  private int panelHeight;

  private Thread animator;

  private volatile boolean running = false;
  private volatile boolean isUserPaused = false;
  private volatile boolean isWindowPaused = false;

  private Graphics2D dbg;
  private Image dbImage = null;

  private static final int NO_DELAYS_PER_YIELD = 16;
  private static final int MAX_FRAME_SKIPS = 5;

  private Color backgroundColor;

  private long period;

  public GamePanel (int width, int height, long fps, Color backgroundColor)
  {
    this.panelWidth = width;
    this.panelHeight = height;

    this.backgroundColor = backgroundColor;

    this.period = 1000000L * (long) 1000.0 / fps;

    setBackground (backgroundColor);
    setPreferredSize (new Dimension (panelWidth, panelHeight));

    setFocusable (true);
    requestFocus ();
    readyForPause ();

    addKeyListener (new KeyAdapter ()
    {
      public void keyPressed (KeyEvent e)
      {
        consumeKeyPressed (e.getKeyCode ());
      }
    });
  }

  protected abstract void consumeKeyPressed (int keyCode);

  protected abstract void renderGame (Graphics2D graphics);

  protected abstract void updateGame ();

  @Override
  public void addNotify ()
  {
    super.addNotify ();
    startGame ();
  }

  protected void startGame ()
  {
    if (animator == null || ! running)
    {
      animator = new Thread (this);
      animator.start ();
    }
  }

  protected void stopGame ()
  {
    running = false;
  }

  private void readyForPause ()
  {
    addKeyListener (new KeyAdapter ()
    {
      public void keyPressed (KeyEvent e)
      {
        int keyCode = e.getKeyCode ();
        if ((keyCode == KeyEvent.VK_ESCAPE) || (keyCode == KeyEvent.VK_Q)
          || (keyCode == KeyEvent.VK_END) || (keyCode == KeyEvent.VK_P)
          || ((keyCode == KeyEvent.VK_C) && e.isControlDown ()))
        {
          setUserPaused (! isUserPaused);
        }
      }
    });
  }

  public void run ()
  {
    long beforeTime, afterTime, timeDiff, sleepTime;
    long overSleepTime = 0L;
    int noDelays = 0;
    long excess = 0L;

    beforeTime = System.nanoTime ();

    running = true;

    while (running)
    {
      requestFocus ();
      gameUpdate ();
      gameRender ();
      paintScreen ();

      afterTime = System.nanoTime ();

      timeDiff = afterTime - beforeTime;
      sleepTime = (period - timeDiff) - overSleepTime;

      if (sleepTime > 0)
      {
        try
        {
          Thread.sleep (sleepTime / 1000000L);
        }
        catch (InterruptedException ignored)
        {
        }

        overSleepTime = (System.nanoTime () - afterTime - sleepTime);
      }
      else
      {
        excess -= sleepTime;
        overSleepTime = 0L;

        if (++ noDelays >= NO_DELAYS_PER_YIELD)
        {
          Thread.yield ();
          noDelays = 0;
        }
      }

      beforeTime = System.nanoTime ();

      int skips = 0;

      while ((excess > period) && (skips < MAX_FRAME_SKIPS))
      {
        excess -= period;
        gameUpdate ();
        skips++;
      }
    }
    System.exit (0);
  }

  private void gameUpdate ()
  {
    if (! isUserPaused && ! isWindowPaused)
    {
      updateGame ();
    }
  }

  private void gameRender ()
  {
    if (dbImage == null)
    {
      dbImage = createImage (panelWidth, panelHeight);
      if (dbImage == null)
      {
        System.out.println ("Image is null.");
        return;
      }
      else
      {
        dbg = (Graphics2D) dbImage.getGraphics ();
      }
    }

    dbg.setColor (backgroundColor);
    dbg.fillRect (0, 0, panelWidth, panelHeight);

    renderGame (dbg);
  }

  private void paintScreen ()
  {
    Graphics2D g;

    try
    {
      g = (Graphics2D) this.getGraphics ();
      if ((g != null) && (dbImage != null))
      {
        g.drawImage (dbImage, 0, 0, null);
      }

      Toolkit.getDefaultToolkit ().sync ();

      if (g != null)
      {
        g.dispose ();
      }
    }
    catch (Exception e)
    {
      System.out.println ("Graphics context error : " + e);
    }
  }

  public void setWindowPaused (boolean isPaused)
  {
    isWindowPaused = isPaused;
  }

  public void setUserPaused (boolean isPaused)
  {
    isUserPaused = isPaused;
  }
}

这又是一个抽象类。它的摘要用于可重用性目的。你不必知道我的游戏循环的确切实现。你可以创建自己的自定义游戏面板,继承它,并实现它的抽象方法,一切都会很好。

让我们现在创建一个Box

import java.awt.*;

public class Box extends Rectangle
{
  private Color color;
  private Direction currentDirection = Direction.None;
  private int speed;

  public Box (int size, int speed, Color color)
  {
    super (size, size);

    this.speed = speed;
    this.color = color;
  }

  public void update ()
  {
    switch (currentDirection)
    {
      case None:
        break;
      case North:
        y -= speed;
        break;
      case South:
        y += speed;
        break;
      case East:
        x += speed;
        break;
      case West:
        x -= speed;
        break;
    }
  }

  public void draw (Graphics2D graphics)
  {
    graphics.setColor (color);

    graphics.fill (this);
  }

  public void setDirection (Direction direction)
  {
    currentDirection = direction;
  }
}

这里没什么不寻常的。它是一个Rectangle形状,有update方法根据它所拥有的Direction更新其状态,以及draw方法,使用graphics在屏幕上呈现它对象作为上下文。

Direction中使用的Box枚举如下所示:

public enum Direction
{
  None,
  North,
  South,
  East,
  West
}

现在是时候创建我们自己的自定义BoxPanel,它将继承自GamePanel。这是它的样子:

import java.awt.*;
import java.awt.event.KeyEvent;

public class BoxPanel extends GamePanel
{
  private Box box;

  public BoxPanel ()
  {
    super (800, 600, 60, Color.lightGray);

    box = new Box (80, 5, Color.darkGray);
  }

  @Override
  protected void consumeKeyPressed (int keyCode)
  {
    switch (keyCode)
    {
      case KeyEvent.VK_W:
        box.setDirection (Direction.North);
        break;
      case KeyEvent.VK_S:
        box.setDirection (Direction.South);
        break;
      case KeyEvent.VK_D:
        box.setDirection (Direction.East);
        break;
      case KeyEvent.VK_A:
        box.setDirection (Direction.West);
        break;
      case KeyEvent.VK_SPACE:
        box.setDirection (Direction.None);
        break;
    }
  }

  @Override
  protected void renderGame (Graphics2D graphics)
  {
    box.draw (graphics);
  }

  @Override
  protected void updateGame ()
  {
    box.update ();
  }
}

它基本上创建了一个box并实现了GamePanel的抽象方法,其中所做的只是更新框并使用适当的方法渲染框。

consumeKeyPressed完全是关于处理按键操作,我所做的只是适当地设置box的方向。

最后,我的BoxFrame将所有内容组合成一个可运行的演示:

public class BoxFrame extends GameFrame
{
  public BoxFrame ()
  {
    super ("Box Demo", new BoxPanel ());
  }

  public static void main (String[] args)
  {
    new BoxFrame ();
  }
}

那就是它!

您也可以在自己的项目中使用GameFrameGamePanel。抽象真的得到了回报。不是吗?

如果您不了解游戏循环或其他任何内容,您无需担心。通过反复阅读代码,您最终会理解它。

这是一个可运行的演示,展示了如何创建平滑的动作。我建议您也考虑插值以创建平滑的动作。

您可以在创建FPS时调整BoxPanel值,以改变游戏的平滑因素。

运行代码,阅读,重新阅读,理解它。然后把它自己写成练习。

仅供参考,我的游戏循环使用名为Double Buffering的概念在屏幕上平滑渲染对象。

您也可以创建具有updatedraw方法的其他对象,并可以将其调用放入自定义面板的updateGamerenderGame方法中,以及对象也将适当地呈现。