Stap 4: Berekening van het scherm
De klasse Screen is waar de meerderheid van de berekeningen zijn gedaan om het programma werkend te krijgen. Als u wilt werken, moet de klasse de volgende invoer:
import java.util.ArrayList;import java.awt.Color;
De werkelijke klasse begint als volgt:
public class Screen { public int[][] map; public int mapWidth, mapHeight, width, height; public ArrayList textures;
De kaart is het dezelfde kaart gemaakt in de game klasse. Het scherm gebruikt dit om erachter te komen waar de muren zijn en hoe ver weg van de speler hebben. Definieer de grootte van het scherm en altijd moet hetzelfde zijn als de breedte en hoogte van het frame gemaakt in de Game klasse breedte en hoogte. Texturen is een lijst van alle van de texturen, zodat het scherm kan toegang krijgen tot de pixels van de texturen. Nadat de variabelen zijn gedeclareerd hoeven te worden geïnitialiseerd in de constructor als volgt:
public Screen(int[][] m, ArrayList tex, int w, int h) { map = m; textures = tex; width = w; height = h; }
Nu zijn tijd om te schrijven van de ene methode de klasse heeft: een update-methode. De methode update worden opnieuw berekend hoe het scherm eruit moet zien voor de gebruiker op basis van hun positie in de kaart. De methode heet voortdurend, en de bijgewerkte array van pixels aan de Game klasse retourneert. De methode begint door het scherm "op te schonen". Het doet dit door alle pixels op de bovenste helft tot één kleur en alle pixels op de bodem naar de andere.
public int[] update(Camera camera, int[] pixels) { for(int n=0; n<pixels.length/2; n++) { if(pixels[n] != Color.DARK_GRAY.getRGB()) pixels[n] = Color.DARK_GRAY.getRGB(); } for(int i=pixels.length/2; i<pixels.length; i++) { if(pixels[i] != Color.gray.getRGB()) pixels[i] = Color.gray.getRGB(); }
Na de boven- en onderkant van het scherm worden twee verschillende kleuren ook maakt het lijkt alsof er een vloer en een plafond. Na de pixel is array uitgeschakeld dan is het tijd om te gaan op de belangrijkste berekeningen. Het programma elke verticale balk op het scherm doorlopen en werpt een straal om erachter te komen welke muur op het scherm op die verticale balk worden moet. Het begin van de lus ziet er zo uit:
for(int x=0; x<width; x=x+1) { double cameraX = 2 * x / (double)(width) -1; double rayDirX = camera.xDir + camera.xPlane * cameraX; double rayDirY = camera.yDir + camera.yPlane * cameraX; //Map position int mapX = (int)camera.xPos; int mapY = (int)camera.yPos; //length of ray from current position to next x or y-side double sideDistX; double sideDistY; //Length of ray from one side to next in map double deltaDistX = Math.sqrt(1 + (rayDirY*rayDirY) / (rayDirX*rayDirX)); double deltaDistY = Math.sqrt(1 + (rayDirX*rayDirX) / (rayDirY*rayDirY)); double perpWallDist; //Direction to go in x and y int stepX, stepY; boolean hit = false;//was a wall hit int side=0;//was the wall vertical or horizontal
Alles wat er gebeurt hier is sommige variabelen die worden gebruikt door de rest van de lus worden berekend. CameraX is de x-coördinaat van de huidige verticale streep op het vlak van de camera, en de rayDir variabelen maken een vector voor naar de vleug. Alle variabelen die eindigt op DistX of DistY worden berekend zodat het programma controleert alleen voor botsingen op de plaatsen waar de botsingen kunnen eventueel optreden. perpWallDist is dat de afstand tussen de speler de eerste muur de straal botst met. Dit zal later worden berekend. We moeten erachter te komen een paar van de andere variabelen die op basis van de definitie die wij al berekend nadat dat wordt gedaan.
//Figure out the step direction and initial distance to a sideif (rayDirX < 0) { stepX = -1; sideDistX = (camera.xPos - mapX) * deltaDistX; } else { stepX = 1; sideDistX = (mapX + 1.0 - camera.xPos) * deltaDistX; } if (rayDirY < 0) { stepY = -1; sideDistY = (camera.yPos - mapY) * deltaDistY; } else { stepY = 1; sideDistY = (mapY + 1.0 - camera.yPos) * deltaDistY; }
Zodra dit is gebeurd het is tijd om erachter te komen waar de straal met een muur botst. Om dit te doen het programma doorloopt een lus waar wordt gecontroleerd als de straal is gekomen in contact met een muur en niet verplaatst naar de volgende mogelijke botsing verwijzen, alvorens opnieuw te controleren.
//Loop to find where the ray hits a wallwhile(!hit) { //Jump to next square if (sideDistX < sideDistY) { sideDistX += deltaDistX; mapX += stepX; side = 0; } else { sideDistY += deltaDistY; mapY += stepY; side = 1; } //Check if ray has hit a wall if(map[mapX][mapY] > 0) hit = true; }
Nu we weten waar de straal raakt een muur kunnen we beginnen met het uitzoeken hoe de muur eruit moet zien in de verticale streep wij zijn momenteel op. Om dit te doen we eerst het berekenen van de afstand tot de muur, en gebruik vervolgens die afstand om erachter te komen hoe lang de muur moet worden weergegeven in de verticale strook. Wij vervolgens vertalen die hoogte een start en finish in termen van de pixels op het scherm. De code ziet er zo uit:
//Calculate distance to the point of impactif(side==0) perpWallDist = Math.abs((mapX - camera.xPos + (1 - stepX) / 2) / rayDirX); else perpWallDist = Math.abs((mapY - camera.yPos + (1 - stepY) / 2) / rayDirY); //Now calculate the height of the wall based on the distance from the camera int lineHeight; if(perpWallDist > 0) lineHeight = Math.abs((int)(height / perpWallDist)); else lineHeight = height; //calculate lowest and highest pixel to fill in current stripe int drawStart = -lineHeight/2+ height/2; if(drawStart < 0) drawStart = 0; int drawEnd = lineHeight/2 + height/2; if(drawEnd >= height) drawEnd = height - 1;
Nadat dat wordt berekend is het tijd om te beginnen met het uitzoeken wat pixels uit de textuur van de muur daadwerkelijk wordt weergegeven aan de gebruiker. Hiervoor moeten we eerst uitzoeken wat textuur is gekoppeld aan de muur we gewoon hit en dan aan de x-coördinaat op de textuur van de pixels die wordt weergegeven aan de gebruiker achterhalen.
//add a textureint texNum = map[mapX][mapY] - 1; double wallX;//Exact position of where wall was hit if(side==1) {//If its a y-axis wall wallX = (camera.xPos + ((mapY - camera.yPos + (1 - stepY) / 2) / rayDirY) * rayDirX); } else {//X-axis wall wallX = (camera.yPos + ((mapX - camera.xPos + (1 - stepX) / 2) / rayDirX) * rayDirY); } wallX-=Math.floor(wallX); //x coordinate on the texture int texX = (int)(wallX * (textures.get(texNum).SIZE)); if(side == 0 && rayDirX > 0) texX = textures.get(texNum).SIZE - texX - 1; if(side == 1 && rayDirY < 0) texX = textures.get(texNum).SIZE - texX - 1;
De x-coördinaat wordt berekend door het nemen van de exacte positie van waar de muur werd geraakt op de 2D kaart en de integerwaarde, waardoor alleen het decimaalteken af te trekken. Dit decimale (wallX) wordt vervolgens vermenigvuldigd met de grootte van de textuur van de muur om de exacte x-coördinaat op de muur van de pixels die we willen vestigen. Zodra we weten dat het enige links te doen berekenen de y-coördinaten van de pixels op de textuur en hen op het scherm tekent. Daartoe we alle pixels op het scherm in de verticale strook we doen berekeningen doorlopen voor en berekenen de de exacte y-coördinaat van de pixel op de textuur. Gebruik dit het programma vervolgens schrijft de gegevens van de pixel op de structuur in de matrix van pixels op het scherm. Het programma maakt ook horizontale muren donkerder dan verticale muren hier om een fundamentele verlichting effect te geven.
//calculate y coordinate on texturefor(int y=drawStart; y<drawEnd; y++) { int texY = (((y*2 - height + lineHeight) << 6) / lineHeight) / 2; int color; if(side==0) color = textures.get(texNum).pixels[texX + (texY * textures.get(texNum).SIZE)]; else color = (textures.get(texNum).pixels[texX + (texY * textures.get(texNum).SIZE)]>>1) & 8355711;//Make y sides darker pixels[x + y*(width)] = color; }
Na dat, all that's left in de klasse Screen, klasse is de pixel array terug
return pixels;
En de klasse wordt gedaan. Nu alles wat we moeten doen is een paar lijnen van code toevoegen in de Game klasse om het scherm werkend te krijgen. Voeg dit met de variabelen aan de top:
public Screen screen;
En in de constructor toevoegen dat dit ergens na texturen is geïnitialiseerd.
screen = new Screen(map, mapWidth, mapHeight, textures, 640, 480);
En tot slot in de run methode toevoegen
screen.update(camera, pixels);
vlak voor camera.update(map). En het programma is klaar!