I have a tiled map on which I set characters(every character is of the size of one tile). I managed to make them clickable, even when screen resizes everything works perfect. Every time I click on character I want a button to show up above it. For a button I use stage and place the button in the place I clicked with small transition and it also works.
My problem is when I try to use clicklistener on this button. If the screen does not resize, clicklistener works. Problem starts when the screen get resized - clicks on players works well, only button does not work - after a resize the clicking space and the button space are not aligned. For test purposes I made a test map that shows me grids. It seems that stage is not properly resized. Picture for a reference. I tried many solutions I came upon on the Internet and still can't find a solution to my problem. I have shortened my code down to a minimum basic example:
public class Test extends ApplicationAdapter {
public static Stage stage;
public TiledMap tiledMap;
static OrthographicCamera camera;
TiledMapRenderer tiledMapRenderer;
public static boolean showMenu;
GestureDetector gesture;
InputMultiplexer myInputMultiplexer;
public static int posx, posy;
public static Image move;
public Texture moveMenu;
#Override
public void create() {
moveMenu = new Texture(Gdx.files.internal("move.png"));
gesture = new GestureDetector(new MyGestureListener());
myInputMultiplexer = new InputMultiplexer();
float unitScale = 1 / 32f;
camera = new OrthographicCamera();
camera.setToOrtho(true, 33, 21);
stage = new Stage(new ScreenViewport());
stage.getViewport().setCamera(camera);
tiledMap = new TmxMapLoader().load("test.tmx");
tiledMapRenderer = new OrthogonalTiledMapRenderer(tiledMap, unitScale);
myInputMultiplexer.addProcessor(stage);
myInputMultiplexer.addProcessor(gesture);
Gdx.input.setInputProcessor(myInputMultiplexer);
move = new Image(moveMenu);
move.setWidth(2);
move.setHeight(2);
move.addListener(new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
move(); //my action, works fine
showMenu = false;
}
});
stage.addActor(move);
}
#Override
public void render() {
super.render();
stage.act();
tiledMapRenderer.setView(camera);
camera.update();
tiledMapRenderer.render();
if (showMenu) {
mainMenuDraw();
}
}
public static void mainMenuDraw() {
move.setPosition(posx, posy-2);
stage.draw();
}
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
}
public static OrthographicCamera getCamera() {
return camera;
}
public static Vector3 unprojectCoords(Vector3 coords) {
camera.unproject(coords);
return coords;
} }
and part of my gesturelistener:
public boolean touchDown(float x, float y, int pointer, int button) {
Vector3 temp_coord = new Vector3(x, y, 0);
Vector3 coords = Test.unprojectCoords(temp_coord);
return false;
}
#Override
public boolean tap(float x, float y, int count, int button) {
Vector3 temp_coord = new Vector3(x, y, 0);
Vector3 coords = Test.unprojectCoords(temp_coord);
Test.posx = (int) coords.x;
Test.posy = (int) coords.y;
tap = true;
Test.showMenu = true;
return false;
}
I would suggest to you read more about re-sizing and displaying pixels.
You need to recalculate pixels always when you render something.
- player, background image, buttons, events.
Actually you don't need to use resize method, just get the camera width and height.
You need to share more code, because it depends on everything.
I don't see how you are doing rendering.
tiledMapRenderer.render(), background rendering / layers?
player rendering?
menu rendering?
buttons rendering?
Example: (the same should be for event handler)
public GameButton(TextureRegion reg, float x, float y, OrthographicCamera cam) {
this.reg = reg;
this.x = x;
this.y = y;
this.cam = cam;
width = reg.getRegionWidth();
height = reg.getRegionHeight();
vec = new Vector3();
Texture tex = Game.res.getTexture("hud");
font = new TextureRegion[11];
for(int i = 0; i < 11; i++) {
font[i] = new TextureRegion(tex, 32 + i * 9, 16, 9, 9); //use height and width here)
}
}
Related
I want to add to the score of my game +1 when an enemy was touched, I tried two methods addListener and touchDown but not worked for me or I didn't use them right.
How can I do that my (enemy object is linked to an userData and Actor classes, I regroup many different sizes for my enemy in an enum class also those enemies move from the top of the screen to bot. How to detect if an enemy was touched?
public class GameStage extends Stage {
// This will be our viewport measurements while working with the debug renderer
private static final int VIEWPORT_WIDTH = 13;
private static final int VIEWPORT_HEIGHT = 20;
private World world;
private Ground ground;
private Enemy enemy;
private final float TIME_STEP = 1 / 300f;
private float accumulator = 0f;
private Rectangle bounds;
private Vector3 touchPoint = new Vector3();;
private int score;
private String yourScoreName;
BitmapFont yourBitmapFontName;
private SpriteBatch batch;
private OrthographicCamera camera;
private Box2DDebugRenderer renderer;
public GameStage() {
world = WorldUtils.createWorld();
renderer = new Box2DDebugRenderer();
Gdx.input.setInputProcessor(this);
batch = new SpriteBatch();
score = 0;
yourScoreName = "score: 0";
yourBitmapFontName = new BitmapFont();
setUpWorld();
setUpCamera();
}
public void setUpWorld(){
world = WorldUtils.createWorld();
setUpGround();
createEnemy();
}
private void setUpGround(){
ground = new Ground (WorldUtils.createGround(world));
addActor(ground);
}
private void createEnemy() {
enemy = new Enemy(WorldUtils.createEnemy(world));
// (1) *****using addListener method
enemy.addListener(new InputListener()
{
#Override
public boolean touchDown(InputEvent event, float x, float y,
int pointer, int button)
{
score++;
yourScoreName = "score: " + score;
return true;
}
});
/*enemy.addListener(new ClickListener() {
public void clicked() {
world.destroyBody(enemy.getBody());
}});*/
//bounds = new Rectangle(enemy.getX(), enemy.getY(), enemy.getWidth(), enemy.getHeight());
addActor(enemy);
}
private void setUpCamera() {
camera = new OrthographicCamera(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0f);
camera.update();
}
#Override
public void act(float delta) {
super.act(delta);
checkEnemy();
// Fixed timestep
accumulator += delta;
while (accumulator >= delta) {
world.step(TIME_STEP, 6, 2);
accumulator -= TIME_STEP;
}
//TODO: Implement interpolation
}
private void checkEnemy(){
final Body body = enemy.getBody();
UserData userData = enemy.getUserData();
bounds = new Rectangle(enemy.getBody().getPosition().x, enemy.getBody().getPosition().y, enemy.getUserData().getWidth(), enemy.getUserData().getHeight());
// bounds = new Rectangle(body.getPosition().x, body.getPosition().y,userData.getWidth() ,userData.getHeight());
if (!BodyUtils.enemyInBounds(body,userData)){
world.destroyBody(body);
createEnemy();}
}
public World getWorld(){
return world;
}
// (2) ****using TouchDown method
#Override
public boolean touchDown(int x, int y, int pointer, int button) {
// Need to get the actual coordinates
translateScreenToWorldCoordinates(x, y);
// score++;
// yourScoreName = "score: " + score;
if(enemyTouched(touchPoint.x,touchPoint.y)){
// world.destroyBody(enemy.getBody());
score++;
yourScoreName = "score: " + score;
}
return super.touchDown(x, y, pointer, button);
}
private boolean enemyTouched(float x, float y) {
return bounds.contains(x, y);
}
private void translateScreenToWorldCoordinates(int x, int y) {
getCamera().unproject(touchPoint.set(x, y, 0));
}
#Override
public void draw() {
super.draw();
batch.begin();
yourBitmapFontName.setColor(1.0f, 1.0f, 1.0f, 1.0f);
yourBitmapFontName.draw(batch, yourScoreName, 25, 100);
batch.end();
enemy.setBounds(enemy.getBody().getPosition().x,enemy.getBody().getPosition().y,enemy.getUserData().getWidth(),enemy.getUserData().getHeight());
renderer.render(world, camera.combined);
}
}
A screen from my game:
It should work the way you did it (with the addListener() method). But you have to set the correct bounds of the actor (width, height, position): actor.setBounds(x, y, width, height). I would use the body to get these values. You can also use a ClickListener instead of the InputListener.
I am trying to implement movement to a point, where mouse was clicked.
But I have a problem with mirrored behaviour agains X axis.
When I click on top -> it moves to the bottom, when I click on bottom -> it moves to the top.
Here is for example original position
I clicked on the screen in position with red cross.
But it moves down (as arrow showed).
What's the problem? It seems something with movement vector I presume.
public class Player {
private static final float PLAYER_CIRCLE_RADIUS = 24f;
private static final float MOVEMENT_SPEED = 200f;
private final Circle playerCircle;
private Vector2 direction = new Vector2();
private Vector2 position;
private Vector2 velocity = new Vector2();
private Vector2 movement = new Vector2();
private Vector2 mouseClick = new Vector2();
public Player(float x, float y) {
position = new Vector2(x, y);
playerCircle = new Circle(x, y, PLAYER_CIRCLE_RADIUS);
}
public void draw(ShapeRenderer shapeRenderer) {
shapeRenderer.circle(position.x, position.y, playerCircle.radius);
}
public void update(float delta) {
movement.set(velocity).scl(delta);
if (position.dst2(mouseClick) > movement.len2()) { position.add(movement); }
else { position.set(mouseClick); }
}
public void setDirection(float x, float y) {
mouseClick.set(x, y);
direction.set(mouseClick).sub(position).nor();
velocity.set(direction).scl(MOVEMENT_SPEED);
}
public Vector2 getDirection() {
return direction;
}
public Circle getPlayerCircle() {
return playerCircle;
}
public Vector2 getMouseClick() {
return mouseClick;
}
}
public class GameScreen extends ScreenAdapter {
private static final float WORLD_WIDTH = 640;
private static final float WORLD_HEIGHT = 480;
private ShapeRenderer shapeRenderer;
private Viewport viewport;
private Camera camera;
private Player player;
private Destination dest;
#Override
public void render(float delta) {
clearScreen();
update(delta);
shapeRenderer.setProjectionMatrix(camera.projection);
shapeRenderer.setTransformMatrix(camera.view);
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
dest.draw(shapeRenderer);
player.draw(shapeRenderer);
shapeRenderer.end();
}
#Override
public void resize(int width, int height) {
viewport.update(width, height);
}
#Override
public void show() {
camera = new OrthographicCamera();
camera.position.set(WORLD_WIDTH / 2, WORLD_HEIGHT / 2, 0);
viewport = new FitViewport(WORLD_WIDTH, WORLD_HEIGHT, camera);
shapeRenderer = new ShapeRenderer();
player = new Player(WORLD_WIDTH / 2, WORLD_HEIGHT / 2);
dest = new Destination();
Gdx.input.setInputProcessor(new InputAdapter() {
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
dest.setPosition(screenX, screenY);
camera.unproject(new Vector3(screenX, screenY, 0));
player.setDirection(screenX, screenY);
return true;
}
});
}
private void clearScreen() {
Gdx.gl.glClearColor(Color.BLACK.r, Color.BLACK.g, Color.BLACK.b, Color.BLACK.a);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
private void update(float delta) {
player.update(delta);
}
}
What you describe is actually mirrored on the Y-Axis.
The reason for this behaviour is most likely that your drawing matrix is set in such a way that the origin is in the bottom-left corner with the Y-Axis pointing up, but gui's have their origin at the top-left corner with the Y-Axis pointing down. So when you get your mouse position you should do something like:
actualPosY = screenHeight - mousePosY
This effectively transforms your mouse position to your drawing space.
I've created a game that uses 4 different GameStates: READY,RUNNING,GAMEOVER,and HIGHSCORE(variation of GAMEOVER except this one notifies the player that a highscore has been reached). My issue is that the way I've set up my InputHandler is that in the GameState.READY form the user can touch anywhere in the screen to advance into the GameState.RUNNING form. I've tried multiple tactics into creating a playButton, however nothing seems to be working my way. I created a PlayButton class as such:
public class PlayButton {
private Vector2 position;
private Rectangle boundingRectangle;
private int width;
private int height;
private PlayScreen playScreen;
public PlayButton (float x, float y, int width, int height) {
this.width = width;
this.height = height;
position = new Vector2(x, y);
boundingRectangle = new Rectangle();
}
public void update(float delta) {
boundingRectangle.set(position.x,(position.y),width,height);
}
public float getTheX() {
return position.x;
}
public float getY() {
return position.y;
}
public float getWidth() {
return width;
}
public float getHeight() {
return height;
}
public Rectangle getBoundingRectangle(){
return boundingRectangle;
}
}
and in my InputHandler i tried this:
public InputHandler(GameWrold myWorld, float scaleFactorX, float scaleFactorY){
this.myWorld = myWorld;
mySam = myWorld.getSam();
playButton = new PlayButton(45,gameHeight-75,50,-35);
buttonPlay = myWorld.getPlayButton();
int midPointY = myWorld.getMidPointY();
this.scaleFactorX = scaleFactorX;
this.scaleFactorY = scaleFactorY;
}
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
Vector2 touchPos = new Vector2();
touchPos.set(Gdx.input.getX(), Gdx.input.getY());
Vector2 tuch = new Vector2(screenX,screenY);
buttonPlay.getBoundingRectangle();
touch = new Rectangle(touchPos.x,touchPos.y,5,5);
if (myWorld.isReady()) {
if(playButton.getBoundingRectangle().contains(touchPos)){
myWorld.start();
}
}
mySam.onClick();
if (myWorld.isGameOver() || myWorld.isHighScore()) {
// Reset all variables, go to GameState.READ
myWorld.restart();
}
return true;
}
}
As you can see I tried creating a Rectangle with the touch variable and I tried checking if touch collided with the playButton boundingRectangle like this:
if(Intersector.overlaps(touch,playButton.getBoundingRectangle())){ or
(playButton.getBoundingRectangle().overlaps(playButton.getBoundingRectangle()))
Do not invent a circle and don't break open doors.
Use Scene2d :)
It is a simple UI framework fully compatible with libGDX that allows you to create Buttons, Sliders, Windows and another widgets in about one line of code.
You will find a nice description in a link I've attached above but TLDR version is:
Create Skin and pack a texture atlas for this using TexturePacker (free version is enough usually)
Create a Stage with appropriate Viewport
Stage stage = new Stage(new FitViewport(WIDTH, HEIGHT));
Set the stage as input processor (it means that all event on the stage will be catched)
Gdx.input.setInputProcessor(stage);
Create a Button with appropriate style (defined in Skin)
Button button = new Button(skin, "style");
Attach a ClickListener with appropriate action to the Button
button.addListener(new ClickListener(){
#Override
public void clicked(InputEvent event, float x, float y)
{
//some action
}
});
Set it's position and add it to the stage
button.setPosition(x, y);
stage.addActor(button);
Fire act() and draw() stage's methods in your render()
//render()
stage.act();
stage.draw();
You've earned few hours ;)
On my class I'm implementing ApplicationListener, so on my create method this is what I do:
public void create(){
camera=new Camera();
camera.setToOrtho(false,screenW,screenH);
}
//then on the render method:
public void render(){
camera.update();
}
But then when I use Gdx.input.getY() the result is reversed, when I go up the Y coordinate is less and when I go down the it gets higher.For better understanding:
Have a look at the camera class and the unproject method. That should translate between screen coordinates and world coordinates. (Probably more efficient way to do this, but for illustration):
float x = Gdx.input.getX();
float y = Gdx.input.getY();
float z = 0.0f;
Vector3 screenCoordinates = new Vector3(x, y, z);
Vector3 worldCoordinates = camera.unproject(screenCoordinates);
Then use the worldCoordinates vector for whatever you need it for.
EDIT: Added small working example and screenshot. My screen capture didn't capture the mouse, thus the red "star". But this simple app displays y coordinates in "initial" and "unprojected" coords as you move the mouse around the screen. Try it for yourself. I think this is what you are getting at, no?
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.math.Vector3;
public class SimpleInputCoords implements ApplicationListener {
private OrthographicCamera camera;
#Override
public void create() {
camera = new OrthographicCamera();
camera.setToOrtho(false, 800, 480);
}
#Override
public void render() {
int x = Gdx.input.getX();
int y = Gdx.input.getY();
int z = 0;
System.out.println("UnmodifiedYCoord:"+y);
Vector3 screenCoordinates = new Vector3(x, y, z);
Vector3 worldCoordinates = camera.unproject(screenCoordinates);
System.out.println("UnprojectedYCoord:"+worldCoordinates.y);
}
#Override
public void dispose() {
}
#Override
public void resize(int width, int height) {
}
#Override
public void pause() {
}
#Override
public void resume() {
}
}
In Java coordinates start in the upper left corner (0,0). On a 100 x 100, (100,0) would be upper right, and (0, 100) would be lower left. So the behavior you are seeing is expected.
http://inetjava.sourceforge.net/lectures/part2_applets/InetJava-2.1-2.2-Introduction-to-AWT-and-Applets_files/image001.gif
I have an an Actor called tile that I'd like to add to a table to make something like a checker board. I can get the tile to draw, however the batch.draw always asks for a position. If I set the position then the tile won't be drawn at the end of the row like I want. For example,
batch.draw(texture, 0, 0);
Would draw:
As you can see, I have a table with buttons and they line up on the row correctly, since they aren't drawn by position. Is there a way I can get my tile to draw the same way?
Here's my code:
Tile.java
public class Tile extends Actor {
boolean filled;
int row;
int column;
Texture texture;
public Tile(int row, int column){
this.row = row;
this.column = column;
texture = new Texture(Gdx.files.internal("assets/game/tile.png"));
this.addListener(new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
System.out.println("down");
return true;
}
public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
System.out.println("up");
}
});
}
#Override
public void draw (SpriteBatch batch, float parentAlpha) {
//batch.setColor(0.5f, 0.5f, 0.5f, 1);
batch.draw(texture, 0, 0);
}
public int getRow(){
return row;
}
public int getColumn(){
return column;
}
}
MainScreen.java
public class MainScreen implements Screen {
MyGame myGame;
private final Stage stage;
private final SpriteBatch spriteBatch;
Table table;
private TextureAtlas buttonsUi;
Boolean nextScreen= false;
public MainScreen(MyGame myGame) {
spriteBatch = new SpriteBatch();
stage = new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false, spriteBatch);
this.myGame = myGame;
Gdx.input.setInputProcessor(stage);
//Create the text label
Skin pennySkin = new Skin(Gdx.files.internal("uiskin.json"));
Label pennyLabel = new Label("PennyPop", pennySkin);
//Create the sfx and api button button
ImageButton sfxButton = createSfxButton();
ImageButton apiButton = createApiButton();
ImageButton gameButton = createGameButton();
table = new Table();
table.add(sfxButton);
table.add(apiButton).padLeft(10);
table.add(gameButton).padLeft(10);
table.add(new Tile(0,0)).padLeft(10); //Tile added here to the row
table.debug();
//VericalGroup the buttons and the label
VerticalGroup vGroup = new VerticalGroup();
vGroup.addActor(pennyLabel);
vGroup.addActor(table);
//Put in another group to align correctly with api widget
Table rootTable = new Table();
rootTable.setName("root table");
rootTable.setFillParent(true);
rootTable.add(vGroup);
rootTable.debug();
// stage.addActor(pennyLabel);
stage.addActor(rootTable);
}
#Override
public void dispose() {
spriteBatch.dispose();
stage.dispose();
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
stage.act(delta);
stage.draw();
Table.drawDebug(stage);
if(nextScreen){
myGame.setScreen(new GameScreen(myGame));
dispose();
}
}
#Override
public void resize(int width, int height) {
stage.setViewport(width, height, false);
}
#Override
public void hide() {
Gdx.input.setInputProcessor(null);
}
#Override
public void show() {
Gdx.input.setInputProcessor(stage);
}
#Override
public void pause() {
// Irrelevant on desktop, ignore this
}
#Override
public void resume() {
// Irrelevant on desktop, ignore this
}
Your Tile Actor must set its own size so the Table knows how to place it. Since it's a subclass of Actor, it already has the size fields available for you to fill in (don't create your own height and width variables, which would hide the correct ones).
texture = new Texture(Gdx.files.internal("assets/game/tile.png"));
width = texture.getWidth();
height = texture.getHeight();
Then to draw it, use the x and y fields (also part of the Actor superclass). The Table will set the x and y values for you, so this is all you need to do.
#Override
public void draw (SpriteBatch batch, float parentAlpha) {
batch.setColor(1, 1, 1, parentAlpha);
batch.draw(texture, x, y);
}
Note that you must always set color, even if it's white, because its always possible that the batch's color has been left as something non-white by some other thing it has just drawn.