Defold Menjadi Sepenuhnya 3D: Bedah Teknis dan Tutorial Arsitektur
Ringkasnya
Defold kini telah berevolusi menjadi 3D game engine yang mumpuni dengan tetap mempertahankan performa tinggi dan build size yang sangat ringan. Artikel ini membedah arsitektur rendering pipeline Lua, pembuatan sistem kamera perspektif, hingga optimasi shader untuk platform WebGL dan mobile. Dilengkapi dengan panduan sinkronisasi state untuk multiplayer, tutorial ini menjadi referensi teknis esensial bagi developer yang ingin membangun game 3D performan tanpa beban infrastruktur yang berat.
Setiap indie developer tahu momen ketika game engine mereka mengkhianati mereka. Anda memulai dengan konsep 2D yang ringan, lalu scope creep memperkenalkan elemen 3D, dan tiba-tiba build size engine Anda membengkak, load times melambat, dan web builds mengalami crash karena out of memory. Engine masif seperti Unity dan Unreal memang fenomenal untuk fidelitas AAA, namun bagi solo developer yang menargetkan distribusi cross-platform yang lancar—terutama WebGL dan mobile—engine tersebut seringkali terasa seperti mengendarai tank hanya untuk pergi ke toko kelontong.
Inilah Defold. Secara historis dikenal sebagai 2D engine yang ultra-cepat dan zero-bloat yang digunakan oleh berbagai studio untuk mencapai setiap platform mulai dari HTML5 hingga Nintendo Switch dengan satu codebase, Defold kini telah resmi beranjak dewasa. Ia sekarang merupakan 3D game engine yang sepenuhnya mampu. Walaupun secara teknis ia selalu melakukan rendering dalam konteks 3D di balik layar (memproyeksikan bidang datar melalui orthographic camera), update terbaru telah memperkenalkan tooling 3D khusus, dukungan glTF mesh yang lengkap, dan workflow yang disederhanakan untuk pengembangan 3D murni.
Ini adalah pergeseran besar bagi ekosistem ini. Jika Anda mencari engine yang melakukan kompilasi dalam hitungan detik, menghasilkan biner berukuran satu digit megabyte, namun tetap memberi Anda kekuatan untuk merender dunia 3D yang dinamis, Defold kini menjadi kontender kelas atas. Dalam defold 3d game engine tutorial dan bedah arsitektur ini, kita akan menyelam jauh ke dalam bagaimana rendering pipeline 3D milik Defold bekerja, cara melakukan scripting custom 3D camera dari nol, dan implikasi backend dari networking pada game 3D yang ringan.
Di Balik Layar: Realita 3D Pipeline pada Defold
Untuk memahami cara menguasai Defold dalam 3D, Anda harus memahami filosofi rendering-nya. Defold tidak memberikan PBR (Physically Based Rendering) pipeline yang sudah terkonfigurasi secara out of the box seperti yang dilakukan Unreal Engine. Sebaliknya, ia menyediakan render script berbasis data yang sangat dioptimalkan yang ditulis dalam Lua.
Segala sesuatu yang digambar di layar dalam Defold ditangani oleh render_script. Secara default, script ini dikonfigurasi untuk 2D. Ia mengatur orthographic projection matrix, mengurutkan sprite berdasarkan Z-value (depth), dan menggambarnya secara back-to-front. Untuk membuka kapabilitas 3D Defold, kita harus menulis ulang script ini untuk menggunakan perspective projection matrix, mengaktifkan hardware depth testing, dan mendefinisikan custom render predicates untuk 3D models kita.
Akses low-level ini adalah pedang bermata dua. Di satu sisi, Anda harus menulis sedikit matematika matriks. Di sisi lain, Anda memiliki kontrol mutlak atas draw calls Anda, memungkinkan Anda mengoptimalkan rendering untuk hardware ultra-low-end dengan cara yang sangat sulit dilakukan pada monolithic engines.
Merancang Custom 3D Render Script Anda
Untuk merender 3D models yang sebenarnya tanpa tumpang tindih yang salah berdasarkan urutan gambar, kita perlu mengaktifkan depth buffer (Z-buffer). Berikut adalah fondasi 3D render script yang menggantikan default pipeline milik Defold.
-- main/3d_pipeline.render_script
function init(self)
-- Define the background clear color (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
}
-- Create render predicates. 'model' is the default tag for 3D meshes
self.predicates = {
model = render.predicate({"model"}),
gui = render.predicate({"gui"}),
text = render.predicate({"text"})
}
end
function update(self)
-- 1. Setup the rendering state for the frame
render.set_depth_mask(true)
render.set_stencil_mask(0xff)
render.clear(self.clear_buffers)
-- 2. Configure the 3D Camera Projection
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 degree 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. Draw 3D Models with Depth Testing enabled
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)
-- The view matrix is passed via messages from our camera script
if self.view_matrix then
render.set_view(self.view_matrix)
render.draw(self.predicates.model)
end
-- 4. Draw GUI over the 3D scene (Orthographic)
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
Perhatikan betapa eksplisitnya kita mengelola state di sini. Kita membersihkan depth buffer, menghitung perspective matrix berdasarkan dimensi jendela saat ini, menerapkan back-face culling untuk menghemat siklus rasterisasi, dan akhirnya, mengalihkan projection matrix kembali ke orthographic sebelum melakukan rendering pada GUI. Ini memberi Anda arsitektur rendering split-pipeline yang tangguh.
Membangun First-Person 3D Camera Controller
A render script saja tidak akan memperlihatkan banyak hal tanpa kamera yang bergerak melalui ruang. Defold beroperasi secara intensif pada arsitektur Message Passing. Berbeda dengan engine berorientasi objek di mana kamera mungkin secara langsung memanggil transform.Translate(), di Defold, camera script kita akan menghitung view matrix-nya dan mengirimkannya ke render script yang baru saja kita tulis.
Mari kita susun kamera First-Person standar yang menangani mouse-look (pitch dan yaw) serta pergerakan keyboard (WASD).
-- 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
-- Hide and capture the mouse cursor
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()
-- Apply movement velocity
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
-- Calculate the view matrix
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)
-- Send the calculated view matrix to the render pipeline
msg.post("@render:", "set_view_matrix", { matrix = view_matrix })
-- Reset velocity for the next frame
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
-- Clamp pitch to prevent the camera from flipping over
self.pitch = math.max(-89, math.min(89, self.pitch))
-- Convert Euler angles to a Quaternion
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
Script ini menangkap pergerakan delta mouse untuk menyesuaikan pitch dan yaw, mengubah sudut Euler tersebut menjadi rotasi Quaternion yang tangguh. Ia kemudian menurunkan vektor forward dan right secara langsung dari rotasi tersebut untuk memastikan bahwa menekan "W" selalu menggerakkan Anda ke arah yang sedang Anda lihat.
Integrasi Aset 3D dan Custom Shaders
Dengan update terbaru Defold, membawa aset 3D ke dalam proyek Anda menjadi sangat mudah. Engine ini secara native mendukung format .gltf dan .glb, yang telah menjadi standar industri untuk pengembangan web dan game ringan.
Namun, melakukan rendering pada mesh memerlukan material, dan material memerlukan shaders. Secara default, Defold menyertakan material dasar, tetapi menulis GLSL shaders Anda sendiri memberikan kekhasan visual yang diperlukan agar menonjol. Mari kita tulis shader bertekstur unlit yang cepat dan dioptimalkan secara sempurna untuk target mobile atau HTML5.
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()
{
// Calculate the final screen-space position of the vertex
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()
{
// Sample the texture and multiply by a color tint
lowp vec4 tex_color = texture2D(texture_sampler, var_texcoord0.xy);
gl_FragColor = tex_color * tint;
}
Dalam file material Defold Anda, Anda memetakan uniform view_proj ke view-projection matrix bawaan engine, dan texture_sampler ke diffuse texture dari mesh Anda. Karena shaders ini tidak menghitung pencahayaan dinamis atau shadow maps, mereka berjalan sangat cepat, memungkinkan Anda mempertahankan 60 FPS dengan mudah pada hardware low-end.
Menangani 3D Multiplayer dan State Synchronization
Ketika Anda bertransisi dari game berbasis grid 2D ke lingkungan 3D penuh, kompleksitas arsitektur networking Anda meningkat secara eksponensial. Berpindah dari 2D ke 3D berarti State Synchronization Anda sekarang harus memperhitungkan kedalaman Z-axis, ketidakakuratan floating-point di berbagai physics engines, dan rotasi quaternion penuh. Jika Anda menangani ini dengan buruk, Anda akan berakhir dengan visual stuttering yang parah—masalah umum yang kami eksplorasi saat menganalisis How To Fix Player Location Desync In Uefn And Unreal Engine Multiplayer.
Karena Defold adalah engine yang ringan, ia tidak dilengkapi dengan sistem replikasi yang berat dan bawaan seperti RPCs milik Unreal Engine. Anda bertanggung jawab untuk mempacking state data Anda secara efisien.
Relying pada REST APIs untuk menyinkronkan posisi 3D akan menghambat game loop Anda secara instan. Sebaliknya, Anda memerlukan koneksi persistent yang bidirectional. Meskipun panduan kami sebelumnya membahas cara Ditch Http Polling An Unreal Engine Websockets Tutorial For Real Time Backends, prinsip arsitektur yang sama persis berlaku langsung pada ekstensi WebSocket berbasis Lua milik Defold.
Berikut adalah contoh bagaimana Anda harus menserialisasi data 3D transform untuk meminimalkan ukuran payload melalui WebSockets:
-- 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)
-- Compress the payload to the absolute minimum required data
-- We round position to 2 decimal places to save bandwidth
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,
-- Quaternions require all 4 components for accurate reconstruction
qx = rot.x,
qy = rot.y,
qz = rot.z,
qw = rot.w
}
return json.encode(payload)
end
Dengan membulatkan float dan mempacking hanya data esensial, Anda mencegah network buffers Anda meluap selama pembaruan gerakan yang cepat. Mengelola aliran ini adalah kunci untuk permainan cross-platform yang responsif.
Beban Infrastruktur pada Cross-Platform 3D
Menulis game client 3D yang sangat dioptimalkan dalam Defold sangatlah memuaskan. Engine ini tidak menghalangi Anda, melakukan kompilasi dalam hitungan milidetik, dan membiarkan Anda fokus sepenuhnya pada logika.
Namun, saat Anda memutuskan untuk membuat game 3D tersebut menjadi multiplayer, menambahkan cloud saves, atau mengimplementasikan cross-platform leaderboards, fokus Anda segera bergeser dari pengembangan game ke server orchestration. Anda tiba-tiba menemukan diri Anda menulis Dockerfiles, mengonfigurasi Kubernetes clusters, bergulat dengan Redis instances untuk session state, dan mencoba mengamankan WebSocket gateways Anda dari serangan DDoS.
Membangun ini sendiri memerlukan pengaturan load balancers, database sharding, dan SSL cert management — yang bisa memakan waktu 4-6 minggu kerja hanya untuk menjalankan prototipe yang andal.
Dengan horizOn, layanan backend ini sudah terkonfigurasi sebelumnya, membiarkan Anda merilis game Anda alih-alih infrastruktur Anda. horizOn menyediakan integrasi native untuk user authentication, real-time database syncing, dan server-authoritative logic, menjembatani celah dengan sempurna untuk lightweight engines seperti Defold yang tidak menyertakan ekosistem backend proprietary. Anda tetap dapat mempertahankan kecepatan client engine yang kecil sambil melakukan outsourcing tugas berat arsitektur server.
Best Practices untuk Pengembangan 3D di Defold
Jika Anda berencana untuk membangun proyek 3D yang layak secara komersial di Defold, patuhi pedoman arsitektur berikut dengan ketat:
- Jaga Geometri Tetap Sangat Teroptimasi: Defold dirancang untuk kecepatan. Untuk mempertahankan keunggulan ringannya, jaga geometri level Anda di bawah 100.000 poligon total per scene, terutama jika Anda menargetkan HTML5/WebGL. Gunakan baked normal maps daripada high-density meshes untuk mensimulasikan detail.
- Manfaatkan Render Predicates untuk Frustum Culling: Secara default, Defold tidak secara otomatis melakukan culling pada objek yang berada di luar pandangan kamera dalam ruang 3D. Anda harus menulis logika frustum culling kustom di Lua, secara dinamis menonaktifkan komponen model dari objek yang berada di luar batas untuk menghemat waktu rasterisasi.
- Konsolidasikan Draw Calls melalui Atlasing: Setiap material dan tekstur yang unik memerlukan draw call terpisah yang dikirim ke GPU. Gabungkan tekstur Anda ke dalam texture atlases yang besar. Jika 10 model 3D yang berbeda berbagi material dan atlas yang sama persis, Defold dapat melakukan batching dengan jauh lebih efisien di balik layar.
- Hitung Terlebih Dahulu Matematika yang Kompleks: Perkalian matriks dan konversi quaternion adalah operasi yang sangat mahal dalam Lua. Cache vektor forward dan right Anda dan hanya hitung ulang saat rotasi pemain benar-benar berubah, daripada melakukan matematika berat tersebut tanpa syarat di setiap frame.
- Pisahkan Logika dari Frekuensi Render: Logika game Anda (
update) mungkin berjalan pada 60 FPS, tetapi langkah physics atau networking kustom Anda mungkin berjalan pada 30 FPS. Lakukan interpolasi pada posisi visual 3D Anda berdasarkan velocity daripada langsung menempelkannya ke state terbaru untuk memastikan rendering yang sangat mulus pada berbagai refresh rate monitor. - Kelola Garbage Collection pada Lua: Dalam lingkungan 3D yang dinamis, Anda sering membuat dan menghancurkan objek vektor dan matriks. Garbage Collector pada Lua dapat menyebabkan lonjakan frame yang nyata jika tidak dikelola. Gunakan kembali instance
vmath.vector3danvmath.matrix4sebisa mungkin dengan memperbarui nilai internalnya secara langsung, alih-alih menginstansiasi variabel lokal baru di dalam loopupdateAnda. Alokasikan memori pool terlebih dahulu untuk peluru dan entitas. - Bake Pencahayaan Anda Secara Eksternal: Karena pencahayaan dinamis dalam custom GLSL shaders akan dengan cepat menghabiskan budget performa Anda pada perangkat mobile, lakukan bake pada global illumination dan ambient occlusion langsung ke dalam tekstur Anda menggunakan Blender atau Maya sebelum mengekspor model glTF Anda. Unlit shader sederhana dengan pencahayaan yang di-bake dengan indah akan selalu mengungguli shader dinamis yang kompleks pada browser web mobile.
Kesimpulan
Evolusi Defold menjadi 3D game engine yang tangguh adalah kemenangan besar bagi developer independen. Ia berhasil mempertahankan waktu kompilasi yang secepat kilat dan footprint biner yang sangat kecil sambil menawarkan fondasi matematika mentah dan tooling yang diperlukan untuk membangun dunia 3D yang luas dan menarik. Dengan menguasai custom render scripts, memahami operasi matriks, dan menserialisasi data network Anda secara efisien, Anda dapat membangun judul cross-platform yang bersaing secara teknis dengan engine yang jauh lebih besar dan berat.
Ketika Anda siap untuk membawa client 3D Anda yang sangat teroptimasi secara online dan menskalakan multiplayer backend Anda tanpa pusing mengelola infrastruktur mentah, cobalah horizOn secara gratis atau pelajari API docs untuk melihat seberapa cepat Anda dapat mengintegrasikan layanan real-time ke dalam proyek Defold Anda berikutnya.