محرك Defold ينتقل بالكامل إلى عالم الـ 3D: تحليل تقني ودليل حول Architecture المحرك
باختصار
يستعرض هذا المقال التحول التقني لمحرك Defold إلى دعم ألعاب الـ 3D بالكامل، مع التركيز على كيفية بناء Rendering Pipeline مخصص واستخدام Lua لبرمجة الكاميرا والـ Shaders بكفاءة عالية. يتناول الدليل استراتيجيات تحسين الأداء مثل Frustum Culling وإدارة الذاكرة عبر Garbage Collection لضمان سلاسة الألعاب على WebGL والموبايل. كما يبرز المقال دور منصة horizOn في توفير بنية تحتية جاهزة للـ Backend لدعم ألعاب الـ Multiplayer وتجاوز تعقيدات إدارة الخوادم.
كل مطور ألعاب indie يعرف اللحظة التي يخذله فيها محرك الألعاب الخاص به. تبدأ بمفهوم بسيط ثنائي الأبعاد 2D، ثم يؤدي توسع النطاق (scope creep) إلى إدخال عناصر 3D، وفجأة ينفجر حجم البناء (build size)، وتصبح أوقات التحميل بطيئة جداً، وتتعطل إصدارات الويب بسبب نفاذ الذاكرة. المحركات الضخمة مثل Unity و Unreal مذهلة لتحقيق جودة AAA، ولكن بالنسبة للمطورين المنفردين (solo developers) الذين يهدفون إلى توزيع سلس عبر المنصات (cross-platform)—خاصة WebGL والموبايل—غالباً ما تبدو هذه المحركات وكأنها قيادة دبابة للذهاب لشراء البقالة.
هنا يأتي دور Defold. تاريخياً، عُرف Defold كمحرك 2D فائق السرعة وبدون زوائد (zero-bloat)، تستخدمه الاستوديوهات للوصول إلى كل المنصات من HTML5 إلى Nintendo Switch بكود برمجى واحد (single codebase). والآن، نضج Defold رسمياً ليصبح محرك ألعاب 3D كامل القدرات. وبينما كان تقنياً يقوم بـ Rendering في سياق 3D خلف الكواليس (عبر عرض مسطحات عبر orthographic camera)، فإن التحديثات الأخيرة قدمت أدوات 3D مخصصة، ودعماً كاملاً لـ glTF mesh، وسير عمل مبسط لتطوير ألعاب 3D حقيقية.
هذا تحول هائل في النظام البيئي (ecosystem). إذا كنت تبحث عن محرك يقوم بعملية الـ compile في ثوانٍ، وينتج ملفات binary بحجم ميجابايتات قليلة، ومع ذلك يمنحك القوة لإنشاء عوالم 3D ديناميكية، فإن Defold هو الآن منافس من الدرجة الأولى. في هذا الدليل حول defold 3d game engine والتحليل التقني لـ Architecture الخاصة به، سنغوص بعمق في كيفية عمل الـ 3D rendering pipeline في Defold، وكيفية برمجة custom 3D camera من الصفر، والآثار المترتبة على Backend عند ربط لعبة 3D خفيفة بالشبكة.
تحت الغطاء: حقيقة الـ 3D Pipeline في Defold
لفهم كيفية احتراف Defold في الـ 3D، يجب أن تفهم فلسفة الـ Rendering الخاصة به. لا يمنحك Defold نظام PBR (Physically Based Rendering) جاهزاً كما يفعل Unreal Engine. بدلاً من ذلك، فإنه يوفر render_script فائق التحسين وقائم على البيانات (data-driven) مكتوب بلغة Lua.
كل شيء يتم رسمه على الشاشة في Defold تتم معالجته بواسطة render_script. افتراضياً، يتم إعداد هذا السكريبت لـ 2D؛ حيث يقوم بإعداد orthographic projection matrix، وفرز الـ sprites حسب قيمة Z (العمق)، ورسمها من الخلف إلى الأمام. لفتح إمكانيات 3D في Defold، يجب علينا إعادة كتابة هذا السكريبت لاستخدام perspective projection matrix، وتفعيل hardware depth testing، وتحديد custom render predicates لنماذج الـ 3D الخاصة بنا.
هذا الوصول منخفض المستوى (low-level access) هو سلاح ذو حدين. من ناحية، عليك كتابة بعض رياضيات المصفوفات (matrix math). ومن ناحية أخرى، لديك تحكم مطلق في draw calls الخاصة بك، مما يسمح لك بتحسين الـ Rendering للأجهزة الضعيفة جداً بطرق تجعلها المحركات الضخمة صعبة للغاية.
بناء Custom 3D Render Script الخاص بك
لرسم نماذج 3D حقيقية دون تداخلها بشكل خاطئ بناءً على ترتيب الرسم، نحتاج إلى تفعيل depth buffer (أو Z-buffer). إليك 3D render script أساسي يستبدل الـ pipeline الافتراضي في 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
لاحظ مدى الدقة في إدارة الحالة (state) هنا. نقوم بمسح الـ depth buffer، وحساب perspective matrix بناءً على أبعاد النافذة الحالية، وفرض back-face culling لتوفير دورات الـ rasterization، وأخيراً، إعادة الـ projection matrix إلى orthographic قبل رسم الـ GUI. هذا يمنحك Architecture متينة لعملية Rendering مقسمة.
بناء First-Person 3D Camera Controller
لن يظهر لك الـ render script الكثير دون وجود كاميرا تتحرك في المكان. يعتمد Defold بشكل كبير على Message Passing architecture. على عكس المحركات المعتمدة على البرمجة الكائنية (object-oriented) حيث قد تستدعي الكاميرا مباشرة transform.Translate()، في Defold، سيقوم سكريبت الكاميرا بحساب view matrix الخاصة به وإرسالها إلى الـ render script الذي كتبناه للتو.
لنقم ببناء كاميرا First-Person قياسية تتعامل مع حركة الماوس (pitch و yaw) وحركة لوحة المفاتيح (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
يقوم هذا السكريبت بالتقاط حركات الماوس لتعديل الـ pitch و yaw، وتحويل زوايا Euler تلك إلى Quaternion rotation قوي. ثم يستنتج متجهات الـ forward و right مباشرة من ذلك الـ rotation لضمان أن الضغط على "W" يحركك دائماً في الاتجاه الذي تنظر إليه حالياً.
تكامل أصول الـ 3D والـ Custom Shaders
مع تحديثات Defold الأخيرة، أصبح جلب أصول 3D إلى مشروعك أمراً هيناً. يدعم المحرك بشكل أصلي تنسيقات .gltf و .glb التي أصبحت معيار الصناعة لتطوير الويب والألعاب الخفيفة.
ومع ذلك، فإن رسم mesh يتطلب material، والـ materials تتطلب shaders. بشكل افتراضي، يتضمن Defold مواد أساسية، ولكن كتابة GLSL shaders الخاصة بك تمنحك التميز البصري اللازم للتألق. لنكتب unlit textured shader سريعاً ومحسناً تماماً لمنصات الموبايل أو 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;
}
في ملف Defold material الخاص بك، تقوم بربط الـ view_proj uniform بـ view-projection matrix المدمجة في المحرك، والـ texture_sampler بـ diffuse texture الخاصة بالـ mesh. ونظراً لأن هذه الـ shaders لا تقوم بحساب إضاءة ديناميكية أو shadow maps، فإنها تعمل بسرعة مذهلة، مما يسمح لك بالحفاظ على 60 FPS بسهولة على الأجهزة الضعيفة.
التعامل مع 3D Multiplayer ومزامنة الحالة
عندما تنتقل من لعبة 2D تعتمد على الشبكة (grid-based) إلى بيئة 3D كاملة، تزداد تعقيدات الـ networking architecture بشكل كبير. الانتقال من 2D إلى 3D يعني أن مزامنة الحالة (state synchronization) يجب أن تأخذ في الاعتبار عمق محور Z، وعدم دقة الأعداد العائمة (floating-point) عبر محركات الفيزياء، و Quaternion rotations الكاملة. إذا تعاملت مع هذا بشكل سيئ، فسينتهي بك الأمر مع تقطع بصري (visual stuttering) شديد—وهي مشكلة شائعة استكشفناها عند تحليل How To Fix Player Location Desync In Uefn And Unreal Engine Multiplayer.
بما أن Defold محرك خفيف، فهو لا يأتي مع نظام replication ثقيل مدمج مثل RPCs في Unreal Engine. أنت مسؤول عن حزم بيانات الحالة (state data) بكفاءة.
الاعتماد على REST APIs لمزامنة مواقع 3D سيؤدي إلى خنق الـ game loop فوراً. بدلاً من ذلك، تحتاج إلى اتصالات مستمرة وثنائية الاتجاه (bidirectional). وبينما غطى دليلنا السابق كيفية Ditch Http Polling An Unreal Engine Websockets Tutorial For Real Time Backends، فإن نفس مبادئ الـ Architecture تنطبق مباشرة على إضافات WebSocket الخاصة بـ Defold المستندة إلى Lua.
إليك مثال لكيفية عمل serialize لبيانات 3D transform لتقليل حجم الـ payload عبر 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
من خلال تقريب الأرقام العائمة وحزم البيانات الأساسية فقط، فإنك تمنع network buffers من الفيضان أثناء تحديثات الحركة السريعة. إدارة هذا التدفق هو المفتاح للعب سلس عبر المنصات.
عبء البنية التحتية للـ 3D عبر المنصات
كتابة game client 3D محسن للغاية في Defold هو أمر مرضٍ للغاية. المحرك لا يعترض طريقك، ويقوم بعملية الـ compile في أجزاء من الثانية، ويسمح لك بالتركيز بشكل صارم على المنطق (logic).
ومع ذلك، في اللحظة التي تقرر فيها جعل لعبة الـ 3D تلك Multiplayer، أو إضافة cloud saves، أو تنفيذ cross-platform leaderboards، يتحول تركيزك فوراً من تطوير اللعبة إلى إدارة الخوادم (server orchestration). تجد نفسك فجأة تكتب Dockerfiles، وتعدل Kubernetes clusters، وتصارع مع Redis لمزامنة الجلسات، وتحاول تأمين WebSocket gateways ضد هجمات DDoS.
بناء هذا بنفسك يتطلب إعداد load balancers، و database sharding، وإدارة SSL cert — وهو عمل يستغرق بسهولة من 4 إلى 6 أسابيع فقط للحصول على prototype موثوق.
مع horizOn، تأتي خدمات الـ Backend هذه معدة مسبقاً، مما يتيح لك إطلاق لعبتك بدلاً من إطلاق بنية تحتية. يوفر horizOn تكاملات أصلية لـ user authentication، ومزامنة قواعد البيانات في الوقت الفعلي، و server-authoritative logic، مما يسد الفجوة بشكل مثالي للمحركات الخفيفة مثل Defold التي لا تشحن مع أنظمة Backend خاصة بها. يمكنك الحفاظ على سرعة محرك عميل صغير مع إسناد المهام الثقيلة لـ server architecture إلى جهة خارجية.
أفضل الممارسات لتطوير 3D في Defold
إذا كنت تخطط لبناء مشروع 3D تجاري ناجح في Defold، فالتزم بدقة بهذه الإرشادات البرمجية:
- حافظ على الهندسة (Geometry) محسنة للغاية: تم تصميم Defold للسرعة. للحفاظ على ميزته كخادم خفيف، اجعل مستوى الـ geometry أقل من 100,000 polygon في المشهد الواحد، خاصة إذا كنت تستهدف HTML5/WebGL. استخدم baked normal maps بدلاً من meshes عالية الكثافة لمحاكاة التفاصيل.
- استخدم Render Predicates لـ Frustum Culling: لا يقوم Defold تلقائياً باستبعاد الكائنات الموجودة خارج نطاق رؤية الكاميرا في مساحة 3D. يجب عليك كتابة custom frustum culling logic بلغة Lua، لتعطيل مكونات الـ model للكائنات الخارجة عن الحدود لتوفير وقت الـ rasterization.
- دمج Draw Calls عبر الـ Atlasing: تتطلب كل مادة (material) و texture فريد draw call منفصل يتم إرساله إلى الـ GPU. ادمج الـ textures الخاصة بك في texture atlases كبيرة. إذا تشارك 10 نماذج 3D مختلفة في نفس الـ material والـ atlas، فيمكن لـ Defold معالجتها كدفعة واحدة (batch) بكفاءة أكبر.
- الحساب المسبق للرياضيات المعقدة: ضرب المصفوفات وتحويلات الـ quaternion هي عمليات مكلفة للغاية في Lua. قم بتخزين (cache) متجهات الـ forward و right الخاصة بك وقم بإعادة حسابها فقط عندما يتغير دوران اللاعب فعلياً، بدلاً من القيام بالعمليات الرياضية الثقيلة في كل frame دون داعٍ.
- فصل المنطق (Logic) عن تردد الـ Render: قد يعمل منطق اللعبة (
update) بسرعة 60 FPS، لكن خطوات الفيزياء أو الـ networking المخصصة قد تعمل بسرعة 30 FPS. قم بعمل Interpolate لمواقع الـ 3D المرئية بناءً على السرعة بدلاً من جعلها تقفز مباشرة إلى أحدث حالة لضمان Rendering سلس على شاشات ذات معدلات تحديث مختلفة. - إدارة Lua Garbage Collection: في بيئة 3D ديناميكية، أنت تقوم بإنشاء وتدمير كائنات vector ومصفوفات بشكل متكرر. يمكن لـ Garbage collector في Lua أن يسبب قفزات ملحوظة في الـ frame (frame spikes) إذا تُرك دون إدارة. أعد استخدام
vmath.vector3وvmath.matrix4كلما أمكن عن طريق تحديث قيمها الداخلية مباشرة، بدلاً من إنشاء متغيرات محلية جديدة داخل loop الـupdate. قم بتخصيص memory pools للرصاص والكائنات. - خبز الإضاءة (Bake Lighting) خارجياً: لأن الإضاءة الديناميكية في custom GLSL shaders ستستهلك بسرعة ميزانية الأداء على أجهزة الموبايل، قم بخبز global illumination و ambient occlusion مباشرة في الـ textures باستخدام Blender أو Maya قبل تصدير نماذج glTF. إن shader بسيط من نوع unlit مع إضاءة مخبوزة بشكل جميل سيتفوق دائماً في الأداء على shader ديناميكي معقد على متصفحات الويب بالموبايل.
الخاتمة
تطور Defold إلى محرك ألعاب 3D قوي هو فوز كبير للمطورين المستقلين. إنه يحتفظ بنجاح بأوقات الـ compile البرقية وحجم الـ binary الصغير جداً مع تقديم الأسس الرياضية والأدوات اللازمة لبناء عوالم 3D واسعة وجذابة. من خلال احتراف custom render scripts، وفهم عمليات المصفوفات، وتسلسل بيانات الشبكة بكفاءة، يمكنك بناء ألعاب cross-platform تنافس تقنياً المحركات الأكبر والأثقل.
عندما تكون مستعداً لنقل الـ client 3D المحسن الخاص بك إلى الإنترنت وتوسيع الـ multiplayer backend دون صداع إدارة البنية التحتية الخام، جرب horizOn مجاناً أو راجع API docs لترى مدى سرعة دمج الخدمات في الوقت الفعلي في مشروع Defold القادم.