Esecuzione di un effetto SNES Mode 7 (affine transform) in pygame


19

Esiste una risposta breve su come eseguire un effetto di tipo kart Mode 7 / mario in pygame?

Ho cercato su Google ampiamente, tutti i documenti che posso inventare sono dozzine di pagine in altre lingue (asm, c) con molte equazioni dall'aspetto strano e simili.

Idealmente, vorrei trovare qualcosa spiegato più in inglese che in termini matematici.

Posso usare PIL o pygame per manipolare l'immagine / la trama, o qualsiasi altra cosa sia necessaria.

Mi piacerebbe davvero ottenere un effetto modalità 7 in Pygame, ma mi sembra vicino alla fine del mio ingegno. L'aiuto sarebbe molto apprezzato. Tutte le risorse o le spiegazioni che puoi fornire sarebbero fantastiche, anche se non sono così semplici come vorrei che fossero.

Se riesco a capirlo, scriverò definitivamente come fare la modalità 7 per la pagina dei neofiti.

modifica: modalità 7 doc: http://www.coranac.com/tonc/text/mode7.htm


5
sembra che ci siano equazioni qui: en.wikipedia.org/wiki/Mode_7 Anche se al giorno d'oggi abbiamo un'accelerazione 3D, cose come la Modalità 7 o il modo stravagante di lavorare sul destino sono più una curiosità che una soluzione.
salmonmoose

3
@ 2D_Guy questa pagina mi spiega molto bene l'algoritmo. Vuoi sapere come farlo o lo desideri già implementato per te?
Gustavo Maciel il

1
@stephelton Sui sistemi SNES, l'unico strato che può essere distorto, ruotato .. (trasformazioni affine applicate con matrici) è il settimo strato. Il livello di sfondo. Tutti gli altri livelli sono stati usati per semplici sprite, quindi se volevi un effetto 3D, dovevi usare questo livello, ecco da dove veniva il nome :)
Gustavo Maciel

3
@GustavoMaciel: è un po 'impreciso. SNES aveva 8 diverse modalità (0-7), in cui fino a 4 livelli di sfondo avevano funzionalità diverse, ma solo una modalità (modalità 7, da cui il nome) supportava la rotazione e il ridimensionamento (e ti limitava anche a un singolo livello). Non puoi davvero combinare le modalità.
Michael Madsen,

1
@Michael: aggiungerei anche: SNES è stata una delle prime console popolari ad usare questo effetto negli anni '90 (con il gioco F-Zero), ed è per questo che da quel momento in poi le persone hanno iniziato a riferirsi a tutti gli effetti del piano 2D mappati a trama orizzontale visti in altri giochi come "modalità 7". In realtà, questo tipo di effetto non era nuovo ed esisteva molto tempo fa in arcade, cfr. Space Harrier / Hang-On (1985).
Tigrou,

Risposte:


45

Modalità 7 è un effetto molto semplice. Proietta una trama (o piastrelle) 2D x / y su un pavimento / soffitto. I vecchi SNES usano l'hardware per fare questo, ma i computer moderni sono così potenti che puoi farlo in tempo reale (e non hai bisogno di ASM come dici).

La formula matematica 3D di base per proiettare un punto 3D (x, y, z) su un punto 2D (x, y) è:

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

Quando ci pensi, ha senso. Gli oggetti lontani sono più piccoli degli oggetti vicini a te. Pensa ai binari della ferrovia che non vanno da nessuna parte:

inserisci qui la descrizione dell'immagine

Se guardiamo indietro ai valori di input della formula: xe ysarà il pixel corrente che stiamo elaborando e zsaranno le informazioni sulla distanza su quanto dista il punto. Per capire cosa zdovrebbe essere, guarda quell'immagine, mostra i zvalori per l'immagine sopra:

inserisci qui la descrizione dell'immagine

viola = vicino distanza, rosso = lontano

Quindi, in questo esempio, il zvalore è y - horizon (assumendo(x:0, y:0) sia al centro dello schermo)

Se mettiamo tutto insieme, diventa: (pseudocodice)

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
     ...
  }

Un'ultima cosa: se vuoi creare un gioco di Mario Kart, suppongo che tu voglia anche ruotare la mappa. Beh, è ​​anche molto semplice: ruotare sxe syprima di ottenere il valore della trama. Ecco la formula:

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

e se vuoi spostarti attraverso la mappa, aggiungi un po 'di offset prima di ottenere il valore della trama:

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

NOTA: ho testato l'algoritmo (quasi copia-incolla) e funziona. Ecco l'esempio: http://glslsandbox.com/e#26532.3 (richiede un browser recente e abilitato WebGL)

inserisci qui la descrizione dell'immagine

NOTA 2: uso la matematica semplice perché hai detto che vuoi qualcosa di semplice (e non mi sembra familiare con la matematica vettoriale). Puoi ottenere le stesse cose usando la formula di Wikipedia o i tutorial che dai. Il modo in cui lo hanno fatto è molto più complesso ma hai molte più possibilità di configurare l'effetto (alla fine funziona allo stesso modo ...).

Per ulteriori informazioni, suggerisco di leggere: http://en.wikipedia.org/wiki/3D_projection#Perspective_projection


Una cosa da aggiungere, poiché il peccato e il cos dell'angolo sono per lo più costanti per fotogramma, assicurati di calcolarli al di fuori del ciclo per capire tutte le posizioni x, y.
hobberwickey,

1

Ecco il codice per farlo. Sono lo stesso codice del tutorial che ho realizzato sul mio blog . Controlla lì per imparare il metodo Mode 7 e il RayCasting.

Fondamentalmente, lo pseudo codice è:

//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

Ecco il codice che ho creato in JAVA, seguendo il mio tutorial.

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

Il risultato è:

inserisci qui la descrizione dell'immagine


La spiegazione è qui programandocoisas.blogspot.com.br . Puoi trovare lì il tutorial passo dopo passo per rendere questo effetto. Ma aggiornerò il mio post in modo da rendere i commenti migliori;).
Vinícius Biavatti,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.