At the beginning I have to say that I'm learning java on my own and may make a stupidest mistake ever...
I'm facing a problem with FPS drop while drawing Bitmaps in my 2d game.
My game's map consists of Tiles 100x100px, and every tile has some surface graphic and may have something else (like tree, rock, or whatever).
I have 2 main drawing methods. Both looks similar. First is drawing map and second everything that is on it (trees etc.).
Here is a code. I'm limiting what is to be drawn in this method.
public static void drawMap(Canvas canvas, Player player, ArrayList<MapField> map)
{
int a = player.getShiftX()/Constants.TILE_SIZE;
int b = player.getShiftY()/Constants.TILE_SIZE;
for (int x = a-Constants.VISIBILITY_X; x<=a+Constants.VISIBILITY_X; x++)
{
if (x>=0&&x<=99)
{
for (int y = b-Constants.VISIBILITY_Y; y<=b+Constants.VISIBILITY_Y*2-1; y++)
{
if (y>=0&&y<=99)
{
map.get(x+y*100).update();
map.get(x+y*100).draw(canvas);
}
}
}
}
}
Then I'm calling:
public void draw(Canvas canvas)
{
canvas.drawBitmap(Graphics.TILES_BITMAP[tileId], null, rect, p);
}
When I limit "vision" to 3 squares each direction (drawing about 60 tiles) FPS is 60. As soon as I get rid of that limit (whole screen is to be drew - about 250 tiles) FPS are dropping to 27-30 which makes game unplayable.
It is normal behaviour? Is Java that limited?
Or just maybe I made a mistake here?
Full code to be seen here (if anyone care to check):
gitlink
Someone told me that with that amount of graphics i should already use some OpenGL and suggested me to learn LibGDX for example. But as for me, pure java is more elegant :)
Ok, i've managed to solve this issue. It would only for for API26+ though.
Instead of just:
canvas = this.surfaceHolder.lockCanvas();
i've put:
canvas = this.surfaceHolder.lockHardwareCanvas();
And now again i have 60FPS+ :)
Related
sorry for the badly worded question, I'm not exactly sure how to ask this. I'm making a small game with LibGDX and I'm having trouble with collisions.
Basically, my original idea was to simply check whether or not the terrain rectangle and the player rectangle overlapped each other, and if they did, I would move the player rectangle so that it wouldn't overlap the terrain. However, I did this and it wasn't working how I had expected, and it was basically thinking that the top and bottom of the player rectangle were also colliding with the side of the terrain, and then it slides the rectangle along the terrain.
So, I was recommended to try to make a sort of box cast and honestly it hasn't really been working either. I'm new to libgdx so I'm not sure if there's an easier way to do this or not but I've tried looking around. Here's my code:
void handleCollisions() {
Rectangle pRect = player.getRect();
for(Rectangle mapRect : mapRects) { //mapRect is the terrain rectangle
//if(mapRect.overlaps(pRect)) {
float amountCollidedTop = 0f;
float amountCollidedBottom = 0f;
float amountCollidedLeft = 0f;
float amountCollidedRight = 0f;
float xCollided = 0;
float yCollided = 0;
if(mapRect.overlaps(player.boxCastTop)) {
}
if(mapRect.overlaps(player.boxCastRight)) {
xCollided = player.boxCastRight.x + player.boxCastRight.width - mapRect.x;
if(mapRect.y > player.boxCastRight.y) {
yCollided = mapRect.height-(mapRect.y - player.boxCastRight.y);
} else {
yCollided = mapRect.height-(player.boxCastRight.y-mapRect.y);
}
//what percentage of the box cast is being collided with?
amountCollidedRight = (xCollided*yCollided)/player.boxCastRight.area();
System.out.println(amountCollidedRight);
}
//}
}
}
Here's what the collision looks like
Here is what is appearing in the console
Maybe this isn't the way to go with collisions? If it isn't, is there another way that works better? If anyone needs anything clarified please let me know. Thanks!
You should have a look at the libGDX Demo Projects for reference.
Especially libgdx-demo-cuboc could be interesting for you, since it is a platformer with a simple collision system (most of it is implemented in the class Bob I think).
Or if you want to use Box2D (a 2D physics library that many libGDX projects make use of) you can have a look at the libgdx-demo-vector-pinball project.
I've tried:
1.Creating a separate variable called "factor" and multiplying or dividing literally everything with it: entity velocities, object sizes, fonts, resolution etc..
(the factor is always relative to the resolution so the objects are scaled properly)
public class Player extends Entity{
float size;
public Player(needed variables) {
super(needed variables);
resize();
}
public void resize() {
/*
Resize everything.
This method is supposed to be called from a separate resizing
function located in another class when the JFrame size is changed.
the function has to play with the choice between divide or multiply
variables with the factor
*/
}
public void tick() {
x += velX*factor;
y += velY*factor;
}
etc..
}
By using this factor to multiply literally everything, it makes the code really messy and hard to read sometimes.
2.Rendering to a BufferedImage and scaling the BufferedImage to fit to the JFrame.
void render() {
//Render the game to a new BufferedImage
BufferedImage renderedFrame = new BufferedImage(1920, 1080, BufferedImage.TYPE_RGB);
renderedFrame.createGraphics();
Graphics g = renderedFrame.getGraphics();
//Render the game ....
//Scale the BufferedImage to fit the current resolution and render it to the Canvas
BufferStrategy bs = getBufferStrategy();
Graphics f = bs.getDrawGraphics();
f.drawImage(renderedFrame.getScaledInstance(1280, 720, Image.SCALE_FAST), 0, 0, null);
f.dispose();
bs.show();
}
Which makes the code much more readable but then there comes 2 problems:
Mouse input problems and resizing the BufferedImage is taking too much resources which makes the game laggy.
3.I could basically try to make a separate unit system for the game.. but then there's the same problem, when it comes to rendering strings or rectangles I'd have to multiply everything with the factor and the code is horrible after that.
Is there any better ways of rendering 2D games? If no then I'll think about moving on to OpenGL.
Thanks in advance.
The way I've done this most successfully is by scaling the graphics object. You end up with something like the following:
final int gameUnitsPerScreenDim = 32;
void render(JPanel panel, Graphics2D g2) {
double pixelHeight = panel.getHeight();
double pixelsPerGameUnit = pixelHeight / gameUnitsPerScreenDim;
g2.scale(pixelsPerGameUnit, pixelsPerGameUnit);
...
}
And then for the simulation, you use game units. How big a game unit actually is is a bit arbitrary, although if you're making a tiled game there's probably some obvious value that it should be.
Instead of using scale, you can also create an AffineTransform which lets you reuse it:
if (this.tf == null || /* image size changed since the last frame */) {
...
this.tf = AffineTransform.getScaleInstance(pxPerGu, pxPerGu);
}
g2.setTransform(this.tf);
(Calling scale creates a new AffineTransform every time you call it.)
That's even a little more efficient, although probably not by much.
(If you want, you can also use a transform to invert the y-axis and translate so the origin is at the center of the image. This makes a lot of trigonometry and stuff feel more natural. Inverting the y-axis makes working with text a pain, though.)
Also, using OpenGL is probably better. Having written a couple of simple games using Swing for fun, I don't see a good reason to do it.
The basic question here is: how to ALWAYS keep your sprites within the Fitviewport? How to keep a reference to the view in order to have the proper coordinates as to where to draw?
I'm trying to spawn enemies into the gameplay screen. But this is handled by a FitViewport, and enemies and even the player can move outside the FitViewport on certain screen resolutions. So far the problem seems to be in the Y axis.
The FitViewport is made like this:
gameCamera = new OrthographicCamera();
gameCamera.setToOrtho(false);
gameViewport = new FitViewport(MyGame.WORLD_WIDTH,MyGame.WORLD_HEIGHT,gameCamera);
gameViewport.setScreenBounds(0,0,MyGame.WORLD_WIDTH,MyGame.WORLD_HEIGHT);
Then the camera position gets updated like this at the resize() method:
gameViewport.update(width,height); //not used when using the virtual viewport in the render method.
gameCamera.position.set(player.position.x + 200,player.position.y, 0);
Then the update() method calls the Player's own update() method which includes these lines:
//POSITION UPDATE
if (this.position.x<0) this.position.x=0;
if (this.position.x>Gdx.graphics.getWidth() - width) this.position.x= Gdx.graphics.getWidth() - width;
if (this.position.y<0) this.position.y = 0;
if (this.position.y>PlayScreen.gameViewport.getScreenHeight() - height) this.position.y = PlayScreen.gameViewport.getScreenHeight()- height;
Notice for the X axis I'm still using Gdx.graphics dimensions because I'm yet to make it work with PlayScreen.gameViewport.getScreenHeight() (gameViewport has been set to static for this purpose).
Also on enemy spawn (the problem related here is that they spawn outside of the screen Y in terms of what I see) I have this code inside the update() method of the Screen implementing all these viewports:
//Alien Spawn
if (System.currentTimeMillis() - lastZSpawn >= SpawnTimer){
count++;
lastZSpawn= System.currentTimeMillis();
for (int i=0;i<count;i++){
int x = Gdx.graphics.getWidth();
int y = random.nextInt((int)gameViewport.getScreenHeight() - Alien.height);
if (entities.size()<6){
entities.add(new Alien(new Vector2(x,y),1, alienImages,(float)((0))));
}
}
}
Also using gameViewport.getScreenHeight() here cause Gdx.graphics wasnt giving the correct result (it gave me the same issue really).
The render() method is correctly implemented in terms of the batch and applying the viewport:
MyGame.batch.setProjectionMatrix(gameCamera.combined);
gameViewport.apply();
MyGame.batch.begin();
for (int i = entities.size()-1; i>=0;i--){
entities.get(i).render();
}
You should never change the position of your player or enemies when resizing, that's why a viewport is for, remove all the code that do that first, to make your viewport work as you expected you need to create a new instance of camera passing the new viewport width and height when you resize, i prefer to make my camera static so i can acess its atribbutes from everywhere i want, you should do something like this:
public static OrthographicCamera update(int width,int height){
instance = new OrthographicCamera(width, height);
instance.setToOrtho(false);
return instance;
}
The answer to my problem is posted by myself in another question of mine which was also driven by a confusion in the implementation of FitViewports and using WorldWidth and WorldHeight properties as coordinates of reference when drawing objects into the game, and also correctly setting the camera position taking these values into consideration aswell.
The answer is here even though its text and not code and its mainly what i already wrote in this very post. FitViewport doesnt scale properly Libgdx
I'm learning how to make an Applet using Java in the form of a game. In the game, I have a character sprite drawn at the center and moves when the player presses w a s d.
It goes like this:
public game extends applet implements KeyListener {
int x, y;
URL url;
Image image;
public void init() {
x = getSize().width/2;
y = getSize().height/2;
url = new URL(getCodeBase());
image = getImage(url, "player.gif"); //take note that this is a still image
addKeyListener(this);
}
public void paint(Graphics g) {
g.drawImage(image, x, y, 32, 32, this); //the size of the image is 32x32
}
public void KeyPressed(arg0) {
char c = arg0.getKeyChar();
switch(c) {
case 'w':
y -= 10;
break;
/*And so on. You guys know how it works.*/
}
repaint();
}
My problem is, the character sprite seems dull when the user doesn't press anything. What I want to do is to make the Image an array of images and put a simple image animation by looping the array in paint like so:
public void paint(Graphics g) {
for(int i = 0; ; i++) {
g.drawImage(image[i], x, y, 32, 32, this);
if(i == image.size() - 1) { i = 0;}
}
}
However, if I do this, I won't be able to get anymore KeyEvents that would activate when the user wants to move. My question is this: How will I make it so that my character does an animation when the program is "idle" (i.e. the user isn't pressing anything) while still maintaining the capability to take in KeyEvents (e.g. moving when the player types in w, a, s, or d, and then continuing the idle animation after repainting)?
Thanks in advance.
PS. I'm still quite a beginner in Java so sorry if my code is not very nice. Any advice is welcome.
You need to make your application multithreaded so that the painting runs in a separate thread.
Otherwise you will have the painting blocked while waiting for the next key.
This is not a trivial change to your code though.
Perhaps you would give yourself a new class, say InterestingCharacter, which can cycle through any of N states (corresponding to your N images). You clearly can't let any paint method run infinitely, but if your InterestingCharacter could render itself in its current state you might be onto something with that. Maybe it will be enough that this InterestingCharacter knows what state it is in and then some other object manages the rendering. Would it be helpful if the InterestingCharacter could tell you that its state has changed and so needs to be rendered again? If so, you could implement the Observer pattern such that the character is observed and your game an observer.
I think the trick will be to break the problem down into a few classes that have appropriate responsibilities--ideally a class should have one responsibility and each of its methods should do one thing.
Just some ideas to help you move forward. Experiment with it and see how it goes. Hope it helps!
Few days ago I figured out how to do some scrolling in LibGdx. Now I'm triying to do something related. I want to repeat the background. My scrolling follows a ship (Is an s[ace ship game). In the background there is a space photo loaded as a Texture. When the ship reach the end of the backgorund, It keeps going and there's no background anymore. I have read about wrap but I don't really understand How It works. I did that:
px=new Pixmap(Gdx.files.internal("fondo.jpg"));
background=new Texture(px);
background.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);
And then, in my render method
spriteBatch.begin();
spriteBatch.draw(background,0,0,500,50);
drawShip();
spriteBatch.end();
Of course It doesn't work, It only draws the background once. I don't know how make this wrap method work. Any help?
SOLUTION
I figured It out. It's not a nice code but It works.
First I declare two Textures with the same image
bck1=new Texture(Gdx.files.internal("fondo.jpg"));
bck2=new Texture(Gdx.files.internal("fondo.jpg"));
Also I declare two variables like this to specify the X value of the position of each bck
int posXBck1=0,posXBck2=0;
Then I use that in Render()
public void calculoPosicionFondos(){
posXBck2=posXBck1+ANCHODEFONDO;
if(cam.position.x>=posXBck2+cam.viewportWidth/2){
posXBck1=posXBck2;
}
}
Where:
ANCHODEFONDO is the width of my background
Cam is an OtrhoCam.
So I said that if the cam is in bck2 (wich means that you can't see bck1 anymore) It change positions, giving bck1 de position of bck2 and, in the next render loop, recalculating bck2
Then just paint both bck in your render mode.
Like Teitus said, do not load your texture multiple times, ever! Anyway, you where on the right track with the wrapper:
texture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);
Now you can just use the draw method with the source location. The source location is the area you choose to draw on the texture.
batch.draw(texture, x, y, srcX, srcY, srcWidth, srcHeight)
To scroll your texture from right to left all you have to do is increase srcX incrementally. So create a int that increments in the update/render method.
int sourceX = 0;
//render() method
//Increment the variable where to draw from on the image.
sourceX += 10;
//Simply draw it using that variable in the srcX.
batch.draw(YourTexture, 0, 0, sourceX, 0, screenWidth, screenHeight);
Because you are wrapping the texture it will wrap/loop and scroll indefinitely. There might be a issue with the sourceX int if the game runs for a very long time because a int can only hold 2147483647. It takes a while but you can fix it by subtracting the image width each time the number goes over the total image width.
Don't to this, please:
bck1=new Texture(Gdx.files.internal("fondo.jpg"));
bck2=new Texture(Gdx.files.internal("fondo.jpg"));
That will load your big background texture twice. That's a complete waste. If you want to keep your solution at least do:
bck1=new Texture(Gdx.files.internal("fondo.jpg"));
bck2=bkg1;
Regarding the texture Wrapping. If your texture is 500px wide, and you draw a 500px sprite, you won't see any repetition. If you want it repeated 2 times, draw it 1000px wide with 0-2 texture coordinates.
I'm not sure how spriteBatch handles the call you posted, you could try that one, or may be use the overload that uses a texture region and set your region manually.
I see this is a pretty old question, but I think there is an easier way to accomplish background scrolling. Just use the Sprite class. Here is a snippet I use for layered background images that scroll from right to left.
public class LevelLayer
{
public float speedScalar = 1;
private List<Sprite> backgroundSprites = new ArrayList<Sprite>();
public LevelLayer()
{
}
public void addSpriteLayer(Texture texture, float startingPointX, float y, int repeats)
{
for (int k = 0; k < repeats; k++)
{
Sprite s = new Sprite(texture);
s.setX(startingPointX + (k*texture.getWidth()));
s.setY(y);
backgroundSprites.add(s);
}
}
public void render(SpriteBatch spriteBatch, float speed)
{
for (Sprite s : backgroundSprites)
{
float delta = s.getX() - (speed * speedScalar);
s.setX(delta);
s.draw(spriteBatch);
}
}
}
Then you can use the same texture or series of textures like so:
someLayer.addSpriteLayer(sideWalkTexture1, 0, 0, 15);
someLayer.addSpriteLayer(sideWalkTexture2, 15 * sideWalkTexture1.getWidth(), 0, 7);
I change background repeating sections randomly in code and make new ones or reset existing sets when they go off screen. All the layers go to a pool and get pulled randomly when a new one is needed.
SOLUTION
I figured It out. It's not a nice code but It works.
First I declare two Textures with the same image
bck1=new Texture(Gdx.files.internal("fondo.jpg"));
bck2=new Texture(Gdx.files.internal("fondo.jpg"));
Also I declare two variables like this to specify the X value of the position of each bck
int posXBck1=0,posXBck2=0;
Then I use that in Render()
public void calculoPosicionFondos(){
posXBck2=posXBck1+ANCHODEFONDO;
if(cam.position.x>=posXBck2+cam.viewportWidth/2){
posXBck1=posXBck2;
}
}
Where:
ANCHODEFONDO is the width of my background
Cam is an OtrhoCam.
So I said that if the cam is in bck2 (wich means that you can't see bck1 anymore) It change positions, giving bck1 de position of bck2 and, in the next render loop, recalculating bck2
Then just draw both bck in your render()