True sprite centering libgdx

So right now I am trying to figure out a way to truly center a sprite, no matter of its size. Right now my current formula is this:


	public void addMapObject(PlayerRenderer player) {
		player.setMap(this);
		
		int divideWidth = player.getPlayerFrame().getRegionWidth() / (Constants.pixel / 2);
		
		int offsetX = player.getPlayerFrame().getRegionWidth() / divideWidth;
		
		int divideHeight = player.getPlayerFrame().getRegionHeight() / (Constants.pixel / 2);	
		
		int offsetY = player.getPlayerFrame().getRegionHeight() / divideHeight;
		
		if (player.getPlayerFrame().getRegionHeight() == Constants.pixel) {	
			offsetY = 0;
		}
		
		if (player.getPlayerFrame().getRegionWidth() == Constants.pixel) {
			offsetX = 0;
		}		
		
		player.setPlayerPosition((width/2) - (player.getPlayerFrame().getRegionWidth()) - offsetX, (height/2) - (player.getPlayerFrame().getRegionHeight()) - offsetY);
		
		mapObjects.addMapObject(player); //Need to make the actor class part of a "abstract life object class" and make PlayerRenderer a sub-class of that class 
	}

The api I use is libgdx.

But what I am trying to say is that I have a image that is 64x64, another one that is 32x48. I am trying to figure out a way to truly center these sprites on the screen, right now I am having issues dealing with offset’s. Does anyone have any references, or ideas which could be used to help me?

Not sure about libGDX specifically, but the thought process should be:

x = (windowWidth/2)-(imageWidth/2)
y = (windowHeight/2)-(imageHeight/2)

Not knowing how much of your code works, if you want to just center it on your screen.



player.setPlayerPosition((Gdx.graphics.getWidth() / 2 ) - (texture.getWidth() / 2), (Gdx.graphics.getHeight() / 2 ) - (texture.getHeight() / 2));


Well, ray, just as i went to hit post, you replied.

@The two above, actually that is a good idea. I had to change it a little bit because it was not giving me the desired effect. This is what I currently have, and so far it seems to work:


	public void addMapObject(PlayerRenderer player) {
		player.setMap(this);
		
		int divideWidth = player.getPlayerFrame().getRegionWidth() / (Constants.pixel / 2);
		int offsetX = Constants.pixel/2;
		if (divideWidth % 2 == 1) {
			offsetX = Constants.pixel-8;
		}
		
		int divideHeight = player.getPlayerFrame().getRegionHeight() / (Constants.pixel / 2);
		int offsetY = Constants.pixel / 2;
		if (divideHeight % 2 == 1) {
			offsetY = Constants.pixel-8;
		}
		
		player.setPlayerPosition((width/2) - (player.getPlayerFrame().getRegionWidth()/2) - offsetX, (height/2) - (player.getPlayerFrame().getRegionHeight()/2) - offsetY);
		mapObjects.addMapObject(player); //Need to make the actor class part of a "abstract life object class" and make PlayerRenderer a sub-class of that class to 
	}

One thing to keep in mind, typically it’s a bad idea to draw your player in the center of the screen, what you actually want to do is draw your player in relation to where he is on the map, then tell the camera to center on the player.

For example:

float entityX = mapX+xOnMap; 
float entityY = mapY+yOnMap;

x/yOnMap would be where you want your guy to pop up on the map in relation to the map. (like 100,100 would put up 100 right and down from the map’s 0,0)

then

entityObject.draw(entityX, entityY);

Reason this is ideal is it makes it easier for you to interact with the world, if you relate all of the player’s coordinates with where the map is. Then, you can move/pan around the map anywhere you want and the player will “stay put” in whatever position he’s in on the map.

of course, I have no idea what kind of game you’re making, or the context of it. Rendering the player position related to the screen might make sense for your game. But if you’re planning a game with maps that pan around the screen, it makes more sense to render the player at mapX+xOnMap and mapY+yOnMap.

Hmm, I was thinking the same from the beginning. Since what I am making is technically a RPG engine for pokemon (well for now I am working on a library which I could use on any future projects). But this is how my map class works, and how it is rendered:


package mon.str.life;

import mon.str.constants.Constants;
import mon.str.handlers.AbstractHandlers;
import mon.str.handlers.ExceptionHandler;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.maps.MapProperties;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapRenderer;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;

public class MapHandler extends AbstractHandlers {
	
	private TiledMapRenderer renderMap;
	private TiledMap map;
	private String name;
	private MapProperties mapProps;
	private OrthographicCamera camera;
	private Rectangle bounds;
	private MapObjects mapObjects;
	private float x, y;
	private float width = Gdx.graphics.getWidth();
	private float height = Gdx.graphics.getHeight();
	private Viewport viewport;
	
	public MapHandler(String name) {
		this.name = name;
		try {
			map = new TmxMapLoader().load(Gdx.files.internal("maps/"+ this.name).toString());
		} catch(Exception e) {
			new ExceptionHandler(this.getClass().getName(), e);
		}
		camera = new OrthographicCamera();
		camera.setToOrtho(false, width, height);
		camera.update();
		viewport = new FitViewport(width, height, camera);
		mapProps = map.getProperties();
		renderMap = new OrthogonalTiledMapRenderer(map);
		mapObjects = new MapObjects();
		bounds = new Rectangle();
	}

	public void update() {
		renderMap.setView(camera);
		renderMap.render();
		x = -camera.position.x;
		y = -camera.position.y;
		bounds.set(x+(width/2), y+(height/2), getHeight(), getHeight());
	}
	
	public MapProperties getMapPropertiesByName(String name) {
		try {
			return new TmxMapLoader().load(Gdx.files.internal("maps/"+ name).toString()).getProperties();
		} catch(Exception e) {
			new ExceptionHandler(this.getClass().getName(), e);
		}
		return null;
	}

	public MapProperties getMapProperties() {
		return mapProps;
	}
		
	public OrthographicCamera getCamera() {
		return camera;
	}
	
	public TiledMapRenderer getRender() {
		return renderMap;
	}
		
	public void setMapName(String name) {
		this.name = name;
	}
	
	public String getMapName() {
		return name;
	}
	
	public int getWidth() {
		return (Integer) mapProps.get("width")*Constants.pixel;
	}

	public int getHeight() {
		return (Integer) mapProps.get("height")*Constants.pixel;
	}
	
	public int getTileWidth() {
		return (Integer) mapProps.get("width");
	}

	public int getTileHeight() {
		return (Integer) mapProps.get("height");
	}
		
	public void dispose() {
		((OrthogonalTiledMapRenderer) renderMap).dispose();
	//	for (MapObjects mo : mapObjects) {
		//after I make this "life object super class", make sure to add a dispose method, so we can dispose of all the objects with this one piece of code.	
	//	}
	}

	public Rectangle getBounds() {
		return bounds;
	}

	public void setBounds(Rectangle bounds) {
		this.bounds = bounds;
	}

	public MapObjects getMapObjects() {
		return mapObjects;
	}

	public void addMapObject(PlayerRenderer player) {
		player.setMap(this);
		
		int divideWidth = player.getPlayerFrame().getRegionWidth() / (Constants.pixel / 2);
		int offsetX = Constants.pixel/2;
		if (divideWidth % 2 == 1) {
			offsetX = Constants.pixel - (Constants.pixel / 4);
		}
		
		int divideHeight = player.getPlayerFrame().getRegionHeight() / (Constants.pixel / 2);
		int offsetY = Constants.pixel / 2;
		if (divideHeight % 2 == 1) {
			offsetY = Constants.pixel - (Constants.pixel / 4);
		}
		
		player.setPlayerPosition((width/2) - (player.getPlayerFrame().getRegionWidth()/2) - offsetX, (height/2) - (player.getPlayerFrame().getRegionHeight()/2) - offsetY);
		mapObjects.addMapObject(player); //Need to make the actor class part of a "abstract life object class" and make PlayerRenderer a sub-class of that class to 
	}

	public Viewport getViewport() {
		return viewport;
	}

	public void setViewport(Viewport viewport) {
		this.viewport = viewport;
	}
}

it is not typically a bad idea; OP mentioned pokemon which does exactly that.
Also your solution is very specific… it depends if its top down, side scroller, 3D, depends what you can do and where enemies can come from, game mechanics, map layout, art style and your artistic vision and aaalll that

Of course the point being: Be thoughtful when programming the camera since its very important, is absolutely valid.

There is a difference in drawing the character in the middle of the camera and centering the camera on the character.

Okay, so it is doing what I need it to do so far:

Image frame is 64x64, the entire sheet is 256x256

32x48, 128x192


	public void addMapObject(PlayerRenderer player) {
		player.setMap(this);
		
		int divideWidth = player.getPlayerFrame().getRegionWidth() / (Constants.pixel / 2);
		int offsetX = 0;
		if (divideWidth % 2 == 1) {
			offsetX = -(Constants.pixel / (Constants.pixel / 2));
		}
		
		int divideHeight = player.getPlayerFrame().getRegionHeight() / (Constants.pixel / 2);
		int offsetY = 0;
		if (divideHeight % 2 == 1) {
			offsetY = -(Constants.pixel / (Constants.pixel / 2));
		}

		player.setPlayerPosition(-x+offsetX, -y+offsetY);
		mapObjects.addMapObject(player); //Need to make the actor class part of a "abstract life object class" and make PlayerRenderer a sub-class of that class to 
	}

I removed the dependency of the game screen thanks to what a few people mentioned above, right now I am trying to get the first image to center properly inside the tile like as what you see in the second one.
So what I can tell so far it depends on the image size.

Yea I guess so, just that you would never “draw the character in the middle of the camera”. You draw the character where it is in the world and then move the camera where you wanna look… most times at your character

libgdx’s Sprite class has a setCenter(x,y) method, just use that?

Isnt that used for stretching and skewing a image?


	public void addMapObject(PlayerRenderer player) {
		player.setMap(this);
		int divideWidth = player.getPlayerFrame().getRegionWidth() / (Constants.pixel / 2);
		int divideHeight = player.getPlayerFrame().getRegionHeight() / (Constants.pixel / 2);
		int offsetX = 0;
		int offsetY = 0;
		
		if (divideWidth % 2 == 1) {
			offsetX -=(Constants.pixel / (Constants.pixel / 2));
		}
		
		if (divideHeight % 2 == 1) {
			offsetY -=(Constants.pixel / (Constants.pixel / 2));
		}

		player.setPlayerPosition(-x + offsetX, -y + offsetY); //Now the player will spawn at (0,0) in the map).
		player.getBounds().set(x + (viewport.getWorldWidth() / 2) , y + (viewport.getWorldHeight() / 2), player.getPlayerFrame().getRegionWidth(), player.getPlayerFrame().getRegionHeight());
		mapObjects.addMapObject(player); //Need to make the actor class part of a "abstract life object class" and make PlayerRenderer a sub-class of that class to 
	}

So it seems like I cannot go by multiples of 16, because of the image that is 252 x 256. Now I need to find a more dynamic way for it to work. I guess it might have to be relevant to the image size. Hmm… :frowning:
Also here is my PlayerRenderer:


package mon.str.life;

import mon.str.constants.Constants;
import mon.str.handlers.ExceptionHandler;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.MoveToAction;
import com.badlogic.gdx.utils.viewport.StretchViewport;

public class PlayerRenderer extends Actor {
	
	private Texture texture;
	private Stage stage;
	private MoveToAction moveAction;
	private int columns = 4, rows = 4;
	private TextureRegion[][] textureRegion;
	private TextureRegion[] frames = new TextureRegion[rows * columns];
	private TextureRegion currentFrame;
	private Animation animation;
	private String player;
	private float speed = .50f;
	private MapHandler map;
	private Rectangle bounds;
	private CollisionDetection cd;
	
	public PlayerRenderer(String player) {
		stage = new Stage(new StretchViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()));
		this.player = player;
		Gdx.input.setInputProcessor(stage);
		try {
			texture = new Texture(Gdx.files.internal("players/"+ this.player).toString());
		} catch(Exception e) {
			new ExceptionHandler(this.getClass().getName(), e);
		}
		textureRegion = TextureRegion.split(texture, texture.getWidth()/columns, texture.getHeight()/rows);
		int index = 0;
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				frames[index++] = textureRegion[i][j];
			}
		}
		animation = new Animation(1, frames);
		currentFrame = animation.getKeyFrame(0);
		moveAction = new MoveToAction();
		stage.addActor(this);
		bounds = new Rectangle();
	}
		
	public void draw(Batch batch, float alpha) {
		stage.act(Gdx.graphics.getDeltaTime());
		movement();
		map.getCamera().position.set(getX(), getY(), 0);
		map.getCamera().update();
		batch.setProjectionMatrix(map.getCamera().combined);
		batch.draw(currentFrame, getX(), getY());
	}
	
	public void setPlayerPosition(float x, float y) {
		moveAction.setPosition(x, y);
		addAction(moveAction);
	}
	
	public Stage getStage() {
		return stage;
	}
	
	public void setMap(MapHandler map) {
		this.map = map;
		cd = new CollisionDetection(this);
	}
	
	public MapHandler getMap() {
		return map;
	}
	
	public TextureRegion getPlayerFrame() {
		return currentFrame;
	}
	
	public void movement() {
		//if (!cd.canMove()) {
	//		return; // for now
	//	}
		if (Gdx.input.isKeyPressed(Keys.D)) {
			if (!getActions().contains(moveAction, true)) {
				moveAction.reset();
				currentFrame = animation.getKeyFrame(MovementHandler.goRight());
				moveAction.setPosition(getX()+Constants.pixel, getY());
				moveAction.setDuration(speed);
				addAction(moveAction);
			}
		} else if (Gdx.input.isKeyPressed(Keys.S)) {
			if (!getActions().contains(moveAction, true)) {
				moveAction.reset();
				currentFrame = animation.getKeyFrame(MovementHandler.goDown());
				moveAction.setPosition(getX(), getY()-Constants.pixel);
				moveAction.setDuration(speed);
				addAction(moveAction);
			}
		} else if (Gdx.input.isKeyPressed(Keys.A)) {
			if (!getActions().contains(moveAction, true)) {
				moveAction.reset();
				currentFrame = animation.getKeyFrame(MovementHandler.goLeft());
				moveAction.setPosition(getX()-Constants.pixel, getY());
				moveAction.setDuration(speed);
				addAction(moveAction);
			}
		} else if (Gdx.input.isKeyPressed(Keys.W)) {
			if (!getActions().contains(moveAction, true)) {
				moveAction.reset();
				currentFrame = animation.getKeyFrame(MovementHandler.goUp());
				moveAction.setPosition(getX(), getY()+Constants.pixel);
				moveAction.setDuration(speed);
				addAction(moveAction);
			}
		}
	}
		
	public void dispose() {
		stage.dispose();
	}

	public Rectangle getBounds() {
		return bounds;
	}
	
	public Texture getTexture() {
		return texture;
	}
	
	public void setBounds(Rectangle bounds) {
		this.bounds = bounds;
	}
}

Yeah and rotating

I see, well I found a better way how to deal with my issue, and it has been working so far.