Błąd animacji, migotanie

0

I kolejny mój problem. Znowu okaże się, że jestem ślepy i głupi, ale cóż, dopiero się uczę.
Mój program miał za zadanie wyświetlenie postaci podobnej do pacmana, ale z zamkniętymi ustami i mrugającymi oczami. Sam pacman miał poruszać się ze stałą prędkością, odbijając się od krawędzi ekranu. Kod pliku "SpriteTest" przedstawia się następująco:

import graphic.Animation;
import graphic.ScreenManager;
import graphic.Sprite;
import java.awt.DisplayMode;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import javax.swing.ImageIcon;

public class SpriteTest {
    public static void main(String[] args) {
        SpriteTest test = new SpriteTest();
        test.run();
    }
    private static final DisplayMode POSSIBLE_MODES[] = {
        new DisplayMode(1600, 900, 32, 75),
        new DisplayMode(1600, 900, 16, 75),
        new DisplayMode(800, 600, 32, 75),
        new DisplayMode(800, 600, 16, 75),
        new DisplayMode(640, 480, 32, 75),
        new DisplayMode(640, 480, 16, 75)
    };
    
    private static final long DEMO_TIME = 10000;
    private ScreenManager screen;
    private Image bgImage;
    private Sprite sprite;
    
    public void loadImages() {
        //ładowanie rysunków
        bgImage = loadImage("images/background.jpg");
        Image player1 = loadImage("images/player1.png");
        Image player2 = loadImage("images/player2.png");
        Image player3 = loadImage("images/player3.png");
        //tworzenie sprite
        Animation anim = new Animation();
        anim.addFrame(player1, 250);
        anim.addFrame(player2, 150);
        anim.addFrame(player1, 150);
        anim.addFrame(player2, 150);
        anim.addFrame(player3, 200);
        anim.addFrame(player2, 150);
        sprite = new Sprite(anim);
        //uruchomienie sprite poruszającego się w dół i w prawo
        sprite.setVelocityX(0.2f);
        sprite.setVelocityY(0.2f);
    }
    private Image loadImage(String fileName) {
        return new ImageIcon(fileName).getImage();
    }
    public void run() {
        screen = new ScreenManager();
        try {
            DisplayMode displayMode = screen.findFirstCompatibleMode(POSSIBLE_MODES);
            screen.setFullScreen(displayMode);
            loadImages();
            animationLoop();
        }
        finally {
            screen.restoreScreen();
        }
    }
    public void animationLoop() {
        long startTime = System.currentTimeMillis();
        long currTime = startTime;
        while (currTime - startTime < DEMO_TIME) {
            long elapsedTime = System.currentTimeMillis() - currTime;
            currTime += elapsedTime;
            //aktualizacja sprite
            update(elapsedTime);
            //narysowanie i aktualizacja zawartości ekranu
            Graphics2D g = screen.getGraphics();
            draw(g);
            g.dispose();
            screen.update();
            //chwila przerwy
            try {
                Thread.sleep(20);
            }
            catch (InterruptedException ex) {}
        }
    }
    
    public void update(long elapsedTime) {
        //sprawdzenie granic sprite
        if (sprite.getX() < 0) sprite.setVelocityX(Math.abs(sprite.getVelocityX()));
        else if (sprite.getX() + sprite.getWidth() >= screen.getWidth()) sprite.setVelocityX(-Math.abs(sprite.getVelocityX()));
        if (sprite.getY() < 0) sprite.setVelocityY(Math.abs(sprite.getVelocityY()));
        else if (sprite.getY() + sprite.getHeight() >= screen.getHeight()) sprite.setVelocityY(-Math.abs(sprite.getVelocityY()));
        //aktualizacja duszka
        sprite.update(elapsedTime);
    }
    public void draw(Graphics g) {
        //Narysowanie tła
        g.drawImage(bgImage, 0, 0, null);
        //rysowanie sprite
        g.drawImage(sprite.getImage(), Math.round(sprite.getX()), Math.round(sprite.getY()), null);
    }
}

W imporcie na początku:

import graphic.Animation;
import graphic.ScreenManager;
import graphic.Sprite;

Prowadzi do moich plików. ScreenManager:

package graphic;


import java.awt.*;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;

public class ScreenManager
{
	private GraphicsDevice device;
	
	public ScreenManager()
	{
		GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
		device = environment.getDefaultScreenDevice();
	}
        
        public DisplayMode[] getCompatibleModes() {
            return device.getDisplayModes();
        }
        
        public DisplayMode findFirstCompatibleMode(DisplayMode modes[]) {
            DisplayMode goodModes[] = device.getDisplayModes();
            for (int i = 0; i < modes.length; i++) {
                for (int j = 0; j <goodModes.length; j++) {
                    if (displayModesMatch(modes[i], goodModes[j])) {
                        return modes[i];
                    }
                }
            }
            
            return null;
        }
	
	public DisplayMode getCurrentDisplayMode() {
            return device.getDisplayMode();
        }
        
        public boolean displayModesMatch(DisplayMode mode1, DisplayMode mode2) {
            if (mode1.getWidth() != mode2.getWidth() || mode1.getHeight() != mode2.getHeight()) return false;
            if (mode1.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI && mode2.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI && mode1.getBitDepth() != mode2.getBitDepth()) return false;
            if (mode1.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN && mode2.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN && mode1.getRefreshRate() != mode2.getRefreshRate()) return false;
            return true;
        }
        
        public void setFullScreen(DisplayMode displayMode) {
            final JFrame frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setUndecorated(true);
            frame.setIgnoreRepaint(true);
            frame.setResizable(false);
            
            device.setFullScreenWindow(frame);
            
            if (displayMode != null && device.isDisplayChangeSupported()) {
                try {
                    device.setDisplayMode(displayMode);
                }
                catch (IllegalArgumentException ex) {}
                //poprawka dla systemu MacOS X:
                frame.setSize(displayMode.getWidth(), displayMode.getHeight());
                }
                
                try {
                    EventQueue.invokeAndWait(new Runnable() {
                        public void run() {
                            frame.createBufferStrategy(2);
                        }
                    });
                }
                catch (InterruptedException | InvocationTargetException ex) {
                    //ignorowanie
                }
        }
        
        public Graphics2D getGraphics() {
            Window window = device.getFullScreenWindow();
            if (window != null) {
                BufferStrategy strategy = window.getBufferStrategy();
                return (Graphics2D)strategy.getDrawGraphics();
            }
            else return null;
        }
        
        public void update() {
            Window window = device.getFullScreenWindow();
            if (window != null) {
                BufferStrategy strategy = window.getBufferStrategy();
                if (!strategy.contentsLost()) strategy.show();
            }
            //naprawa problemu z kolejką zdarzeń w Linux
            Toolkit.getDefaultToolkit().sync();
        }
        
        public JFrame getFullScreenWindow() {
            return (JFrame)device.getFullScreenWindow();
        }
        
        public int getWidth() {
            Window window = device.getFullScreenWindow();
            if (window != null) return window.getWidth();
            else return 0;
                }
        public int getHeight() {
            Window window = device.getFullScreenWindow();
            if (window != null) return window.getHeight();
            else return 0;
                }
        
        public BufferedImage createCompatibleImage(int w, int h, int transparancy) {
            Window window = device.getFullScreenWindow();
            if (window != null) {
                GraphicsConfiguration gc = window.getGraphicsConfiguration();
                return gc.createCompatibleImage(w, h, transparancy);
            }
            return null;
        }

    public void restoreScreen() {
        Window window = device.getFullScreenWindow();
            if (window != null) window.dispose();
            device.setFullScreenWindow(null);
    }
}

Animation:

package graphic;

import java.awt.Image;
import java.util.ArrayList;

/*
 * Klasa Animation - zarządzanie serią rysunków (ramek) oraz czasem ich wyświetlania
 */

public final class Animation {
    private ArrayList frames;
    private int currFrameIndex;
    private long animTime;
    private long totalDuration;
    
    public Animation() {
        frames = new ArrayList();
        totalDuration = 0;
        start();
    }
    //dodaje do animacji rysunek o określonym czasie wyświetlania
    public synchronized void addFrame(Image image, long duration) {
        totalDuration += duration;
        frames.add(new AnimFrame(image, totalDuration));
    }
    //uruchamia animację od początku
    public synchronized void start() {
        animTime = 0;
        currFrameIndex = 0;
    }
    //modyfikuje bieżącą ramkę animacji
    public synchronized void update(long elapsedTime) {
        if (frames.size() > 1) {
            animTime += elapsedTime;
            if (animTime >= totalDuration) {
                animTime = animTime % totalDuration;
                currFrameIndex = 0;
            }
            
            while (animTime > getFrame(currFrameIndex).endTime) currFrameIndex++;
        }
    }
    
    //pobiera bieżący rysunek z animacji. Zwraca null, jeżeli animacja nie zawiera rysunków
    public synchronized Image getImage() {
        if (frames.isEmpty()) return null;
        else return getFrame(currFrameIndex).image;
    }
    private AnimFrame getFrame(int i) {
        return (AnimFrame)frames.get(i);
    }
    private class AnimFrame {
        Image image;
        long endTime;
        public AnimFrame(Image image, long endTime) {
            this.image = image;
            this.endTime = endTime;
        }
    }
} 

Sprite:

package graphic;

import java.awt.Image;

public class Sprite {
    private Animation anim;
    //pozycja w pikselach
    private float x;
    private float y;
    //prędkość w pikselach na milisekundę
    private float dx;
    private float dy;
    
    //nowy obiekt Sprite z obiektem Animation
    public Sprite(Animation anim) {
        this.anim = anim;
    }
    
    //aktualizuje Animation dla bieżącego Sprite oraz swoją pozycję na podstawie prędkości
    public void update(long elapsedTime) {
        x += dx * elapsedTime;
        y += dy * elapsedTime;
    }
    
    //zwracanie współrzędnych x i y obiektu Sprite
    public float getX() {
        return x;
    }
    public float getY() {
        return y;
    }
    
    //ustawianie współrzędnych x i y obiektu Sprite
    public void setX(float x) {
        this.x = x;
    }
    
    public void setY(float y) {
        this.y = y;
    }
    
    //zwracanie szerokości i wysokości obiektu Sprite na podstawie rozmiaru bieżącego rysunku
    public int getWidth() {
        return anim.getImage().getWidth(null);
    }
    public int getHeight() {
        return anim.getImage().getHeight(null);
    }
    //zwraca prędkość w poziomie i w pionie obiektu Sprite w pikselach na milisekundę
    public float getVelocityX() {
        return dx;
    }
    public float getVelocityY() {
        return dy;
    }
    //ustawia prędkość w poziomie i wpionie obiektu Sprite w pikselach na milisekundę
    public void setVelocityX(float dx) {
        this.dx = dx;
    }
    public void setVelocityY(float dY) {
        this.dy = dy;
    }
    //zwraca bieżący rysunek dla tego obiektu Sprite
    public Image getImage() {
        return anim.getImage();
    }
}

Reszta importów pochodzi ze standardowych bibliotek Javy.

Wynik działania:
1.

bgImage = loadImage("images/background.jpg");
...
g.drawImage(bgImage, 0, 0, null);

nie rysuje tła
2. Migotanie obrazu. Nie powinno się tak dziać, ponieważ w pliku ScreenManager mamy

public Graphics2D getGraphics() {
            Window window = device.getFullScreenWindow();
            if (window != null) {
                BufferStrategy strategy = window.getBufferStrategy();
                return (Graphics2D)strategy.getDrawGraphics();
            }
            else return null;
        }
  1. Nie widać animacji, nie ma obrazków, nic się nie przesuwa

Innymi słowy, kupa. Program, z którego korzystam, to NetBeans, nie wyświetla on żadnych błędów.

Książka, z której się uczę: "Java. Tworzenie gier", wyd. Helion. Co ciekawe, gdy książka była wydana, była jeszcze poprzednia wersja Javy, a ja swoją wersję pobrałem po tym, gdy wydano książkę, więc posiadam tą samą wersję lub nowszą, czyli to nie jest wina wersji Javy.

0

może ci choć trochę pomoże mój kod, podwójne buforowanie w swingu domyślnie jest włączone, kółko jeżdżące wkoło okna.

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package javaswingkolo;

import javax.swing.SwingUtilities;

/**
 *
 * @author marcin
 */
public class JavaSwingKolo {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                ClassWindow classWindow = new ClassWindow();

            }
        });
    }
}

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package javaswingkolo;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

/**
 *
 * @author marcin
 */
public class ClassWindow extends JFrame{
    private int widthWindow = 800;
    private int heightWindow = 600;
    JPanel jPanel = new JPanel();
    ClassDrawCircle classDrawCircle = new ClassDrawCircle();
    Timer timer;

    public ClassWindow() {
        setTitle("kolo");
        setSize(new Dimension(widthWindow, heightWindow));
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
        
        add(jPanel);

        timer = new Timer(1, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                add(classDrawCircle);
                classDrawCircle.repaint();
                revalidate();
            }
        });
        timer.start();
    }
    
}

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package javaswingkolo;

import java.awt.Graphics;
import javax.swing.JPanel;

/**
 *
 * @author marcin
 */
public class ClassDrawCircle extends JPanel{
    
    private int posX = 50;
    private int posY = 50;
    private int widthRect = 100;
    private int heightRect = 100;
    private int course = 1;
    
    @Override
    public void paint(Graphics graphics){
        super.paint(graphics);
        drawCircle(graphics);
    }
    
    private void drawCircle(Graphics graphics){
        switch(course){
            case 1:
                graphics.fillRoundRect(++posX, posY, widthRect, heightRect, 360, 360);
                System.out.println("posX = " + posX);
                if(posX == 650){
                    course = 2;
                }
                break;
            case 2:
                graphics.fillRoundRect(posX, ++posY, widthRect, heightRect, 360, 360);
                System.out.println("posY = " + posY);
                if(posY == 450){
                    course = 3;
                }
                break;
            case 3:
                graphics.fillRoundRect(--posX, posY, widthRect, heightRect, 360, 360);
                System.out.println("posX = " + posX);
                if(posX == 50){
                    course = 4;
                }
                break;
            case 4:
                graphics.fillRoundRect(posX, --posY, widthRect, heightRect, 360, 360);
                System.out.println("posY = " + posY);
                if(posY == 50){
                    course = 1;
                }
                break;
        }
    }
}

 
1

Wywołania getGraphics i dispose wywal poza pętlę. Skoro chcesz w kółko macieju rysować na ekranie to po jakiego grzyba tworzysz (i to nie ty) oraz usuwasz grafikę? To tak jakbyś wyrywał sobie wycieraczkę spod nóg żeby po wypieprzeniu się z powrotem na nią stawać i od nowa...
Wystarczająco dużo roboty grafice przysporzy zamalowywanie tła i ponowne odrysowywanie sprite'ów (to też może spowodować migotanie jeżeli między odrysowaniem tła i odrysowaniem sprite'ów musisz coś intensywnie obliczać, albo co gorsze masz powstawiane jakieś sleepy).

Migotanie to nic innego jak zamalowanie kawałka ekranu zupełnie innym kolorem i odczekanie jakiegoś czasu. Skoro nie ty to robisz, to pozwalasz, żeby coś robiło to za Ciebie (np. przez bezsensowne zajmowanie się usuwaniem i tworzeniem kolejnych obiektów w pamięci). Takie zajmowanie systemu niepotrzebnymi nikomu operacjami, to typowy błąd wszystkich programujących we mgle. Dodatkowo, idealnie wykonana animacja w czasie swojego działania nie produkuje ani jednego nowego bajta na stercie (a śmieciarce daje dobry powód do niczym nie zmąconego lenistwa:).

0

Po wywaleniu tych dwóch linijek wyskakują 4 wyjątki NullPointerException, gdy otoczyłem jeden z nich try... catch, pojawił się nowy wyjątek. Gdy ten nowy wyjątek również otoczyłem, wyszło mi dokładnie to samo - nic nie widać, ekran migocze.

0

W każdej linijce, w której występował wyjątek, była używana inna moja metoda, w której ten wyjątek nastąpił. W ten sposób, można było dotrzeć do jednego wyjątku, w linijce

g.drawImage(bgImage, 0, 0, null);

po wpisaniu samego "g." wyświetliła się lista metod, drawImage było na liście, więc to nie jest problem. 0, 0, null też nie powinno robić problemów. Obiekt bgImage jest poprawny:

private Image bgImage;
...
bgImage = loadImage("images/background.jpg");

nazwy się zgadzają, lokalizacja się zgadza, typ pliku się zgadza. Jedyna rzecz, która może według mnie nie się nie zgadzać, to "private" przy deklaracji pola składowego Image bgImage, jednak bez względu na to, co tam użyję, wynik jest zawsze taki sam.
Metoda loadImage też raczej nie powinna sprawiać problemu:

private Image loadImage(String fileName) {
        return new ImageIcon(fileName).getImage();
    }

ja to rozumiem w ten sposób: używając metody, podajemy nazwę pliku. W zamian, metoda zwraca nam obiekt ImageIcon (javax.swing.ImageIcon) o podanej nazwie, używając getImage. Nie mam pojęcia, co jest źle, bo nie wyświetla żadnego błędu z wyjątkiem małej informacji, że Thread.sleep() jest w pętli.

0

@norbi452 o_O private ci przeszkadza? albo sleep() ty w ogóle rozumiesz co robisz? o_O
Odpal łaskawie debugger i ZOBACZ która referencja jest nullem. Obstawiam że podajesz złą ścieżkę do pliku ;]

0

@norbi452, @Olamagato napisał, żebyś te dwie instrukcje wywalił poza pętlę, a nie wywalił zupełnie. Zupełnie to wywal tylko dispose(),

0

@bogdans, zrobiłem tak i nadal to samo.
@Shalom, debugger zwraca moją uwagę na linijkę 14 w SpriteTest:

private static final DisplayMode POSSIBLE_MODES[] = {
        new DisplayMode(1600, 900, 32, 0),
        new DisplayMode(1600, 900, 16, 0),
        new DisplayMode(800, 600, 32, 0),
        new DisplayMode(800, 600, 16, 0),
        new DisplayMode(640, 480, 32, 0),
        new DisplayMode(640, 480, 16, 0)
    };

Tradycyjnie, nie wyświetla żadnego błędu. Tradycyjnie, sprawdziłem w dwóch książkach i wszystko powinno się zgadzać. Tradycyjnie, nie wiem, na czym polega problem.

0

Tradycyjnie przyczyna problemu znajduje się między krzesłem i klawiaturą. Spójrz gdzie masz nawiasy kwadratowe.

0

@norbi452, jak byś udostępnił pliki graficzne, to można by potestować.

0

No ale w czym problem, rzuca ci wyjątek, który łapiesz, masz stack trace... czego ci więcej trzeba?? Ustaw sobie breakpoint przed wywołaniem tej metody, sprawdź zmienne i gotowe...

1

Z opisanych przez Ciebie objawów u mnie występuje tylko migotanie. Wniosek, w złym katalogu masz obrazki.
Poprawiłem trochę metodę animationLoop()

    public void animationLoop() {
        long startTime = System.currentTimeMillis();
        long currTime = startTime;
	Graphics2D g = screen.getGraphics();
        while (currTime - startTime < DEMO_TIME) {
            long elapsedTime = System.currentTimeMillis() - currTime;
            currTime += elapsedTime;
            //aktualizacja sprite
            update(elapsedTime);
            //narysowanie i aktualizacja zawartości ekranu
            draw(g);
            screen.update();
            //chwila przerwy
            try {
                Thread.sleep(20);
            }
            catch (InterruptedException ex) {}
        }
    } 
0

Święta racja - zamieniłem "images/nazwa.typ" na "src/images/nazwa.typ" i wszystko się wyświetla. Mam jednak jeszcze kilka problemów:

  • Migotanie
  • Tło za małe (ale to nic, po prostu je zwiększę w pierwszym lepszym Paincie)
  • Animowany ludek nie mruga oczami
  • Ludek zostawia swój ślad
    ...
    Przeanalizowałem jednak wygląd działającego programu, i mam następujące wnioski:
  • Ekran miga pod obrazkiem tła
  • Sprite znajduje się NA tle
  • "Ślad" sprita znajduje się POD tłem
    Stąd wnioskuję, że jeżeli rozciągnę tło, to nie powinno być ani migotania, ani śladu. Zostaje brak animacji.

1 użytkowników online, w tym zalogowanych: 0, gości: 1