Plansza aksonometryczna

Założenia

Chcemy stworzyć planszę aksonometryczną z postacią poruszaną z klawiatury przy użyciu strzałek

Na wideo kolejno używam kolejno strzałki w lewo, w górę, w prawo i w dół.
Rysunki do animacji wykonała Martyna Sobienikow. Postać na cześć M.S. nazwałem Martynką.

Przygotowanie

Animacja ruchu składa się z czterech obrazków z postacią ustawioną w prawo (Rys. 136)


Rys. 136. Postać skierowana w prawo

oraz z czterech obrazków z postacią ustawiona w lewo (Rys. 137)


Rys. 137. Postać skierowana w lewo

Obrazki mają wielkość 42 x 85 pikseli dopasowana do wielkości planszy.

Struktura projektu

Mój projekt ma strukturę (Rys. 138)


Rys. 138. Struktura projektu

Kody

Plik module-info
module planszaakson {
    requires java.desktop;
}
Klasa AnimFrame
package planszaakson;
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
class AnimFrame extends JComponent {
    private static final long serialVersionUID = -2960156825708485829L;
    private final BufferedImage bimage;
    public AnimFrame(String obrazek, boolean flip) {
        if (flip) {
            bimage = U.flipHorizontally(U.fileToBimage(obrazek));
        } else {
            bimage = U.fileToBimage(obrazek);
        }
        setLayout(null);
        setSize(U.ANIMFRAME_WIDTH, U.ANIMFRAME_HEIGHT);
        setBackground(Color.WHITE);
    }
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.drawImage(bimage, 0, 0, U.ANIMFRAME_WIDTH, U.ANIMFRAME_HEIGHT, this);
    }
}
Klasa Sprite
package planszaakson;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
/**
 * animacja z wykorzystaniem klasy Timer i CardLayout
 */
class Sprite extends JComponent implements ActionListener {
    private static final long serialVersionUID = -1708095494186169452L;
    private final CardLayout cl;
    private final File[] files;
    private int frames;
    private final Timer timer;
    private boolean going;
    private boolean loop;
    public Sprite(String dir, String ext, boolean flip) {
        cl = new CardLayout();
        setPreferredSize(new Dimension(182, 335));
        setBounds(0, 0, 182, 335);
        setBackground(Color.WHITE);
        setLayout(cl);
        files = U.listFiles("planszaakson/assets/" + dir, ext);
        frames = files.length;
        for (File file : files) {
            AnimFrame card = new AnimFrame("" + file, flip);
            add(card, "" + file);
        }
        going = false;
        loop = false;
        timer = new Timer(0, this);
        timer.setInitialDelay(0);
        timer.setDelay(60);
        timer.start();
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        if (going) {
            if (!loop) {
                if (frames > 0) {
                    cl.next(Sprite.this);
                    frames--;
                } else {
                    timer.stop();
                }
            } else {
                cl.next(Sprite.this);
            }
        }
    }
    public void setGoing(boolean going) {
        this.going = going;
        if (going) {
            frames = files.length;
            timer.restart();
        } else {
            timer.stop();
        }
    }
    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}
Klasa U
package planszaakson;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
class U {
    private U() {
    }
    public static final int FRAME_WIDTH = 640;
    public static final int FRAME_HEIGHT = 480;
    public static final int ANIMFRAME_WIDTH = 45;
    public static final int ANIMFRAME_HEIGHT = 87;
    public static final Dimension DIM = new Dimension(FRAME_WIDTH, FRAME_HEIGHT);
    public static final Dimension DIM1 = new Dimension(ANIMFRAME_WIDTH,
            ANIMFRAME_HEIGHT);
    public static final int SCREEN_WIDTH = Toolkit.getDefaultToolkit()
            .getScreenSize().width;
    public static final int SCREEN_HEIGHT = Toolkit.getDefaultToolkit()
            .getScreenSize().height;
    /**
     * tworzy listę plików File typu "*ext"  w podanym katalogu
     *
     * @param dir String - katalog zawierający pliki
     * @param ext String - rozszerzenie, np. "png"
     * @return File[] - lista plików w podanym katalogu
     */
    public static File[] listFiles(String dir, String ext) {
        File file = new File(dir);
        File[] files = null;
        if (file.isDirectory()) {
            FileExtensionFilter fef = new FileExtensionFilter(file, ext);
            files = file.listFiles(fef);
        } else {
            System.out.println("Podaj nazwę katalogu z obrazkami");
        }
        return files;
    }
    /**
     * Wczytuje obrazek z pliku i zamienia go na BufferedImage
     * Ta metoda, z nieznanych mi przyczyn nie chciała działać z filtrami
     * <code>RescaleOp</code> i <code>ConvolveOp</code>, czyli np. blur(),
     * sharp(), brightness()
     *
     * @param plik String - wskazuje plik z obrazkiem
     * @return BufferedImage - zwraca obrazek buforowany znajdujący się w pliku
     */
    public static BufferedImage fileToBimage(String plik) {
        File f = new File(plik);
        BufferedImage bimage = null;
        try {
            bimage = ImageIO.read(f);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bimage;
    }
    /**
     * Filtr odwracający BufferedImage w poziomie (wzdłuż osi pionowej)
     *
     * @param bimage BufferedImage - filtrowany obrazek
     * @return BufferedImage - przefiltrowany obrazek
     */
    public static BufferedImage flipHorizontally(BufferedImage bimage) {
        AffineTransform tx = AffineTransform.getScaleInstance(-1, 1);
        tx.translate(-bimage.getWidth(null), 0);
        AffineTransformOp op = new AffineTransformOp(tx,
                AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
        return op.filter(bimage, null);
}
}
Klasa FileExtensionFilter
package planszaakson;
import java.io.*;
/**
 * Filtr plików z danym rozszerzeniem
 * np. dla png wpisujemy "png"
 */
class FileExtensionFilter implements FilenameFilter {
    /**
     * Tworzy obiekt filtra dla podanego katalogu i rozszerzenia
     */
    public FileExtensionFilter(File dir, String ext) {
        accept(dir, ext);
    }
    @Override
    public boolean accept(File dir, String ext) {
        return ext.endsWith(ext) || ext.endsWith(ext.toUpperCase());
    }
}
Klasa PlanszaAkson
package planszaakson;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class PlanszaAkson extends JFrame implements KeyListener {
    private static final long serialVersionUID = 5509515767913681613L;
    private static int martyx = U.FRAME_WIDTH / 2;//posx
    private static int martyy = U.FRAME_HEIGHT / 2;//posy
    private static final int walkxspeed = 6;//xspeed
    private static final int walkyspeed = 3;//yspeed
    private static int walkmode = 0;
    private Sprite anim;
    public PlanszaAkson() {
        setLayout(null);
        setTitle("Plansza aksonometryczna");
        setResizable(false);
        setPreferredSize(U.DIM);
        setBackground(Color.WHITE);
        getContentPane().setBackground(Color.WHITE);
        setBounds((U.SCREEN_WIDTH - U.FRAME_WIDTH) / 2,
                (U.SCREEN_HEIGHT - U.FRAME_HEIGHT) / 2, U.FRAME_WIDTH,
                U.FRAME_HEIGHT);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        anim = new Sprite("R", "png", false);
        anim.setBounds(martyx - U.ANIMFRAME_WIDTH / 2, martyy
                - U.ANIMFRAME_HEIGHT, U.ANIMFRAME_WIDTH, U.ANIMFRAME_HEIGHT);
        add(anim);
        addKeyListener(this);
        setVisible(true);
    }
    @Override
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
        switch (keyCode) {
            case KeyEvent.VK_RIGHT:
                this.remove(anim);
                walkmode = 1;
                martyx += walkxspeed;
                martyy += walkyspeed;
                anim = new Sprite("R", "png", false);
                break;
            case KeyEvent.VK_LEFT:
                this.remove(anim);
                walkmode = 2;
                martyx -= walkxspeed;
                martyy -= walkyspeed;
                anim = new Sprite("L", "png", false);
                break;
            case KeyEvent.VK_UP:
                this.remove(anim);
                walkmode = 3;
                martyx += walkxspeed;
                martyy -= walkyspeed;
                anim = new Sprite("L", "png", true);
                break;
            case KeyEvent.VK_DOWN:
                this.remove(anim);
                walkmode = 4;
                martyx -= walkxspeed;
                martyy += walkyspeed;
                anim = new Sprite("R", "png", true);
                break;
        }
        if (walkmode > 0) {
            anim.setBounds(martyx - U.ANIMFRAME_WIDTH / 2, martyy
                    - U.ANIMFRAME_HEIGHT, U.ANIMFRAME_WIDTH, U.ANIMFRAME_HEIGHT);
            add(anim);
            anim.setGoing(true);
            repaint();
        }
    }
    @Override
    public void keyReleased(KeyEvent e) {
        e.consume();
        walkmode = 0;
    }
    @Override
    public void keyTyped(KeyEvent e) {
        e.consume();
        walkmode = 0;
    }
}
Klasa Main
package planszaakson;
import javax.swing.*;
class Main {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(PlanszaAkson::new);
    }
}

Wynik

Po uruchomieniu kodu otrzymujemy ramkę. (Rys. 139)


Rys. 139. Plansza aksonometryczna z Martynką

Użycie strzałek zostało pokazane na wideo na początku.

Uwagi

Pomysł klas AnimFrame i Sprite był wzorowany na rozwiązaniu przedstawionym w książce Brackeen D i in., 2004. Java.Tworzenie gier, Wyd. Helion.
Pomysł planszy aksonometrycznej z postacią Martynki był wzorowany na pomyśle z książki Turner B. i in. 2001. Flash 5. Gry i kreskówki. F/x. Efekty specjalne. Wyd. Helion. Postacią animowaną był tam Walter.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *