how to scroll my map in java? - java

Firstly, happy new year all.
Ive got a
int[] map = new int[2550];
and in that array I place the id of a block at that pos, so for example if the top left block in the map has a id of one
map[0] = 1
Now I'm trying to think how I would scroll my map now, as I render the map like so -
for(int y = 0 ;y < current.height;y++){
for(int x = 0 ; x < current.width;x++){
Block block = current.getBlock(x, y);
g.drawImage(block.texture, x * 30 , y * 30);
}
}
How would I scroll the map? to make it so for example the block that was at 0,0 is now at 0,1 or whatever. Thanks for the help.
Edit: Im using LWJGL and slick2d to do my rendering and stuff, so I cant use Swing or AWT.

Scrolling is simply drawing all your graphics with a variable offset. Let there be a current scroll position:
int scrollX, scrollY;
Update these variables (and redraw) whenever you want to scroll. In your drawing routine, change the loop like this:
for (int y = 0; y < current.height; y++) {
for (int x = 0; x < current.width; x++) {
Block block = current.getBlock(x, y);
g.drawImage(block.texture, x * 30 - scrollX, y * 30 - scrollY);
}
}
Now, that's basic scrolling, and that will work. However, if you are adding scrolling you probably are planning to have maps much larger than the screen. In that case, it is inefficient to draw the entire map every time. Therefore, instead of iterating over the entire map, you iterate over only the visible portion of the map, which is not too complex:
int startX = Math.max(scrollX / 30, 0);
int startY = Math.max(scrollY / 30, 0);
int limitX = Math.min((scrollX + windowWidth ) / 30 + 1, current.width );
int limitY = Math.min((scrollX + windowHeight) / 30 + 1, current.height);
for (int y = startY; y < limitY; y++) {
for (int x = startX; x < limitX; x++) {
Block block = current.getBlock(x, y);
g.drawImage(block.texture, x * 30 - scrollX, y * 30 - scrollY);
}
}
The min and max are to avoid trying to draw tiles outside the bounds of the map. Note that this is exactly the same loop as before, except that the loop bounds have been changed.
By the way, you should also put the size of your tiles in a variable, so that if you ever decide to resize your tiles, or add zooming, you don't have to change code all over the place:
static final int TILE_SIZE = 30;
...
g.drawImage(block.texture, x * TILE_SIZE - scrollX, y * TILE_SIZE - scrollY);

Related

Processing - rendering shapes is too slow

I have been doing a small little project using Processing, and the effect I wanted to achieve was a kind of "mountains" forming and moving, using Perlin Noise with the noise() function, with 2 parameters.
I was originally using a image for the background, but for illustrational purposes, I made the background black, and it's basically the same effect.
My issue is that I want to have a "history" of the mountains because they should fade away after some time, and so I made a history of PShapes, and draw the history and update it each frame.
Updating it is no issue, but drawing the PShapes seems to take a lot of time, reducing the frame rate from 60 to 10 when the length of the history is 100 elements.
Below is the code I used :
float noise_y = 0;
float noise_increment = 0.01;
// increment x in the loop by this amount instead of 1
// makes the drawing faster, since the PShapes have less vertices
// however, mountains look sharper, not as smooth
// bigger inc = better fps
final int xInc = 1;
// maximum length of the array
// bigger = less frames :(
final int arrLen = 100;
int lastIndex = 0;
PShape[] history = new PShape[arrLen];
boolean full = false;
// use this to add shapes in the history
PShape aux;
void setup() {
size(1280, 720);
}
void draw() {
background(0);
// create PShape object
aux = createShape();
aux.beginShape();
aux.noFill();
aux.stroke(255);
aux.strokeWeight(0.5);
for (float x = 0; x < width + xInc; x = x + xInc) {
float noise = noise(x / 150, noise_y) ;
// get the actual y coordinate
float y = map(noise, 0, 1, height / 2, 0);
// create vertex of shape at x, y
aux.vertex(x, y);
}
aux.endShape();
// push the current one in the history
history[lastIndex++] = aux;
// if it reached the maximum length, start it over ( kinda works like a queue )
if (lastIndex == arrLen) {
lastIndex = 0;
full = true;
}
// draw the history
// this part takes the MOST TIME to draw, need to fix it.
// without it is running at 60 FPS, with it goes as low as 10 FPS
if (full) {
for (int i = 0; i < arrLen; i++) {
shape(history[i]);
}
} else {
for (int i = 0; i < lastIndex; i++) {
shape(history[i]);
}
}
noise_y = noise_y - noise_increment;
println(frameRate);
}
I have tried to use different ways of rendering the "mountains" : I tried writing my own class of a curve and draw lines that link the points, but I get the same performance. I tried grouping the PShapes into a PShape group object like
PShape p = new PShape(GROUP);
p.addChild(someShape);
and I got the same performance.
I was thinking of using multiple threads to render each shape individually, but after doing some research, there's only one thread that is responsible with rendering - the Animation Thread, so that won't do me any good, either.
I really want to finish this, it seems really simple but I can't figure it out.
One possible solution would be, not to draw all the generated shapes, but to draw only the new shape.
To "see" the shapes of the previous frames, the scene can't be cleared at the begin of the frame, of course.
Since the scene is never cleared, this would cause, that the entire view is covered, by shapes over time. But if the scene would be slightly faded out at the begin of a new frame, instead of clearing it, then the "older" shapes would get darker and darker by time. This gives a feeling as the "older" frames would drift away into the depth by time.
Clear the background at the initlization:
void setup() {
size(1280, 720);
background(0);
}
Create the scene with the fade effect:
void draw() {
// "fade" the entire view
blendMode(DIFFERENCE);
fill(1, 1, 1, 255);
rect(0, 0, width, height);
blendMode(ADD);
// create PShape object
aux = createShape();
aux.beginShape();
aux.stroke(255);
aux.strokeWeight(0.5);
aux.noFill();
for (float x = 0; x < width + xInc; x = x + xInc) {
float noise = noise(x / 150, noise_y) ;
// get the actual y coordinate
float y = map(noise, 0, 1, height / 2, 0);
// create vertex of shape at x, y
aux.vertex(x, y);
}
aux.endShape();
// push the current one in the history
int currentIndex = lastIndex;
history[lastIndex++] = aux;
if (lastIndex == arrLen)
lastIndex = 0;
// draw the newes shape
shape(history[currentIndex]);
noise_y = noise_y - noise_increment;
println(frameRate, full ? arrLen : lastIndex);
}
See the preview:

Drawing rectangles from center of given coordinate

I made a program where user enters number of rectangles to be drawn and coordinates at which the rectangles are drawn. My rectangles are currently drawn like this:
and I want to achieve this:
This is the code I use to draw the rectangles:
int povecaj_kvadrat=0;
for(int x=0;x<broj_kvadrata;x++) {
Rectangle2D.Float kvadrat=new Rectangle2D.Float(brojevi_koordinate[0],brojevi_koordinate[1],50+povecaj_kvadrat,50+povecaj_kvadrat);
ploca.draw((kvadrat));
povecaj_kvadrat=povecaj_kvadrat+15;
}
}
How do I set the coordinates of the rectangles so that they are drawn like in the second image?
You will have to take into account the additional size of each Rectangle, along with its position within the loop to compute the correct coordinates of each rectangle.
The additional size has been moved as a variable (diffSize), so that your loop can use its value.
The difference in coordinates between two iterations will be half this diff size, multiplied by the inverse of the position in the loop, because the lower the increment (x) and the Rectangle size, the bigger the coordinates .
int gap = 0;
int maxNumber = 3;
int diffSize = 20;
int[] coordinates = { 10, 10 };
for (int x = 0; x <= maxNumber; x++) {
Rectangle2D.Float rectangle = new Rectangle2D.Float(
coordinates[0] + ((diffSize / 2) * (maxNumber - x)),
coordinates[1] + ((diffSize / 2) * (maxNumber - x)),
50 + gap, 50 + gap);
g2d.draw((rectangle));
gap = gap + diffSize;
}
Note that I am unsure of the correct behaviour if diffSize is odd (because (diffSize / 2) will be rounded down to the nearest int value), so I would keep an even value for diffSize.

Recalculate X and Y Coordinates From New Screensize

I'm trying to display tiles from an array I have so that they always fill the size of the screen when drawn together. I am ignoring aspect ratio for now.
Here's how my code works. I have tile objects that are passed on to a tileset (class for managing an array of tile objects), and then I iterate through the tileset array, returning each tile object id and rendering a subimage of my tileset image based on said ids.
Here's my mapUpdate method, which is called on every JFrame resize event:
public synchronized void mapUpdate(Screen screen) {
factorX = (float)(screen.getWidth() / scW);
factorY = (float)(screen.getHeight() / scH);
for (int i = 0; i < tileset.getRows(); i++) {
for (int j = 0; j < tileset.getCols(); j++) {
int x = tileset.getTile(i, j).getX();
int y = tileset.getTile(i, j).getY();
tileset.getTile(i, j).setX((int)(x * factorX));
tileset.getTile(i, j).setY((int)(y * factorY));
}
}
mapTiles.clear();
for (int i = 0; i * 70 < mapImage.getWidth(); i++) {
mapTiles.add(mapImage.getSubimage(70 * i, 0, 70, 70).getScaledInstance(screen.getWidth() / 10, screen.getHeight() / 10, Image.SCALE_SMOOTH));
}
}
mapTiles is an ArrayList of Images, and on each resize event it resets the arraylist, scales my subimages to 1/10th width and height, and then re-adds the newly sized images for me to pull out for rendering (the tileset image is only 3 tiles with an original size of 70x70).
And here is the componentResized method if you were curious:
public void componentResized(ComponentEvent e) {
canvas.setSize(app.getContentPane().getWidth(), app.getContentPane().getHeight());
if (level1 != null) {
level1.mapUpdate(this);
}
}
As you can see in my mapUpdate method, I attempt to get a float to multiply each current x and y value by to receive the new correct values (it will round the integers), but this doesn't work at all.
Is there any solution to easily re-calculate my X and Y coordinates so that the tiles are drawn correctly?
Your factorX should not be a float. It should be an int. If you use a float you will get rounding, so occasionally you will have a pixel gap between tiles because of rounding. If you just use an int then you don't have to worry about this. Then the location is just the factor * the index value of the for loop.
On the other hand the easiest solution is to just use a JPanel with a GridLayout. Then you can add a JLabel with an Image icon. The GridLayout will resize each component equally.
You can then even use the Stretch Icon and the images will be dynamically resized as the frame is resized.
SOLUTION courtesy of #MadProgrammer - The solution was to simply calculate the new X and Y "origins" for each tile. I still need to implement adjusting the position for tiles that have moved based on the newly calculated origins, but this solution works for tiles that do not move (again, simply recalculate the X and Y origin positions).
public synchronized void mapUpdate(Screen screen) {
int originX = screen.getWidth() / 10;
int originY = screen.getHeight() / 10;
for (int i = 0; i < tileset.getRows(); i++) {
for (int j = 0; j < tileset.getCols(); j++) {
tileset.getTile(i, j).setX((originX * j));
tileset.getTile(i, j).setY((originY * i));
}
}
mapTiles.clear();
for (int i = 0; i * 70 < mapImage.getWidth(); i++) {
mapTiles.add(mapImage.getSubimage(70 * i, 0, 70, 70).getScaledInstance(screen.getWidth() / 10, screen.getHeight() / 10, Image.SCALE_SMOOTH));
}
}

Java smooth scrolling (Game)

Ok, so I have a game I just started and I am kind of stuck on the smooth scrolling. I have the basic scrolling part done but my background (Grid) only moves by intervals of 50.
for (int x = (getPlayerX() / getTileSize()) - 6; x < (getPlayerX() / getTileSize()) + 9; x++)
{
for (int y = (getPlayerY() / getTileSize()) - 5; y < (getPlayerY() / getTileSize()) + 8; y++)
{
int xPos = ((x - (getPlayerX() / tileSize)) + (getScreenX() / tileSize) - 1) * tileSize;
int yPos = ((y - (getPlayerY() / tileSize)) + (getScreenY() / tileSize) - 1) * tileSize;
if (x > 0 && x < mapX && y > 0 && y < mapY)
{
if (getTiles()[x][y].tileID == 0)
{
g.drawRect(xPos, yPos, tileSize, tileSize);
}
if (getTiles()[x][y].tileID == 1)
{
g.fillRect(xPos, yPos, tileSize + 1, tileSize + 1);
}
}
}
}
Sorry about the subtraction and addition in the for loops, I have them set up so it will display from 1 to whatever instead of 0 to whatever - 1.
So basically I want to redraw the grid every pixel I move, instead of every 50. Put I dont want to iterate over every pixel on the screen
Ok, at first you are confusing with the background and the grid. A grid is a way to check collisions and a spatial partitioning algorithm that never moves. A background is an image which is drawn behind every entity in your game in every level.
What you are trying to do is make the level scroll, as per what I understand. To do so, we create a class called View also called as a Camera which draws the background and the visible entities by centering the entity which is controlled by the player.
And to center an entity and draw the map, I use these classes. Hope you understand that I wrote them in C#.
Map
MapInfo
MapLayer
MapLoader
MapManager
MapView
And here is my implementation of Grid. I have the java versions of the same classes here but I haven't implemented the Grid class in java.
You can read my tutorial Using Grids For Collisions for more info on how it works. Hope it helps.

How to speed up my games fps?

I have my game which, on every render loop, loops through all the blocks in my map (128x128) which as you can probably tell, causes a lot of lag. When I first made it, I had to make it render only the blocks on the screen, or it would crash instantly. Now I only render the blocks on the screen, but still loop through all the blocks to see if they are on the screen, which makes my fps about 2.
for (int y = 0; y < 128; y++) {
for (int x = 0; x < 128; x++) {
Block b = LevelGen.getBlockAt(x, y);
if (Forgotten.isInsideGameWindow(x * 30, y * 30)) {
arg1.drawImage(b.getTexture(), x * 30, y * 30);
}
}
}
Is there a way to make it so doesn't loop through all of them?
Figure out the size of your display window, and only iterate blocks that are within it.
final int tile_size = 30;
final RectangleType displayWindow = Forgotten.getDisplayWindow ();
final int left = displayWindow.getLeft () / tile_size - 1;
final int right = displayWindow.getRight () / tile_size + 1;
final int top = displayWindow.getTop () / tile_size - 1;
final int bottom = displayWindow.getBottom () / tile_size + 1;
for (int y = top; y < bottom; ++y)
for (int x = left; x < right; ++x)
canvas.drawImage (LevelGen.getBlockAt (x,y).getTexture (),
x * tile_size, y * tile_size);
You may also want to figure out which area(s) of the canvas actually want to be drawn, and instead keep a "dirty rectangle" list of areas to be redrawn. Whenever a tile changes, or a sprite/particle/whatever passes through its space, add that tile to the rectangle. Even if you just use a single dirty rectangle that enlarges to encompass all updates during a frame, if your game doesn't actually have "stuff happening" at all points on the display at all times, your frame rate will be higher on average (but suffer from large-scale effects)
expanding upon that:
public class Map {
private Rectangle dirtyRect = null;
public void dirty (Rectangle areaAffected) {
if (null == dirtyRect) {
dirtyRect = areaAffected; return;
}
dirtyRect = new Rectangle ( Math.min (dirtyRect.getLeft (),
areaAffected.getLeft () ),
Math.min (dirtyRect.getTop (),
areaAffected.getTop () ),
Math.max (dirtyRect.getRight (),
areaAffected.getRight () ),
Math.max (dirtyRect.getBottom (),
areaAffected.getBottom () ));
}
Then use the dirty rectangle in place of displayWindow for normal draws, and you can test if (null == getDirtyRectangle ()) return; to skip drawing at all if nothing's changed.
You could just insert a break inside of your code block.. but then I don't see the purpose of this code looking through 16, 32, or 256 times...
Well, you could place all the blocks in an array, include coordinates in the block object then use those to draw the block. Then you'd only have to loop through the blocks that appear on screen. Only useful if there aren't that many blocks on screen though.

Categories