Wykonywanie efektu SNES Mode 7 (transformacja afiniczna) w grze pygame

19

Czy istnieje krótka odpowiedź na temat tego, jak zrobić efekt typu Kart 7 / Mario Kart w grze pygame?

Poszukałem obszernie, wszystkie dokumenty, które mogę wymyślić, to dziesiątki stron w innych językach (asm, c) z wieloma dziwnie wyglądającymi równaniami i tym podobne.

Idealnie chciałbym znaleźć coś wyjaśnionego bardziej w języku angielskim niż w kategoriach matematycznych.

Mogę używać PIL lub pygame do manipulowania obrazem / teksturą lub cokolwiek innego, co jest konieczne.

Naprawdę chciałbym osiągnąć efekt trybu 7 w grze pygame, ale wydaje mi się, że jestem bliski końca mojego dowcipu. Pomoc byłaby bardzo mile widziana. Wszelkie dostępne zasoby i wyjaśnienia byłyby fantastyczne, nawet jeśli nie są tak proste, jak chciałbym.

Jeśli uda mi się to rozgryźć, napiszę definitywne instrukcje dotyczące trybu 7 dla początkujących.

edycja: mode 7 doc: http://www.coranac.com/tonc/text/mode7.htm

2D_Guy
źródło
5
wydaje się, że istnieją tu równania: en.wikipedia.org/wiki/Mode_7 Chociaż obecnie mamy akcelerację 3D, takie rzeczy jak Tryb 7 lub niesamowity sposób, w jaki działało przeznaczenie, jest bardziej ciekawostką niż rozwiązaniem.
łosoś łosoś
3
@ 2D_Guy, ta strona bardzo dobrze wyjaśnia mi algorytm. Chcesz wiedzieć, jak to zrobić, czy chcesz, aby zostało to już zaimplementowane?
Gustavo Maciel
1
@stephelton W systemach SNES jedyną warstwą, która może być zniekształcona, obrócona .. (zastosowane transformacje afiniczne z macierzami) jest siódma warstwa. Warstwa tła. Wszystkie pozostałe warstwy były przyzwyczajone do prostych duszek, więc jeśli chcesz efektu 3D, musisz użyć tej warstwy, stąd pochodzi nazwa :)
Gustavo Maciel
3
@GustavoMaciel: To trochę niedokładne. SNES miał 8 różnych trybów (0-7), w których do 4 warstw tła miało różną funkcjonalność, ale tylko jeden tryb (tryb 7, stąd nazwa) obsługiwał obracanie i skalowanie (a także ograniczał cię do pojedynczej warstwy). Tak naprawdę nie można połączyć trybów.
Michael Madsen
1
@Michael: Dodałbym również: SNES był jedną z pierwszych popularnych konsol, które korzystały z tego efektu w latach 90-tych (z grą F-Zero), i dlatego ludzie zaczęli odnosić się do wszystkich poziomych efektów 2D odwzorowanych w płaszczyźnie tekstury widocznych w innych gry jako „tryb 7”. W rzeczywistości ten efekt nie był nowy i istniał dawno temu w salonach gier, por. Space Harrier / Hang-On (1985).
tigrou

Odpowiedzi:

45

Tryb 7 to bardzo prosty efekt. Wyświetla teksturę 2D (lub kafelki) 2D na pewnej podłodze / suficie. Stare SNES używają do tego sprzętu, ale współczesne komputery są tak potężne, że możesz to robić w czasie rzeczywistym (i jak już wspomniałeś, nie potrzebujesz ASM).

Podstawowa formuła matematyczna 3D do rzutowania punktu 3D (x, y, z) na punkt 2D (x, y) to:

x' = x / z;
y' = y / z; 

Kiedy się nad tym zastanowić, ma to sens. Obiekty znajdujące się w dużej odległości są mniejsze niż obiekty w pobliżu. Pomyśl o torach kolejowych zmierzających do nikąd:

wprowadź opis zdjęcia tutaj

Jeśli spojrzymy wstecz na wartości wejściowe formuły: xi ybędzie to bieżący piksel, który przetwarzamy, i zbędzie informacja o odległości, jak daleko jest punkt. Aby zrozumieć, co zpowinno być, spójrz na to zdjęcie, pokazuje zwartości dla powyższego obrazu:

wprowadź opis zdjęcia tutaj

fioletowy = bliski dystans, czerwony = daleki dystans

W tym przykładzie zwartość to y - horizon(zakładając, że (x:0, y:0)jest na środku ekranu)

Jeśli poskładamy wszystko razem, staje się: (pseudokod)

for (y = -yres/2 ; y < yres/2 ; y++)
  for (x = -xres/2 ; x < xres/2 ; x++)
  {
     horizon = 20; //adjust if needed
     fov = 200; 

     px = x;
     py = fov; 
     pz = y + horizon;      

     //projection 
     sx = px / pz;
     sy = py / pz; 

     scaling = 100; //adjust if needed, depends of texture size
     color = get2DTexture(sx * scaling, sy * scaling);  

     //put (color) at (x, y) on screen
     ...
  }

I ostatnia rzecz: jeśli chcesz stworzyć grę Mario Kart, przypuszczam, że chcesz również obrócić mapę. Cóż, jest to również bardzo łatwe: obracaj sxi syzanim uzyskasz wartość tekstury. Oto wzór:

  x' = x * cos(angle) - y * sin(angle);
  y' = x * sin(angle) + y * cos(angle);

a jeśli chcesz przejść przez mapę, po prostu dodaj przesunięcie, zanim uzyskasz wartość tekstury:

  get2DTexture(sx * scaling + xOffset, sy * scaling + yOffset);

UWAGA: przetestowałem algorytm (prawie kopiuj-wklej) i działa. Oto przykład: http://glslsandbox.com/e#26532.3 (wymaga najnowszej przeglądarki i włączonej WebGL)

wprowadź opis zdjęcia tutaj

UWAGA 2: używam prostej matematyki, ponieważ powiedziałeś, że chcesz czegoś prostego (i nie wydaje się, że znasz matematykę wektorową). Możesz osiągnąć te same rzeczy, używając formuły wikipedia lub samouczków, które dajesz. Sposób, w jaki to zrobili, jest znacznie bardziej złożony, ale masz znacznie więcej możliwości konfiguracji efektu (w końcu działa tak samo ...).

Aby uzyskać więcej informacji, sugeruję przeczytanie: http://en.wikipedia.org/wiki/3D_projection#Perspective_projection

tigrou
źródło
Należy dodać jedną rzecz, ponieważ sin i cos kąta są przeważnie stałe dla każdej klatki, należy obliczyć je poza pętlą, aby ustalić wszystkie pozycje x, y.
hobberwickey
1

Oto kod, aby to zrobić. Jestem tym samym kodem samouczka, który napisałem na moim blogu . Sprawdź tam, aby poznać metodę Mode 7 i RayCasting.

Zasadniczo pseudo kod to:

//This is the pseudo-code to generate the basic mode7

for each y in the view do
    y' <- y / z
    for each x in the view do
        x' <- x / z
        put x',y' texture pixel value in x,y view pixel
    end for
    z <- z + 1
end for

Oto kod, który wykonałem w JAVA, postępując zgodnie z moim samouczkiem.

package mode7;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;

/**
 * Mode 7 - Basic Implementation
 * This code will map a texture to create a pseudo-3d perspective.
 * This is an infinite render mode. The texture will be repeated without bounds.
 * @author VINICIUS
 */
public class BasicModeSeven {

    //Sizes
    public static final int WIDTH = 800;
    public static final int WIDTH_CENTER = WIDTH/2;
    public static final int HEIGHT = 600;
    public static final int HEIGHT_CENTER = HEIGHT/2;

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws IOException {

        //Create Frame
        JFrame frame = new JFrame("Mode 7");
        frame.setSize(WIDTH, HEIGHT);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        //Create Buffered Images:
        //image - This is the image that will be printed in the render view
        //texture - This is the image that will be mapped to the render view
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        BufferedImage texture = ImageIO.read(new File("src/mode7/texture.png"));

        //The new coords that will be used to get the pixel on the texture
        double _x, _y;

        //z - the incrementable variable that beggins at -300 and go to 300, because 
        //the depth will be in the center of the HEIGHT
        double z =  HEIGHT_CENTER * -1;

        //Scales just to control de scale of the printed pixel. It is not necessary
        double scaleX = 16.0;
        double scaleY = 16.0; 

        //Mode 7 - loop (Left Top to Down)
        for(int y = 0; y < HEIGHT; y++){

            _y = y / z; //The new _y coord generated
            if(_y < 0)_y *= -1; //Control the _y because the z starting with a negative number
            _y *= scaleY; //Increase the size using scale
            _y %= texture.getHeight(); //Repeat the pixel avoiding get texture out of bounds 

            for(int x = 0; x < WIDTH; x++){

                _x = (WIDTH_CENTER - x) / z; //The new _x coord generated
                if(_x < 0)_x *= -1; //Control the _x to dont be negative
                _x *= scaleX; //Increase the size using scale
                _x %= texture.getWidth(); //Repeat the pixel avoiding get texture out of bounds 

                //Set x,y of the view image with the _x,_y pixel in the texture
                image.setRGB(x, y, texture.getRGB((int)_x, (int)_y));
            }

            //Increment depth
            z++;
        }

        //Loop to render the generated image
        while(true){
            frame.getGraphics().drawImage(image, 0, 0, null);
        }
    }
}

Wynik to:

wprowadź opis zdjęcia tutaj

Vinícius Biavatti
źródło
Wyjaśnienie znajduje się tutaj programandocoisas.blogspot.com.br . Możesz znaleźć samouczek krok po kroku, aby uzyskać ten efekt. Ale zaktualizuję swój post, aby komentarze były lepsze;).
Vinícius Biavatti