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);
Related
On my game screen i want to have a swipe detected only if its more than 100px, this is because the user taps a lot on the game screen and it tends to detect a swipe which changes the screen back to title. How can i make the swipe detect only if its longer than 100px?
There are two ways to achieve this.
The first one is to save the starting point of the touch and measure the distance on end of the touch event, just like Paul mentioned.
The second is to enlarge the tap square size if you use the GestureDetector of libgdx. Its defaulted to 40px which means if you're finger moves more than 20px to any side it's no longer a tap event but a pan event. I'd recommend using the GestureListener/Detector as it will give you the basic mobile gestures out of the box rather than recoding them.
On a side note: Determining the distance by pixels is error-prone because the pixel density will vary between mobile devices, especially if you code for android! 100px on one device may be only half the distance than on another device. Take pixel density into consideration when doing this or change to relative measurements like 1/3 of the screen size!
Save the position in the touch up and down.
private Vector2 downPos = new Vector2(), upPos = new Vector2();
private Vector3 tmp = new Vector3();
public void touchDown(float x, float y.....) {
tmp.set(x, y, 0);
camera.unproject(tmp);
downPos.set(tmp.x, tmp.y);
}
public void touchUp(float x, float y.....) {
tmp.set(x, y, 0);
camera.unproject(tmp);
upPos.set(tmp.x, tmp.y);
float distance = downPos.dst(upPos); // the distance between thoose vectors
if (distance > 100) {
// there was a swipe of a distance longer than 100 pixels.
}
}
If you don't want to do that only on touch up, put the code in the touchdrag method.
I have a simple function that increases the speed of my object in the screen:
float Velocity = 10;
float MaxVelocity = 100;
float VelocityGiven = 0;
RectF position = new RectF(ScreenHeight/2, ScreenWidth,0,0);
public void update()
{
if(VelocityGiven < MaxVelocity)
{
Position.left -= Velocity;
VelocityGiven += Velocity;
}
}
public void draw(Canvas canvas)
{
canvas.drawBitmap(Bitmap,Position,null);
}
But depending on the phone screen size, or pixel density, this function moves the object too fast or too slow.
if I try that on a galaxy S4, witch has a really big screen resolution, the object goes slowly.
But if I try it on a low end device (small screens), the object goes very fast,
What can I do to avoid that?
I already have my FPS controlled, on every phone, this runs at 30 FPS. so it's not about the update ratio.
It depends on what you wish to do exactly. One simple approach would be to make everything proportional to screen size. For instance:
Position.left -= Velocity * getWidth() / REFERENCE_WIDTH;
where REFENRENCE_WIDTH corresponds to a screen resolution where your app behaves as you like.
You need to work with density independent units. That way it moves the same relative amount no matter what the pixel size is. To do this, decide how "big" your area is going to be. For example, to fix your width at a logical 1000 units:
float scaleWidth = (screenWidth / 1000);
float velocity = 10 * scaleWidth;
float maxVelocity = 100 * scaleWidth;
This would mean that no matter how wide the display actually is, at max velocity it would take 10 movements to go all the way across.
You are probably best to move your objects based on a grid system independent of the screen size. You could you meters, feet, or what ever works as your unit. Then get the screen size and screen resolution of the device and figure out what the relationship is. If your object moves one foot then how many pixels is that on the device. Based on resolution.
Here is a very general overview on screen sizes for android:
http://developer.android.com/guide/practices/screens_support.html
There are probably a lot more, and better, places to look for getting the device resolution and size.
Hope that at least gets you started :)
i want to do a 2D game with backgrounds and sprites (views) moving on the screen.
I want to make a game with a scrolling ground. I mean the user must see a horizon in the top part of the screen filling the 30% of the screen size. The ground must be scrolling and must be the 70% of the screen size. For example, if i put a car on the ground, the car must be driving into a scrolling road and the sky (horizon) must be seen on the screen, in the top of the road, filling the 30% of the screen.
I am searching in google about scrolling games but i can't find the way to achieve this kind of scrolling ground game with horizon.
Any ideas and approaches will be grated, i'm just making a research about how to do this.
Thanks
This kind of effect can be done in various ways, here is one very basic example I can come up with.
First create a background image for your horizon - a blue sky with a sun would be good. Now create some detail images for the background, such as clouds and birds. These can move accross the background image from left to right (and/or vice-versa). In your rendering code you would render the "background" image first, and then the "detail" images. Make sure that your background image covers around 35% of the screen, so that when you render the 70% ground layer there is some overlap - preventing a hole where the two layers meet.
Next create a textured image for the ground. For this I would use a static image that has the correct type of texture for what you are trying to represent (such as dirt). It may also be good to add some basic detail to the top of this image (such as mountains, trees, etc).
This should be rendered after the background layer.
Once you have this layout in place, the next step would be to simulate the depth of your world. For this you would need to create objects (2D images) that would be placed in your "world". Some examples would be trees, rocks, houses, etc.
To define your world you would need to store 2 coordinates for each object - a position on the x-axis as well as a depth value on the z-axis (you could also use a y-axis component to include height, but I will omit that for this example).
You will also need to track your player's position on the same x and z axis. These values will change in realtime as the player moves into the screen - z will change based on speed, and x will change based on steering (for example).
Also define a view distance - the number of units away from the player at which objects will be visible.
Now once you have your world set up this way, the rendering is what will give the illusion of moving into the screen. First render your player object at the bottom of the ground layer. Next, for each world object, calculate it's distance to the player - if it's distance is within the view distance you defined then it should be rendered, otherwise it can be ignored.
Once you find an object that should be rendered, you need to scale it based on it's distance from the player. The formula for this scaling would be something like:
distance_from_player_z = object.z - player.z
scale = ( view_distance - distance_from_player_z ) / view_distance
This will result in a float value between 0.0 and 1.0, which can be used to scale your object's size. Using this, the larger the distance from the player, the smaller the object becomes.
Next you need to calculate the position on the x-axis and y-axis to render your object. This can be achieved with the simple 3D projection formulas:
distance_from_player_x = object.x - player.x
x_render = player.x + ( distance_from_player_x / distance_from_player_z )
y_render = ( distance_from_player_z / view_distance ) * ( height_of_background_img );
This calculates the distance of the object relative to the player on the x-axis only. It then takes this value and "projects" it, based on the distance it is away from the player on the z-axis. The result is that the farther away the object on the z-axis, the closer it is to the player on the x-axis. The y-axis part uses the distance away from the player to place the object "higher" on the background image.
So with all this information, here is a (very basic) example in code (for a single object):
// define the render size of background (resolution specific)
public final static float RENDER_SIZE_Y = 720.0f * 0.7f; // 70% of 720p
// define your view distance (in world units)
public final static float VIEW_DISTANCE = 10.0f;
// calculate the distance between the object and the player (x + z axis)
float distanceX = object.x - player.x;
float distanceZ = object.z - player.z;
// check if object is visible - i.e. within view distance and in front of player
if ( distanceZ > 0 && distanceZ <= VIEW_DISTANCE ) {
// object is in view, render it
float scale = ( VIEW_DISTANCE - distanceZ ) / VIEW_DISTANCE;
float renderSize = ( object.size * scale );
// calculate the projected x,y values to render at
float renderX = player.x + ( distanceX / distanceZ );
float renderY = ( distanceZ / VIEW_DISTANCE ) * RENDER_SIZE_Y;
// now render the object scaled to "renderSize" at (renderX, renderY)
}
Note that if distance is smaller than or equal to zero, it means that the object is behind the player, and also not visible. This is important as distanceZ==0 will cause an error, so be sure to exclude it. You may also need to tweak the renderX value, depending on resolution, but I will leave that up to you.
While this is not at all a complete implementation, it should get you going in the right direction.
I hope this makes sense to you, and if not, feel free to ask :)
Well, you can use libgdx (http://libgdx.badlogicgames.com/).
The superjumper example will put you in the right way :) (https://github.com/libgdx/libgdx/tree/master/demos/superjumper)
I am learning how to make live wallpapers, but I have a dilemma I'm sure all who start off have as well.
There is so many resolution screen sizes, how can I just make one set of artwork to be rescaled in code for all versions? I know it's been done as I seen the images in the apk's on a lot of them and they get rescaled.
If it was just one image that did not need any positioning that would be easy, but my problem is I have to get the background image rescaled to fit all devices, I also have animations that fit in a certain x and y position on that background image to fit in place so it looks like the whole background is being animated but only parts of it is (my way of staying away from 300 images of frame by frame live wallpapers).
So the background image needs to be rescaled and the animations need to be rescaled as well to the exact percentage as the background image and they need to sit in a specific x and y position.
Any help would be appreciated so I can get this going.
I tired a few things, figured I would make a scaler for everything example: int scaler; then in onSurfaceChanged scaler = width /1024; //if the bigger image is 1024. that will give me a ratio to work with everywhere. then scale accordingly using scaleBitmap by multiplying the scaler by the image height and width, and also use the same scaler for positioning example image x lets say is at 50, scale it using the same thing x = scaler * 50; that should take care of scaling and positioning, just how to translate all this into java is the next lesson, since I'm new to java, I used to program for flash and php but this is a lot different, take some getting used to. Next thing is how to pan the width, when you move your screen from side to side how to make the image show is the next puzzle I have figure out. Right now it just shows the same width no matter what even though the width is double what the surface shows. If you got an answer or somewhere I can find out the info on this one that would be greatly appreciated.
Well, um, all I can say is "Welcome to the real world." You get your screen dimensions passed to you via onSurfaceChanged, and yes, it is your job to figure out how to scale everything based on this data. That's why they pay us the big bucks. :-)
You will want to make sure your resources are large enough to fit the biggest display you intend to support, so you will always be shrinking things (which distorts much less than expanding things).
Suggest starting with "best practices for screen independence" here: http://developer.android.com/guide/practices/screens_support.html
Additional comments in re your request for more help...
You cannot (necessarily) scale your artwork just using the width, because you need to support multiple aspect ratios. If the screen proportions do not match your artwork, you must decide if you want to distort your artwork, leave blank spaces, etc.
I'm not sure how to interpret your trouble passing around the screen dimensions. Most of us put all of our active code within a single engine class, so our methods can share data via private variables. For example, in the Cube wallpaper in the SDK, onSurfaceChanged() sets mCenterX for later use in drawCube(). I suggest beginning with a similar, simple approach.
Handling scrolling takes some "intelligence" and a careful assessment of the data you receive via onOffsetsChanged(). xStep indicates how many screens your launcher supports. Normally xStep will be 0.25, indicating 5 screens (i.e. xOffset = 0, 0.25, 0.5, 0.75, or 1) but it can be any value from 0 to 1; 0.5 would indicate 3 screens. xPixels gives you an indication of how much the launcher "wants" you to shift your imagery based on the screen you're on; normally you should respect this. On my phone, the launcher "desires" a virtual wallpaper with twice the pixels of the physical screen, so each scroll is supposed to shift things only one quarter of one screen's pixels. All this, and more, is documented in http://developer.android.com/reference/android/app/WallpaperManager.html
This is not "easy" coding--apps are easier than wallpaper. :-)
Good luck...George
P.S. I'll throw in one more thing: somewhere along the line you might want to retrieve the "desired minimum width" of the wallpaper desired by the launcher, so you can explicitly understand the virtualization implicit in xPixels. For example, in my engine constructor, I have
mContext = getApplicationContext();
mWM = WallpaperManager.getInstance(mContext);
mDW = mWM.getDesiredMinimumWidth();
My device has 320 pixel width; I get mDW = 640; as I scroll from screen to screen, xPixels changes by 80 each time...because four scrolls (across five screens) is supposed to double the amount of revealed artwork (this effect is called "parallax scrolling"). The rightmost section has xPixels equals 0; the center (of five) sections has xPixels = -160, etc.
I've used this code snippet to scale one image to fit on different screen sizes.
Bitmap image1, pic1;
image1 = BitmapFactory.decodeResource(getResources(), R.drawable.image1);
float xScale = (float) canvas.getWidth() / image1.getWidth();
float yScale = (float) canvas.getHeight() / image1.getHeight();
float scale = Math.max(xScale, yScale); //selects the larger size to grow the images by
//scale = (float) (scale*1.1); //this allows for ensuring the image covers the whole screen.
scaledWidth = scale * image1.getWidth();
scaledHeight = scale * image1.getHeight();
pic1 = Bitmap.createScaledBitmap(image1, (int)scaledWidth, (int)scaledHeight, true);
Make sure that the edges don't contain vital information as it will be scaled out of the picture on some screen ratios.
I am learning how to make live wallpapers, but I have a dilemma I'm sure all who start off have as well.
There is so many resolution screen sizes, how can I just make one set of artwork to be rescaled in code for all versions? I know it's been done as I seen the images in the apk's on a lot of them and they get rescaled.
If it was just one image that did not need any positioning that would be easy, but my problem is I have to get the background image rescaled to fit all devices, I also have animations that fit in a certain x and y position on that background image to fit in place so it looks like the whole background is being animated but only parts of it is (my way of staying away from 300 images of frame by frame live wallpapers).
So the background image needs to be rescaled and the animations need to be rescaled as well to the exact percentage as the background image and they need to sit in a specific x and y position.
Any help would be appreciated so I can get this going.
I tired a few things, figured I would make a scaler for everything example: int scaler; then in onSurfaceChanged scaler = width /1024; //if the bigger image is 1024. that will give me a ratio to work with everywhere. then scale accordingly using scaleBitmap by multiplying the scaler by the image height and width, and also use the same scaler for positioning example image x lets say is at 50, scale it using the same thing x = scaler * 50; that should take care of scaling and positioning, just how to translate all this into java is the next lesson, since I'm new to java, I used to program for flash and php but this is a lot different, take some getting used to. Next thing is how to pan the width, when you move your screen from side to side how to make the image show is the next puzzle I have figure out. Right now it just shows the same width no matter what even though the width is double what the surface shows. If you got an answer or somewhere I can find out the info on this one that would be greatly appreciated.
Well, um, all I can say is "Welcome to the real world." You get your screen dimensions passed to you via onSurfaceChanged, and yes, it is your job to figure out how to scale everything based on this data. That's why they pay us the big bucks. :-)
You will want to make sure your resources are large enough to fit the biggest display you intend to support, so you will always be shrinking things (which distorts much less than expanding things).
Suggest starting with "best practices for screen independence" here: http://developer.android.com/guide/practices/screens_support.html
Additional comments in re your request for more help...
You cannot (necessarily) scale your artwork just using the width, because you need to support multiple aspect ratios. If the screen proportions do not match your artwork, you must decide if you want to distort your artwork, leave blank spaces, etc.
I'm not sure how to interpret your trouble passing around the screen dimensions. Most of us put all of our active code within a single engine class, so our methods can share data via private variables. For example, in the Cube wallpaper in the SDK, onSurfaceChanged() sets mCenterX for later use in drawCube(). I suggest beginning with a similar, simple approach.
Handling scrolling takes some "intelligence" and a careful assessment of the data you receive via onOffsetsChanged(). xStep indicates how many screens your launcher supports. Normally xStep will be 0.25, indicating 5 screens (i.e. xOffset = 0, 0.25, 0.5, 0.75, or 1) but it can be any value from 0 to 1; 0.5 would indicate 3 screens. xPixels gives you an indication of how much the launcher "wants" you to shift your imagery based on the screen you're on; normally you should respect this. On my phone, the launcher "desires" a virtual wallpaper with twice the pixels of the physical screen, so each scroll is supposed to shift things only one quarter of one screen's pixels. All this, and more, is documented in http://developer.android.com/reference/android/app/WallpaperManager.html
This is not "easy" coding--apps are easier than wallpaper. :-)
Good luck...George
P.S. I'll throw in one more thing: somewhere along the line you might want to retrieve the "desired minimum width" of the wallpaper desired by the launcher, so you can explicitly understand the virtualization implicit in xPixels. For example, in my engine constructor, I have
mContext = getApplicationContext();
mWM = WallpaperManager.getInstance(mContext);
mDW = mWM.getDesiredMinimumWidth();
My device has 320 pixel width; I get mDW = 640; as I scroll from screen to screen, xPixels changes by 80 each time...because four scrolls (across five screens) is supposed to double the amount of revealed artwork (this effect is called "parallax scrolling"). The rightmost section has xPixels equals 0; the center (of five) sections has xPixels = -160, etc.
I've used this code snippet to scale one image to fit on different screen sizes.
Bitmap image1, pic1;
image1 = BitmapFactory.decodeResource(getResources(), R.drawable.image1);
float xScale = (float) canvas.getWidth() / image1.getWidth();
float yScale = (float) canvas.getHeight() / image1.getHeight();
float scale = Math.max(xScale, yScale); //selects the larger size to grow the images by
//scale = (float) (scale*1.1); //this allows for ensuring the image covers the whole screen.
scaledWidth = scale * image1.getWidth();
scaledHeight = scale * image1.getHeight();
pic1 = Bitmap.createScaledBitmap(image1, (int)scaledWidth, (int)scaledHeight, true);
Make sure that the edges don't contain vital information as it will be scaled out of the picture on some screen ratios.