
أريد مشاركة الآليات التي نستخدمها على الخادم من أجل التصحيح المرئي لمنطق اللعبة وطرق تغيير حالات المطابقة في الوقت الفعلي.
في المقالات السابقة ، أخبروا بالتفصيل (القائمة الموجودة أسفل القطع مباشرة) حول كيفية ترتيب ECS في مشروعنا الجديد قيد التطوير وكيفية اختيار الحلول الجاهزة. أحد هذه الحلول كان
Entitas . لم يناسبنا هذا في المقام الأول بسبب عدم تخزين تاريخ الولايات ، لكني أحببته كثيرًا لأنه في Unity يمكنك رؤية جميع الإحصائيات المتعلقة باستخدام الكيانات والمكونات ونظام التجمع وأداء كل نظام بشكل مرئي وواضح.
هذا ألهمنا لإنشاء أدواتنا الخاصة على خادم اللعبة لمشاهدة ما يحدث في المباراة مع اللاعبين ، وكيف يلعبون ، وكيف سيكون أداء النظام ككل. على العميل ، لدينا أيضًا تطورات مماثلة للتصحيح البصري للعبة ، ولكن الأدوات الموجودة في العميل أبسط قليلاً مقارنة بما قمنا به على الخادم.

القائمة الموعودة لجميع المقالات المنشورة حول المشروع:
- " كيف تأرجحنا على مطلق النار سريع الخطى: التكنولوجيا والنهج ."
- " كيف ولماذا كتبنا ECS ."
- " عندما كتبنا رمز شبكة مطلق النار PvP للجوّال: مزامنة اللاعب على العميل ".
- " التفاعل بين العميل والخادم في مطلق النار PvP المحمول الجديد وجهاز خادم اللعبة: المشاكل والحلول ."
الآن إلى أسفل هذه المقالة. للبدء ، كتبنا خادم ويب صغيرًا كشف بعض API. يفتح الخادم نفسه ببساطة منفذ مأخذ التوصيل ويستمع لطلبات http على هذا المنفذ.
المعالجة طريقة قياسية إلى حد ماprivate bool HandleHttp(Socket socket) { var buf = new byte[8192]; var bufLen = 0; var recvBuf = new byte[8192]; var bodyStart = -1; while(bodyStart == -1) { var recvLen = socket.Receive(recvBuf); if(recvLen == 0) { return true; } Buffer.BlockCopy(recvBuf, 0, buf, bufLen, recvLen); bufLen += recvLen; bodyStart = FindBodyStart(buf, bufLen); } var headers = Encoding.UTF8.GetString(buf, 0, bodyStart - 2).Replace("\r", "").Split('\n'); var main = headers[0].Split(' '); var reqMethod = ParseRequestMethod(main[0]); if (reqMethod == RequestMethod.Invalid) { SendResponse(400, socket); return true; }
ثم نقوم بتسجيل عدة معالجات خاصة لكل طلب.
واحد منهم هو معالج مشاهدة جميع المباريات في اللعبة. لدينا فئة خاصة تتحكم في عمر جميع المباريات.
للتطوير السريع ، أضافوا ببساطة طريقة إليها ، وأصدروا قائمة بالمطابقات على منافذهم بتنسيق json public string ListMatches(string method, string path) { var sb = new StringBuilder(); sb.Append("[\n"); foreach (var match in _matches.Values) { sb.Append("{id:\"" + match.GameId + "\"" + ", www:" + match.Tool.Port + "},\n" ); } sb.Append("]"); return sb.ToString(); }

النقر على رابط المباراة ، انتقل إلى قائمة التحكم. هنا يصبح الأمر أكثر إثارة للاهتمام.
كل تطابق على Debug-assembly الخاص بالخادم يعطي بيانات كاملة عن نفسه. بما في ذلك GameState الذي
كتبنا عنه . دعني أذكرك بأن هذه هي في الأساس حالة المباراة بأكملها ، بما في ذلك البيانات الثابتة والديناميكية. بعد الحصول على هذه البيانات ، يمكننا عرض معلومات مختلفة حول المباراة في html. يمكننا أيضًا تغيير هذه البيانات مباشرة ، ولكن المزيد عن ذلك لاحقًا.
يؤدي الرابط الأول إلى سجل المطابقة القياسي:

نعرض فيه البيانات المفيدة الرئيسية حول الاتصالات ، وكمية البيانات المنقولة ، ودورات حياة الشخصيات الرئيسية والسجلات الأخرى.
يؤدي الرابط الثاني من GameViewer إلى تمثيل مرئي حقيقي للمباراة:

يقوم المولد الذي ينشئ رمز ECS لنا لحزم البيانات أيضًا بإنشاء رمز إضافي لتمثيل البيانات في json. يتيح لك هذا بسهولة قراءة بنية المطابقة من json وعرضها باستخدام مكتبة three.js في WebGL للعرض.
يبدو هيكل البيانات شيء من هذا القبيل { enums: { "HostilityLayer": { 1: "PlayerTeam1", 2: "PlayerTeam2", 3: "NeutralShootable", } }, components: { Transform: { name: 'Transform', fields: { Angle: {type: "float"}, Position: {type: "Vector2"}, }, }, TransformExact: { name: 'TransformExact', fields: { Angle: {type: "float"}, Position: {type: "Vector2"}, } } }, tables: { Transform: { name: 'Transform', component: 'Transform', }, TransformExact: { name: 'TransformExact', component: 'TransformExact', hint: "Copy of Transform for these entities that need full precision when sent over network", } } }
ودورة التقديم للأجسام الديناميكية (في حالتنا ، اللاعبين) هي هكذا var rulebook = {}; var worldstate = {}; var physics = {}; var update_dynamic_physics; var camera, scene, renderer; var controls; function init3D () { camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000); camera.up.set(0,0,1); scene = new THREE.Scene(); scene.add( new THREE.AmbientLight( 0x404040 ) ); var light = new THREE.DirectionalLight( 0xFFFFFF, 1 ); light.position.set(-11, -23, 45); scene.add( light ); renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement ); controls = new THREE.OrbitControls( camera, renderer.domElement ); var cam = localStorage.getObject('gv_camera'); if (cam) { camera.matrix.fromArray(cam.matrix); camera.matrix.decompose(camera.position, camera.quaternion, camera.scale); controls.target.set(cam.target.x, cam.target.y, cam.target.z); } else { camera.position.x = 40; camera.position.y = 40; camera.position.z = 50; controls.target.set(0, 0, 0); } window.addEventListener( 'resize', onWindowResize, false ); } init3D(); function handle_recv_dynamic (r) { eval('physics = ' + r + ';'); update_dynamic_physics(); sleep(10) .then(() => ajax("GET", "/physics/dynamic/")) .then(handle_recv_dynamic); } (function init_dynamic_physics () { var colour = 0x4B5440; var material = new THREE.MeshLambertMaterial({color: colour, flatShading: true}); var meshes = {}; update_dynamic_physics = function () { var i, p, mesh; var to_del = {}; for (i in meshes) to_del[i] = true; for (i in physics) { p = physics[i]; mesh = meshes[p.id]; if (!mesh) { mesh = create_shapes(worldstate, 'Dynamic', p, material, layers.dynamic_collider); meshes[p.id] = mesh; } mesh.position.x = p.pos[0]; mesh.position.y = p.pos[1]; delete to_del[p.id]; } for (i in to_del) { mesh = meshes[i]; scene.remove(mesh); delete meshes[i]; } } })();
يحتوي كل كيان تقريبًا لديه منطق الحركة في عالمنا المادي على مكون تحويل. لمشاهدة قائمة بجميع المكونات ، انقر على الرابط WorldState Table Editor.

في القائمة المنسدلة أعلاه ، يمكنك تحديد أنواع مختلفة من المكونات والاطلاع على حالتها الحالية. لذا ، يوضح الشكل أعلاه جميع التحولات في اللعبة. الشيء الأكثر إثارة للاهتمام: إذا قمت بتغيير قيم التحويل في هذا المحرر ، فإن اللاعب أو كيان اللعبة الآخر سينتقل بحدة إلى النقطة المطلوبة (في بعض الأحيان نستمتع في اختبارات اللعب).
يحدث تغيير البيانات في مطابقة حقيقية عند تحرير جدول html لأننا نسحب رابطًا خاصًا إلى هذا الجدول ، والذي يحتوي على اسم الجدول والحقل والبيانات الجديدة:
function handle_edit (id, table_name, field_name, value) { var data = table_name + "\n" + field_name + "\n" + id + "\n" + value; ajax("POST", tableset_name + "/edit/", data); }
يشترك خادم اللعبة في عنوان URL المطلوب ، الفريد في الجدول ، بفضل الرمز الذي تم إنشاؤه:
public static void RegisterEditorHandlers(Action<string, Func<string, string, string>> addHandler, string path, Func<TableSet> ts) { addHandler(path + "/data/", (p, b) => EditorPackJson(ts())); addHandler(path + "/edit/", (p, b) => EditorUpdate(ts(), b)); addHandler(path + "/ins/", (p, b) => EditorInsert(ts(), b)); addHandler(path + "/del/", (p, b) => EditorDelete(ts(), b)); addHandler(path + "/create/", (p, b) => EditorCreateEntity(ts(), b)); }
ميزة أخرى مفيدة للمحرر المرئي تسمح لنا بمراقبة امتلاء المخزن المؤقت لإدخال اللاعبين. على النحو التالي من المقالات السابقة للدورة ، يعد ذلك ضروريًا للحفاظ على لعبة مريحة للعملاء ، حتى لا ترتعش اللعبة ، ولا يتأخر العالم.

يمثل المحور الرأسي للرسم البياني عدد إدخالات المشغل المتوفرة حاليًا في المخزن المؤقت للإدخال على الخادم. من الناحية المثالية ، يجب ألا يكون هذا الرقم أكثر أو أقل من 1. ولكن نظرًا لعدم استقرار الشبكة والتكيف المستمر للعملاء وفقًا لهذه المعايير ، يتأرجح الرسم البياني عادةً في المنطقة المجاورة مباشرة لهذه العلامة. إذا انخفض جدول دخول اللاعب بشكل حاد ، فإننا نتفهم أن العميل على الأرجح فقد الاتصال. إذا انخفض إلى قيمة كبيرة إلى حد ما ، ثم استعاد فجأة ، فإن هذا يعني أن العميل يواجه مشاكل خطيرة مع استقرار الاتصال.
*****
تتيح لك الميزات الموضحة لمحرر المباريات لدينا على خادم اللعبة تصحيح لحظات الشبكة والألعاب بشكل فعال ومراقبة المباريات في الوقت الفعلي. ولكن عند حث المنتج ، يتم تعطيل هذه الوظائف ، لأنها تخلق حمولة كبيرة على الخادم ومجمع القمامة. تجدر الإشارة إلى أن النظام تمت كتابته في وقت قصير جدًا ، بفضل مولد رمز ECS الموجود بالفعل. نعم ، لم تتم كتابة الخادم وفقًا لجميع قواعد معايير الويب الحديثة ، ولكنه يساعدنا كثيرًا في العمل اليومي وتصحيح أخطاء النظام. يتطور أيضًا تدريجيًا ، ويكتسب فرصًا جديدة. عندما يكون هناك ما يكفي منهم ، سنعود إلى هذا الموضوع.