Hello I am an inexperienced programmer and this is my first question on Stack Overflow!
I am attempting to implement 'fog of war' in my Java game. This means most of my map begins off black and then as one of my characters moves around parts of the map will be revealed. I have searched around including here and found a few suggestions and tried tweaking them myself. Each of my approaches works, however I run into significant runtime issues with each. For comparison, before any of my fog of war attempts I was getting 250-300 FPS.
Here is my basic approach:
Render my background and all objects on my JPanel
Create a black BufferedImage (fogofwarBI)
Work out which areas of my map need to be visible
Set the relevant pixels on my fogofwarBI to be fully transparent
Render my fogofwarBI, thus covering parts of the screen with black and in transparent sections allowing the background and objects to be seen.
For initialising the buffered image I have done the following in my FogOfWar() class:
private BufferedImage blackBI = loader.loadImage("/map_black_2160x1620.png");
private BufferedImage fogofwarBI = new BufferedImage(blackBI.getWidth(), blackBI.getHeight(), BufferedImage.TYPE_INT_ARGB);
public FogOfWar() {
fogofwarBI.getGraphics().drawImage(blackBI,0,0,null);
}
In each of my attempts I start the character in a middle of 'visible' terrain, ie. in a section of my map which has no fog (where my fogofwarBI will have fully transparent pixels).
Attempt 1: setRGB
First I find the 'new' coordinates in my character's field of vision if it has moved. ie. not every pixel within the character's range of sight, but just the pixels at the edge of his range of vision in the direction he is moving. This is done with a for loop, and will go through up to 400 or so pixels.
I feed each of these x and y coordinates into my FogOfWar class.
I check if these x,y coordinates are already visible (in which case I don't bother doing anything to them to save time). I do this check by maintaining a Set of Lists. Where each List contains two elements: an x and y value. And the Set is a unique set of the coordinate Lists. The Set begins empty, and I will add x,y coordinates to represent transparent pixels. I use the Set to keep the collection unique and because I understand the List.contains function is a fast way of doing this check. And I store the coordinates in a List to avoid mixing up x and y.
If a given x,y position on my fogofwarBI is not currently visible I add set the RBG to be transparent using .setRGB, and add it to my transparentPoints Set so that coordinate will not be edited again in future.
Set<List<Integer>> transparentPoints = new HashSet<List<Integer>>();
public void editFog(int x, int y) {
if (transparentPoints.contains(Arrays.asList(x,y)) == false){
fogofwarBI.setRGB(x,y,0); // 0 is transparent in ARGB
transparentPoints.add(Arrays.asList(x,y));
}
}
I then render it using
public void render(Graphics g, Camera camera) {
g.drawImage(fogofwarBI, 0, 0, Game.v_WIDTH, Game.v_HEIGHT,
camera.getX()-Game.v_WIDTH/2, camera.getY()-Game.v_HEIGHT/2,
camera.getX()+Game.v_WIDTH/2, camera.getY()+Game.v_HEIGHT/2, null);
}
Where I am basically applying the correct part of my fogofwarBI to my JPanel (800*600) based on where my game camera is.
Results:
Works correctly.
FPS of 20-30 when moving through fog, otherwise normal (250-300).
This method is slow due to the .setRGB function, being run up to 400 times each time my game 'ticks'.
Attempt 2: Raster
In this attempt I create a raster of my fogofwarBI to play with the pixels directly in an array format.
private BufferedImage blackBI = loader.loadImage("/map_black_2160x1620.png");
private BufferedImage fogofwarBI = new BufferedImage(blackBI.getWidth(), blackBI.getHeight(), BufferedImage.TYPE_INT_ARGB);
WritableRaster raster = fogofwarBI.getRaster();
DataBufferInt dataBuffer = (DataBufferInt)raster.getDataBuffer();
int[] pixels = dataBuffer.getData();
public FogOfWar() {
fogofwarBI.getGraphics().drawImage(blackBI,0,0,null);
}
My editFog method then looks like this:
public void editFog(int x, int y) {
if (transparentPoints.contains(Arrays.asList(x,y)) == false){
pixels[(x)+((y)*Game.m_WIDTH)] = 0; // 0 is transparent in ARGB
transparentPoints.add(Arrays.asList(x,y));
}
}
My understanding is that the raster is in (constant?) communication with the pixels array, and so I render the BI in the same way as in attempt 1.
Results:
Works correctly.
A constant FPS of around 15.
I believe it is constantly this slow (regardless of whether my character is moving through fog or not) because whilst manipulating the pixels array is quick, the raster is constantly working.
Attempt 3: Smaller Raster
This is a variation on attempt 2.
I read somewhere that constantly resizing a BufferedImage using the 10 input version of .drawImage is slow. I also thought that having a raster for a 2160*1620 BufferedImage might be slow.
Therefore I tried having my 'fog layer' only equal to the size of my view (800*600), and updating every pixel using a for loop, based on whether the current pixel should be black or visible from my standard transparentPoints Set and based on my camera position.
So now my editFog Class just updates the Set of invisible pixels and my render class looks like this:
public void render(Graphics g, Camera camera) {
int xOffset = camera.getX() - Game.v_WIDTH/2;
int yOffset = camera.getY() - Game.v_HEIGHT/2;
for (int i = 0; i<Game.v_WIDTH; i++) {
for (int j = 0; j<Game.v_HEIGHT; j++) {
if ( transparentPoints.contains(Arrays.asList(i+xOffset,j+yOffset)) ) {
pixels[i+j*Game.v_WIDTH] = 0;
} else {
pixels[i+j*Game.v_WIDTH] = myBlackARGB;
}
}
}
g.drawImage(fogofwarBI, 0, 0, null);
}
So I am no longer resizing my fogofwarBI on the fly, but I am updating every single pixel every time.
Result:
Works correctly.
FPS: Constantly 1 FPS - worst result yet!
I guess that any savings of not resizing my fogofwarBI and having it smaller are massively outweighed by updating 800*600 pixels in the raster rather than around 400.
I have run out of ideas and none of my internet searching is getting me any further in trying to do this in a better way. I think there must be a way to do fog of war effectively, but perhaps I am not yet familiar enough with Java or the available tools.
And pointers as to whether my current attempts could be improved or whether I should be trying something else altogether would be very much appreciated.
Thanks!
This is a good question. I am not familar with the awt/swing type rendering, so I can only try to explain a possible solution for the problem.
From a performance standpoint I think it is a better choice to chunk/raster the FOW in bigger sections of the map rather than using a pixelbased system. That will reduce the amount of checks per tick and updating it will also take less resources, as only a small portion of the window/map needs to update. The larger the grid, the less checks, but there is a visual penalty the bigger you go.
Leaving it like that would make the FOW look blocky/pixelated, but its not something you can't fix.
For the direct surrounding of a player, you can add a circle texture with the player at its center. You can than use blending (I believe the term in awt/swing is composite) to 'override' the alpha where the circle overlaps the FOW texture. This way the pixel-based updating is done by the renderingAPI which usually uses hardware enhanced methods to achieve these things. (for custom pixel-based rendering, something like 'shader scripts' are often used if supported by the rendering API)
This is enough if you only need temporary vission in the FOW (if you don't need to 'remember' the map), you don't even need a texture grid for the FOW than, but I suspect you do want to 'remember' the map. So in that case:
The blocky/pixelated look can be fixed like they do with grid-based terain. Basically add a small additional textures/shapes based on the surroundings to make things look nice. The link below provides good examples and a detailed explanation on how to do the 'terrain-transitions' as they are called.
https://www.gamedev.net/articles/programming/general-and-gameplay-programming/tilemap-based-game-techniques-handling-terrai-r934/
I hope this gives a better result. If you cannot get a better result, I would advise switching over to something like OpenGL for the render engine as it is meant for games, while the awt/swing API is primarely used for UI/application rendering.
Related
I'm trying to figure out how to use my thermal sensor to change colors to an overlay that I have over the Android camera. The problem is that the data I get back is in a 16x4 array. How do I resize this 16x4 grid to a different resolution? Such as 32x8, 48x12...etc.
Edit:
For instance, I have this as my draw method:
public void onDraw(Canvas canvas){
super.onDraw(canvas);
for(int x = 0; x < 4; x++){
for(int y = 0; y < 16; y++){
// mapping 2D array to 1D array
int index = x*GRID_WIDTH + y;
tempVal = currentTemperatureValues.get(index);
// 68x68 bitmap squares to display temperature data
if(tempVal >= 40.0)
bitmaps[x][y].eraseColor(Color.RED);
else if(tempVal < 40.0 && tempVal > 35.0)
bitmaps[x][y].eraseColor(Color.YELLOW);
else
bitmaps[x][y].eraseColor(Color.BLUE);
}
}
combinedBitmap = mergeBitmaps();
// combinedBitmap = fastblur(combinedBitmap, 45);
paint.setAlpha(alphaValue);
canvas.drawBitmap(combinedBitmap, xBitmap, yBitmap, paint);
Log.i(TAG,"Done drawing");
}
The current implementation is to draw to a 16x4 overlay over my camera preview, but resolution is very low, and I'd like to improve it the best I can.
The Bitmap class in the Android API (that's what I'm assuming you're using) has a static method called createScaledBitmap: http://developer.android.com/reference/android/graphics/Bitmap.html#createScaledBitmap%28android.graphics.Bitmap,%20int,%20int,%20boolean%29
What this method does is that it accepts an already created Bitmap, you specify the final width and height dimensions as well as a boolean flag called filter. Setting this to false does nearest neighbour interpolation while true does bilinear interpolation.
As an example, given that you have a 2D array of Bitmaps, you could resize one like so:
Bitmap resize = Bitmap.createScaledBitmap(bitmaps[x][y], 32, 8, true);
The first parameter is the Bitmap you want resized, the second parameter is the width, third parameter the height, and the last is the filter flag. The output (of course) is stored in resize and is your resized / scaled image. Currently, the Javadoc for this method (as you can see) provides no explanation for what filter does. I had to look at the Android source to figure out what exactly it was doing, and also from experience as I have used the method before.
Generally, you set this to false if you are shrinking the image, while you set this to true if you are upscaling the image. The reason why is because when you are interpolating an image from small to large, you are trying to create more information than what was initially available. Doing this with nearest neighbour will introduce blocking artifacts, and so bilinear interpolation will help smooth this out. Going from large to small has no noticeable artifacts using either method, and so you generally choose nearest neighbour as it's more computationally efficient. There will obviously be blurring as you resize to a larger image. The larger you go, the more blurriness you get, but that beats that blockiness you get with nearest neighbour.
For using just the Android API, this is the best and easiest solution you can get. If you want to get into more sophisticated interpolation techniques (Cubic, Lanczos, etc...), unfortunately you will have to implement that yourself. Try bilinear first and see what you get.
I'm new to Java's graphics library and I'm still coming to grips with its limitations. Below I have a draw function for a Grid class, which draws a 2D array of tiles as filled rectangles.
Not that it's pertinent to the question but the the scale and offset arguments are there to adjust the Grid's tiles and tileSize variables such that it's drawn in the correct position and scale on the screen.
My question is it normal for this to lag considerably when the tiles variable is quite big? I normally get around 500 fps without any Grids on screen, and no noticeable reduction with a Grid of tiles[10][10] to [50][50]. But at tiles[1000][1000], or 1,000,000 total rectangles to draw, fps drops to 7.
I know a million is a lot, but they are just rectangles after all, and my pc can play a game like Skyrim on full settings no problem. I'd imagine there's more than a million polygons on display in Skyrim, and they comes with all sorts of highres textures and lighting and all so, why should a million gray squares be such a problem? Is Java's graphics library really poor? Am I expecting too much? Or, as I suspect, is there a much better way of drawing something like this?
I can provide the main class's paintComponent if that's important, but it's just a call to _Grid.draw() so I don't think the problem's there..
public Graphics draw(Graphics g, double scale, Point offset) {
Graphics2D g2 = (Graphics2D) g;
for(int i = 0; i != this.tiles.length; i++) {
for(int j = 0; j != this.tiles[0].length; j++) {
boolean draw = true;
if(this.tiles[i][j].type.equals("EMPTY")) {
draw = false;
} else if(this.tiles[i][j].type.equals("PATH")) {
g2.setColor(Color.LIGHT_GRAY);
} else if(this.tiles[i][j].type.equals("WALL")) {
g2.setColor(Color.DARK_GRAY);
}
if(draw) {
g2.fillRect((int)((this.xPos+i*this.tileSize)*scale + offset.x),
(int)((this.yPos+j*this.tileSize)*scale + offset.y),
(int)(this.tileSize*scale),
(int)(this.tileSize*scale));
}
}
}
return g2;
}
Java's BufferedImage class is slow. It's a known fact. If you want to do fast image manipulation then it's the wrong tool to use.
Additionally games like Skyrim are using the graphics card to do most of the work, using the Java Image stuff it is all being done in the CPU.
You should really look into using a game framework - there are 2d ones like Slick2d and 3d ones like jMonkeyEngine3.
A few things you might consider trying:
If your original Graphics object is being created by a BufferedImage, consider using VolatileImage instead. As others have said, everything in this draw method is happening on the CPU via the Swing Event Dispatch Thread, so you are limited to a single CPU for drawing (unless you spawn more Threads somewhere). VolatileImage, when used for double-buffering, takes advantage of the graphics hardware and can be MUCH faster for this kind of thing.
You're doing a TON of String comparisons, which can be very slow. I'd consider refactoring your type field to be a custom enum instead, then have that enum define its own draw method that takes a Graphics object and draws itself. That way, you could call this.tyles[i][j].type.draw(g2) and rely on late binding to draw the right colored rectangle, thereby eliminating the String comparisons.
Make sure you're only drawing the stuff that's on screen. I don't know anything about your tiles array, but if it's significantly larger than what's actually being rendered to screen, you could be wasting a lot of CPU cycles there.
This might sound silly, but you're actually doing twice as many memory accesses as you really need. I'm going to assume you have Tile[][] tiles ... defined somewhere in your source code. In the outer loop, grab the row first by writing something like Tile[] row = tiles[i];, then in your inner loop, get the type by calling row[j].type. This will cut your memory access count in half when iterating over the tile array.
I have an array that holds the names and then displays the appropriate graphics using:
public void paintComponent(Graphics g){
for(int i = 0; i<20; i++)
{
for(int j = 0; j<20; j++)
{
if(testMap.getData(i+(rowX-7), j+(columnY-7)).indexOf("rock") >= 0)
{
g.drawImage(image3, j*50 + 5*add, i*50 + 5*add2, 50, 50, this);
}
}
}
This works well displaying the actual graphics of my game. However, for my minimap, I do the same thing, with different values and increased numbers in the 'i' and 'j' in the for loop. This creates a smaller version of the map with a bigger scope, which leaves me with this. (Note, I only included one of the drawImage() methods, I removed the rest here to make the code more readable):
This is pretty much my desired effect (aside from the positioning, which I can change easily), however, this only shows a smaller version of what I already see on the screen. Any larger than about 20 x20, though, and the game begins to lag heavily -- probably something to do with the terrible way that I coded it.
I have tried replacing the images with squares using fillRect, but it does not help the issue.
Here is the code of my main class if anybody would like to take a look: http://pastebin.com/eEQWs2DS
The for loop within the paintComponent method begins at around line 3160, the loop for the main display is around 2678. I set up the Jframe at around 1260.
So, with all that said, basically my question is this:
Is there a better, more efficient way to create my minimap? My idea was to generate an image at the beginning of the program so that the game wouldn't have to recalculate every time the frame refreshes. I could create the map in advance, but I would have to manually update that every time I changed the map, which is definitely a hassle. I am having trouble researching how to do the former. My other idea is to slow the refresh rate of the minimap only, but I also do not know how to do that.
Other than that, if there are any simple fixes to the lag issue, please enlighten me. I apologize for the very, very messy code -- This is my first time programming anything with a display so I sort of just...hacked at it until it worked.
I don't know how easy this would be with your implementation, but instead of drawing an image, perhaps you could draw a square of a certain color based on what type of tile it should be?
For instance if you're looping through your list of tiles and you find a grass tile, you would first draw the grass tile at its proper location, then draw a smaller green square on the minimap.
The downside to this is that first you'd have to define what colors to use for the tiles, or perhaps when you load the tiles you can compute an average color for each one, and then just use that. Another issue is that the houses may not translate well on to the minimap, since the tiles have much more detail. You could draw some kind of house icon on the minimap instead of actually drawing any of the house tiles, though.
Basically, you want to use simpler representations of the objects in your map on the minimap, since it's smaller and less detail can be drawn anyway.
Have a look at how I do it in Tyrant (a Java roguelike):
https://github.com/mikera/tyrant/blob/master/src/main/java/mikera/tyrant/LevelMapPanel.java
Key tricks:
Use an offscreen BufferedImage. Cache this to avoid recreating it each time you paint.
Use an int[] array to setup up a single colour for each tile
Use setRGB with the array to draw all the map at once
Draw the mini-map at x2 zoom to get 2x2 pixel blocks for each tile.
If you want to be even more efficient, keep a changed flag and only update the mini-map BufferedImage when something visible on the mini-map changes. I didn't think it was worth it as the above is already very efficient, but YMMV.
I'm currently working on a simple 2D top-down shooter in which you maneuver your character around a map. The graphics are still very much in the "testing" phase (solid colors and rectangles). The drawing process is as follows:
I have the map (this is basically just walls or free space) stored as an integer array which is then looped through and painted onto my JPanel.
Once the map is drawn, I go through each "entity" on the map (powerups, other characters) and place them.
Finally, I draw the user's character
I experimented with different methods, and finally decided that the user's character would stay in the center of the screen with the map moving around it.
Currently, it works but it seems slow. I only have 4 walls, 2 entities, and the user's character being drawn and it's at 90FPS, spiking around 60-70 quite often. This would be acceptable if the game were finished, however I still have fancier graphics, many more entities, walls, bullets, etc. to be rendered onto the map. I've tried increasing the speed of my gameLoop, which can get it as high as 230 FPS, but even then when I move into areas with entities or walls it spikes down to 50-70.
I've tried limiting what is drawn to only what is visible but the results were overall negligible and even negative.
The current paintComponent() method looks something like this:
public void painComponent(Graphics g) {
for(int x = 0; x < Map.size.getWidth() ; x++) {
for(int y = 0; y < Map.size.getHeight(); y++) {
if(mapArray[y][x] == WALL) {
g.setColor(Color.BLACK);
g.fillRect(x, y, wallSize, wallSize);
}
}
}
for(int i = 0; i < map.countEntities(); i++) {
Entity obj = map.getEntity(i);
g.setColor(Color.WHITE);
g.fillRect(obj.getCoords().x, obj.getCoords().y, obj.getSize().width, obj.getSize().height);
}
Robot bot = map.getPlayerBot();
g.setColor(Color.BLACK);
g.fillRect(bot.getCoords().x, bot.getCoords().y, bot.getSize().width, bot.getSize().height);
}
Do any of you gurus out there have any tips to speed this up a bit?
Beyond the answers mentioned in the following links:
Java 2D Drawing Optimal Performance,
Java2D Performance Issues,
Java 2D game graphics
and How to display many SVGs in Java with high performance
A common and simple trick to improve 2D performance is to draw all the static objects ('background') into a buffered image, then when drawing the next image you just offset the pixels of your background image from the previous screen accordingly, then you need only draw the pixels around at most two edges of the screen rather than the entire screen, this only really matters when you're drawing a lot of image layers to the screen.
Beyond all of that make sure you have double buffering enabled so that the drawing doesn't try and draw to the actual screen while you're still drawing the next image.
Seph's answer was taken into consideration and implemented, but it did not increase the speed. Apparently, the image being drawn was not the problem at all. The problem was the timer that I was using to call the update methods. I was using an instance of the Timer class which seems to be very unreliable, especially at small intervals such as these. Upon removing the timer and calling my update methods based on the system clock, I was able to reach and maintain incredibly high FPS.
I'm designing a Canvas object which is been used to draw a BufferedImage of size 228x262 pixels.
That image is been drawn using Graphics2D.drawImage(...) method. I'm doing a pixel basis color manipulation within given offset ranges. A sample of the code below:
for( int i = frameOffset; i < colorClock; i++ ) {
rgb[i] = new Color(this.colorBK).getRGB();
}
Where rbg is set to that bufferedimage I'm changing in.
The problem is that code is painting slow.
I'm creating the image using GraphicsConfiguration.createCompatibleImage, and I'm using double buffering via Buffer Strategy.
Any lights please?
Thanks on adv.
If you run the loop every time you draw the image, the loop might be the bottleneck. There is an completely unnecessary object allocation which will make the garbage collector to run quite often.
I'm assuming that colorBK is int. If this is the case, you just create and initialize a Color object and ask it to return a rgb value that is assigned to rgb array. What actually happens is that you assign the value of colorBK in the rgb array. So, equivalent and more efficient implementation would be rgb[i] = colorBK.
To optimize this even more, you could assign the value of colorBK to a final local variable. This would avoid fetching the value of the field over and over again. So the loop could look like this:
final int color = colorBK;
for( int i = frameOffset; i < colorClock; i++ ) {
rgb[i] = color;
}
To get even more performance gain, you should think that if there is completely different ways of doing this. As the above example just changes some pixels to certain color, I could assume that this could be done with an image and a couple of fillRects.
So you would fill a rect behind the image with the color you want (in this case colorBK). If the image has transparent pixels in those areas the above loop changes they remain unchanged in the canvas and the same effect is gained. This might be more efficient as the graphics methods are better optimized and does not involve heavy array usage.
Don't create a new Color just to extract an RGB integer for every pixel in your image. The only single parameter constructor I can find for Color is one that takes an int RGB - can you not just use colorBK directly?
Also, if you are doing this conversion on every paint that will be slow; you should only need to do the conversion once.