Kamerayı tam piksel aralıklarında nasıl hareket ettirebilirim?


13

Temelde kameranın alt piksellerde hareket etmesini durdurmak istiyorum, çünkü bu çok az olsa bile boyutlarını gözle görülür şekilde değiştiren spritelara yol açıyor. (Bunun için daha iyi bir terim var mı?) Bunun net pikselli grafiklere sahip olmak istediğim bir piksel sanat oyunu olduğunu unutmayın. İşte sorunu gösteren bir gif:

resim açıklamasını buraya girin

Şimdi denediğim şey şuydu: Kamerayı hareket ettirin, geçerli konumu yansıtın (böylece ekran koordinatlarıdır) ve sonra yuvarlak veya int'ye çevirin. Bundan sonra tekrar dünya koordinatlarına dönüştürün ve bunu yeni kamera konumu olarak kullanın. Bildiğim kadarıyla, bu kamerayı kesirlerine değil gerçek ekran koordinatlarına kilitlemelidir.

Bununla birlikte, bazı nedenlerden dolayı, yyeni pozisyonun değeri patlamaktadır. Saniyeler içinde böyle bir şeye yükselir 334756315000.

LibGDX wiki'sindeki koda dayalı bir SSCCE (veya bir MCVE) :

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());
    }
}

ve burada vesc_map.jpgdungeon_guy.png

Ayrıca, bu sorunu çözmenin daha basit ve / veya daha iyi yolları hakkında bilgi edinmek istiyorum.


"Bunun için daha iyi bir terim var mı?" Aliasing? Bu durumda, Çok örnekli kenar yumuşatma (MSAA) alternatif bir çözüm olabilir mi?
Yousef Amar

@Paraknight Üzgünüm, yumuşatılmış grafiklere ihtiyaç duyduğum ve kenar yumuşatmanın işe yaramayacağı (AFAIK) pikselli bir oyun üzerinde çalıştığımı belirtmeyi unuttum. Bu bilgiyi soruya ekledim. Yine de teşekkürler.
Joschua

Daha yüksek çözünürlükte çalışmak için bir neden var mı, yoksa 1 piksel çözünürlükte çalışmak ve nihai sonucu yükseltmek mi?
Felsir

@Felsir Evet, ekran pikselleri içinde hareket ediyorum, böylece oyun mümkün olduğunca pürüzsüz hissediyor. Bütün bir doku pikseli taşımak çok gergin görünüyor.
Joschua

Yanıtlar:


22

Sorununuz kamerayı tam piksel artışlarla taşımak değil. Texel-pixel oranınız biraz tamsayı değildir . StackExchange'te cevapladığım bu benzer sorudan bazı örnekler ödünç alacağım .

İşte Mario'nun iki kopyası - her ikisi de ekran boyunca aynı oranda hareket ediyor (ya dünyaya doğru hareket eden sprite'lar ya da sola hareket eden kamera tarafından eşdeğer çıkıyor), ancak sadece en üstteki bu dalgalanan eserleri ve altta yapmaz:

İki Mario sprite

Bunun nedeni, en iyi Mario'yu 1.01x'lik 1 faktörle hafifçe ölçeklememdi - bu ekstra küçük bölüm artık ekranın piksel ızgarasıyla aynı çizgide olmadığı anlamına geliyor.

Küçük bir çeviri bozukluğu sorun değil - "En yakın" doku filtrelemesi, özel bir şey yapmadan yine de en yakın texele yapışacaktır.

Ancak ölçek uyuşmazlığı, bu yakalamanın her zaman aynı yönde olmadığı anlamına gelir. Bir noktada, en yakın tekneyi arayan bir piksel hafifçe sağa doğru çekerken, başka bir piksel hafifçe sola alır - ve şimdi bir metin sütunu atlanmış veya çoğaltılmıştır, bu da bir dalgalanma veya ışıltı yaratır. ekranda ilerlerken hareketli grafik üzerinde hareket eder.

İşte daha yakından bakış. Sınırsız bir alt piksel çözünürlüğü ile render edebiliriz gibi, yarı saydam bir mantar sprite animasyonunu oynadım. Sonra en yakın komşu örnekleme kullanarak bir piksel ızgara overlaid. Pikselin tamamı, örnekleme noktasının (merkezdeki nokta) altındaki hareketli grafiğin kısmıyla eşleşecek şekilde renk değiştirir.

1: 1 Ölçeklendirme

Dalgalanma olmadan hareket eden düzgün ölçeklenmiş bir hareketli grafik

Hareketli grafik, alt piksel koordinatlarına sorunsuz bir şekilde hareket etse de, oluşturulan görüntü tam pikselde her seyahat ettiğinde yine de doğru şekilde yapışır. Bu işi yapmak için özel bir şey yapmamız gerekmiyor.

1: 1.0625 Ölçekleme

16x16 hareketli grafik, 17x17 ekran görüntüsünde sunuldu

Bu örnekte, 16x16 texel mantar sprite 17x17 ekran piksele kadar ölçeklendirilmiştir ve bu nedenle yakalama görüntünün bir bölümünden diğerine farklı bir şekilde gerçekleşir ve hareket ederken onu uzatan ve ezen dalgalanma veya dalga oluşturur.

Bu nedenle, püf noktası, kamera boyutunuzu / görüş alanınızı, varlıklarınızın kaynak metinlerinin tamsayı sayıda ekran pikseli ile eşleşecek şekilde ayarlamasıdır. 1: 1 olmak zorunda değil - herhangi bir sayı harika çalışıyor:

1: 3 Ölçeklendirme

Üçlü ölçekte yakınlaştırma

Hedef çözünürlüğünüze bağlı olarak bunu biraz farklı yapmanız gerekir - sadece pencereye sığacak şekilde ölçeklendirmek neredeyse her zaman kesirli bir ölçekle sonuçlanır. Belirli çözünürlükler için ekranın kenarlarında bir miktar dolgu gerekebilir.


1
Her şeyden önce, çok teşekkürler! Bu harika bir görselleştirme. Yalnız bunun için bir oy verin. Ne yazık ki sağlanan kodda tamsayı olmayan bir şey bulamıyorum. Boyutu mapSprite100x100, The playerSprite1x1 boyutunda ve görüntü alanı 32x20 boyutunda. Her şeyi 16'nın katlarıyla değiştirmek bile sorunu çözmüyor gibi görünüyor. Herhangi bir fikir?
Joschua

2
Her yerde tamsayılara sahip olmak ve yine de tamsayı olmayan bir oran elde etmek tamamen mümkündür. 17 ekran pikselinde görüntülenen 16 texnel sprite örneğini ele alalım. Her iki değer de tamsayıdır, ancak oranları değildir. Libgdx hakkında fazla bir şey bilmiyorum, ancak Unity'de dünya birimi başına kaç tane tek tek metin gösterdiğime (örneğin, hareketli grafiğin çözünürlüğünün hareketli grafiğin dünya boyutuna bölünmesiyle), kaç tane dünya birimini görüntülediğime bakardım penceresini (kameranın boyutunu kullanarak) ve penceremde kaç ekran pikseli olduğunu gösterir. Bu 3 değeri birbirine bağlayarak, belirli bir ekran penceresi boyutu için metin / piksel oranını hesaplayabiliriz.
DMGregory

"ve görünümünüz 32x20 boyutuna ayarlandı" - pencerenizin "istemci boyutu" (yenilenebilir alan) bunun bir tam sayı katı değilse, tahmin etmiyorum, görünüm alanınızdaki 1 birim tamsayı olmayacak piksel sayısı.
MaulingMonkey

Teşekkürler. Tekrar denedim. window size1000x1000 (piksel), WORLD_WIDTHx WORLD_HEIGHT100x100, FillViewport10x10, playerSprite1x1'dir. Ne yazık ki, sorun hala devam ediyor.
Joschua

Bu ayrıntılar bir yorum dizisinde sıralanmaya çalışmak için biraz fazla olacak. Bir Oyun Geliştirme Sohbet odası başlatmak mı yoksa bana Twitter'da @D_M_Gregory üzerinden mesaj göndermek ister misiniz?
DMGregory

1

burada küçük bir kontrol listesi:

  1. "Geçerli kamera konumunu" "hedef kamera konumu" ile ayırın. (bahsedilen ndenarodev gibi) Örneğin "mevcut kamera konumu" 1.1 ise - "kamera hedef konumu" 1.0 yapın

Geçerli kamera konumunu yalnızca kameranın hareket etmesi gerekiyorsa değiştirin.
Hedef kamera konumu transform.position'dan başka bir şeyse - transform.position (kameranızın) konumunu "kamera konumunu hedefleyin" olarak ayarlayın.

aim_camera_position = Mathf.Round(current_camera_position)

if (transform.position != aim_camera_position)
{
  transform.position = aim_camera_position;
}

bu probleminizi çözmeli. Değilse: söyle. Ancak bunları da yapmayı düşünmelisiniz:

  1. "kamera projeksiyonu" nu "ortografik" olarak ayarlayın (anormal şekilde artan y probleminiz için) (bundan böyle yalnızca "Görüş Alanı" ile zoom yapmalısınız)

  2. kamera dönüşünü 90 ° adımlarla ayarlayın (0 ° veya 90 ° veya 180 °)

  3. eğer bilmiyorsanız mipmap oluşturmayın.

  4. spritelarınız için yeterli boyutta kullanın ve bilinear veya trilinear filtre kullanmayın

  5. 5.

1 birim bir piksel birimi değilse, ekran çözünürlüğüne bağlı olabilir. Görüş alanına erişiminiz var mı? Görüş alanınıza bağlı olarak: 1 birim ne kadar çözünürlük olduğunu öğrenin.

float tan2Fov = tan(radians( Field of view / 2)); 
float xScrWidth = _CamNear*tan2Fov*_CamAspect * 2; 
float xScrHeight = _CamNear*tan2Fov * 2; 
float onePixel(width) = xWidth / screenResolutionX 
float onePixel(height) = yHeight / screenResolutionY 

^ Ve şimdi onePixel (genişlik) ve onePixel (yükseklik) 1.0000f değilse, bu birimleri kameranızla sola ve sağa hareket ettirin.


Hey! Cevabınız için teşekkürler. 1) Gerçek kamera konumunu ve yuvarlatılmış olanı zaten ayrı olarak saklıyorum. 2) LibGDX'leri kullanıyorum OrthographicCameraki umarım böyle çalışmalıdır. (Unity kullanıyorsunuz, değil mi?) 3-5) Bunları zaten oyunumda yapıyorum.
Joschua

Tamam-o zaman 1 pikselin kaç birim olduğunu bulmanız gerekiyor. ekran çözünürlüğüne bağlı olabilir. "5" i düzenledim. cevap yazımda. bunu dene.
OC_RaizW

0

Ben libgdx aşina değilim ama başka yerlerde benzer sorunları var. Bence problem, lerp ve potansiyel olarak kayan nokta değerlerinde hata kullanmanızdır. Sorununuzun olası bir çözümünün kendi kamera konum nesnenizi oluşturmak olduğunu düşünüyorum. Hareket kodunuz için bu nesneyi kullanın ve buna göre güncelleyin ve ardından kameranın konumunu konum nesnenizin en yakın istenen (tamsayı?) Değerine ayarlayın.


Her şeyden önce, anlayışınız için teşekkürler. Gerçekten kayan nokta hataları ile ilgili bir şey olabilir. Ancak yben kullanmadan önce de ( anormal olarak artma sorunu ) mevcuttu lerp. Aslında dün ekledim, böylece kamera yaklaşırken spriteların sabit kalmadığı diğer sorunu da görebiliyordum.
Joschua
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.