البحر ، القراصنة - لعبة على الإنترنت ثلاثية الأبعاد في المتصفح

تحية لمستخدمي هبر والقراء العاديين. هذه هي قصة تطوير لعبة متعددة اللاعبين عبر الإنترنت تستند إلى متصفح مع رسومات ثلاثية الأبعاد منخفضة وفيزياء ثنائية الأبعاد بسيطة.



هناك الكثير من الألعاب المصغرة ثنائية الأبعاد القائمة على المتصفح ، ولكن هذا المشروع جديد بالنسبة لي. في gamedev ، يمكن أن يكون حل المشكلات التي لم تواجهها بعد مثيرًا ومثيرًا للاهتمام. الشيء الرئيسي هو عدم التعثر مع أجزاء الطحن وبدء لعبة عمل بينما هناك رغبة وتحفيز ، لذلك دعونا لا نضيع الوقت ونبدأ في التطور!





اللعبة باختصار



Survival Fight هو وضع اللعبة الوحيد في الوقت الحالي. المعارك من 2 إلى 6 سفن بدون ولادة جديدة ، حيث يعتبر الفائز الأخير على قيد الحياة ويحصل على × 3 نقاط وذهبية.



ضوابط الممرات : الأزرار W ، A ، D أو الأسهم للتحرك ، شريط الفضاء لاطلاق النار على سفن العدو. لا تحتاج إلى التصويب ، لا يمكنك أن تفوت ، يعتمد الضرر على العشوائية وزاوية اللقطة. ويصاحب الضرر الأكبر ميدالية "الحق على الهدف".



نربح الذهب من خلال احتلالنا المراكز الأولى في تقييمات اللاعبين في 24 ساعة وفي 7 أيام (تتم إعادة التعيين في الساعة 00:00 بتوقيت موسكو) ومن خلال إكمال المهام اليومية (يتم إصدار واحدة من ثلاثة في اليوم ، بدوره). هناك أيضًا ذهب للمعارك ، ولكن أقل.



إنفاق الذهبوضع أشرعة سوداء على سفينتك لمدة 24 ساعة. الخطط لإضافة القدرة على إيقاظ Kraken ، الذي سيحمل الجزء السفلي من أي سفينة معادية لها مخالبها العملاقة :)



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



مكدس التكنولوجيا



تعد Three.js واحدة من المكتبات الأكثر شعبية للعمل مع 3D في المستعرض مع وثائق جيدة والعديد من الأمثلة المختلفة. أيضا ، لقد استخدمت Three.js من قبل - الاختيار واضح.



يرجع نقص محرك اللعبة إلى نقص الخبرة ذات الصلة والرغبة في تعلم شيء بدونه يعمل كل شيء بشكل جيد على أي حال :)



Node.js لأنه بسيط وسريع ومريح ، على الرغم من أنني لم يكن لدي أي خبرة في Node.js مباشرة. لقد اعتبرت Java بديلاً ، وأجريت بعض التجارب المحلية ، بما في ذلك مع مآخذ الويب ، لكنني لم أجرؤ على معرفة ما إذا كان من الصعب تشغيل Java على VPS. خيار آخر - الذهاب ، بناءه يجعلني محبطًا - لم يتقدم في دراسته ذرة واحدة.



بالنسبة لمقابس الويب ، استخدم الوحدة النمطية ws في Node.js.



PHP و MySQLخيار أقل وضوحًا ، لكن المعيار لا يزال هو نفسه - بسرعة وسهولة ، نظرًا لوجود خبرة في هذه التقنيات.



اتضح مثل هذا:







PHP مطلوب في المقام الأول لتقديم صفحات الويب للعميل وطلبات AJAX النادرة ، ولكن في الغالب لا يزال العميل يتواصل مع خادم الألعاب على Node.js عبر مآخذ الويب.



لم أكن أرغب في ربط خادم اللعبة بقاعدة البيانات على الإطلاق ، لذلك كل شيء يمر عبر PHP. في رأيي ، هناك إيجابيات هنا ، على الرغم من أنني لست متأكدًا مما إذا كانت ذات أهمية. على سبيل المثال ، نظرًا لأن البيانات الجاهزة في النموذج المطلوب تأتي إلى Node.js ، لا تضيع Node.js معالجة الوقت والاستفسارات الإضافية في قاعدة البيانات ، ولكنها تتعامل مع أشياء أكثر أهمية - فهي "تهضم" إجراءات اللاعبين وتغير حالة عالم اللعبة في الغرف.



النموذج الأول



بدأ التطوير بشيء بسيط وأهم - نموذج معين من عالم اللعبة ، يصف المعارك البحرية من وجهة نظر الخادم. قماش عادي 2D مثالي للعرض التخطيطي للنموذج على الشاشة.







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



يتم تقليل اللقطات والضربات إلى عمليات بسيطة مع ناقلات اتجاه السفينة واتجاه اللقطة. لا توجد قذائف هنا. إذا كان المنتج النقطي للمتجهات المعيارية ملائمًا للقيم المقبولة مع مراعاة المسافة إلى الهدف ، فسيكون هناك لقطة وضرب إذا ضغط اللاعب على الزر.



جافا سكريبت من جانب العميل لتقديم نموذج عالم اللعبة ، ومعالجة حركة السفن والطلقات ، قمت بنقلها إلى خادم Node.js دون تغيير تقريبًا.



خادم اللعبة



يتكون خادم Node.js WebSocket من 3 نصوص برمجية فقط:



  • main.js - البرنامج النصي الرئيسي الذي يتلقى رسائل WS من اللاعبين ، ويخلق غرفًا ويجعل تروس هذا الجهاز تدور
  • room.js - نص برمجي مسؤول عن اللعب داخل الغرفة: تحديث عالم اللعبة ، وإرسال التحديثات للاعبين في الغرفة
  • funcs.js - يتضمن فصلًا للعمل مع المتجهات ، واثنين من الوظائف المساعدة وفئة تنفذ قائمة مرتبطة بشكل مزدوج


مع تقدم التطوير ، تمت إضافة فئات جديدة - كلها تقريبًا مرتبطة مباشرة بلعبة اللعب وانتهت في ملف room.js. في بعض الأحيان يكون من الملائم العمل مع الفصول بشكل منفصل (في ملفات منفصلة) ، ولكن الخيار الكل في واحد ليس سيئًا أيضًا ، طالما أنه لا يوجد الكثير من الفئات (من المناسب التمرير لأعلى وتذكر المعلمات التي تقبلها طريقة فئة أخرى).



القائمة الحالية لفئات خادم اللعبة:



  • WaitRoom - الغرفة التي ينتظر فيها اللاعبون بداية المعركة ، ولديها طريقة التجزئة الخاصة بها التي ترسل تحديثاتها وتبدأ في إنشاء غرفة اللعبة عندما يكون أكثر من نصف اللاعبين جاهزين للمعركة
  • Room — , : /, ,
  • Player — «» :
  • Ship — : , , ,
  • PhysicsEngine — ,
  • PhysicsBody


Room
let upd = {p: [], t: this.gamet};
let t = Date.now();
let dt = t - this.lt;
let nalive = 0;

for (let i in this.players) {
	this.players[i].tick(t, dt);
}

this.physics.run(dt);

for (let i in this.players) {
	upd.p.push(this.players[i].getUpd());
}

this.chronology.addLast(clone(upd));
if (this.chronology.n > 30) this.chronology.remFirst();

let updjson = JSON.stringify(upd);

for (let i in this.players) {
	let pl = this.players[i];
	if (pl.ship.health > 0) nalive++;
	if (pl.deadLeave) continue;
	pl.cl.ws.send(updjson);
}

this.lt = t;
this.gamet += dt;

if (nalive <= 1) return false;
return true;




بالإضافة إلى الفصول الدراسية ، هناك وظائف مثل الحصول على بيانات المستخدم وتحديث مهمة يومية والحصول على مكافأة وشراء مظهر. ترسل هذه الوظائف بشكل أساسي طلبات https إلى PHP ، والتي تقوم بتنفيذ استعلام MySQL واحد أو أكثر وترجع النتيجة.



تأخيرات الشبكة



تعويض وقت الاستجابة للشبكة هو جزء مهم من تطوير الألعاب عبر الإنترنت. حول هذا الموضوع ، لقد قمت بإعادة قراءة سلسلة من المقالات مرارا هنا حول حبري . في حالة معركة السفن الشراعية ، يمكن أن يكون تعويض التأخير بسيطًا ، ولكن لا يزال عليك تقديم تنازلات.



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



عند حل مشكلة التأخير ، يعتمد الكثير على اللعبة ووتيرتها. أنا أضحي باستجابة سريعة لأفعال اللاعب لصالح الرسوم المتحركة السلسة والمراسلات الدقيقة للصورة لحالة عالم اللعبة في وقت معين. الاستثناء الوحيد هو أن مدفعًا يتم تشغيله على الفور بضغطة زر. يمكن أن يعزى الباقي لقوانين الكون وفائض رم من طاقم السفينة :)



نهاية المقدمة



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



سفينة ثلاثية الأبعاد



يدعم Three.js استخدام النماذج ثلاثية الأبعاد الجاهزة بتنسيقات مختلفة. اخترت تنسيق GLTF / GLB لنفسي ، حيث يمكن تضمين القوام والرسوم المتحركة ، أي لا يجب أن يتساءل المطور "هل تم تحميل جميع القوام؟"



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







تظليل لإله تظليل



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



الآلية أو الطريقة التي استخدمتها عند إنشاء نظام جزيئات لتحريك الضرر للسفينة أو سطح مائي ديناميكي أو قاع بحر ثابت هو نفسه: توفر ShaderMaterial الخاصة واجهة مبسطة لاستخدام تظليلها (رمز GLSL الخاص بها) ، يتيح لك BufferGeometry إنشاء هندسة من البيانات العشوائية ...



الفراغ الفارغ ، بنية التعليمات البرمجية التي كانت مناسبة بالنسبة لي للنسخ والإكمال والتعديل لإنشاء كائن ثلاثي الأبعاد الخاص بي بطريقة مماثلة:



إظهار الرمز
let vs = `
	attribute vec4 color;
	varying vec4 vColor;

	void main(){
		vColor = color;
		gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
		// gl_PointSize = 5.0; // for particles
	}
`;
let fs = `
	uniform float opacity;
	varying vec4 vColor;

	void main() {
		gl_FragColor = vec4(vColor.xyz, vColor.w * opacity);
	}
`;

let material = new THREE.ShaderMaterial( {
	uniforms: {
		opacity: {value: 0.5}
	},
	vertexShader: vs,
	fragmentShader: fs,
	transparent: true
});

let geometry = new THREE.BufferGeometry();

//let indices = [];
let vertices = [];
let colors = [];

/* ... */

//geometry.setIndex( indices );
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 4 ) );

let mesh = new THREE.Mesh(geometry, material);




تلف السفن



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







البحر



يتم تنفيذ البحر أيضًا باستخدام ShaderMaterial. يتحرك كل قمة في جميع الاتجاهات الثلاثة على طول الجيب ، مكونًا موجات عشوائية. تحدد السمات السعات لكل اتجاه للحركة ومرحلة الجيب.



لتنويع الألوان على الماء وجعل اللعبة أكثر إثارة وإرضاء للعين ، تقرر إضافة الجزء السفلي والجزر. يعتمد اللون السفلي على الارتفاع / العمق ويتألق عبر سطح الماء مما يخلق مناطق مظلمة وخفيفة.



تم إنشاء قاع البحر من خريطة ارتفاع تم إنشاؤها على مرحلتين: أولاً ، تم إنشاء الجزء السفلي بدون جزر في محرر رسومي (في حالتي ، تم تقديم الأدوات -> الغيوم وضبابية غوسية) ، ثم تمت إضافة الجزر بترتيب عشوائي باستخدام Canvas JS عبر الإنترنت على jsFiddle رسم دائرة وعدم وضوح. بعض الجزر منخفضة ، من خلالها يمكنك إطلاق النار على المعارضين ، والبعض الآخر له ارتفاع معين ، ولا تمر الطلقات من خلالها. بالإضافة إلى خريطة الارتفاع نفسها ، عند الإخراج ، أتلقى بيانات بتنسيق json حول الجزر (موقعها وحجمها) للفيزياء على الخادم.







ماذا بعد؟



هناك العديد من الخطط لتطوير اللعبة. أهمها وسائط لعبة جديدة. الأصغر منها - ابتكار الظلال / الانعكاسات على الماء ، مع مراعاة قيود الأداء لـ WebGL و JS. لقد ذكرت بالفعل فرصة إيقاظ Kraken :) لم يتم بعد تطبيق توحيد اللاعبين في الغرف بناءً على خبرتهم المتراكمة. من التحسينات ذات الأولوية الواضحة ، ولكن ليست عالية جدًا ، إنشاء العديد من خرائط قاع البحر والجزر واختيار واحدة منها بشكل عشوائي لمعركة جديدة.



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



هناك أيضًا أسئلة أجد صعوبة في الإجابة عليها الآن: كم عدد اللاعبين عبر الإنترنت الذين يمكنهم تحمل خادم افتراضي رخيص ، وما إذا كان من الممكن جمع عدد معين على الأقل من اللاعبين المهتمين وكيفية القيام بذلك.



بيضة عيد الفصح



من منا لا يحب تذكر ألعاب الكمبيوتر القديمة التي أعطت الكثير من المشاعر؟ أحب إعادة تشغيل لعبة Corsairs 2 (المعروفة أيضًا باسم Sea Dogs 2) مرارًا وتكرارًا حتى الآن. لا يسعني إلا أن أضيف سراً إلى لعبتي وأذكر بشكل صريح وغير مباشر بـ "كورسيرز 2". لن أفصح عن جميع البطاقات ، لكنني سأقدم تلميحًا: بيضة عيد الفصح هي شيء معين يمكنك العثور عليه أثناء استكشاف البحر (لا تحتاج إلى الإبحار بعيدًا عبر البحر اللانهائي ، الكائن في حدود المعقول ، ولكن لا يزال احتمال العثور عليه ليس مرتفعًا). تقوم بيضة عيد الفصح بإصلاح السفينة التالفة بالكامل.



ماذا حدث



فيديو دقيقة (اختبار من جهازين):





رابط للعبة: https://sailfire.pw



هناك أيضًا نموذج اتصال ، يتم إرسال الرسائل إلي في البرقيات: https://sailfire.pw/feedback/

روابط للراغبين في مواكبة الأخبار والتحديثات: VK Public ، قناة Telegram



All Articles