Using offscreen rendering for a minimap

There are generally two ways of creating minimaps. One way is to let an artist draw a representation of the map, as shown in the following screenshot. This usually ends up beautifully as it gives considerable freedom to the artist when it comes to style. The method is not that viable during development when scenes might be changing a lot, or for games with procedural content where the end result is not known beforehand.

Using offscreen rendering for a minimap

Minimap with unit marker

In those cases, taking a snapshot of the actual scene can be very helpful. The resulting image can then be run through various filters (or shaders during rendering) to get a less raw look.

In this recipe, we'll achieve this by creating a new ViewPort port, and FrameBuffer to store a snapshot of a camera. Finally, we'll create NiftyImage out of it and display it as a GUI element.

How to do it...

We're going to start by creating a Util class to handle the rendering of our minimap. This will consist of the following 15 steps:

  1. Define a new class called MinimapUtil.
  2. It will only have one static method, createMiniMap, with the following declaration:
    public static void createMiniMap(final SimpleApplication app, final Spatial scene, int width, int height)
  3. The first thing we do is create a new camera called offScreenCamera with the same width and height that were supplied to the method.
  4. The camera should have the parallel projection set to true, and a frustrum that spans between 1 and 1000 in depth, -width to width, and -height to height, as shown in the following code:
    offScreenCamera.setParallelProjection(true);
    offScreenCamera.setFrustum(1, 1000, -width, width, height, -height);
  5. It should be located at some distance above the scene and rotated downwards, as shown in the following code:
    offScreenCamera.setLocation(new Vector3f(0, 100f, 0));
    offScreenCamera.setRotation(new Quaternion().fromAngles(new float[]{FastMath.HALF_PI,FastMath.PI,0}));
  6. Next, we create a new ViewPort by calling the application's RenderManager and its createPreView method using offScreenCamera:
    final ViewPort offScreenView = app.getRenderManager().createPreView(scene.getName() + "_View", offScreenCamera);
    offScreenView.setClearFlags(true, true, true);
    offScreenView.setBackgroundColor(ColorRGBA.DarkGray.mult(ColorRGBA.Blue).mult(0.3f));
  7. Now, we need a Texture2D class to store the data in, so we create a class called offScreenTexture with the same width and height as before and set MinFilter to Trilinear:
    final Texture2D offScreenTexture = new Texture2D(width, height, Image.Format.RGB8);
    offScreenTexture.setMinFilter(Texture.MinFilter.Trilinear);
  8. A FrameBuffer class is needed as a medium for the data, so we create one with the same width and height, and 1 sample, as shown in the following code:
    FrameBuffer offScreenBuffer = new FrameBuffer(width, height, 1);
  9. We set DepthBuffer to be Image.Format.Depth and offScreenTexture to be ColorTexture:
    offScreenBuffer.setDepthBuffer(Image.Format.Depth);
    offScreenBuffer.setColorTexture(offScreenTexture);
  10. Then, we set outPutFrameBuffer of offScreenView to be offScreenBuffer:
    offScreenView.setOutputFrameBuffer(offScreenBuffer);
  11. Unless the scene we supplied already has some lights, we should add at least one Light class to it.
  12. Then, we attach the scene to offScreenView:
    offScreenView.attachScene(scene);
  13. To store the texture, we can add it to AssetManager with the following line:
    ((DesktopAssetManager)app.getAssetManager()).addToCache( new TextureKey(scene.getName()+"_mini.png", true), offScreenTexture);
  14. Now, we can do the actual rendering by calling the application's renderManager and renderViewPort methods:
    app.getRenderManager().renderViewPort(offScreenView, 0);
  15. After this, we're done and can call removePreview to discard offScreeenView:
    app.getRenderManager().removePreView(offScreenView);

With the Util class done, we can create a screen Controller class. Perform the following additional six steps to do this:

  1. Create a new class called GameScreenController that extends NiftyController.
  2. For now, it only needs one public method called createMinimap that takes a scene as the input.
  3. The first thing the createMinimap method should do is call MiniMapUtil.createMinimap.
  4. With the scene rendered, we can create NiftyImage with the nifty.createImage method.
  5. Then, we can apply the image to our minimap element in the Nifty screen with the following line:
    screen.findElementByName("minimap").getRenderer(ImageRenderer.class).setImage(image);
  6. Now, all we need to do is add a panel element called minimap to a screen that uses GameScreenController as the controller.

How it works...

Offscreen rendering is just what it sounds like. We render something in a view that is not related to the main view that the player sees. To do this, we set up a new viewport and camera. It's not possible to render something directly to a texture, which is why FrameBuffer is used as the medium.

Once the texture object is created and added to the asset manager, it's possible to keep changing it if we would like to at a later stage. It's even possible to have a live view of the scene in the minimap, although this would probably cost unnecessary resources. In this case, we remove the view as soon as we've rendered it once.

The example is limited in some sense, like it expects that there is a correlation between the size of the scene and the size of the minimap.

Nifty uses its own image format, NiftyImage, so we need to convert the image we saved; however, Nifty's createImage will automatically find the texture in the asset manager based on the name (key).

There's more…

Usually, on a minimap, players will want some kind of indication about their (and others) whereabouts. Let's implement that in the minimap we just created:

  1. First of all, we need to change the minimap element in our screen a bit. We set childLayout to absolute and add another panel inside it called playerIcon with a small width and height.
  2. Next, we add a new Element field called playerIcon to the GameScreenController and use findElementByName in the bind method to set it.
  3. Then, we add another method called updatePlayerPosition with two integers, x and y, as the input.
  4. This method should use setConstraintX and setConstraintY on the playerIcon element to set the position. Those methods take SizeValue as the input, and we supply the x and y values with the "px" definition.
  5. Finally, in the same method, we need to call layoutElements() on the minimap element to make it update its child elements.

For other things, such as visible enemies, we can use the builder interface to create them as and when we need them and then use markForRemoval to remove them when they're not needed anymore. An example of this process can be seen in the Handling a game message queue recipe.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.176.194