Raycasting'i uygulamak için kullandığım formülleri burada açıklayacağım. Bu raycast, Cub3D adlı 3D bir oyun oluşturmak için yapılmıştır. Proje hala devam etmektedir ⚙️🛠.
Not: Henüz tuşlarla ilgili bitmiş değilim .. Ve evet, oyuncu duvarların içinden geçebilir 🧟♀️
Eğer raycasting veya oyun geliştirmeyle ilgili herhangi bir konuda spesifik sorularınız varsa, sormaktan çekinmeyin!
Raycasting'in genel fikri basittir ve formüller çok karmaşık değildir. Duvarları oluşturmak, çizmek ve etrafında hareket etmek için kullandığım hesaplamaları açıklayacağım.
Öncelikle, formüllerde kullandığım matematik kavramlarını size anlatayım.. Right triangle trigonometry (SOHCAHTOA), and Vectors: Components(X , Y), Quantity (magnitude and direction).
Formüllerin ana fikrini açıklamak için, oyuncunun 2D harita üzerindeki konumunu (x, y) koordinatlarıyla varsayalım. "Oyuncu, kendisinin önündeki duvara (KUZEY) bakıyor, diyelim ki bakış açısı 90 ve doğrudan kuzeye doğru bakıyor." Kendi koordinatından duvarın noktasına olan mesafe bir ışını temsil eder. Oyuncunun Görüş Açısı (FOV)'na bağlı olarak ışınların sayısı belirlenecektir. "Eğer FOV 120 ise, bakış genişliği bakış açısından (90) sola 60 derece ve sağa 60 derece olacak şekilde 60 derece olur".
Oyuncunun koordinatını ve bakış açısını kullanarak, ışının vektörünü (X ve Y bileşenleri) hesaplayabiliriz. Ardından, ışınların dikey ve yatay olarak duvara ulaşabileceği noktaların ofsetini (ızgara çizgisi aykırısı) hesaplayabiliriz
Her bir ışın, yatay veya dikey olarak bir ızgara çizgisine çarptığında, aslında duvarın olup olmadığını kontrol etmemiz gereken nokta olmalıdır. Bunun için öncelikle oyuncunun koordinatlarından hareketle ışının vektörünü hesaplamamız gerekiyor, ardından ilk ızgara çizgisine çarpmak için belirli bir değeri (parametreleri) ekliyoruz ve ardından duvara ulaşmak için her bir ızgara çizgisine çarpmak için sürekli bir sabit değeri ekliyoruz. Bu sabit değerlere de (Izgara Ofsetleri) denir.
Yatay Izgara Çizgileri, (KUZEY ve GÜNEY) veya 2D haritanın üst ve alt kenarlarıdır. Dikey Izgara Çizgileri ise (BATI ve DOĞU) veya 2D haritanın sol ve sağ kenarlarıdır. Oyuncunun bakış açısına bağlı olarak, ışının aslında dikey olarak nereye çarptığını (sol veya sağ) ve yatay olarak nereye çarptığını (yukarı veya aşağı) belirleyebiliriz. Bunun öncesinde aslında ışının çizgisi ve ışının vektörünü nasıl hesaplayabileceğimizi anlamamız gerekiyor.
Işının vektörünü (X ve Y bileşenleri) ışın Y ve ışın X elde etmek için bilinmesi gereken ilk şey, sağ üçgenin kurallarını kullanabileceğimizdir.
Bu, ışının ızgara çizgilerine çarptığında nasıl görüneceğiyle ilgili bir örnektir. Onları çizmek için desmos kullandım. Ve ofsetleri ve parametreyi manuel olarak ekledim, bakış açısı 60 ve pozisyon (77, 77) için. Kırmızı nokta oyuncunun pozisyonunu, turuncu noktalar üst tarafı, yeşil noktalar alt tarafı, siyah noktalar sol tarafı ve mor noktalar sağ tarafı temsil eder.
Yatay Izgara Çizgisi Kontrol Edici Formülleri hesaplamak için şu adımları izleyebiliriz:
Işın Y noktası, oyuncunun Y koordinatı olmalıdır.
Ardından ışın X noktasını elde etmek için sağ üçgen kurallarını (SOHCAHTOA) kullanabiliriz. Işın hattı Hipotenüs olacak, Karşıt ise oyuncunun Y koordinatı ile Işın Y noktası arasındaki fark olacak ve Yanlış ise aradığımız ışın X bileşeni olacak. Dolayısıyla ışın X, karşıt (ışın Y) / tan(bakış açısı) olarak hesaplanır.
Bunları ızgara boyutuna (64) ölçeklendirmek için:
Işın Y: Öncelikle oyuncunun Y noktasını 64 birime ölçeklendirmeniz gerekmektedir. ((pY / 64) * 64); Işın X: Oyuncunun X koordinatını eklemelisiniz. (+ pX); Bir sonraki yatay ızgara çizgisine çarpmalarını sağlamak için bir parametre eklememiz gerekmektedir:
Işın Y noktasına, oyuncu pozisyonundan bir sonraki yatay ızgara çizgisine olan mesafeyi eklemeniz gerekmektedir (oyuncu Y koordinatı ile bir sonraki Y çizgisi arasındaki fark), böylece bir sonraki yatay ızgara çizgisine çarpabilir.
rayY = ((pY / 64) * 64) + (Y.line - pY);
rayX = (pY - rayY) / -tan(looking angle) + pX;
Eğer oyuncu (Güney) yönüne bakıyorsa, yön yatayda farklı olduğu için işaretler sadece değişecektir. Aşağıdaki gibi güncellenir:
rayY = ((pY / 64) * 64) - (Y.line - pY);
rayX = (pY - rayY) / -tan(looking angle) + pX;
Dikey ızgara çizgisi çarpmasını kontrol etmek için, aynı mantığı kullanırız ancak zıttıdır. Hesaplama sadece ışın Y'ye bağlı olacaktır, çünkü ışın Y noktasına çarpabilecek Y çizgilerine bağlıdır. Daha önce yatay kontrol edicilerde ışın X'ye bağlıydı, çünkü X çizgilerine çarpabiliyordu.
Bu nedenle formül şu şekilde değişir:
rayX = ((pX / 64) * 64) + (X.line - pX);
rayY = (pX - rayX) / -tan(looking angle) + pY;
rayX = ((pX / 64) * 64) - (X.line - pX);
rayY = (pX - rayX) / -tan(looking angle) + pY;
Ofset, temel olarak hesaplamanın hatadan veya aykırı noktadan çıktığını veya hangi noktaya çarpabileceğini belirtmek için kullanılan bir miktar veya değeri ifade eder. Burada, bir sonraki ızgara çizgisine çarpmak için her seferinde eklemek istediğimiz değeri ifade eder. Yani, ışınların tam olarak ızgara çizgilerine çarpmasını istiyoruz. Bu nedenle, bu değerleri hesaplamak için tekrar SOHCAHTOA kullanacağız.
Y ofseti ızgara boyutu (64) olacaktır. Bu şekilde bir sonraki yatay çizgiye çarpabilir. X ofsetini elde etmek için (SOHCAHTOA) -> Y ofseti * Tan formülünü kullanabiliriz.
oY = 64;
oX = oY * tan(looking angle);
ofsetler aynı olacak, sadece Y'nin yönü değişecektir.
oY = -64;
oX = oY * tan(looking angle);
X ofseti ızgara boyutu (64) olacaktır. Bu şekilde bir sonraki dikey çizgiye çarpabilir. Y ofsetini elde etmek için (SOHCAHTOA) -> X ofseti * Tan formülünü kullanabiliriz.
oX = 64;
oY = oX * tan(looking angle);
ofsetler aynı olacak, sadece X'in yönü değişecektir.
oX = -64;
oY = oX * tan(looking angle);
Sonra, duvar kontrol döngüsünü gerçekleştiririz. Döngüde, ışın değerlerine ofset değerlerini ekleriz, böylece duvara çarpıncaya kadar devam eder.
while (!wall)
{
rayY += oY;
rayX += oX;
}
Örneğin, diyelim ki oyuncunun bakış açısı 60'tır. Kuzey'e doğru Doğu'ya bakıyor. Dikey olarak çizgi sağ tarafa çarpmalı ve yatay olarak üst tarafa çarpmalıdır. Kullanacağımız formüller ise şunlardır:
rayY = ((pY / 64) * 64) + (Y.line - pY);
rayX = (pY - rayY) / -tan(looking angle) + pX;
oX = 64;
oY = oX * tan(looking angle);
Ve dikey olarak, çevirsek:
rayX = ((pX / 64) * 64) + (X.line - pX);
rayY = (pX - rayX) / -tan(looking angle) + pY;
oY = 64;
oX = oY * tan(looking angle);
Şimdi duvar kontrol döngüsünü yapabiliriz (ışın noktalarına ofsetleri ekleyerek), duvara çarpıncaya kadar devam ederiz. Ardından dikey kontrol ve yatay kontrol için ışının hattını hesaplayarak en kısa olanı seçeriz (duvara ilk çarpan).
Böylece bakış açısına bağlı olarak duvara kadar ulaşan noktaları elde etmek için doğru formülleri seçebiliriz.
Duvara ilk çarpanın dikey mi yoksa yatay mı ızgara çizgisi kontrol edicisinin olduğunu kontrol etmek için bir döngü oluşturun. Ardından, ışının duvara çarptığı nokta ile oyuncunun konumu arasındaki mesafeyi hesaplayın ve bu ışın hattının uzunluğu olmalıdır. Bu hesaplama için mesafeyi hesaplamak için Pythagoras formülünü kullanabilirsiniz.pythagorean rule to calculate the length of the line between two points.
Bu değeri (ışının uzunluğunu) çizim için kaydedin, ancak öncelikle ekranın genişliği ve yüksekliğinin nasıl ölçeklendirilmesi gerektiğini açıklayalım. Nasıl görünmesi gerektiği hakkında bilgi verelim!
Ekranın genişliği sabit olacak. Ekran genişliğini istediğiniz şekilde seçebilirsiniz. Ekran genişliği, oyuncunun görebileceği görüntünün genişliğidir. Genişlik, ışınların sayısı olacaktır. Her bir ışın arasındaki açı, FOV / genişlik olmalıdır.
angle = looking_angle - (FOV / 2);
while (--WIDTH)
{
ray[i].angle = angle;
angle += (FOV / WIDTH);
}
Ekranın yüksekliği de sabit olacak. Ekran yüksekliği, duvarın ulaşabileceği maksimum yüksekliktir ve istediğiniz bir değeri seçebilirsiniz. Her duvarın yüksekliğini elde etmek için ızgara boyutunu (64) ekran yüksekliğiyle çarpıp ışın uzunluğuna bölmelisiniz.
wall_height = (64 * HEIGHT) / ray_len;
Tamam, hepsini bir araya getirelim. Tüm bu fikirleri kullanarak sonunda şu adımları takip edebiliriz. Işınların sayısı genişlik boyutu olduğundan bu döngüyü oluşturabiliriz, döngü içinde aşağıdaki adımları gerçekleştirmeliyiz.
1- Izgara çizgisi kontrolünü yapın ve son ışın noktalarını elde edin.
2- Duvar yüksekliğini, ekranın YÜKSEKLİĞİ ve ışın uzunluğunu kullanarak hesaplayın.
3- Duvarların başlangıç noktasını ve bitiş noktasını hesaplayın.
4- Çizmeye başlayın.
x = -1;
while (++x < WIDTH)
{
check_gridline(ray[x]);
ray[x].wall_height = (64 * HEIGHT) / ray[x].ray_len;
begin = (HEIGHT / 2) - (wall_height / 2);
end = (HEIGHT / 2) + (wall_height / 2);
y = begin - 1;
while (++y < end)
draw_line(x, y);
}