2.5d Isometric view?

Hey all,

I’m messing around with a game and having a hell of a time trying to get 2D isometric working happily. Basically the game logic all happens using a square unrotated 2D grid, then after drawing the background I’m trying just to rotate/translate everything else to match the background image. This isn’t working.

Aside from the fact that the dimensions of a space at a given position are completely different than those at another point, there’s a lot of other annoying crap to consider which is giving me a massive headache.

The easiest thing would obviously be to have a bunch of isometric sprites and then draw them at the correct locations and just translate/rotate/scale everything the same amount. Doing so would obviously make the background match up exactly with the active sprites.

However, I’ve got a 2D isometric background image drawn by an artist, so I’ve got to somehow match to that. It seems like a fool’s errand. Does anyone have any pointers or methods they do when writing a game using 2.5d? If your advice is “there’s no way to really match up with an artist’s image unless you know the exact skew it had,” then that of course makes sense. But I’m curious if anyone has done 2D isometric using backgrounds, rather than systematically placed tiles.

Thanks.

If you don’t know the transform of the background images, you’re fairly boned. Ask the artist, or try to calculate it yourself.

One way would be to place a grid along the floor plane of the original background image, then calculating the tilt/rotation/skew/whatever from that.

OR you could just hack it until it looks good enough. :wink:

This is my OpenGL projection matrix for isometric transforms:

http://www.java-gaming.org/index.php/topic,10237.msg82081.html#msg82081 (scroll to the screenshot)
http://www.java-gaming.org/index.php/topic,10237.msg83479.html#msg83479

You can probably use your own Matrix classes to do this operation in software.

Just feed modelspace 3d points and you’ll get (normalized) 2d screen coords.
Scale and translate until it fits your artists image.

I’ve been trying to hack it with little success because of various issues, most prevalent of which is that I’m not very good at 3D. Pretty much I’ve tried getting away with it just by applying a rotation along the center, which kinda sorta works but doesn’t really line up properly.

Maybe if I better explain exactly what I’ve got that’ll help.

Here’s what I’ve been working with:

  1. The artist draw a simple grid-based “floor plan” that had no perspective at all - i.e. was completely from a top-down view.
  2. The artist applied Adobe Illustrator’s 3D transform function to the grid, with the rotations of these values along the axes: (30,26,-37).
  3. I’ve got my game in ortho mode, so if I try to simply apply that same rotation to everything it goes fairly nuts, and my sprites are squeezed in a fairly screwed up way. The only thing it appears I can do are rotations, translations, and scales along the X and Y axes only. Therefore it becomes impossible to directly emulate this viewpoint.

So, any ideas, anyone? It seems like since I’ve got these coordinates I should be all set, but that’s unfortunately not the case because the transformations applied are not ortho like my game. Any way to easily get that matching?

Did you try the matrix I gave you?

Isometric projection is simply:

  1. looking at the world top-down
  2. rotating the world 45deg (both FLAT axis… either X,Y or X,Z… affect eachother)
  3. subtracting UP axis (in modelspace) from Y (in screenspace) to make higher units go to the top of the screen
  4. scaling the screenspace X or Y, to make the tiles look stretched horizontally.

My projection matrix does just that.

If your unit is at XYZ, pass the vector through the matrix and you get the XY (and depth) coordinates.

Just to clarify: you’d pass in 1 vector per unit/item. You’d never need to do any transforms in java.awt.Graphics

I didn’t try it because I wouldn’t know what to do with it :P.

Here’s the extent of my OpenGL transformation knowledge:


glTranslatef();
glRotatef();
glScalef();

So if you tell me to “apply a matrix” I won’t really know how. :slight_smile: Similarly, I don’t know if an exact isometric matrix transformation would work, because the grid got moved by (30,26,-37), which isn’t exact.

I might need a little babying here, or you can just tell me to sod off and read an OpenGL book. I wouldn’t mind some babying, though. :wink:

:wink:


import java.awt.Color;
import java.awt.Graphics;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Isometric extends JPanel
{
   public static void main(String[] args)
   {
      JFrame frame = new JFrame();
      frame.getContentPane().add(new Isometric());
      frame.setSize(640, 480);
      frame.setVisible(true);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   }

   @Override
   protected void paintComponent(Graphics g)
   {
      super.paintComponent(g);

      g.setColor(Color.WHITE);

      final int tileCount = 16;
      final int tileSize = 8;

      Color[] colors = new Color[3];
      colors[0] = Color.RED;
      colors[1] = Color.GREEN;
      colors[2] = Color.BLUE;

      Color[] tileColors = new Color[tileCount * tileCount];
      float[][] tileMidPoints = new float[tileCount * tileCount][3];

      // create random world
      for (int y = 0; y < tileCount; y++)
      {
         for (int x = 0; x < tileCount; x++)
         {
            int i = y * tileCount + x;

            tileColors[i] = colors[(int) (Math.random() * colors.length)];

            float[] point = tileMidPoints[i];
            point[0] = x;
            point[1] = 0.0f;
            point[2] = y;
         }
      }

      // render top down
      for (int i = 0; i < tileCount * tileCount; i++)
      {
         int x = i % tileCount;
         int y = i / tileCount;

         g.setColor(tileColors[i]);
         g.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
      }

      // render isometric
      Matrix4 iso = Isometric.createIsometricMatrix();

      for (int i = 0; i < tileCount * tileCount; i++)
      {
         float[] model = tileMidPoints[i];
         float[] view = new float[3];

         iso.transform(model, view);

         float x = view[0];
         float y = view[1];

         // scale
         x *= 10.0f;
         y *= 10.0f;

         // translate
         x += this.getWidth() / 2;
         y += this.getHeight() / 2;

         int xi = (int) x;
         int yi = (int) y;
         int r = 4;

         g.setColor(tileColors[i]);
         g.fillRect(xi - r, yi - r, r * 2 + 1, r * 2 + 1);
      }
   }

   private static Matrix4 createIsometricMatrix()
   {
      // --> 1.0, 0.0, -1.0, 0.0 // x
      // --> 0.5, 2.0, 0.5, 0.0 // y
      // --> 0.0, -0.05, 0.0, 0.0 // depth
      // --> 0.0, 0.0, 0.0, 1.0 // [nothing]

      Matrix4 iso = new Matrix4();
      iso.m00 = iso.m33 = 1.0f;
      iso.m10 = iso.m12 = 0.5f;
      iso.m11 = 2.0f;
      iso.m02 = -1.0f;
      iso.m21 = -0.05f;
      return iso;
   }

   public static class Matrix4
   {
      public float m00, m01, m02, m03;
      public float m10, m11, m12, m13;
      public float m20, m21, m22, m23;
      public float m30, m31, m32, m33;

      public void transform(float[] in, float[] out)
      {
         float x = in[0];
         float y = in[1];
         float z = in[2];

         out[0] = m00 * x + m01 * y + m02 * z + m03;
         out[1] = m10 * x + m11 * y + m12 * z + m13;
         out[2] = m20 * x + m21 * y + m22 * z + m23;
      }
   }
}


Oh, and it might look like a hexagonal map, but that’s just because I just rectangles instead of polygons to render the isometric tiles.

I was too lazy, but well, here you go:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Isometric extends JPanel
{
   public static void main(String[] args)
   {
      JFrame frame = new JFrame();
      frame.getContentPane().add(new Isometric());
      frame.setSize(640, 480);
      frame.setVisible(true);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   }

   @Override
   protected void paintComponent(Graphics g)
   {
      super.paintComponent(g);

      g.setColor(Color.WHITE);

      final int tileCount = 16;
      final int tileSize = 8;

      Color[] colors = new Color[3];
      colors[0] = Color.RED;
      colors[1] = Color.GREEN;
      colors[2] = Color.BLUE;

      List<Color> colorList = new ArrayList<Color>();
      List<float[]> quads = new ArrayList<float[]>();
      List<Boolean> isTile = new ArrayList<Boolean>();

      // create tile grid
      for (int y = 0; y < tileCount; y++)
      {
         for (int x = 0; x < tileCount; x++)
         {
            float[] point = new float[3];
            point[0] = x;
            point[1] = 0.0f;
            point[2] = y;

            quads.add(point);
            colorList.add(colors[(int) (Math.random() * colors.length)]);
            isTile.add(Boolean.TRUE);
         }
      }

      // nice arcs (3d)
      for (int k = 0; k <= tileCount; k += 4)
      {
         for (int i = 0; i <= 10000; i++)
         {
            float v = k / (float)tileCount;
            float u = i / 10000.0f;

            float[] point = new float[3];
            point[0] = v * (tileCount-1);
            point[1] = (float) -Math.sqrt(Math.sin(u * Math.PI)) * 5.0f;
            point[2] = u * (tileCount-1);

            quads.add(point);
            colorList.add(Color.BLACK);
            isTile.add(Boolean.FALSE);
         }
      }

      // render top down
      for (int i = 0; i < quads.size(); i++)
      {
         float[] point = quads.get(i);

         int x = (int) point[0];
         int y = (int) point[2];

         g.setColor(colorList.get(i));
         g.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
      }

      // render isometric
      Matrix4 iso = Isometric.createIsometricMatrix(10, this.getWidth() / 2, this.getHeight() / 2);

      final float[] view = new float[3];

      final float[] xRel = new float[] { -0.5f, -0.5f, +0.5f, +0.5f };
      final float[] yRel = new float[] { -0.5f, +0.5f, +0.5f, -0.5f };

      for (int i = 0; i < quads.size(); i++)
      {
         g.setColor(colorList.get(i));

         if (!isTile.get(i).booleanValue())
         {
            float[] model = quads.get(i).clone();

            iso.transform(model, view);

            g.fillOval((int) view[0] - 2, (int) view[1] - 2, 5, 5);
         }
         else
         {
            Polygon p = new Polygon();
            for (int k = 0; k < 4; k++)
            {
               float[] model = quads.get(i).clone();

               // translate to tile corners in model space
               model[0] += xRel[k];
               model[2] += yRel[k];

               iso.transform(model, view);

               p.addPoint((int) view[0], (int) view[1]);
            }

            g.fillPolygon(p);
         }
      }
   }

   private static Matrix4 createIsometricMatrix(int scale, int x, int y)
   {
      // --> 1.0, 0.0, -1.0, 0.0 // x
      // --> 0.5, 2.0, 0.5, 0.0 // y
      // --> 0.0, -0.05, 0.0, 0.0 // depth
      // --> 0.0, 0.0, 0.0, 1.0 // [nothing]

      Matrix4 iso = new Matrix4();
      iso.m00 = iso.m33 = 1.0f * scale;
      iso.m10 = iso.m12 = 0.5f * scale;
      iso.m11 = 2.0f * scale;
      iso.m02 = -1.0f * scale;
      iso.m21 = -0.05f * scale;
      iso.m03 = x;
      iso.m13 = y;
      return iso;
   }

   public static class Matrix4
   {
      public float m00, m01, m02, m03;
      public float m10, m11, m12, m13;
      public float m20, m21, m22, m23;
      public float m30, m31, m32, m33;

      public void transform(float[] in, float[] out)
      {
         float x = in[0];
         float y = in[1];
         float z = in[2];

         out[0] = m00 * x + m01 * y + m02 * z + m03;
         out[1] = m10 * x + m11 * y + m12 * z + m13;
         out[2] = m20 * x + m21 * y + m22 * z + m23;
      }
   }
}


Sorry for the messy code, but you should get started with that.

The arc looks weird, because it is composed out of tiles…

Thanks for the babying, I’ll try that out. :slight_smile:

I updated the arcs a bit, so that you wouldn’t get the idea that they were skewed.