Java的基本的2D游戏动画口吃 [英] Java basic 2d game animation stutter

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

问题描述

所以,我一直在努力了一段时间的2D RPG游戏,现在我似乎无法修复这一问题。图形似乎跳或每隔几秒钟口吃原因未知。这让很烦人的,因为我不知道是什么原因造成它做的。

下面是一个非常基本的程序我写的,只是有一个红色方块从屏幕的一侧移动到另一侧。即使在这种非常基本的程序仍然广场结结巴巴每隔几个更新,我真的无法弄清楚了我的生活。

 公共类主要继承JPanel {INT X = 0,Y = 0;公众的JFrame窗口=新的JFrame(窗口);公众的Main(){
    window.setSize(1000,500);
    window.setVisible(真);
    window.add(本);
}公共无效的paintComponent(图形G){
    super.paintComponent方法(G);
    g.setColor(Color.red);
    g.fillRect(X,Y​​,500,500);
    X + = 3;
    如果(X GT; 900){
        X = 0;
    }
}公共无效的start(){
    而(真){
        重绘();
        尝试{
            视频下载(16);
        }赶上(InterruptedException的E){
            e.printStackTrace();
        }
    }
}公共静态无效的主要(字串[] args){
    主要的游戏=新的Main();    游戏开始();
}}

如果您运行的类,您将看到的问题是什么图形。显然,我的游戏是由许多更多的类,并且是比这更加复杂,但相同原理适用。如果任何人有任何了解我的问题,我很乐意听到它。提前致谢。

更新

下面是我的两大类:

主要类别:结果
    包com.ultimatum.Main;

 进口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;@燮pressWarnings(串行)
公共类最后通牒继承JPanel {    / **
     * @参数x这是屏幕的起始宽度与可手动调节,但不会比这个整数任何较低
     * @参数ý这是屏幕的起始高度,并且可以手动进行调整,但不会比这个整数任何较低
     * @参数contentPlaneX这是内容平面的宽度是多少(帧,边框)
     * @参数contentPlaneY这是内容平面的高度是多少(帧-边框)
     * /
    公众诠释X = 850,Y = 610,contentPlaneX,contentPlaneY,middleX,middleY,tileSize = 90;    公共尺寸=的minimumSize新尺寸(X,Y);    公众的JFrame窗口=新的JFrame(最后通牒); //这使得在JFrame的游戏
    公共KeyStates keyStates =新KeyStates();
    公共UserInput输入=新UserInput(keyStates);
    公共ImageLoader的ImageLoader的=新ImageLoader的();
    公共静态最后通牒最后通牒; //使此类的对象
    公共静态ScreenUpdaterス; //这是创建将要作出的改变到屏幕上的对象。例如,动画。
    私人的BufferedImage screenImage;    公共布尔isWindowInFullscreenMode;    私人布尔imagesLoaded;    公共无效initializeUltimatum(){
        toWindowedMode();        addMouseListener将(输入);
        addMouseMotionListener(输入);        contentPlaneX = window.getContentPane()的getWidth()。
        contentPlaneY = window.getContentPane()的getHeight()。
        middleX =(int)的contentPlaneX / 2;
        middleY =(int)的contentPlaneY / 2;
        苏=新ScreenUpdater(最后通牒,keyStates,ImageLoader的测试,tileSize);        imageLoader.loadImages();
    }    公共无效toFullscreenMode(){
        window.dispose();
        window.setUndecorated(真);
        window.setVisible(真);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setBounds(0,0,Toolkit.getDefaultToolkit().getScreenSize().width,Toolkit.getDefaultToolkit().getScreenSize().height);
        addListenersAndClassToWindow();
        isWindowInFullscreenMode = TRUE;
    }    公共无效toWindowedMode(){
        window.dispose();
        window.setUndecorated(假);
        window.setSize(X,Y);
        window.setMinimumSize(的minimumSize);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setVisible(真);
        window.setLocationRelativeTo(NULL);
        addListenersAndClassToWindow();
        isWindowInFullscreenMode = FALSE;
    }    公共无效addListenersAndClassToWindow(){
        window.add(最后通牒); //此连接的paintComponent和框架这一类
        window.addKeyListener(输入);
    }    公共无效的paintComponent(图形G){
        如果(imagesLoaded){
            super.paintComponent方法(G);
            //su.updateScreen(g);
            g.drawImage(screenImage,0,0,contentPlaneX,contentPlaneY,NULL);
        }否则imagesLoaded = TRUE;
    }    公共无效更新(){
        screenImage = su.updateScreen();
    }    / **
     *这个主类设置的程序。 while循环,保持游戏的运行也包含这个类里面。大多数这类容易
     *可读所以我不打算发表评论那么多。
     * /
    公共静态无效的主要(字串[] args){
        最后通牒=新的最后通牒();
        ultimatum.initializeUltimatum();        最终诠释FPS = 60,TARGET_TIME = 1000 / FPS;        长开始,经过,等待;        而(真){//这个循环的目的是保持游戏的流畅运行所有计算机上
            开始= System.nanoTime();            ultimatum.update();
            ultimatum.repaint(); //这将调用paintComponent方法            经过= System.nanoTime() - 启动;            等待= TARGET_TIME-已过/ 1000000;
            如果(等待℃的)等待= TARGET_TIME;
        尝试{//捕捉到该错误的情况下,试图给出一个错误(它不会)
            视频下载(等待); //这是怎么长的等待它,直到屏幕重绘得到
        }赶上(例外五){
            e.printStackTrace();
        }
    }
}
}

屏幕更新:

 包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;
    公共Resizer的RS; //这是捕获两个整数的调整对象
    公共KeyStates KS;
    公共ImageLoader的装载机;
    公共推子推子;
    公共TextBoxGenerator的textBox;
    公共初始化器initer;
    公共tilemap的以旧换新;
    大众玩家P;
    公共BuildingGenerator BG;    //菜单
    公共TitleScreen titleScreen;
    公共启动画面启动画面;
    公共EscapeScreen escScreen;    //城镇
    公共TownSuperClass镇;    //路线
    公共RouteSuperClass航线;    //建筑
    公共HealingCenter healingCenter;    公众最终诠释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;    公众诠释screenNo = TITLE_SCREEN;    公众诠释prevScreen = 0;
    公共布尔prevMenuState,menuState; //这些变量的方法checkEsc
    公共布尔isMouseVisible = TRUE,prevIsMouseVisible; //从可见设置鼠标无形的,反之亦然简单的布尔    公共ScreenUpdater(最后通牒最后通牒,KeyStates keyStates,ImageLoader的ImageLoader的,字符串的位置,
            INT tileSize){        UL =最后通牒;
        KS = keyStates;
        装载机= ImageLoader的;
        推子=新的推子(UL,本);
        textBox中=新TextBoxGenerator(装载机,KS,UL);
        initer =新的初始化程序(推子的textBox);
        fader.sendIniterData(initer);        P =新播放器(UL,推子,装载机,KS,initer,这一点);
        fader.sendPlayerData(P);        TM =新的tilemap的(tileSize,装载机,P);
        fader.sendTileMapData(TM);        RS =新的调整器(UL,P);        BG =新BuildingGenerator(UL,P,装载机,TM);        //以下是本场比赛的状态被装载        //菜单
        titleScreen =新TitleScreen(UL,为此,装载机,KS,推子);
        启动画面=新的启动画面(UL,为此,推子,装载机,KS,的textBox);
        escScreen =新EscapeScreen(UL,推子,装载机,KS);
        rs.sendEscapeScreenData(escScreen);        //城镇
        城镇=新TownSuperClass(P,推子,BG,TM,这一点);        //路线
        路线=新RouteSuperClass(P,推子,BG,TM,这一点);        //建筑
        healingCenter =新HealingCenter(UL,推子,装载机,KS,的textBox);
    }    公共无效clearScreen(图形G){
        g.setColor(Color.black);
        g.fillRect(0,0,ul.contentPlaneX,ul.contentPlaneY);
    }    公共无效checkEsc(图形G){
        如果(ks.escReleased&安培;&安培; screenNo>负载和放大器;&安培;!fader.fadingOut和放大器;&安培; fader.fadingIn){
            如果(screenNo< HEALING_CENTER&放大器;&安培;!p.isMoving){
        menuState = TRUE;
            prevScreen = screenNo;
        }
        否则,如果(screenNo == ESC_MENU)menuState = FALSE;
    }    如果(prevMenuState!= menuState){
        INT toScreen;
        布尔mouseVisiblity;
        如果(menu​​State){
            toScreen = ESC_MENU;
            mouseVisiblity =真;
        }
        其他{
            toScreen = prevScreen;
            mouseVisiblity = FALSE;
        }        fader.FadeOut(G,255,toScreen,假的,,0,0,假,0,mouseVisiblity); //零点并不重要,因为布尔设置为false
        如果(!fader.fadingOut){
            prevMenuState = menuState;
            initer.initFader();
        }
    }
}公共无效checkForF11(){
    如果(ks.isF11 pressedThenReleased){
        如果(ul.isWindowInFullscreenMode)ul.toWindowedMode();
        别的ul.toFullscreenMode();
    }
}公共无效setMouseVisible(){
    ul.window.setCursor(ul.window.getToolkit()createCustomCursor(loader.cursor,新点(0,0),可见));
}公共无效setMouseInvisble(){
    ul.window.setCursor(ul.window.getToolkit()createCustomCursor(新的BufferedImage(3,3,BufferedImage.TYPE_INT_ARGB),新的点(0,0),清除));
}公共无效checkMouseState(){
    如果(isMouseVisible!= prevIsMouseVisible){
        如果(isMouseVisible)setMouseVisible();
        别的setMouseInvisble();
        prevIsMouseVisible = isMouseVisible;
    }
}公众的BufferedImage updateScreen(){
    BufferedImage的screenImage =新的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();
    clearScreen(screenGraphics);
    开关(screenNo){
        案例TITLE_SCREEN:
            titleScreen.titleScreen(screenGraphics);
            打破;
        案例START_SCREEN:
            startScreen.startScreen(screenGraphics);
            打破;
        案例TOWN_ONE:
            towns.townOne(screenGraphics);
            打破;
        案例ROUTE_ONE:
            routes.routeOne(screenGraphics);
            打破;
        案例HEALING_CENTER:
            healingCenter.healingCenter(screenGraphics);
            打破;
        案例ESC_MENU:
            escScreen.escapeScreen(screenGraphics);
            打破;
    }
    checkEsc(screenGraphics);
    rs.checkForResize();
    ks.update();
    checkMouseState();    //g.drawImage(screenImage,0,0,ul.contentPlaneX,ul.contentPlaneY,NULL);
    //screenGraphics.dispose();
    返回screenImage;
}
}


解决方案

不更新在的paintComponent 法国家,绘画可以发生任何数量的原因,许多其中,你不启动或将有关通知。相反,国家应只能由你的主循环更新

请参阅AWT 绘画和Swing 有关如何画的详细信息工作在Swing

更新

秋千定时基于解决方案...

这个例子可以让你制作动画1-10万的精灵,每个精灵的动作和旋转独立。很显然,我没有碰撞检测,但动画作为一个整体移动以及

很多小马

 进口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(新的Runnable(){
            @覆盖
            公共无效的run(){
                尝试{
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                }赶上(ClassNotFoundException的| InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException前){
                    ex.printStackTrace();
                }                PaintPane窗格=新PaintPane();                JSlider的滑块=新JSlider的(1,10000);
                slider.addChangeListener(新的ChangeListener(){
                    @覆盖
                    公共无效stateChanged(的ChangeEvent发送){
                        尝试{
                            pane.setQuantity(slider.getValue());
                        }赶上(IOException异常前){
                            ex.printStackTrace();
                        }
                    }
                });
                slider.setValue(1);                JFrame的帧=新的JFrame(测试);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(面板);
                frame.add(滑块,BorderLayout.SOUTH);
                frame.pack();
                frame.setLocationRelativeTo(NULL);
                frame.setVisible(真);
            }
        });
    }    公共静态类PaintPane继承JPanel {        私有静态最终诠释SPOOL_DELTA = 100;        私人列表<&雪碧GT;池;
        私人列表<&雪碧GT;精灵;
        私人诠释数量;        公共PaintPane(){
            尝试{
                IMG的BufferedImage = ImageIO.read(的getClass()的getResource(/资源/ Pony.png));                池=新的ArrayList<>(128);
                精灵=新的ArrayList<>(128);
                定时器定时器=新定时器(40,新的ActionListener(){
                    @覆盖
                    公共无效的actionPerformed(ActionEvent的五){                        如果(sprites.size()&下;数量){
                            清单<&雪碧GT; TOADD =新的ArrayList<>(SPOOL_DELTA);
                            INT =需要的数量 - sprites.size();
                            如果(pool.isEmpty()){
                                对于(INT指数= 0;指数 - LT; Math.min(SPOOL_DELTA,需要);指数++){
                                    INT X =(int)的(的Math.random()*的getWidth());
                                    INT Y =(int)的(的Math.random()*的getHeight());
                                    toAdd.add(新雪碧(IMG,新点(x,y)));
                                }
                            }其他{
                                toAdd.addAll(pool.subList(0,Math.min(SPOOL_DELTA,pool.size())));
                                pool.removeAll(TOADD);
                            }
                            sprites.addAll(TOADD);
                        }否则如果(sprites.size()>数量){
                            清单<&雪碧GT;文档,删除=新的ArrayList<>(SPOOL_DELTA);
                            诠释所需= sprites.size() - 数量;
                            如果(sprites.size()>需要){
                                toRemove.addAll(sprites.subList(0,Math.min(SPOOL_DELTA,必需)));
                                sprites.removeAll(文档,删除);
                                pool.addAll(文档,删除);
                            }
                        }                        对于(雪碧精灵:精灵){
                            sprite.update(的getSize());
                        }
                        重绘();
                    }
                });
                timer.start();
            }赶上(IOException异常前){
                ex.printStackTrace();
            }            并入setfont(的getFont()的deriveFont(Font.BOLD,18F));
        }        @覆盖
        公共尺寸的get preferredSize(){
            返回新尺寸(200,200);
        }        @覆盖
        保护无效paintComponent(图形G){
            super.paintComponent方法(G);
            Graphics2D的G2D =(Graphics2D的)g.create();
            对于(雪碧精灵:精灵){
                sprite.draw(G2D,这一点);
            }
            。字符串文本= NumberFormat.getNumberInstance()格式(sprites.size());
            FontMetrics对象FM = g2d.getFontMetrics();
            INT X =的getWidth() - fm.stringWidth(文本);
            INT Y =(的getHeight() - fm.getHeight())+ fm.getAscent();
            g2d.drawString(文字,X,Y);
            g2d.dispose();
        }        公共无效setQuantity(int值)抛出IOException
            this.quantity =价值;
        }    }    公共静态类雪碧{        私人的BufferedImage IMG;
        私人点位置;
        私人双角;        私人点三角洲;
        私人双angleDelta;        公共雪碧(BufferedImage的缓存,点位置){
            IMG =高速缓存;
            this.location =新的点(位置);
            增量=新点(RND(),RND());
            而(angleDelta == 0){
                angleDelta =(的Math.random()* 5) - 2.5;
            }
        }        保护INT RND(){
            int值= 0;
            而(价值== 0){
                值=(INT)(的Math.random()* 9) - 4;
            }
            返回值;
        }        公共无效更新(尺寸大小){
            location.x + = delta.x;
            location.y + = delta.y;            如果(location.x℃,){
                location.x = 0;
                delta.x * = -1;
            }
            如果(location.x + img.getWidth()> size.width){
                location.x = size.width - img.getWidth();
                delta.x * = -1;
            }
            如果(location.y℃,){
                location.y = 0;
                delta.y * = -1;
            }
            如果(location.y + img.getHeight()> size.height){
                location.y = size.height - img.getHeight();
                delta.y * = -1;
            }            角+ = angleDelta;
        }        公共无效画(Graphics2D的G2D,JComponent的父){
            Graphics2D的G =(Graphics2D的)g2d.create();
            在的AffineTransform = AffineTransform.getTranslateInstance(location.x,location.y);
            at.rotate(Math.toRadians(角度),img.getWidth()/ 2,img.getHeight()/ 2);
            g.transform(AT);
            g.drawImage(IMG,0,0,父);
            g.dispose();
        }    }}

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

如果你感觉真的很勇敢,<一个href=\"http://stackoverflow.com/questions/26898536/moving-jlabel-to-other-jlabels-gui/26899099#26899099\">Moving JLabel以其他的JLabel - GUI 和<一个href=\"http://stackoverflow.com/questions/28619150/move-image-in-a-spiral-fashion-in-java/28619554#28619554\">Move以螺旋方式在Java 这是关键帧基于动画(基于时间)

的实施例的图像

更新

这是一个更新从使用基于时间的动画,并增加了在一些旋转到对象(和其他一些图形更新)的问题的原贴code

您会注意到,我已经使用了的ReentrantLock 在临界点所在的形状要么更新,要么画,这应该prevent可能的竞争条件或脏读/发生写入

以下是在10相同的动画,5,2 1秒持续时间

10秒 href=\"http://i.stack.imgur.com/MN0l9.gif\" rel=\"nofollow\"> href=\"http://i.stack.imgur.com/zLD9N.gif\" rel=\"nofollow\">

有一件事我确实注意到,当时,较小的更新范围(即窗口),TE更好的动画,所以你可能会考虑使用类似<一个href=\"https://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#repaint(java.awt.Rectangle)\"相对=nofollow> 重绘(矩形) 减少面积的部分尝试更新量

 进口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;公共类主要继承JPanel {    双X = 0,Y = 0;
    私人矩形形状;
    私人双天使= 0;    私人的ReentrantLock updateLock =新的ReentrantLock();    公众的JFrame窗口=新的JFrame(窗口);    公众的Main(){
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setSize(1000,500);
        window.add(本);
        window.setVisible(真);
    }    公共无效的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);        updateLock.lock();
        尝试{
            g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(天使)
                    shape.getCenterX(),
                    shape.getCenterY()));
            g2d.fill(形状);
        } {最后
            updateLock.unlock();
        }
        g2d.dispose();
    }    公共无效的start(){
        形状=新Rectangle2D.Double(X,Y,50,50);
        线程t =新主题(新的Runnable(){
            @覆盖
            公共无效的run(){
                长STARTTIME = System.nanoTime();
                长运行时间= TimeUnit.NANOSECONDS.convert(10,TimeUnit.SECONDS);
                的System.out.println(运行时);                双rotateFrom = 0;
                双rotateTo = 720;
                而(真){                    长今= System.nanoTime();
                    长差异=现在 - 的startTime;
                    双进度=差异/(双)运行;
                    如果(进度&GT; 1.0D){
                        进度= 0D;
                        STARTTIME = System.nanoTime();
                    }                    X =(的getWidth()*进展);                    updateLock.lock();
                    尝试{
                        天使= rotateFrom +((rotateTo - rotateFrom)*进度);
                        shape.setRect(X,Y​​,50,50);
                    } {最后
                        updateLock.unlock();
                    }                    重绘();
                    尝试{
                        视频下载(8);
                    }赶上(InterruptedException的E){
                        e.printStackTrace();
                    }
                }
            }
        });
        t.se​​tDaemon(真);
        t.start();
    }    公共静态无效的主要(字串[] args){
        EventQueue.invokeLater(新的Runnable(){
            @覆盖
            公共无效的run(){
                尝试{
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                }赶上(ClassNotFoundException的| InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException前){
                    ex.printStackTrace();
                }                主要的游戏=新的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天全站免登陆