Zasadniczo chcę powstrzymać kamerę przed poruszaniem się w subpikselach, ponieważ myślę, że prowadzi to do duszków, które choć nieznacznie zmieniają wymiary. (Czy istnieje na to lepszy termin?) Zauważ, że jest to gra pikselowa, w której chcę mieć wyraźną grafikę pikselową. Oto gif, który pokazuje problem:
Teraz próbowałem: Przesunąć kamerę, rzutować aktualną pozycję (więc współrzędne ekranu), a następnie zaokrąglić lub rzutować na int. Następnie przekonwertuj go z powrotem na współrzędne świata i użyj go jako nowej pozycji kamery. O ile wiem, powinno to zablokować kamerę do rzeczywistych współrzędnych ekranu, a nie ich ułamków.
Jednak z jakiegoś powodu y
wartość nowej pozycji po prostu eksploduje. W ciągu kilku sekund staje się podobny do 334756315000
.
Oto SSCCE (lub MCVE) oparty na kodzie w wiki 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());
}
}
a oto isc_map.jpg
dungeon_guy.png
Chciałbym również dowiedzieć się o prostszych i / lub lepszych sposobach rozwiązania tego problemu.
Odpowiedzi:
Twój problem polega na tym, że aparat nie porusza się w pełnych pikselach. Chodzi o to, że stosunek tekstu do piksela jest nieco inny niż całkowity . Pożyczę kilka przykładów z tego podobnego pytania, na które odpowiedziałem na StackExchange .
Oto dwie kopie Mario - obie poruszają się po ekranie w tym samym tempie (albo przez duszki poruszające się w prawo na świecie, albo przez kamerę poruszającą się w lewo - kończy się to tak samo), ale tylko górna pokazuje te falujące artefakty i dolna nie:
Powodem jest to, że nieznacznie przeskalowałem górnego Mario o 1 współczynnik 1,01x - ten bardzo mały ułamek oznacza, że nie jest już w linii z siatką pikseli ekranu.
Niewielkie przesunięcie translacji nie stanowi problemu - filtrowanie tekstur „Najbliższe” i tak przyciąga się do najbliższego texla bez żadnego specjalnego działania.
Jednak niedopasowanie skali oznacza, że przyciąganie nie zawsze jest w tym samym kierunku. W jednym miejscu piksel spoglądający w górę na najbliższy texel przesunie go nieco w prawo, podczas gdy inny piksel przesuwa jeden nieco w lewo - a teraz kolumna tekstur została albo pominięta, albo zduplikowana pomiędzy nimi, tworząc falowanie lub migotanie, które przesuwa się nad duszkiem poruszającym się po ekranie.
Oto bliższe spojrzenie. Animowałem półprzezroczystego duszka grzybowego poruszającego się płynnie, jakbyśmy mogli go renderować z nieograniczoną rozdzielczością subpikseli. Następnie nałożyłem siatkę pikseli za pomocą próbkowania najbliższego sąsiada. Cały piksel zmienia kolor, aby dopasować część duszka pod punktem próbkowania (kropka na środku).
Skalowanie 1: 1
Mimo, że duszek przesuwa się płynnie do współrzędnych subpikseli, jego renderowany obraz wciąż przyciąga się poprawnie za każdym razem, gdy przemieści pełny piksel. Nie musimy robić nic specjalnego, aby to zadziałało.
Skalowanie 1: 1,0625
W tym przykładzie, duszek grzybowy 16x16 texel został przeskalowany do pikseli ekranowych 17x17, więc przyciąganie odbywa się inaczej w zależności od części obrazu, tworząc falę lub falę, która rozciąga ją i zgniata podczas ruchu.
Sztuczka polega więc na dostosowaniu rozmiaru / pola widzenia kamery, tak aby źródłowe tekstury zasobów były mapowane na liczbę całkowitą pikseli ekranu. Nie musi to być 1: 1 - każda liczba całkowita działa świetnie:
Skalowanie 1: 3
Musisz to zrobić nieco inaczej w zależności od docelowej rozdzielczości - po prostu skalowanie w celu dopasowania do okna prawie zawsze spowoduje ułamek skali. W przypadku niektórych rozdzielczości może być wymagana pewna ilość wypełnienia na krawędziach ekranu.
źródło
mapSprite
to 100x100, TheplayerSprite
jest ustawiony na rozmiar 1x1, a rzutnia jest ustawiona na rozmiar 32x20. Nawet zmiana wszystkiego na wielokrotności 16 nie wydaje się rozwiązać problemu. Jakieś pomysły?window size
to 1000x1000 (piksele),WORLD_WIDTH
xWORLD_HEIGHT
to 100x100,FillViewport
to 10x10,playerSprite
to 1x1. Niestety problem nadal występuje.tutaj mała lista kontrolna:
Zmieniaj tylko bieżącą pozycję kamery, jeśli kamera się poruszy.
Jeśli pozycja kamery celu jest czymś innym niż transform.position - ustaw transform.position (twojej kamery) na „celuj pozycją kamery”.
to powinno rozwiązać twój problem. Jeśli nie: powiedz mi. Jednak powinieneś również rozważyć zrobienie tych rzeczy:
ustaw „projekcję kamery” na „ortograficzną” (w przypadku nienormalnie rosnącego problemu y) (odtąd powinieneś powiększać tylko za pomocą „pola widzenia”)
ustawić obrót kamery o 90 ° - w krokach (0 ° lub 90 ° lub 180 °)
nie generuj mipmap, jeśli nie wiesz, czy powinieneś.
użyj wystarczającej wielkości dla swoich duszków i nie używaj dwuliniowego lub trójliniowego filtra
Jeśli 1 jednostka nie jest jednostką pikseli, być może zależy to od rozdzielczości ekranu. Czy masz dostęp do pola widzenia? W zależności od pola widzenia: dowiedz się, ile rozdzielczości wynosi 1 jednostka.
^ A teraz, jeśli onePixel (szerokość) i onePixel (wysokość) nie mają wartości 1,0000f, po prostu przesuń te jednostki w lewo i prawo za pomocą aparatu.
źródło
OrthographicCamera
więc mam nadzieję, że tak powinno działać. (Używasz Unity, prawda?) 3-5) Już to robię w mojej grze.Nie znam libgdx, ale mam podobne problemy w innych miejscach. Myślę, że problem polega na tym, że używasz lerp i potencjalnie błąd w wartościach zmiennoprzecinkowych. Myślę, że możliwym rozwiązaniem twojego problemu jest stworzenie własnego obiektu lokalizacji kamery. Użyj tego obiektu do kodu ruchu i odpowiednio go zaktualizuj, a następnie ustaw pozycję kamery na najbliższą poszukiwaną (liczbę całkowitą?) Obiektu lokalizacji.
źródło
y
nieprawidłowy wzrost) występował również przed użyciemlerp
. I rzeczywiście dodaje go wczoraj więc ludzie mogli również zobaczyć inny problem, gdzie wielkość duchy nie pozostać stałą, gdy kamera zbliża.