Java基础2d游戏动画卡顿 [英] Java basic 2d game animation stutter

查看:37
本文介绍了Java基础2d游戏动画卡顿的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以,我已经在 2d RPG 上工作了一段时间,但我似乎无法解决这个问题.出于未知原因,图形似乎每隔几秒钟就会跳跃"或断断续续.这很烦人,因为我不知道是什么原因造成的.

这是我编写的一个非常基本的程序,它只有一个从屏幕一侧移动到另一侧的红色方块.即使在这个非常基本的程序中,方块仍然每隔几次更新就会断断续续,我真的无法弄清楚我的生活.

public class Main extends JPanel {int x=0, y=0;public JFrame window = new JFrame("Window");公共主(){window.setSize(1000, 500);window.setVisible(true);window.add(this);}公共无效paintComponent(图形g){super.paintComponent(g);g.setColor(Color.red);g.fillRect(x, y, 500, 500);x+=3;如果(x>900){x=0;}}公共无效开始(){而(真){重绘();尝试 {线程睡眠(16);} catch (InterruptedException e) {e.printStackTrace();}}}公共静态无效主(字符串 [] args){主游戏 = new Main();游戏开始();}}

如果你运行这个类,你会以图形方式看到问题所在.显然,我的游戏由更多的类组成,并且比这复杂得多,但同样的原则也适用.如果有人对我的问题有任何见解,我很乐意听到.提前致谢.

更新

这是我的两个主要课程:

主类:
包com.ultimatum.Main;

import java.awt.Dimension;导入 java.awt.Graphics;导入 java.awt.Toolkit;导入 java.awt.image.BufferedImage;导入 javax.swing.JFrame;导入 javax.swing.JPanel;导入 com.ultimatum.Mangers.ImageLoader;导入 com.ultimatum.Mangers.KeyStates;导入 com.ultimatum.Mangers.ScreenUpdater;导入 com.ultimatum.Mangers.UserInput;@SuppressWarnings("串行")公共类最后通牒扩展 JPanel {/*** @param x 这是屏幕的起始宽度,可以手动调整,但不会低于这个整数* @param y 这是屏幕的起始高度,可以手动调整,但不会低于这个整数* @param contentPlaneX 这是内容平面的宽度是多少(Frame-Borders)* @param contentPlaneY 这是内容平面的高度是多少(Frame-Borders)*/公共 int x=850,y=610,contentPlaneX,contentPlaneY,middleX,middleY,tileSize=90;public Dimension minimumSize = new Dimension(x, y);public JFrame window = new JFrame("Ultimatum");//这使得游戏的JFramepublic KeyStates keyStates = new KeyStates();公共用户输入输入 = 新用户输入(keyStates);public ImageLoader imageLoader = new ImageLoader();public static Ultimatum ultimatum;//制作这个类的对象public static Sc​​reenUpdater su;//这是创建将要对屏幕进行更改的对象.以动画为例.私有 BufferedImage 屏幕图像;public boolean isWindowInFullscreenMode;私有布尔图像加载;公共无效初始化最后通牒(){toWindowedMode();添加鼠标监听器(输入);addMouseMotionListener(输入);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;}公共无效 addListenersAndClassToWindow(){window.add(ultimatum);//把paintComponent和frame连接到这个类window.addKeyListener(输入);}公共无效paintComponent(图形g){如果(图片加载){super.paintComponent(g);//su.updateScreen(g);g.drawImage(screenImage, 0, 0, contentPlaneX, contentPlaneY, null);}else imagesLoaded = true;}公共无效更新(){screenImage = su.updateScreen();}/*** 这个主类设置程序.保持游戏运行的 while 循环也包含在这个类中.这门课的大部分内容很容易* 可读,所以我不打算评论那么多.*/公共静态无效主(字符串 [] args){最后通牒 = 新的最后通牒();ultimatum.initializeUltimatum();final int FPS=60,TARGET_TIME=1000/FPS;漫长的开始,经过,等待;while(true){//这个循环的目的是为了让游戏在所有电脑上都能流畅运行开始 = System.nanoTime();最后通牒.更新();ultimatum.repaint();//这个调用paintComponent方法elapsed = System.nanoTime() - 开始;等待 = TARGET_TIME-elapsed/1000000;if(wait<0) wait = TARGET_TIME;try{//捕获错误以防尝试给出错误(它不会)Thread.sleep(wait);//这是等待屏幕重绘的时间}catch(异常e){e.printStackTrace();}}}}

屏幕更新程序:

package com.ultimatum.Mangers;导入 java.awt.Color;导入 java.awt.Graphics;导入 java.awt.Graphics2D;导入 java.awt.Point;导入 java.awt.image.BufferedImage;导入 com.ultimatum.Engine.BuildingGenerator;导入 com.ultimatum.Engine.TextBoxGenerator;导入 com.ultimatum.Entities.Character.Player;导入 com.ultimatum.Gamestates.Buildings.HealingCenter;导入 com.ultimatum.Gamestates.Menus.EscapeScreen;导入 com.ultimatum.Gamestates.Menus.StartScreen;导入 com.ultimatum.Gamestates.Menus.TitleScreen;导入 com.ultimatum.Gamestates.Routes.RouteSuperClass;导入 com.ultimatum.Gamestates.Towns.TownSuperClass;导入 com.ultimatum.Main.Ultimatum;公共类 ScreenUpdater {公开最后通牒 ul;public Resizer rs;//这个是捕获两个整数resize的对象公共密钥状态 ks;公共 ImageLoader 加载器;公共推子推子;公共文本框生成器文本框;公共初始化程序;公共 TileMap tm;公共玩家 p;public BuildingGenerator bg;//菜单公共 TitleScreen titleScreen;公共开始屏幕开始屏幕;公共 EscapeScreen escScreen;//城市公共城镇超级类城镇;//路由公共 RouteSuperClass 路线;//建筑物公共康复中心康复中心;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;//这些变量用于checkEsc方法public boolean isMouseVisible=true, prevIsMouseVisible;//设置鼠标从可见到不可见的简单布尔值,反之亦然public Sc​​reenUpdater(Ultimatum ultimatum, KeyStates keyStates, ImageLoader imageloader, String location,int tileSize){ul = 最后通牒;ks = 键状态;加载器 = 图像加载器;推子=新推子(ul,这个);textBox = new TextBoxGenerator(loader, ks, ul);初始化器 = 新初始化器(推子,文本框);推子.sendIniterData(initer);p = new Player(ul,fader,loader,ks,initer,this);推子.sendPlayerData(p);tm = new TileMap(tileSize, loader, p);推子.sendTileMapData(tm);rs = new Resizer(ul, p);bg = new BuildingGenerator(ul, p, loader, tm);//下面是正在加载的游戏状态//菜单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);//城市城镇 = 新 TownSuperClass(p, 推子, bg, tm, this);//路由路线 = 新的 RouteSuperClass(p, 推子, bg, tm, this);//建筑物治疗中心 = 新治疗中心(UL,推子,加载器,ks,文本框);}公共无效clearScreen(图形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){菜单状态=真;上一个屏幕=屏幕编号;}否则 if(screenNo==ESC_MENU) menuState=false;}if(prevMenuState!=menuState){int toScreen;布尔鼠标可见性;如果(菜单状态){toScreen=ESC_MENU;鼠标可见性=真;}别的{toScreen=prevScreen;鼠标可见性=假;}fader.FadeOut(g, 255, toScreen, false, "", 0, 0, false, 0, mouseVisiblity);//零不重要,因为布尔值设置为false如果(!fader.fadingOut){上一个菜单状态=菜单状态;initer.initFader();}}}公共无效 checkForF11(){如果(ks.isF11PressedThenReleased){if(ul.isWindowInFullscreenMode) ul.toWindowedMode();否则 ul.toFullscreenMode();}}公共无效 setMouseVisible(){ul.window.setCursor(ul.window.getToolkit().createCustomCursor(loader.cursor, new Point(0, 0),"Visible"));}公共无效 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();否则 setMouseInvisble();prevIsMouseVisible=isMouseVisible;}}公共缓冲图像更新屏幕(){BufferedImage screenImage = new BufferedImage(ul.contentPlaneX, ul.contentPlaneY, BufferedImage.TYPE_INT_ARGB);Graphics2D screenGraphics = screenImage.createGraphics();颜色 oldColor = screenGraphics.getColor();screenGraphics.setPaint(Color.white);screenGraphics.fillRect(0, 0, ul.contentPlaneX, ul.contentPlaneY);screenGraphics.setColor(oldColor);checkForF11();清除屏幕(屏幕图形);开关(屏幕号){案例 TITLE_SCREEN:titleScreen.titleScreen(screenGraphics);休息;案例 START_SCREEN:startScreen.startScreen(screenGraphics);休息;案例 TOWN_ONE:towns.townOne(screenGraphics);休息;案例 ROUTE_ONE:路线.routeOne(screenGraphics);休息;案例 HEALING_CENTER:治疗中心.治疗中心(屏幕图形);休息;案例ESC_MENU:escScreen.escapeScreen(screenGraphics);休息;}checkEsc(screenGraphics);rs.checkForResize();ks.update();checkMouseState();//g.drawImage(screenImage, 0, 0, ul.contentPlaneX, ul.contentPlaneY, null);//screenGraphics.dispose();返回屏幕图像;}}

解决方案

不要更新 paintComponent 方法中的状态,绘画可能出于多种原因而发生,其中许多原因您没有启动或将被通知.相反,状态应该只由您的主循环"更新

请参阅

import java.awt.BorderLayout;导入 java.awt.Dimension;导入 java.awt.EventQueue;导入 java.awt.Font;导入 java.awt.FontMetrics;导入 java.awt.Graphics;导入 java.awt.Graphics2D;导入 java.awt.Point;导入 java.awt.event.ActionEvent;导入 java.awt.event.ActionListener;导入 java.awt.geom.AffineTransform;导入 java.awt.image.BufferedImage;导入 java.io.IOException;导入 java.text.NumberFormat;导入 java.util.ArrayList;导入 java.util.List;导入 javax.imageio.ImageIO;导入 javax.swing.JComponent;导入 javax.swing.JFrame;导入 javax.swing.JPanel;导入 javax.swing.JSlider;导入 javax.swing.Timer;导入 javax.swing.UIManager;导入 javax.swing.UnsupportedLookAndFeelException;导入 javax.swing.event.ChangeEvent;导入 javax.swing.event.ChangeListener;公共类测试{公共静态无效主(字符串 [] args){新测试();}公共测试(){EventQueue.invokeLater(new Runnable() {@覆盖公共无效运行(){尝试 {UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {ex.printStackTrace();}PaintPane 窗格 = new PaintPane();JSlider 滑块 = new JSlider(1, 10000);滑块.addChangeListener(新的ChangeListener(){@覆盖public void stateChanged(ChangeEvent e) {尝试 {窗格.setQuantity(slider.getValue());} catch (IOException ex) {ex.printStackTrace();}}});滑块.setValue(1);JFrame frame = new JFrame("测试");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);框架.添加(窗格);frame.add(slider, BorderLayout.SOUTH);框架.pack();frame.setLocationRelativeTo(null);frame.setVisible(true);}});}公共静态类 PaintPane 扩展 JPanel {私有静态最终 int SPOOL_DELTA = 100;私人列表<Sprite>水池;私人列表<Sprite>精灵;私有整数数量;公共画板(){尝试 {BufferedImage img = ImageIO.read(getClass().getResource("/resources/Pony.png"));池 = 新的 ArrayList(128);sprites = new ArrayList<>(128);定时器 timer = new Timer(40, new ActionListener() {@覆盖public void actionPerformed(ActionEvent e) {如果(精灵.大小()<数量){列表<精灵>toAdd = new ArrayList<>(SPOOL_DELTA);int required = 数量 - sprites.size();如果(池.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)));}} 别的 {toAdd.addAll(pool.subList(0, Math.min(SPOOL_DELTA, pool.size())));pool.removeAll(toAdd);}sprites.addAll(toAdd);} else if (sprites.size() > 数量) {列表<精灵>toRemove = new ArrayList<>(SPOOL_DELTA);int required = sprites.size() - 数量;if (sprites.size() > required) {toRemove.addAll(sprites.subList(0, Math.min(SPOOL_DELTA, required)));sprites.removeAll(toRemove);pool.addAll(toRemove);}}对于(精灵精灵:精灵){sprite.update(getSize());}重绘();}});定时器开始();} catch (IOException ex) {ex.printStackTrace();}setFont(getFont().deriveFont(Font.BOLD, 18f));}@覆盖公共维度 getPreferredSize() {返回新维度(200, 200);}@覆盖受保护的无效paintComponent(图形g){super.paintComponent(g);Graphics2D g2d = (Graphics2D) g.create();对于(精灵精灵:精灵){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) 抛出 IOException {this.quantity = 价值;}}公共静态类精灵{私有 BufferedImage img;私人点位;私人双角;私人点差;私人双角Delta;公共精灵(缓冲图像缓存,点位置){img = 缓存;this.location = new Point(location);delta = 新点(rnd(),rnd());而(angleDelta == 0){angleDelta = (Math.random() * 5) - 2.5;}}受保护的 int rnd() {整数值 = 0;而(值== 0){值 = (int) (Math.random() * 9) - 4;}返回值;}公共无效更新(尺寸大小){位置.x += delta.x;location.y += delta.y;如果 (location.x <0) {位置.x = 0;delta.x *= -1;}if (location.x + img.getWidth() > size.width) {location.x = size.width - img.getWidth();delta.x *= -1;}如果 (location.y <0) {位置.y = 0;delta.y *= -1;}if (location.y + img.getHeight() > size.height) {location.y = size.height - img.getHeight();delta.y *= -1;}角度 +=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();}}}

例如,您还可以使用基于时间"的动画,而不是基于线性的动画

  • 我注意到的一件事是,更新范围(即窗口)越小,动画效果越好,因此您可以考虑使用类似 repaint(Rectangle) 减少组件尝试更新的区域

    import java.awt.Color;导入 java.awt.EventQueue;导入 java.awt.Graphics;导入 java.awt.Graphics2D;导入 java.awt.Rectangle;导入 java.awt.RenderingHints;导入 java.awt.geom.AffineTransform;导入 java.awt.geom.Rectangle2D;导入 java.util.concurrent.TimeUnit;导入 java.util.concurrent.locks.ReentrantLock;导入 javax.swing.JFrame;导入 javax.swing.JPanel;导入 javax.swing.UIManager;导入 javax.swing.UnsupportedLookAndFeelException;公共类 Main 扩展 JPanel {双 x = 0, y = 0;私有 Rectangle2D 形状;私人双天使 = 0;private ReentrantLock updateLock = new ReentrantLock();public JFrame window = new JFrame("Window");公共主(){window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);window.setSize(1000, 500);window.add(this);window.setVisible(true);}公共无效paintComponent(图形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);更新锁.lock();尝试 {g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(angel),shape.getCenterX(),shape.getCenterY()));g2d.fill(形状);} 最后 {updateLock.unlock();}g2d.dispose();}公共无效开始(){shape = new Rectangle2D.Double(x, y, 50, 50);线程 t = 新线程(新的 Runnable() {@覆盖公共无效运行(){long startTime = System.nanoTime();long runTime = TimeUnit.NANOOSECONDS.convert(10, TimeUnit.SECONDS);System.out.println(runTime);双旋转自 = 0;双旋转 = 720;而(真){long now = System.nanoTime();长差异 = 现在 - 开始时间;双进度=差异/(双)运行时间;如果(进度> 1.0d){进度 = 0d;startTime = System.nanoTime();}x = (getWidth() * 进度);更新锁.lock();尝试 {天使 = rotateFrom + ((rotateTo - rotateFrom) * 进度);shape.setRect(x, y, 50, 50);} 最后 {updateLock.unlock();}重绘();尝试 {线程睡眠(8);} catch (InterruptedException e) {e.printStackTrace();}}}});t.setDaemon(真);t.start();}公共静态无效主(字符串 [] args){EventQueue.invokeLater(new Runnable() {@覆盖公共无效运行(){尝试 {UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {ex.printStackTrace();}主游戏 = new Main();游戏开始();}});}}

    So, I have been working on a 2d rpg for some time now and I can't seem to fix this one problem. The graphics seem to "jump" or stutter every few seconds for an unknown reason. This is getting quite annoying because I don't know what is causing it.

    Here is a very basic program I wrote that just has a red square that moves from one side of the screen to the other side. Even in this very basic program the square still stutters every few updates and I really can't figure it out for the life of me.

    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();
    }
    
    }
    

    If you run the class you will see what the problem is graphically. Obviously my game is made up of many more classes and is far more complex than this, but the same principal applies. If any one has any insight to my problem I would love to hear it. Thanks in advance.

    Updated

    Here are my two main classes:

    Main Class:
    package 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();
            }
        }
    }
    }
    

    Screen Updater:

    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;
    }
    }
    

    解决方案

    Don't update the state in the paintComponent method, painting can happen for any number reasons, many of which you don't initiate or will be notified about. Instead, the state should only be updated by your "main loop"

    See Painting in AWT and Swing for more details about how painting works in Swing

    Updated

    Swing Timer based solution...

    The example allows you to animate 1-10, 000 sprites, each sprite moves and spins independently. Obviously, I don't have collision detection, but the animation as a whole moves well

    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();
            }
    
        }
    
    }
    

    You could also use a "time" based animation, instead of linear based animation, for example

    And if you're feeling really brave, Moving JLabel to other JLabels - GUI and Move image in a spiral fashion in java which are examples of key-frame based animations (time based)

    Updated

    This is an update to the original posted code from the question that is using a time based animation and adds in some rotation to the object (and some other graphical updates).

    You'll note that I've used a ReentrantLock around the critical points where the shape is either updated or painted, this should prevent possible race conditions or dirty read/writes from occurring

    The following is the same animation at 10, 5, 2 and 1 second durations

    One thing I did note, was, the smaller the update range (ie window), te better the animation, so you might consider using something like repaint(Rectangle) to reduce the amount of area the component tries to update

    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();
                }
            });
        }
    
    }
    

    这篇关于Java基础2d游戏动画卡顿的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆