Question

EDIT: This is an SSCCE to demonstrate my problem.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.awt.Graphics2D;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

public class Main {
    public static BufferedImage map, tileSand, tileSea;
    public static void main(String[] args) {
        map = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
        for(int x = 0; x < 50 ; x++){
            for(int y = 0; y < 50 ; y++){
                boolean colour = Math.random() < 0.5;
                if (colour){
                    map.setRGB(x, y, -256);
                } else {
                    map.setRGB(x, y, -16776961);
                }
            }
        }
        tileSand = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB);
        Graphics g = tileSand.getGraphics();
        g.setColor(Color.YELLOW);
        g.fillRect(0, 0, 32, 32);
        tileSea  = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB);
        g.setColor(Color.BLUE);
        g = tileSea.getGraphics();
        g.fillRect(0, 0, 32, 32);
        Island test = new Main.Island();
        test.start();
        long start, sleep;
        while(true) {
            start  = System.currentTimeMillis();
            test.getCanvas().requestFocus();
            test.getCanvas().repaint();
            sleep = 15-(System.currentTimeMillis()-start);
            try {
                Thread.sleep(sleep > 0 ? sleep : 0);
            } catch (InterruptedException e) {
            }
        }
    }

    static class Island implements Runnable {

        private Tile[][] tiles;
        private JFrame frame;
        private JPanel panel;
        private Thread logicThread;
        private boolean running = false;
        private boolean paused = false;
        private Image image;
        private Player player;

        public Island() {
            image = new BufferedImage(1027, 768, BufferedImage.TYPE_INT_ARGB);
            player = new Player();
            tiles = new Tile[map.getWidth()][map.getHeight()];
            int rgb;
            for(int x = 0; x < map.getWidth(); x++) {
                for(int y = 0; y < map.getHeight(); y++) {
                    rgb = map.getRGB(x, y);
                    switch (rgb) {
                        case -16776961: tiles[x][y] = new Tile("sea");
                                        break;
                        case -256:  tiles[x][y] = new Tile("sand");
                                    break;
                    }
                }
            }
            makeMap();
            makeFrame();
            addBindings();
            logicThread = new Thread(this);
        }

        public JPanel getCanvas() {
            return panel;
        }

        public void start() {
            running = true;
            paused = false;
            logicThread.start();
        }

        public void run() {
            long sleep, before;
            while(running){
                before = System.currentTimeMillis();
                player.move();
                try {
                    sleep = 15-(System.currentTimeMillis()-before);
                    Thread.sleep(sleep > 0 ? sleep : 0);
                } catch (InterruptedException ex) {
                }
                while(running && paused);
            }
            paused = false;
        }

        private void makeFrame() {
            frame = new JFrame("Island");
            panel = new JPanel(){
                @Override
                public void paintComponent(Graphics g) {
                    super.paintComponent(g);
                    Graphics2D g2d = (Graphics2D) image.getGraphics();
                    g2d.setColor(Color.BLACK);
                    g2d.fillRect(0, 0, 1024, 768);
                    long xl, yl;
                    xl = player.getLocation().x-512;
                    yl = player.getLocation().y-384;
                    int x2, y2;
                    x2 = (int) Math.floor(xl / 32);
                    y2 = (int) Math.floor(yl / 32);
                    int xoffset, yoffset;
                    xoffset = (int) (xl % 32);
                    yoffset = (int) (yl % 32);
                    for(int x = x2; x < x2+40; x++) {
                        for(int y = y2; y < y2+40; y++) {
                            if (x < tiles.length && x > 0 && y < tiles[0].length && y > 0) {
                                g2d.drawImage(tiles[x][y].getContent(), (x-x2)*32 - xoffset, (y-y2)*32 - yoffset, null);
                            }
                        }
                    }
                    g.drawImage(image, 0, 0, null);
                }
            };
            panel.setPreferredSize(new Dimension(1024, 768));
            panel.setIgnoreRepaint(true);
            frame.add(panel);
            frame.pack();
            frame.setVisible(true);
        }

        private void addBindings() {
            InputMap inputMap = panel.getInputMap();
            ActionMap actionMap = panel.getActionMap();
            inputMap.put(KeyStroke.getKeyStroke("Q"),"hold-sprint");
            actionMap.put("hold-sprint", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.sprint(true);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("released Q"),"release-sprint");
            actionMap.put("release-sprint", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.sprint(false);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("RIGHT"),"hold-right");
            actionMap.put("hold-right", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.right(true);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("released RIGHT"),"release-right");
            actionMap.put("release-right", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.right(false);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("DOWN"),"hold-down");
            actionMap.put("hold-down", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.down(true);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("released DOWN"),"release-down");
            actionMap.put("release-down", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.down(false);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("LEFT"),"hold-left");
            actionMap.put("hold-left", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.left(true);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("released LEFT"),"release-left");
            actionMap.put("release-left", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.left(false);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("UP"),"hold-up");
            actionMap.put("hold-up", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.up(true);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("released UP"),"release-up");
            actionMap.put("release-up", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.up(false);
                }
            });
        }

        private void makeMap() {
            for(int x = 0; x < tiles.length; x++) {
                for(int y = 0; y < tiles[0].length; y++) {
                    switch (tiles[x][y].getType()) {
                        case "sea": tiles[x][y].setContent(tileSea);
                                    break;
                        case "sand":    tiles[x][y].setContent(tileSand);
                                        break;
                    }
                }   
            }
        }
    }

    static class Player{
        private Point location;
        private int xspeed, yspeed;
        private boolean left, up, right, down, sprint;

        public Player() {
            location = new Point(0, 0);
            left = false;
            up = false;
            right = false;
            down = false;
            sprint = false;
            xspeed = yspeed = 0;
        }
        public void left(boolean state){
            left = state;
        }

        public void up(boolean state){
            up = state;
        }

        public void right(boolean state){
            right = state;
        }

        public void down(boolean state){
            down = state;
        }

        public void sprint(boolean state) {
            sprint = state;
        }

        public void move() {
            if (sprint) {
                if (left) {
                    if(xspeed>-10)
                        xspeed--;
                } if (right) {
                    if(xspeed<10)
                        xspeed++;
                } if (up) {
                    if(yspeed>-10)
                        yspeed--;
                } if (down) {
                    if(yspeed<10)
                        yspeed++;
                }
            } else {
                if (left) {
                    if(xspeed>-5)
                        xspeed--;
                } if (right) {
                    if(xspeed<5)
                        xspeed++;
                } if (up) {
                    if(yspeed>-5)
                        yspeed--;
                } if (down) {
                    if(yspeed<5)
                        yspeed++;
                }
            }
            if (!sprint) {
                if (xspeed > 5) {
                    xspeed--;
                } if (xspeed < -5) {
                    xspeed++;
                }   if (yspeed > 5) {
                    yspeed--;
                } if (yspeed < -5) {
                    yspeed++;
                }
            }
            if (!left && !right) {
                if (xspeed > 0) {
                    xspeed--;
                } if (xspeed < 0) {
                    xspeed++;
                }
            } if (!up && !down) {
                if (yspeed > 0) {
                    yspeed--;
                } if (yspeed < 0) {
                    yspeed++;
                }
            }
            location.x = location.x + xspeed;
            location.y = location.y + yspeed;
        }

        public Point getLocation() {
            return location;
        }
    }

    static class Tile {
        private String type;
        private BufferedImage tile;

        public Tile(String type) {
            this.type = type;
        }

        public String getType() {
            return type;
        }

        public void setContent(BufferedImage newTile) {
            tile = newTile;
        }

        public BufferedImage getContent() {
            return tile;
        }
    }
}

I have a JPanel with a modified paintComponent method. In this method I draw the relevant tiles from a 2d array to the screen based on the player location. I draw all my tiles to a BufferedImage and then I apply it to the screen at the end of the paint method. In theory this should not create tearing as I am double buffering? Here is the appropriate code, image is just a BufferedImage with the dimensions of the screen:

    public void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D) image.getGraphics();
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0, 0, 1024, 768);
        long xl, yl;
        xl = player.getLocation().x-512;
        yl = player.getLocation().y-384;
        int x2, y2;
        x2 = (int) Math.floor(xl / 32);
        y2 = (int) Math.floor(yl / 32);
        int xoffset, yoffset;
        xoffset = (int) (xl % 32);
        yoffset = (int) (yl % 32);
        for(int x = x2; x < x2+40; x++) {
            for(int y = y2; y < y2+40; y++) {
                if (x < tiles.length && x > 0 && y < tiles[0].length && y > 0) {
                    g2d.drawImage(tiles[x][y].getContent(), (x-x2)*32 - xoffset, (y-y2)*32 - yoffset, null);
                }
            }
        }
        g.drawImage(image, 0, 0, null);
    }

I think the tearing could possibly be caused by the player's location changing over the duration of the paint method causing the tiles not to match up, the faster the player goes the more obvious the effect is. However I get the players location at the start and store it in two longs, I was under the impression that longs are passed by value not reference so the player location should be constant whilst the method runs.

I am happy to provide more code :), and thanks in advance.

Was it helpful?

Solution

Just so if people find my question and wonder what I ended up doing to "fix" it, switch over to c++ and SDL or some other image library before you are to far into your project, it's simply better for this kind of thing.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top