Libgdx - camera.unproject is not fixing my coordinates - camera

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.

Related

Implementing a keylistener in one class to work with a jframe created in another class

End goal: use a KeyListener to create random shapes (with random attributes). A new random shape will display when its corresponding key is pressed (Ex. 'c' for circle). I must use 2 classes to implement this. ShapeDriver deals with the creation and painting of the shapes (KeyListener interface is implemented here). ShapeWindow creates a ShapeDriver object (which is a JPanel) that then gets added to the JFrame.
The creation and painting of the shapes is working correctly, the only issue is adding the KeyListener.
import java.util.List;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.Dimension;
public class ShapeDriver extends JPanel implements KeyListener {
public final int FRAME_WIDTH = 600;
public final int FRAME_HEIGHT = 600;
private Random random;
private ArrayList<Shape> shapes;
public JPanel panel;
public ShapeDriver() {
super();
random = new Random();
shapes = new ArrayList<Shape>();
panel = new JPanel();
panel.setSize(FRAME_WIDTH, FRAME_HEIGHT);
addKeyListener(this); //my best attempt at adding the KeyListener (which
doesn't work
this.add(panel);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Color fillColor = new Color(random.nextInt(255),
random.nextInt(255), random.nextInt(255));
Color borderColor = new Color(random.nextInt(255),
random.nextInt(255), random.nextInt(255));
int x = random.nextInt(255);
int y = random.nextInt(255);
shapes.add(new Hexagon(fillColor, borderColor, x,y));
for(Shape s : shapes)
{
System.out.println(s.toString());
s.draw(g);
}
}
public void keyPressed(KeyEvent e) {
System.out.println("keyPressed");
Color fillColor = new Color(random.nextInt(255), random.nextInt(255),
random.nextInt(255));
Color borderColor = new Color(random.nextInt(255), random.nextInt(255),
random.nextInt(255));
int x = random.nextInt(255);
int y = random.nextInt(255);
if(e.getKeyChar() == 'c')
{
shapes.add(new Circle(fillColor, borderColor, x, y));
}
else if(e.getKeyChar() == 'r')
{
shapes.add(new Rectangle(fillColor, borderColor, x, y));
}
else if(e.getKeyChar() == 'o')
{
shapes.add(new Oval(fillColor, borderColor, x, y));
}
else if(e.getKeyChar() == 's')
{
shapes.add(new Square(fillColor, borderColor, x, y));
}
else if(e.getKeyChar() == 't')
{
shapes.add(new Triangle(fillColor, borderColor, x, y));
}
else if(e.getKeyChar() == 'p')
{
shapes.add(new Parallelogram(fillColor, borderColor, x, y));
}
else if(e.getKeyChar() == 'h')
{
shapes.add(new Hexagon(fillColor, borderColor, x, y));
}
repaint();
}
#Override
public void keyReleased(KeyEvent e) { }
#Override
public void keyTyped(KeyEvent e) { }
}
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Color;
public class ShapeWindow extends JFrame {
JPanel shapeDriver;
public ShapeWindow() {
super();
JFrame frame = new JFrame();
final int FRAME_WIDTH = 400;
final int FRAME_HEIGHT = 400;
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setTitle("ShapeGenerator");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
shapeDriver = new ShapeDriver();
frame.add(shapeDriver);
frame.setVisible(true);
}
public static void main(String[] args) {
JFrame shapeWindow = new ShapeWindow();
}
}
The JFrame is created correctly, the uncommented shape in ShapeDriver displays correctly, and the frame repaints properly when I resize it (in other words the KeyListener is the only thing that doesn't work).
Any assistance is much appreciated. Also, I'm new to this so if I formatted something horribly wrong let me know, Thanks!
Add the below in the ShapeDriver constructor after addKeyListener() -
this.setFocusable(true);
this.requestFocusInWindow();
Refer this.

Unable to identify where a NullPointerException is coming from

so I am getting the following error:
Exception in thread "Thread-0" java.lang.NullPointerException
at dev.tamir.firstgame.entities.creatures.Player.getInput(Player.java:19)
at dev.tamir.firstgame.entities.creatures.Player.tick(Player.java:31)
at dev.tamir.firstgame.states.GameState.tick(GameState.java:25)
at dev.tamir.firstgame.Game.tick(Game.java:65)
at dev.tamir.firstgame.Game.run(Game.java:110)
at java.lang.Thread.run(Unknown Source)
And I've checked all the lines Java had marked me and I can not find what is producing the null.
Player:
package dev.tamir.firstgame.entities.creatures;
import java.awt.Graphics;
import dev.tamir.firstgame.Game;
import dev.tamir.firstgame.gfx.Assets;
public class Player extends Creature {
private Game game;
public Player(Game game, float x, float y) {
super(game, x, y, Creature.DEFAULT_CREATURE_WIDTH, Creature.DEFAULT_CREATURE_HEIGHT);
}
#Override
public void tick() {
getInput();
move();
game.getGameCamera().centerOnEntity(this);
}
private void getInput() {
xMove = 0;
yMove = 0;
if(game.getKeyManager().up)
yMove = -speed;
if(game.getKeyManager().down)
yMove = speed;
if(game.getKeyManager().left)
xMove = -speed;
if(game.getKeyManager().right)
xMove = speed;
}
#Override
public void render(Graphics g) {
g.drawImage(Assets.robro[7], (int) (x - game.getGameCamera().getxOffset()), (int) (y - game.getGameCamera().getyOffset()), width, height, null);
}
}
Gamestate:
package dev.tamir.firstgame.states;
import java.awt.Graphics;
import dev.tamir.firstgame.Game;
import dev.tamir.firstgame.entities.creatures.Player;
import dev.tamir.firstgame.tiles.Tile;
import dev.tamir.firstgame.worlds.World;
public class GameState extends State {
private Player player;
private World world;
public GameState(Game game) {
super(game);
player = new Player(game, 0, 0);
world = new World(game, "res/worlds/world1.txt");
}
#Override
public void tick() {
world.tick();
player.tick();
}
#Override
public void render(Graphics g) {
world.render(g);
player.render(g);
}
}
Game:
package dev.tamir.firstgame;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import dev.tamir.firstgame.Display.Display;
import dev.tamir.firstgame.gfx.Assets;
import dev.tamir.firstgame.gfx.GameCamera;
import dev.tamir.firstgame.input.KeyManager;
import dev.tamir.firstgame.states.GameState;
import dev.tamir.firstgame.states.MenuState;
import dev.tamir.firstgame.states.State;
public class Game implements Runnable {
private Display display;
private Thread thread;
private BufferStrategy bs;
private Graphics g;
//States
private State gameState;
private State menuState;
//Input
private KeyManager keyManager;
//Camera
private GameCamera gameCamera;
private boolean running = false;
private int width, height;
public String title;
public Game(String title, int width, int height) {
this.width = width;
this.height = height;
this.title = title;
keyManager = new KeyManager();
}
private void init() {
display = new Display(title, width, height);
display.getFrame().addKeyListener(keyManager);
Assets.init();
gameCamera = new GameCamera(this, 0,0);
gameState = new GameState(this);
menuState = new MenuState(this);
State.setState(gameState);
}
private void tick() {
keyManager.tick();
if(State.getState() != null)
State.getState().tick();
}
private void render() {
bs = display.getCanvas().getBufferStrategy();
if(bs == null) {
display.getCanvas().createBufferStrategy(3);
return;
}
g = bs.getDrawGraphics();
//Clear
g.clearRect(0, 0, width, height);
//Draw
if(State.getState() != null)
State.getState().render(g);
//End of Draw
bs.show();
g.dispose();
}
public void run() {
init();
int fps = 60;
double timePerTick = 1000000000 / fps;
double delta = 0;
long now;
long lastTime = System.nanoTime();
long timer = 0;
int ticks = 0;
while (running) {
now = System.nanoTime();
delta += (now - lastTime) / timePerTick;
timer += now - lastTime;
lastTime = now;
if(delta >= 1) {
tick();
render();
ticks++;
delta--;
}
if(timer >= 1000000000) {
System.out.println("FPS: " + ticks );
ticks = 0;
timer = 0;
}
}
stop();
}
public KeyManager getKeyManager() {
return keyManager;
}
public GameCamera getGameCamera() {
return gameCamera;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public synchronized void start() {
if (running)
return;
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop() {
if (!running)
return;
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
help would be very appreciated, as I've been looking for hours now and I don't know what is causing the null and I am suspecting the error log may even be misleading me.
My guess would be you have a private Game in your creature class. When you call super() in Player, you pass a Game object to Creature. The Creature constructor most likely has a line like this.game = game;
Of course, I can't say for sure because the Creature class is not included in your post, but that's the most likely code setup. Because game would then be private to Creature, Player cannot see it. That means the private Game game that you declare in Player is never set.
After you call super, do this.game = game;
This will almost certainly take care of your issue.
Just for future reference, the message you got when the error resulted is the call stack; basically it tells you what methods called what, the most recent being at the top. The error took place at line 19 in Player.getInput(), which was called by tick() in that same class.
The only object you use in getInput() is game, and so that must be the source of the null pointer. From there, it's a quick check to see that game is a private field of Player, and since it is null that's a huge clue that it was never initialized (though that is not always the case). Private fields most often are initialized in the class constuctor (but they don't have to be... your Player class is pretty sparse so it wouldn't take that long to look through all of it if you absolutly had to. Looking at the Player constructor, we see a Game object named game is passed in, which suggests that it was intended to be used to initialize game and yet never is. Voilà, we found the problem!
I'm sorry if that last paragraph felt a little condecending; it wasn't meant to be. I just wanted to walk you through how I found your issue. Hopefully knowing how I found it will help you find later errors on your own.

How to draw several lines slowly in constant velocity on canvas by Android?

I need capture the mark to draw a figure on canvas in Android, and the effect just like the follow gif:
Well, as far, I can draw a side with constant velocity by ValueAnimator. However, I just only can draw one side at one time, because I can't save the last side when drawing the next side. So, is there a good way to solve the problem?
Code for draw a line slowly by ValueAnimator:
GraphicsView.java
public class GraphicsView extends View {
private int stepX, stepY = 0;
private int startX, startY, stopX, stopY = 0;
private Paint paint = null;
public GraphicsView(Context context) {
super(context);
// Paint
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
startX = 40;
startY = 397;
stopX = 1040;
stopY = 397;
Init();
}
public void Init(){
ValueAnimator animatorX = ValueAnimator.ofFloat(startX, stopX);
ValueAnimator animatorY = ValueAnimator.ofFloat(startY, stopY);
animatorX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
stepX = Math.round((Float)valueAnimator.getAnimatedValue()); invalidate();
}
});
animatorY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
stepY = Math.round((Float)valueAnimator.getAnimatedValue()); invalidate();
}
});
AnimatorSet set = new AnimatorSet();
LinearInterpolator l = new LinearInterpolator();
set.setInterpolator(l);
set.setDuration(3000);
set.playTogether(animatorX, animatorY);
set.start();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawLine(startX, startY, stepX, stepY, paint);
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
private Display display = null;
private GraphicsView view = null;
private ConstraintLayout layout = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
display = getWindowManager().getDefaultDisplay();
layout = (ConstraintLayout)findViewById(R.id.main_layout);
view = new GraphicsView(this);
view.setMinimumWidth(display.getWidth());
view.setMinimumHeight(display.getHeight());
layout.addView(view);
}
}
you can use the ObjectAnimator class to callback
to one of your class methods every time you'd like to draw a bit more of the path.
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;
import android.util.Log;
public class PathView extends View
{
Path path;
Paint paint;
float length;
public PathView(Context context)
{
super(context);
}
public PathView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public PathView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
public void init()
{
paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(10);
paint.setStyle(Paint.Style.STROKE);
path = new Path();
path.moveTo(50, 50);
path.lineTo(50, 500);
path.lineTo(200, 500);
path.lineTo(200, 300);
path.lineTo(350, 300);
// Measure the path
PathMeasure measure = new PathMeasure(path, false);
length = measure.getLength();
float[] intervals = new float[]{length, length};
ObjectAnimator animator = ObjectAnimator.ofFloat(PathView.this, "phase", 1.0f, 0.0f);
animator.setDuration(3000);
animator.start();
}
//is called by animtor object
public void setPhase(float phase)
{
Log.d("pathview","setPhase called with:" + String.valueOf(phase));
paint.setPathEffect(createPathEffect(length, phase, 0.0f));
invalidate();//will calll onDraw
}
private static PathEffect createPathEffect(float pathLength, float phase, float offset)
{
return new DashPathEffect(new float[] { pathLength, pathLength },
Math.max(phase * pathLength, offset));
}
#Override
public void onDraw(Canvas c)
{
super.onDraw(c);
c.drawPath(path, paint);
}
}
Then, just call init() to begin the animation, like this (or if you'd like it to start as soon as the view is inflated, put the init() call inside the constructors):
PathView path_view = (PathView) root_view.findViewById(R.id.path);
path_view.init();
Also see this question here, and
Using Value Animator Example
Reference 1
Reference 2
Reference 3

Problems with KeyListener and JOGL

I'm trying to bind a key to translate a GL_QUAD around the screen. I created a class, as I will attach below, that implements KeyListener, and within that I have a method that upon the keypress of 'd', adds 0.1 to the x coordinates of the quad vertices. Now, I have two questions relating to this.
Firstly, it doesn't seem to do anything. Upon the keypress, nothing happens to the object.
Is there a better way to achieve what I am trying to do? My end goal is to eventually end up with a sprite, that the camera is focused upon, that can move around a visually 2D game world.
Thanks for your time.
Code:
SpriteTest.java
package com.mangostudios.spritetest;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLCanvas;
import com.jogamp.opengl.util.FPSAnimator;
public class SpriteTest
{
public static void main(String[] args) {
GLProfile glp = GLProfile.getDefault();
GLCapabilities caps = new GLCapabilities(glp);
GLCanvas canvas = new GLCanvas(caps);
Frame frame = new Frame("AWT Window Test");
frame.setSize(300, 300);
frame.add(canvas);
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
canvas.addGLEventListener(new Renderer());
FPSAnimator animator = new FPSAnimator(canvas, 60);
//animator.add(canvas);
animator.start();
}
}
Renderer.java
package com.mangostudios.spritetest;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
public class Renderer implements GLEventListener {
InputListener input = new InputListener();
#Override
public void display(GLAutoDrawable drawable) {
update();
render(drawable);
}
#Override
public void dispose(GLAutoDrawable drawable) {
}
#Override
public void init(GLAutoDrawable drawable) {
}
#Override
public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) {
}
private void update() {
}
private void render(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
// draw a triangle filling the window
gl.glBegin(GL2.GL_QUADS);
gl.glVertex2f( input.xTran, 0.1f);
gl.glVertex2f( input.xTran,-0.1f);
gl.glVertex2f( -input.xTran, -0.1f);
gl.glVertex2f( -input.xTran, 0.1f);
gl.glEnd();
}
}
InputListener.java
package com.mangostudios.spritetest;
import com.jogamp.newt.event.KeyEvent;
import com.jogamp.newt.event.KeyListener;
public class InputListener implements KeyListener{
boolean loopBool = false;
float xTran = 0.1f;
float yTran = 0.1f;
#Override
public void keyPressed(KeyEvent d) {
loopBool = true;
while (loopBool = true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
#Override
public void keyReleased(KeyEvent d) {
}
}
At first, you never call addKeyListener(). Secondly, you shouldn't put an infinite loop into keyPressed(). Thirdly, you use a NEWT KeyListener whereas you use an AWT GLCanvas :s Rather use GLWindow with a NEWT KeyListener or use an AWT GLCanvas with an AWT KeyListener or use NewtCanvasAWT. Finally, before writing your own example, try mine on Wikipedia in order to understand why it works.

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);
}