Basicamente, quero impedir que a câmera se mova em subpixels, pois acho que isso leva os sprites a mudar visivelmente suas dimensões, mesmo que ligeiramente. (Existe um termo melhor para isso?) Observe que este é um jogo de pixel-art onde eu quero ter gráficos pixelados nítidos. Aqui está um gif que mostra o problema:
Agora, o que eu tentei foi o seguinte: Mova a câmera, projete a posição atual (portanto, as coordenadas da tela) e, em seguida, arredonde ou faça a conversão para int. Depois disso, converta-o de volta às coordenadas do mundo e use-o como a nova posição da câmera. Tanto quanto eu sei, isso deve bloquear a câmera nas coordenadas reais da tela, não em frações.
Por alguma razão, no entanto, o y
valor da nova posição simplesmente explode. Em questão de segundos, aumenta para algo como 334756315000
.
Aqui está um SSCCE (ou um MCVE) baseado no código no wiki do LibGDX :
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.viewport.ExtendViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
public class PixelMoveCameraTest implements ApplicationListener {
static final int WORLD_WIDTH = 100;
static final int WORLD_HEIGHT = 100;
private OrthographicCamera cam;
private SpriteBatch batch;
private Sprite mapSprite;
private float rotationSpeed;
private Viewport viewport;
private Sprite playerSprite;
private Vector3 newCamPosition;
@Override
public void create() {
rotationSpeed = 0.5f;
playerSprite = new Sprite(new Texture("/path/to/dungeon_guy.png"));
playerSprite.setSize(1f, 1f);
mapSprite = new Sprite(new Texture("/path/to/sc_map.jpg"));
mapSprite.setPosition(0, 0);
mapSprite.setSize(WORLD_WIDTH, WORLD_HEIGHT);
float w = Gdx.graphics.getWidth();
float h = Gdx.graphics.getHeight();
// Constructs a new OrthographicCamera, using the given viewport width and height
// Height is multiplied by aspect ratio.
cam = new OrthographicCamera();
cam.position.set(0, 0, 0);
cam.update();
newCamPosition = cam.position.cpy();
viewport = new ExtendViewport(32, 20, cam);
batch = new SpriteBatch();
}
@Override
public void render() {
handleInput();
cam.update();
batch.setProjectionMatrix(cam.combined);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
mapSprite.draw(batch);
playerSprite.draw(batch);
batch.end();
}
private static float MOVEMENT_SPEED = 0.2f;
private void handleInput() {
if (Gdx.input.isKeyPressed(Input.Keys.A)) {
cam.zoom += 0.02;
}
if (Gdx.input.isKeyPressed(Input.Keys.Q)) {
cam.zoom -= 0.02;
}
if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
newCamPosition.add(-MOVEMENT_SPEED, 0, 0);
}
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
newCamPosition.add(MOVEMENT_SPEED, 0, 0);
}
if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
newCamPosition.add(0, -MOVEMENT_SPEED, 0);
}
if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
newCamPosition.add(0, MOVEMENT_SPEED, 0);
}
if (Gdx.input.isKeyPressed(Input.Keys.W)) {
cam.rotate(-rotationSpeed, 0, 0, 1);
}
if (Gdx.input.isKeyPressed(Input.Keys.E)) {
cam.rotate(rotationSpeed, 0, 0, 1);
}
cam.zoom = MathUtils.clamp(cam.zoom, 0.1f, 100 / cam.viewportWidth);
float effectiveViewportWidth = cam.viewportWidth * cam.zoom;
float effectiveViewportHeight = cam.viewportHeight * cam.zoom;
cam.position.lerp(newCamPosition, 0.02f);
cam.position.x = MathUtils.clamp(cam.position.x,
effectiveViewportWidth / 2f, 100 - effectiveViewportWidth / 2f);
cam.position.y = MathUtils.clamp(cam.position.y,
effectiveViewportHeight / 2f, 100 - effectiveViewportHeight / 2f);
// if this is false, the "bug" (y increasing a lot) doesn't appear
if (true) {
Vector3 v = viewport.project(cam.position.cpy());
System.out.println(v);
v = viewport.unproject(new Vector3((int) v.x, (int) v.y, v.z));
cam.position.set(v);
}
playerSprite.setPosition(newCamPosition.x, newCamPosition.y);
}
@Override
public void resize(int width, int height) {
viewport.update(width, height);
}
@Override
public void resume() {
}
@Override
public void dispose() {
mapSprite.getTexture().dispose();
batch.dispose();
}
@Override
public void pause() {
}
public static void main(String[] args) {
new Lwjgl3Application(new PixelMoveCameraTest(), new Lwjgl3ApplicationConfiguration());
}
}
e aqui está osc_map.jpg
e odungeon_guy.png
Eu também estaria interessado em aprender sobre maneiras mais simples e / ou melhores de corrigir esse problema.
Respostas:
Seu problema não está movendo a câmera em incrementos de pixel completo. É que a sua relação texel / pixel é um pouco não inteira . Emprestarei alguns exemplos dessa pergunta semelhante que respondi no StackExchange .
Aqui estão duas cópias de Mario - ambas estão se movendo pela tela na mesma proporção (pelos sprites se movendo para a direita no mundo ou pela câmera se movendo para a esquerda - acaba equivalente), mas apenas a primeira mostra esses artefatos ondulados e os o inferior não:
A razão é que eu dimensionei ligeiramente o Mario superior em 1 fator de 1,01x - essa fração minúscula significa que ele não está mais alinhado com a grade de pixels da tela.
Um pequeno desalinhamento da tradução não é um problema - a filtragem de textura "mais próxima" se encaixa no texel mais próximo de qualquer maneira, sem que façamos algo especial.
Mas uma incompatibilidade de escala significa que esse encaixe nem sempre é na mesma direção. Em um ponto, um pixel olhando para o texel mais próximo escolherá um ligeiramente para a direita, enquanto outro pixel escolherá um para a esquerda - e agora uma coluna de texels foi omitida ou duplicada no meio, criando uma ondulação ou brilho que se move sobre o sprite enquanto viaja pela tela.
Aqui está um olhar mais atento. Animai um sprite translúcido de cogumelo movendo-se suavemente, como se pudéssemos renderizá-lo com resolução ilimitada de sub-pixels. Sobreponha uma grade de pixels usando a amostragem do vizinho mais próximo. O pixel inteiro muda de cor para corresponder à parte do sprite sob o ponto de amostragem (o ponto no centro).
Escala 1: 1
Mesmo que o sprite se mova suavemente para as coordenadas de sub-pixel, sua imagem renderizada ainda se encaixa corretamente toda vez que ele percorre um pixel inteiro. Não precisamos fazer nada de especial para fazer isso funcionar.
1: 1.0625 Scaling
Neste exemplo, o sprite de cogumelo texel de 16x16 foi dimensionado para 17x17 pixels da tela e, portanto, o encaixe acontece de maneira diferente de uma parte da imagem para outra, criando a ondulação ou onda que a estica e a comprime enquanto se move.
Portanto, o truque é ajustar o tamanho / campo de visão da câmera para que os textos de origem dos seus ativos sejam mapeados para um número inteiro de pixels da tela. Não precisa ser 1: 1 - qualquer número inteiro funciona muito bem:
Escala 1: 3
Você precisará fazer isso de maneira um pouco diferente, dependendo da resolução de seu objetivo - simplesmente aumentar o tamanho para caber na janela quase sempre resultará em uma escala fracionária. Alguma quantidade de preenchimento nas bordas da tela pode ser necessária para determinadas resoluções.
fonte
mapSprite
é 100x100, OplayerSprite
é definido como um tamanho de 1x1 e a janela de exibição é definida como um tamanho de 32x20. Mesmo mudar tudo para múltiplos de 16 não parece resolver o problema. Alguma ideia?window size
é 1000x1000 (pixels),WORLD_WIDTH
xWORLD_HEIGHT
é 100x100,FillViewport
é 10x10,playerSprite
é 1x1. Infelizmente, o problema ainda persiste.aqui uma pequena lista de verificação:
E apenas mude a posição atual da câmera se a câmera se mover.
Se a posição da câmera de mira for algo diferente de transform.position - defina transform.position (da sua câmera) para "mire a posição da câmera".
isso deve resolver seu problema. Se não: me diga. No entanto, você deve considerar fazer essas coisas também:
defina "projeção da câmera" como "ortográfica" (para o seu problema em Y que aumenta anormalmente) (a partir de agora, você só deve ampliar com o "Campo de visão")
defina a rotação da câmera em passos de 90 ° (0 ° ou 90 ° ou 180 °)
não gere mipmaps se você não souber se deve.
use tamanho suficiente para seus sprites e não use filtro bilinear ou trilinear
Se 1 unidade não é uma unidade de pixels, talvez sua resolução de tela seja dependente. Você tem acesso ao campo de visão? Dependendo do seu campo de visão: Descubra quanta resolução é 1 unidade.
^ E agora se onePixel (largura) e onePixel (altura) não são 1.0000f, mova-se nessas unidades para a esquerda e a direita com sua câmera.
fonte
OrthographicCamera
para que esperemos que funcione assim. (Você está usando o Unity, certo?) 3-5) Eu já faço isso no meu jogo.Não estou familiarizado com a libgdx, mas tenho problemas semelhantes em outros lugares. Eu acho que o problema está no fato de você estar usando o lerp e potencialmente erro em valores de ponto flutuante. Acho que uma possível solução para o seu problema é criar seu próprio objeto de localização da câmera. Use esse objeto para o seu código de movimento, atualize-o de acordo e defina a posição da câmera para o valor desejado (número inteiro?) Mais próximo do seu objeto de localização.
fonte
y
aumentar anormalmente) também estava presente antes de eu estar usandolerp
. Na verdade, eu o adicionei ontem para que as pessoas pudessem ver o outro problema, onde o tamanho dos sprites não permanece constante quando a câmera está se aproximando.