Grundsätzlich möchte ich verhindern, dass sich die Kamera in Subpixeln bewegt, da dies meiner Meinung nach dazu führt, dass Sprites ihre Abmessungen sichtbar ändern, wenn auch nur geringfügig. (Gibt es einen besseren Begriff dafür?) Beachten Sie, dass dies ein Pixel-Art-Spiel ist, bei dem ich gestochen scharfe Pixelgrafiken haben möchte. Hier ist ein GIF, das das Problem zeigt:
Ich habe Folgendes versucht: Bewege die Kamera, projiziere die aktuelle Position (also die Bildschirmkoordinaten) und runde sie dann oder wirke sie auf int. Danach wandle es wieder in Weltkoordinaten um und verwende diese als neue Kameraposition. Soweit ich weiß, sollte dies die Kamera an die tatsächlichen Bildschirmkoordinaten und nicht an Bruchteile davon binden.
Aus irgendeinem Grund y
explodiert jedoch der Wert der neuen Position. In wenigen Sekunden steigt es auf so etwas wie 334756315000
.
Hier ist eine SSCCE (oder eine MCVE), die auf dem Code im LibGDX-Wiki basiert :
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());
}
}
und hier ist dassc_map.jpg
und dasdungeon_guy.png
Ich würde auch gerne mehr über einfachere und / oder bessere Möglichkeiten zur Behebung dieses Problems erfahren.
Antworten:
Ihr Problem besteht darin, die Kamera nicht in Vollpixelschritten zu bewegen. Es ist so, dass Ihr Texel-zu-Pixel-Verhältnis nicht ganzzahlig ist . Ich werde einige Beispiele aus dieser ähnlichen Frage ausleihen, die ich auf StackExchange beantwortet habe .
Hier sind zwei Kopien von Mario - beide bewegen sich mit der gleichen Geschwindigkeit über den Bildschirm (entweder durch die Sprites, die sich nach rechts in der Welt bewegen, oder durch die Kamera, die sich nach links bewegt - es endet gleichwertig), aber nur die obere zeigt diese plätschernden Artefakte und unten tut man nicht:
Der Grund dafür ist, dass ich den oberen Mario leicht um den Faktor 1,01 skaliert habe - dieser winzige Bruchteil bedeutet, dass er nicht mehr mit dem Pixelraster des Bildschirms übereinstimmt.
Eine kleine Übersetzung ist kein Problem - die "Nearest" -Texturfilterung wird ohnehin auf das nächstgelegene Texel ausgerichtet, ohne dass wir etwas Besonderes tun.
Aber eine Skaleninkongruenz bedeutet, dass dieses Einrasten nicht immer in die gleiche Richtung geht. An einer Stelle wählt ein Pixel, das auf das nächste Texel schaut, ein Pixel leicht nach rechts aus, während ein anderes Pixel ein Pixel leicht nach links auswählt - und jetzt wurde eine Texelspalte entweder weggelassen oder dazwischen dupliziert, wodurch eine Kräuselung oder ein Schimmer erzeugt wird Bewegt sich über das Sprite, während es sich über den Bildschirm bewegt.
Hier ist ein genauerer Blick. Ich habe ein durchscheinendes Pilz-Sprite animiert, das sich reibungslos bewegt, als könnten wir es mit unbegrenzter Subpixel-Auflösung rendern. Dann habe ich ein Pixelraster mit dem nächsten Nachbarn überlagert. Das gesamte Pixel ändert die Farbe, um dem Teil des Sprites unter dem Abtastpunkt (dem Punkt in der Mitte) zu entsprechen.
1: 1-Skalierung
Auch wenn sich das Sprite reibungslos in Subpixel-Koordinaten bewegt, wird das gerenderte Bild bei jeder Bewegung um ein ganzes Pixel korrekt gefangen. Wir müssen nichts Besonderes tun, damit dies funktioniert.
1: 1.0625 Skalierung
In diesem Beispiel wurde das 16 x 16-Texel-Pilz-Sprite auf 17 x 17 Bildschirmpixel skaliert, sodass das Einrasten von einem Teil des Bildes zum anderen unterschiedlich verläuft und die Welligkeit oder Welle erzeugt, die es bei seiner Bewegung dehnt und zusammenpresst.
Der Trick besteht also darin, die Größe / das Sichtfeld Ihrer Kamera so anzupassen, dass die Quelltexte Ihrer Assets einer ganzzahligen Anzahl von Bildschirmpixeln zugeordnet werden. Es muss nicht 1: 1 sein - jede ganze Zahl funktioniert hervorragend:
1: 3-Skalierung
Je nach Auflösung des Ziels müssen Sie dies etwas anders machen. Wenn Sie es einfach an das Fenster anpassen, erhalten Sie fast immer eine gebrochene Skalierung. Für bestimmte Auflösungen ist möglicherweise etwas Abstand an den Bildschirmrändern erforderlich.
quelle
mapSprite
ist 100x100, DieplayerSprite
ist auf eine Größe von 1x1 eingestellt und das Ansichtsfenster ist auf eine Größe von 32x20 eingestellt. Selbst wenn alles auf ein Vielfaches von 16 geändert wird, scheint das Problem nicht gelöst zu sein. Irgendwelche Ideen?window size
ist 1000x1000 (Pixel),WORLD_WIDTH
xWORLD_HEIGHT
ist 100x100,FillViewport
ist 10x10,playerSprite
ist 1x1. Leider besteht das Problem immer noch.hier eine kleine Checkliste:
Und ändern Sie die aktuelle Kameraposition nur, wenn sich die Kamera bewegen soll.
Wenn die Zielkameraposition etwas anderes als transform.position ist, stellen Sie transform.position (Ihrer Kamera) auf "Zielkameraposition".
Dies sollte Ihr Problem lösen. Wenn nicht: sag es mir. Sie sollten jedoch in Betracht ziehen, auch diese Dinge zu tun:
setze "camera projection" auf "ortographic" (für dein abnormal ansteigendes y-problem) (ab jetzt solltest du nur noch mit dem "field of view" zoomen)
Kameradrehung in 90 ° - Schritten einstellen (0 ° oder 90 ° oder 180 °)
Generieren Sie keine Mipmaps, wenn Sie nicht wissen, ob Sie sollten.
Verwenden Sie genügend Größe für Ihre Sprites und verwenden Sie keine bilinearen oder trilinearen Filter
Wenn 1 Einheit keine Pixeleinheit ist, hängt sie möglicherweise von der Bildschirmauflösung ab. Haben Sie Zugang zum Sichtfeld? Abhängig von Ihrem Sichtfeld: Finden Sie heraus, wie hoch die Auflösung 1 Einheit ist.
^ Wenn onePixel (width) und onePixel (height) nicht 1.0000f sind, bewegen Sie diese Einheiten mit Ihrer Kamera nach links und rechts.
quelle
OrthographicCamera
damit es hoffentlich so funktioniert. (Du benutzt Unity, richtig?) 3-5) Ich mache die bereits in meinem Spiel.Ich bin nicht mit libgdx vertraut, habe aber an anderer Stelle ähnliche Probleme. Ich denke, das Problem liegt in der Tatsache, dass Sie Lerp und möglicherweise Fehler in Gleitkommawerten verwenden. Ich denke, eine mögliche Lösung für Ihr Problem besteht darin, ein eigenes Kameraobjekt zu erstellen. Verwenden Sie dieses Objekt für Ihren Bewegungscode und aktualisieren Sie es entsprechend. Stellen Sie dann die Position der Kamera auf den nächsten gewünschten Wert (Ganzzahl?) Ihres Standortobjekts ein.
quelle
y
abnormalen Zunahme) war jedoch auch vorhanden, bevor ich es verwendetelerp
. Ich habe es gestern hinzugefügt, damit die Leute auch das andere Problem sehen können, bei dem die Größe der Sprites nicht konstant bleibt, wenn sich die Kamera nähert.