我一直在研究这个立方体的运动,然而,它的运动非常丑陋和突然,所以无论如何我能使它“平滑”和“干净”吗?
这是我的代码:
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 ();
}
}
非常感谢你的帮助! :)
答案 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 ();
}
}
那就是它!
您也可以在自己的项目中使用GameFrame
和GamePanel
。抽象真的得到了回报。不是吗?
如果您不了解游戏循环或其他任何内容,您无需担心。通过反复阅读代码,您最终会理解它。
这是一个可运行的演示,展示了如何创建平滑的动作。我建议您也考虑插值以创建平滑的动作。
您可以在创建FPS
时调整BoxPanel
值,以改变游戏的平滑因素。
运行代码,阅读,重新阅读,理解它。然后把它自己写成练习。
仅供参考,我的游戏循环使用名为Double Buffering的概念在屏幕上平滑渲染对象。
您也可以创建具有update
和draw
方法的其他对象,并可以将其调用放入自定义面板的updateGame
和renderGame
方法中,以及对象也将适当地呈现。