Jak radzić sobie z różnymi współczynnikami kształtu w libGDX?

81

Zaimplementowałem kilka ekranów używając libGDX, które oczywiście korzystałyby z Screenklasy dostarczonej przez framework libGDX. Jednak implementacja dla tych ekranów działa tylko ze wstępnie zdefiniowanymi rozmiarami ekranu. Na przykład, jeśli duszek był przeznaczony dla ekranu o rozmiarze 640 x 480 (współczynnik proporcji 4: 3), nie będzie działał zgodnie z przeznaczeniem na innych rozmiarach ekranu, ponieważ sprite dopasowuje się do granic ekranu i nie są skalowane do rozmiaru ekranu w ogóle. Co więcej, gdyby libGDX zapewniało proste skalowanie, problem, z którym się zmagam, nadal istniałby, ponieważ spowodowałoby to zmianę proporcji ekranu gry.

Po poszukaniu informacji w Internecie trafiłem na blog / forum , na którym omawiano ten sam problem. Zaimplementowałem to i na razie działa dobrze. Ale chcę potwierdzić, czy jest to najlepsza opcja, aby to osiągnąć, czy też istnieją lepsze alternatywy. Poniżej znajduje się kod pokazujący, jak radzę sobie z tym uzasadnionym problemem.

LINK FORUM: http://www.java-gaming.org/index.php?topic=25685.new

public class SplashScreen implements Screen {

    // Aspect Ratio maintenance
    private static final int VIRTUAL_WIDTH = 640;
    private static final int VIRTUAL_HEIGHT = 480;
    private static final float ASPECT_RATIO = (float) VIRTUAL_WIDTH / (float) VIRTUAL_HEIGHT;

    private Camera camera;
    private Rectangle viewport;
    // ------end------

    MainGame TempMainGame;

    public Texture splashScreen;
    public TextureRegion splashScreenRegion;
    public SpriteBatch splashScreenSprite;

    public SplashScreen(MainGame maingame) {
        TempMainGame = maingame;
    }

    @Override
    public void dispose() {
        splashScreenSprite.dispose();
        splashScreen.dispose();
    }

    @Override
    public void render(float arg0) {
        //----Aspect Ratio maintenance

        // update camera
        camera.update();
        camera.apply(Gdx.gl10);

        // set viewport
        Gdx.gl.glViewport((int) viewport.x, (int) viewport.y,
        (int) viewport.width, (int) viewport.height);

        // clear previous frame
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

        // DRAW EVERYTHING
        //--maintenance end--

        splashScreenSprite.begin();
        splashScreenSprite.disableBlending();
        splashScreenSprite.draw(splashScreenRegion, 0, 0);
        splashScreenSprite.end();
    }

    @Override
    public void resize(int width, int height) {
        //--Aspect Ratio Maintenance--
        // calculate new viewport
        float aspectRatio = (float)width/(float)height;
        float scale = 1f;
        Vector2 crop = new Vector2(0f, 0f);

        if(aspectRatio > ASPECT_RATIO) {
            scale = (float) height / (float) VIRTUAL_HEIGHT;
            crop.x = (width - VIRTUAL_WIDTH * scale) / 2f;
        } else if(aspectRatio < ASPECT_RATIO) {
            scale = (float) width / (float) VIRTUAL_WIDTH;
            crop.y = (height - VIRTUAL_HEIGHT * scale) / 2f;
        } else {
            scale = (float) width / (float) VIRTUAL_WIDTH;
        }

        float w = (float) VIRTUAL_WIDTH * scale;
        float h = (float) VIRTUAL_HEIGHT * scale;
        viewport = new Rectangle(crop.x, crop.y, w, h);
        //Maintenance ends here--
    }

    @Override
    public void show() {
        camera = new OrthographicCamera(VIRTUAL_WIDTH, VIRTUAL_HEIGHT); //Aspect Ratio Maintenance

        splashScreen = new Texture(Gdx.files.internal("images/splashScreen.png"));
        splashScreenRegion = new TextureRegion(splashScreen, 0, 0, 640, 480);
        splashScreenSprite = new SpriteBatch();

        if(Assets.load()) {
            this.dispose();
            TempMainGame.setScreen(TempMainGame.mainmenu);
        }
    }
}

AKTUALIZACJA: Niedawno dowiedziałem się, że libGDX ma swoją własną funkcjonalność do utrzymywania współczynników proporcji, które chciałbym tutaj omówić. Podczas wyszukiwania informacji o problemie ze współczynnikiem proporcji w Internecie natknąłem się na kilka forów / programistów, którzy mieli problem z pytaniem „Jak zachować proporcje na różnych rozmiarach ekranu?” Jedno z rozwiązań, które naprawdę mi się sprawdziło, zostało zamieszczone powyżej.

Później, kiedy przystąpiłem do implementacji touchDown()metod dla ekranu, stwierdziłem, że ze względu na skalowanie przy touchDown()zmianie rozmiaru współrzędne, na których zaimplementowałem , zmieniłyby się znacznie. Po przepracowaniu jakiegoś kodu w celu przetłumaczenia współrzędnych zgodnie ze zmianą rozmiaru ekranu, zmniejszyłem tę kwotę w dużym stopniu, ale nie udało mi się zachować ich z dokładnością do punktu. Na przykład, gdybym zaimplementował touchDown()teksturę, zmiana rozmiaru ekranu spowodowałaby przesunięcie narzędzia touchListener w obszarze tekstury o kilka pikseli w prawo lub w lewo, w zależności od zmiany rozmiaru i było to oczywiście niepożądane.

Później dowiedziałem się, że klasa stage ma własną natywną funkcjonalność, aby utrzymać współczynnik kształtu ( boolean stretch = false). Teraz, gdy zaimplementowałem mój ekran przy użyciu klasy stage, współczynnik proporcji jest przez nią dobrze utrzymywany. Jednak w przypadku zmiany rozmiaru lub innych rozmiarów ekranu, generowany czarny obszar zawsze pojawia się po prawej stronie ekranu; to znaczy ekran nie jest wyśrodkowany, co sprawia, że ​​jest dość brzydki, jeśli czarny obszar jest znacznie duży.

Czy jakikolwiek członek społeczności może mi pomóc rozwiązać ten problem?

Rafay
źródło
Czy możesz podać link do bloga lub forum, na którym występuje ten sam problem?
Steve Blackwell
@SteveBlackwell tutaj jest link: java-gaming.org/index.php?topic=25685.new
Rafay,
@SteveBlackwell Zobacz zaktualizowane pytanie i sprawdź, czy możesz w tym pomóc.
Rafay
1
Nie wiem zbyt wiele o scenie, ale patrząc tutaj , wydaje się, że powinno to zostać naprawione. W docs nie są zbyt wiele pomocy do centrowania. Może mógłbyś trochę przesunąć kamerę lub dostosować rzutnię.
Steve Blackwell
1
Cóż, mój problem został rozwiązany. Publikuję to jako odpowiedź.
Rafay

Odpowiedzi:

51

Jak to się teraz robi:

Ponieważ jest to jedno z najbardziej znanych pytań dotyczących libgdx, podam mu małą aktualizację :

LibGDX v1.0 wprowadzono w Viewportcelu rozwiązania tego problemu. Jest o wiele łatwiejszy w użyciu, a strategia skalowania jest podłączana, co oznacza, że ​​pojedyncza linia może zmienić zachowanie i możesz z nią grać i zobaczyć, która z nich najlepiej pasuje do Twojej gry.

Wszystko, co musisz o tym wiedzieć, znajdziesz tutaj .

nikt
źródło
Próbowałem, ale nadal moje TextureRegionStreches. Jeśli pozostaw reszie(int width, int height)funkcję pustą. THe TextureRegionnie strectch.
Muhammad Babar
Ale nawet kiedy używamy Viewports, musimy używać oddzielnych tekstur dla różnych proporcji, prawda? Czy istnieje logika, aby uprościć tę metodę? A może projekt graficzny powinien odbywać się z zachowaniem niezależności od rozdzielczości?
WeirdElfB0y
Dobry samouczek dotyczący korzystania z Viewports: gamefromscratch.com/post/2014/12/09/...
ubzack
Dodając do tej odpowiedzi i odpowiadając na pytania w komentarzach, możesz narysować swój świat na 16: 9 i nadać swojej teksturze większą wysokość, aby móc załadować się w 4: 3. Następnie, podczas tworzenia instancji rzutni, możesz sprawdzić faktyczny współczynnik proporcji ekranu i zachowując standard szerokości, możesz nadać rzutni wysokość, która będzie pasować do używanego współczynnika proporcji. Wadą jest to, że będziesz mieć dużo "pustego" miejsca w rozdzielczości 4: 3, ale jeśli twoja tekstura to poradzi, wszystko będzie wyświetlane jako narysowane i
nierozciągnięte
24

EDYCJA: libGDX ewoluowało. Najlepszą odpowiedzią jest teraz ta przez użytkownika. Koniecznie sprawdź link do Viewportdokumentacji. .

Kiedy SteveBlack zamieścił link do problemu, który został zgłoszony na zajęciach scenicznych, poszedłem tam tylko po to, aby odkryć, że problem (to nie był w rzeczywistości mój problem) został rozwiązany w najnowszych nocnych nocach.

Po przeszukaniu w Internecie problemu, który miałem, nie mogłem znaleźć żadnych rozwiązań, więc zdecydowałem się skontaktować bezpośrednio z osobą, która zgłosiła błąd. Następnie odpowiedział mi na forach libgdx i jestem mu wdzięczny za pomoc. Tutaj jest link

To był pojedynczy wiersz kodu i wszystko, co musisz zrobić, to:

W metodzie resize ():

stage.setViewport(640, 480, false);
stage.getCamera().position.set(640/2, 480/2, 0);

Gdzie 640 X 480 to rozdzielczość Twojego TextureRegion, która opisuje zamierzony współczynnik proporcji. Jeśli twój rozmiar TextureRegion wynosił 320 x 240, to oba argumenty powinny zostać zmienione na nową rozdzielczość, aby załatwić sprawę.

Link do profilu oryginalnej osoby, która faktycznie rozwiązała mój problem

Rafay
źródło
6
W libGDX 0.9.6 wystarczy stage.setViewport (640, 480, false), ponieważ zawiera już wywołanie stage.getCamera () .position.set (640/2, 480/2, 0);
Alexis Pautrot
1
setViewportz 3 parametrami jest teraz niedostępny!
Muhammad Babar
Zmieniłem zaakceptowaną odpowiedź na opublikowaną przez nikogo, na co zwrócił uwagę Steve Blackwell, ponieważ wydaje się, że jest najnowsza i poprawna.
Rafay
7

Czarne pasy po lewej / prawej lub u góry / u dołu wyglądają lepiej niż zniekształcanie całej sceny w celu dopasowania do ekranu. Jeśli celujesz w współczynnik proporcji, który znajduje się pośrodku możliwych zakresów (4: 3 to prawdopodobnie najniższy koniec, 16: 9 to prawdopodobnie wysoki koniec), paski powinny pozostać małe dla większości urządzeń. Dzięki temu możesz używać większości ekranu nawet na większych ekranach, a masz już kod tego faceta, więc jest to całkiem proste. Byłoby jeszcze lepiej, gdyby była to tylko opcja wbudowana w libgdx.

Ale myślę, że najlepszym podejściem jest użycie całego ekranu. Nie zrobiłem tego jeszcze, ale takie podejście przyjmuję przy następnym projekcie. Chodzi o to, że jeśli ktoś ma szerszy ekran, to powinien widzieć więcej po bokach. Chris Pruett mówi o tym, jak to zrobić w jednym ze swoich wykładów ( link do miejsca w rozmowie - w rzeczywistości całość jest całkiem niezła). Chodzi o to, aby przeskalować wysokość, a następnie ustawić rzutnię wystarczająco szeroką, aby pasowała do ekranu. OpenGL powinien zająć się wyświetleniem reszty. Sposób, w jaki to robi, jest tutaj .

W przypadku libgdx może istnieje łatwy sposób na zrobienie tego ze sceną2d, przesuwając kamerę po scenie, ale tak naprawdę nigdy nie pracowałem ze sceną2d. W każdym razie uruchomienie aplikacji jako natywnego okna o zmiennym rozmiarze jest znacznie łatwiejszym sposobem przetestowania wielu rozmiarów ekranu niż utworzenie zestawu plików AVD.

Steve Blackwell
źródło
„W każdym razie uruchomienie aplikacji jako natywnego okna z możliwością zmiany rozmiaru jest znacznie łatwiejszym sposobem na przetestowanie wielu rozmiarów ekranu niż utworzenie zestawu plików AVD”. Dobrze powiedziane. w rzeczywistości próbuję pracować nad tym zjawiskiem, aby zapewnić maksymalną zgodność, zamiast kierować reklamy na różne rozmiary ekranu z różnymi rozmiarami tekstur.
Rafay
3

ResolutionFileResolver klasa pozwala rozwiązać nazwy pliku do najlepszej rozdzielczości. Uważam, że będzie to działać również z różnymi współczynnikami kształtu, pod warunkiem, że stworzyłeś sprite'y dla tych współczynników. Istnieje przykład użycia w AssetManagerTest .

Doran
źródło
0

Używam tej metody z http://blog.acamara.es/2012/02/05/keep-screen-aspect-ratio-with-different-resolutions-using-libgdx/

Stworzyłem tę funkcję, która zwraca dotknięty punkt na ekranie, przeskalowany w dół do żądanej pozycji gry.

public Vector2 getScaledPos(float x, float y) {
    float yR = viewport.height / (y - viewport.y);
    y = CAMERA_HEIGHT / yR;

    float xR = viewport.width / (x - viewport.x);
    x = CAMERA_WIDTH / xR;

    return new Vector2(x, CAMERA_HEIGHT - y);
}

Użyj tego w touchdown / up / drag i możesz sprawdzić dotyk w swojej grze.

Boldijar Paul
źródło
Wydaje się to podobne do metody nieprojektowej aparatu. Czy robi to samo?
milosmns
@milosmns to jest sposób, w jaki robiłem to rok temu, dobrze, że odkryłem nieprojektową rzecz .. jest dobrze, gdy twój aparat jest zawsze w tej samej pozycji ..
Boldijar Paul
0

Ten kod działa dla mnie z najnowszą aktualizacją: * OrthographicCamera to cam, to nie powoduje przycinania, po prostu zmienia widok, więc szerokość jest nadal „tyle” razy większa niż rzeczywiste okno / urządzenie

public void resize(int width, int height) {
    int newW = width, newH = height;
    if (cam.viewportWidth > width) {
        float scale = (float) cam.viewportWidth / (float) width;
        newW *= scale;
        newH *= scale;
    }

    // true here to flip the Y-axis
    cam.setToOrtho(true, newW, newH);
}
milosmns
źródło