LibGDX stage coordinates change on window resize - resize

I have seen lots of topics on LibGDX and screen/cam coordinates and also some on window resizing, but I just can't find the solution to the following problem I have.
When making a basic stage and a basic actor in this stage, say windowsize 480x320, everything is OK. I can click my actor and it will respond. But when I resize my window, say to 600x320, everything looks right, but my clicklistener is not working anymore. Also, the stage coordinates are moved or messed up.
I use the following code:
stage.addListener(new InputListener() {
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
//determine if actor was hit
System.out.println(stage.hit(x, y, true));
return true;
}
});
Also, I am resizing my stage camera viewport to correspond to the window:
stage.getCamera().viewportWidth = Gdx.graphics.getWidth();
stage.getCamera().viewportHeight = Gdx.graphics.getHeight();
So when resizing, I get the desired effect on screen, but my listener does not respond - the actor seems 'offset' of where I am clicking. What am I doing wrong? Should I move my actor or my cam, or zoom my cam according to the resize? Can someone please explain this to me?
Thanks a lot in advance!
EDIT: below is the complete code of my class.
public class HelpMePlease implements ApplicationListener{
// A standard simple Actor Class
class CustomActor extends Actor {
Texture texture = new Texture(Gdx.files.internal("data/testTex2.png"));
TextureRegion pixelTexture = new TextureRegion(texture, 0, 0, 1, 1);
Sprite sprite = new Sprite(texture);
public CustomActor() {
setWidth(128);
setHeight(128);
setBounds(getX(), getY(), getWidth(), getHeight());
}
#Override
public void draw(SpriteBatch batch, float parentAlpha) {
batch.draw(sprite, getX(), getY(), 0f, 0f, getWidth(), getHeight(), getScaleX(), getScaleY(), getRotation());
}
}
public Stage stage;
public CustomActor actor;
#Override
public void create() {
stage = new Stage(480,320,true);
actor = new CustomActor();
stage.addListener(new InputListener() {
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
//determine if actor was hit
System.out.println(stage.hit(x, y, true));
return true;
}
});
Gdx.input.setInputProcessor(stage);
stage.addActor(actor);
}
#Override
public void resize(int width, int height) {
//resize cam viewport
stage.getCamera().viewportWidth = Gdx.graphics.getWidth();
stage.getCamera().viewportHeight = Gdx.graphics.getHeight();
}
#Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
stage.getCamera().update(); //just to be sure, I don't know if this is necessary
stage.act();
stage.draw();
}
#Override
public void pause() {
// TODO Auto-generated method stub
}
#Override
public void resume() {
// TODO Auto-generated method stub
}
#Override
public void dispose() {
// TODO Auto-generated method stub
}
}

You can change your resize por this using the latest nightly.
#Override
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
}
the last parameter "true" will center the camera in the screen

I think your actor is positioning itself good enough, but your display may be a bit off.
Try
batch.draw(sprite, getX(), getY(), 0f, 0f, getWidth(), getHeight(), getScaleX(), getScaleY(), getRotation());
Instead of
batch.draw(sprite, getX(), getY(), getWidth(), getHeight(), 0f, 0f, getScaleX(), getScaleY(), getRotation());
Spritebatch has the following arguments:
public void draw (Texture texture, float x, float y, float width, float height, int srcX, int srcY, int srcWidth,int srcHeight, boolean flipX, boolean flipY)
I guess you just mixed some arguments up by mistake, would you kindly take a look at it?

Problem solved by updating my LibGDX Version using Gradle and using the new Viewport options!
Thanks for taking the time everyone!

In my case adding
stage.getViewport().setScreenSize(width, height);
in resize() solved problem

Related

Eclipse plugin - ColumnLabelProvider display only image

So I am developing an Eclipse plug-in and using ColumnLabelProvider to provide label for the columns of my tree viewer.
However, in one of the columns, I only intend to display an image and no text. However, in the final display, Eclipse reserves blank space for the text element even if I return a null.
Is there any way to make it display only image and in the full space provided?
Here is the code snippet:
column4.setLabelProvider(new ColumnLabelProvider() {
#Override
public String getText(Object element) {
return null;
}
#Override
public Image getImage(Object element) {
/* Code to Display an image follows */
.....
}
});
ColumnLabelProvider will always leave space for the text.
You can use a class derived from OwnerDrawLabelProvider to draw the column yourself.
Something like:
public abstract class CentredImageCellLabelProvider extends OwnerDrawLabelProvider
{
protected CentredImageCellLabelProvider()
{
}
#Override
protected void measure(Event event, Object element)
{
}
#Override
protected void erase(final Event event, final Object element)
{
// Don't call super.erase() to suppress non-standard selection draw
}
#Override
protected void paint(final Event event, final Object element)
{
TableItem item = (TableItem)event.item;
Rectangle itemBounds = item.getBounds(event.index);
GC gc = event.gc;
Image image = getImage(element);
Rectangle imageBounds = image.getBounds();
int x = event.x + Math.max(0, (itemBounds.width - imageBounds.width) / 2);
int y = event.y + Math.max(0, (itemBounds.height - imageBounds.height) / 2);
gc.drawImage(image, x, y);
}
protected abstract Image getImage(Object element);
}

Efficient way to load large image sequences for animation

I have 4 image sequences (png format) that I need to use for my animations. Each sequence is about 50 frames long and each frame is about 300x300, so I have something like 30 MB of resources to load. What's the most efficient way to load them to avoid memory leaks? Should I go for xml drawable animations or there's a better way?
I don't need to display all of them on the screen at the same time. Only one animation at time will be displayed.
Maybe is a bit late :) but I hope this can help other users.
At the end I've solved it with a simple loop which loads an image, draws it and destroy it.
int frames = 50;
//LOADING-REMOVING NEEDED FRAMES
//set animation range
i = currentFrame % frames;
//frame to cache
frameList.add(BitmapsLoader(i)); //custom function to load a specified frame from res
if (frameList.size() == 3) {
//frame to draw
canvas.drawBitmap(frameList.get(1), left, top, null);
//frame to remove
frameList.get(0).recycle();
frameList.remove(0);
}
This keeps the memory allocation stable, no matters how many frames your animation is. Obviously it costs more on CPU, as we're not pre-loading all resources but we keep loading them at each frame. Despite these facts, my app is running smoothly.
Tested on a Samsung Galaxy S3 and on a Galaxy Tab 4.
Thanks a lot.
I'm facing the same problem and after seen your answer for a while I have solved it by overriding an AnimationDrawable.
So, if the problem is you that can't load all images in array because it is too big for the memory to hold it, then load the image when you need it.
My AnimationDrawable is this:
public abstract class MyAnimationDrawable extends AnimationDrawable {
private Context context;
private int current;
private int reqWidth;
private int reqHeight;
private int totalTime;
public MyAnimationDrawable(Context context, int reqWidth, int reqHeight) {
this.context = context;
this.current = 0;
//In my case size of screen to scale Drawable
this.reqWidth = reqWidth;
this.reqHeight = reqHeight;
this.totalTime = 0;
}
#Override
public void addFrame(Drawable frame, int duration) {
super.addFrame(frame, duration);
totalTime += duration;
}
#Override
public void start() {
super.start();
new Handler().postDelayed(new Runnable() {
public void run() {
onAnimationFinish();
}
}, totalTime);
}
public int getTotalTime() {
return totalTime;
}
#Override
public void draw(Canvas canvas) {
try {
//Loading image from assets, you could make it from resources
Bitmap bmp = BitmapFactory.decodeStream(context.getAssets().open("presentation/intro_000"+(current < 10 ? "0"+current : current)+".jpg"));
//Scaling image to fitCenter
Matrix m = new Matrix();
m.setRectToRect(new RectF(0, 0, bmp.getWidth(), bmp.getHeight()), new RectF(0, 0, reqWidth, reqHeight), Matrix.ScaleToFit.CENTER);
bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
//Calculating the start 'x' and 'y' to paint the Bitmap
int x = (reqWidth - bmp.getWidth()) / 2;
int y = (reqHeight - bmp.getHeight()) / 2;
//Painting Bitmap in canvas
canvas.drawBitmap(bmp, x, y, null);
//Jump to next item
current++;
} catch (IOException e) {
e.printStackTrace();
}
}
abstract void onAnimationFinish();
}
Now to play animation you need to do what next
//Get your ImageView
View image = MainActivity.this.findViewById(R.id.presentation);
//Create AnimationDrawable
final AnimationDrawable animation = new MyAnimationDrawable(this, displayMetrics.widthPixels, displayMetrics.heightPixels) {
#Override
void onAnimationFinish() {
//Do something when finish animation
}
};
animation.setOneShot(true); //dont repeat animation
//This is just to say that my AnimationDrawable has 72 frames with 50 milliseconds interval
try {
//Always load same bitmap, anyway you load the right one in draw() method in MyAnimationDrawable
Bitmap bmp = BitmapFactory.decodeStream(MainActivity.this.getAssets().open("presentation/intro_00000.jpg"));
for (int i = 0; i < 72; i++) {
animation.addFrame(new BitmapDrawable(getResources(), bmp), 50);
}
} catch (IOException e) {
e.printStackTrace();
}
//Set AnimationDrawable to ImageView
if (Build.VERSION.SDK_INT < 16) {
image.setBackgroundDrawable(animation);
} else {
image.setBackground(animation);
}
//Start animation
image.post(new Runnable() {
#Override
public void run() {
animation.start();
}
});
That is all, and works OK form me!!!

Libgdx - camera.unproject is not fixing my coordinates

The following code gives me very strange y coordinates.
10-18 00:13:36.834 30543-30567/com.xxxx.yyyy.android I/x﹕ 137.4782
10-18 00:13:36.834 30543-30567/com.xxxx.yyyy.android I/y﹕ -1984.2426
10-18 00:13:36.835 30543-30567/com.xxxx.yyyy.android I/ux﹕ 91.65213
10-18 00:13:36.835 30543-30567/com.xxxx.yyyy.android I/uy﹕ -1984.2426
I imagine I set up everything wrong rather than do it wrong while running?
The camera.unproject call should take care of all remapping from screen coordinates to game coordinates, shouldn't it? Or do i have to scale and invert before unprojecting?
package com.xxxx.yyyy;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
public class LetterActor extends Actor
{
private Texture texture;
private Vector3 touchPosition = new Vector3();
private Camera camera;
private boolean unproject = true;
public LetterActor(Texture letterTexture, Camera theCamera)
{
texture = letterTexture;
camera = theCamera;
touchPosition.set(240, 800, 0);
camera.unproject(touchPosition);
setPosition(touchPosition.x, touchPosition.y);
setSize(texture.getWidth(), texture.getHeight());
addListener(new InputListener()
{
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
{
touchPosition.set(x, y, 0);
if (unproject)
{
camera.unproject(touchPosition);
}
setPosition(touchPosition.x, touchPosition.y);
logPositions(x, y, touchPosition.x, touchPosition.y);
return true;
}
#Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button)
{
touchPosition.set(x, y, 0);
if (unproject)
{
camera.unproject(touchPosition);
}
setPosition(touchPosition.x, touchPosition.y);
logPositions(x, y, touchPosition.x, touchPosition.y);
}
#Override
public void touchDragged(InputEvent event, float x, float y, int pointer)
{
touchPosition.set(x, y, 0);
if (unproject)
{
camera.unproject(touchPosition);
}
setPosition(touchPosition.x, touchPosition.y);
logPositions(x, y, touchPosition.x, touchPosition.y);
}
});
}
private void screenTo()
{
}
private void logPositions(float x, float y,float ux, float uy)
{
Gdx.app.log("x", Float.toString(x));
Gdx.app.log("y", Float.toString(y));
Gdx.app.log("ux", Float.toString(ux));
Gdx.app.log("uy", Float.toString(y));
}
#Override
public void draw(Batch batch, float alpha)
{
batch.draw(texture, getX(), getY(), getWidth(), getHeight());
}
#Override
public void act(float delta) {}
}
package com.xxxx.yyyy;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.utils.viewport.ExtendViewport;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.utils.viewport.FitViewport;
public class WordPuzzle extends ApplicationAdapter
{
private final static float VIRTUAL_WIDTH = 480;
private final static float VIRTUAL_HEIGHT = 800;
private OrthographicCamera camera;
private FitViewport viewport;
private Stage stage;
#Override
public void create()
{
camera = new OrthographicCamera(VIRTUAL_WIDTH, VIRTUAL_HEIGHT);
camera.setToOrtho(false, VIRTUAL_WIDTH, VIRTUAL_HEIGHT);
viewport = new FitViewport(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, camera);
stage = new Stage();
stage.setViewport(viewport);
Gdx.input.setInputProcessor(stage);
Texture[] textures = LetterLoader.loadLetters();
for (int i = 0; i < textures.length; i++)
{
LetterActor letterActor = new LetterActor(textures[i], camera);
letterActor.setTouchable(Touchable.enabled);
stage.addActor(letterActor);
}
}
#Override
public void render()
{
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
#Override public void resize(int width, int height)
{
stage.getViewport().update(width, height, true);
}
#Override public void dispose()
{
stage.dispose();
}
}
package com.xxxx.yyyy;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
public class LetterLoader {
public static Texture[] loadLetters()
{
Texture[] letters = new Texture[26];
for (int i = 0; i < 26; i++)
{
char letter = (char) (i + 65);
letters[i] = new Texture(Gdx.files.internal("bigletters/" + letter + ".png"));
}
return letters;
}
}
First, the touch position (x, y) you get from the input listener are already the correct coordinates.
Concerning your output, you actually print y two times, but call it uy the second time:
Gdx.app.log("uy", Float.toString(y));
If touchPosition.set(240, 800, 0); is in screen coordinates, then you need to unproject them, but
camera.unproject(touchPosition);
assumes that your camera fills the whole screen, thus it calls internally:
unproject(screenCoords, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Since you use a virtual size, this is wrong. The most simple solution would be to use the unproject method from the viewport that you are using:
viewport.unproject(touchPosition);
This will call the camera unproject method with the correct parameters automatically.
Since you are using Stage and InputListener, the coordinates you get in touchDown and the related methods are already in world coordinates, so it doesn't make sense to unproject them. You can use the x and y directly.
Also (although this is irrelevant to InputListener), camera.unproject assumes a Viewport that fills the screen, which is not true of FitViewport, which you're using. If you are using a Viewport class, you need to use viewport.unproject instead of camera.unproject, so it takes the black bars into account.
But you only need to worry about unprojecting for stuff not related to the Stage.

Stage, viewport, camera, scaling, and Nexus 7

I am a newbies in Android and I am learning how to use libgdx as well. I found a few tutorials that use the “DisplayScreen extends AbstractScreen” coding technique, and they use Stage and Actor for displaying output. My interpretation of this coding technique is that DisplayScreen will use whatever in AbstractScreen unless it is #Override (please correct me if I am wrong).
Therefore, if I place the code in AbstractScreen resize() to scale the display to a bigger screen yet maintaining the aspect ratio; the stage in DisplayScreen should resize it to fit the bigger screen. The main objective is that I only want to concentrate my game development in 800x480 environment and completely ignore all different sizes/resolution. The resize() in AbstractScreen will do all the hard work to scale and fit my game to any resolution.
Please allow me to use my test example for better explanation. I have no problem displaying my 800x480 black background on my phone. However, the same background was displayed BUT not maintaining its aspect ratio on Nexus 7.
This turorial fixed my problem mentioned above (I adopted to have two black bars on both side of the screen). However, I have a small problem integrating this solution to the “DisplayScreen extends AbstractScreen” technique.
Please see this screen shot here. My issues are:
Why my black box is not resize to fit the screen, yet leaving two red bar on both sides of the screen?
I don’t understand why the black image only displaying 766x480 on my Nexus 7?
It will be fantastic if someone can point me to the right direction.
Code for awesomegame
package com.example.somegame;
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.assets.AssetManager;
import com.example.somegame.screens.DisplayScreen;
public class awesomegame extends Game {
public static AssetManager AssetManager = new AssetManager();
#Override
public void create() {
}
#Override
public void render() {
super.render();
}
#Override
public void resize(int width, int height) {
super.resize(width, height);
setScreen( new DisplayScreen(this) );
}
#Override
public void setScreen(Screen screen) {
super.setScreen( screen );
}
Code for AbstractScreen
package com.example.somegame.screens;
import com.example.somegame.awesomegame;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.Scaling;
public class AbstractScreen implements Screen{
private awesomegame awesomegame;
private Stage stage;
private BitmapFont font;
private SpriteBatch batch;
private OrthographicCamera camera;
public AbstractScreen(awesomegame awesomegame) {
this.awesomegame = awesomegame;
camera = new OrthographicCamera();
camera.setToOrtho(false, 800, 480);
camera.update();
stage = new Stage(800, 480, false);
}
#Override
public void render(float delta) {
stage.act( delta );
Gdx.gl.glClearColor( .5f, .5f, 0f, 1f );
Gdx.gl.glClear( GL20.GL_COLOR_BUFFER_BIT );
stage.draw();
}
#Override
public void resize(int width, int height) {
Vector2 size = Scaling.fit.apply(800, 480, width, height);
int viewportX = (int)(width - size.x) / 2;
int viewportY = (int)(height - size.y) / 2;
int viewportWidth = (int)size.x;
int viewportHeight = (int)size.y;
Gdx.gl.glViewport(viewportX, viewportY, viewportWidth, viewportHeight);
stage.setViewport(800, 480, true);
}
Code for DisplayScreen
package com.example.somegame.screens;
import com.example.somegame.awesomegame;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
public class DisplayScreen extends AbstractScreen {
private Image loadingBg;
private Stage stage;
float loadingPercent;
public DisplayScreen(awesomegame awesomegame) {
super (awesomegame);
}
#Override
public void show()
{
super.show();
awesomegame.AssetManager.load("img/loading.atlas", TextureAtlas.class);
awesomegame.AssetManager.finishLoading();
stage = new Stage();
TextureAtlas atlas = awesomegame.AssetManager.get("img/loading.atlas", TextureAtlas.class);
loadingBg = new Image(atlas.findRegion("loadingBg"));
loadingBg.setSize(800, 480);
loadingBg.setX(0);
loadingBg.setY(0);
stage.addActor(loadingBg);
// add all asset need to be loaded here. for example
// awesomegame.AssetManager.load("img/whatever.pack", TextureAtlas.class);
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor( 1f, 0f, 0f, 1f );
stage.draw();
}
The Nexus 7 has a screen resolution of 1280 x 800, but part of the height is used for the onscreen menu panel (with the back/home/menu buttons).
The main culprit in your code is here, where you try to enforce a specific aspect ratio that doesn't fit this adjusted dimension, resulting in those bars on the side:
public void resize(int width, int height) {
Vector2 size = Scaling.fit.apply(800, 480, width, height);
...
}
Looks like you pulled that resize function off another stackoverflow post. I had done the same, but switched to something simpler when i ran into the same issue:
public void resize(int width, int height) {
stage.setViewport(true, width,height);
stage.getCamera().setToOrtho(false,width,height);
...
}
This code works for me with the latest update:
OrthographicCamera is cam, this makes no cropping, just changes the viewport so the width is still "that many" times larger than the actual window/device
public void resize(int width, int height) {
int newW = width, newH = height;
if (cam.viewportWidth > width) {
float scale = (float) cam.viewportWidth / (float) width;
newW *= scale;
newH *= scale;
}
// true here to flip the Y-axis
cam.setToOrtho(true, newW, newH);
}

smartgwt position of mouse event

I'm trying to position a context menu in a Smart GWT Canvas, by using
`
addRecordClickHandler(new RecordClickHandler() {
public void onRecordClick(RecordClickEvent event) {
getContextMenu.setRect(rect)
getContextMenu().show();
}
});
`
The problem is that theres doesn't seem to be a straightforward way to get the X/Y coordinates of my mouse click event, which I can use to create the rect. I can get the AbsoluteTop & absoluteLeft of the enclosing Canvas, but that doesn't help me position the context menu window accurately.
RecordClickEvents are usually used with ListGrids. With a Canvas you can use ClickEvent, which has getX() and getY() methods.
addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
int x = event.getX();
int y = event.getY();
}
});