FiveM Yetenek Ağacı Sistemi: Seviye Atlama İlerlemesini Doğru Kurgulayın
FiveM sunucunuza bir yetenek ağacı ekleyin. Oyunculara anlamlı uzun vadeli hedefler sunmak için XP, kilit açmalar, UI tasarımı, meslek bağlantıları ve en iyi yetenek scriptleri.
Agency Scripts
Agency Scripts Kurucusu ve Baş Geliştiricisi
Yetenek Ağaçları Roleplay Sunucularını Neden Dönüştürür?
Çoğu FiveM roleplay sunucusu, bir oyuncunun meslek seçtiği ve tüm araç ve yeteneklerine anında erişebildiği meslek tabanlı bir ilerlemeye dayanır. İşlevsel olsa da bu yaklaşım hiçbir gelişim veya uzun vadeli yatırım hissi sunmaz. Yetenek ağaçları bu dinamiği kökten değiştirir. RPG tarzı ilerleme sunarak oyuncular eylemleriyle deneyim kazanır ve dallara ayrılan yetenek ağaçlarında becerilerin, perk'lerin ve pasif bonusların kilidini açmak için yetenek puanları harcar. Yüzlerce aracı tamir eden bir mekanik zamanla daha hızlı ve daha verimli hale gelir. Sürekli kilit açan bir suçlu bu işte giderek ustalaşır. Bu durum, adanmışlığı ödüllendiren ve her karakterin benzersiz hissettirmesini sağlayan anlamlı bir karakter gelişimi yaratır. İşin püf noktası, sunucunuzu bir MMO grind'ına dönüştürmek yerine roleplay'i zenginleştiren bir sistem tasarlamak, yetenek farklılıklarının RP senaryolarına hükmetmek yerine onları tamamlamasını sağlayacak kadar bonusları ölçülü tutmaktır.
Yetenek Ağacı Veri Yapısı Tasarımı
İyi tasarlanmış bir yetenek ağacı, temiz bir veri yapısıyla başlar. Her yetenek ağacı; dövüş, üretim, sürüş, tıp veya suç gibi bir kategoriye aittir. Her ağaç içinde, bireysel düğümler kilidi açılabilecek yetenekleri temsil eder. Her düğüm benzersiz bir tanımlayıcıya, bir görünen ada, bir açıklamaya, maksimum seviyeye, seviye başına puan maliyetine, önce açılması gereken önkoşul düğümlere ve uyguladığı gerçek oynanış etkisine sahiptir. Ağaç tanımlarını hem sunucunun hem de istemcinin başvurabilmesi için paylaşılan bir yapılandırma dosyasında saklayın. Oyuncunun ilerlemesi, hangi düğümlerin kilidini hangi seviyeye kadar açtıklarını ve mevcut harcanmamış yetenek puanlarını takip eden ayrı bir veri yapısıdır. Ağaç tanımını oyuncu ilerlemesinden ayrı tutmak, düğüm tanımlayıcıları sabit kaldığı sürece oyuncu verilerini taşımak zorunda kalmadan ağacı güncelleyebileceğiniz veya yeniden dengeleyebileceğiniz anlamına gelir.
-- shared/skill_trees.lua
SkillTrees = {
mechanic = {
label = 'Mechanic',
icon = 'fa-wrench',
nodes = {
quickFix = {
label = 'Quick Fix',
desc = 'Reduce vehicle repair time',
maxLevel = 5,
cost = 1,
prereqs = {},
effect = {type = 'repair_speed', perLevel = 0.10},
},
engineExpert = {
label = 'Engine Expert',
desc = 'Repair engines to higher condition',
maxLevel = 3,
cost = 2,
prereqs = {'quickFix'},
effect = {type = 'engine_quality', perLevel = 0.15},
},
bodyworkMaster = {
label = 'Bodywork Master',
desc = 'Repair body damage more effectively',
maxLevel = 3,
cost = 2,
prereqs = {'quickFix'},
effect = {type = 'body_quality', perLevel = 0.15},
},
turboTuner = {
label = 'Turbo Tuner',
desc = 'Unlock performance tuning abilities',
maxLevel = 1,
cost = 5,
prereqs = {'engineExpert'},
effect = {type = 'unlock_tuning', perLevel = 1},
},
},
},
criminal = {
label = 'Street Smarts',
icon = 'fa-mask',
nodes = {
lockpicking = {
label = 'Lockpicking',
desc = 'Pick locks faster with fewer failures',
maxLevel = 5,
cost = 1,
prereqs = {},
effect = {type = 'lockpick_speed', perLevel = 0.12},
},
silentStep = {
label = 'Silent Step',
desc = 'Reduce noise while crouching',
maxLevel = 3,
cost = 2,
prereqs = {'lockpicking'},
effect = {type = 'noise_reduction', perLevel = 0.20},
},
safecracker = {
label = 'Safecracker',
desc = 'Attempt to crack safes',
maxLevel = 1,
cost = 4,
prereqs = {'lockpicking'},
effect = {type = 'unlock_safecrack', perLevel = 1},
},
},
},
}
Deneyim Takibi ve Seviye Atlama
Deneyim, tekrarlayan kasılma döngüleri yerine oyun içi eylemler yoluyla organik olarak kazanılmalıdır. Beceri ağacı kategorisi başına deneyimi takip edin, böylece mekanik işleri yapmak mekanik XP'si, suç işlemek suç XP'si kazandırır vb. Her seviye için deneyim eşikleri tanımlayın ve bir oyuncu eşiği aştığında, o ağaçta harcamak üzere bir veya daha fazla beceri puanı kazansın. Kötüye kullanım döngülerini önlemek için tekrarlanan aynı eylemlerde azalan getiri uygulayın. Örneğin, 10 dakikalık bir zaman diliminde yapılan ilk araç onarımı 50 XP verebilir, ancak sonraki onarımlar sırasıyla 25, ardından 10, ardından 5 verir. Bu, anlamsız tekrarlar yerine çeşitli oynanışı ödüllendirir. Sunucu, XP verme konusunda tek yetkili olmalı ve istemcinin kendi deneyim değerlerini bildirmesine asla güvenmemelidir. Eylemin gerçekten gerçekleştiğini doğruladıktan sonra XP etkinliklerini sunucu tarafı mantığından tetikleyin.
-- server/experience.lua
local XP_THRESHOLDS = {0, 100, 300, 600, 1000, 1500, 2200, 3000, 4000, 5500}
local recentActions = {}
function GrantXP(playerId, tree, amount, actionType)
local identifier = GetPlayerIdentifier(playerId, 0)
local now = os.time()
-- Diminishing returns for repeated actions
local key = identifier .. ':' .. tree .. ':' .. actionType
if not recentActions[key] then
recentActions[key] = {count = 0, resetAt = now + 600}
end
local ra = recentActions[key]
if now > ra.resetAt then
ra.count = 0
ra.resetAt = now + 600
end
ra.count = ra.count + 1
local multiplier = math.max(0.1, 1.0 - (ra.count - 1) * 0.25)
local finalXP = math.floor(amount * multiplier)
-- Update database
MySQL.update([[
UPDATE character_skills SET xp = xp + ?
WHERE identifier = ? AND tree = ?
]], {finalXP, identifier, tree})
-- Check for level up
local data = MySQL.single.await([[
SELECT xp, level, points FROM character_skills
WHERE identifier = ? AND tree = ?
]], {identifier, tree})
if data then
local nextThreshold = XP_THRESHOLDS[data.level + 2]
if nextThreshold and data.xp >= nextThreshold then
local newLevel = data.level + 1
MySQL.update([[
UPDATE character_skills
SET level = ?, points = points + 1
WHERE identifier = ? AND tree = ?
]], {newLevel, identifier, tree})
TriggerClientEvent('skills:levelUp', playerId, tree, newLevel)
end
end
TriggerClientEvent('skills:xpGained', playerId, tree, finalXP)
end
Yetenek Etkilerini Oynanışa Uygulama
Beceri efektleri, sistemin oyuncular için somutlaştığı yerdir. Kilidi açılan her düğüm, belirli bir oyun mekaniğini ölçülebilir bir şekilde değiştirir. Bir oyuncu tanımlayıcısı ile bir efekt türü alan ve ardından bu efekte katkıda bulunan kilidi açılmış tüm düğümlerden gelen toplam bonusu hesaplayan merkezi bir efekt çözücü uygulayın. Başka bir script bir oyuncunun onarım hızı bonusunu öğrenmek istediğinde, bu çözücüyü çağırır ve geriye bir çarpan alır. Bu, beceri sisteminizi diğer kaynaklardan bağımsız tutar. Diğer scriptlerin beceri ağaçları, düğümler veya seviyeler hakkında bilgi sahibi olması gerekmez; yalnızca türe göre bir bonus değeri isterler. Kasa açma veya turbo modifiyesi gibi açılabilir yetenekler için çözücü, oyuncunun gerekli düğüme sahip olup olmadığını belirten bir boolean döndürür. Bu mimari, becerilerin mevcut scriptlere minimum kod değişikliğiyle entegre edilmesini kolaylaştırır.
-- server/effects.lua
local playerSkillCache = {}
function GetSkillBonus(playerId, effectType)
local identifier = GetPlayerIdentifier(playerId, 0)
local cache = playerSkillCache[identifier]
if not cache then return 0.0 end
local total = 0.0
for treeName, treeDef in pairs(SkillTrees) do
for nodeId, nodeDef in pairs(treeDef.nodes) do
if nodeDef.effect.type == effectType then
local unlocked = cache[treeName] and cache[treeName][nodeId] or 0
total = total + (nodeDef.effect.perLevel * unlocked)
end
end
end
return total
end
function HasSkillUnlock(playerId, effectType)
return GetSkillBonus(playerId, effectType) > 0
end
-- Export for other resources
exports('GetSkillBonus', GetSkillBonus)
exports('HasSkillUnlock', HasSkillUnlock)
-- Example: another resource checking repair speed
-- local bonus = exports['skillsystem']:GetSkillBonus(source, 'repair_speed')
-- local repairTime = baseTime * (1.0 - bonus)
Yetenek Ağacı NUI Geliştirme
Beceri ağacınızın görsel sunumu, oyuncuların ilerleme sistemiyle ne kadar etkileşim içinde hissettiklerini doğrudan etkiler. Her ağacı, ön koşulları gösteren çizgilerle birbirine bağlanan düğümlere sahip dallanan bir grafik olarak görüntüleyen bir NUI paneli oluşturun. Kilidi açılmış düğümler parlamalı veya renkle yanıp sönmeli, kilitli ancak erişilebilir düğümler hafifçe karartılmış görünmeli ve ön koşulları karşılanmayan düğümler gri renkte olmalıdır. Oyuncunun mevcut seviyesini, XP ilerleme çubuğunu ve mevcut beceri puanlarını üst kısımda belirgin bir şekilde gösterin. Bir oyuncu bir düğüme tıkladığında; açıklamasını, maksimum üzerinden mevcut seviyesini, yükseltme maliyetini ve seviye başına efekt bonusunu görüntüleyin. Oyuncular yapılandırmalarını dikkatlice planlamak isteyebileceğinden, puan harcamadan önce bir onay adımı ekleyin. Durum değişiklikleri için akıcı CSS geçişleri kullanın, böylece bir düğümü yükseltmek kısa bir parlama veya ölçeklendirme animasyonuyla tatmin edici hissettirir. Meta değiştiğinde veya farklı bir oyun tarzı denemek istediklerinde oyuncuların yapılandırmalarını sıfırlayabilmeleri için ücretsiz veya oyun içi para maliyeti olan bir sıfırlama seçeneği eklemeyi değerlendirin.
Dengeleme ve Anti-Exploit Değerlendirmeleri
Denge, her ilerleme sisteminin en zor kısmıdır. Bonuslar çok güçlüyse, yüksek seviyeli oyuncular dokunulmaz hale gelir ve yeni oyuncular çaresiz hisseder. Bonuslar çok zayıfsa, kimse ilerlemeyi önemsemez. Maksimum seviyede yetenek başına yüzde 5 ila 15 bonus ile muhafazakar bir başlangıç yapın ve oyuncu geri bildirimlerini takip edin. Her yetenek puanı dağıtımı için sunucu taraflı doğrulama uygulayın. Bir oyuncu bir düğümü yükseltme talebinde bulunduğunda, yeterli puana sahip olduğunu, tüm ön koşulların karşılandığını ve düğümün zaten maksimum seviyede olmadığını doğrulayın. Bu kontroller için istemcinin durumuna asla güvenmeyin. Suiistimalleri tespit edip geri alabilmek adına tüm yetenek puanı işlemleri için günlük kaydı ekleyin. Seri suiistimal girişimlerini önlemek için yetenek dağıtım isteklerini sınırlandırın. Rekabetçi senaryolar için, tek bir karakterin yatırım yapabileceği ağaç sayısını sınırlayan yetenek üst sınırlarını göz önünde bulundurun; böylece bir oyuncunun her şeyi maksimuma çıkarmasına izin vermek yerine anlamlı seçimler yapmaya zorlarsınız. Bu, çeşitli karakter yapılandırmaları oluşturur ve birbirini tamamlayan yetenek setlerine sahip oyuncular arasındaki iş birliğini teşvik eder.
Veri Tabanı Şeması ve Taşıma Stratejisi
Veritabanı şemanızı hem mevcut yetenek verilerini hem de gelecekteki ağaç genişletmelerini sorunsuzca karşılayacak şekilde tasarlayın. Oyuncu yetenek dağılımlarını tek bir JSON blob yerine karakter ve ağaç başına ayrı satırlar olarak saklayın. Bu, belirli yetenekleri sorgulamayı verimli hale getirir ve analizler için veritabanı düzeyinde toplamaya olanak tanır. Ağaç adı, mevcut seviye, toplam XP, kullanılabilir puanlar için sütunlar ve bu ağaç içindeki düğüm dağılımları için bir JSON sütunu ekleyin. Gelecekteki güncellemelerde yeni yetenek ağaçları veya düğümler eklediğinizde, yeni düğümler doğrudan sıfır seviyesinden başladığı için mevcut oyuncu verileri bozulmadan kalır. Bir düğümü yeniden adlandırır veya kaldırırsanız, harcanan puanları oyuncunun o ağaç için mevcut havuzuna iade ederek geçişi yönetin. Geçiş mantığının hangi değişiklikleri uygulayacağını bilmesi için ağaç tanımlarınızı sürümleyin. Bu ileriye dönük yaklaşım, yetenek sisteminiz aylar süren sunucu işletimi boyunca geliştikçe sizi sancılı veri geçişlerinden kurtarır.
-- SQL schema
CREATE TABLE IF NOT EXISTS character_skills (
id INT AUTO_INCREMENT PRIMARY KEY,
identifier VARCHAR(64) NOT NULL,
char_slot INT DEFAULT 1,
tree VARCHAR(32) NOT NULL,
level INT DEFAULT 0,
xp INT DEFAULT 0,
points INT DEFAULT 0,
allocations JSON DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY unique_char_tree (identifier, char_slot, tree),
INDEX idx_identifier (identifier)
);