问题
I am currently in the process of experimenting with a 2D tile based side scrolling game in Java, primarily based on the code and examples from "Developing Games in Java" by David Brackeen
At the moment the map files are 100x100 tiles in size (each tile is 64x64 pixels). I have already configured the system to only display the tiles which are visible to the player. The Graphics system is managed by a ScreenManager class that returns the graphics object of the current BufferStrategy as follows:
ScreenManager.java
private GraphicsDevice device;
...
/**
* Gets the graphics context for the display. The
* ScreenManager uses double buffering, so applications must
* call update() to show any graphics drawn.
* <p>
* The application must dispose of the graphics object.
*/
public Graphics2D getGraphics(){
Window window = device.getFullScreenWindow();
if(window != null){
BufferStrategy strategy = window.getBufferStrategy();
return (Graphics2D)strategy.getDrawGraphics();
}
else{
return null;
}
}
After the graphics from this ScreenManager is passed along in the game loop to the draw method of the TreeRenderer.
TreeMapRenderer.java
/**
Draws the specified TileMap.
*/
public void draw(Graphics2D g, TileMap map,
int screenWidth, int screenHeight, float fr)
{
Sprite player = map.getPlayer();
int mapWidth = tilesToPixels(map.getWidth());
int mapHeight = tilesToPixels(map.getHeight());
// get the scrolling position of the map
// based on player's position
int offsetX = screenWidth / 2 -
Math.round(player.getX()) - TILE_SIZE;
offsetX = Math.min(offsetX, 0);
offsetX = Math.max(offsetX, screenWidth - mapWidth);
// get the y offset to draw all sprites and tiles
int offsetY = screenHeight /2 -
Math.round(player.getY()) - TILE_SIZE;
offsetY = Math.min(offsetY,0);
offsetY = Math.max(offsetY, screenHeight - mapHeight);
// draw the visible tiles
int firstTileY = pixelsToTiles(-offsetY);
int lastTileY = firstTileY + pixelsToTiles(screenHeight) +1;
int firstTileX = pixelsToTiles(-offsetX);
int lastTileX = firstTileX +
pixelsToTiles(screenWidth) + 1;
//HERE IS WHERE THE SYSTEM BOGS dOWN (checking ~280 tiles per iteration)
for (int y=firstTileY; y<lastTileY; y++) {
for (int x=firstTileX; x <= lastTileX; x++) {
if(map.getTile(x, y) != null){
Image image = map.getTile(x, y).getImage();
if (image != null) {
g.drawImage(image,
tilesToPixels(x) + offsetX,
tilesToPixels(y) + offsetY,
null);
}
}
}
}
// draw player
g.drawImage(player.getImage(),
Math.round(player.getX()) + offsetX,
Math.round(player.getY()) + offsetY,
null);
The algorithm works correctly selecting the correct FROM and TO values for the X and Y axis culling the needed tiles from 10000 to ~285.
My problem is that even with this the game will only run at around 8-10 FPS while the tiles are being rendered. If I turn off tile rendering than the system runs at 80 FPS (easy to run fast when there is nothing to do)
Do you have any ideas on how to speed up this process? I would like to see something at least around the 30 FPS mark to make this playable.
And finally although I am open to using 3rd party libraries to do this I would like to try and implement this logic myself before admitting defeat.
EDIT:
As requested here is the extra information for how the call for Image image = map.getTile(x, y).getImage(); works.
The map here come from the following TileMap class
TileMap.java
public class TileMap {
private Tile[][] tiles;
private LinkedList sprites;
private Sprite player;
private GraphicsConfiguration gc;
/**
Creates a new TileMap with the specified width and
height (in number of tiles) of the map.
*/
public TileMap(GraphicsConfiguration gc, int width, int height) {
this.gc = gc;
tiles = new Tile[width][height];
overlayer = new Tile[width][height];
sprites = new LinkedList();
}
/**
Gets the width of this TileMap (number of tiles across).
*/
public int getWidth() {
return tiles.length;
}
/**
Gets the height of this TileMap (number of tiles down).
*/
public int getHeight() {
return tiles[0].length;
}
/**
Gets the tile at the specified location. Returns null if
no tile is at the location or if the location is out of
bounds.
*/
public Tile getTile(int x, int y) {
if (x < 0 || x >= getWidth() ||
y < 0 || y >= getHeight())
{
return null;
}
else {
return tiles[x][y];
}
}
/**
* Helper method to set a tile. If blocking is not defined than it is set to false.
*
* @param x
* @param y
* @param tile
*/
public void setTile(int x, int y,Image tile){
this.setTile(x,y,tile,false);
}
/**
Sets the tile at the specified location.
*/
public void setTile(int x, int y, Image tile, boolean blocking) {
if(tiles[x][y] == null){
Tile t = new Tile(gc, tile, blocking);
tiles[x][y] = t;
}
else{
tiles[x][y].addImage(tile);
tiles[x][y].setBlocking(blocking);
}
}
...
With the Tile here being an instance of the following code. Essentially this class just holds the Image which can be updated by adding an overlay layer to it always using gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT); and a boolean to tell if it will block the player. The image that is passed in is also created in this manner.
Tile.java
public class Tile {
private Image image;
private boolean blocking = false;
private GraphicsConfiguration gc;
/**
* Creates a new Tile to be used with a TileMap
* @param image The base image for this Tile
* @param blocking Will this tile allow the user to walk over/through
*/
public Tile(GraphicsConfiguration gc, Image image, boolean blocking){
this.gc = gc;
this.image = image;
this.blocking = blocking;
}
public Tile(GraphicsConfiguration gc, Image image){
this.gc = gc;
this.image = image;
this.blocking = false;
}
/**
Creates a duplicate of this animation. The list of frames
are shared between the two Animations, but each Animation
can be animated independently.
*/
public Object clone() {
return new Tile(gc, image, blocking);
}
/**
* Used to add an overlay to the existing tile
* @param image2 The image to overlay
*/
public void addImage(Image image2){
BufferedImage base = (BufferedImage)image;
BufferedImage overlay = (BufferedImage)image2;
// create the new image, canvas size is the max. of both image sizes
int w = Math.max(base.getWidth(), overlay.getWidth());
int h = Math.max(base.getHeight(), overlay.getHeight());
//BufferedImage combined = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
BufferedImage combined = gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT);
// paint both images, preserving the alpha channels
Graphics g = combined.getGraphics();
g.drawImage(image, 0, 0, null);
g.drawImage(overlay, 0, 0, null);
this.image = (Image)combined;
}
public boolean isBlocking(){
return this.blocking;
}
public void setBlocking(boolean blocking){
this.blocking = blocking;
}
public Image getImage(){
return this.image;
}
}
回答1:
I would use a pixel rendering engine (google it ;D)
Basically what you do, it have a giant array of intergers corresponding to the image you are drawing.
Basically you have each tile have an array of intergers representing its pixels. When you render that tile, you "copy" (it's slightly more complicated than that) the array of tile to the big array :)
Then once you are done rendering everything to the master array, you draw that on the screen.
This way, you are only dealing with integers and not whole pictures everytime you draw something. This makes it a lot faster.
I learned this using MrDeathJockey's (youtube) tutorials and combining them with DesignsbyZephyr's (also youtube). Although I do not recommend using his technique (he only uses 4 colors and 8 bit graphics, as with deathJockey's tutorials you can customize the size of the images and even have multiple sprite sheets with different resolutions (useful for fonts)
I did however use some of the offset stuff (to make the screen move instead of the player) and the InputHandler by Zephyr :)
Hope this helps! -Camodude009
回答2:
Create transparent images (not translucent) since translucent images require higher ram and are stored in the system memory instead of the standard java heap. Creating translucent images require several native calls to access the native system memory. Use BufferedImages instead of Image. You can cast BufferedImage to Image at any time.
来源:https://stackoverflow.com/questions/10046401/java-tile-based-game-performance