Java基础2d游戏动画口吃

时间:2016-03-14 04:44:02

标签: java animation graphics jpanel 2d

所以,我现在已经有一段时间在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;
}
}

1 个答案:

答案 0 :(得分:4)

不要更新paintComponent方法中的状态,绘画可能由于任何数量原因而发生,其中许多原因是您未启动或将收到通知。相反,只应通过“主循环”更新状态

有关绘画如何在Swing

中工作的详细信息,请参阅Painting in AWT and Swing

<强>更新

基于Timer的解决方案......

该示例允许您为1-10,000个精灵设置动画,每个精灵独立移动和旋转。显然,我没有碰撞检测,但动画整体运作良好

Lots of ponies

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 - GUIMove image in a spiral fashion in java这些是基于关键帧的动画(基于时间)的例子

<强>更新

这是对使用基于时间的动画的问题的原始发布代码的更新,并为对象添加了一些旋转(以及一些其他图形更新)。

你会注意到我在更新或绘制形状的关键点周围使用ReentrantLock,这可以防止可能的竞争条件或脏读/写发生

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

10 Seconds 5Seconds 2Seconds 1second

我注意到的一点是,更新范围(即窗口)越小,动画效果越好,因此您可以考虑使用类似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();
            }
        });
    }

}