所以,我现在已经有一段时间在2d RPG上工作,我似乎无法解决这个问题。由于未知原因,图形似乎每隔几秒就会“跳跃”或断断续续。这变得非常烦人,因为我不知道是什么导致它。
这是我写的一个非常基本的程序,它只有一个红色方块,从屏幕的一侧移动到另一侧。即使在这个非常基本的程序中,广场仍然会每隔几个更新一次,我真的无法理解我的生活。
public class Main extends JPanel {
int x=0, y=0;
public JFrame window = new JFrame("Window");
public Main(){
window.setSize(1000, 500);
window.setVisible(true);
window.add(this);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.red);
g.fillRect(x, y, 500, 500);
x+=3;
if(x>900){
x=0;
}
}
public void start(){
while(true){
repaint();
try {
Thread.sleep(16);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args){
Main game = new Main();
game.start();
}
}
如果您运行该课程,您将以图形方式查看问题所在。显然我的游戏由更多的类组成,并且比这复杂得多,但同样的原则适用。如果任何人对我的问题有任何见解,我很乐意听到。提前致谢。
更新
以下是我的两个主要课程:
主类:
包com.ultimatum.Main;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import com.ultimatum.Mangers.ImageLoader;
import com.ultimatum.Mangers.KeyStates;
import com.ultimatum.Mangers.ScreenUpdater;
import com.ultimatum.Mangers.UserInput;
@SuppressWarnings("serial")
public class Ultimatum extends JPanel {
/**
* @param x This is the start width of the screen and can be adjusted manually, but will not go any lower than this integer
* @param y This is the start height of the screen and can be adjusted manually, but will not go any lower than this integer
* @param contentPlaneX This is how much the width of the content plane is (Frame-Borders)
* @param contentPlaneY This is how much the height of the content plane is (Frame-Borders)
*/
public int x=850, y=610, contentPlaneX, contentPlaneY, middleX, middleY, tileSize=90;
public Dimension minimumSize = new Dimension(x, y);
public JFrame window = new JFrame("Ultimatum");//This makes the JFrame for the game
public KeyStates keyStates = new KeyStates();
public UserInput input = new UserInput(keyStates);
public ImageLoader imageLoader = new ImageLoader();
public static Ultimatum ultimatum;//Makes the object of this class
public static ScreenUpdater su;//This is creating the object that is going to be making changes to the screen. For example, the animation.
private BufferedImage screenImage;
public boolean isWindowInFullscreenMode;
private boolean imagesLoaded;
public void initializeUltimatum(){
toWindowedMode();
addMouseListener(input);
addMouseMotionListener(input);
contentPlaneX=window.getContentPane().getWidth();
contentPlaneY=window.getContentPane().getHeight();
middleX=(int)contentPlaneX/2;
middleY=(int)contentPlaneY/2;
su = new ScreenUpdater(ultimatum, keyStates, imageLoader, "Test", tileSize);
imageLoader.loadImages();
}
public void toFullscreenMode(){
window.dispose();
window.setUndecorated(true);
window.setVisible(true);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setBounds(0,0,Toolkit.getDefaultToolkit().getScreenSize().width,Toolkit.getDefaultToolkit().getScreenSize().height);
addListenersAndClassToWindow();
isWindowInFullscreenMode=true;
}
public void toWindowedMode(){
window.dispose();
window.setUndecorated(false);
window.setSize(x,y);
window.setMinimumSize(minimumSize);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
window.setLocationRelativeTo(null);
addListenersAndClassToWindow();
isWindowInFullscreenMode=false;
}
public void addListenersAndClassToWindow(){
window.add(ultimatum);//This connects paintComponent and the frame to this class
window.addKeyListener(input);
}
public void paintComponent(Graphics g){
if(imagesLoaded){
super.paintComponent(g);
//su.updateScreen(g);
g.drawImage(screenImage, 0, 0, contentPlaneX, contentPlaneY, null);
}else imagesLoaded = true;
}
public void update(){
screenImage = su.updateScreen();
}
/**
* This main class sets up the program. The while loop that keeps the game running is also contained inside this class. Most of this class is easily
* readable so i'm not going to comment that much.
*/
public static void main(String[] args){
ultimatum = new Ultimatum();
ultimatum.initializeUltimatum();
final int FPS=60, TARGET_TIME=1000/FPS;
long start, elapsed, wait;
while(true){//This loops purpose is to keep the game running smooth on all computers
start = System.nanoTime();
ultimatum.update();
ultimatum.repaint();//This calls the paintComponent method
elapsed = System.nanoTime() - start;
wait = TARGET_TIME-elapsed/1000000;
if(wait<0) wait = TARGET_TIME;
try{//Catches the error in case the tries to give an error (which it won't)
Thread.sleep(wait);//This is how long it waits it till the screen gets repainted
}catch(Exception e){
e.printStackTrace();
}
}
}
}
屏幕更新程序:
package com.ultimatum.Mangers;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import com.ultimatum.Engine.BuildingGenerator;
import com.ultimatum.Engine.TextBoxGenerator;
import com.ultimatum.Entities.Character.Player;
import com.ultimatum.Gamestates.Buildings.HealingCenter;
import com.ultimatum.Gamestates.Menus.EscapeScreen;
import com.ultimatum.Gamestates.Menus.StartScreen;
import com.ultimatum.Gamestates.Menus.TitleScreen;
import com.ultimatum.Gamestates.Routes.RouteSuperClass;
import com.ultimatum.Gamestates.Towns.TownSuperClass;
import com.ultimatum.Main.Ultimatum;
public class ScreenUpdater {
public Ultimatum ul;
public Resizer rs;//This is the object that captures the resize in two integers
public KeyStates ks;
public ImageLoader loader;
public Fader fader;
public TextBoxGenerator textBox;
public Initializer initer;
public TileMap tm;
public Player p;
public BuildingGenerator bg;
//Menus
public TitleScreen titleScreen;
public StartScreen startScreen;
public EscapeScreen escScreen;
//Towns
public TownSuperClass towns;
//Routes
public RouteSuperClass routes;
//Buildings
public HealingCenter healingCenter;
public final int TITLE_SCREEN=0, START_SCREEN=TITLE_SCREEN+1, LOAD=START_SCREEN+1, TOWN_ONE=LOAD+1,
ROUTE_ONE=TOWN_ONE+1, HEALING_CENTER=ROUTE_ONE+1, ESC_MENU=HEALING_CENTER+1;
public int screenNo = TITLE_SCREEN;
public int prevScreen=0;
public boolean prevMenuState, menuState;//These variables are for the checkEsc method
public boolean isMouseVisible=true, prevIsMouseVisible;//Simple boolean for setting the mouse from visible to invisible and vice versa
public ScreenUpdater(Ultimatum ultimatum, KeyStates keyStates, ImageLoader imageloader, String location,
int tileSize){
ul = ultimatum;
ks = keyStates;
loader = imageloader;
fader = new Fader(ul, this);
textBox = new TextBoxGenerator(loader, ks, ul);
initer = new Initializer(fader, textBox);
fader.sendIniterData(initer);
p = new Player(ul, fader, loader, ks, initer, this);
fader.sendPlayerData(p);
tm = new TileMap(tileSize, loader, p);
fader.sendTileMapData(tm);
rs = new Resizer(ul, p);
bg = new BuildingGenerator(ul, p, loader, tm);
//Below are the game states being loaded
//Menus
titleScreen = new TitleScreen(ul, this, loader, ks, fader);
startScreen = new StartScreen(ul, this, fader, loader, ks, textBox);
escScreen = new EscapeScreen(ul, fader, loader, ks);
rs.sendEscapeScreenData(escScreen);
//Towns
towns = new TownSuperClass(p, fader, bg, tm, this);
//Routes
routes = new RouteSuperClass(p, fader, bg, tm, this);
//Buildings
healingCenter = new HealingCenter(ul, fader, loader, ks, textBox);
}
public void clearScreen(Graphics g){
g.setColor(Color.black);
g.fillRect(0, 0, ul.contentPlaneX, ul.contentPlaneY);
}
public void checkEsc(Graphics g){
if(ks.escReleased&&screenNo>LOAD&&!fader.fadingOut&&fader.fadingIn){
if(screenNo<HEALING_CENTER&&!p.isMoving){
menuState=true;
prevScreen=screenNo;
}
else if(screenNo==ESC_MENU) menuState=false;
}
if(prevMenuState!=menuState){
int toScreen;
boolean mouseVisiblity;
if(menuState){
toScreen=ESC_MENU;
mouseVisiblity=true;
}
else{
toScreen=prevScreen;
mouseVisiblity=false;
}
fader.FadeOut(g, 255, toScreen, false, "", 0, 0, false, 0, mouseVisiblity);//The zeros don't matter because the boolean is set to false
if(!fader.fadingOut){
prevMenuState=menuState;
initer.initFader();
}
}
}
public void checkForF11(){
if(ks.isF11PressedThenReleased){
if(ul.isWindowInFullscreenMode) ul.toWindowedMode();
else ul.toFullscreenMode();
}
}
public void setMouseVisible(){
ul.window.setCursor(ul.window.getToolkit().createCustomCursor(loader.cursor, new Point(0, 0),"Visible"));
}
public void setMouseInvisble(){
ul.window.setCursor(ul.window.getToolkit().createCustomCursor(new BufferedImage(3, 3, BufferedImage.TYPE_INT_ARGB), new Point(0, 0),"Clear"));
}
public void checkMouseState(){
if(isMouseVisible!=prevIsMouseVisible){
if(isMouseVisible) setMouseVisible();
else setMouseInvisble();
prevIsMouseVisible=isMouseVisible;
}
}
public BufferedImage updateScreen(){
BufferedImage screenImage = new BufferedImage(ul.contentPlaneX, ul.contentPlaneY, BufferedImage.TYPE_INT_ARGB);
Graphics2D screenGraphics = screenImage.createGraphics();
Color oldColor = screenGraphics.getColor();
screenGraphics.setPaint(Color.white);
screenGraphics.fillRect(0, 0, ul.contentPlaneX, ul.contentPlaneY);
screenGraphics.setColor(oldColor);
checkForF11();
clearScreen(screenGraphics);
switch(screenNo){
case TITLE_SCREEN:
titleScreen.titleScreen(screenGraphics);
break;
case START_SCREEN:
startScreen.startScreen(screenGraphics);
break;
case TOWN_ONE:
towns.townOne(screenGraphics);
break;
case ROUTE_ONE:
routes.routeOne(screenGraphics);
break;
case HEALING_CENTER:
healingCenter.healingCenter(screenGraphics);
break;
case ESC_MENU:
escScreen.escapeScreen(screenGraphics);
break;
}
checkEsc(screenGraphics);
rs.checkForResize();
ks.update();
checkMouseState();
//g.drawImage(screenImage, 0, 0, ul.contentPlaneX, ul.contentPlaneY, null);
//screenGraphics.dispose();
return screenImage;
}
}
答案 0 :(得分:4)
不要更新paintComponent
方法中的状态,绘画可能由于任何数量原因而发生,其中许多原因是您未启动或将收到通知。相反,只应通过“主循环”更新状态
有关绘画如何在Swing
中工作的详细信息,请参阅Painting in AWT and Swing<强>更新强>
基于Timer
的解决方案......
该示例允许您为1-10,000个精灵设置动画,每个精灵独立移动和旋转。显然,我没有碰撞检测,但动画整体运作良好
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
PaintPane pane = new PaintPane();
JSlider slider = new JSlider(1, 10000);
slider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
try {
pane.setQuantity(slider.getValue());
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
slider.setValue(1);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane);
frame.add(slider, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class PaintPane extends JPanel {
private static final int SPOOL_DELTA = 100;
private List<Sprite> pool;
private List<Sprite> sprites;
private int quantity;
public PaintPane() {
try {
BufferedImage img = ImageIO.read(getClass().getResource("/resources/Pony.png"));
pool = new ArrayList<>(128);
sprites = new ArrayList<>(128);
Timer timer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (sprites.size() < quantity) {
List<Sprite> toAdd = new ArrayList<>(SPOOL_DELTA);
int required = quantity - sprites.size();
if (pool.isEmpty()) {
for (int index = 0; index < Math.min(SPOOL_DELTA, required); index++) {
int x = (int)(Math.random() * getWidth());
int y = (int)(Math.random() * getHeight());
toAdd.add(new Sprite(img, new Point(x, y)));
}
} else {
toAdd.addAll(pool.subList(0, Math.min(SPOOL_DELTA, pool.size())));
pool.removeAll(toAdd);
}
sprites.addAll(toAdd);
} else if (sprites.size() > quantity) {
List<Sprite> toRemove = new ArrayList<>(SPOOL_DELTA);
int required = sprites.size() - quantity;
if (sprites.size() > required) {
toRemove.addAll(sprites.subList(0, Math.min(SPOOL_DELTA, required)));
sprites.removeAll(toRemove);
pool.addAll(toRemove);
}
}
for (Sprite sprite : sprites) {
sprite.update(getSize());
}
repaint();
}
});
timer.start();
} catch (IOException ex) {
ex.printStackTrace();
}
setFont(getFont().deriveFont(Font.BOLD, 18f));
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (Sprite sprite : sprites) {
sprite.draw(g2d, this);
}
String text = NumberFormat.getNumberInstance().format(sprites.size());
FontMetrics fm = g2d.getFontMetrics();
int x = getWidth() - fm.stringWidth(text);
int y = (getHeight() - fm.getHeight()) + fm.getAscent();
g2d.drawString(text, x, y);
g2d.dispose();
}
public void setQuantity(int value) throws IOException {
this.quantity = value;
}
}
public static class Sprite {
private BufferedImage img;
private Point location;
private double angle;
private Point delta;
private double angleDelta;
public Sprite(BufferedImage cache, Point location) {
img = cache;
this.location = new Point(location);
delta = new Point(rnd(), rnd());
while (angleDelta == 0) {
angleDelta = (Math.random() * 5) - 2.5;
}
}
protected int rnd() {
int value = 0;
while (value == 0) {
value = (int) (Math.random() * 9) - 4;
}
return value;
}
public void update(Dimension size) {
location.x += delta.x;
location.y += delta.y;
if (location.x < 0) {
location.x = 0;
delta.x *= -1;
}
if (location.x + img.getWidth() > size.width) {
location.x = size.width - img.getWidth();
delta.x *= -1;
}
if (location.y < 0) {
location.y = 0;
delta.y *= -1;
}
if (location.y + img.getHeight() > size.height) {
location.y = size.height - img.getHeight();
delta.y *= -1;
}
angle += angleDelta;
}
public void draw(Graphics2D g2d, JComponent parent) {
Graphics2D g = (Graphics2D) g2d.create();
AffineTransform at = AffineTransform.getTranslateInstance(location.x, location.y);
at.rotate(Math.toRadians(angle), img.getWidth() / 2, img.getHeight() / 2);
g.transform(at);
g.drawImage(img, 0, 0, parent);
g.dispose();
}
}
}
您还可以使用基于“时间”的动画,而不是基于线性的动画,例如
如果你真的很勇敢,Moving JLabel to other JLabels - GUI和Move image in a spiral fashion in java这些是基于关键帧的动画(基于时间)的例子
<强>更新强>
这是对使用基于时间的动画的问题的原始发布代码的更新,并为对象添加了一些旋转(以及一些其他图形更新)。
你会注意到我在更新或绘制形状的关键点周围使用ReentrantLock
,这可以防止可能的竞争条件或脏读/写发生
以下是10,5,2和1秒持续时间的相同动画
我注意到的一点是,更新范围(即窗口)越小,动画效果越好,因此您可以考虑使用类似repaint(Rectangle)
的内容来减少组件尝试更新的区域数量< / p>
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Main extends JPanel {
double x = 0, y = 0;
private Rectangle2D shape;
private double angel = 0;
private ReentrantLock updateLock = new ReentrantLock();
public JFrame window = new JFrame("Window");
public Main() {
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setSize(1000, 500);
window.add(this);
window.setVisible(true);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.setColor(Color.red);
updateLock.lock();
try {
g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(angel),
shape.getCenterX(),
shape.getCenterY()));
g2d.fill(shape);
} finally {
updateLock.unlock();
}
g2d.dispose();
}
public void start() {
shape = new Rectangle2D.Double(x, y, 50, 50);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
long startTime = System.nanoTime();
long runTime = TimeUnit.NANOSECONDS.convert(10, TimeUnit.SECONDS);
System.out.println(runTime);
double rotateFrom = 0;
double rotateTo = 720;
while (true) {
long now = System.nanoTime();
long diff = now - startTime;
double progress = diff / (double) runTime;
if (progress > 1.0d) {
progress = 0d;
startTime = System.nanoTime();
}
x = (getWidth() * progress);
updateLock.lock();
try {
angel = rotateFrom + ((rotateTo - rotateFrom) * progress);
shape.setRect(x, y, 50, 50);
} finally {
updateLock.unlock();
}
repaint();
try {
Thread.sleep(8);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.setDaemon(true);
t.start();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Main game = new Main();
game.start();
}
});
}
}