# NextBot NPC Oluşturma

- **İngilizce aslı:** https://wiki.facepunch.com/gmod/NextBot_NPC_Creation
- **Çeviri tarihi:** 2025-09-08

Nextbot, Team Fortress 2 ve Left 4 Dead'deki yapay zekanın kullandığı sistemdir. Bu eğitim, düşmanları (sizi) arayacak ve ölene ya da çok uzaklaşana kadar onları kovalayacak basit bir yapay zeka için gereken adımları ele alacaktır. Ayrıca, hiç düşman olmadığında rastgele başka şeyler de yapacaktır.

## Hadi başlayalım

Öncelikle, varlığınız (entity) için .lua dosyasını oluşturun. Bu eğitim için oluşturduğum dosya "addons/Nextbot_tut/lua/entities" klasöründe bulunuyor ve "simple_nextbot.lua" olarak adlandırılmıştır. Şimdi bu dosyayı açın, böylece kodu eklemeye başlayabilirsiniz.

## Kod

### Varlıklar için ihtiyacımız olan temel şeyler

Kullanılacak temel varlığı tanımlayarak ve onu spawn edilebilir (spawnable) yaparak başlayın. Şu ana kadar diğer tüm varlıklarla hemen hemen aynı. Burada modeli ayarlıyor ve daha sonra kullanacağımız bazı değişkenleri tanımlıyoruz.

```lua
AddCSLuaFile()

ENT.Base 			= "base_nextbot"
ENT.Spawnable		= true

function ENT:Initialize()

	self:SetModel( "models/hunter.mdl" )

	self.LoseTargetDist	= 2000	-- Düşmanı kaybetmeden önce ne kadar uzakta olması gerektiği
	self.SearchRadius 	= 1000	-- Ne kadar uzağa kadar düşman aranacağı

end
```

### Düşmanla ilgili şeyler

Bu bölüm, düşmanla ilgili konular için bazı kullanışlı fonksiyonlar ekler. Bir NPC/bot bir şeyleri hedefleyemiyorsa tamamlanmış sayılmaz, değil mi? Bunlar, hala bir düşman olup olmadığını ya da düşmanın kaçıp kaçmadığını kontrol eden bir fonksiyon ve düşman aramak için bir fonksiyon içerir. Ne işe yaradıklarını tam olarak bilmeniz için her türlü yorumu ekledim.

```lua
----------------------------------------------------
-- ENT:Get/SetEnemy()
-- Düşmanımızı kayıtlı tutmak için kullanılan basit fonksiyonlar
----------------------------------------------------
function ENT:SetEnemy(ent)
	self.Enemy = ent
end
function ENT:GetEnemy()
	return self.Enemy
end

----------------------------------------------------
-- ENT:HaveEnemy()
-- Eğer bir düşmanımız varsa true (doğru) döndürür
----------------------------------------------------
function ENT:HaveEnemy()
	-- Mevcut düşmanımız geçerliyse
	if ( self:GetEnemy() and IsValid(self:GetEnemy()) ) then
		-- Düşman çok uzaktaysa
		if ( self:GetRangeTo(self:GetEnemy():GetPos()) > self.LoseTargetDist ) then
			-- Düşman kaybedildiyse, yeni bir tane aramak için FindEnemy() fonksiyonunu çağır
			-- FindEnemy(), bir düşman bulunursa true (doğru) döndürür, bu da bu fonksiyonun true (doğru) döndürmesini sağlar
			return self:FindEnemy()
		-- Eğer düşman öldüyse (Alive() kullanmadan önce oyuncu olup olmadığını kontrol etmeliyiz)
		elseif ( self:GetEnemy():IsPlayer() and !self:GetEnemy():Alive() ) then
			return self:FindEnemy()		-- Arama bir şey bulamazsa false (yanlış) döndür
		end
		-- Düşman ne çok uzakta ne de ölü, bu yüzden true (doğru) döndürebiliriz
		return true
	else
		-- Düşman geçerli değil, o yüzden yeni bir tane arayalım
		return self:FindEnemy()
	end
end

----------------------------------------------------
-- ENT:FindEnemy()
-- Bir tane bulursak true (doğru) döndürür ve düşmanımızı ayarlar
----------------------------------------------------
function ENT:FindEnemy()
	-- Etrafımızdaki varlıkları ara
	-- Bu, istediğiniz şekilde yapılabilir, örn. görme yetisini taklit etmek için ents.FindInCone()
	local _ents = ents.FindInSphere( self:GetPos(), self.SearchRadius )
	-- Burada, yukarıdaki aramanın bulduğu her varlık arasında döngüye girip istediğimiz olup olmadığına bakıyoruz
	for k,v in ipairs( _ents ) do
		if ( v:IsPlayer() ) then
			-- Bir tane bulduk, o yüzden onu düşmanımız olarak ayarlayalım ve true (doğru) döndürelim
			self:SetEnemy(v)
			return true
		end
	end
	-- Hiçbir şey bulamadık, bu yüzden düşmanımızı nil (hiçbir şey) olarak ayarlayacağız ve false (yanlış) döndüreceğiz
	self:SetEnemy(nil)
	return false
end
```

### Coroutine'ler

Artık bot düşmanları bulabildiğine göre, sadece bir düşmana sahip olmaktan başka bir şey yapmasını sağlamalıyız. Bu sonraki bölüm, yapay zekamızın çoğunun kurulacağı yerdir. Fonksiyon bir coroutine'dir, yani `coroutine.wait( zaman )` kullanarak belirli bir süre için duraklatabileceğiniz devasa bir döngüsel kod bölümüdür. Coroutine'ler, işleri zamanlanmış bir sırayla yapmanıza olanak tanır ve botun oyuncuya dönmesini veya bir animasyon oynatmasını sağlamak için fonksiyonu duraklatmanıza izin verir. Ve hepsi bir `while true` döngüsü içinde olduğu için, bot var olduğu sürece çalışacaktır. Yani yapay zekanız yapabileceği her şeyi çalıştırmayı bitirdikten sonra geri dönüp tekrar yapacaktır. İşte çok basit bir bot örneği.

```lua
function ENT:RunBehaviour()

	while ( true ) do							-- İşte döngü, sonsuza kadar çalışacak

		self:StartActivity( ACT_WALK )			-- Yürüme animasyonu
		self.loco:SetDesiredSpeed( 200 )		-- Yürüme hızı
		self:MoveToPos( self:GetPos() + Vector( math.Rand( -1, 1 ), math.Rand( -1, 1 ), 0 ) * 400 ) -- Yaklaşık 400 birim içindeki rastgele bir yere yürü (yielding)
		self:StartActivity( ACT_IDLE )			-- Boşta durma animasyonu
		coroutine.wait(2)						-- 2 saniye bekle

		coroutine.yield()
		-- Fonksiyon burada bitti, ancak döngünün başına geri dönecek ve botun başka bir yere yürümesini sağlayacak
	end

end
```

Oldukça basit, değil mi? Rastgele bir noktaya yürü ve başka bir rastgele noktaya yürümeden önce 2 saniye boşta bekle. Bu ne kadar hoş olsa da istediğimiz şey bu değil, sırada çok daha iyi bir yapay zeka var.

### Botumuzun "beyni"

Bu kod bazılarına ne kadar korkutucu görünse de, aslında oldukça basittir:

- Bir düşmanımız olup olmadığını kontrol et, yoksa yukarıdaki `HaveEnemy()` fonksiyonunu kullanarak bir tane arayacak.
- Eğer bir düşman varsa, bazı animasyonları oynat ve oyuncuya doğru koş.
- Hiç düşman yoksa, rastgele bir noktaya yürü.
- 2 saniye boşta bekle. O kadar da kötü değil, değil mi? Koda bir göz atın, her şeyin ne işe yaradığını bilmeniz için yorumlarla doldurdum.

```lua
----------------------------------------------------
-- ENT:RunBehaviour()
-- Yapay zekamızın ana kısmı burasıdır
----------------------------------------------------
function ENT:RunBehaviour()
	-- Bu fonksiyon, varlık ilk spawn olduğunda çağrılır, NPC var olduğu sürece çalışacak devasa bir döngü görevi görür
	while ( true ) do
		-- Yukarıda bahsedilen fonksiyonları kullanarak bir düşmanımız olup olmadığını/bulup bulamayacağımızı görelim
		if ( self:HaveEnemy() ) then
			-- Artık bir düşmanımız olduğuna göre, bu bloktaki kod çalışacak
			self.loco:FaceTowards(self:GetEnemy():GetPos())	-- Düşmanımıza dön
			self:PlaySequenceAndWait( "plant" )		-- Bir düşman bulduğumuzu göstermek için bir poz verelim
			self:PlaySequenceAndWait( "hunter_angry" )-- Düşmana kızgın olduğumuzu göstermek için bir animasyon oynat
			self:PlaySequenceAndWait( "unplant" )	-- Pozdan çık
			self:StartActivity( ACT_RUN )			-- Animasyonu ayarla
			self.loco:SetDesiredSpeed( 450 )		-- Hareket edeceğimiz hızı ayarla. Endişelenmeyin, animasyon eşleşmek için hızlanacak/yavaşlayacaktır
			self.loco:SetAcceleration(900)			-- Düşmana hızla koşacağız, bu yüzden gerçekten hızlı bir şekilde hızlanmak istiyoruz
			self:ChaseEnemy( ) 						-- Yakında incelenecek olan MoveToPos gibi yeni bir fonksiyon.
			self.loco:SetAcceleration(400)			-- Düşmanı kovalamayı bitirdiğimiz için bunu varsayılan değerine geri ayarla
			self:PlaySequenceAndWait( "charge_miss_slide" )	-- Hareketi durdurduğumuzda havalı bir animasyon oynatalım
			self:StartActivity( ACT_IDLE )			--İşimiz bitti, o yüzden boşa dönelim
			-- Şimdi yukarıdaki fonksiyon yapması gerekeni bitirdiğinde, kod başlangıca geri dönecektir
			-- if ifadesinden sonra bir şeyler koymazsanız. O zaman döngüden önce o çalıştırılır
		else
			-- Bir düşman bulamadığımız için, gezinelim
			-- Garry'nin test botunda kullanılan kodun aynısı
			self:StartActivity( ACT_WALK )			-- Yürüme animasyonu
			self.loco:SetDesiredSpeed( 200 )		-- Yürüme hızı
			self:MoveToPos( self:GetPos() + Vector( math.Rand( -1, 1 ), math.Rand( -1, 1 ), 0 ) * 400 ) -- Yaklaşık 400 birim içindeki rastgele bir yere yürü (yielding)
			self:StartActivity( ACT_IDLE )
		end
		-- Kodun bu noktasında bot, oyuncuyu kovalamayı bırakmış veya rastgele bir noktaya yürümeyi bitirmiştir
		-- Bu sonraki fonksiyonu kullanarak, devam edip tekrarlamadan önce 2 saniye bekleyeceğiz
		coroutine.wait(2)

	end

end
```

### ChaseEnemy() fonksiyonu nedir?

Kodu inceledikten sonra muhtemelen wiki'de hiçbir yerde olmayan ve bu eğitimde henüz yayınlanmamış bir fonksiyon fark ettiniz. `ChaseEnemy()`, şimdi inceleyeceğimiz şeydir; bazı kullanışlı değişiklikler dışında Garry'nin Nextbot tabanında yaptığı `MoveToPos` fonksiyonuyla hemen hemen aynıdır:

- Doğrudan belirlenen düşmana giden bir takip yolu oluşturur.
- Düşman ve bot hareket ettikçe yolu güncellemeye devam eder.
- `HaveEnemy()` fonksiyonunu kullanarak, artık bir düşmanımız yoksa onu kovalamayı bırakır. İşte böyle görünüyor:

```lua
----------------------------------------------------
-- ENT:ChaseEnemy()
-- Garry'nin MoveToPos fonksiyonuna benzer şekilde çalışır
--  ancak sürekli olarak
--  artık bir düşman kalmayana kadar
--  düşmanın pozisyonunu takip eder.
----------------------------------------------------
function ENT:ChaseEnemy( options )

	local options = options or {}
	local path = Path( "Follow" )
	path:SetMinLookAheadDistance( options.lookahead or 300 )
	path:SetGoalTolerance( options.tolerance or 20 )
	path:Compute( self, self:GetEnemy():GetPos() )		-- Düşmanın konumuna doğru yolu hesapla

	if ( !path:IsValid() ) then return "failed" end

	while ( path:IsValid() and self:HaveEnemy() ) do

		if ( path:GetAge() > 0.1 ) then					-- Oyuncuyu takip ettiğimiz için yolu sürekli olarak yeniden oluşturmalıyız
			path:Compute(self, self:GetEnemy():GetPos())-- Düşmanın konumuna doğru yolu yeniden hesapla
		end
		path:Update( self )								-- Bu fonksiyon botu yol boyunca hareket ettirir

		if ( options.draw ) then path:Draw() end
		-- Eğer takılırsak, HandleStuck fonksiyonunu çağır ve vazgeç
		if ( self.loco:IsStuck() ) then
			self:HandleStuck()
			return "stuck"
		end

		coroutine.yield()

	end

	return "ok"

end
```

Garry'nin fonksiyonuna ve ardından buna bir göz atın ve nelerin daha ayrıntılı olarak değiştirildiğini görün.

### Neredeyse bitti

Artık bot tamamlandığına göre, onu NPC spawn sekmesine eklemek isteyeceksiniz. Oldukça kolay, ama yine de bu eğitim için kullanılan kod burada:

```lua
list.Set( "NPC", "simple_nextbot", {
	Name = "Simple bot",
	Class = "simple_nextbot",
	Category = "Nextbot"
})
```

Bunu kullandığınızda, her iki "simple_nextbot" ifadesini de botun dosya adıyla değiştirin, geri kalanını kendiniz çözmeniz oldukça kolaydır.

## Görevler

Artık haritada dolaşan temel bir botunuz var ve hepsi bu kadar. İşte işleri biraz renklendirmek için kendi başınıza deneyebileceğiniz bazı şeyler:

- Sadece oyunculardan daha fazlasını ara
- Etrafta dolaşırken sesler çal.
- Sadece önündeki düşmanları ara, etrafındakileri değil.
- Düşman pompalı tüfek tutuyorsa saklanmasını sağla.
- Düşmana çok yaklaştığında kovalamayı bırak ve bir yakın dövüş saldırısı yap.

## Kodun tamamı

```lua
AddCSLuaFile()

ENT.Base 			= "base_nextbot"
ENT.Spawnable		= true

function ENT:Initialize()

	self:SetModel( "models/player/gman_high.mdl" )

	self.LoseTargetDist	= 2000	-- Düşmanı kaybetmeden önce ne kadar uzakta olması gerektiği
	self.SearchRadius 	= 1000	-- Ne kadar uzağa kadar düşman aranacağı

end

----------------------------------------------------
-- ENT:Get/SetEnemy()
-- Düşmanımızı kayıtlı tutmak için kullanılan basit fonksiyonlar
----------------------------------------------------
function ENT:SetEnemy(ent)
	self.Enemy = ent
end
function ENT:GetEnemy()
	return self.Enemy
end

----------------------------------------------------
-- ENT:HaveEnemy()
-- Eğer bir düşmanımız varsa true (doğru) döndürür
----------------------------------------------------
function ENT:HaveEnemy()
	-- Mevcut düşmanımız geçerliyse
	if ( self:GetEnemy() and IsValid(self:GetEnemy()) ) then
		-- Düşman çok uzaktaysa
		if ( self:GetRangeTo(self:GetEnemy():GetPos()) > self.LoseTargetDist ) then
			-- Düşman kaybedildiyse, yeni bir tane aramak için FindEnemy() fonksiyonunu çağır
			-- FindEnemy(), bir düşman bulunursa true (doğru) döndürür, bu da bu fonksiyonun true (doğru) döndürmesini sağlar
			return self:FindEnemy()
		-- Eğer düşman öldüyse (Alive() kullanmadan önce oyuncu olup olmadığını kontrol etmeliyiz)
		elseif ( self:GetEnemy():IsPlayer() and !self:GetEnemy():Alive() ) then
			return self:FindEnemy()		-- Arama bir şey bulamazsa false (yanlış) döndür
		end
		-- Düşman ne çok uzakta ne de ölü, bu yüzden true (doğru) döndürebiliriz
		return true
	else
		-- Düşman geçerli değil, o yüzden yeni bir tane arayalım
		return self:FindEnemy()
	end
end

----------------------------------------------------
-- ENT:FindEnemy()
-- Bir tane bulursak true (doğru) döndürür ve düşmanımızı ayarlar
----------------------------------------------------
function ENT:FindEnemy()
	-- Etrafımızdaki varlıkları ara
	-- Bu, istediğiniz şekilde yapılabilir, örn. görme yetisini taklit etmek için ents.FindInCone()
	local _ents = ents.FindInSphere( self:GetPos(), self.SearchRadius )
	-- Burada, yukarıdaki aramanın bulduğu her varlık arasında döngüye girip istediğimiz olup olmadığına bakıyoruz
	for k,v in ipairs( _ents ) do
		if ( v:IsPlayer() ) then
			-- Bir tane bulduk, o yüzden onu düşmanımız olarak ayarlayalım ve true (doğru) döndürelim
			self:SetEnemy(v)
			return true
		end
	end
	-- Hiçbir şey bulamadık, bu yüzden düşmanımızı nil (hiçbir şey) olarak ayarlayacağız ve false (yanlış) döndüreceğiz
	self:SetEnemy(nil)
	return false
end

----------------------------------------------------
-- ENT:RunBehaviour()
-- Yapay zekamızın ana kısmı burasıdır
----------------------------------------------------
function ENT:RunBehaviour()
	-- Bu fonksiyon, varlık ilk spawn olduğunda çağrılır. NPC var olduğu sürece çalışacak devasa bir döngü görevi görür
	while ( true ) do
		-- Yukarıda bahsedilen fonksiyonları kullanarak bir düşmanımız olup olmadığını/bulup bulamayacağımızı görelim
		if ( self:HaveEnemy() ) then
			-- Artık bir düşmanımız olduğuna göre, bu bloktaki kod çalışacak
			self.loco:FaceTowards(self:GetEnemy():GetPos())	-- Düşmanımıza dön
			self:PlaySequenceAndWait( "plant" )		-- Bir düşman bulduğumuzu göstermek için bir poz verelim
			self:PlaySequenceAndWait( "hunter_angry" )-- Düşmana kızgın olduğumuzu göstermek için bir animasyon oynat
			self:PlaySequenceAndWait( "unplant" )	-- Pozdan çık
			self:StartActivity( ACT_RUN )			-- Animasyonu ayarla
			self.loco:SetDesiredSpeed( 450 )		-- Hareket edeceğimiz hızı ayarla. Endişelenmeyin, animasyon eşleşmek için hızlanacak/yavaşlayacaktır
			self.loco:SetAcceleration(900)			-- Düşmana hızla koşacağız, bu yüzden gerçekten hızlı bir şekilde hızlanmak istiyoruz
			self:ChaseEnemy( ) 						-- MoveToPos gibi yeni bir fonksiyon.
			self.loco:SetAcceleration(400)			-- Düşmanı kovalamayı bitirdiğimiz için bunu varsayılan değerine geri ayarla
			self:PlaySequenceAndWait( "charge_miss_slide" )	-- Hareketi durdurduğumuzda havalı bir animasyon oynatalım
			self:StartActivity( ACT_IDLE )			--İşimiz bitti, o yüzden boşa dönelim
			-- Şimdi yukarıdaki fonksiyon yapması gerekeni bitirdiğinde, kod başlangıca geri dönecektir
			-- if ifadesinden sonra bir şeyler koymazsanız. O zaman döngüden önce o çalıştırılır
		else
			-- Bir düşman bulamadığımız için, gezinelim
			-- Garry'nin test botunda kullanılan kodun aynısı
			self:StartActivity( ACT_WALK )			-- Yürüme animasyonu
			self.loco:SetDesiredSpeed( 200 )		-- Yürüme hızı
			self:MoveToPos( self:GetPos() + Vector( math.Rand( -1, 1 ), math.Rand( -1, 1 ), 0 ) * 400 ) -- Yaklaşık 400 birim içindeki rastgele bir yere yürü (yielding)
			self:StartActivity( ACT_IDLE )
		end
		-- Kodun bu noktasında bot, oyuncuyu kovalamayı bırakmış veya rastgele bir noktaya yürümeyi bitirmiştir
		-- Bu sonraki fonksiyonu kullanarak, devam edip tekrarlamadan önce 2 saniye bekleyeceğiz
		coroutine.wait(2)

	end

end

----------------------------------------------------
-- ENT:ChaseEnemy()
-- Garry'nin MoveToPos fonksiyonuna benzer şekilde çalışır
--  ancak artık bir tane kalmayana kadar sürekli olarak
--  düşmanın pozisyonunu takip eder.
----------------------------------------------------
function ENT:ChaseEnemy( options )

	local options = options or {}
	local path = Path( "Follow" )
	path:SetMinLookAheadDistance( options.lookahead or 300 )
	path:SetGoalTolerance( options.tolerance or 20 )
	path:Compute( self, self:GetEnemy():GetPos() )		-- Düşmanın konumuna doğru yolu hesapla

	if ( !path:IsValid() ) then return "failed" end

	while ( path:IsValid() and self:HaveEnemy() ) do

		if ( path:GetAge() > 0.1 ) then					-- Oyuncuyu takip ettiğimiz için yolu sürekli olarak yeniden oluşturmalıyız
			path:Compute(self, self:GetEnemy():GetPos())-- Düşmanın konumuna doğru yolu yeniden hesapla
		end
		path:Update( self )								-- Bu fonksiyon botu yol boyunca hareket ettirir

		if ( options.draw ) then path:Draw() end
		-- Eğer takılırsak, HandleStuck fonksiyonunu çağır ve vazgeç
		if ( self.loco:IsStuck() ) then
			self:HandleStuck()
			return "stuck"
		end

		coroutine.yield()

	end

	return "ok"

end

list.Set( "NPC", "simple_nextbot", {
	Name = "Simple bot",
	Class = "simple_nextbot",
	Category = "Nextbot"
})
```

## Kullanışlı Konsol Komutları

İşte [NextBot](https://gmod.tr/wiki/NextBot)'larınızı oluşturmanıza ve hatalarını ayıklamanıza yardımcı olabilecek bazı kullanışlı konsol komutları.

| Komut | Açıklama |
|---|---|
| nb_debug <değerler> | Nextbot'ların çeşitli bölümlerinde hata ayıklamanızı sağlar. Değerler bir boşlukla birleştirilebilir. Olası değerler şunlardır: BEHAVIOR, LOOK_AT, PATH, ANIMATION, LOCOMOTION, VISION, HEARING, EVENTS, ERRORS |

## Diğer Örnekler

Size yol göstermesi için bazı ek örnekler.

[raubana'dan kullanışlı NextBot örnekleri](https://github.com/raubana/garrysmod_nextbot_demos/tree/master/lua/entities)

- [GMod Wiki ana sayfası](https://gmod.tr/wiki)

---

[Tüm sunucular](https://gmod.tr/) · [GmodTR Launcher](https://gmod.tr/launcher) · [Rehber](https://gmod.tr/rehber) · [GMod Wiki](https://gmod.tr/wiki) · [GmodTR nedir?](https://gmod.tr/hakkinda) · [Sunucu ekle](https://gmod.tr/sunucu-ekle) · [Discord](https://discord.gg/gmodtr)

Kaynak: https://gmod.tr/wiki/NextBot_NPC_Creation
