So i had a console game i made, and i thought in making it 2d.
So far so good everything was fine.
I have movement and other things working fine.
And the drawing was fine,until i had this idea.
( my "map" is a Tile[][] )
I thought in creating a class that would represent a layer then a playable map would be a
Layer[] and i would draw the layers over each others and maybe store some on memory for faster drawing.
I thought this was a 'good'/'okay' idea.(i would appreciate if someone would tell me if it is a good idea or not)
The problem is when i use 2 Layers the second layer always messes the first one.
The code i use to draw the 'map' looks like this:
for (TileLayer layer : layers)
g.drawImage(layer.getImage(), 0, 0, null);
the layer.getImage() is correct (pretty sure, works with just one layer).
The problem is that when i draw 2 layers the second one blacks out everything in the first layer.
I have the layer draw code like this:
BufferedImage img = new BufferedImage(tiles.length * Tile.TILE_SIZE,
tiles[0].length*Tile.TILE_SIZE,
ColorSpace.TYPE_RGB);
for (int x = 0; x < tiles.length; x++) {
for (int y = 0; y < tiles[0].length; y++) {
//System.out.println("ID:" + tiles[x][y].getId());
img.getGraphics().drawImage(tiles[x][y].getImage(),
x * Tile.TILE_SIZE,
y * Tile.TILE_SIZE,
null);
}
}
Its a java applet i was overriding 'paint', i have changed that.
I tried using a transparent image to represent 'air' ( thought this should work probably messed it up).
tried adding a condition to prevent drawing when it shouldn't.
I might have made something that works in a wrong way.
Help on a proper way to do this would be nice.
(or tell me where I might have it wrong)
In the example, you appear to be using the wrong constant as the BufferedImage type.
If you want your BufferedImage to allow transparency, set the last parameter to BufferedImage.TYPE_INT_ARGB (or one of the type constants on BufferedImage that has "A" in the last bit).
Related
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.
I'm making a Minecraft like game using OpenGL and I wonder, what is the best solution to make a flexible rendering without perfomance loss. I mean there are different types of blocks with different ways of drawing: usual blocks are just simple cubes with textures, stairs are more complex and liquid blocks like water and lava need texture update. I have a chunk with display list. Every block has it's own type (byte value). According to this value I can find "full" block representation (it is common for all blocks of same type and contains name, draw method and additional info). Now I need to remove rendering invisible faces (if block has a neighbour at the left, we don't need to draw it's left face). But it means I need to pass 6 arguments to the drawing method (or an array, or a mask). Is it a good way of drawing, or I have choosen a dumb solution? What can you recommend for drawing different types of blocks in it's own way?
for(int x = 0; x < 16; x++){
for(int y = 0; y < 128; y++){
for(int z = 0; z < 16; z++){
GL11.glPushMatrix();
GL11.glTranslatef(x, y, z);
BlockTypes.getBlockType(blocks[x][y][z].type).draw();
GL11.glPopMatrix();
}
}
}
I recommend using a VBO for each chunk and shaders, rather than a display list and the fixed pipeline(which is deprecated actually).
Using a VBO gives multiple advantages. It lets you use shaders, giving far more flexibility than fixed pipeline. With shaders you can send off texture data, colour data, etc. separately from vertex/index data. As well, VBOs are faster in general.
One way VBOs give more flexibility is with water and lava, which require textures updates can be called separately from the buffering, reducing need to rebuffer the entire chunk.
As for the invisible face detection, I believe that is the only way to do it. Instead of passing 6 arguments, just pass the chunk to the method. You can also make a method that retrieves the blocks surrounding the block you are checking.
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 am practicing for a test I have coming up and I am looking through some practice questions. I have come across a question that is challenging me more than the others. I am needing to write a method, using swing it seems, that creates 100 rectangles of a given width and varying heights found through simple mathematics. I have made my array that holds each height and I have my width set. How might I go about creating these rectangles using swing? I would prefer that this question be answered in a way that tries to push me in the right direction (i.e. try using JLabel or something of that nature) rather than doing it for me. How would I ever learn it that way? I may end up with some follow up questions as well. Here is the code that makes my Array for heights and sets my width. It is probably assumed but my parameters are in pixels.
public void paintComponent() {
int[] heights = new int[100];
int width = 10
int initialHeight = 100;
for (int i=0; i<100; i++) {
int usedHeight = height - i;
heights[i] = usedHeight;
}
Again, please do not do it for me. I just need to be pointed in the right direction. I assume I will be using swing in some way to do this.
Basically, what you want to do is subclass JComponent and override paintComponent. The Graphics object passed to it lets you do what you need (hint).
General approach:
Make a JFrame with a JPanel
Give it a layout (something simple like a GridLayout will probably
work for this)
Add the Components to your container (I'll point you towards Rectangle) (or you can just use JPanels and set their border/fill color)
One of the hardest things to learn in Swing is layout managers, so expect to spend some time bashing your head against that wall until it gives way. Prepare to have swing draw all the rectangles on top of one another, draw them in a single column, and not draw them at all. Probably not in that order.
OK, first, you have some bugs in your current code (assuming height is decreasing):
public void paintComponent() {
int[] heights = new int[100];
int width = 10;
int initialHeight = 100;
for (int i=0; i<100; i++) {
heights[i] = initialHeight;
initialHeight--;
//also heights[i] = 100-i;
}
}
When usedHeight was declared inside the cycle, it existed ony during one 'cycle' of the cycle.
Second, if you want to understand how swing works, download 5+- already working examples and figure it out from code (worked for me just fine).
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.