FiveM NPC Diyalog Sistemi: Dallanan Konuşmaları Scriptleyin
FiveM NPC'lerinize gerçek bir kişilik kazandırın. Derin hikaye odaklı roleplay sunucuları için diyalog ağaçları, seslendirme replikleri, görev kancaları ve en iyi scriptler.
Agency Scripts
Agency Scripts Kurucusu ve Baş Geliştiricisi
NPC Diyalogları Neden Önemlidir?
NPC diyalog sistemleri, statik pedleri sunucunuza kişilik kazandıran ve oyuncuları etkinlikler boyunca yönlendiren yaşayan karakterlere dönüştürür. Diyalog olmadan NPC'ler menü açan etkileşim hedeflerinden biraz fazlasıdır. Uygun bir diyalog sistemiyle görev verenlere, hikaye anlatıcılarına, kendine has tavrı olan dükkan sahiplerine ve oyuncunun itibarına veya mesleğine göre farklı tepkiler veren muhbirlere dönüşürler. Bir diyalog sistemi, sunucuya özel arka plan hikayeleri, dallara ayrılan olay örgüleri ve gelecekteki etkileşimleri etkileyen oyuncu seçimleri için fırsatlar yaratır. Bir oyuncunun kendisini adıyla karşılayan, arabasının durumu hakkında yorum yapan ve tekrar gelen bir müşteri mi yoksa ilk kez gelen bir ziyaretçi mi olduğuna bağlı olarak farklı hizmetler sunan bir tamirci NPC'sine yaklaşmasının ne kadar daha ilgi çekici olduğunu düşünün. Bu seviyedeki bir etkileyicilik, oyuncuların sunucunuza bağlı kalmasını sağlar ve standart rutin görevlerin ötesini keşfetmeleri için nedenler sunar.
Diyalog Ağacı Veri Yapısı
Herhangi bir diyalog sisteminin temeli, konuşma akışını tanımlayan veri yapısıdır. Bir diyalog ağacı düğümlerden oluşur; her düğüm NPC metnini ve diğer düğümlere bağlanan oyuncu yanıt seçeneklerinin bir listesini içerir. Bu ağaç yapısı dallanan konuşmaları, oyuncu durumuna dayalı koşullu yolları ve konuşmanın önceki noktalarına dönen döngüleri destekler. Diyalog düğümlerinizi veri odaklı olacak şekilde tasarlayın, böylece sunucu ekibi Lua koduna dokunmadan yeni konuşmalar oluşturabilir. İşte pratik bir diyalog ağacı formatı:
Config.Dialogues = {
['mechanic_greeting'] = {
npcName = 'Tony the Mechanic',
nodes = {
['start'] = {
text = "Hey there! Car giving you trouble, or are you just here for a tune-up?",
animation = 'WORLD_HUMAN_WELDING',
options = {
{
label = "I need repairs",
next = 'repairs',
condition = function(player)
return IsPlayerInVehicle(player)
end
},
{
label = "What services do you offer?",
next = 'services'
},
{
label = "Just browsing, thanks",
next = 'goodbye'
},
}
},
['repairs'] = {
text = "Let me take a look... Yeah, your engine's seen better days. I can fix it up for $500. What do you say?",
options = {
{ label = "Fix it up", next = 'repair_accept', action = 'repair_vehicle' },
{ label = "Too expensive", next = 'haggle' },
{ label = "Never mind", next = 'goodbye' },
}
},
['haggle'] = {
text = "Look, parts aren't cheap. But since you seem like a decent person, I can do $350. Final offer.",
options = {
{ label = "Deal!", next = 'repair_accept', action = 'repair_vehicle_discount' },
{ label = "I'll pass", next = 'goodbye' },
}
},
['services'] = {
text = "I do repairs, custom paint jobs, performance tuning, and tire changes. What catches your eye?",
options = {
{ label = "Tell me about tuning", next = 'tuning_info' },
{ label = "Back to start", next = 'start' },
}
},
['repair_accept'] = {
text = "Alright, give me a minute... Done! She's running smooth now. Take care of her out there.",
options = {
{ label = "Thanks, Tony!", next = 'end' },
}
},
['goodbye'] = {
text = "No worries. Come back anytime you need help with your ride!",
options = {
{ label = "See you around", next = 'end' },
}
},
}
},
}
The condition Seçeneklerdeki function, oyuncunun mevcut durumuna göre seçimleri dinamik olarak göstermenizi veya gizlemenizi sağlar. Tamir seçeneği yalnızca oyuncu bir araçla geldiğinde görünür, görevle ilgili seçenekler yalnızca oyuncu doğru aşamaya ulaştığında çıkar ve VIP seçenekleri belirli izinlere sahip oyuncularla sınırlandırılabilir. İlgili action alanı, belirli bir seçenek seçildiğinde sunucu tarafı fonksiyonları tetikleyerek diyalog seçimlerini oynanış sonuçlarına bağlar.
NPC Oluşturma ve Yönetme
Diyalog NPC'lerinin güvenilir bir şekilde spawn edilmesi, tutarlı konumlara yerleştirilmesi ve GTA dünyasının yaratabileceği kaostan etkilenmemesi gerekir. Bir diyalog NPC'si spawn ederken modeli istemeniz, ped'i oluşturmanız, GTA'nın temizleme sistemlerinin silmemesi için mission entity olarak ayarlamanız, uzaklaşmaması için konumunu dondurmanız ve oyuncuların görev vereninizi öldürememesi için onu ölümsüz yapmanız gerekir. Yoğun sunucularda bellek tasarrufu sağlamak amacıyla, oyuncular yakındayken ped'leri oluşturan ve menzilde oyuncu olmadığında onları kaldıran merkezi bir NPC yöneticisi kullanın:
local spawnedNPCs = {}
function SpawnDialogueNPC(npcId, config)
if spawnedNPCs[npcId] then return end
local model = GetHashKey(config.model)
RequestModel(model)
while not HasModelLoaded(model) do Wait(10) end
local ped = CreatePed(0, model, config.coords.x, config.coords.y,
config.coords.z, config.heading, false, true)
SetEntityInvincible(ped, true)
SetBlockingOfNonTemporaryEvents(ped, true)
FreezeEntityPosition(ped, true)
SetPedFleeAttributes(ped, 0, false)
SetPedCombatAttributes(ped, 46, true)
SetPedCanRagdoll(ped, false)
SetEntityAsMissionEntity(ped, true, true)
SetModelAsNoLongerNeeded(model)
-- Play idle animation if configured
if config.scenario then
TaskStartScenarioInPlace(ped, config.scenario, 0, true)
end
spawnedNPCs[npcId] = { entity = ped, config = config }
return ped
end
Şunun kombinasyonu: SetBlockingOfNonTemporaryEvents ve FreezeEntityPosition yakındaki patlamalar, polis kovalamacaları veya saldırgan oyuncular gibi ortam olaylarının NPC'nizin kaçmasına, karşılık vermesine veya ragdoll durumuna düşmesine neden olmamasını sağlar. Bu korumalar olmadan oyuncular, yoldan geçen bir arabanın çarpması sonucu yerde kıvranan bir görev vericiyle karşılaşabilir ve oluşturmak için emek verdiğiniz atmosfer tamamen bozulabilir.
Sinematik Kamera Sistemi
Diyaloglar sırasındaki kamera kullanımı, deneyimi bir menü etkileşiminden sinematik bir ana dönüştürür. Bir diyalog başladığında, arka planı bulanıklaştırmak ve dikkati sohbete çekmek için alan derinliğini kullanarak NPC'nin yüzüne hafif bir kaydırmayla odaklanan bir kamera oluşturun. Konuşma ilerledikçe kamera açıları arasında geçiş yapın: Oyuncu bir seçim yaptığında kamerayı oyuncuya, NPC yanıt verdiğinde ise tekrar NPC'ye çevirin. GTA'nın yerel kamera sistemi pozisyon, rotasyon, görüş açısı ve alan derinliği üzerinde tam kontrol sağlar:
local dialogueCam = nil
function StartDialogueCamera(npcPed)
local npcCoords = GetEntityCoords(npcPed)
local npcHeading = GetEntityHeading(npcPed)
local playerPed = PlayerPedId()
-- Calculate camera position offset from NPC face
local angleRad = math.rad(npcHeading + 160)
local camX = npcCoords.x + (math.sin(angleRad) * 1.5)
local camY = npcCoords.y + (math.cos(angleRad) * 1.5)
local camZ = npcCoords.z + 0.6
dialogueCam = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
SetCamCoord(dialogueCam, camX, camY, camZ)
PointCamAtPedBone(dialogueCam, npcPed, 31086, 0.0, 0.0, 0.1, true) -- Head bone
-- Depth of field for cinematic look
SetCamNearDof(dialogueCam, 0.5)
SetCamFarDof(dialogueCam, 3.5)
SetCamDofStrength(dialogueCam, 0.6)
SetCamUseShallowDofMode(dialogueCam, true)
SetCamFov(dialogueCam, 40.0) -- Tighter shot
SetCamActive(dialogueCam, true)
RenderScriptCams(true, true, 800, true, false)
-- Disable player controls during dialogue
SetPlayerControl(PlayerId(), false, 0)
-- Make player face NPC
TaskTurnPedToFaceEntity(playerPed, npcPed, 1000)
end
function StopDialogueCamera()
if dialogueCam then
RenderScriptCams(false, true, 500, true, false)
DestroyCam(dialogueCam, true)
dialogueCam = nil
SetPlayerControl(PlayerId(), true, 0)
end
end
The PointCamAtPedBone native özellikle güçlüdür çünkü küçük animasyon hareketlerinden bağımsız olarak kamera odağını NPC'nin kafasına kilitler ve konuşma boyunca kadrajı tutarlı tutar. Şuradaki geçiş süreleri: RenderScriptCams sarsıcı geçişler yerine yumuşak kamera kararmaları oluşturun ve sunucunuzun temposuna en uygun hissi bulmak için 500ms ile 1000ms arasındaki değerleri deneyin.
Altyazı Görüntüleme Sistemi
Bir altyazı sistemi, hikaye odaklı oyunların konuşmaları nasıl gösterdiğini taklit ederek NPC diyalog metnini ekranın alt kısmında görsel olarak çekici bir şekilde sunar. Tüm metin bloğunu bir kerede dökmek yerine, karakterleri teker teker gösteren bir daktilo efekti uygulayarak NPC'nin aktif olarak konuştuğu hissini yaratın. Altyazı gösterimi için NUI kullanın; çünkü yazı tipleri, renkler, animasyonlar ve konumlandırma üzerinde tam CSS kontrolü sağlar. Her diyalog düğümünün metnini aktif hale geldiğinde, NPC'nin adı ve görüntüleme stilini etkilemesi gereken tüm duygu etiketleriyle birlikte NUI karesine gönderin:
// Subtitle display JavaScript (html/subtitles.js)
let typewriterTimeout = null;
window.addEventListener('message', (event) => {
const data = event.data;
if (data.action === 'showDialogue') {
clearTimeout(typewriterTimeout);
const container = document.getElementById('subtitle-container');
const nameEl = document.getElementById('npc-name');
const textEl = document.getElementById('dialogue-text');
const optionsEl = document.getElementById('dialogue-options');
container.style.display = 'block';
nameEl.textContent = data.npcName;
textEl.textContent = '';
optionsEl.innerHTML = '';
// Typewriter effect
let charIndex = 0;
const fullText = data.text;
function typeNext() {
if (charIndex < fullText.length) {
textEl.textContent += fullText[charIndex];
charIndex++;
typewriterTimeout = setTimeout(typeNext, 30);
} else {
// Show options after text completes
showOptions(data.options);
}
}
typeNext();
}
if (data.action === 'hideDialogue') {
document.getElementById('subtitle-container').style.display = 'none';
}
});
function showOptions(options) {
const optionsEl = document.getElementById('dialogue-options');
options.forEach((opt, index) => {
const btn = document.createElement('button');
btn.className = 'dialogue-option';
btn.innerHTML = `${index + 1} ${opt.label}`;
btn.onclick = () => {
fetch(`https://${GetParentResourceName()}/selectOption`, {
method: 'POST',
body: JSON.stringify({ index: index })
});
};
optionsEl.appendChild(btn);
});
}
Altyazı kapsayıcısını, sunucunuzun temasına uyan yarı saydam koyu bir arka plan, yuvarlatılmış köşeler ve zarif bir gradyan kenarlık ile stillendirin. Minimap veya diğer HUD öğeleriyle örtüşmemesi için yeterli dolguyla ekranın alt ortasına konumlandırın. Oyuncuların tıklamadan hızlıca seçenekleri belirleyebilmesi için sayı tuşlarına basabilecekleri klavye kısayolları ekleyin, bu durum konuşma akışı sırasında daha doğal hissettirir.
Görev Sistemi Entegrasyonu
Diyalog sistemleri, bir görev framework'üne bağlandığında gerçekten güçlü hale gelir. Diyalog ağacının action yanıt seçeneklerindeki alanı, görev mantığının yürütüldüğü kanca noktasını sağlar. Bir oyuncu diyalog yoluyla bir görevi kabul ettiğinde, action handler oyuncunun görev günlüğünde bir görev girdisi oluşturmalı, gerekli rota noktalarını veya hedefleri ayarlamalı ve sonraki diyalog etkileşimleri üzerinden ilerlemeyi takip etmelidir. Oyuncuların bağlantıyı kesip kaldıkları yerden devam edebilmeleri için görev ilerlemesini oyuncu başına veritabanında saklayın. Görevleri, her bir durumun bir diyalog düğümüne ve sonraki diyalog kullanılabilir hale gelmeden önce tamamlanması gereken bir dizi hedefe karşılık geldiği durum makineleri olarak tasarlayın:
-- Server-side quest actions triggered by dialogue choices
local QuestActions = {
['accept_delivery_job'] = function(src, npcId)
local Player = QBCore.Functions.GetPlayer(src)
local citizenid = Player.PlayerData.citizenid
-- Create quest entry
MySQL.insert(
'INSERT INTO player_quests (citizenid, quest_id, stage, started_at) VALUES (?, ?, ?, NOW())',
{citizenid, 'tony_delivery_1', 'pickup'}
)
-- Set waypoint for pickup location
TriggerClientEvent('quest:client:setWaypoint', src, {
coords = vector3(482.5, -1311.2, 29.2),
blipSprite = 501,
blipColor = 5,
label = 'Package Pickup'
})
TriggerClientEvent('QBCore:Notify', src, 'Quest started: Special Delivery', 'success')
end,
['complete_delivery'] = function(src, npcId)
local Player = QBCore.Functions.GetPlayer(src)
local citizenid = Player.PlayerData.citizenid
MySQL.update(
'UPDATE player_quests SET stage = ?, completed_at = NOW() WHERE citizenid = ? AND quest_id = ?',
{'completed', citizenid, 'tony_delivery_1'}
)
Player.Functions.AddMoney('cash', 1500, 'quest-delivery-reward')
TriggerClientEvent('QBCore:Notify', src, 'Quest complete! Reward: $1,500', 'success')
end,
}
Hangi diyalog düğümlerinin kullanılabilir olduğunu dinamik olarak değiştirmek için görev aşamasını kullanın. Bir oyuncu teslimatı tamamladıktan sonra Tony'ye döndüğünde, diyalog sistemi görev aşamasını kontrol eder ve ilk karşılama yerine ödüllü bir tamamlama diyaloğu sunar. Bu, NPC'lerin oyuncunun ilerlemesini tanıdığı ve buna göre tepki verdiği doğal bir konuşma akışı oluşturarak dünyanın duyarlı ve canlı hissettirmesini sağlar.
NPC Animasyonları ve İfadeleri
Konuşurken tamamen hareketsiz duran statik NPC'ler robotik hissettirir ve atmosferi bozar. Diyalog sisteminize animasyon desteği ekleyin, böylece NPC'ler konuşmalar sırasında jestler yapsın, duygularını göstersin ve tepki versin. GTA V: diyalogda belirli noktalarda tetikleyebileceğiniz jestleri, yüz ifadelerini ve vücut dilini kapsayan devasa bir animasyon sözlüğü kitaplığına sahiptir. Animasyonları node düzeyinde atayın, böylece her diyalog satırının kendine eşlik eden bir jesti olabilir. NPC iyi bir haber verdiğinde, neşeli bir el sallama animasyonu oynatın. Ciddi bir şey tartıştıklarında, kolları kavuşturulmuş sert bir duruş kullanın. Oyuncu yanıtları arasındaki boş anlar için, bir düşünme veya bekleme animasyonunu döngüye alın. Ayrıca şu gibi yüz animasyonu native'lerini de kullanabilirsiniz: SetFacialIdleAnimOverride NPC'nin dinlenme ifadesini konuşmanın havasına uyacak şekilde değiştirerek mutlu, öfkeli, korkmuş veya kafası karışmış görünmesini sağlamak için. En ikna edici performanslar için vücut ve yüz animasyonlarını birleştirin ve animasyonları her zaman oyun içinde test edin; çünkü bazı animasyon sözlükleri farklı ped modellerinde farklı görünür ve erkek bir ped modelinde çalışan bir animasyon, kadın bir ped modelinde kırpılabilir veya garip durabilir.
Performans ve En İyi Uygulamalar
Harita genelinde onlarca NPC oluşturan bir diyalog sistemi, dikkatli bir performans yönetimi gerektirir. NPC'leri yalnızca oyuncular çizim mesafesindeyken, genellikle 50-100 metre, oluşturun ve yakınlarda oyuncu olmadığında kaldırın. NPC başına ayrı thread'ler oluşturmak yerine tüm NPC oluşturma mesafelerini yönetmek için tek bir thread kullanın, çünkü bu istemci tarafındaki yükü önemli ölçüde azaltır. Konuşmalar sırasında dosyaları okumak yerine diyalog ağacı verilerini resource başlangıcında önbelleğe alın. Diyalog kamera geçişlerinizi akıcı tutun ancak kameraları aşırı derecede oluşturup silmeyin, çünkü kamera işlemleri belirgin bir performans maliyetine sahiptir. Birden fazla oyuncu aynı NPC ile aynı anda etkileşime girdiğinde, her oyuncu bağımsız olarak çalışan kendi diyalog örneğini almalıdır; yani diyalog durumu NPC varlığı yerine oyuncu başına saklanmalıdır. Oyuncunun bağlantısı koptuğunda veya konuşmanın ortasında uzaklaştığında tüm diyalog kaynaklarını temizleyin, kamerayı silin, NUI odağını bırakın ve oyuncu kontrollerini geri yükleyin. Diyalog sisteminizi en kötü durum senaryosunu düşünerek test edin: Oyuncunun oyunu diyalog ortasında çökebilir veya kamera etkinken alt-F4 çekebilir. Sisteminiz bu durumları tespit etmeli ve yeniden bağlanıldığında askıda kalan kameraları veya kilitli kontrolleri önlemek için sorunsuz bir şekilde temizlik yapmalıdır.