Slick2D and JBox2D. How to draw

Romeo picture Romeo · Apr 3, 2012 · Viewed 10.6k times · Source

Before asking this i did A LOT of searching on the net. I just can't do it. It's a little hard for me to understand. So how do i draw the images at the right screen positions coresponding to bodies in world position? Thanx.

If anyone else finds himslef in front of the same obstacle i posted a HOW TO, thanx to normalocity's good explanation. You can find it here: http://romeo.akademx.ro/2012/04/06/slick-and-box2d/

This is the render function:

public void render(GameContainer container, StateBasedGame game, Graphics g)
        throws SlickException {
    g.setBackground(Color.white);

    g.pushTransform();
    g.translate(worldToScreen(body.getPosition()).x, worldToScreen(body.getPosition()).y);
    g.rotate(15, 15, (float) Math.toDegrees(body.getAngle()));
    part.draw();
    g.popTransform();

    g.drawString("Count: " + cont, 5, 40);
    //world.drawDebugData();
}

And these are the function i use to transform world-screen coordinations:

public Vec2 screenToWorld(Vec2 screenV) {
    return new Vec2((screenV.x - offset.x) / scaleFactor, yFlip
            * (screenV.y - offset.y) / scaleFactor);
}

public Vec2 worldToScreen(Vec2 worldV) {
    return new Vec2(worldV.x * scaleFactor + offset.x, yFlip * worldV.y
            * scaleFactor + offset.y);
}

I also happen to use the SlickDebugDraw found at this link: http://slick.javaunlimited.net/viewtopic.php?f=19&t=3610&sid=69614ac53aaf5724b808b75173e8e48e

But his DebugDraw draws a completely another thing then my render function. I'm a bit confused.

Answer

jefflunt picture jefflunt · Apr 4, 2012

If all you're doing is drawing sprites in a 2D world, then there's basically two things you need to keep track of in order to decide which sprites to draw on the screen and where on the screen to draw them. You have to think of your sprites as existing at a certain location in the world, and what you see on the screen as just one view of the world, focusing on an area.

The two things you need to keep track of are:

  1. Each sprite needs to have its location within the world
  2. Your "camera" needs to track its location relative to the world.

So, let's say you have a big, big world, with a 2D coordinate (x, y) space of 1,000,000 x 1,000,000 pixels (I'm using pixels as the unit of measure here, but that's an arbitrary choice, and the size of the world doesn't matter, I've just chosen a big one). Then, let's say you have a "camera" that's pointed at that world, and the view of that camera is what is displayed on your screen. The display that camera gives you is 1024x768 pixels in size.

Let's also say that you use the arrow keys to move that camera around the world.

So, your world's coordinate space maps to your screen as such:

(0, 0)        +x
      +------------------>
      |
   +y |
      |      *  <= example sprite in your world @ coordinates (x=200, y=200)
      |
     \ /

When your sprites move "right" they increase their x coordinate. When they move "left" they decrease their x coordinate. When moving "up" they decrease their y coordinate (because y increases downward, on monitor displays), and when moving "down" sprites increase their y coordinate.

Now, again, what you see in our screen is just the camera's view of the world. So, let's set that the camera's upper-left corner is at (x=500, y=500). That would look something like:

(0, 0)        +x
      +---------------------------------->
      |
   +y |
      |      *  <= example sprite in your world @ coordinates (x=200, y=200)
      |
      |
      |         +===================+
      |         |     the area      |
      |         |  that the camera  |
      |         |    "sees" and     |
      |         |   shows on your   |
      |         |       screen      |
      |         +===================+
     \ /

With that setup, let's say that the camera is at (500, 500) (that is, the upper-left corner of the camera's view, as shown in this example, is at the world coordinates (500, 500). And because the camera shows you an area that is 1024x768 is size, then the opposite, lower-right corner is (500+1024, 500+768) = (x=1524, y=1268).

Note that the sprite in our world is not inside that camera's view area. That means, when we render the camera's view on the screen, we won't see the sprite.

If, instead, the camera moved to (200, 200), then the view area of the camera would cover the world coordinates from upper-left @ (200, 200) to lower-right @ (1224, 968), and look something like this:

(0, 0)        +x
      +---------------------------------->
      |   
   +y |  +===================+
      |  |                   |
      |  |   * <= sprite     |
      |  |                   |
      |  |                   | <= camera's view of the world
      |  +===================+
      |
      |
      |
      |
     \ /

When the camera is in this position, the sprite is visible. If the sprite is @ (500, 500), and the camera is at (200, 200), then when we draw the sprite on the screen, the sprite will appear, on our screen at coordinates 300, 300.

Why?

Because, and this is really the answer to your question, where you draw things on the screen is sprite's world location (500, 500), minus the camera's location (200, 200), which equals (300, 300).

So, to review:

You move the camera's position around the world using the arrow keys (or the mouse, or whatever other control scheme you want), and you render the sprites location relative to the camera position, by taking the sprite's position and subtracting the camera's position, and what you get are the screen coordinates where the sprite should appear.

But there's one more thing...

It's really inefficient to draw every sprite in the world. You only need to draw the sprites that are within the camera's view, otherwise you're drawing things that you won't see on your screen, and therefore, wasting rendering/CPU/GPU time.

So, when you're rendering the camera's view, you need to iterate through your sprites, checking to see if they are "on camera" (that is, whether or not they're within the view of the camera), and only drawing them if they are within this view.

In order to do that, you have to take the dimensions of your camera (1024x768, in our example), and check to see if the sprite's position is inside the rectangle of the camera's view - which is the position of the camera's upper-left corner, plus the camera's width and height.

So, if our camera shows us a view that's 1024x768 pixels in size, and it's upper-left corner is at (200, 200), then the view rectangle is:

(200, 200)                      (1224, 200)
           +===================+
           |                   |
           |    *              |
           |                   |
           |                   |
           +===================+
(200, 968)                      (1224, 968)

The sprite's position @ (500, 500) is within the camera's view, in this case.

If you need more examples, I have a working Slick2D tech demo, called Pedestrians, that has code you can look at. For details on how I calculate the area of the world that should be rendered, look at the render method inside this file, and pay special attention to the startX, startY, stopX, stopY variables, which for me control the area of sprites I'm going to draw. It should also be noted that my sprites (or "pedestrians") exist on a TileMap, so they aren't 1 pixel is size - they have a width and height of their own. This adds a small bit of complexity to how to decide what to draw, but it basically comes down to, "draw what's within the camera's view, plus a little extra around the edges."

Clone the Pedeestrians repository on your own machine, get it working by adding the same dependencies to the project as any other Slick2D project, and play/modify the rendering code until you understand what's going on. Only through practice and study will you get all the little ins/outs of how this works. The good news is that once you figure out how to do rendering with this basic 2D world vs. camera method, you'll pretty much know how to render graphics for all 2D apps, because the concepts translate to all languages.

I also have various videos of Pedestrians being run on my YouTube channel (the most relevant video is probably this one, which shows my basic pedestrians being rendered, and the camera moving around), so you can see what this all looks like without having to build the project first.