Att göra en grundläggande 3D-motor i Java (4 / 5 steg)
Steg 4: Beräkning av skärmen
Skärmen är där majoriteten av beräkningarna som är gjort för att få programmet arbetar. För att arbeta, måste klassen import av följande produkter:
import java.util.ArrayList;
import java.awt.Color;
Den faktiska klassen börjar såhär:
allmän klass skärmen {
Public int [] [] karta;
Public int mapWidth, mapHeight, bredd, höjd;
offentliga ArrayList texturer;
Kartan är samma karta skapas i klassen spel. Skärmen använder denna för att räkna ut var väggarna är och hur långt bort från spelaren de är. Bredd och höjd definiera storleken på skärmen, och bör alltid vara samma som bredden och höjden av ramen skapad i klassen spel. Texturer är en lista över alla texturer så att skärmen kan komma åt bildpunkter med texturer. Efter dessa variabler deklareras de måste initieras i konstruktören som så:
offentliga skärmen (int [] [] m, ArrayList tex, int w, int h) {
karta = m;
texturer = tex;
bredd = w;
höjd = h;
}
Nu är det dags att skriva en metod i klassen har: en uppdateringsmetoden. Metoden update beräknar om hur skärmen ska se till användare baserat på deras position i kartan. Metoden kallas ständigt, och returnerar uppdaterade pixlar till klassen spel. Metoden börjar med att "rensa" på skärmen. Det gör detta genom att alla bildpunkter på toppen hälften till en färg och alla bildpunkter på botten till en annan.
Public int [] uppdatering (kamera kamera, int [] bildpunkter) {
för (int n = 0; n < pixels.length/2; n ++) {
om (pixlar [n]! = Color.DARK_GRAY.getRGB()) pixlar [n] = Color.DARK_GRAY.getRGB();
}
för (int i=pixels.length/2; i < pixels.length; i ++) {
om (pixlar [i]! = Color.gray.getRGB()) pixlar [i] = Color.gray.getRGB();
}
Med toppen och botten av skärmen vara två olika färger gör det verka som om det finns ett golv och ett tak också. Efter pixeln avmarkeras array då är det dags att gå vidare till de viktigaste beräkningarna. Programmet loopar igenom varje lodrätt streck på skärmen och kastar en ray att räkna ut vilken vägg bör vara på skärmen på det lodräta strecket. I början av slingan ser ut så här:
för (int x = 0; x < bredd, x = x + 1) {
Double cameraX = 2 * x / (double)(width) -1;
Double rayDirX = camera.xDir + camera.xPlane * cameraX;
Double rayDirY = camera.yDir + camera.yPlane * cameraX;
Karta position
int mapX = (int)camera.xPos;
int mapY = (int)camera.yPos;
längden på ray från nuvarande position till nästa x eller y-sida
dubbla sideDistX;
dubbla sideDistY;
Längden på ray från en sida till nästa karta
Double deltaDistX = Math.sqrt (1 + (rayDirY * rayDirY) / (rayDirX * rayDirX));
Double deltaDistY = Math.sqrt (1 + (rayDirX * rayDirX) / (rayDirY * rayDirY));
dubbla perpWallDist;
Riktning att gå i x och y
int stepX, stepY;
booleska hit = false; //was en vägg hit
int sida = 0; //was vägg vertikal eller horisontell
Allt som händer här är vissa variabler som kommer att användas av resten av loopen beräknas. CameraX är x-koordinaten för nuvarande vertikala strecket på kameran planet rayDir variabler göra en vektor för strålen. Alla variabler slutar med DistX eller DistY beräknas så att endast kontrolleras för kollisioner på de platser där kollisioner kan eventuellt förekomma. perpWallDist är avståndet från spelaren till första väggen strålen kolliderar med. Detta kommer att beräknas senare. När det är gjort måste vi räkna ut några av de andra variablerna baserat på den beräknade vi redan.
Räkna ut steg riktningen och ursprungliga avståndet till en sida
om (rayDirX < 0)
{
stepX = -1;
sideDistX = (camera.xPos - mapX) * deltaDistX;
}
annat
{
stepX = 1;
sideDistX = (mapX + 1.0 - camera.xPos) * deltaDistX;
}
om (rayDirY < 0)
{
stepY = -1;
sideDistY = (camera.yPos - mapY) * deltaDistY;
}
annat
{
stepY = 1;
sideDistY = (mapY + 1.0 - camera.yPos) * deltaDistY;
}
När det är klart det är dags att räkna ut där strålen kolliderar med en vägg. Att göra detta program går igenom en slinga där det kontrolleras om ray har kommit i kontakt med en mur, och om inte flyttas till nästa möjliga sammanstötningen kopplingspunkt innan du kontrollerar igen.
Slinga för att hitta där strålen träffar en vägg
While(!hit) {
Hoppa till nästa ruta
om (sideDistX < sideDistY)
{
sideDistX += deltaDistX;
mapX += stepX;
sidan = 0;
}
annat
{
sideDistY += deltaDistY;
mapY += stepY;
sida = 1;
}
Kontrollera om ray har drabbat en vägg
om (karta [mapX] [kartor] > 0) hit = sant;
}
Nu när vi vet där strålen träffar en vägg som vi kan börja räkna ut hur väggen ska se i vertikala stripe vi är för tillfället. För att göra detta vi först beräkna avståndet till väggen och sedan använda denna sträcka för att räkna ut hur lång väggen ska visas i den vertikala band. Vi översätter det höjden en start och slut i form av pixlar på skärmen. Koden ser ut så här:
Beräkna avstånd till islagspunkten
IF(Side==0)
perpWallDist = Math.abs ((mapX - camera.xPos + (1 - stepX) / 2) / rayDirX);
annat
perpWallDist = Math.abs ((mapY - camera.yPos + (1 - stepY) / 2) / rayDirY);
Nu beräkna höjden på väggen beroende på avståndet från kameran
int lineHeight;
om (perpWallDist > 0) lineHeight = Math.abs((int)(height / perpWallDist));
annat lineHeight = höjd;
Beräkna lägsta och högsta pixel fylla i nuvarande stripe
int drawStart = - lineHeight / 2 + höjd/2.
IF(drawStart < 0)
drawStart = 0;
int drawEnd = lineHeight/2 + höjd/2.
om (drawEnd > = höjd)
drawEnd = höjd - 1.
Efter som beräknas är det dags att börja räkna ut vilka pixlar från strukturen på väggen faktiskt kommer att visas för användaren. För detta måste vi först räkna ut vilken konsistens är associerad med väggen vi bara hit och sedan räkna ut x-koordinaten på textur av pixlar som visas för användaren.
lägga en textur
int texNum = karta [mapX] [kartor] - 1.
dubbla wallX; //Exact position där väggen blev påkörd
IF(Side==1) {//If det är en y-vägg
wallX = (camera.xPos + ((mapY - camera.yPos + (1 - stepY) / 2) / rayDirY) * rayDirX);
} annat {//X-axis vägg
wallX = (camera.yPos + ((mapX - camera.xPos + (1 - stepX) / 2) / rayDirX) * rayDirY);
}
wallX-= Math.floor(wallX);
x-koordinat på konsistens
int texX = (int) (wallX * (textures.get(texNum). STORLEK));
om (sidan == 0 & & rayDirX > 0) texX = textures.get(texNum). STORLEK - texX - 1;
om (sidan == 1 & & rayDirY < 0) texX = textures.get(texNum). STORLEK - texX - 1;
X-koordinaten beräknas genom att den exakta positionen av där väggen blev påkörd på 2D-kartan och subtrahera heltalsvärdet, lämnar bara decimalen. Denna decimal (wallX) multipliceras sedan med storleken på strukturen på väggen för att få exakta x-koordinaten på väggen vi vill rikta bildpunkter. När vi vet att det enda som återstår att göra är att beräkna y-koordinaterna för pixlarna på konsistens och dra dem på skärmen. Att göra detta vi loopa igenom alla bildpunkter på skärmen i den vertikala band vi gör beräkningar för och beräkna den exakta y-koordinaten för pixeln i texturen. Med detta programmet sedan skriver data från pixeln på konsistens till mängd pixlar på skärmen. Programmet gör också horisontella väggarna mörkare än vertikala väggar här att ge en grundläggande ljuseffekt.
beräkna y-koordinaten på konsistens
för (int y = drawStart, y < drawEnd; y ++) {
int texY = (((y * 2 - höjd + lineHeight) << 6) / lineHeight) / 2;
int färg;
IF(Side==0) färg = textures.get (texNum) .pixels [texX + (texY * textures.get(texNum). STORLEK)];
annan färg = (textures.get (texNum) .pixels [texX + (texY * textures.get(texNum). STORLEK)] >> 1) & 8355711; //Make y sidor mörkare
pixlar [x + y*(width)] = färg;
}
Efter det, allt som är kvar i klassen skärm är tillbaka arrayen pixel
returnera pixlar.
Och klassen är gjort. Nu allt vi behöver göra är att lägga några rader kod i spelet klass att få skärmen fungerar. Lägg till detta med variablerna högst upp:
offentliga skärmen;
Och Lägg till detta någonstans efter texturer har initierats i konstruktören.
skärmen = ny skärm (karta, mapWidth, mapHeight, texturer, 640, 480);
Och slutligen, i kör metoden Lägg till
screen.Update (kamera, pixlar);
precis innan camera.update(map). Och programmet är klar!