Defold Tamamen 3D'ye Geçiyor: Teknik İnceleme ve Mimari Tutorial
Özet olarak
Defold, hafif yapısını koruyarak tam kapsamlı bir 3D oyun motoruna dönüştü ve geliştiricilere düşük boyutlu, yüksek performanslı cross-platform projeler için güçlü bir alternatif sunuyor. Bu teknik incelemede, özel render script'lerinden First-Person kamera kontrollerine ve ağ senkronizasyonu optimizasyonlarına kadar motorun mimari yeteneklerini derinlemesine inceliyoruz. Özellikle WebGL ve mobil odaklı 3D projelerde performans darboğazlarını aşmak isteyen kıdemli geliştiriciler için Defold'un sunduğu veri odaklı (data-driven) kontrol imkanları kritik avantajlar sağlıyor.
Her indie geliştirici, oyun motorunun kendisine ihanet ettiği o anı bilir. Hafif sıklet bir 2D konseptle başlarsınız, scope creep süreci 3D elementleri işin içine dahil eder ve aniden motorunuzun build boyutu patlar, yükleme süreleriniz yavaşlar ve web build'leriniz memory yetersizliğinden çöker. Unity ve Unreal gibi devasa motorlar AAA kalitesi için fevkalade olsa da, sürtünmesiz ve cross-platform dağıtımı hedefleyen —özellikle WebGL ve mobil— solo geliştiriciler için genellikle bakkala tankla gitmek gibi hissettirirler.
İşte burada sahneye Defold çıkıyor. Tarihsel olarak, tek bir codebase ile HTML5'ten Nintendo Switch'e kadar her platformu hedefleyen stüdyolar tarafından kullanılan ultra hızlı ve sıfır-bloat bir 2D motoru olarak savunulan Defold, artık resmen büyüdü. Defold artık tam teşekküllü bir 3D oyun motoru. Teknik olarak arka planda her zaman bir 3D bağlamda render yapsa da (ortografik bir kamera aracılığıyla düz düzlemler yansıtarak), son güncellemelerle birlikte özel 3D araçları, tam glTF mesh desteği ve gerçek 3D geliştirme için optimize edilmiş bir workflow sunmaya başladı.
Bu, ekosistem için devasa bir değişim. Eğer saniyeler içinde derlenen, tek haneli megabayt boyutlarında binary dosyaları üreten ve size hala dinamik 3D dünyalar render etme gücü veren bir motor arıyorsanız, Defold artık birinci sınıf bir aday. Bu defold 3d game engine tutorial ve mimari incelemesinde, Defold'un 3D rendering pipeline yapısının nasıl çalıştığına, sıfırdan özel bir 3D kamera script'inin nasıl yazılacağına ve hafif sıklet bir 3D oyunun networking süreçlerinin Backend tarafındaki yansımalarına derinlemesine dalacağız.
İşin Mutfağı: Defold'un 3D Pipeline Gerçekliği
3D'de Defold uzmanı olmak için öncelikle onun rendering felsefesini anlamalısınız. Defold, Unreal Engine'in yaptığı gibi size kutudan çıktığı haliyle önceden konfigüre edilmiş bir PBR (Physically Based Rendering) pipeline sunmaz. Bunun yerine, Lua ile yazılmış, son derece optimize ve data-driven bir render script sağlar.
Defold'da ekranda çizilen her şey bir render_script tarafından yönetilir. Varsayılan olarak bu script 2D için yapılandırılmıştır. Bir ortografik projeksiyon matrisi kurar, sprite'ları Z-değerlerine (derinlik) göre sıralar ve onları arkadan öne doğru çizer. Defold'un 3D yeteneklerini açığa çıkarmak için bu script'i perspektif projeksiyon matrisi kullanacak, donanım tabanlı depth testing'i etkinleştirecek ve 3D model'lerimiz için özel render predicate'leri tanımlayacak şekilde yeniden yazmalıyız.
Bu düşük seviyeli erişim iki ucu keskin bir kılıçtır. Bir yandan biraz matris matematiği yazmanız gerekir. Diğer yandan ise draw call'larınız üzerinde mutlak kontrole sahip olursunuz; bu da monolitik motorların inanılmaz derecede zorlaştırdığı şekillerde, rendering sürecini ultra düşük seviyeli donanımlar için optimize etmenize olanak tanır.
Özel 3D Render Script'inizi Mimari Olarak İnşa Etmek
Gerçek 3D model'leri, çizim sırasına göre yanlış şekilde üst üste binmeden render etmek için derinlik tamponunu (Z-buffer) etkinleştirmemiz gerekir. İşte Defold'un varsayılan pipeline'ının yerini alacak temel bir 3D render script'i.
-- main/3d_pipeline.render_script
function init(self)
-- Arka plan temizleme rengini tanımlayın (RGBA)
self.clear_color = vmath.vector4(0.1, 0.1, 0.12, 1.0)
self.clear_buffers = {
[render.BUFFER_COLOR_BIT] = self.clear_color,
[render.BUFFER_DEPTH_BIT] = 1.0,
[render.BUFFER_STENCIL_BIT] = 0
}
-- Render predicate'lerini oluşturun. 'model', 3D mesh'ler için varsayılan etikettir
self.predicates = {
model = render.predicate({"model"}),
gui = render.predicate({"gui"}),
text = render.predicate({"text"})
}
end
function update(self)
-- 1. Kare için rendering durumunu ayarlayın
render.set_depth_mask(true)
render.set_stencil_mask(0xff)
render.clear(self.clear_buffers)
-- 2. 3D Kamera Projeksiyonunu Yapılandırın
local window_width = render.get_window_width()
local window_height = render.get_window_height()
if window_width == 0 or window_height == 0 then return end
local aspect_ratio = window_width / window_height
local fov = math.rad(60) -- 60 derece Field of View
local near_z = 0.1
local far_z = 1000.0
local proj_matrix = vmath.matrix4_perspective(fov, aspect_ratio, near_z, far_z)
render.set_projection(proj_matrix)
-- 3. Depth Testing etkinleştirilmiş şekilde 3D Model'leri çizin
render.set_depth_test(render.COMPARE_LEQUAL)
render.set_cull_face(render.FACE_BACK)
render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
-- View matrisi, kamera script'imizden mesajlar yoluyla iletilir
if self.view_matrix then
render.set_view(self.view_matrix)
render.draw(self.predicates.model)
end
-- 4. 3D sahnenin üzerine GUI çizin (Ortografik)
render.set_depth_mask(false)
render.set_depth_test(render.COMPARE_ALWAYS)
local gui_proj = vmath.matrix4_orthographic(0, window_width, 0, window_height, -1, 1)
render.set_projection(gui_proj)
render.set_view(vmath.matrix4())
render.draw(self.predicates.gui)
render.draw(self.predicates.text)
end
function on_message(self, message_id, message)
if message_id == hash("set_view_matrix") then
self.view_matrix = message.matrix
end
end
Burada durumu (state) ne kadar açık bir şekilde yönettiğimize dikkat edin. Derinlik tamponunu temizliyor, mevcut pencere boyutlarına göre perspektif matrisini hesaplıyor, rasterizasyon döngülerinden tasarruf etmek için back-face culling uyguluyor ve son olarak GUI'yi render etmeden önce projeksiyon matrisini tekrar ortografik moduna döndürüyoruz. Bu size sağlam ve ayrılmış bir pipeline (split-pipeline) rendering mimarisi sağlar.
First-Person 3D Kamera Kontrolcüsü Oluşturmak
Tek başına bir render script, uzayda hareket eden bir kamera olmadan size pek bir şey göstermez. Defold, yoğun bir şekilde Message Passing mimarisi üzerine çalışır. Kameranın doğrudan transform.Translate() çağırdığı nesne yönelimli motorların aksine, Defold'da kamera script'imiz kendi view matrisini hesaplayacak ve bunu az önce yazdığımız render script'ine gönderecektir.
Mouse-look (pitch ve yaw) ve klavye hareketlerini (WASD) işleyen standart bir First-Person kamera oluşturalım.
-- scripts/camera_controller.script
go.property("mouse_sensitivity", 0.2)
go.property("move_speed", 10.0)
function init(self)
msg.post(".", "acquire_input_focus")
self.pitch = 0
self.yaw = 0
-- Mouse imlecini gizleyin ve yakalayın
window.set_mouse_lock(true)
self.forward = vmath.vector3(0, 0, -1)
self.right = vmath.vector3(1, 0, 0)
self.up = vmath.vector3(0, 1, 0)
self.velocity = vmath.vector3(0)
end
function update(self, dt)
local pos = go.get_position()
-- Hareket hızını uygulayın
if vmath.length_sqr(self.velocity) > 0 then
local move_dir = vmath.normalize(self.velocity)
pos = pos + move_dir * self.move_speed * dt
go.set_position(pos)
end
-- View matrisini hesaplayın
local rotation = go.get_rotation()
self.forward = vmath.rotate(rotation, vmath.vector3(0, 0, -1))
local target = pos + self.forward
local view_matrix = vmath.matrix4_look_at(pos, target, self.up)
-- Hesaplanan view matrisini render pipeline'ına gönderin
msg.post("@render:", "set_view_matrix", { matrix = view_matrix })
-- Bir sonraki kare için hızı sıfırlayın
self.velocity = vmath.vector3(0)
end
function on_input(self, action_id, action)
if action_id == hash("mouse_moved") then
self.yaw = self.yaw - action.dx * self.mouse_sensitivity
self.pitch = self.pitch + action.dy * self.mouse_sensitivity
-- Kameranın ters dönmesini önlemek için pitch değerini sınırlayın
self.pitch = math.max(-89, math.min(89, self.pitch))
-- Euler açılarını bir Quaternion'a dönüştürün
local rot_y = vmath.quat_rotation_y(math.rad(self.yaw))
local rot_x = vmath.quat_rotation_x(math.rad(self.pitch))
go.set_rotation(rot_y * rot_x)
elseif action_id == hash("move_forward") then
self.velocity = self.velocity + self.forward
elseif action_id == hash("move_backward") then
self.velocity = self.velocity - self.forward
elseif action_id == hash("move_left") then
self.right = vmath.cross(self.forward, self.up)
self.velocity = self.velocity - self.right
elseif action_id == hash("move_right") then
self.right = vmath.cross(self.forward, self.up)
self.velocity = self.velocity + self.right
end
end
Bu script, pitch ve yaw değerlerini ayarlamak için fare delta hareketlerini yakalar ve bu Euler açılarını sağlam bir Quaternion rotasyonuna dönüştürür. Ardından, "W" tuşuna basmanın sizi her zaman o an baktığınız yöne hareket ettirmesini sağlamak için forward ve right vektörlerini doğrudan bu rotasyondan türetir.
3D Asset Entegrasyonu ve Özel Shader'lar
Defold'un son güncellemeleriyle, projenize 3D asset getirmek artık çok kolay. Motor, web ve hafif sıklet oyun geliştirme için endüstri standardı haline gelen .gltf ve .glb formatlarını yerel olarak destekliyor.
Ancak, bir mesh render etmek bir materyal gerektirir ve materyaller de Shader'lara ihtiyaç duyar. Varsayılan olarak Defold temel materyaller içerir, ancak kendi GLSL shader'larınızı yazmak size öne çıkmanız için gereken görsel özgünlüğü verir. Mobil veya HTML5 hedefleri için mükemmel şekilde optimize edilmiş, hızlı ve unlit textured bir shader yazalım.
Vertex Shader (model.vp)
// model.vp
uniform highp mat4 view_proj;
uniform highp mat4 world;
attribute highp vec4 position;
attribute mediump vec2 texcoord0;
varying mediump vec2 var_texcoord0;
void main()
{
// Vertex'in nihai ekran uzayındaki pozisyonunu hesaplayın
vec4 p = view_proj * world * vec4(position.xyz, 1.0);
var_texcoord0 = texcoord0;
gl_Position = p;
}
Fragment Shader (model.fp)
// model.fp
varying mediump vec2 var_texcoord0;
uniform lowp sampler2D texture_sampler;
uniform lowp vec4 tint;
void main()
{
// Dokuyu örnekleyin ve bir renk tonuyla (tint) çarpın
lowp vec4 tex_color = texture2D(texture_sampler, var_texcoord0.xy);
gl_FragColor = tex_color * tint;
}
Defold materyal dosyanızda, view_proj uniform'unu motorun yerleşik view-projection matrisine ve texture_sampler'ı mesh'inizin diffuse dokusuna eşlersiniz. Bu shader'lar dinamik ışıklandırma veya shadow map hesaplamadığı için inanılmaz derecede hızlı çalışır ve düşük seviyeli donanımlarda kolayca 60 FPS korumanıza olanak tanır.
3D Multiplayer ve Durum Senkronizasyonu
2D grid tabanlı bir oyundan tam 3D bir ortama geçtiğinizde, networking mimarinizin karmaşıklığı katlanarak artar. 2D'den 3D'ye geçmek, durum senkronizasyonunuzun (state synchronization) artık Z-ekseni derinliğini, fizik motorları arasındaki floating-point tutarsızlıklarını ve tam quaternion rotasyonlarını hesaba katması gerektiği anlamına gelir. Bunu kötü yönetirseniz, ciddi görsel takılmalarla (stuttering) karşılaşırsınız — ki bu, How To Fix Player Location Desync In Uefn And Unreal Engine Multiplayer analizimizde incelediğimiz yaygın bir sorundur.
Defold hafif sıklet bir motor olduğu için, Unreal Engine'in RPC'leri gibi ağır ve yerleşik bir replikasyon sistemiyle gelmez. Durum verilerinizi verimli bir şekilde paketlemek sizin sorumluluğunuzdadır.
3D pozisyonları senkronize etmek için REST API'lere güvenmek oyun loop'unuzu anında darboğaza sokacaktır. Bunun yerine kalıcı, çift yönlü bağlantılara ihtiyacınız var. Önceki rehberimiz Ditch Http Polling An Unreal Engine Websockets Tutorial For Real Time Backends konusunu kapsasa da, aynı mimari prensipler doğrudan Defold'un Lua tabanlı WebSocket eklentileri için de geçerlidir.
İşte WebSocket üzerinden payload boyutunu minimize etmek için 3D transform verilerini nasıl serialize etmeniz gerektiğine dair bir örnek:
-- scripts/network_sync.lua
local json = require "builtins.scripts.json"
function serialize_transform(go_id)
local pos = go.get_position(go_id)
local rot = go.get_rotation(go_id)
-- Payload'u gereken mutlak minimum veriye sıkıştırın
-- Bant genişliğinden tasarruf etmek için pozisyonu 2 ondalık basamağa yuvarlıyoruz
local payload = {
id = hash_to_hex(go_id),
x = math.floor(pos.x * 100) / 100,
y = math.floor(pos.y * 100) / 100,
z = math.floor(pos.z * 100) / 100,
-- Quaternion'lar doğru rekonstrüksiyon için 4 bileşenin tamamını gerektirir
qx = rot.x,
qy = rot.y,
qz = rot.z,
qw = rot.w
}
return json.encode(payload)
end
Float'ları yuvarlayarak ve yalnızca temel verileri paketleyerek, hızlı hareket güncellemeleri sırasında ağ tamponlarınızın taşmasını önlersiniz. Bu akışı yönetmek, akıcı bir cross-platform deneyiminin anahtarıdır.
Cross-Platform 3D Geliştirmenin Altyapı Yükü
Defold'da son derece optimize edilmiş bir 3D oyun client'ı yazmak son derece tatmin edicidir. Motor işinize karışmaz, milisaniyeler içinde derlenir ve doğrudan mantığa odaklanmanıza izin verir.
Ancak, o 3D oyunu multiplayer yapmaya, bulut kayıtları eklemeye veya cross-platform leaderboard'lar uygulamaya karar verdiğiniz an, odağınız aniden oyun geliştirmeden sunucu orkestrasyonuna kayar. Kendinizi bir anda Dockerfile'lar yazarken, Kubernetes cluster'ları yapılandırırken, session state için Redis instance'larıyla boğuşurken ve WebSocket gateway'lerinizi DDoS saldırılarına karşı korumaya çalışırken bulursunuz.
Bunu kendiniz inşa etmek; load balancer'lar, database sharding ve SSL cert yönetimi kurmanızı gerektirir — ki bu, sadece güvenilir bir prototip çalıştırmak için bile 4-6 haftalık bir iş demektir.
horizOn ile bu Backend servisleri önceden konfigüre edilmiş olarak gelir ve altyapınız yerine oyununuzu yayınlamanıza olanak tanır. horizOn; kullanıcı kimlik doğrulama, gerçek zamanlı veritabanı senkronizasyonu ve server-authoritative mantık için yerel entegrasyonlar sağlayarak, tescilli backend ekosistemleriyle gelmeyen Defold gibi hafif motorlar için aradaki boşluğu mükemmel şekilde doldurur. Sunucu mimarisinin ağır işlerini dış kaynak kullanarak hallederken, küçük bir client motorunun hızını korumaya devam edersiniz.
Defold 3D Geliştirme İçin En İyi Pratikler
Defold'da ticari olarak uygulanabilir bir 3D proje inşa etmeyi planlıyorsanız, şu mimari yönergelere sıkı sıkıya bağlı kalın:
- Geometriyi Yoğun Şekilde Optimize Edin: Defold hız için tasarlanmıştır. Hafiflik avantajını korumak için, özellikle HTML5/WebGL hedefliyorsanız, sahne başına toplam poligon sayısını 100.000'in altında tutun. Detayları simüle etmek için yüksek yoğunluklu mesh'ler yerine baked normal map'ler kullanın.
- Frustum Culling İçin Render Predicate'lerini Kullanın: Defold, 3D uzayda kameranın görüş alanı dışındaki nesneleri varsayılan olarak otomatik olarak ayıklamaz (cull). Rasterizasyon süresinden tasarruf etmek için Lua'da özel frustum culling mantığı yazmalı ve sınırların dışındaki nesnelerin model bileşenlerini dinamik olarak devre dışı bırakmalısınız.
- Atlasing ile Draw Call'ları Birleştirin: Her benzersiz materyal ve doku, GPU'ya gönderilen ayrı bir draw call gerektirir. Dokularınızı büyük doku atlaslarında (texture atlases) birleştirin. Eğer 10 farklı 3D model tamamen aynı materyali ve atlası paylaşıyorsa, Defold bunları arka planda çok daha verimli bir şekilde batch edebilir.
- Karmaşık Matematik İşlemlerini Önceden Hesaplayın: Matris çarpımları ve quaternion dönüşümleri Lua'da oldukça maliyetli işlemlerdir. Forward ve right vektörlerinizi cache'leyin ve ağır matematiği her karede koşulsuz yapmak yerine, yalnızca oyuncunun rotasyonu gerçekten değiştiğinde yeniden hesaplayın.
- Mantığı (Logic) Render Frekansından Ayırın: Oyun mantığınız (
update) 60 FPS'de çalışabilir ancak özel fizik veya networking adımlarınız 30 FPS'de tick yapabilir. Değişken monitör tazeleme hızlarında tereyağı gibi pürüzsüz bir rendering sağlamak için, 3D görsel pozisyonlarınızı doğrudan en son duruma sabitlemek yerine hıza göre interpolate edin. - Lua Garbage Collection Sürecini Yönetin: Dinamik bir 3D ortamda sık sık vektör nesneleri ve matrisler oluşturup yok edersiniz. Lua'nın Garbage Collector süreci, yönetilmezse fark edilebilir kare sıçramalarına (frame spikes) neden olabilir.
updatedöngünüzün içinde yeni yerel değişkenler oluşturmak yerine,vmath.vector3vevmath.matrix4instance'larını doğrudan dahili değerlerini güncelleyerek mümkün olduğunca yeniden kullanın. Mermiler ve varlıklar için memory pool'lar (bellek havuzları) önceden tahsis edin. - Işıklandırmayı Harici Olarak Bake Edin: Özel GLSL shader'larındaki dinamik ışıklandırma mobil cihazlarda performans bütçenizi hızla tüketeceğinden, glTF modellerinizi dışa aktarmadan önce global illumination ve ambient occlusion verilerinizi Blender veya Maya kullanarak doğrudan dokularınıza bake edin. Güzelce bake edilmiş ışıklandırmaya sahip basit bir unlit shader, mobil web tarayıcılarında her zaman karmaşık bir dinamik shader'dan daha iyi performans gösterecektir.
Sonuç
Defold'un sağlam bir 3D oyun motoruna dönüşümü, bağımsız geliştiriciler için devasa bir kazanımdır. Yıldırım hızındaki derleme sürelerini ve inanılmaz derecede küçük binary boyutlarını korurken, geniş ve ilgi çekici 3D dünyalar inşa etmek için gereken ham matematiksel temelleri ve araçları sunmayı başarıyor. Özel render script'lerinde uzmanlaşarak, matris operasyonlarını anlayarak ve ağ verilerinizi verimli bir şekilde serialize ederek, teknik olarak çok daha büyük ve hantal motorlarla rekabet edebilecek cross-platform yapımlar inşa edebilirsiniz.
Optimize edilmiş 3D client'ınızı çevrimiçi hale getirmeye ve ham altyapı yönetme baş ağrısı olmadan multiplayer backend'inizi ölçeklendirmeye hazır olduğunuzda, horizOn'u ücretsiz deneyin veya bir sonraki Defold projenize gerçek zamanlı servisleri ne kadar hızlı entegre edebileceğinizi görmek için API dökümanlarına göz atın.