Basit Yol Bulma

NextBot'lar için otomatik yol bulma

NextBot'lar, mevcut konumlarından hedeflerine nasıl gideceklerini hesaplamak için navmesh'i kullanır.

Bu işlem, PathFollower gibi yardımcı sınıflar kullanılarak yapılır ve yol oluşturma işleminin çoğu dahili olarak gerçekleştirilir.

Ancak, PathFollower:Compute fonksiyonunun 3. argümanını kullanarak, NextBot'ların yolu hesaplarken nerelere gidip gidemeyeceğini etkileyebilirsiniz.

<!-- Belki daha sonra buraya örnekler eklenebilir -->

Aşağıda, yol oluşturma üzerinde tam kontrole sahip olmanızı sağlayan ve böylece bunun sadece NextBot'larla sınırlı kalmamasını sağlayan örnekler bulacaksınız.

Terminoloji

  • navmesh - Navigasyon ağı.
  • agent (ajan/aktör) - Ajan, oluşturulan yolu kullanacak olan varlıktır, örneğin bir oyuncu botu veya bir NPC.
  • path (yol) - Oluşturulan yol, ajanın hedefe ulaşmak için geçmesi gereken bir dizi düğüm.
  • node (düğüm) - Ulaşılabilecek pozisyonları belirten, birbirine bağlı noktalar.

A* ve navmesh kütüphanesi kullanarak manuel yol bulma

CNavArea, oyuncu botları için yol bulma gibi kendi amaçlarınız için tamamen özel bir A* yol bulma algoritmasını hızlıca kurmanıza olanak tanıyan metotlar sağlar.

Bu metotlar şunlardır:

Bunların hepsi A* yol bulma algoritmasında kullanılan temel fonksiyonlardır.

İşte bu metotları kullanarak Wikipedia'daki temel bir A* algoritmasını nasıl uygulayacağınıza dair bir örnek:

function Astar( start, goal ) if ( !IsValid( start ) || !IsValid( goal ) ) then return false end if ( start == goal ) then return true end start:ClearSearchLists() start:AddToOpenList() local cameFrom = {} start:SetCostSoFar( 0 ) start:SetTotalCost( heuristic_cost_estimate( start, goal ) ) start:UpdateOnOpenList() while ( !start:IsOpenListEmpty() ) do local current = start:PopOpenList() // Açık listedeki en düşük maliyetli alanı kaldır ve geri döndür if ( current == goal ) then return reconstruct_path( cameFrom, current ) end current:AddToClosedList() for k, neighbor in pairs( current:GetAdjacentAreas() ) do local newCostSoFar = current:GetCostSoFar() + heuristic_cost_estimate( current, neighbor ) if ( neighbor:IsUnderwater() ) then // Buraya kendi alan filtrelerinizi veya başka şeyleri ekleyin continue end if ( ( neighbor:IsOpen() || neighbor:IsClosed() ) && neighbor:GetCostSoFar() <= newCostSoFar ) then continue else neighbor:SetCostSoFar( newCostSoFar ); neighbor:SetTotalCost( newCostSoFar + heuristic_cost_estimate( neighbor, goal ) ) if ( neighbor:IsClosed() ) then neighbor:RemoveFromClosedList() end if ( neighbor:IsOpen() ) then // Bu alan zaten açık listede, maliyetleri sıralı tutmak için listedeki konumunu güncelle neighbor:UpdateOnOpenList() else neighbor:AddToOpenList() end cameFrom[ neighbor:GetID() ] = current:GetID() end end end return false end function heuristic_cost_estimate( start, goal ) // Belki hangi köşenin en yakın/en uzak olduğu veya başka bir şey üzerine bazı hesaplamalarla oynayın return start:GetCenter():Distance( goal:GetCenter() ) end // CNavArea'ları tablo anahtarı olarak kullanmak çalışmaz, bu yüzden ID'leri kullanıyoruz function reconstruct_path( cameFrom, current ) local total_path = { current } current = current:GetID() while ( cameFrom[ current ] ) do current = cameFrom[ current ] table.insert( total_path, navmesh.GetNavAreaByID( current ) ) end return total_path end

Bu örnekte, Astar() fonksiyonu 2 argüman alır: Başlangıç CNavArea'sı, örneğin oyuncu botunun veya NPC'nin o an bulunduğu yer, ve hedef CNavArea'sı, yani botun veya NPC'nin gitmek istediği yer.

Astar() fonksiyonu 3 değerden birini döndürür:

  • Hedef CNavArea'sına fiziksel olarak ulaşmak imkansızsa, false döndürür
  • Başlangıç CNavArea'sı hedef CNavArea ile aynıysa, true döndürür
  • Aksi takdirde, botun/NPC'nin hedefe ulaşmak için içinden geçmesi gereken bir CNavArea dizisi döndürür (birbiri ardına, hedef CNavArea dizideki ilk girdi olacak şekilde, gerekirse dizi sırasını tersine çevirmek için table.Reverse kullanın).

Aşağıda, bu örnek fonksiyonu kullanarak oyuncu botlarını yol bulma ile belirli bir hedefe nasıl hareket ettireceğinize dair birkaç örnek bulacaksınız.

Başlangıç ve hedef pozisyonları olarak vektörleri kullanma

İstediğiniz Vector pozisyonuna en yakın CNavArea'yı bulmak için navmesh.GetNearestNavArea fonksiyonunu kullanabilirsiniz.

İşte Astar() fonksiyonu için girdi olarak 2 vektör alan bir örnek fonksiyon:

function AstarVector( start, goal ) local startArea = navmesh.GetNearestNavArea( start ) local goalArea = navmesh.GetNearestNavArea( goal ) return Astar( startArea, goalArea ) end

A*, alanlarla değil, nokta düğümleriyle en iyi şekilde çalışır

Garry's Mod'da, navigasyon ağı Vector pozisyonlarından değil, CNavArea'lardan oluşur. Sonuç olarak, buradaki örneklerin ajanı çoğunlukla bir CNavArea'nın merkezinden diğerinin merkezine taşıdığını fark edeceksiniz. Bu, oluşturulan yolların doğal görünmemesine neden olur.

Daha doğal yollar elde etmek için, ajanın içinden geçmesi gereken pozisyonları oluştururken, CNavArea'ların köşelerini kullanmak, köşeleri kesmek veya belirli pozisyonları atmak için Görüş Hattı (Line of Sight) kontrollerini kullanmak gibi daha gelişmiş teknikler gerekecektir.

Yol bulma kolay değildir

Lütfen tüm bu örneklerin eğitim amaçlı olarak olabildiğince basit tutulduğunu unutmayın. Mutlak en verimli yolları oluşturmayabilir. Yolların doğal hissettirmesi veya verimli olması için ajanın hareket koduna daha fazla çalışma yapılması gerekecektir.

Oluşturulan yolu test etmek ve görselleştirmek için örnek konsol komutu

Bu örnek kod, oluşturulan yolu debugoverlay kullanarak doğrudan Astar() fonksiyonundan çizecek bir fonksiyon içerir.

function drawThePath( path, time ) local prevArea for _, area in pairs( path ) do debugoverlay.Sphere( area:GetCenter(), 8, time or 9, color_white, true ) if ( prevArea ) then debugoverlay.Line( area:GetCenter(), prevArea:GetCenter(), time or 9, color_white, true ) end area:Draw() prevArea = area end end

Lütfen debugoverlay'in yalnızca çok oyunculu modda ve yalnızca developer konsol komutu sıfırdan farklı bir değere ayarlandığında çalıştığını unutmayın.

Bu örnek, yol oluşturma işleminizi test etmek ve hatalarını ayıklamak için Astar() ve drawThePath() fonksiyonlarını birlikte nasıl kullanabileceğinizi gösterir.

concommand.Add( "test_astar", function( ply ) // Konsol komutunu çalıştıran oyuncunun başlangıç pozisyonunu kullan local start = navmesh.GetNearestNavArea( ply:GetPos() ) // Hedef pozisyon, bu örnek için oyuncunun nişan aldığı pozisyonu kullan local goal = navmesh.GetNearestNavArea( ply:GetEyeTrace().HitPos ) local path = Astar( start, goal ) if ( !istable( path ) ) then // Hedefe fiziksel olarak ulaşamıyoruz veya zaten hedefin içindeyiz. return end PrintTable( path ) // Hata ayıklama için oluşturulan yolu konsola yazdır drawThePath( path ) // Oluşturulan yolu 9 saniye boyunca çiz end)

Oyuncu botları ile örnek kullanım

Bu örnek, bir oyuncu botunu (bir ajan) oluşturulan yol boyunca hareket ettirmenin en temel yolunu gösterir:

local rePathDelay = 1 // Yolu güncel tutmak için yeniden oluşturmadan önce kaç saniye geçmesi gerektiği hook.Add( "StartCommand", "astar_example", function( ply, cmd ) // Bu kodu sadece botlar üzerinde ve sadece bot_mimic 0'a ayarlıysa çalıştır if ( !ply:IsBot() || GetConVarNumber( "bot_mimic" ) != 0 ) then return end cmd:ClearButtons() cmd:ClearMovement() local currentArea = navmesh.GetNearestNavArea( ply:GetPos() ) // hedef oyuncuyla aynı hızda kalmak için yolu her X saniyede bir yeniden oluşturmak için dahili değişken ply.lastRePath = ply.lastRePath or 0 // yolun ne sıklıkta (yeniden) oluşturulabileceğini sınırlamak için dahili değişken ply.lastRePath2 = ply.lastRePath2 or 0 if ( ply.path && ply.lastRePath + rePathDelay < CurTime() && currentArea != ply.targetArea ) then ply.path = nil ply.lastRePath = CurTime() end if ( !ply.path && ply.lastRePath2 + rePathDelay < CurTime() ) then local targetPos = Entity( 1 ):GetPos() // gidilecek hedef pozisyon, sunucudaki ilk oyuncu local targetArea = navmesh.GetNearestNavArea( targetPos ) ply.targetArea = nil ply.path = Astar( currentArea, targetArea ) if ( !istable( ply.path ) ) then // Hedefle aynı alandayız veya hedefe gidemiyoruz ply.path = nil // Yolu temizle, vazgeç ve bir dahaki sefere tekrar dene ply.lastRePath2 = CurTime() return end //PrintTable( ply.path ) // TODO: Alan kesişimlerine ara noktalar ekle // TODO: Son alanda, son alanın merkezine değil, hedef pozisyona doğru hareket et table.remove( ply.path ) // Sadece bu örnek için, başlangıç alanını kaldır, zaten içindeyiz! end // Yolumuz yok veya boş (hedefe vardık), yeni bir yol bulmaya çalış. if ( !ply.path || #ply.path < 1 ) then ply.path = nil ply.targetArea = nil return end // Hedefimize giden bir yolumuz var! drawThePath( ply.path, .1 ) // Hata ayıklama için yolu çiz // Gitmek istediğimiz bir sonraki alanı seç if ( !IsValid( ply.targetArea ) ) then ply.targetArea = ply.path[ #ply.path ] end // Seçtiğimiz alan geçersiz veya zaten oradayız, kaldır, vazgeç ve bir sonraki döngüyü bekle if ( !IsValid( ply.targetArea ) || ( ply.targetArea == currentArea && ply.targetArea:GetCenter():Distance( ply:GetPos() ) < 64 ) ) then table.remove( ply.path ) // Son elemanı kaldırır ply.targetArea = nil return end // Gidilecek hedefi bulduk, oraya nişan al ve HAREKET ET local targetang = ( ply.targetArea:GetCenter() - ply:GetPos() ):GetNormalized():Angle() cmd:SetViewAngles( targetang ) cmd:SetForwardMove( 1000 ) end )

Bu örnek, sunucudaki tüm botların sunucudaki ilk oyuncuya doğru hareket etmesini sağlayacaktır. Oluşturulan yol güzel değil, ancak bu örneğin amacı, bir ajanı oluşturulmuş bir yol boyunca hareket ettirmek için yolun nasıl kullanılacağını göstermektir.

Tüm sunucular · GmodTR Launcher · Rehber · GMod Wiki · GmodTR nedir? · Sunucu ekle · Discord