How to prevent texture bleeding in a tilemap in LibGDX - java

I know there are quite some questions (and answers) on this topic, but they all have different solutions, and none of them seems to be working in my case.
I'm developing a small test project with libGDX, in which I tried to add a simple tilemap. I created the tilemap using Tiled, which seems to be working quite good, except for the texture bleeding, that causes black lines (the background color) to appear between the tiles sometimes.
What I've tried so far:
I read several SO-questions, tutorials and forum posts, and tried almost all of the solutions, but I just don't seem to get this working. Most of the answers said that I would need a padding between the tiles, but this doesn't seem to fix it. I also tried loading the tilemap with different parameters (e.g. to use the Nearest filter when loading them) or rounding the camera's position to prevent rounding problems, but this did even make it worse.
My current setup:
You can find the whole project on GitHub. The branch is called 'tile_map_scaling'
At the moment I'm using a tileset that is made of this tile-picture:
It has two pixels of space between every tile, to use as padding and margin.
My Tiled tileset settings look like this:
I use two pixels of margin and spacing, to (try to) prevent the bleeding here.
Most of the time it is rendered just fine, but still sometimes there are these lines between the tiles like in this picture (sometimes they seem to appear only on a part of the map):
I'm currently loading the tile map into the asset manager without any parameters:
public void load() {
AssetManager manager = new AssetManager();
manager.setLoader(TiledMap.class, new TmxMapLoader(new InternalFileHandleResolver()));
manager.setErrorListener(this);
manager.load("map/map.tmx", TiledMap.class, new AssetLoaderParameters());
}
... and use it like this:
public class GameScreen {
public static final float WORLD_TO_SCREEN = 4.0f;
public static final float SCENE_WIDTH = 1280f;
public static final float SCENE_HEIGHT = 720f;
//...
private Viewport viewport;
private OrthographicCamera camera;
private TiledMap map;
private OrthogonalTiledMapRenderer renderer;
public GameScreen() {
camera = new OrthographicCamera();
viewport = new FitViewport(SCENE_WIDTH, SCENE_HEIGHT, camera);
map = assetManager.get("map/map.tmx");
renderer = new OrthogonalTiledMapRenderer(map);
}
#Override
public void render(float delta) {
//clear the screen (with a black screen)
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
moveCamera(delta);
renderer.setView(camera);
renderer.render();
//... draw the player, some debug graphics, a hud, ...
moveCameraToPlayer();
}
private void moveCamera(float delta) {
if (Gdx.input.isKeyPressed(Keys.LEFT)) {
camera.position.x -= CAMERA_SPEED * delta;
}
else if (Gdx.input.isKeyPressed(Keys.RIGHT)) {
camera.position.x += CAMERA_SPEED * delta;
}
// ...
//update the camera to re-calculate the matrices
camera.update();
}
private void moveCameraToPlayer() {
Vector2 dwarfPosition = dwarf.getPosition();
//movement in positive X and Y direction
float deltaX = camera.position.x - dwarfPosition.x;
float deltaY = camera.position.y - dwarfPosition.y;
float movementXPos = deltaX - MOVEMENT_RANGE_X;
float movementYPos = deltaY - MOVEMENT_RANGE_Y;
//movement in negative X and Y direction
deltaX = dwarfPosition.x - camera.position.x;
deltaY = dwarfPosition.y - camera.position.y;
float movementXNeg = deltaX - MOVEMENT_RANGE_X;
float movementYNeg = deltaY - MOVEMENT_RANGE_Y;
camera.position.x -= Math.max(movementXPos, 0);
camera.position.y -= Math.max(movementYPos, 0);
camera.position.x += Math.max(movementXNeg, 0);
camera.position.y += Math.max(movementYNeg, 0);
camera.update();
}
// ... some other methods ...
}
The question:
I am using padding on the tilemap and also tried different loading parameters and rounding the camera position, but still I have this texture bleeding problem in my tilemap.
What am I missing? Or what am I doing wrong?
Any help on this would be great.

You need to pad the edges of your tiles in you tilesheet.
It looks like you've tried to do this but the padding is transparent, it needs to be of the color of the pixel it is padding.
So if you have an image like this (where each letter is a pixel and the tile size is one):
AB
CB
then padding it should look something like this
A B
AAABBB
A B
C C
CCCCCC
C C
The pixel being padded must be padded with a pixel of the same color.
(I'll try try create a pull request with a fix for your git-repo as well.)

As a little addition to bornander's answer, I created some python scripts, that do all the work to generate a tileset texture, that has the correct edge padding (that bornander explained in his answer) from a texture, that has no padding yet.
Just in case anyone can make use of it, it can be found on GitHub:
https://github.com/tfassbender/libGdxImageTools

There is also a npm package that can extrude the tiles. It was built for the Phaser JS game library, but you could still use it. https://github.com/sporadic-labs/tile-extruder

Related

How to zoom into mouse position with correct translation

I'm trying to zoom a grid in Processing and I am having trouble with applying the correct translation such that zooming is centered around the mouse position. I have searched the web for a while but nothing I try seems to work.
The screen size is width and height and the mouse position is mouseX and mouseY.
The code I have at the moment is below, but it zooms the grid (controlled by player.zoom) from the top left corner which is not what I want. To update the translation of the grid, player has the 2d vector player.translate.
void mouseWheel(MouseEvent event) {
float zoomFactor = 200.0f;
float scroll = event.getCount();
player.zoom -= scroll * player.zoom / zoomFactor;
// player.translate.x += something;
// player.translate.y += something;
}
If you need more details to answer I can link the repo with the source code.
I have created a very simple mock-up for you which will hopefully point you in the right direction into applying this to your player.
So this little demo shows the zooming in to the centre of an ellipse whilst keeping it as the central focus.
float scale = 1;
// displacement left/right
float xPan = 720;
// displacement up/down
float yPan = 450;
boolean zoomIn = true;
void setup() {
size(1440, 900);
}
void draw() {
// allows us to zoom into the center of the screen rather than the corner
translate(width/2, height/2);
scale(scale);
translate(-xPan, -yPan);
background(200);
// draws the ellipse in the center
ellipse(width/2, height/2, 100, 100);
// does the zooming
if (zoomIn) {
scale *= 1.01;
}
}
I suggest you to copy this into a new project and then comment out specific lines to try to understand what's going on and how you can transfer this over to your own project.
The same principles still apply, I didn't do this with mouse input in the effort of making the program as simple as possible.

Zoom in on point in canvas, where everything is based off of a certain point

Alright, bit of a strange one. I'll do my best to explain.
I'm building a game on Android (Java). And I'm just working out the views first. The first thing I'm working on is a solar system view (top down, 2d, no tilt). I've got all the planets and the star displaying properly, and in proportion. I can pan, and zoom. The only issue is, the way I've set up all the circles to draw, they're all based on one point (where the star is), and when I 'zoom'/'pan' I'm not actually zooming/panning. For the pan, I'm just moving the point where everything revolves, and for the zoom, I'm not scaling the whole canvas (tried that, didn't work out) I'm adjusting the size of the elements I'm drawing, in accordance with the zoom.
So I've got a 'star' that starts out in the middle of the screen, the point is called orbitLocation.
And a zoom variable that starts at 1f.
The star is drawn very simply - canvas.drawCircle((float)orbitLocation.x, (float)orbitLocation.y, this.radius * zoom, this.paint);
Each of the planets are positioned like so:
canvas.drawCircle(getPlanetX(angle, orbitLocation, zoom), getPlanetY(angle, orbitLocation, zoom), this.radius * zoom, this.paint);
getPlanetX and getPlanetY look like this:
private float getPlanetX(double angle, Point orbitingPoint, float zoom) {
return orbitingPoint.x + (this.distance * Constants.AU * zoom) * (float)Math.sin(angle);
}
private float getPlanetY(double angle, Point orbitingPoint, float zoom) {
return orbitingPoint.y + (this.distance * Constants.AU * zoom) * (float)Math.cos(angle);
}
Constants.AU is a constant int 11507 in place for Astronomical Unit - for proper scaling.
Now the problem I'm having with zoom, is if I pan, and then zoom, the whole thing scales from the orbitLocation, disregarding where I'm trying to zoom in on, so whatever was right in the middle of my screen, quickly gets thrown way off screen.
Desired outcome. Have whatever is in the center of the screen (or between fingers in pinch, either way), stay in the center of the screen, and the orbit point adjust itself in accordance.
Here's the relevant part of my touch event:
if (touchCount == 2) { // if pinch
if (startZoomTouchPoint != null) {
float newDistance = getDistance(event, 0, 1);
float distanceDifference = (startZoomDistance - newDistance);
zoom -= distanceDifference * (zoom / 100);
if (zoom < MIN_ZOOM) {
zoom = MIN_ZOOM;
}
// Point zoomCenter = getZoomCenter(event);
// int yOrbitOffset = orbitLocation.y - zoomCenter.y;
// int xOrbitOffset = orbitLocation.x - zoomCenter.x;
// orbitLocation.x += xOrbitOffset + Constants.AU * zoom;
// orbitLocation.y += yOrbitOffset + Constants.AU * zoom;
startZoomDistance = newDistance;
updateStartZoomTouchPoint(event);
}
}
Those commented lines in the middle are my latest attempt to achieve my goal.
Every time I try, I either move the orbitLocation way too much, or not enough, or it moves around weirdly.
getZoomCenter literally just gets the point between the 2 fingers. We can just use the center of the screen if that's easier.
Alright, that's all I've got. Let me know if you need any more info.
Please look at the following code snippet
private ScaleGestureDetector mScaleDetector;
private float zoom = 1.f;
public MyCustomView(Context mContext){
...
// View code goes here
...
mScaleDetector = new ScaleGestureDetector(context, new
ScaleListener());
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
return true;
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.scale(zoom, zoom);
...
// onDraw() code goes here
...
canvas.restore();
}
private class ScaleListener
extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
zoom = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
invalidate();
return true;
}
}
Basically, you need to scale the canvas based on the scale value obtained from the touch event. You can do that by
Save the canvas
Scale the canvas
Draw what you were drawing
Restore the canvas
and you can set the zoom on the response of scale gesture detector. Call invalidate afterwards.
For more information visit https://developer.android.com/training/gestures/scale

How to make LibGDX Actions moveTo() animate from one point to another in a curved line?

I am working on a project in LibGDX, and I am using Scene2D actors for some of my sprites. In this regard, I have a sprite, which is spawning somewhere on the screen and needs to move to another position on the screen. To do this I am using the moveTo(xPos, yPos, duration, interpolation) method in the Actions, to make the move animation.
However, when I use this approach, the actor moves like I told it to, but it only moves in a straight line, from point A to B. I have tried several Interpolation options, like Circle interpolation and such, but it seems only to impact the speed of the animation line.
So now my question: How do I make my animation make a smooth curved line (See picture), from A to B?
I am currently using this code to make the Actions animation:
adultCustomerPointActor.addAction(Actions.sequence(
Actions.moveTo(300, 200, 2f, Interpolation.circle)
));
Thanks in advance for your help :)
It's a geometry problem. Using vectors, find the point halfway between the two points:
vec1.set(bx, by).sub(ax, ay).scl(0.5f).add(ax, ay);
Get another vector that is 90 or 270 to from the vector between the points:
vec2.set(bx, by).sub(ax, ay).rotate90().add(vec1);
This vec2 can be scaled to adjust how extreme curvature of the arc is. If you leave it alone, you'll have a quarter circle. You can also scale it negative to reverse the curvature.
Then add the second vector to the first to find the center point of your arc, which we can call point C.
vec1.set(bx, by).sub(vec2); // CB
vec3.set(ax, ay).sub(vec2); // CA
float angle = vec1.angle(vec3);
Now you need a vector that points from point C to point A. You will rotate this vector until it reaches point B. So you need the angle between CA and CB.
So here's a very simplistic class that implements this. It doesn't account yet for deciding if you want the arc to go up or down and if you want to scale how extreme it looks. You could add those as additional parameters with getters/setters. I haven't tested it, so it may need some debugging.
public class ArcToAction extends MoveToAction {
private float angle;
private final Vector2 vec1 = new Vector2(), vec2 = new Vector2(), vec3 = new Vector2();
#Override
protected void begin () {
super.begin();
float ax = target.getX(getAlignment()); // have to recalculate these because private in parent
float ay = target.getY(getAlignment());
vec1.set(getX(), getY()).sub(ax, ay);
vec2.set(vec1).rotate90();
vec1.scl(0.5f).add(ax, ay);
vec2.add(vec1);
vec1.set(bx, by).sub(vec2); // CB
vec3.set(ax, ay).sub(vec2); // CA
angle = vec1.angle(vec3);
}
protected void update (float percent) {
if (percent >= 1){
target.setPosition(getX(), getY(), getAlignment());
return;
}
vec1.set(vec3).rotate(percent * angle);
target.setPosition(vec1.x, vec1.y, getAlignment());
}
}
If you want to support automatic pooling, you can add a method like this:
static public ArcToAction arcTo (float x, float y, float duration, Interpolation interpolation) {
ArcToAction action = Actions.action(ArcToAction .class);
action.setPosition(x, y);
action.setDuration(duration);
action.setInterpolation(interpolation);
return action;
}

libgdx create texture from overlay using pixmap

I am trying to create a method which returns a texture modified by an overlay using libgdx and PixMap.
Assuming I have 2 images:
A Base Image in FileHandle textureInput
And an overlay image in FileHandle overLay
It should produce this texture:
So it should use the RGB values from the textureInput and the alpha values from the overLay and create the final image. I believe I can do this using the Pixmap class but I just can't seem to find exactly how.
Here is what I gather should be the structure of the method:
public Texture getOverlayTexture(FileHandle overLay, FileHandle textureInput){
Pixmap inputPix = new Pixmap(textureInput);
Pixmap overlayPix = new Pixmap(overLay);
Pixmap outputPix = new Pixmap(inputPix.getWidth(), inputPix.getHeight(), Format.RGBA8888);
// go over the inputPix and add each byte to the outputPix
// but only where the same byte is not alpha in the overlayPix
Texture outputTexture = new Texture(outputPix, Format.RGBA8888, false);
inputPix.dispose();
outputPix.dispose();
overlayPix.dispose();
return outputTexture;
}
I am just looking for a bit of direction as to where to go from here. Any help is really appreciated. I apologize if this question is too vague or if my approach is entirely off.
Thanks!
I finally found the way to do this.
How my game is setup is that each item draws itself. They are handed a spritebatch and can do stuff with it. I did it that way various reasons. There is an item manager containing a list of items. Each item has various attributes. Each item has it's own render method along with other independent methods. Here is what finally worked:
A normal item's render method which does not use any alpha masking:
public void render(SpriteBatch batch, int renderLayer) {
if(renderLayer == Integer.parseInt(render_layer)){ // be in the correct render layer
batch.draw(item.region,
item.position.x, // position.x
item.position.y, // position.y
0, //origin x
0, //origin y
item.region.getRegionWidth() , //w
item.region.getRegionHeight(), //h
item.t_scale, //scale x
item.t_scale, //scale y
item.manager.radiansToDegrees(item.rotation)); //angle
}
}
So it is handed a spritebatch that it draws to with the correct image, location, scale, and rotation, and that is that.
After playing around with what I found here: https://gist.github.com/mattdesl/6076846 for a while, this finally worked for an item who needs to use alpha masking:
public void render(SpriteBatch batch, int renderLayer) {
if(renderLayer == Integer.parseInt(render_layer)){
batch.enableBlending();
//draw the alpha mask
drawAlphaMask(batch, item.position.x, item.position.y, item.region.getRegionWidth(), item.region.getRegionHeight());
//draw our foreground elements
drawForeground(batch, item.position.x, item.position.y, item.region.getRegionWidth(), item.region.getRegionHeight());
batch.disableBlending();
}
}
There is a TextureRegion named alphaMask which contains a black shape.
It can be any image, but let's say in this instance its this shape / image:
Here is the function called above that uses that image:
private void drawAlphaMask(SpriteBatch batch, float x, float y, float width, float height) {
//disable RGB color, only enable ALPHA to the frame buffer
Gdx.gl.glColorMask(false, false, false, true);
// Get these values so I can be sure I set them back to how it was
dst = batch.getBlendDstFunc();
src = batch.getBlendSrcFunc();
//change the blending function for our alpha map
batch.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ZERO);
//draw alpha mask sprite
batch.draw(alphaRegion,
x, // position.x
y, // position.y
0, // origin x
0, // origin y
alphaRegion.getRegionWidth(), // w
alphaRegion.getRegionHeight(), // h
item.t_scale, // scale x
item.t_scale, // scale y
item.manager.radiansToDegrees(item.rotation)); // angle
//flush the batch to the GPU
batch.flush();
}
There are a variety of "materials" to apply to any shape. In any instance one of them is assigned to the spriteRegion variable. Let's say right now it is this:
So the drawForeground method called above uses that image like this:
private void drawForeground(SpriteBatch batch, float clipX, float clipY, float clipWidth, float clipHeight) {
//now that the buffer has our alpha, we simply draw the sprite with the mask applied
Gdx.gl.glColorMask(true, true, true, true);
batch.setBlendFunction(GL10.GL_DST_ALPHA, GL10.GL_ONE_MINUS_DST_ALPHA);
batch.draw(spriteRegion,
clipX, // corrected center position.x
clipY, // corrected center position.y
0, //origin x
0, //origin y
spriteRegion.getRegionWidth() , //w
spriteRegion.getRegionHeight(), //h
item.t_scale, //scale x
item.t_scale, //scale y
item.manager.radiansToDegrees(item.rotation)); //angle
//remember to flush before changing GL states again
batch.flush();
// set it back to however it was before
batch.setBlendFunction(src, dst);
}
That all worked right away in the desktop build, and can produce "Brick Beams" (or whatever) in the game nicely:
However in Android and GWT builds (because after all, I am using libgdx) it did not incorporate the alpha mask, and instead rendered the full brick square.
After a lot of looking around I found this: https://github.com/libgdx/libgdx/wiki/Integrating-libgdx-and-the-device-camera
And so to fix this in Android I modified the MainActivity.java onCreate method like this:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
cfg.useGL20 = false;
cfg.r = 8;
cfg.g = 8;
cfg.b = 8;
cfg.a = 8;
initialize(new SuperContraption("android"), cfg);
if (graphics.getView() instanceof SurfaceView) {
SurfaceView glView = (SurfaceView) graphics.getView();
// force alpha channel - I'm not sure we need this as the GL surface
// is already using alpha channel
glView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
}
}
And that fixes it for Android.
I still cannot figure out how to make it work properly in gwt, as I cannot figure out how to tell libgdx to tell GWT to tell webGl to go ahead and pay attention to the alpha channel. I'm interested in how to do something like this in an easier or less expensive way (though this seems to work fine).
If anyone knows how to make this work with GWT, please post as another answer.
Here is the non-working GWT build if you want to see the texture issue:
https://supercontraption.com/assets/play/index.html

libgdx coordinate system differences between rendering and touch input

I have a screen (BaseScreen implements the Screen interface) that renders a PNG image. On click of the screen, it moves the character to the position touched (for testing purposes).
public class DrawingSpriteScreen extends BaseScreen {
private Texture _sourceTexture = null;
float x = 0, y = 0;
#Override
public void create() {
_sourceTexture = new Texture(Gdx.files.internal("data/character.png"));
}
.
.
}
During rendering of the screen, if the user touched the screen, I grab the coordinates of the touch, and then use these to render the character image.
#Override
public void render(float delta) {
if (Gdx.input.justTouched()) {
x = Gdx.input.getX();
y = Gdx.input.getY();
}
super.getGame().batch.draw(_sourceTexture, x, y);
}
The issue is the coordinates for drawing the image start from the bottom left position (as noted in the LibGDX Wiki) and the coordinates for the touch input starts from the upper left corner. So the issue I'm having is that I click on the bottom right, it moves the image to the top right. My coordinates may be X 675 Y 13, which on touch would be near the top of the screen. But the character shows at the bottom, since the coordinates start from the bottom left.
Why is what? Why are the coordinate systems reversed? Am I using the wrong objects to determine this?
To detect collision I use camera.unproject(vector3). I set vector3 as:
x = Gdx.input.getX();
y = Gdx.input.getY();
z=0;
Now I pass this vector in camera.unproject(vector3). Use x and y of this vector to draw your character.
You're doing it right. Libgdx generally provides coordinate systems in their "native" format (in this case the native touch screen coordinates, and the default OpenGL coordinates). This doesn't create any consistency but it does mean the library doesn't have to get in between you and everything else. Most OpenGL games use a camera that maps relatively arbitrary "world" coordinates onto the screen, so the world/game coordinates are often very different from screen coordinates (so consistency is impossible). See Changing the Coordinate System in LibGDX (Java)
There are two ways you can work around this. One is transform your touch coordinates. The other is to use a different camera (a different projection).
To fix the touch coordinates, just subtract the y from the screen height. That's a bit of a hack. More generally you want to "unproject" from the screen into the world (see the
Camera.unproject() variations). This is probably the easiest.
Alternatively, to fix the camera see "Changing the Coordinate System in LibGDX (Java)", or this post on the libgdx forum. Basically you define a custom camera, and then set the SpriteBatch to use that instead of the default.:
// Create a full-screen camera:
camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
// Set it to an orthographic projection with "y down" (the first boolean parameter)
camera.setToOrtho(true, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
camera.update();
// Create a full screen sprite renderer and use the above camera
batch = new SpriteBatch(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
batch.setProjectionMatrix(camera.combined);
While fixing the camera works, it is "swimming upstream" a bit. You'll run into other renderers (ShapeRenderer, the font renderers, etc) that will also default to the "wrong" camera and need to be fixed up.
I had same problem , i simply did this.
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
screenY = (int) (gheight - screenY);
return true;
}
and every time you want to take input from user dont use Gdx.input.getY();
instead use (Gdx.graphics.getHeight()-Gdx.input.getY())
that worked for me.
The link below discusses this problem.
Projects the given coords in world space to screen coordinates.
You need to use the method project(Vector3 worldCoords) in class com.badlogic.gdx.graphics.Camera.
private Camera camera;
............
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
Create an instance of the vector and initialize it with the coordinates of the input event handler.
Vector3 worldCoors = new Vector3(screenX, screenY, 0);
Projects the worldCoors given in world space to screen coordinates.
camera.project(worldCoors);
Use projected coordinates.
world.hitPoint((int) worldCoors.x, (int) worldCoors.y);
OnTouch();
return true;
}

Categories