Bagaimana kami men-debug ECS ​​yang ditulis sendiri di browser pada server game



Saya ingin berbagi mekanisme yang kami gunakan di server untuk debugging visual logika game dan cara-cara untuk mengubah status pertandingan secara real time.

Dalam artikel sebelumnya mereka mengatakan secara detail (daftar tepat di bawah cut) tentang bagaimana ECS diatur dalam proyek baru kami dalam pengembangan dan bagaimana memilih solusi yang sudah jadi. Salah satu solusi tersebut adalah Entitas . Ini tidak sesuai dengan kami di tempat pertama karena kurangnya menyimpan sejarah negara, tetapi saya sangat menyukainya karena di Unity Anda dapat secara visual dan grafis melihat semua statistik tentang penggunaan entitas, komponen, sistem pool, kinerja setiap sistem, dll.

Ini mengilhami kami untuk membuat alat kami sendiri di server permainan untuk menonton apa yang terjadi dalam pertandingan dengan para pemain, bagaimana mereka bermain, bagaimana kinerja sistem secara keseluruhan. Di klien, kami juga memiliki perkembangan yang sama untuk debugging visual game, tetapi alat di klien sedikit lebih sederhana dibandingkan dengan apa yang kami lakukan di server.



Daftar yang dijanjikan dari semua artikel yang dipublikasikan tentang proyek:

  1. " Bagaimana Kami Mengayun di Mobile Fast Paced Shooter: Teknologi dan Pendekatan ."
  2. " Bagaimana dan mengapa kami menulis ECS kami ."
  3. " Ketika kami menulis kode jaringan penembak PvP seluler: sinkronisasi pemain pada klien ."
  4. " Interaksi klien-server dalam perangkat PvP shooter dan server game baru: masalah dan solusi ."

Sekarang ke bagian bawah artikel ini. Untuk memulai, kami menulis server web kecil yang memperlihatkan beberapa API. Server itu sendiri hanya membuka port soket dan mendengarkan permintaan http pada port itu.

Pemrosesan adalah cara yang cukup standar
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; } // receive POST body var body = string.Empty; if(reqMethod == RequestMethod.Post) { body = ReceiveBody(buf, bufLen, headers, bodyStart, socket); if(body == null) { return true; } } var path = main[1]; if(path == "/") { path = "/index.html"; } // try to serve by a file if(File.Exists(_docRoot + path)) { var content = File.ReadAllBytes(_docRoot + path); if (reqMethod == RequestMethod.Head) { content = null; } SendResponse(200, socket, content, GuessMime(path)); return true; } // try to serve by a handle foreach(var handler in _handlers) { if(handler.Match(reqMethod, path)) { if (handler.Async) { _jobs.Enqueue(() => { RunHandler(socket, path, body, handler); socket.Shutdown(SocketShutdown.Both); socket.Close(); }); return false; } else { RunHandler(socket, path, body, handler); return true; } } } // nothing found :-( var msg = "File not found " + path + "\ndoc root " + _docRoot + "\ncurrent dir " + Directory.GetCurrentDirectory(); SendResponse(404, socket, Encoding.UTF8.GetBytes(msg)); return true; } 


Kemudian kami mendaftarkan beberapa penangan khusus untuk setiap permintaan.
Salah satunya adalah pawang melihat semua pertandingan dalam permainan. Kami memiliki kelas khusus yang mengontrol waktu hidup semua pertandingan.

Untuk pengembangan cepat, mereka cukup menambahkan metode untuk itu, mengeluarkan daftar kecocokan pada port mereka dalam format 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(); } 




Mengklik tautan yang cocok, buka menu kontrol. Ini menjadi jauh lebih menarik.

Setiap kecocokan pada Debug-assembly server memberikan data lengkap tentang dirinya. Termasuk GameState yang kami tulis . Biarkan saya mengingatkan Anda bahwa ini pada dasarnya adalah keadaan seluruh pertandingan, termasuk data statis dan dinamis. Dengan data ini, kami dapat menampilkan berbagai informasi tentang kecocokan dalam html. Kami juga dapat langsung mengubah data ini, tetapi lebih lanjut tentang itu nanti.

Tautan pertama mengarah ke log kecocokan standar:



Di dalamnya, kami menampilkan data bermanfaat utama tentang koneksi, jumlah data yang ditransfer, siklus hidup utama karakter dan log lainnya.

Tautan kedua GameViewer mengarah ke representasi visual nyata dari pertandingan:



Generator yang membuat kode ECS bagi kita untuk mengemas data juga membuat kode tambahan untuk mewakili data dalam json. Ini memungkinkan Anda untuk dengan mudah membaca struktur kecocokan dari json dan merendernya menggunakan pustaka three.js di WebGL untuk rendering.

Struktur data terlihat seperti ini
 { 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", } } } 


Dan siklus render tubuh dinamis (dalam kasus kami, pemain) adalah seperti ini
 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]; } } })(); 


Hampir setiap entitas yang memiliki logika gerakan di dunia fisik kita memiliki komponen Transform. Untuk melihat daftar semua komponen, klik tautan WorldState Table Editor.



Di menu tarik-turun di atas, Anda dapat memilih berbagai jenis komponen dan melihat statusnya saat ini. Jadi, gambar di atas menunjukkan semua transformasi dalam game. Hal yang paling menarik: jika Anda mengubah nilai-nilai transformasi dalam editor ini, pemain atau entitas game lainnya akan berteleportasi dengan tajam ke titik yang diinginkan (kadang-kadang kami bersenang-senang pada tes bermain).

Mengubah data dalam kecocokan nyata saat mengedit tabel html terjadi karena kami menarik tautan khusus ke tabel ini, yang berisi nama tabel, bidang, dan data baru:

 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); } 

Server game berlangganan URL yang diinginkan, unik untuk tabel, berkat kode yang dihasilkan:

 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)); } 

Fitur lain yang berguna dari editor visual memungkinkan kita untuk memantau kepenuhan input pemain. Sebagai berikut dari artikel sebelumnya dari siklus, ini diperlukan untuk menjaga game yang nyaman bagi pelanggan, agar game tidak bergerak, dan dunia tidak ketinggalan.



Sumbu vertikal grafik adalah jumlah entri pemain yang saat ini tersedia di buffer input di server. Idealnya, angka ini tidak boleh lebih atau kurang dari 1. Tetapi karena ketidakstabilan jaringan dan penyesuaian konstan klien untuk kriteria ini, grafik biasanya berosilasi di sekitar tanda ini. Jika jadwal masuk untuk pemain turun tajam, kami memahami bahwa klien kemungkinan besar kehilangan koneksi. Jika jatuh ke nilai yang agak besar, dan kemudian tiba-tiba pulih, ini berarti bahwa klien mengalami masalah serius dengan stabilitas koneksi.

*****

Fitur yang dijelaskan dari editor pertandingan kami di server permainan memungkinkan Anda untuk secara efektif men-debug momen jaringan dan game dan memantau pertandingan secara real time. Tetapi pada prod, fungsi-fungsi ini dinonaktifkan, karena mereka menciptakan beban yang signifikan pada server dan pengumpul sampah. Perlu dicatat bahwa sistem ini ditulis dalam waktu yang sangat singkat, berkat generator kode ECS yang sudah ada. Ya, server tidak ditulis sesuai dengan semua aturan standar web modern, tetapi ini sangat membantu kita dalam pekerjaan sehari-hari dan debugging sistem. Dia juga berevolusi secara bertahap, memperoleh peluang baru. Jika jumlahnya cukup, kami akan kembali ke topik ini.

Source: https://habr.com/ru/post/id424267/


All Articles