1
\$\begingroup\$

I am making an isometric game where I have moving Entities's not bound to tiles, and a 3d ArrayList map which is populated by Block's which forms a tower-like structure in the middle of the map.

My problem is, Is that I do not know a way to devise a strategy for drawing the entities and blocks in such a way so that they do not overlap in the wrong way and appear in the correct order. For example, an entity behind the tower will not be drawn over the tower.

Currently, I have crude solution which treats the tower as a simple object and does some maths to determine weather the entity is in the shadow of the tower, And if it is, to not draw it. You can see what I mean in the code I will post below. However, the problem with this is that I have battlements ontop of my tower which are always drawn infront of my the entities. Which does not look right. So I need a proper, dynamic way I can draw all my entities and blocks at the right time in the right way so that they appear in the correct order

Now, I know of the algorithms which people use to draw their objects in the correct way, But I have spent ~8 hours implenting all these different algorithms in my code. ( Such as the one mentioned in this post) However, They all do not create my desired result. I'm not sure if this is due to the nature of my code or some other reason. I simply dont know.

Screenshot of my problem enter image description here As you can see, the entity is drawn over the front battlement block(s)

My Main Class (Where its all happening)

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.IntStream;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.Timer;

public class Main extends JPanel implements ActionListener, KeyListener{

    public static final int WIDTH = 2000;
    public static final int HEIGHT = 2000;
    public static final int GRAVITY = 2;
    public static final int TILESIZE = 50;

    public static String currentMode = "build";
    public static Point mousePoint = new Point();
    public static boolean drawPlaceBlock = false;
    public static boolean placeBlock = false;

    public static int SCREENWIDTH = WIDTH;
    public static int SCREENHEIGHT = HEIGHT;
    public static float xscale = (float) SCREENWIDTH / WIDTH;
    public static float yscale = (float) SCREENHEIGHT / HEIGHT;

    public static JFrame frame = null;

    public BufferedImage drawnBackground;
    public BufferedImage background;

    public static Random random = new Random();
    public static Block currentPlaceBlock ;
    public static AffineTransform normal = new AffineTransform();
    public static Sort sort = new Sort();

    public static Tower tower;

    public ArrayList<Entity> entities = new ArrayList<Entity>();
    public Entity debug = new Entity(0, 0, 0, 30, 30, 300);

    public Main(){
        addKeyListener(this);
        setFocusable(true);
        setFocusTraversalKeysEnabled(false);
        requestFocus();

        tower = new Tower(WIDTH, HEIGHT, 10, 10, 2);
        background = generateBackground();
        entities.add(debug);

        new Thread(() -> {
            gameLoop();
        }).start();

        new Thread(() -> {
            while(true) {
                if (background == null) continue;

                BufferedImage drawnBackground = new BufferedImage(background.getWidth(), background.getHeight(), background.getType());
                Graphics2D g2d = (Graphics2D) drawnBackground.getGraphics();

                g2d.setColor(Color.WHITE);
                g2d.fillRect(0, 0, background.getWidth(), background.getHeight());

                g2d.scale(xscale, yscale);
                g2d.rotate(Math.toRadians(45), WIDTH / 2, HEIGHT / 2);

                g2d.drawImage(background, 0, 0, this);

                this.drawnBackground = drawnBackground;

            }
        }).start();

        this.addComponentListener(new ComponentAdapter() {
             public void componentResized(ComponentEvent e) {
                Rectangle windowSize = frame.getBounds();
                SCREENWIDTH = (int) windowSize.width;
                SCREENHEIGHT = (int) windowSize.height;

                xscale = (float)SCREENWIDTH/WIDTH;
                yscale = (float)SCREENHEIGHT/HEIGHT;
            }
        });
    }
    public Main(int mode){
        if (mode == 0){

        }
    }
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        AffineTransform plain = g2d.getTransform();

        g2d.drawImage(drawnBackground, 0, 0, this);
        g2d.drawString("FPS: " + currentFPS + " | Logic Ticks: " + currentTPS, 0, 10);
        g2d.drawLine(SCREENWIDTH/2, 0, SCREENWIDTH/2, SCREENHEIGHT);
        g2d.drawLine(0, SCREENHEIGHT/2, SCREENWIDTH, SCREENHEIGHT/2);

        g2d.scale(xscale, yscale);
        g2d.rotate(Math.toRadians(45), WIDTH/2, HEIGHT/2);

        normal = g2d.getTransform();

        g2d.drawLine(WIDTH/2, 0, WIDTH/2, HEIGHT);
        g2d.drawLine(0, HEIGHT/2, WIDTH, HEIGHT/2);

        ArrayList<Object> towerBlocks = new ArrayList<Object>();
        for (int h=0;h<tower.map.size()-tower.battlementSize;h++){
            for (int r=0;r<tower.map.get(h).size();r++){
                for (int c=0; c<tower.map.get(h).get(r).size();c++){
                    Block renderBlock = tower.map.get(h).get(r).get(c); 
                    if (!(renderBlock.block.type.equals("air"))){
                        towerBlocks.add(renderBlock);
                    }
                }       
            }
        }
        ArrayList<Object> battlementBlocks = new ArrayList<Object>();
        for (int r=0;r<tower.map.get(tower.map.size()-1).size();r++){
            for (int c=0;c<tower.map.get(tower.map.size()-1).get(r).size();c++){
                Block renderBlock = tower.map.get(tower.map.size()-1).get(r).get(c);    
                if (!(renderBlock.block.type.equals("air"))){
                    battlementBlocks.add(renderBlock);
                }
            }
        }
        for (int e=0;e<entities.size();e++){
            Entity entity = entities.get(e);
            if (entity.x + entity.w >= tower.x-tower.d && entity.x <= tower.x + tower.w || entity.y + entity.h >= tower.y-tower.d && entity.y <= tower.y + tower.h){
                entity.draw(g2d, normal, this);
            }
        }
        for (int i=0; i<towerBlocks.size();i++){
            Block block = (Block)towerBlocks.get(i);
            block.draw(g2d, normal, this);
        }
        for (int i=0; i<battlementBlocks.size();i++){
            Block block = (Block)battlementBlocks.get(i);
            block.draw(g2d, normal, this);
        }
        for (int e=0; e<entities.size();e++){
            Entity entity = entities.get(e);
            if (!(entity.x + entity.w > tower.x-tower.d && entity.x < tower.x + tower.w) || !(entity.y + entity.h > tower.y-tower.d && entity.y <= tower.y + tower.h) || entity.z >= tower.d-(tower.blockd*tower.battlementSize)){
                entity.draw(g2d, normal, this);
            }
        }
        if (drawPlaceBlock && currentPlaceBlock != null){
            int x = currentPlaceBlock.x;int y = currentPlaceBlock.y;int w = currentPlaceBlock.w;int h = currentPlaceBlock.h;int z = currentPlaceBlock.z;int d = currentPlaceBlock.d;
            g2d.setTransform(normal);

            g2d.drawImage(currentPlaceBlock.block.top1, x-d-z,y-d-z, this);
            g2d.drawRect(x-d-z, y-d-z, w, h);

            g2d.transform(AffineTransform.getShearInstance(1, 0));
            g2d.drawImage(currentPlaceBlock.block.side1, x-y-w, y-z-(d-h), this);
            g2d.drawRect(x-y-w, y-z-(d-h), w, h);

            g2d.setTransform(normal);
            g2d.transform(AffineTransform.getShearInstance(0, 1));
            g2d.drawImage(currentPlaceBlock.block.side2, x-z-(d-w), y-x-h, this);
            g2d.drawRect(x-z-(d-w), y-x-h, w, h);
        }
        FPSticks++;     

    }

    public void update(){

        updateEntities();
        testForCollision();
        handleCurrentPlaceBlock();
        updateCursorPosition();

    }
    public void updateEntities(){
        for (int e=0;e<entities.size();e++){
            Entity entity = entities.get(e);
            entity.z-=GRAVITY;
            entity.update();
        }
    }
    public void updateCursorPosition(){
        mousePoint = MouseInfo.getPointerInfo().getLocation();
    }
    public void handleCurrentPlaceBlock(){
        for (int rows=0; rows<tower.map.get(tower.map.size()-1-tower.battlementSize).size();rows++){
            for (int columns=0; columns<tower.map.get(tower.map.size()-1-tower.battlementSize).get(rows).size();columns++){
                Block topBlock = tower.map.get(tower.map.size()-1-tower.battlementSize).get(rows).get(columns);
                Rectangle topBlockRect = new Rectangle(topBlock.x-topBlock.d-topBlock.z, topBlock.y-topBlock.d-topBlock.z, topBlock.w, topBlock.h);
                Area topBlockArea = new Area(topBlockRect);
                topBlockArea.transform(normal);     
                if (topBlockArea.contains(mousePoint)){
                    drawPlaceBlock = true;
                    currentPlaceBlock = new Block(topBlock.x, topBlock.y, topBlock.z+topBlock.d, topBlock.w, topBlock.h, topBlock.d, new BlockType("stone"));
                }
            }
        }
    }
    public void placeBlock(){
        if (currentPlaceBlock != null){
            int columnIndex = (currentPlaceBlock.x-tower.x)/currentPlaceBlock.w;
            int rowIndex = (currentPlaceBlock.y-tower.y)/currentPlaceBlock.h;
            ArrayList<ArrayList<Block>> battlements = tower.map.get(tower.map.size()-1);
            ArrayList<Block> row = battlements.get(rowIndex);
            Block block = currentPlaceBlock;
            row.set(columnIndex, block);
            battlements.set(rowIndex, row);
            tower.map.set(tower.map.size()-1, battlements);

        }
    }
    public void processCollision(Entity collider, Object collidedO){
        int collidedx = 0,collidedy = 0,collidedz = 0,collidedw = 0,collidedh = 0,collidedd = 0;
        if (collidedO instanceof Block){
            Block collided = (Block)collidedO;
            collidedx = collided.x;collidedy = collided.y;collidedz = collided.z;collidedw = collider.w;collidedh = collided.h;collidedd = collided.h;
        } else if (collidedO instanceof Entity){
            Entity collided = (Entity)collidedO;
            collidedx = collided.x;collidedy = collided.y;collidedz = collided.z;collidedw = collider.w;collidedh = collided.h;collidedd = collided.h;
        }
        int leftDistance = (collider.x+collider.w)-collidedx;
        int rightDistance = collider.x-(collidedx+collidedw);
        int topDistance = (collider.y+collider.h)-collidedy;
        int bottomDistance = collider.y-(collidedy+collidedh);
        int[] distanceArr = {leftDistance, rightDistance, topDistance, bottomDistance};
        int index = indexOfSmallestAbs(distanceArr);

        if (collider.z < collidedd+collidedz){
            collider.z+=GRAVITY;
        }
        if (collider.z < collidedd+collidedz){
            if (index == 0){
                collider.x-=collider.speed;
            } else if (index == 1){
                collider.x+=collider.speed;
            } else if (index == 2){
                collider.y-=collider.speed;
            } else if (index == 3){
                collider.y+=collider.speed;
            }
        }
    }
    public void testForCollision(){
        for (int e=0; e<entities.size();e++){
            Entity entity = entities.get(e);
            for (int h=0;h<tower.map.size();h++){
                for (int r=0; r<tower.map.get(h).size();r++){
                    for (int c=0;c<tower.map.get(h).get(r).size();c++){
                        Block block = tower.map.get(h).get(r).get(c);
                        if (!(block.block.type.equals("air"))){
                            if (entity.x + entity.w >= block.x && entity.x <= block.x + block.w && entity.y + entity.h >= block.y && entity.y <= block.y + block.h){
                                processCollision(entity, block);
                            }
                        }
                    }
                }
            }
        }
    }

    public BufferedImage generateBackground(){
        Image grass1 = null;
        File grass1f = new File("grass1.png");
        File backgroundf = new File("background.png");
        try {
            grass1 = ImageIO.read(grass1f);
        }catch (Exception e){

        }
        background = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics g = background.getGraphics();
        for (int h=0;h<HEIGHT/TILESIZE;h++){
            for (int w=0;w<WIDTH/TILESIZE;w++){
                g.drawImage(grass1, w*50, h*50, this);
            }
        }
        try {
            ImageIO.write(background, "PNG", backgroundf);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return background;
    }
    public int indexOfSmallestAbs(int[] arr) {
        return IntStream.range(0, arr.length)
            .reduce((a, b) -> Math.abs(arr[a]) < Math.abs(arr[b]) ? a : b)
            .getAsInt();
    }
    public static void main(String[] args){
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        SCREENWIDTH = (int) screenSize.getWidth();
        SCREENHEIGHT = (int) screenSize.getHeight();
        //SCREENWIDTH = 800;
        //SCREENHEIGHT = 600;
        Main main = new Main();
        frame = new JFrame("360 ATTACK"); // Just use the constructor

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setUndecorated(true);
        frame.add(main);
        frame.setSize(SCREENWIDTH, SCREENHEIGHT);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        frame.addMouseListener(new MouseHandler());

    }
    long targetFPS = 60;
    long currentFPS = targetFPS;
    long currentTPS = targetFPS;
    long FPSticks = 0;
    long TPSticks = 0;
    long oldFPSTime = time();
    long newFPSTime = oldFPSTime;
    public void gameLoop(){
        long previous = time();
        long lag = 0;
        while (true){
            long current = time();
            long elapsed = current-previous;
            previous = current;
            lag+= elapsed;
            while (lag >= 1000/targetFPS){
                update();
                lag-= 1000/targetFPS;
                TPSticks++;
            }
            repaint();
            newFPSTime = time();
            if (newFPSTime > oldFPSTime + 1000){
                oldFPSTime = newFPSTime;
                currentFPS = FPSticks;
                currentTPS = TPSticks;
                FPSticks = 0;
                TPSticks = 0;
            }
        }
    }
    @Override

    public void actionPerformed(ActionEvent e) {

    }
    public long time(){
        return System.currentTimeMillis();
    }
    public void say(String str){
        System.out.println(str);
    }
    @Override
    public void keyPressed(KeyEvent e) {
        int c = e.getKeyCode();
        if (c == KeyEvent.VK_ESCAPE){
            System.exit(0);
        }
        if (c == KeyEvent.VK_W){
            debug.vely = -2;
        }
        if (c == KeyEvent.VK_S){
            debug.vely = 2;
        }
        if (c == KeyEvent.VK_A){
            debug.velx = -2;
        }   
        if (c == KeyEvent.VK_D){
            debug.velx = 2;
        }
        if (c == KeyEvent.VK_Q){
            debug.velz = -2-GRAVITY;
        }
        if (c == KeyEvent.VK_E){
            debug.velz = 2+GRAVITY;
        }
    }
    @Override
    public void keyReleased(KeyEvent e) {
        int c = e.getKeyCode();
        if (c == KeyEvent.VK_W){
            debug.vely = 0;
        }
        if (c == KeyEvent.VK_S){
            debug.vely = 0;
        }
        if (c == KeyEvent.VK_A){
            debug.velx = 0;
        }   
        if (c == KeyEvent.VK_D){
            debug.velx = 0;
        }
        if (c == KeyEvent.VK_Q){
            debug.velz = 0;
        }
        if (c == KeyEvent.VK_E){
            debug.velz = 0;
        }
    }
    @Override
    public void keyTyped(KeyEvent e) {

    }
    public void releaseClick() {
        placeBlock();
        say(": " + "");
    }


}

My other related classes so that you can run it if nessessary

import java.util.ArrayList;

public class Tower {
    ArrayList<ArrayList<ArrayList<Block>>> map = new ArrayList<ArrayList<ArrayList<Block>>>();
    int blockw = 30, blockh = 30, blockd = 30;
    private int WIDTH;private int HEIGHT;
    int x;int y;int z;int d;int w;int h;
    int battlementSize = 1;
    public Tower(int WIDTH, int HEIGHT, int towerw, int towerh, int towerd){

        this.WIDTH = WIDTH;
        this.HEIGHT = HEIGHT;

        ArrayList<Block> row;
        ArrayList<ArrayList<Block>> slice;
        ArrayList<ArrayList<ArrayList<Block>>> height;
        ArrayList<ArrayList<Block>> battlements;
        ArrayList<Block> battlementsBlocks;

        x = ((WIDTH/2)-(towerw*blockw)/2)+(0);
        y = ((HEIGHT/2)-(towerh*blockh)/2)+(0);
        z = 0;
        d = (towerd+battlementSize)*blockd;
        w = towerw*blockw;
        h = towerh*blockh;

        height = new ArrayList<ArrayList<ArrayList<Block>>>();
        for (int d=0; d<towerd;d++){
            slice = new ArrayList<ArrayList<Block>>();
            for (int h=0; h<towerh;h++){
                row = new ArrayList<Block>();
                for (int w=0; w<towerw;w++){
                    row.add(new Block(x+(w*blockw), y+(h*blockh), (d*blockd), blockw, blockh, blockd, new BlockType("stone")));
                }
                slice.add(row);
            }
            height.add(slice);
        }
        battlements = new ArrayList<ArrayList<Block>>();
        for (int h=0;h<towerh;h++){
            battlementsBlocks = new ArrayList<Block>();
            for(int w=0;w<towerw;w++){
                battlementsBlocks.add(new Block(x+(w*blockw), y+(h*blockh), (height.size()*blockd), blockw, blockh, blockd, new BlockType("air")));
            }
            battlements.add(battlementsBlocks);
        }
        height.add(battlements);
        map = height;
    }
}

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class Block {
    BlockType block;
    int x,y,z,w,h,d;
    public Block(int x, int y, int z, int w, int h, int d, BlockType block){
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
        this.h = h;
        this.d = d;

        this.block = block;

        initialize();
    }
    private void initialize(){

    }
    public void draw(Graphics2D g2d, AffineTransform normal, Main observer){
        g2d.setTransform(normal);

        g2d.drawImage(this.block.top1, x-d-z,y-d-z, observer);

        g2d.transform(AffineTransform.getShearInstance(1, 0));
        g2d.drawImage(this.block.side1, x-y-w, y-z-(d-h), observer);

        g2d.setTransform(normal);
        g2d.transform(AffineTransform.getShearInstance(0, 1));
        g2d.drawImage(this.block.side2, x-z-(d-w), y-x-h, observer);
    }
}

import java.awt.Image;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class BlockType {
    String type;
    Image side1, side2, top1;
    public BlockType(String type){
        this.type = type;
        if (type.equals("stone")){
            File fside1 = new File("side1.png");
            File fside2 = new File("side2.png");
            File ftop1 = new File("top1.png");
            try {
                side1 = ImageIO.read(fside1);
                side2 = ImageIO.read(fside2);
                top1 = ImageIO.read(ftop1);
            } catch (IOException e) {

            }
        }
    }
}

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class Entity {
    int velx = 0;
    int vely = 0;
    int velz = 0;
    int speed = 2;
    int x; int y; int z; int w; int h; int d;
    Image side1;
    Image side2;
    Image top1;
    public Entity(int x, int y, int z, int w, int h, int d){
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
        this.h = h;
        this.d = d;
        assignImages();
    }
    private void assignImages(){
        File fside1 = new File("side1.png");
        File fside2 = new File("side2.png");
        File ftop1 = new File("top1.png");
        try {
            side1 = (ImageIO.read(fside1)).getScaledInstance(w, d, Image.SCALE_DEFAULT);
            side2 = (ImageIO.read(fside2)).getScaledInstance(d, h, Image.SCALE_DEFAULT);
            top1 = (ImageIO.read(ftop1)).getScaledInstance(w, h, Image.SCALE_DEFAULT);
        } catch (IOException e) {

        }
    }
    public void update(){
        this.x+=this.velx;
        this.y+=this.vely;
        this.z+=this.velz;

        if (this.z < 0){
            this.z=0;
        }
    }
    public void draw(Graphics2D g2d, AffineTransform normal, Main observer){
        // entity
        g2d.setTransform(normal);
        g2d.drawImage(this.top1,this.x-this.d-this.z,this.y-this.d-this.z,observer);

        g2d.transform(AffineTransform.getShearInstance(1, 0));
        g2d.drawImage(this.side1, this.x-this.y-this.w, this.y-this.z-(this.d-this.h), observer);       

        g2d.setTransform(normal);   
        g2d.transform(AffineTransform.getShearInstance(0, 1));  
        g2d.drawImage(this.side2, this.x-this.z-(this.d-this.w),this.y-this.x-this.h, observer);
    }
}

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class MouseHandler implements MouseListener{
    Main main;
    public MouseHandler(){
        main = new Main(0);
    }
    @Override
    public void mouseClicked(MouseEvent arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void mouseEntered(MouseEvent arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void mouseExited(MouseEvent arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void mousePressed(MouseEvent arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void mouseReleased(MouseEvent arg0) {
        main.releaseClick();
    }
}
\$\endgroup\$
  • 2
    \$\begingroup\$ How does your code fail to reach the desired results? A series of screenshots illustrating cases which are handled successfully and cases which are handled incorrectly would really help to illustrate the problem. Also, I would recommend you to reduce your code to only the parts relevant for the drawing order. The more code you post, the less people will actually read it. \$\endgroup\$ – Philipp Jul 3 '16 at 20:36
  • \$\begingroup\$ Your example image makes my vision go blurry. I can't even see what's at fault. \$\endgroup\$ – Shaun Wild Jul 4 '16 at 11:51
  • \$\begingroup\$ @AlanTuning Its a bad image to demonstrate the issue. Editing question \$\endgroup\$ – Colourfit Jul 4 '16 at 16:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.