I have a little problem with my game. Let's assume that there's a game room of 1024x800 (px) and you can slide it to see more content.
When I place two objects (one next to other), based on the original dimensions, it looks OK on a mdpi device, but in hdpi (for example) they are "closer" (it must be because their bitmaps are larger but the "x" and "y" coordinates are the same).
Here's an example:
So, I think that I must make the game room larger in hdpi with a Rule of Three. First, I thought that I can do it just in the draw method (Canvas.drawBitmap) but it affected some collisions (touch events didn't match, for example). I can't make the rule in every single line where I used X&Y coordinates because I did all my levels and game physics considering the original unity (pixels).
Here is my draw method:
protected void drawSprite(int x, int y, Canvas canvas){
int xv = isHUD?0:lw.getXView();
int yv = isHUD?0:lw.getYView();
canvas.drawBitmap(sprite_index, x+xv, y+yv, paint);
}
getXView() and getYView() returns the coordinate of the actual slide of the view.
And this is a method that I used to check collisions:
public Rect getShape() {
int xv = isHUD?0:lw.getXView();
int yv = isHUD?0:lw.getYView();
return new Rect(x+xv,y+yv,x+width+xv,y+height+yv);
}
What should I do?
Considering that I'm going to put a "Level Editor" in which the players are going to share their creations, storing the "id" and XY coordinates on a database.
Related
I have read a few articles about resolutions, screens, viewports and cameras on the mobile phone, but I am much more confused now, that I were before. Could you please help me to keep up with issue and handle it, as currently I am working on mobile game, but without any success. I am using LibGDX.
Regarding to answer below I changed my program (thanks for explanation Xoppa :)
New piece of code:
#Override
public void create () {
orthographicCamera = new OrthographicCamera();
fillViewport = new FillViewport(960, 600, orthographicCamera);
orthographicCamera.position.set(orthographicCamera.viewportWidth * 0.5f, orthographicCamera.viewportHeight * 0.5f, 0);
fillViewport.apply();
}
.
#Override
public void render () {
Gdx.gl.glClearColor(0.22f, 0.22f, 0.22f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
...
}
.
#Override
public void resize (int width, int height) {
fillViewport.update(960, 600);
orthographicCamera.position.set(960 * 0.5f, 600 * 0.5f, 0);
}
But the result is the same.
Output:
Two small dots are my players. :(
Even if I change the Viewport resolution, size of my players do not change. The only thing that change was visible resolution of viewport, behind it I do not see mz players. I sketeched it for better imagination (values are just for imagination).
Physics body of my players:
public Character(Vector2 startPosition) {
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyType.DynamicBody;
bodyDef.position.set(startPosition);
// Create our body in the world using our body definition
body = Physic.gameWorld.createBody(bodyDef);
// Create a circle shape and set its radius to 6
CircleShape circle = new CircleShape();
circle.setPosition(new Vector2());
circle.setRadius(0.39f);
// Create a fixture definition to apply our shape to
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = circle;
fixtureDef.density = 1f;
// Create our fixture and attach it to the body
body.createFixture(fixtureDef);
circle.dispose();
}
Even if I change the radius of the circle, body will need much more energy to manipulate with and also body is not big enough. Of course I can not set the radius to value which is more than 10f as Box2D doc not recommended it.
But I do not see anything, when I run it, or created physical world objects are too small or flattened (physical world configuration and initialization is good I think; radius of circle physical objects are 0.39). Or am I missing something in the code, some statements or anything else?
But I think I have problem with correct understanding of mentioned issues.
Could you please help me with this or explain it?
First make sure to understand what a camera and viewport is and does. Perhaps this example might help:
Imagine that you're in the park and take a photo with your camera/smartphone of a tree and a bench. The bench is e.g. 3 meters wide and half a meter in height and depth. The tree might be e.g. half a meter wide and 10 meters in height. Now if you look at the photo on the screen of your camera/smartphone, then you'll see that the bench is no longer 3 meters wide, instead it is just a few millimeters wide. The actual size depends on how you've setup your camera (e.g. zoom) as well as the resolution of the photo (in pixels) and the density of the screen you're watching it on.
So practically in the example above the bench and the tree have two different sizes: the actual size in the park in the physical world and the size on the screen you're watching the photo on. Of course the tree and the bench don't actually shrink depending on the photo, they stay the same size. The size on the photo is only the size of the projection of the tree and the bench.
Projection is practically transforming world objects onto the screen, based on various camera settings (like zoom, position, etc.).
The park is much bigger than only the portion you've taken the photo of. When you took the photo you decided which portion of the park (the physical world) you want to project onto the photo. Let's call this the park's viewport.
Likewise you also don't have infinite storage to project the photo, you'll have to define the portion (the resolution of the photo) you want to project onto. Let's call this the photo's viewport.
The park's viewport is expressed in real world units, like meters or inches e.g. The photo's viewport is expressed in pixels (the resolution you've set it to).
When making a game you typically aren't making a photo but rendering to screen. Therefor the photo's viewport is then called the screen viewport. And when making a game your park might not even exist, your game world is virtual. Therefor the park's viewport is then called the virtual viewport.
On a small side-note: pixels are always integers, therefor screenviewport is always expressed in int. World units like meters or inches can be fractional, therefor virtualviewport is always expressed in float.
The Camera class of libgdx does exactly what is described above, it performs the the projection of your virtual world onto the screen. However, in practice that comes with some problems. E.g. not every screen has the same aspect ratio. Therefor you need to define how you want to cope with differences in aspect ratio. E.g. add black bars, expand the virtual world or stretch it, etc.
The Viewport class of libgdx solves that problem by implementation various strategies you can choose from on how to define your virtualviewport and screenviewport. To do this it encapsulates (and manages) a camera for you.
In your code you've given it a OrthographicCamera in the constructor to manage, which is typically used for 2D games. The virtualviewport you've set the OrthographicCamera to in the constructor is overwritten though. Because you've chosen to use a ScreenViewport. This is an implementation that makes the virtualviewport (the portion of the park) the same as the screenviewport (the portion of the screen).
It is very unlikely that you want to use that viewport implementation. Instead you probably want to use e.g. a FillViewport:
public void create () {
viewport = new FillViewport(50f, 50f);
}
This creates a virtualviewport that grows if needed to maintain aspect ratio. The 50 by 50 is in world units, e.g. meters or inches.
Now we need to tell the viewport which screenviewport to use. This depends on the size of the screen of the device and is therefor best set in the resize method.
public void resize (int width, int height) {
viewport.update(width, height);
}
All other code of these methods of you can be removed, you dont need them. You dont need to update the camera in your render method. You can just use the viewport as you normally would. And if you need access to the camera directly (but not to modify it, only to read e.g. its projection matrix), then you can use:
viewport.getCamera()
I have created an Android game using a canvas, but when testing, I have found that the speed and distance of the movements such as flying up or falling down are set right on a phone with a resolution of 1920x1080 (401 ppi). But when I tested on a smaller phone with a resolution of 480 x 854 (196 ppi), I found that the movement of my sprites are a lot quicker which is affecting the gameplay. E.g. The main character sprite jumps a lot higher than I want it to.
Is there any way of keeping the speed and distance the same across all device sizes and types?
Here is some code on how I have implemented the movement:
A sprite class.
//class variables
private int GRAVITY_LIMIT = -30;
public int gravity = 0;
//gravity
if(gravity>GRAVITY_LIMIT){
gravity= gravity-2;
}
//fall
y= y-gravity;
Drawing the sprite
canvas.drawBitmap(bmp1, x, y, null);
When onTouch is triggered (Jumping)
bird.gravity=30;
You should base your movement around world coordinates. For example, set your world to be 10meters x 10meters, so that when you jump, you jump 1m. You then need to map that world to screen pixels.
float worldHeight = 10f;
float worldToPixels = screenHeight/worldHeight;
y = bird.y * worldToPixels;
So, on a 500px height screen, you would jump 50px and on a 1000px height screen you would jump 100px.
Gravity and other forces need to be based on the world as well for it to work on all devices.
Lastly, if you're trying to make a game for multiple devices, it would be better to use a library like libGDX. There are lots of helpful classes like ViewPorts to make this easier.
I have found another solution.
This one works well.
y= y-(gravity * game.getResources().getDisplayMetrics().density);
My question is probably going to be initially very confusing to read, so just bear with me. I'll start it off with a little preface for context:
Preface:
I have an app that will be using an array for path-finding from a map.
^That is very vague: There will be an array of characters, representing walls, stairs, etc., and there will be a function that finds the best path.
I want to display the path on the android screen.
There will be characters that are generated by the array function that represent the generated path (probably "x" or something).
Okay, to make it more clear: There will be a "path" of 'x's in an array. These 'x's represent the path that is going to show up on the Android screen.
My actual question:
How do I translate an 'x' in the array to displaying a line on the screen? I had the idea to use a for loop/if statement that checks if there is an 'x' and if there is, then to display a little red dot/line in a second array that represents the actual screen.
I was trying to find this, but it's such an awkward thing to type into google, so I finished my research with nothing.
Is there some sort of built-in android function that lets you assign different colours to different coordinates?
This is kind of what I want to appear on the screen. If this were the app, the blue would be represented by 'x's in the first array.
there could be several ways to achieve this effect of having a coordinate system mapping to a matrix that describes a path.
Depending on the size of the array and the frequency of update calls (it sounds like the path finding runs once with a single render after), it probably wouldn't be too expensive to just loop through. What I personally would do is start to look at how to draw on a canvas, get the screen size, and adjust the bounds accordingly.
Get screen dimensions in pixels - How to get screen dimensions
http://danielnadeau.blogspot.com/2012/01/android-canvas-beginners-tutorial.html - A nice tutorial on canvases
Once you can draw to a scaled canvas, it is simply a matter of running a loop that looks something like:
float scale_x = screen_width/columns;
float scale_y = screen_height/rows; //pixels per grid square
for( int x = 0; x < columns; x++)
for( int y = 0; y < rows; y++)
if( data[x][y] == 'x') drawRect(x*scale_x, y*scale_y, scale_x,scale_y) //if something found, draw a colored square
I know there are numerous other threads about this. I've been checking through them with no avail for the past 2 days and right now it's driving me nuts.
So basically I have a spritesheet, which I load into a Bitmap, cut the frames apart, perform scaling on them and load them into a list for later animation. Scaling looks good ranging from ldpi to xhdpi, so does animation.
But there's another thing that bugs me, and that's the positioning on different devices. I draw them with canvas.drawBitmap(bitmap, srcRect, destRect, paint). Well, I guess code tells more than thousand words.
//converting dps to pixels
int xCoord = convertToPixels(100);
int yCoord = convertToPixels(150);
//defining destination rectangle; sprite is a Bitmap object
Rect destRect = new Rect(xCoord, yCoord, xCoord+sprite.getWidth(), yCoord+sprite.getHeight());
//drawing
canvas.drawBitmap(sprite, null, destRect, null);
convertToPixels(float dp) just returns a number of pixels with formula Math.round(dp*DENSITY).
I believe that if I specify dps, the aspect ratio on all devices should be the same, e.g. if I put something on 10% of the screen size of one device, it should maintain those 10% on another smaller/bigger/less dense/more dense screen. But I guess my logic is flawed, because it doesn't work. It draws sprites on different devices on different places.
To summarize: drawing a Bitmap object with destRect with x and y coords 100 dps should maintain ratio on all devices with respect to canvas size or I ought to think this way.
I would kindly ask you to help me with this matter, for I'm lost currently lost, more literally than figuratively.
And please don't just give me a link to "Supporting Multiple Screens" topic on developer site or similar sites, because I've read them many times and yet it seems I don't understand them. Thank you!
Are you sure the dip to px calculation is correct? I wrote the following code to do this:
public class ViewHelper {
public static int getPxFromDip(int dips){
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dips, Application.getContext().getResources().getDisplayMetrics());
}
}
Where Application.getContext() is the Application wide context, or provide the local Activity context.
I am working on an Android Application in which a 3d scene is displayed and the user should be able to select an area by clicking/tapping the screen. The scene is pretty much a planar (game) board on which different objects are placed.
Now, the problem is how do I get the clicked area on the board from the actual screen-space coordinates?
I was planning on using gluUnProject(), as I have access to (almost) all the necessary parameters. Unfortunately I am missing the winZ param, and cannot get the current depth as the touch event is occurring in a different thread than the GL-thread.
My new plan is to still use gluUnProject, but with a winZ of 0, and then project the resulting point onto the board (the board stretches from 0,0,0 to 10,0,10 in model space), However, I can't seem to figure out how to do this?
I would be very happy if anyone could help me out with the maths needed to do this (matrices were never my strongest side), or perhaps find a better solution.
To clarify; here is an image of what I want to do:
The red rectangle represent the device screen, the green x is the touch event and the black square is the board (grey subdivisions represent a square of one unit). I need to figure out where on the board the touch has happened (in this case it is in square 1,1).
As you are working in 2D basically already, (I presume you mean your 3D board stretches from 0,0,0 to 10,10,0 (x,y,z).) you could translate and interpolate/extrapolate the 2D/3D space coordinates from your screen space coordinates without the gluUnProject(). You will need your screen resolution, and to pick the resolution of the 3D space grid you wish to convert to. If both the screen and 3D space origins are aligned (0,0 screen space is at 0,0,0 3D space), and your screen dimensions are 320x240, using your existing 10x10 3D grid, then 320/10 = 32, and 240/10 = 24, thus the screen space size of a single 1x1 area is 32x24. So if the user presses on 162, 40, then the user is pressing within ( 5, 1, 0) (162/32 >= 5 but < 6, 40/24 >= 1 but < 2 ) in the 3D space. If you need greater resolution than this you can select a higher 3D space grid resolution (i.e. using 20 instead of 10). You don't need to update the GL matrix to use this factor. Though it may make it simpler in some ways, I'm sure from a modeling perspective you would have additional work to do. Just be aware for a factor like 20, 1,3 would be at (.5, 1.5, 0). If your screen and 3D space origins are not already aligned will need to translate the screen space coord prior to this. If 0,0 screen space is 10,10,0, you will need to take your screen resolution and subtract the current point from it, making 0,0 into 320, 240 in this example, our example point of 162, 40, would be 158 (320-158 == 162), 200 (240-200 == 40).
If you'd like an overview of the projection matrix and how that all works, which could help you understand where to put the screen space dimensions in the unproject matrix, read this chapter of the OpenGL red book. http://www.glprogramming.com/red/chapter03.html
Hope this helps, and good luck!
So, I managed to solve this by doing the following:
float[] clipPoint = new float[4];
int[] viewport = new int[]{0, 0, width, height};
//screenY/screenX are the screen-coordinates, y should be flipped:
screenY = viewport[3] - screenY;
//Calculate a z-value appropriate for the far clip:
float dist = 1.0f;
float z = (1.0f/clip[0] - 1.0f/dist)/(1.0f/clip[0]-1.0f/clip[1]);
//Use gluUnProject to create a 3d point in the far clip plane:
GLU.gluUnProject(screenX, screenY, z, vMatrix, 0, pMatrix, 0, viewport, 0, clipPoint, 0);
//Get a point representing the 'camera':
float eyeX = lookat[0] + eyeOffset[0];
float eyeY = lookat[1] + eyeOffset[1];
float eyeZ = lookat[2] + eyeOffset[2];
//Do some magic to calculate where the line between clipPoint and eye/camera would intersect the y-plane:
float dX = eyeX - clipPoint[0];
float dY = eyeY - clipPoint[1];
float dZ = eyeZ - clipPoint[2];
float resX = glu[0] - (dX/dY)*glu[1];
float resZ = glu[2] - (dZ/dY)*glu[1];
//resX and resZ is the wanted result.