I have recently started messing around with LWJGL and I was making a little game to test some stuff in. I wanted to try to make an EXP bar and thought that it would be fairly easy. I got almost done with it until I ran into a large issue. That issue is properly displaying the exp bar filling up on the screen.
The game window is 1280 pixels in width and 720 pixels in height. The EXP bar is on the very bottom of the window 10 pixels high and stretches across the entire width of the window. As your level increases, the amount of exp needed to level up also increases. I need some help displaying the amount of exp that you currently have. When you first start you need 100 exp to level up. For example, say I have 50 exp. I need to show the exp bar 50% filled. I am asking for a formula to do this.
I tried looking around the internet for an answer but none of them worked, even though a few seemed like they would work just fine.
Current code to render the filled exp:
glColor3f(1f, 0f, 1f);
glBegin(GL_QUADS);
glVertex2i(x, y); // Upper Left
glVertex2i(x, y); //Upper Right
glVertex2i(x, y + height); //Bottom Right
glVertex2i(x, y + height); //Bottom Left
glEnd();
Find what percentage of the experience the player has, and multiply it by the bar's maximum width.
int currentXP;
int lastLevelXP;
int nextLevelXP;
int xpBetweenLevels = nextLevelXP - lastLevelXP;
int xpSinceLevelUp = currentXP - lastLevelXP;
float percentageOfXP = xpSinceLevelUp / (float) xpBetweenLevels;
int barWidth = (int) (percentageOfXP * maxWidth); // maxWidth would be the screen's width
// rendering must use 4 unique points
glColor3f(1f, 0f, 1f);
glBegin(GL_QUADS);
glVertex2i(x, y);
glVertex2i(x + (percentageOfXP * maxWidth), y);
glVertex2i(x + (percentageOfXP * maxWidth), y + height); // where y = 10
glVertex2i(x, y + height);
glEnd();
This way, if the player is at level 2, which starts at 70 XP, has 120 XP, and level 3 starts at 170 XP:
currentXP = 120
lastLevelXP = 70
nextLevelXP = 170
xpBetweenLevels = 170 - 70 = 100
xpSinceLevelUp = 120 - 70 = 50
percentageOfXP = 50 / 100 = 0.5
barWidth = 0.5 * 1280 = 640 = half of screen's width
Also note that, whereas your code only used 2 different vertices, which would result in a line, mine uses 4 which draws a quad.
edit
Since your code resets XP to 0 every level, and simply increases the required amount, the only modification needed is to set lastLevelXP to 0 and nextLevelXP to the amount of XP needed to level up. Thus, xpBetweenLevels becomes nextLevelXP - 0, which is the total XP needed to level up; likewise, xpSinceLevelUp becomes just currentXP. All you need to do is replace the non-rendering code with
int currentXP;
int xpNeeded;
float percentageOfXP = currentXP / (float) xpNeeded;
int barWidth = (int) (percentageOfXP * maxWidth);
Related
I have an isometric map drawn.
I take the current position of my sprite and the target position of where my sprite should be at after the move:
// region is my TextureRegion.
int x1 = getIsometricX(1,1,region);
int x2 = getIsometricX(1,2,region);
int y1= getIsometricY(1,1,region);
int y2 = getIsometricY(1,2,region);
And then I draw a simple line using ShapeRenderer to see if the local/target points are correctly set, and the rectangle so you can see where the sprite rendering starts.
renderer.setProjectionMatrix(camera.combined);
renderer.begin(ShapeRenderer.ShapeType.Line);
renderer.setColor(Color.RED);
renderer.line(x1 + location.getOffsetx(), y1 + location.getOffsety(), x2 + location.getOffsetx(), y2 + location.getOffsety());
renderer.rect(x1 + location.getOffsetx(), y1 + location.getOffsety(), region.getRegionWidth(), region.getRegionHeight());
renderer.end();
Every sprite of mine has set offsetX and offsetY to adjust its location on the isometric tile, because every sprite is different.
Output looks like this:
what you can see here, is the starting point of where the sprite starts to draw (you see that offsets adjusted it so the sprite looks like its on the 1,1 tile.
and you can see the line which starts at the starting draw point of the sprite, and ends at the target draw point of the sprite.
Now my question is, how can I make that sprite move on that line's path, so it will look like the ship is moving forward?
So the main concept of the question is.. How can you make a sprite move in a straight line, from local point to target point?
Some functions you might need to see:
public int getIsometricX(int x, int y, TextureRegion region) {
return (x * GameTile.TILE_WIDTH / 2) - (y * GameTile.TILE_WIDTH / 2) - (region.getRegionWidth() / 2);
}
public int getIsometricY(int x, int y, TextureRegion region) {
return (x * GameTile.TILE_HEIGHT / 2) + (y * GameTile.TILE_HEIGHT / 2) - (region.getRegionHeight() / 2);
}
Tiles are drawn using the same method, just with Tile's texture.
I would like to answer myself this question, because other people might have the same issue and just over-complicate it like me.
If you want to perform any move on your isometric map, do not follow my misunderstandings and calculate it on the isometric coordinates.
You have to calculate it on your flat screen matrix coordinates, and then convert it to isometric coordinates.
For example, I want to move up like this line, all I need to do is this:
ship.y += 0.1f // when it reaches 1, then it will be at 0, 1
So you know that you want to be at 0,1 on your non-isometric map.
So you do this increment, and then for last, you have to convert it to isometric coordinates before drawing:
float x = (ship.x * GameTile.TILE_WIDTH / 2) - (ship.y * GameTile.TILE_WIDTH / 2) - (textyure.getWidth() / 2);
float y = (ship.x * GameTile.TILE_HEIGHT / 2) + (ship.y * GameTile.TILE_HEIGHT / 2) - (texture.getHeight() / 2);
And that will draw it on the isometric map, exactly like on your screen-coordinates, but on an isometric format.
for(float x : new float[targetx - currentx]) {ship.setPosition(ship.getX + x, ship.getY); }
And you would do the same for y.
Edit:
I guess this is wrong since you're calling this every frame I assume.
Instead you would keep track of your distance traveled between target and ship and you would increment sip position by 1 each time to x and y until it reached the distance.
I'm making a little game just for fun and I got stuck making the bullets come out of the gun. In the code below, the direction of the player is a degree angle called rot.
float gunOffsetX = 106, gunOffsetY = 96;
double angle = Math.toRadians(rot); // convert direction of player from degrees to radians for sin and cos
x = getX(); // player X
y = getY(); // player Y
float bulletX = (float) (x + (gunOffsetX * Math.cos(angle) - gunOffsetY * Math.sin(angle)));
float bulletY = (float) (y + (gunOffsetX * Math.sin(angle) + gunOffsetY * Math.cos(angle)));
Instances.fire.add(new Fire(bulletX, bulletY, rot, weapon));
Also tried:
bulletX = (float) (x + Math.cos(angle + Math.atan2(gunOffsetX, gunOffsetY)) * Point2D.distance(0, 0, gunOffsetX, gunOffsetY));
But same results
Supposedly the bullets should spawn at the end of the gun but this isn't the case as you can see in the following gif..
Any help appreciated
One big issue (at least in my opinion) is how this game handles anchor points of shapes, like the player.
We can highlight the anchor point by drawing a little red rectangle on its place:
g.setColor(Color.RED);
g.drawRect((int)player.getX() -5, (int)player.getY() -5, 10, 10);
This comes into the Draw#renderGame(Graphics2D) method, so it looks like:
private void renderGame(Graphics2D g) {
g.rotate(Math.toRadians(player.rot), player.getX()+64, player.getY()+64);
g.drawImage(player.getCurrentFrame(), (int)player.getX(), (int)player.getY(), player.getWidth(), player.getHeight(), null);
g.setColor(Color.RED);
g.drawRect((int)player.getX() -5, (int)player.getY() -5, 10, 10);
g.rotate(-Math.toRadians(player.rot), player.getX()+64, player.getY()+64);
//...
then we'll see that the anchor point is not in the center of the image:
As you can see, the anchor point (the original (0,0) point before the rotation) isn't in the center of the image and the crosshair is related to it, instead of the view of the player.
This happens due to the shifting operation while the player rotation:
g.rotate(Math.toRadians(player.rot), player.getX()+64, player.getY()+64);
//...
g.rotate(-Math.toRadians(player.rot), player.getX()+64, player.getY()+64);
You're shifting the postion with +64. I suggest to remove that and add the shifting to the g.drawImage call instead, so the anchor point is correctly in the center (mind that I avoided the fixed value 64):
g.rotate(Math.toRadians(player.rot), player.getX(), player.getY());
g.drawImage(player.getCurrentFrame(), (int)player.getX() - (player.getWidth() / 2), (int)player.getY() - (player.getHeight() / 2), player.getWidth(), player.getHeight(), null);
g.rotate(-Math.toRadians(player.rot), player.getX(), player.getY());
Then you now fire the gun you'll see that the bullet always "starts" from a certain position from the player. The problem here is the incorrect offset you used. The proper values are:
float gunOffsetX = 35, gunOffsetY = 29;
(I got them by trial and error, so you may adjust them a bit more, if you like)
Now it looks like this:
As you can see, the shot is still a bit misplaced, but this happens due to the incorrect rotation of the bullet (like you did it for the player shape):
g.rotate(Math.toRadians(f.rot), f.getX()+f.getWidth()/2, f.getY()+f.getHeight()/2);
g.drawImage(f.img, (int)f.getX(), (int)f.getY(), f.getWidth(), f.getHeight(), null);
g.rotate(-Math.toRadians(f.rot), f.getX()+f.getWidth()/2, f.getY()+f.getHeight()/2);
It should look like this (without any X or Y adjustments):
g.rotate(Math.toRadians(f.rot), f.getX(), f.getY());
g.drawImage(f.img, (int)f.getX(), (int)f.getY(), f.getWidth(), f.getHeight(), null);
g.rotate(-Math.toRadians(f.rot), f.getX(), f.getY());
The end result is:
The player now correctly looks at the crosshair and the shots are placed in front of the gun.
If you like to fire directly through the center of the crosshair, you'll only need to adjust the player position and the bullet offset a bit.
Player (in Draw#renderGame(Graphics2D)):
g.drawImage(player.getCurrentFrame(), (int)player.getX() - (player.getWidth() / 2), (int)player.getY() - (player.getHeight() / 2) - 30, player.getWidth(), player.getHeight(), null);
(mind the -30 in (int)player.getY() - (player.getHeight() / 2) - 30)
Bullet:
float gunOffsetX = 35, gunOffsetY = 0;
Now the bullet travels right through the crosshair (mind that the red rectangle is right on the weapon):
(I'm a bit too stupid to create proper GIF files, so I can only provide pictures)
Now you have the necessary offset values to get the result you want, but you should definitely try to understand why the values are like they are right now. You need to replace them later with dynamic values, since different weapons need different offsets for the bullet, because the player image differs. It should be helpful to have some kind of class with instances for each weapon type, which contains the images and the coordinates where the weapon barrel is located in the image. Then you can use these coordinates to correctly set the offsets for the bullet image.
I'm trying to draw a NinePatch using a transform matrix so it can be scaled, rotated, moved etc. So I created a class that inherits from LibGDX's NinePatch class and which is responsible of the matrix.
This is how I compute my transform matrix (I update it each time one of the following values changes) :
this.transform
.idt()
.translate(originX, originY, 0)
.rotate(0, 0, 1, rotation)
.scale(scale, scale, 1)
.translate(-originX, -originY, 0)
;
and how I render my custom NinePatch class :
drawConfig.begin(Mode.BATCH);
this.oldTransform.set(drawConfig.getTransformMatrix());
drawConfig.setTransformMatrix(this.transform);
this.draw(drawConfig.getBatch(), this.x, this.y, this.width, this.height); // Libgdx's NinePatch#draw()
drawConfig.setTransformMatrix(this.oldTransform);
Case 1
Here's what I get when I render 4 nine patches with :
Position = 0,0 / Origin = 0,0 / Scale = 0.002 / Rotation = different for each 9patch
I get what I expect to.
Case 2
Now the same 4 nine patches with :
Position = 0,0 / Origin = 0.5,0.5 / Scale = same / Rotation = same
You can see that my 9 patches aren't draw at 0,0 (their position) but at 0.5,0.5 (their origin), like if I had no .translate(-originX, -originY, 0) when computing the transform matrix. Just to be sure, I commented this instruction and I indeed get the same result. So why is my 2nd translation apparently not taken into account?
The problem is probably your scaling. Because it also scales down the translation, your seccond translate actually translates (-originX*scale, -originY*scale, 0) since scale=0.002, it looks like there is no translate at all. For instance for the x coordinate, you compute :
x_final = originX + scale * (-originX + x_initial)
I had to change the code computing my transform matrix to take the scale into account when translating back as pointed by Guillaume G. except my code is different from his :
this.transform
.idt()
.translate(originX, originY, 0)
.rotate(0, 0, 1, rotation)
.scale(scale, scale, 1)
.translate(-originX / scale, -originY / scale, 0);
;
I am currently creating a small 2d-game with lwjgl.
I tried to figure out a way of implementing a Fog-Of-War.
I used a black backgound with alpha set to 0.5.
Then I added a Square, to set alpha to 1 for each tile, which is lit, ending up having a black Background with differend Alpha values.
Then I rendered my Background using the blendfunction:
glBlendFunc(GL_ZERO, GL_SRC_ALPHA)
This works well, but now I have a problem with adding a second layer with transparent parts and apply the Fog-Of-War on them, too.
I've read something about FrameBufferObjects, but I don't know how to use them and if they are the right choice.
Later on I want to lit tiles with an texture/Image to give it a smoother look. So these textures may overlap. This is the reason why I chose to first render the Fog-Of-War.
Do you have an idea how to fix this problem?
Thanks to samgak.
Now I try to render a dark square on each dark tile exept the lit tiles.
I divided each tile in an 8x8 grid for more details. This is my method:
public static void drawFog() {
int width = map.getTileWidth()>>3; //Divide by 8
int height = map.getTileHeight()>>3;
int mapWidth = map.getWidth() << 3;
int mapHeight = map.getHeight() << 3;
//background_x/y is the position of the background in pixel
int mapStartX = (int) Math.floor(background_x / width);
int mapStartY = (int) Math.floor(background_y / height);
//Multiply each color component with 0.5 to get a darker look
glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
glBegin(GL_QUADS);
//RENDERED_TILES_X/Y is the amount of tiles to fill the screen
for(int x = mapStartX; x < (RENDERED_TILES_X<<3) + mapStartX
&& x < mapWidth; x++){
for(int y = mapStartY; y < (RENDERED_TILES_Y<<3) + mapStartY
&& y < mapHeight; y++){
//visible is an boolean-array for each subtile
if(!visible[x][y]){
float tx = (x * width) - background_x;
float ty = (y * height) - background_y;
glVertex2f(tx, ty);
glVertex2f(tx+width, ty);
glVertex2f(tx+width, ty+height);
glVertex2f(tx, ty+height);
}
}
}
glEnd();
}
I set the visible array to false except for an small square.
It will render fine, but if I move the background the whole screen except the visible square turns black.
One approach is to render the Fog-of-War layer last, using an untextured black square rendered over the top of all the other layers after they have been rendered.
Use this blend function:
glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA)
and set the Fog-of-War alpha per-vertex so that when it is 1.0 the black overlay is transparent, and when it is 0.0, it is entirely black. (If you want the alpha to have the opposite meaning, just swap the arguments).
To make it more smooth you can set the alpha per vertex at each of the corners of the square to vary smoothly across it. You could also use a texture with varying alpha values instead of a plain black square, or subdivide the square into 4 or 16 squares to allow finer control.
I want everyone to see the same things on their screen regardless of their screen size and aspect ratio so this is the code I am currently using. (also I am sending net data across with the coordinates of where the other players are on the screen)
int width = 1920, height = 1080;
public OrthographicCamera camera;
Viewport viewport;
//constructor
camera = new OrthographicCamera();
viewport = new ScalingViewport(Scaling.stretch, width, height, camera);
viewport.apply();
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
camera.update();
public void resize(int width, int height) {
viewport.update(width, height);
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
}
now for example I wanted 10 perfect squares going across the middle of the screen so I made then 192 pixels by 192 pixels so I could have 10 perfect squares going across the middle of the screen my system right now works perfect except for the fact that it is rendered internally 1920x1080 on all devices big and small. How would I convert my camera to units and get the size needed for 10 perfect squares to go across the screen? Is that even possible?
Here is my code to draw 10 squares across the screen
float size = 192;
for(int i = 0; i<10; i++){
walls.add(new Stuff(i*size,height/2-size/2,size,size,"middle",1,1,0,1));
}
How would I convert all this code to say units? Or is this an acceptable approach?
You are already using units, they just aren't very meaningful (and it certainly aren't pixels). If you want to use meaningful units (e.g. SI units), then the only thing you have to change in this code are the values. E.g. if the size of your stuff (wall?) is, say 2 meter, then use the value 2 instead of 192. And if you want your users screen to be, say 20 meters (10 walls e.g.) in width and 16:9 aspect ratio, then use that for the Viewport worldWidth and worldHeight.
float worldWidth = 20;
float worldHeight = worldWidth * 9f / 16f;
...
viewport = new StretchViewport(worldWidth, worldHeight, camera);
Make sure to understand that these "pixels" you are talking about only exist in your imagination. See also: http://blog.xoppa.com/pixels/.
You created your ScalingViewport with a width of 1920, so the width in world units will be 1920 on all screens, no matter what. Also, your scene will be distorted on any screen that is not 16:9, since you are stretching to fit whatever the screen is. (Because of the distortion, I personally would never use ScalingViewport with Scaling.stretch, aka StretchViewport.)
If you want your squares to look square on all screens with this type of viewport, you'll have to do some math to change their height (but their width should always be 192 if you want exactly ten to fit across the screen).
public void resize(int width, int height){
float viewportAspect = 1920f / 1080f;
float screenAspect = (float)width / (float)height; //Make sure you cast to floats
boxHeight = 192 * screenAspect / viewportAspect;
viewport.update(width, height, true);
}
The camera always shows the scene in world units, so there's no conversion to do.