i am currently going through some tutorials on android development and libgdx/scene2d atm but got stuck on the touchevents:
For starters i am implementing a board game. By now i managed to draw the board and some meeples (player tokens) as well as adjust their position to the correct coordinates. When i started using libgdx i also successfully implemented a first test to see if the touchinput is working. The test looked something like this:
if(Gdx.input.isTouched()){
touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
camera.unproject(touchPos);
stone.x=touchPos.x - stone.width/2;
stone.y=touchPos.y - stone.height/2;
By now i rearranged my code to use scene2d because of some very convenient features. However it seems like i can't manage to get touchevents to work with scene2d despite i followed some rather simple tutorials. Since some of them reference some recent major changes to scene2d, i wondered if those tutorials might be outdated and hope you guys can help me fix my problem :) I will try and only paste the relevant parts of my code in here to present a minimal example of my non-working code.
Of course i am also glad about any hints on how to "improve" my code in general since i am still learning and probably break some conventions here ^^
Lets start with my actor-class:
//deleted: imports
public class Player extends Actor{
private int xval; //x Position of the Player on the board
private int yval; //y Position of the Player on the board
private Color color;
private TextureRegion playerTexture;
//deleted: some variables that are unimportant for touchevents
public Player(int x, int y, TextureRegion texture){
//initializing some Variables with default values
this.xval = x;
this.yval = y;
color = new Color(1,1,1,1);
playerTexture = texture;
this.setTouchable(Touchable.enabled); //this should be set by default, but i added it just to be sure.
//setBounds seems to be necessary in addition to the "touchable" flag to be able to "grab" or "touch" Actors.
//getX and getY are methods of the actor class which i do not overwrite. I use distinct getters and setters to modify the Variables xval and yval.
setBounds(getX(), getY(), playerTexture.getRegionWidth(), playerTexture.getRegionHeight());
//now i implement the InputListener. This doesnt seem to get called, since the "touch started" message doesn't appear in LogCat.
//In Theory the testevent should move the meeple one space to the right.
//To do that i call event.getTarget() which returns the actor of which the TouchDown is called. I then cast it to my Actor class (Player) and set xval to xval+1.
this.addListener(new InputListener(){
public boolean touchDown(InputEvent event, float x, float y, int pointer, int buttons){
Gdx.app.log("Example", "touch started at (" + x + ", " + y + ")");
((Player)event.getTarget()).setXf(((Player)event.getTarget()).getXf()+1);
return true;
}
});
}
//This is my draw method, which just sets the color of my meeple and then draws it to its current position.
public void draw(Batch batch, float alpha){
batch.setColor(color);
batch.draw(playerTexture, getX(), getY());
}
//deleted: several getters and setters
}
So if i got it right, the gdx inputProcessor is managing all inputs. If i set the Gdx InputProcessor to the stage which contains the actors, it will in case of an event (for example a touchDown) call the inputProcessors of all actors on the stage. Thus since i just added one to my class which includes a touchDown, this should handle the touchDown event in the case that the actor is actually touched. The hitbox to verify that is set by the statement "setBounds" in my Player class.
I implemented this in my ApplicationListener class:
//deleted: imports
public class QuoridorApplicationListener implements ApplicationListener{
Stage stage;
OrthographicCamera camera;
//deleted: several variable declarations.
//deleted: constructor - this only fills some of my variables with values depending on game settings.
#Override
public void create() {
//set stage - since "stage = new Stage(800,400,false)" doesn't seem to work anymore, i did set up a viewport manually. If you got suggestions how to this straightforward, please leave a note :)
Gdx.app.log("Tag", "game started");
camera = new OrthographicCamera();
camera.setToOrtho(false, 800, 480);
Viewport viewport = new Viewport() {};
viewport.setCamera(camera);
stage = new Stage(viewport);
Gdx.input.setInputProcessor(stage); //like i described above, this should forward incoming touchevents to the actors on the stage.
//deleted: setting textures, music, setting up the board (the boardtiles are actors which are added to the stage)
//deleted: setting TextureRegion, player colors, player positions and player attributes.
for (int i = 0; i < playercount; i++){
stage.addActor(players[i]);
}
}
//deleted: dispose(), pause(), resume(), resize() methods.
#Override
public void render() {
camera.update();
for (int i=0; i< playercount; i++){
players[i].setX(/*Formula to determine x-coordinates from Player.xval*/);
players[i].setY(/*Formula to determine x-coordinates from Player.yval*/);
}
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
}
I can't figure out what i am missing to get the touchevents working. So i hope you can help me :) Thanks a lot in advance!
I think your problem originates from your manual creation of a Viewport object, which handles the unprojecting internally.
Instead of
stage = new Stage(800,400,false);
you should use
stage = new Stage();
To resize the the viewport to the correct size once a resize event occurs you need to call
stage.getViewport().update( newWidth, newHeight )
in your ApplicationListener's resize method.
Related
I'm just trying to get libgdx to create a picture wherever I touch the screen.
here's what i have that isn't doing anything
SpriteBatch batch;
Texture img;
#Override
public void create () {
batch = new SpriteBatch();
img = new Texture("badlogic.jpg");
}
#Override
public void render () {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
public class MyInputProcessor implements InputProcessor {
public boolean touchDown (int x, int y, int pointer, int button) {
batch.begin();
batch.draw(img,Gdx.input.getX(),Gdx.input.getY());
batch.end();
return true;
}
... (the rest of the input methods)
if you can't tell, I don't really know what I'm doing yet, I think it has to do with the batch.draw() being in the touchDown method instead of the render area but I can't figure out from research how to do it a different way either
or maybe this is all wrong too, point is I'm doing this to learn so hopefully the correct answer will help me understand some important things about java in general
LibGDX, like basically all game engines, re-renders the entire scene every time render() is called. render() is called repeatedly at the frame rate of the game (typically 60fps if you don't have a complex and unoptimized game). The first drawing-related thing you usually do in the render() method is to clear the screen, which you have already done with Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);. Then you re-draw the whole scene with whatever changes there might be since the last frame.
You are trying to draw something with the batch outside of the render method. In this case, you are doing it when there is a touch down. But since you are doing this only when there is a touch down, the object will appear and disappear on the next call to render(), so it will only be on screen for 1/60th of a second. If you want to do this with an input processor, you need to set some variable to true to indicate the render method should draw it, and other variables to indicate where to draw it. Then in the render() method, you draw the stuff if the variable is true.
Secondly, the x and y that an input processor gets do not necessarily (and usually don't) correspond with the x and y in OpenGL. This is because OpenGL has it's own coordinate system that is not necessarily sized exactly the same as the screen's coordinate system. The screen has (0,0) in the top left with the Y axis going down, and the width and height of the screen matching the number of actual pixels on the screen. OpenGL has (0,0) in the center of the screen with the Y axis going up, and the width and height of the screen being 2 regardless of the actual screen pixels.
But the OpenGL coordinate system is modified with projection matrices. The LibGDX camera classes make this simpler. For 2D drawing, you need an OrthographicCamera. You set the width and size of the OpenGL world using the camera, and can also position the camera. Then you pass the camera's calculated matrices to the SpriteBatch for it to position the scene in OpenGL space.
So to get an input coordinate into your scene's coordinates, you need to use that camera to convert the coordinates.
Finally, LibGDX cannot magically know that it should pass input commands to any old input processor you have created. You have to tell it which InputProcessor it should use with a call to Gdx.input.setInputProcessor().
So to fix up your class:
SpriteBatch batch;
Texture img;
boolean isTouchDown;
final Vector3 touchPosition = new Vector3();
OrthographicCamera camera;
#Override
public void create () {
batch = new SpriteBatch();
img = new Texture("badlogic.jpg");
camera = new OrthographicCamera();
Gdx.input.setInputProcessor(new MyInputProcessor()); // Tell LibGDX what to pass input to
}
#Override
void resize (int width, int height) {
// Set the camera's size in relation to screen or window size
// In a real game you would do something more sophisticated or
// use a Viewport class to manage the camera's size to make your
// game resolution-independent.
camera.viewportWidth = width;
camera.viewportHeight = height;
camera.update(); // re-calculate the camera's matrices
}
#Override
public void render () {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.setProjectionMatrix(camera.combined); // pass camera's matrices to batch
batch.begin();
if (isTouchDown) { // Only draw this while the screen is touched.
batch.draw(img, touchPosition.x, touchPosition.y);
}
batch.end();
}
public class MyInputProcessor implements InputProcessor {
public boolean touchDown (int x, int y, int pointer, int button) {
isTouchDown = true;
touchPosition.set(x, y, 0); // Put screen touch coordinates into vector
camera.unproject(touchPosition); // Convert the screen coordinates to world coordinates
return true;
}
public boolean touchUp (int screenX, int screenY, int pointer, int button){
isTouchDown = false;
return true;
}
//... (the rest of the input methods)
}
I am currently working on a test driven interactive 2D fluid dynamics simulation for my Bachelor thesis. The basic idea is that the user can draw shapes on his screen and the program will simulate how a fluid will flow around these shapes.
So far I have only started with the painting process and I have already run into a little problem.
First, here is a little UML diagram that shows my project structure so far
As you can see, I have created a Painter class that can visit several shapes and will call one of the abstract paint methods depending on the shape type. Then there is the SwingPainter class which inherits from the Painter class and implements the three paint methods as well as the clear method.
Here is the code for my SwingPainter:
public class SwingPainter extends Painter {
private final GraphicPanel graphicPanel;
public SwingPainter(GraphicPanel graphicPanel) {
this.graphicPanel = graphicPanel;
}
private Graphics getGraphics() {
return graphicPanel.getGraphics();
}
#Override
protected void paintLine(Point start, Point end) {
getGraphics().drawLine(start.getX(), start.getY(), end.getX(), end.getY());
}
#Override
protected void paintRectangle(Point start, Point end) {
int minX = start.getX() < end.getX() ? start.getX() : end.getX();
int minY = start.getY() < end.getY() ? start.getY() : end.getY();
int width = Math.abs(start.getX() - end.getX());
int height = Math.abs(start.getY() - end.getY());
getGraphics().drawRect(minX, minY, width, height);
}
#Override
protected void paintCircle(Point center, double radius) {
int minX = center.getX() - (int) radius;
int minY = center.getY() - (int) radius;
int diameter = (int) radius * 2;
getGraphics().drawOval(minX, minY, diameter, diameter);
}
#Override
public void clear() {
graphicPanel.paintComponent(getGraphics());
}
}
So my problem is that the GraphicPanelPresenter(see UML diagram) is responsible for passing the Painter/Visitor to the shapes when the user left clicks or moves the mouse(the second point of a line will follow the cursor for example). That means the actual painting is done outside of Swing's paint method. As a result I have encountered some flickering while painting and I was wondering if anyone knew how to fix that without throwing the whole painter/visitor functionality out the window(since there are other features concerning the shapes that still have to be implemented and could easily be handled by simply passing them another type of visitor).
Well, that was a rather lengthy description for a rather small problem, sorry for the wall of text, but I would be glad for any kind of hint!
You need to do all the of painting in the various Swing paint methods that are designed to be overridden for custom painting (such as paintComponent). Only Swing will know when it's done drawing, or when a new frame needs to be drawn to.
What it really seems like you need to do, is to have your GraphicsPanel have a reference to the object that holds your shape's states, and then draw the graphics that represent that state in paintComponent.
This should also simplify your class relationship diagram as well, since within GraphicsPanel you can call the state object's methods from your listeners to change the state (I would choose different names to make the state object not aware that the changes are based on UI interactions, so if right click rotates, make the method called rotate instead of handleRightClick).
In this following code I have tried to create a coin class and make it so it is a sprite. I have tried to add all these sprites to an array and then draw the array to the screen. It does not produce any errors just does not print the graphic to the screen. I also wanted to know if i could use the sprite to test for collisions. I know my code is not very good as it is quite messy and im just trying to find a solution. Thanks
public class Gold extends Sprite {
private SpriteBatch batch;
private TiledMap map;
private Sprite sprite;
private Boolean isCollected;
public Gold(TiledMap map, Rectangle bounds, Texture gold) {
this.map = map;
sprite = new Sprite(gold);
sprite.setSize( bounds.width / MarioBros.PPM, bounds.height / MarioBros.PPM);
sprite.setPosition(bounds.x / MarioBros.PPM, bounds.y / MarioBros.PPM);
isCollected = false;
}
for (MapObject object : map.getLayers().get(5).getObjects().getByType(RectangleMapObject.class)) {
Rectangle rect = ((RectangleMapObject) object).getRectangle();
for(int i = 0; i < map.getLayers().get(5).getObjects().getCount() - 1; i++){
goldArray[i] = new Gold(map, rect, gold);
}
}
public void drawGold(TiledMap map){
for(int i = 0; i < map.getLayers().get(5).getObjects().getCount() - 1; i++){
goldArray[i].draw(batch);
}
}
In the render:
mapCreator.drawGold(map);
EDIT - I acted upon the first 2 suggestions and now the program outputs this error
Exception in thread "LWJGL Application" java.lang.NullPointerException
at com.alexcz.mariobros.Tools.MapCreator.<init>(MapCreator.java:77)
on this line goldArray[i] = new Gold(rect, gold);
I noticed that your Gold class, which is a Sprite, also has a reference to some other sprite, and that other sprite is the one you have set a region to, and is not the one you are drawing. Remove all references to another sprite in your Gold class.
Also, it would be better not to have a reference to a SpriteBatch or your TiledMap inside your Sprite. That introduces unnecessary coupling that could lead to bugs letter, or just make it harder to maintain your code as it gets more complicated.
public class Gold extends Sprite {
private boolean isCollected; //only use a primitive wrapper if you really need one
public Gold(Rectangle bounds, Texture gold) {
super(gold);
setSize( bounds.width / MarioBros.PPM, bounds.height / MarioBros.PPM);
setPosition(bounds.x / MarioBros.PPM, bounds.y / MarioBros.PPM);
isCollected = false;
}
//...
}
Because the constructor of the Gold class does not call the superclass's (Sprite) constructor, the default constructor is called implicitly. If you take a look at the documentation of the default constructor of Sprite you'll see it specifically notes that:
... Creates an uninitialized sprite. The sprite will need a texture region and bounds set before it can be drawn.
So that explains why nothing is being drawn. One way to fix this is to set a Texture Region. So in the Gold constructor add the following:
setRegion(gold);
There are different solution too. It all depends what you're trying to accomplish.
I am trying to add text for debugging purposes in my program. I call the players debugDraw method like so in the main class:
public void create () {
//Initialize essentials
cam = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
cam.setToOrtho(false);
rend = new ShapeRenderer();
rend.setAutoShapeType(true);
batch = new SpriteBatch();
// Initialize Entities
player = new Player(new Vector2(100, 100), new Vector2(100,100));
enemy = new Enemy(new Vector2(100, 100), new Vector2(100,10));
}
#Override
public void render () {
//Update player
player.update();
//Update camera then set matrix of batch
cam.update();
batch.setTransformMatrix(cam.combined);
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// ShapeRenderer begin
rend.begin();
player.render(rend);
enemy.render(rend);
rend.end();
// ShapeRenderer end
// SpriteBatch begin
batch.begin();
player.renderDebugText(batch);
batch.end();
// SpriteBatch end
}
Here is the entity class which is the parent class to player:
public Entity(Vector2 coords, Vector2 dims){
//Assign constructors
position = coords;
dim = dims;
//For debugging purposes of all entities
debugText = new BitmapFont();
debugText.setColor(Color.WHITE);
}
public void renderDebugText(SpriteBatch batch){
debugText.draw(batch, "Vel.x: " + vel.x, 100, 100);
}
However when I run the program, I just get my normal screen with no text at all. I can't seem to figure out why this isn't working. Any help is extremely appreciated.
Nothing immediately looks wrong with the code you posted, so here's a few ideas;
The default BitmapFont is 15pt, if this is drawn at 15px height then it could be very small if you force your game to a high resolution, like Full-HD. My current project is Full-HD and the font I use looks just about right at 45px, so you could try scaling yours by a factor of 3 or 4. E.g. use this after initialising your font;
bitmapFont.getData().setScale(3);
Your Camera should also have the virtual/viewport dimensions, so if you are forcing a particular resolution then you should pass in your virtual dimensions instead.
As #Tenfour04 has suggested, you should try to avoid multiple instances of the same font, so instead of initialising your font in the Entity class, initialise it in your main game and pass it through the Entity constructor. I can't see how this would fix your issue though as this would be purely for performance.
I made a very simple mistake, but from how much code I posted, it is easily missed. On the line where I put
batch.setTransformMatrix(cam.combined);
it should be replaced with
batch.setProjectionMatrix(cam.combined);
Then all errors go away, sorry, I don't know why it took me so long to figure out. Thanks for all the help!
The basic question here is: how to ALWAYS keep your sprites within the Fitviewport? How to keep a reference to the view in order to have the proper coordinates as to where to draw?
I'm trying to spawn enemies into the gameplay screen. But this is handled by a FitViewport, and enemies and even the player can move outside the FitViewport on certain screen resolutions. So far the problem seems to be in the Y axis.
The FitViewport is made like this:
gameCamera = new OrthographicCamera();
gameCamera.setToOrtho(false);
gameViewport = new FitViewport(MyGame.WORLD_WIDTH,MyGame.WORLD_HEIGHT,gameCamera);
gameViewport.setScreenBounds(0,0,MyGame.WORLD_WIDTH,MyGame.WORLD_HEIGHT);
Then the camera position gets updated like this at the resize() method:
gameViewport.update(width,height); //not used when using the virtual viewport in the render method.
gameCamera.position.set(player.position.x + 200,player.position.y, 0);
Then the update() method calls the Player's own update() method which includes these lines:
//POSITION UPDATE
if (this.position.x<0) this.position.x=0;
if (this.position.x>Gdx.graphics.getWidth() - width) this.position.x= Gdx.graphics.getWidth() - width;
if (this.position.y<0) this.position.y = 0;
if (this.position.y>PlayScreen.gameViewport.getScreenHeight() - height) this.position.y = PlayScreen.gameViewport.getScreenHeight()- height;
Notice for the X axis I'm still using Gdx.graphics dimensions because I'm yet to make it work with PlayScreen.gameViewport.getScreenHeight() (gameViewport has been set to static for this purpose).
Also on enemy spawn (the problem related here is that they spawn outside of the screen Y in terms of what I see) I have this code inside the update() method of the Screen implementing all these viewports:
//Alien Spawn
if (System.currentTimeMillis() - lastZSpawn >= SpawnTimer){
count++;
lastZSpawn= System.currentTimeMillis();
for (int i=0;i<count;i++){
int x = Gdx.graphics.getWidth();
int y = random.nextInt((int)gameViewport.getScreenHeight() - Alien.height);
if (entities.size()<6){
entities.add(new Alien(new Vector2(x,y),1, alienImages,(float)((0))));
}
}
}
Also using gameViewport.getScreenHeight() here cause Gdx.graphics wasnt giving the correct result (it gave me the same issue really).
The render() method is correctly implemented in terms of the batch and applying the viewport:
MyGame.batch.setProjectionMatrix(gameCamera.combined);
gameViewport.apply();
MyGame.batch.begin();
for (int i = entities.size()-1; i>=0;i--){
entities.get(i).render();
}
You should never change the position of your player or enemies when resizing, that's why a viewport is for, remove all the code that do that first, to make your viewport work as you expected you need to create a new instance of camera passing the new viewport width and height when you resize, i prefer to make my camera static so i can acess its atribbutes from everywhere i want, you should do something like this:
public static OrthographicCamera update(int width,int height){
instance = new OrthographicCamera(width, height);
instance.setToOrtho(false);
return instance;
}
The answer to my problem is posted by myself in another question of mine which was also driven by a confusion in the implementation of FitViewports and using WorldWidth and WorldHeight properties as coordinates of reference when drawing objects into the game, and also correctly setting the camera position taking these values into consideration aswell.
The answer is here even though its text and not code and its mainly what i already wrote in this very post. FitViewport doesnt scale properly Libgdx