I have a libgdx application that contains a class Button. The constructor of Button takes three arguements: Filename of graphics, position, and game (the latter being used for callbacks of various sorts).
The button scales itself based on the graphics provided, thus setting its width and height based on the properties of the graphics.
The main class, Game, when a click is detected compares the coordinates of the click up against the coordinates of the button combined with its width and height.
Now, the main issue is that there is a little bit of a horizontal offset between the button and the click coordinates, so the effect is that the graphics show up a few pixels to the right of the clickable area. I cannot for the life of me figure out the source of this discrepancy, so I would greatly appreciate some fresh eyes to see where I'm going wrong here.
Button, constructor and polling-method for clickable area.
public Rectangle getClickArea() {
return new Rectangle(pos.x - (img.getWidth() / 2), pos.y + (img.getHeight() / 2), w, h);
}
public Button(String assetfile, int x, int y, Game game) {
this.game = game;
img = new Pixmap(new FileHandle(assetfile));
pos = new Vector2(x, y);
this.w = img.getWidth();
this.h = img.getHeight();
}
A relevant snippet from InputHandler. It listens for input and passes on the event. Please note that the vertical click position is subtracted from the vertical size of the screen, as vertical 0 is opposite in InputHandler:
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
tracker.click(screenX, Settings.windowSize_Y - screenY);
return false;
}
ClickTracker (referenced as tracker in the above snippet), the Class that does the actual comparison between clicks and clickables:
public void click(int x, int y) {
Vector2 clickPos = new Vector2(x, y);
for (Tickable c : world.getPaintables())
{
if (!(c instanceof Unit))
continue;
if (((Clickable)c).getClickArea().contains(clickPos)) {
System.out.println("Clicked on unit");
}
}
for (Clickable c : clickables)
{
if (c.getClickArea().contains(clickPos)) {
c.clicked(x, y);
}
}
In short: The vertical alignment works as intended, but the horizontal is slightly off. The button graphics appear maybe around 10-20 pixels to the right of the clickable area.
I'll gladly post more info or code if needed, but I believe I have the relevant parts covered.
Edit:
As Maciej Dziuban requested, here's the snipped that draws the UI elements. batch is a SpriteBatch as provided by libgdx:
for (Paintable p : ui) {
batch.draw(new Texture(p.getImg()), p.getImgPos().x, p.getImgPos().y);
}
the getImgPos() is an interface method implemented by all drawable items:
public Vector2 getImgPos() {
return new Vector2(pos.x - (getImg().getWidth() / 2), pos.y);
}
It's worth noting that half of the horizontal image size is subtracted from the X pos, as X pos refers to the bottom center.
You have inconsistency in your position transformations:
Your clickArea's corner is pos translated by [-width/2, height/2] vector.
Your drawArea's corner is pos translated by [-width/2, 0] vector
They clearly should be the same, so if you want your pos to represent bottom-center of your entity (as you've explicitly stated) you have to change your getClickArea() method to, so it matches getImgPos().
public Rectangle getClickArea() {
return new Rectangle(pos.x - (img.getWidth() / 2), pos.y, w, h);
}
Side note: as Tenfour04 noticed, you create new texture each frame and this is huge memory leak. You should make it a field initialized in constructor or even a static variable given some buttons share the texture. Don't forget to call dispose() on resources. For more powerful asset management check out this article (note it may be an overkill in small projects).
Related
I'm trying to zoom a grid in Processing and I am having trouble with applying the correct translation such that zooming is centered around the mouse position. I have searched the web for a while but nothing I try seems to work.
The screen size is width and height and the mouse position is mouseX and mouseY.
The code I have at the moment is below, but it zooms the grid (controlled by player.zoom) from the top left corner which is not what I want. To update the translation of the grid, player has the 2d vector player.translate.
void mouseWheel(MouseEvent event) {
float zoomFactor = 200.0f;
float scroll = event.getCount();
player.zoom -= scroll * player.zoom / zoomFactor;
// player.translate.x += something;
// player.translate.y += something;
}
If you need more details to answer I can link the repo with the source code.
I have created a very simple mock-up for you which will hopefully point you in the right direction into applying this to your player.
So this little demo shows the zooming in to the centre of an ellipse whilst keeping it as the central focus.
float scale = 1;
// displacement left/right
float xPan = 720;
// displacement up/down
float yPan = 450;
boolean zoomIn = true;
void setup() {
size(1440, 900);
}
void draw() {
// allows us to zoom into the center of the screen rather than the corner
translate(width/2, height/2);
scale(scale);
translate(-xPan, -yPan);
background(200);
// draws the ellipse in the center
ellipse(width/2, height/2, 100, 100);
// does the zooming
if (zoomIn) {
scale *= 1.01;
}
}
I suggest you to copy this into a new project and then comment out specific lines to try to understand what's going on and how you can transfer this over to your own project.
The same principles still apply, I didn't do this with mouse input in the effort of making the program as simple as possible.
I am creating a simple 2D game for my computer class. I already have a box that moves around through a level. But, i want to change this box to instead display a stickman standing that I drew. Then, more specifically, i would like in my MovementInput class (where i assign movements to the buttons), I want when neither A(my moving left button) or D(my moving right button) OR when both A and D are held down to display this standing.png image. How would i go about doing this?!
This is my code for drawing the box
public class Man extends AbstractMoveableEntity {
public Man(double x, double y, double width, double height) {
super(x, y, width, height);
}
#Override
public void draw() {
glColor3d(0, 0, 255);
glRectd(x - width / 2, y, x + width / 2, y + height);
}
}
And in my MovementInput class, this is my code for the A&D thing
if ((Keyboard.isKeyDown(Keyboard.KEY_D) &&Keyboard.isKeyDown(Keyboard.KEY_A))||(!Keyboard.isKeyDown(Keyboard.KEY_D) && !Keyboard.isKeyDown(Keyboard.KEY_A))) {
man.setDX(0);
}
Use something called Texture Atlases for multiple frames of animation and a simple timer to loop through the frames. ThinMatrix on YouTube has a really good tutorial for this here as well as many other concepts.
as I understood, libGDX coordinate system set by default, so the (0,0) is located at the bottom left corner, like in the following picture:
(source: gyazo.com)
Is it possible to make it work like JFrame, where the default position is at the top left corner?
Anyhow, back to my actual question, android (OR at least my phone) touch coordinate system is working by full screen (when phone is laying on it's side) so the default (0, 0) position is located at the top right corner, like in the following picture:
(source: gyazo.com)
So what happens when I simply do this:
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
System.out.println("X: " + screenX + " Y: " + screenY);
this.instance.player.setLocation(screenX, screenY);
return false;
}
I simply get the X, Y of touched screen (based on android's fullscreen coordinate system) and then use it on libgdx coordinate system which is totally different,
so if I touched at the top right corner, my blue rectangle will appear at bottom left corner.
Is there a way to make everything work by the top left corner like on PC?
You'll need to use a camera to do this. If you're not using a camera, I'd highly recommend adding one in, as it makes things a lot easier. Simply call camera.setToOrtho(true), which will switch LibGDX's coordinate system to the one you want, (0,0) in the top left.
To do this with a certain viewport width and height other than what you get from Gdx.graphics.getWidth/Height(), you can call camera.setToOrtho(true, viewportWidth, viewportHeight).
I don't know how to set it at top left corner but one thing you could is to convert input coordinate to libgdx coordinate.
I hope that could help you or help somebody else :
int Screen_height = Gdx.graphics.getHeight(); // Your screen height
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
System.out.println("X: " + screenX + " Y: " + (Screen_height - screenY));
this.instance.player.setLocation(screenX, (Screen_height - screenY));
return false;
}
Yes, and it's very easy.
You just have to subtract from the Y value the height of the screen.
So:
The finger position Y = Heigh of the screen - the Y value you have found.
There's an easier way:
Just make the subtraction bellow
Gdx.graphics.getHeight()-screenY
Only this line of code is necessary, as you can see.
Create the variable fingerY and use the subtraction:
fingerY= Gdx.graphics.getHeight()-screenY;
So, if screenY equals 10 from the left bottom corner and the height of the screen equals 240 pixels:
fingerY=240-10
Then fingerY=230 pixels from the bottom left corner, while it is 10 pixels from the top left corner.
You need to use the method project(Vector3 worldCoords) in class com.badlogic.gdx.graphics.Camera.
private Camera camera;
............
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
Create an instance of the vector and initialize it with the coordinates of the input event handler.
Vector3 worldCoors = new Vector3(screenX, screenY, 0);
Projects the worldCoors given in world space to screen coordinates.
camera.project(worldCoors);
Use projected coordinates.
world.hitPoint((int) worldCoors.x, (int) worldCoors.y);
OnTouch();
return true;
}
I'm working at a android game using LIBGDX.
#Override
public boolean touchDown(int x, int y, int pointer, int button) {
// TODO Auto-generated method stub
return false;
}
Here, the x and y returns the position of the touch of the device screen, and the values are between 0 and the device screen width and height.
My game resolution is 800x480, and it will keep its aspect ratio on every device.
I want to find out a way to get the touch position, related to the game rectangle, this image can explain exactly:
Is there a way to do it?
I want to get the touch position related to my viewport..
I use this to keep the aspect ratio
http://www.java-gaming.org/index.php?topic=25685.0
Unproject your touch.
Make a Vector3 object for user touch:
Vector3 touch = new Vector3();
And use the camera to convert the screen touch coordinates, to camera coordinates:
#Override
public boolean touchDown(int x, int y, int pointer, int button){
camera.unproject(touch.set(x, y, 0)); //<---
//use touch.x and touch.y as your new touch point
return false;
}
In the newer version of LibGDX you can achieve it with the built in viewports.
Firstly choose your preferred viewport the one you want here is FitViewport.
You can read about them here:
https://github.com/libgdx/libgdx/wiki/Viewports
Next, you declare and initialize the viewport and pass your resolution and camera:
viewport = new FitViewport(800, 480, cam);
Then edit your "resize" method of the screen class to be like that:
#Override
public void resize(int width, int height) {
viewport.update(width, height);
}
Now wherever you want to get touch points you need to transfer them to new points according to the new resolution. Fortunately, the viewport class does it automatically.
Just write this:
Vector2 newPoints = new Vector2(x,y);
newPoints = game.mmScreen.viewport.unproject(newPoints);
Where x and y are the touch points on the screen and in the second line "newPoints" gets the transformed coordinates. Now you can pass them wherever you want.
After 1-2 paintful hours I finnaly found the solution..
if (Gdx.input.isTouched()) {
float x = Gdx.input.getX();
float y = Gdx.input.getY();
float yR = viewport.height / (y - viewport.y); // the y ratio
y = 480 / yR;
float xR = viewport.width / (x - viewport.x); // the x ratio
x = 800 / xR;
bubbles.add(new Bubble(x, 480 - y));
}
Edit: this is an old deprecared way to do it, so don't.
I have a screen (BaseScreen implements the Screen interface) that renders a PNG image. On click of the screen, it moves the character to the position touched (for testing purposes).
public class DrawingSpriteScreen extends BaseScreen {
private Texture _sourceTexture = null;
float x = 0, y = 0;
#Override
public void create() {
_sourceTexture = new Texture(Gdx.files.internal("data/character.png"));
}
.
.
}
During rendering of the screen, if the user touched the screen, I grab the coordinates of the touch, and then use these to render the character image.
#Override
public void render(float delta) {
if (Gdx.input.justTouched()) {
x = Gdx.input.getX();
y = Gdx.input.getY();
}
super.getGame().batch.draw(_sourceTexture, x, y);
}
The issue is the coordinates for drawing the image start from the bottom left position (as noted in the LibGDX Wiki) and the coordinates for the touch input starts from the upper left corner. So the issue I'm having is that I click on the bottom right, it moves the image to the top right. My coordinates may be X 675 Y 13, which on touch would be near the top of the screen. But the character shows at the bottom, since the coordinates start from the bottom left.
Why is what? Why are the coordinate systems reversed? Am I using the wrong objects to determine this?
To detect collision I use camera.unproject(vector3). I set vector3 as:
x = Gdx.input.getX();
y = Gdx.input.getY();
z=0;
Now I pass this vector in camera.unproject(vector3). Use x and y of this vector to draw your character.
You're doing it right. Libgdx generally provides coordinate systems in their "native" format (in this case the native touch screen coordinates, and the default OpenGL coordinates). This doesn't create any consistency but it does mean the library doesn't have to get in between you and everything else. Most OpenGL games use a camera that maps relatively arbitrary "world" coordinates onto the screen, so the world/game coordinates are often very different from screen coordinates (so consistency is impossible). See Changing the Coordinate System in LibGDX (Java)
There are two ways you can work around this. One is transform your touch coordinates. The other is to use a different camera (a different projection).
To fix the touch coordinates, just subtract the y from the screen height. That's a bit of a hack. More generally you want to "unproject" from the screen into the world (see the
Camera.unproject() variations). This is probably the easiest.
Alternatively, to fix the camera see "Changing the Coordinate System in LibGDX (Java)", or this post on the libgdx forum. Basically you define a custom camera, and then set the SpriteBatch to use that instead of the default.:
// Create a full-screen camera:
camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
// Set it to an orthographic projection with "y down" (the first boolean parameter)
camera.setToOrtho(true, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
camera.update();
// Create a full screen sprite renderer and use the above camera
batch = new SpriteBatch(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
batch.setProjectionMatrix(camera.combined);
While fixing the camera works, it is "swimming upstream" a bit. You'll run into other renderers (ShapeRenderer, the font renderers, etc) that will also default to the "wrong" camera and need to be fixed up.
I had same problem , i simply did this.
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
screenY = (int) (gheight - screenY);
return true;
}
and every time you want to take input from user dont use Gdx.input.getY();
instead use (Gdx.graphics.getHeight()-Gdx.input.getY())
that worked for me.
The link below discusses this problem.
Projects the given coords in world space to screen coordinates.
You need to use the method project(Vector3 worldCoords) in class com.badlogic.gdx.graphics.Camera.
private Camera camera;
............
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
Create an instance of the vector and initialize it with the coordinates of the input event handler.
Vector3 worldCoors = new Vector3(screenX, screenY, 0);
Projects the worldCoors given in world space to screen coordinates.
camera.project(worldCoors);
Use projected coordinates.
world.hitPoint((int) worldCoors.x, (int) worldCoors.y);
OnTouch();
return true;
}