- Katılım
- 8 Eki 2012
- Konular
- 555
- Mesajlar
- 1,301
- Online süresi
- 2mo 3d
- Reaksiyon Skoru
- 718
- Altın Konu
- 68
- TM Yaşı
- 13 Yıl 8 Ay 2 Gün
- Başarım Puanı
- 282
- MmoLira
- 2,295
- DevLira
- 243
ROHAN2 WORLD 1-120 TR TİPİ OFFICIAL YOHARA, BALATHOR VE AMON! 80. GÜNÜNDE! +10.000 ONLİNE! HİLE VE BOT %100 ENGELLİ HEMEN TIKLA!
Linkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir.
Bir oyunun 3B ortamda yer alması, sürükleyiciliği büyük ölçüde artırır, ancak aslında tam bir 3B motoru uygulamak çok karmaşık olabilir. Neyse ki, 3D efektini nispeten kolay bir şekilde elde etmek için kullanılabilecek bazı hileler var. Bu hilelerden birine raycasting denir. Raycasting, ekrandaki her dikey çubuk için kameradan bir ışın göndererek ve bu ışının katı bir nesneyle nerede çarpıştığını bularak çalışır. Raycasting de çok hızlıdır ve Wolfenstein 3D gibi ilk 3D oyunlardan bazıları bunu kullanmıştır. Burada açıklanan motor, tüm duvarların aynı boyut ve şekilde olacağı çok basit bir ışın döküm motorudur.
Bir raycasting motoru yapmak çok zor değil, ancak kesinlikle programlama konusunda önceden biraz deneyim gerektiriyor. Bazı önceki programlama deneyimlerine ek olarak, Eclipse veya Netbeans gibi bir IDE de öneriyorum .
İpucu EkleSoru sorYorum Yapİndir
Adım 1: Ana Sınıf
Yapılması gereken ilk şey bir ana sınıftır. Ana sınıf, görüntüleri kullanıcıya göstermeyi, oynatıcıya neyin gösterilmesi gerektiğini yeniden hesaplamak için diğer sınıfları çağırmayı ve kameranın konumunu güncellemeyi işleyecektir.Bu sınıf için ithalat şöyle olacaktır:
Kod:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.ArrayList;
import javax.swing.JFrame;
Sınıf ve değişkenleri şöyle görünecektir:
public class Game,
JFrame'i genişletir Runnable{ private static final long serialVersionUID = 1L;
public int mapWidth = 15;
public int mapHeight = 15;
özel Konu iş parçacığı;
özel boolean çalıştırma;
özel BufferedImage görüntüsü;
public int[] piksel;
genel statik int[][] harita =
{
{1,1,1,1,1,1,1,1,2,2,2,2,2,2,2},
{1,0,0,0,0,0,0,0,2,0,0,0,0,0,2},
{1,0,3,3,3,3,3,0,0,0,0,0,0,0,2},
{1,0,3,0,0,0,3,0,2,0,0,0,0,0,2},
{1,0,3,0,0,0,3,0,2,2,2,0,2,2,2},
{1,0,3,0,0,0,3,0,2,0,0,0,0,0,2},
{1,0,3,3,0,3,3,0,2,0,0,0,0,0,2},
{1,0,0,0,0,0,0,0,2,0,0,0,0,0,2},
{1,1,1,1,1,1,1,4,4,4,4,0,4,4,4},
{1,0,0,0,0,0,1,4,0,0,0,0,0,0,4},
{1,0,0,0,0,0,1,4,0,0,0,0,0,0,4},
{1,0,0,2,0,0,1,4,0,3,3,3,3,0,4},
{1,0,0,0,0,0,1,4,0,3,3,3,3,0,4},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,4},
{1,1,1,1,1,1,4,4,4,4,4,4,4,4,4}
};
Haritanın istediğiniz şekilde yeniden yapılandırılabileceğini unutmayın, burada sahip olduğum şey sadece bir örnek. Haritadaki sayılar, o konumda ne tür bir duvar olacağını gösterir. 0, boş alanı temsil ederken, diğer herhangi bir sayı sağlam bir duvarı ve onunla birlikte gelen dokuyu temsil eder. BufferedImage, kullanıcıya gösterilen şeydir ve pikseller, görüntüdeki tüm piksellerin bir dizisidir. Diğer değişkenler gerçekten tekrar görünmeyecek, sadece grafiklerin ve programın düzgün çalışmasını sağlamak için kullanılıyorlar.
Yapıcı şimdilik şöyle görünecek:
public Game() {
thread = new Thread(bu);
resim = yeni BufferedImage(640, 480, BufferedImage.TYPE_INT_RGB);
piksel = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
setSize(640, 480);
setResizable(yanlış);
setTitle("3D Motoru");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBackground(Color.black);
setLocationRelativeTo(null);
setVisible(doğru);
Başlat();
}
Bunların çoğu sadece sınıf değişkenlerinin ve çerçevenin başlatılmasıdır. "Pikseller ="'den sonraki kod, pikselleri ve görüntüyü birbirine bağlar, böylece piksellerdeki veri değerleri her değiştirildiğinde, görüntü kullanıcıya gösterildiğinde karşılık gelen değişiklikler görüntüde görünür.
Başlatma ve durdurma yöntemleri basittir ve programın güvenli bir şekilde başlayıp bitmesini sağlamak için kullanılır.
özel senkronize geçersiz start() {
çalışıyor = true;
thread.start();
}
genel senkronize geçersiz durdurma () {
koşu = yanlış;
Deneyin {
thread.join();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
Game sınıfında olması gereken son iki metot render ve run metotlarıdır. Render yöntemi şöyle görünecektir:
public void render() {
BufferStrategy bs = getBufferStrategy();
if(bs == boş) {
createBufferStrategy(3);
dönüş;
}
Grafikler g = bs.getDrawGraphics();
g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
bs.show();
}
Ekran güncellemelerinin daha düzgün olması için oluşturma sırasında bir arabellek stratejisi kullanılır. Genel olarak, bir arabellek stratejisi kullanmak, oyunun çalışırken daha iyi görünmesine yardımcı olur. Görüntüyü ekrana gerçekten çizmek için, arabellek stratejisinden bir grafik nesnesi elde edilir ve görüntüümüzü çizmek için kullanılır.
Run yöntemi, programın farklı bölümlerinin ne sıklıkta güncellendiğini ele aldığı için çok önemlidir. Bunu yapmak için saniyenin 1/60'ının ne zaman geçtiğini ve ekranın ve kameranın ne zaman güncellendiğini takip etmek için bazı kodlar kullanır. Bu, programın ne kadar sorunsuz çalıştığını geliştirir. Çalıştırma yöntemi şöyle görünür:
public void run() {
long lastTime = System.nanoTime();
son çift ns = 1000000000.0 / 60.0;//saniyede 60 kez
çift delta = 0;
requestFocus();
koşarken) {
artık çok uzun = System.nanoTime();
delta = delta + ((şimdi-son Zaman) / ns);
lastTime = şimdi;
while (delta >= 1)//Güncellemenin saniyede yalnızca 60 kez yapıldığından emin olun
{
// tüm mantık kısıtlamalı zamanı yönetir
delta--;
}
render();//ekrana sınırsız süre gösterir
}
}
Tüm bu yöntemler, yapıcılar ve değişkenler bir kez girdikten sonra, Game sınıfında şu anda yapılacak tek şey bir ana yöntem eklemektir. Ana yöntem çok kolay, tek yapmanız gereken:
public static void main(String [] args) {
Game game = new Game();
}
Ve şimdi ana sınıf şu an için bitti! Programı şimdi çalıştırırsanız, siyah bir ekran açılmalıdır.
İpucu EkleSoru sorYorum Yapİndir
2. Adım: Doku Sınıfı
Linkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir.
Linkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir.
Linkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir.
Linkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir.
Ekranın nasıl görünmesi gerektiğini bulmak için hesaplamalara atlamadan önce, bir dolambaçlı yoldan gideceğim ve bir Texture sınıfı kuracağım. Dokular ortamdaki çeşitli duvarlara uygulanacak ve proje klasörüne kaydedilen görüntülerden gelecektir. Görsellerde bu projede kullanacağım internetten bulduğum 4 dokuya yer verdim. İstediğiniz dokuları kullanabilirsiniz. Bu dokuları kullanmak için proje dosyası içindeki bir klasöre koymanızı tavsiye ederim. Bunu yapmak için proje klasörüne gidin (Eclipse'de bu, çalışma alanı klasöründe bulunur). Proje klasörüne ulaştıktan sonra "res" veya benzeri bir adla yeni bir klasör oluşturun. Dokuları bu klasöre yerleştirin. Dokuları başka bir yere yerleştirebilirsin, burası benim dokularımı sakladığım yer. Bu yapıldıktan sonra dokuları kullanılabilir hale getirmek için kodu yazmaya başlayabiliriz.
Sınıf için ithalat şunlardır:
java.awt.image.BufferedImage'ı içe aktar;
java.io.File'ı içe aktarın;
java.io.IOException'ı içe aktarın;
javax.imageio.ImageIO'yu içe aktar;
Sınıf başlığı ve değişkenleri şöyle görünecektir:
public class Doku {
public int[] piksel;
özel Dize konumu;
public final int SIZE;
Dizi pikselleri, doku görüntüsündeki tüm pikseller için verileri tutmak için kullanılır. Loc, bilgisayara doku için görüntü dosyasının nerede bulunabileceğini belirtmek için kullanılır. SIZE, dokunun bir tarafta ne kadar büyük olduğudur (64x64 boyutunda bir görüntü 64 boyutunda olacaktır) ve tüm dokular tam kare olacaktır.
Yapıcı, loc ve SIZE değişkenlerini başlatacak ve görüntü verilerini piksellere yüklemek için a yöntemini çağıracaktır. Şuna benziyor:
public Texture(Dize konumu, int boyutu) {
konum = konum;
BOYUT = boyut;
piksel = new int[SIZE * SIZE];
yük();
}
Şimdi Texture sınıfı için geriye kalan tek şey, görüntülerden veri almak ve bunları bir dizi piksel verisinde depolamak için bir yükleme yöntemi eklemek. Bu yöntem şöyle görünecektir:
özel boşluk yükü () {
Deneyin {
BufferedImage resmi = ImageIO.read(yeni Dosya(loc));
int w = image.getWidth();
int h = image.getHeight();
image.getRGB(0, 0, w, h, piksel, 0, w);
} yakalama (IOException e) {
e.printStackTrace();
}
}
Yükleme yöntemi, loc'un işaret ettiği dosyadaki verileri okuyarak ve bu verileri arabelleğe alınmış bir görüntüye yazarak çalışır. Her piksel için veriler daha sonra arabelleğe alınan görüntüden alınır ve piksellerde saklanır.
Bu noktada Texture sınıfı tamamlandı, bu yüzden devam edeceğim ve son programda kullanılacak birkaç doku tanımlayacağım. Bunu yapmak için şunu koyun
public static Texture wood = new Texture("res/wood.png", 64);
public static Texture brick = new Texture("res/redbrick.png", 64);
public static Texture bluestone = new Texture("res/bluestone.png", 64);
public static Texture stone = new Texture("res/greystone.png", 64);
"public class Texture" satırı ile "public int[] piksel" arasında.
Bu dokuları programın geri kalanı için erişilebilir kılmak için devam edelim ve onları Game sınıfına verelim. Bunu yapmak için tüm dokuları tutacak bir ArrayList'e ihtiyacımız olacak ve dokuları bu ArrayList'e eklememiz gerekecek. ArrayList'i oluşturmak için aşağıdaki kod satırını değişkenlerle sınıfın en üstüne yakın bir yere koyun:
public ArrayList<Texture> dokuları;
Bu ArrayList'in yapıcıda başlatılması gerekecek ve dokular da yapıcıda buna eklenmelidir. Yapıcıda aşağıdaki kod bitini ekleyin:
dokular = new ArrayList<Texture>();
textures.add(Texture.wood);
textures.add(Texture.brick);
textures.add(Texture.bluestone);
textures.add(Texture.stone);
Ve şimdi dokular gitmek için iyi!
İpucu EkleSoru sorYorum Yapİndir
3. Adım: Kamera Sınıfı
Şimdi başka bir yoldan gidelim ve Camera sınıfını ayarlayalım. Camera sınıfı, oyuncunun 2B haritada nerede bulunduğunu takip eder ve ayrıca oyuncunun konumunu güncellemekle ilgilenir. Bunu yapmak için sınıf KeyListener'ı uygulayacak, bu nedenle KeyEvent ve KeyListener'ı içe aktarması gerekecek.içe aktar java.awt.event.KeyEvent;<br>java.awt.event.KeyListener'ı içe aktar;
Kameranın konumunu ve ne görebileceğini takip etmek için birçok değişkene ihtiyaç vardır. Bu nedenle sınıfın ilk öbeği şöyle görünür:
public class Camera, KeyListener'ı uygular {
genel çift xPos, yPos, xDir, yDir, xPlane, yPlane;
public boolean sol, sağ, ileri, geri;
genel son çift MOVE_SPEED = .08;
public final double ROTATION_SPEED = .045;
xPos ve yPos, oyuncunun Game sınıfında oluşturulan 2B harita üzerindeki konumudur. xDir ve yDir, oyuncunun baktığı yönü gösteren bir vektörün x ve y bileşenleridir. xPlane ve yPlane ayrıca bir vektörün x ve y bileşenleridir. xPlane ve yPlane tarafından tanımlanan vektör, yön vektörüne her zaman diktir ve bir tarafta kameranın görüş alanının en uzak kenarını gösterir. Diğer taraftaki en uzak kenar sadece negatif düzlem vektörüdür. Yön vektörü ve düzlem vektörünün kombinasyonu, kameranın görüş alanında ne olduğunu tanımlar. Boolean'lar, kullanıcının kamerayı hareket ettirebilmesi için hangi tuşlara basıldığını takip etmek için kullanılır. MOVE_SPEED ve ROTATION_SPEED, kullanıcı ilgili tuşa basarken kameranın ne kadar hızlı hareket ettiğini ve döndüğünü belirler.
Sonraki, yapıcıdır. Yapıcı, sınıfa kameranın nerede olduğunu ve ne görebileceğini söyleyen değerleri alır ve bunları karşılık gelen değişkene (xPos, yPos...) atar.
genel Kamera(çift x, çift y, çift xd, çift yd, çift xp, çift yp)
{
xPos = x;
yPos = y;
xDir = xd;
yDir = yd;
xDüzlem = xp;
yDüzlem = yp;
}
Son programda bir kamera nesnesine ihtiyaç duyulacak, o yüzden devam edelim ve bir tane ekleyelim. Diğer tüm değişken bildirimleriyle birlikte Game sınıfında
genel Kamera kamerası;
ve yapıcıda ekleyin
kamera = yeni Kamera(4.5, 4.5, 1, 0, 0, -.66);
addKeyListener(kamera);
Bu kamera benim kullandığım harita ile çalışacak, farklı bir harita kullanıyorsanız veya farklı bir konumda başlamak istiyorsanız xPos ve yPos değerlerini ayarlayın (örneğimde 4 ve 6). .66 kullanmak, iyi bir görüş alanı olduğunu düşündüğüm şeyi veriyor, ancak değeri farklı bir FOV elde etmek için ayarlayabilirsiniz.
Artık Camera sınıfının bir yapıcısı olduğuna göre, kullanıcının girdilerini izlemek ve kameranın konumunu/yönünü güncellemek için yöntemler eklemeye başlayabiliriz. Camera sınıfı, KeyboardListener'ı uyguladığı için, ondan uygulanan tüm yöntemlere sahip olmalıdır. Eclipse, otomatik olarak bu yöntemleri eklemenizi ister. keyTyped yöntemini boş bırakabilirsiniz, ancak diğer iki yöntem kullanılacaktır. keyPressed, karşılık gelen tuşlara basıldığında booleanları true olarak ayarlar ve keyReleased, tuşlar bırakıldığında bunları tekrar false olarak değiştirir. Yöntemler şöyle görünür:
public void keyPressed(KeyEvent key) {
if((key.getKeyCode() == KeyEvent.VK_LEFT))
sol = doğru;
if((key.getKeyCode() == KeyEvent.VK_RIGHT))
doğru = doğru;
if((key.getKeyCode() == KeyEvent.VK_UP))
ileri = doğru;
if((key.getKeyCode() == KeyEvent.VK_DOWN))
geri = doğru;
}
ve
public void keyReleased(KeyEvent key) {
if((key.getKeyCode() == KeyEvent.VK_LEFT))
sol = yanlış;
if((key.getKeyCode() == KeyEvent.VK_RIGHT))
doğru = yanlış;
if((key.getKeyCode() == KeyEvent.VK_UP))
ileri = yanlış;
if((key.getKeyCode() == KeyEvent.VK_DOWN))
geri = yanlış;
}
Artık Camera sınıfı hangi tuşlara basıldığını takip ettiğine göre, oyuncunun pozisyonunu güncellemeye başlayabiliriz. Bunu yapmak için Game sınıfının run yönteminde çağrılan bir güncelleme yöntemini kullanacağız. Bu sırada devam edeceğiz ve Game sınıfında çağrıldığında haritayı ona ileterek güncelleme yöntemine çarpışma algılama ekleyeceğiz. Güncelleme yöntemi şöyle görünür:
public void update(int[][] map) {
if(ileri) {
if(map[(int)(xPos + xDir * MOVE_SPEED)][(int)yPos] == 0) {
xPos+=xDir*TAŞI_HIZI;
}
if(harita[(int)xPos][(int)(yPos + yDir * MOVE_SPEED)] ==0)
yPos+=yDir*MOVE_SPEED;
}
eğer(geri) {
if(harita[(int)(xPos - xDir * MOVE_SPEED)][(int)yPos] == 0)
xPos-=xDir*TAŞI_HIZI;
if(harita[(int)xPos][(int)(yPos - yDir * MOVE_SPEED)]==0)
yPos-=yDir*MOVE_SPEED;
}
if(sağ) {
çift eskixDir=xDir;
xDir=xDir*Math.cos(-ROTATION_SPEED) - yDir*Math.sin(-ROTATION_SPEED);
yDir=oldxDir*Math.sin(-ROTATION_SPEED) + yDir*Math.cos(-ROTATION_SPEED);
double oldxPlane = xPlane;
xPlane=xPlane*Math.cos(-ROTATION_SPEED) - yPlane*Math.sin(-ROTATION_SPEED);
yPlane=oldxPlane*Math.sin(-ROTATION_SPEED) + yPlane*Math.cos(-ROTATION_SPEED);
}
if(sol) {
çift eskixDir=xDir;
xDir=xDir*Math.cos(ROTATION_SPEED) - yDir*Math.sin(ROTATION_SPEED);
yDir=oldxDir*Math.sin(ROTATION_SPEED) + yDir*Math.cos(ROTATION_SPEED);
double oldxPlane = xPlane;
xPlane=xPlane*Math.cos(ROTATION_SPEED) - yPlane*Math.sin(ROTATION_SPEED);
yPlane=oldxPlane*Math.sin(ROTATION_SPEED) + yPlane*Math.cos(ROTATION_SPEED);
}
}
Yöntemin ileri ve geri hareketi kontrol eden kısımları xPos ve yPos'a sırasıyla xDir ve yDir ekleyerek çalışır. Bu hareket gerçekleşmeden önce program, hareketin kamerayı bir duvarın içine sokup sokmayacağını kontrol eder ve eğer yapacaksa hareketi gerçekleştirmez. Döndürme için hem yön vektörü hem de düzlem vektörü, dönüş matrisi ile çarpılır, bu:
[ cos(ROTATION_SPEED) -sin(ROTATION_SPEED) ]
[ günah(ROTATION_SPEED) cos(ROTATION_SPEED) ]
yeni değerlerini elde etmek için. Güncelleme yöntemi tamamlandıktan sonra artık Game sınıfından çağırabiliriz. Game sınıfının çalıştırma yönteminde, burada gösterildiği yere aşağıdaki kod satırını ekleyin
Bunu ekle:
camera.update(harita);
burada:
koşarken) {
artık çok uzun = System.nanoTime();
delta = delta + ((şimdi-son Zaman) / ns);
lastTime = şimdi;
while (delta >= 1)//Güncellemenin saniyede yalnızca 60 kez yapıldığından emin olun
{
// tüm mantık kısıtlamalı zamanı yönetir
camera.update(harita);
delta--;
}
render();//ekrana sınırsız süre gösterir
}
Artık bu bittiğine göre, nihayet son sınıfa geçebilir ve ekranı hesaplayabiliriz!
İpucu EkleSoru sorYorum Yapİndir
Adım 4: Ekranın Hesaplanması
Screen sınıfı, programın çalışmasını sağlamak için hesaplamaların çoğunun yapıldığı yerdir. Çalışmak için sınıfın aşağıdaki içe aktarmalara ihtiyacı vardır:java.util.ArrayList'i içe aktarın;
java.awt.Color'ı içe aktar;
Gerçek sınıf şöyle başlar:
public class Ekran {
public int[][] map;
public int mapWidth, mapHeight, genişlik, yükseklik;
genel ArrayList dokuları;
Harita, oyun sınıfında oluşturulan haritanın aynısıdır. Ekran bunu duvarların nerede olduğunu ve oyuncudan ne kadar uzakta olduklarını bulmak için kullanır. Genişlik ve yükseklik ekranın boyutunu tanımlar ve her zaman Game sınıfında oluşturulan çerçevenin genişliği ve yüksekliği ile aynı olmalıdır. Dokular, ekranın dokuların piksellerine erişebilmesi için tüm dokuların bir listesidir. Bu değişkenler bildirildikten sonra, yapıcıda şu şekilde başlatılmaları gerekir:
public Screen(int[][] m, ArrayList tex, int w, int h) {
harita = m;
dokular = teks;
genişlik = w;
yükseklik = h;
}
Şimdi sınıfın sahip olduğu tek yöntemi yazma zamanı: bir güncelleme yöntemi. Güncelleme yöntemi, haritadaki konumlarına göre ekranın kullanıcıya nasıl görünmesi gerektiğini yeniden hesaplar. Yöntem sürekli olarak çağrılır ve güncellenmiş piksel dizisini Game sınıfına döndürür. Yöntem, ekranı "temizleyerek" başlar. Bunu, üst yarıdaki tüm pikselleri bir renge ve alttaki tüm pikselleri diğerine ayarlayarak yapar.
public int[] güncelleme(Kamera kamerası, int[] piksel) {
for(int n=0; n<piksel.uzunluk/2; n++) {
if(piksel[n] != Color.DARK_GRAY.getRGB()) piksel[n] = Color.DARK_GRAY.getRGB();
}
for(int i=piksel.uzunluk/2; i<piksel.uzunluk; i++) {
if(piksel != Color.gray.getRGB()) piksel = Color.gray.getRGB();
}
Ekranın üst ve alt kısmının iki farklı renk olması da zemin ve tavan varmış gibi görünmesini sağlıyor. Piksel dizisi temizlendikten sonra ana hesaplamalara geçme zamanıdır. Program, ekrandaki her dikey çubuktan geçer ve o dikey çubukta ekranda hangi duvarın olması gerektiğini bulmak için bir ışın gönderir. Döngünün başlangıcı şöyle görünür:
for(int x=0; x<genişlik; x=x+1) {
çift kameraX = 2 * x / (çift)(genişlik) -1;
double rayDirX = camera.xDir + camera.xPlane * cameraX;
double rayDirY = camera.yDir + camera.yPlane * cameraX;
//harita konumu
int mapX = (int)camera.xPos;
int mapY = (int)camera.yPos;
//geçerli konumdan sonraki x veya y tarafına kadar ışının uzunluğu
çift taraflıDistX;
çift taraflıDistY;
//Haritada bir taraftan diğerine ışın uzunluğu
double deltaDistX = Math.sqrt(1 + (rayDirY*rayDirY) / (rayDirX*rayDirX));
double deltaDistY = Math.sqrt(1 + (rayDirX*rayDirX) / (rayDirY*rayDirY));
çift perpWallDist;
//x ve y'de gidilecek yön
int adımX, adımY;
boole isabeti = yanlış;//duvar isabetiydi
int yan=0;//duvar dikey mi yoksa yatay mıydı
Burada olan tek şey, döngünün geri kalanı tarafından kullanılacak bazı değişkenlerin hesaplanmasıdır. CameraX, kamera düzlemindeki geçerli dikey şeridin x koordinatıdır ve rayDir değişkenleri, ışın için bir vektör oluşturur. DistX veya DistY ile biten tüm değişkenler hesaplanır, böylece program yalnızca çarpışmaların meydana gelebileceği yerlerde çarpışmaları kontrol eder. perpWallDist, oynatıcıdan ışının çarptığı ilk duvara kadar olan mesafedir. Bu daha sonra hesaplanacaktır. Bu yapıldıktan sonra, daha önce hesapladığımıza dayalı olarak diğer birkaç değişkeni bulmamız gerekiyor.
//
(rayDirX < 0) ise adım yönünü ve bir tarafa olan başlangıç mesafesini hesaplayın
{
adımX = -1;
sideDistX = (camera.xPos - mapX) * deltaDistX;
}
Başka
{
adımX = 1;
sideDistX = (mapX + 1.0 - camera.xPos) * deltaDistX;
}
if (rayDirY < 0)
{
adımY = -1;
sideDistY = (camera.yPos - mapY) * deltaDistY;
}
Başka
{
adımY = 1;
sideDistY = (mapY + 1.0 - camera.yPos) * deltaDistY;
}
Bu yapıldıktan sonra, ışının bir duvarla nerede çarpıştığını bulmanın zamanı geldi. Bunu yapmak için program, ışının bir duvarla temas edip etmediğini kontrol ettiği ve tekrar kontrol etmeden önce bir sonraki olası çarpışma noktasına hareket edip etmediğini kontrol ettiği bir döngüden geçer.
//Işının duvara çarptığı yeri bulmak için döngü yapın
while(!hit) {
//Bir sonraki kareye atla
if (sideDistX < sideDistY)
{
sideDistX += deltaDistX;
mapX += stepX;
yan = 0;
}
Başka
{
sideDistY += deltaDistY;
mapY += stepY;
yan = 1;
}
//Ray'in duvara çarpıp çarpmadığını kontrol edin
if(map[mapX][mapY] > 0) isabet = doğru;
}
Artık ışının duvara çarptığı yeri bildiğimize göre, üzerinde bulunduğumuz dikey şeritte duvarın nasıl görünmesi gerektiğini bulmaya başlayabiliriz. Bunu yapmak için önce duvara olan mesafeyi hesaplıyoruz ve sonra bu mesafeyi duvarın dikey şeritte ne kadar uzun görünmesi gerektiğini bulmak için kullanıyoruz. Daha sonra bu yüksekliği ekrandaki pikseller açısından bir başlangıca ve bitişe çeviriyoruz. Kod şöyle görünür:
// Çarpma noktasına olan mesafeyi hesapla
if(yan==0)
perpWallDist = Math.abs((mapX - camera.xPos + (1 - stepX) / 2) / rayDirX);
Başka
perpWallDist = Math.abs((mapY - camera.yPos + (1 - stepY) / 2) / rayDirY);
//Şimdi kameradan uzaklığa göre duvarın yüksekliğini hesapla
int lineHeight;
if(perpWallDist > 0) lineHeight = Math.abs((int)(height / perpWallDist));
else lineHeight = yükseklik;
//geçerli şeridi doldurmak için en düşük ve en yüksek pikseli hesapla
int drawStart = -lineYükseklik/2+ yükseklik/2;
if(drawStart < 0)
drawStart = 0;
int drawEnd = lineHeight/2 + yükseklik/2;
if(drawEnd >= yükseklik)
drawEnd = yükseklik - 1;
Bu hesaplandıktan sonra, duvarın dokusundan hangi piksellerin kullanıcıya gerçekten görüneceğini bulmaya başlamanın zamanı geldi. Bunun için önce çarptığımız duvarla hangi dokunun ilişkili olduğunu bulmalı ve ardından kullanıcıya görünecek piksellerin dokusu üzerindeki x koordinatını bulmalıyız.
//bir doku ekle
int texNum = map[mapX][mapY] - 1;
double wallX;//Duvarın çarpıldığı yerin tam konumu
if(yan==1) {//Y ekseni duvarı ise
wallX = (camera.xPos + ((mapY - camera.yPos + (1 - stepY) / 2) / rayDirY) * rayDirX);
} başka {//X ekseni duvarı
wallX = (camera.yPos + ((mapX - camera.xPos + (1 - stepX) / 2) / rayDirX) * rayDirY);
}
wallX-=Math.floor(wallX);
// doku üzerinde x koordinatı
int texX = (int)(duvarX * (texture.get(texNum).SIZE));
if(yan == 0 && rayDirX > 0) texX = textures.get(texNum).SIZE - texX - 1;
if(yan == 1 && rayDirY < 0) texX = textures.get(texNum).SIZE - texX - 1;
X-koordinatı, 2B haritada duvarın çarpıldığı yerin tam konumu alınarak ve tamsayı değeri çıkarılarak, yalnızca ondalık sayı bırakılarak hesaplanır. Bu ondalık sayı (wallX), çizmek istediğimiz piksellerin duvarındaki tam x koordinatını elde etmek için duvarın dokusunun boyutuyla çarpılır. Tek yapmamız gereken doku üzerindeki piksellerin y-koordinatlarını hesaplamak ve ekrana çizmek olduğunu anladığımızda. Bunu yapmak için, hesaplamalar yaptığımız dikey şeritte ekrandaki tüm pikseller arasında dolaşıyoruz ve doku üzerindeki pikselin tam y-koordinatını hesaplıyoruz. Program bunu kullanarak dokudaki pikselden gelen verileri ekrandaki piksel dizisine yazar. Program ayrıca temel bir aydınlatma efekti vermek için yatay duvarları dikey duvarlardan daha koyu yapar.
//doku üzerinde y koordinatını hesapla
for(int y=drawStart; y<drawEnd; y++) {
int texY = (((y*2 - yükseklik + satırYüksekliği) << 6) / satırYüksekliği) / 2;
int renk;
if(side==0) color = textures.get(texNum).pixels[texX + (texY * textures.get(texNum).SIZE)];
else color = (texture.get(texNum).pixels[texX + (texY * textures.get(texNum).SIZE)]>>1) & 8355711;//Y tarafı daha koyu yap
piksel[x + y*(genişlik)] = renk;
}
Bundan sonra, Screen sınıfında kalan tek şey piksel dizisini döndürmek.
dönüş pikselleri;
Ve ders bitti. Şimdi tek yapmamız gereken, ekranın çalışmasını sağlamak için Game sınıfına birkaç satır kod eklemek. En üstteki değişkenlerle şunu ekleyin:
genel Ekran ekranı;
Ve yapıcıda, dokular başlatıldıktan sonra bunu bir yere ekleyin.
ekran = yeni Ekran(harita, mapWidth, mapHeight, dokular, 640, 480);
Ve son olarak, çalıştırma yönteminde ekleyin
screen.update(kamera, piksel);
camera.update(harita)'dan hemen önce. Ve program tamamlandı!
İpucu EkleSoru sorYorum Yapİndir
Adım 5: Son Kod
İşte her sınıf için kodun tam bir kopyası.ekler
- oyun.txt
İndir - doku.txt
İndir - kamera.txt
Linkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir. - Ekran.txt
Moderatör tarafında düzenlendi:
- Katılım
- 31 Eki 2022
- Konular
- 217
- Mesajlar
- 690
- Çözüm
- 1
- Online süresi
- 29d 10h
- Reaksiyon Skoru
- 666
- Altın Konu
- 18
- Başarım Puanı
- 171
- MmoLira
- 2,890
- DevLira
- 30
Teşekkürler paylaşım içinLinkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir.
Bir oyunun 3B ortamda yer alması, sürükleyiciliği büyük ölçüde artırır, ancak aslında tam bir 3B motoru uygulamak çok karmaşık olabilir. Neyse ki, 3D efektini nispeten kolay bir şekilde elde etmek için kullanılabilecek bazı hileler var. Bu hilelerden birine raycasting denir. Raycasting, ekrandaki her dikey çubuk için kameradan bir ışın göndererek ve bu ışının katı bir nesneyle nerede çarpıştığını bularak çalışır. Raycasting de çok hızlıdır ve Wolfenstein 3D gibi ilk 3D oyunlardan bazıları bunu kullanmıştır. Burada açıklanan motor, tüm duvarların aynı boyut ve şekilde olacağı çok basit bir ışın döküm motorudur.
Bir raycasting motoru yapmak çok zor değil, ancak kesinlikle programlama konusunda önceden biraz deneyim gerektiriyor. Bazı önceki programlama deneyimlerine ek olarak, Eclipse veya Netbeans gibi bir IDE de öneriyorum .
İpucu EkleSoru sorYorum Yapİndir
Adım 1: Ana Sınıf
Yapılması gereken ilk şey bir ana sınıftır. Ana sınıf, görüntüleri kullanıcıya göstermeyi, oynatıcıya neyin gösterilmesi gerektiğini yeniden hesaplamak için diğer sınıfları çağırmayı ve kameranın konumunu güncellemeyi işleyecektir.
Bu sınıf için ithalat şöyle olacaktır:
Kod:import java.awt.Color; import java.awt.Graphics; import java.awt.image.BufferStrategy; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.util.ArrayList; import javax.swing.JFrame;
Sınıf ve değişkenleri şöyle görünecektir:
public class Game,
JFrame'i genişletir Runnable{ private static final long serialVersionUID = 1L;
public int mapWidth = 15;
public int mapHeight = 15;
özel Konu iş parçacığı;
özel boolean çalıştırma;
özel BufferedImage görüntüsü;
public int[] piksel;
genel statik int[][] harita =
{
{1,1,1,1,1,1,1,1,2,2,2,2,2,2,2},
{1,0,0,0,0,0,0,0,2,0,0,0,0,0,2},
{1,0,3,3,3,3,3,0,0,0,0,0,0,0,2},
{1,0,3,0,0,0,3,0,2,0,0,0,0,0,2},
{1,0,3,0,0,0,3,0,2,2,2,0,2,2,2},
{1,0,3,0,0,0,3,0,2,0,0,0,0,0,2},
{1,0,3,3,0,3,3,0,2,0,0,0,0,0,2},
{1,0,0,0,0,0,0,0,2,0,0,0,0,0,2},
{1,1,1,1,1,1,1,4,4,4,4,0,4,4,4},
{1,0,0,0,0,0,1,4,0,0,0,0,0,0,4},
{1,0,0,0,0,0,1,4,0,0,0,0,0,0,4},
{1,0,0,2,0,0,1,4,0,3,3,3,3,0,4},
{1,0,0,0,0,0,1,4,0,3,3,3,3,0,4},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,4},
{1,1,1,1,1,1,4,4,4,4,4,4,4,4,4}
};
Haritanın istediğiniz şekilde yeniden yapılandırılabileceğini unutmayın, burada sahip olduğum şey sadece bir örnek. Haritadaki sayılar, o konumda ne tür bir duvar olacağını gösterir. 0, boş alanı temsil ederken, diğer herhangi bir sayı sağlam bir duvarı ve onunla birlikte gelen dokuyu temsil eder. BufferedImage, kullanıcıya gösterilen şeydir ve pikseller, görüntüdeki tüm piksellerin bir dizisidir. Diğer değişkenler gerçekten tekrar görünmeyecek, sadece grafiklerin ve programın düzgün çalışmasını sağlamak için kullanılıyorlar.
Yapıcı şimdilik şöyle görünecek:
public Game() {
thread = new Thread(bu);
resim = yeni BufferedImage(640, 480, BufferedImage.TYPE_INT_RGB);
piksel = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
setSize(640, 480);
setResizable(yanlış);
setTitle("3D Motoru");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBackground(Color.black);
setLocationRelativeTo(null);
setVisible(doğru);
Başlat();
}
Bunların çoğu sadece sınıf değişkenlerinin ve çerçevenin başlatılmasıdır. "Pikseller ="'den sonraki kod, pikselleri ve görüntüyü birbirine bağlar, böylece piksellerdeki veri değerleri her değiştirildiğinde, görüntü kullanıcıya gösterildiğinde karşılık gelen değişiklikler görüntüde görünür.
Başlatma ve durdurma yöntemleri basittir ve programın güvenli bir şekilde başlayıp bitmesini sağlamak için kullanılır.
özel senkronize geçersiz start() {
çalışıyor = true;
thread.start();
}
genel senkronize geçersiz durdurma () {
koşu = yanlış;
Deneyin {
thread.join();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
Game sınıfında olması gereken son iki metot render ve run metotlarıdır. Render yöntemi şöyle görünecektir:
public void render() {
BufferStrategy bs = getBufferStrategy();
if(bs == boş) {
createBufferStrategy(3);
dönüş;
}
Grafikler g = bs.getDrawGraphics();
g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
bs.show();
}
Ekran güncellemelerinin daha düzgün olması için oluşturma sırasında bir arabellek stratejisi kullanılır. Genel olarak, bir arabellek stratejisi kullanmak, oyunun çalışırken daha iyi görünmesine yardımcı olur. Görüntüyü ekrana gerçekten çizmek için, arabellek stratejisinden bir grafik nesnesi elde edilir ve görüntüümüzü çizmek için kullanılır.
Run yöntemi, programın farklı bölümlerinin ne sıklıkta güncellendiğini ele aldığı için çok önemlidir. Bunu yapmak için saniyenin 1/60'ının ne zaman geçtiğini ve ekranın ve kameranın ne zaman güncellendiğini takip etmek için bazı kodlar kullanır. Bu, programın ne kadar sorunsuz çalıştığını geliştirir. Çalıştırma yöntemi şöyle görünür:
public void run() {
long lastTime = System.nanoTime();
son çift ns = 1000000000.0 / 60.0;//saniyede 60 kez
çift delta = 0;
requestFocus();
koşarken) {
artık çok uzun = System.nanoTime();
delta = delta + ((şimdi-son Zaman) / ns);
lastTime = şimdi;
while (delta >= 1)//Güncellemenin saniyede yalnızca 60 kez yapıldığından emin olun
{
// tüm mantık kısıtlamalı zamanı yönetir
delta--;
}
render();//ekrana sınırsız süre gösterir
}
}
Tüm bu yöntemler, yapıcılar ve değişkenler bir kez girdikten sonra, Game sınıfında şu anda yapılacak tek şey bir ana yöntem eklemektir. Ana yöntem çok kolay, tek yapmanız gereken:
public static void main(String [] args) {
Game game = new Game();
}
Ve şimdi ana sınıf şu an için bitti! Programı şimdi çalıştırırsanız, siyah bir ekran açılmalıdır.
İpucu EkleSoru sorYorum Yapİndir
2. Adım: Doku Sınıfı
Linkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir.
Linkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir.
Linkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir.
Linkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir.
Ekranın nasıl görünmesi gerektiğini bulmak için hesaplamalara atlamadan önce, bir dolambaçlı yoldan gideceğim ve bir Texture sınıfı kuracağım. Dokular ortamdaki çeşitli duvarlara uygulanacak ve proje klasörüne kaydedilen görüntülerden gelecektir. Görsellerde bu projede kullanacağım internetten bulduğum 4 dokuya yer verdim. İstediğiniz dokuları kullanabilirsiniz. Bu dokuları kullanmak için proje dosyası içindeki bir klasöre koymanızı tavsiye ederim. Bunu yapmak için proje klasörüne gidin (Eclipse'de bu, çalışma alanı klasöründe bulunur). Proje klasörüne ulaştıktan sonra "res" veya benzeri bir adla yeni bir klasör oluşturun. Dokuları bu klasöre yerleştirin. Dokuları başka bir yere yerleştirebilirsin, burası benim dokularımı sakladığım yer. Bu yapıldıktan sonra dokuları kullanılabilir hale getirmek için kodu yazmaya başlayabiliriz.
Sınıf için ithalat şunlardır:
java.awt.image.BufferedImage'ı içe aktar;
java.io.File'ı içe aktarın;
java.io.IOException'ı içe aktarın;
javax.imageio.ImageIO'yu içe aktar;
Sınıf başlığı ve değişkenleri şöyle görünecektir:
public class Doku {
public int[] piksel;
özel Dize konumu;
public final int SIZE;
Dizi pikselleri, doku görüntüsündeki tüm pikseller için verileri tutmak için kullanılır. Loc, bilgisayara doku için görüntü dosyasının nerede bulunabileceğini belirtmek için kullanılır. SIZE, dokunun bir tarafta ne kadar büyük olduğudur (64x64 boyutunda bir görüntü 64 boyutunda olacaktır) ve tüm dokular tam kare olacaktır.
Yapıcı, loc ve SIZE değişkenlerini başlatacak ve görüntü verilerini piksellere yüklemek için a yöntemini çağıracaktır. Şuna benziyor:
public Texture(Dize konumu, int boyutu) {
konum = konum;
BOYUT = boyut;
piksel = new int[SIZE * SIZE];
yük();
}
Şimdi Texture sınıfı için geriye kalan tek şey, görüntülerden veri almak ve bunları bir dizi piksel verisinde depolamak için bir yükleme yöntemi eklemek. Bu yöntem şöyle görünecektir:
özel boşluk yükü () {
Deneyin {
BufferedImage resmi = ImageIO.read(yeni Dosya(loc));
int w = image.getWidth();
int h = image.getHeight();
image.getRGB(0, 0, w, h, piksel, 0, w);
} yakalama (IOException e) {
e.printStackTrace();
}
}
Yükleme yöntemi, loc'un işaret ettiği dosyadaki verileri okuyarak ve bu verileri arabelleğe alınmış bir görüntüye yazarak çalışır. Her piksel için veriler daha sonra arabelleğe alınan görüntüden alınır ve piksellerde saklanır.
Bu noktada Texture sınıfı tamamlandı, bu yüzden devam edeceğim ve son programda kullanılacak birkaç doku tanımlayacağım. Bunu yapmak için şunu koyun
public static Texture wood = new Texture("res/wood.png", 64);
public static Texture brick = new Texture("res/redbrick.png", 64);
public static Texture bluestone = new Texture("res/bluestone.png", 64);
public static Texture stone = new Texture("res/greystone.png", 64);
"public class Texture" satırı ile "public int[] piksel" arasında.
Bu dokuları programın geri kalanı için erişilebilir kılmak için devam edelim ve onları Game sınıfına verelim. Bunu yapmak için tüm dokuları tutacak bir ArrayList'e ihtiyacımız olacak ve dokuları bu ArrayList'e eklememiz gerekecek. ArrayList'i oluşturmak için aşağıdaki kod satırını değişkenlerle sınıfın en üstüne yakın bir yere koyun:
public ArrayList<Texture> dokuları;
Bu ArrayList'in yapıcıda başlatılması gerekecek ve dokular da yapıcıda buna eklenmelidir. Yapıcıda aşağıdaki kod bitini ekleyin:
dokular = new ArrayList<Texture>();
textures.add(Texture.wood);
textures.add(Texture.brick);
textures.add(Texture.bluestone);
textures.add(Texture.stone);
Ve şimdi dokular gitmek için iyi!
İpucu EkleSoru sorYorum Yapİndir
3. Adım: Kamera Sınıfı
Şimdi başka bir yoldan gidelim ve Camera sınıfını ayarlayalım. Camera sınıfı, oyuncunun 2B haritada nerede bulunduğunu takip eder ve ayrıca oyuncunun konumunu güncellemekle ilgilenir. Bunu yapmak için sınıf KeyListener'ı uygulayacak, bu nedenle KeyEvent ve KeyListener'ı içe aktarması gerekecek.
içe aktar java.awt.event.KeyEvent;<br>java.awt.event.KeyListener'ı içe aktar;
Kameranın konumunu ve ne görebileceğini takip etmek için birçok değişkene ihtiyaç vardır. Bu nedenle sınıfın ilk öbeği şöyle görünür:
public class Camera, KeyListener'ı uygular {
genel çift xPos, yPos, xDir, yDir, xPlane, yPlane;
public boolean sol, sağ, ileri, geri;
genel son çift MOVE_SPEED = .08;
public final double ROTATION_SPEED = .045;
xPos ve yPos, oyuncunun Game sınıfında oluşturulan 2B harita üzerindeki konumudur. xDir ve yDir, oyuncunun baktığı yönü gösteren bir vektörün x ve y bileşenleridir. xPlane ve yPlane ayrıca bir vektörün x ve y bileşenleridir. xPlane ve yPlane tarafından tanımlanan vektör, yön vektörüne her zaman diktir ve bir tarafta kameranın görüş alanının en uzak kenarını gösterir. Diğer taraftaki en uzak kenar sadece negatif düzlem vektörüdür. Yön vektörü ve düzlem vektörünün kombinasyonu, kameranın görüş alanında ne olduğunu tanımlar. Boolean'lar, kullanıcının kamerayı hareket ettirebilmesi için hangi tuşlara basıldığını takip etmek için kullanılır. MOVE_SPEED ve ROTATION_SPEED, kullanıcı ilgili tuşa basarken kameranın ne kadar hızlı hareket ettiğini ve döndüğünü belirler.
Sonraki, yapıcıdır. Yapıcı, sınıfa kameranın nerede olduğunu ve ne görebileceğini söyleyen değerleri alır ve bunları karşılık gelen değişkene (xPos, yPos...) atar.
genel Kamera(çift x, çift y, çift xd, çift yd, çift xp, çift yp)
{
xPos = x;
yPos = y;
xDir = xd;
yDir = yd;
xDüzlem = xp;
yDüzlem = yp;
}
Son programda bir kamera nesnesine ihtiyaç duyulacak, o yüzden devam edelim ve bir tane ekleyelim. Diğer tüm değişken bildirimleriyle birlikte Game sınıfında
genel Kamera kamerası;
ve yapıcıda ekleyin
kamera = yeni Kamera(4.5, 4.5, 1, 0, 0, -.66);
addKeyListener(kamera);
Bu kamera benim kullandığım harita ile çalışacak, farklı bir harita kullanıyorsanız veya farklı bir konumda başlamak istiyorsanız xPos ve yPos değerlerini ayarlayın (örneğimde 4 ve 6). .66 kullanmak, iyi bir görüş alanı olduğunu düşündüğüm şeyi veriyor, ancak değeri farklı bir FOV elde etmek için ayarlayabilirsiniz.
Artık Camera sınıfının bir yapıcısı olduğuna göre, kullanıcının girdilerini izlemek ve kameranın konumunu/yönünü güncellemek için yöntemler eklemeye başlayabiliriz. Camera sınıfı, KeyboardListener'ı uyguladığı için, ondan uygulanan tüm yöntemlere sahip olmalıdır. Eclipse, otomatik olarak bu yöntemleri eklemenizi ister. keyTyped yöntemini boş bırakabilirsiniz, ancak diğer iki yöntem kullanılacaktır. keyPressed, karşılık gelen tuşlara basıldığında booleanları true olarak ayarlar ve keyReleased, tuşlar bırakıldığında bunları tekrar false olarak değiştirir. Yöntemler şöyle görünür:
public void keyPressed(KeyEvent key) {
if((key.getKeyCode() == KeyEvent.VK_LEFT))
sol = doğru;
if((key.getKeyCode() == KeyEvent.VK_RIGHT))
doğru = doğru;
if((key.getKeyCode() == KeyEvent.VK_UP))
ileri = doğru;
if((key.getKeyCode() == KeyEvent.VK_DOWN))
geri = doğru;
}
ve
public void keyReleased(KeyEvent key) {
if((key.getKeyCode() == KeyEvent.VK_LEFT))
sol = yanlış;
if((key.getKeyCode() == KeyEvent.VK_RIGHT))
doğru = yanlış;
if((key.getKeyCode() == KeyEvent.VK_UP))
ileri = yanlış;
if((key.getKeyCode() == KeyEvent.VK_DOWN))
geri = yanlış;
}
Artık Camera sınıfı hangi tuşlara basıldığını takip ettiğine göre, oyuncunun pozisyonunu güncellemeye başlayabiliriz. Bunu yapmak için Game sınıfının run yönteminde çağrılan bir güncelleme yöntemini kullanacağız. Bu sırada devam edeceğiz ve Game sınıfında çağrıldığında haritayı ona ileterek güncelleme yöntemine çarpışma algılama ekleyeceğiz. Güncelleme yöntemi şöyle görünür:
public void update(int[][] map) {
if(ileri) {
if(map[(int)(xPos + xDir * MOVE_SPEED)][(int)yPos] == 0) {
xPos+=xDir*TAŞI_HIZI;
}
if(harita[(int)xPos][(int)(yPos + yDir * MOVE_SPEED)] ==0)
yPos+=yDir*MOVE_SPEED;
}
eğer(geri) {
if(harita[(int)(xPos - xDir * MOVE_SPEED)][(int)yPos] == 0)
xPos-=xDir*TAŞI_HIZI;
if(harita[(int)xPos][(int)(yPos - yDir * MOVE_SPEED)]==0)
yPos-=yDir*MOVE_SPEED;
}
if(sağ) {
çift eskixDir=xDir;
xDir=xDir*Math.cos(-ROTATION_SPEED) - yDir*Math.sin(-ROTATION_SPEED);
yDir=oldxDir*Math.sin(-ROTATION_SPEED) + yDir*Math.cos(-ROTATION_SPEED);
double oldxPlane = xPlane;
xPlane=xPlane*Math.cos(-ROTATION_SPEED) - yPlane*Math.sin(-ROTATION_SPEED);
yPlane=oldxPlane*Math.sin(-ROTATION_SPEED) + yPlane*Math.cos(-ROTATION_SPEED);
}
if(sol) {
çift eskixDir=xDir;
xDir=xDir*Math.cos(ROTATION_SPEED) - yDir*Math.sin(ROTATION_SPEED);
yDir=oldxDir*Math.sin(ROTATION_SPEED) + yDir*Math.cos(ROTATION_SPEED);
double oldxPlane = xPlane;
xPlane=xPlane*Math.cos(ROTATION_SPEED) - yPlane*Math.sin(ROTATION_SPEED);
yPlane=oldxPlane*Math.sin(ROTATION_SPEED) + yPlane*Math.cos(ROTATION_SPEED);
}
}
Yöntemin ileri ve geri hareketi kontrol eden kısımları xPos ve yPos'a sırasıyla xDir ve yDir ekleyerek çalışır. Bu hareket gerçekleşmeden önce program, hareketin kamerayı bir duvarın içine sokup sokmayacağını kontrol eder ve eğer yapacaksa hareketi gerçekleştirmez. Döndürme için hem yön vektörü hem de düzlem vektörü, dönüş matrisi ile çarpılır, bu:
[ cos(ROTATION_SPEED) -sin(ROTATION_SPEED) ]
[ günah(ROTATION_SPEED) cos(ROTATION_SPEED) ]
yeni değerlerini elde etmek için. Güncelleme yöntemi tamamlandıktan sonra artık Game sınıfından çağırabiliriz. Game sınıfının çalıştırma yönteminde, burada gösterildiği yere aşağıdaki kod satırını ekleyin
Bunu ekle:
camera.update(harita);
burada:
koşarken) {
artık çok uzun = System.nanoTime();
delta = delta + ((şimdi-son Zaman) / ns);
lastTime = şimdi;
while (delta >= 1)//Güncellemenin saniyede yalnızca 60 kez yapıldığından emin olun
{
// tüm mantık kısıtlamalı zamanı yönetir
camera.update(harita);
delta--;
}
render();//ekrana sınırsız süre gösterir
}
Artık bu bittiğine göre, nihayet son sınıfa geçebilir ve ekranı hesaplayabiliriz!
İpucu EkleSoru sorYorum Yapİndir
Adım 4: Ekranın Hesaplanması
Screen sınıfı, programın çalışmasını sağlamak için hesaplamaların çoğunun yapıldığı yerdir. Çalışmak için sınıfın aşağıdaki içe aktarmalara ihtiyacı vardır:
java.util.ArrayList'i içe aktarın;
java.awt.Color'ı içe aktar;
Gerçek sınıf şöyle başlar:
public class Ekran {
public int[][] map;
public int mapWidth, mapHeight, genişlik, yükseklik;
genel ArrayList dokuları;
Harita, oyun sınıfında oluşturulan haritanın aynısıdır. Ekran bunu duvarların nerede olduğunu ve oyuncudan ne kadar uzakta olduklarını bulmak için kullanır. Genişlik ve yükseklik ekranın boyutunu tanımlar ve her zaman Game sınıfında oluşturulan çerçevenin genişliği ve yüksekliği ile aynı olmalıdır. Dokular, ekranın dokuların piksellerine erişebilmesi için tüm dokuların bir listesidir. Bu değişkenler bildirildikten sonra, yapıcıda şu şekilde başlatılmaları gerekir:
public Screen(int[][] m, ArrayList tex, int w, int h) {
harita = m;
dokular = teks;
genişlik = w;
yükseklik = h;
}
Şimdi sınıfın sahip olduğu tek yöntemi yazma zamanı: bir güncelleme yöntemi. Güncelleme yöntemi, haritadaki konumlarına göre ekranın kullanıcıya nasıl görünmesi gerektiğini yeniden hesaplar. Yöntem sürekli olarak çağrılır ve güncellenmiş piksel dizisini Game sınıfına döndürür. Yöntem, ekranı "temizleyerek" başlar. Bunu, üst yarıdaki tüm pikselleri bir renge ve alttaki tüm pikselleri diğerine ayarlayarak yapar.
public int[] güncelleme(Kamera kamerası, int[] piksel) {
for(int n=0; n<piksel.uzunluk/2; n++) {
if(piksel[n] != Color.DARK_GRAY.getRGB()) piksel[n] = Color.DARK_GRAY.getRGB();
}
for(int i=piksel.uzunluk/2; i<piksel.uzunluk; i++) {
if(piksel != Color.gray.getRGB()) piksel = Color.gray.getRGB();
}
Ekranın üst ve alt kısmının iki farklı renk olması da zemin ve tavan varmış gibi görünmesini sağlıyor. Piksel dizisi temizlendikten sonra ana hesaplamalara geçme zamanıdır. Program, ekrandaki her dikey çubuktan geçer ve o dikey çubukta ekranda hangi duvarın olması gerektiğini bulmak için bir ışın gönderir. Döngünün başlangıcı şöyle görünür:
for(int x=0; x<genişlik; x=x+1) {
çift kameraX = 2 * x / (çift)(genişlik) -1;
double rayDirX = camera.xDir + camera.xPlane * cameraX;
double rayDirY = camera.yDir + camera.yPlane * cameraX;
//harita konumu
int mapX = (int)camera.xPos;
int mapY = (int)camera.yPos;
//geçerli konumdan sonraki x veya y tarafına kadar ışının uzunluğu
çift taraflıDistX;
çift taraflıDistY;
//Haritada bir taraftan diğerine ışın uzunluğu
double deltaDistX = Math.sqrt(1 + (rayDirY*rayDirY) / (rayDirX*rayDirX));
double deltaDistY = Math.sqrt(1 + (rayDirX*rayDirX) / (rayDirY*rayDirY));
çift perpWallDist;
//x ve y'de gidilecek yön
int adımX, adımY;
boole isabeti = yanlış;//duvar isabetiydi
int yan=0;//duvar dikey mi yoksa yatay mıydı
Burada olan tek şey, döngünün geri kalanı tarafından kullanılacak bazı değişkenlerin hesaplanmasıdır. CameraX, kamera düzlemindeki geçerli dikey şeridin x koordinatıdır ve rayDir değişkenleri, ışın için bir vektör oluşturur. DistX veya DistY ile biten tüm değişkenler hesaplanır, böylece program yalnızca çarpışmaların meydana gelebileceği yerlerde çarpışmaları kontrol eder. perpWallDist, oynatıcıdan ışının çarptığı ilk duvara kadar olan mesafedir. Bu daha sonra hesaplanacaktır. Bu yapıldıktan sonra, daha önce hesapladığımıza dayalı olarak diğer birkaç değişkeni bulmamız gerekiyor.
//
(rayDirX < 0) ise adım yönünü ve bir tarafa olan başlangıç mesafesini hesaplayın
{
adımX = -1;
sideDistX = (camera.xPos - mapX) * deltaDistX;
}
Başka
{
adımX = 1;
sideDistX = (mapX + 1.0 - camera.xPos) * deltaDistX;
}
if (rayDirY < 0)
{
adımY = -1;
sideDistY = (camera.yPos - mapY) * deltaDistY;
}
Başka
{
adımY = 1;
sideDistY = (mapY + 1.0 - camera.yPos) * deltaDistY;
}
Bu yapıldıktan sonra, ışının bir duvarla nerede çarpıştığını bulmanın zamanı geldi. Bunu yapmak için program, ışının bir duvarla temas edip etmediğini kontrol ettiği ve tekrar kontrol etmeden önce bir sonraki olası çarpışma noktasına hareket edip etmediğini kontrol ettiği bir döngüden geçer.
//Işının duvara çarptığı yeri bulmak için döngü yapın
while(!hit) {
//Bir sonraki kareye atla
if (sideDistX < sideDistY)
{
sideDistX += deltaDistX;
mapX += stepX;
yan = 0;
}
Başka
{
sideDistY += deltaDistY;
mapY += stepY;
yan = 1;
}
//Ray'in duvara çarpıp çarpmadığını kontrol edin
if(map[mapX][mapY] > 0) isabet = doğru;
}
Artık ışının duvara çarptığı yeri bildiğimize göre, üzerinde bulunduğumuz dikey şeritte duvarın nasıl görünmesi gerektiğini bulmaya başlayabiliriz. Bunu yapmak için önce duvara olan mesafeyi hesaplıyoruz ve sonra bu mesafeyi duvarın dikey şeritte ne kadar uzun görünmesi gerektiğini bulmak için kullanıyoruz. Daha sonra bu yüksekliği ekrandaki pikseller açısından bir başlangıca ve bitişe çeviriyoruz. Kod şöyle görünür:
// Çarpma noktasına olan mesafeyi hesapla
if(yan==0)
perpWallDist = Math.abs((mapX - camera.xPos + (1 - stepX) / 2) / rayDirX);
Başka
perpWallDist = Math.abs((mapY - camera.yPos + (1 - stepY) / 2) / rayDirY);
//Şimdi kameradan uzaklığa göre duvarın yüksekliğini hesapla
int lineHeight;
if(perpWallDist > 0) lineHeight = Math.abs((int)(height / perpWallDist));
else lineHeight = yükseklik;
//geçerli şeridi doldurmak için en düşük ve en yüksek pikseli hesapla
int drawStart = -lineYükseklik/2+ yükseklik/2;
if(drawStart < 0)
drawStart = 0;
int drawEnd = lineHeight/2 + yükseklik/2;
if(drawEnd >= yükseklik)
drawEnd = yükseklik - 1;
Bu hesaplandıktan sonra, duvarın dokusundan hangi piksellerin kullanıcıya gerçekten görüneceğini bulmaya başlamanın zamanı geldi. Bunun için önce çarptığımız duvarla hangi dokunun ilişkili olduğunu bulmalı ve ardından kullanıcıya görünecek piksellerin dokusu üzerindeki x koordinatını bulmalıyız.
//bir doku ekle
int texNum = map[mapX][mapY] - 1;
double wallX;//Duvarın çarpıldığı yerin tam konumu
if(yan==1) {//Y ekseni duvarı ise
wallX = (camera.xPos + ((mapY - camera.yPos + (1 - stepY) / 2) / rayDirY) * rayDirX);
} başka {//X ekseni duvarı
wallX = (camera.yPos + ((mapX - camera.xPos + (1 - stepX) / 2) / rayDirX) * rayDirY);
}
wallX-=Math.floor(wallX);
// doku üzerinde x koordinatı
int texX = (int)(duvarX * (texture.get(texNum).SIZE));
if(yan == 0 && rayDirX > 0) texX = textures.get(texNum).SIZE - texX - 1;
if(yan == 1 && rayDirY < 0) texX = textures.get(texNum).SIZE - texX - 1;
X-koordinatı, 2B haritada duvarın çarpıldığı yerin tam konumu alınarak ve tamsayı değeri çıkarılarak, yalnızca ondalık sayı bırakılarak hesaplanır. Bu ondalık sayı (wallX), çizmek istediğimiz piksellerin duvarındaki tam x koordinatını elde etmek için duvarın dokusunun boyutuyla çarpılır. Tek yapmamız gereken doku üzerindeki piksellerin y-koordinatlarını hesaplamak ve ekrana çizmek olduğunu anladığımızda. Bunu yapmak için, hesaplamalar yaptığımız dikey şeritte ekrandaki tüm pikseller arasında dolaşıyoruz ve doku üzerindeki pikselin tam y-koordinatını hesaplıyoruz. Program bunu kullanarak dokudaki pikselden gelen verileri ekrandaki piksel dizisine yazar. Program ayrıca temel bir aydınlatma efekti vermek için yatay duvarları dikey duvarlardan daha koyu yapar.
//doku üzerinde y koordinatını hesapla
for(int y=drawStart; y<drawEnd; y++) {
int texY = (((y*2 - yükseklik + satırYüksekliği) << 6) / satırYüksekliği) / 2;
int renk;
if(side==0) color = textures.get(texNum).pixels[texX + (texY * textures.get(texNum).SIZE)];
else color = (texture.get(texNum).pixels[texX + (texY * textures.get(texNum).SIZE)]>>1) & 8355711;//Y tarafı daha koyu yap
piksel[x + y*(genişlik)] = renk;
}
Bundan sonra, Screen sınıfında kalan tek şey piksel dizisini döndürmek.
dönüş pikselleri;
Ve ders bitti. Şimdi tek yapmamız gereken, ekranın çalışmasını sağlamak için Game sınıfına birkaç satır kod eklemek. En üstteki değişkenlerle şunu ekleyin:
genel Ekran ekranı;
Ve yapıcıda, dokular başlatıldıktan sonra bunu bir yere ekleyin.
ekran = yeni Ekran(harita, mapWidth, mapHeight, dokular, 640, 480);
Ve son olarak, çalıştırma yönteminde ekleyin
screen.update(kamera, piksel);
camera.update(harita)'dan hemen önce. Ve program tamamlandı!
İpucu EkleSoru sorYorum Yapİndir
Adım 5: Son Kod
İşte her sınıf için kodun tam bir kopyası.
ekler
oyun.txt![]()
İndir doku.txt![]()
İndir kamera.txt![]()
Linkleri görebilmek için Turkmmo Forumuna ÜYE olmanız gerekmektedir. Ekran.txt![]()
Şu an konuyu görüntüleyenler (Toplam : 0, Üye: 0, Misafir: 0)
Benzer konular
- Cevaplar
- 13
- Görüntüleme
- 1K
- Cevaplar
- 14
- Görüntüleme
- 724





