1 / 51

multi player game

u0643u064au0641u064au0629 u0635u0646u0627u0639u0629 u0644u0639u0628u0629 u0645u062au0639u062fu062fu0629 u000bu0627u0644u0644u0627u0639u0628u064au0646u00a0u0639u0628u0631 u0627u0644u0625u0646u062au0631u0646u062a u0628u0648u0627u0633u0637u0629:u200b

Ahmed457
Download Presentation

multi player game

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Phaser, Socket.io ,Node.js​ كيفية صناعة لعبة متعددة اللاعبين عبر الإنترنت بواسطة:​

  2. Lesson Teacher: Ahmed Joha

  3. Done By: Nader Rashed Saleh

  4. مجريات المشروع​ سنرى كيفية برمجة العميل والخادم للعبة أساسية متعددة اللاعبين عبر الإنترنت في الوقت الفعلي ، وكيفية جعلهم يتفاعلون باستخدام Socket.io.​   في اللعبة، يقوم كل لاعب بتحريك شخصيته من خلال النقر على الخريطة ،​   بينما تتحرك شخصيات اللاعبين الآخرين في الوقت الفعلي على الشاشة.​ ...  لن تكون ممتعة للغاية ،​ سيمكن المصمم من بناء لعبته المستقبلية.​

  5.   العميل مكتوب بلغة Javascript مع Phaser ويعمل في متصفح اللاعبين.​ ...​ الخادم مكتوب بلغة Javascript أيضًا ، باستخدام Node.js ​ ووحدة Express. يتواصل العميل والخادم باستخدام Socket.io.​ ... الأصول المستخدمة لهذا العرض التوضيحي موجودة في نفس الملف.​

  6. الخادمserver.js​ var express = require('express'); var app = express(); var server = require('http').Server(app); var io = require('socket.io').listen(server); ​ Express هي الوحدة التي سنستخدمها لخدمة الملفات للعملاء.​ نقوم بإنشاء مثيل جديد يسمى التطبيق ،​ ومن خلال دمجها في وحدة http ،​ نحن نضمن أن يعمل تطبيقنا السريع كخادم http.​ أخيرًا ، نطلب الوحدة socket.io ونجعلها تستمع إلى الاتصالات بهذا الخادم.​ هذا هو رمز معياري إلى حد ما لبدء استخدام التطبيقات البسيطة.​

  7. الخادمserver.js​ app.use('/css',express.static(__dirname + '/css')); app.use('/js',express.static(__dirname + '/js')); app.use('/assets',express.static(__dirname + '/assets')); app.get('/',function(req,res){ res.sendFile(__dirname+'/index.html'); }); ​ هذه السطور ضرورية لتكون قادرًا على تقديم ملفات ثابتة مثل أوراق أنماط CSS أو أصول اللعبة التي لا يمكن الوصول إليها مباشرة ولكن يجب أن يتم الوصول إليها من خلال لعبتك. للراحة ، تتيح لك الوسيطة الثانية ()app.use  تحديد مسارات افتراضية ، وهي ليست المسارات الحقيقية لمواردك ، ولكنها ستكون المسارات التي تستخدمها البرامج النصية للوصول إليها.​

  8. الخادمserver.js​ app.get('/',function(req,res){ res.sendFile(__dirname+'/index.html'); }); server.listen(8081,function(){ // Listens to port 8081 console.log('Listening on '+server.address().port); }); ​ ننتهي من إعداد الخادم من خلال الإشارة إلى المنفذ الذي يجب أن يستمع إليه الخادم وتحديد الملف الذي سيتم استخدامه كصفحة جذر.​

  9. العميلindex.html​ <script src="/socket.io/socket.io.js"></script>​ ​ Index.html هي صفحة المدخل التي سيتم عرضها عند الاتصال بالخادم وكذلك في هذه الحالة الصفحة التي سيتم عرض اللعبة عليها.​ ولكن يجب أن تحتوي على الأقل على عنصر div مع المعرف "game".  يجب أن تتضمن أيضًا ملفات Javascript اللازمة للعبة ، بما في ذلك socket.io.

  10. في هذه المرحلة ، إذا تم تشغيل الخادم (عن طريق كتابة `node server.js` في terminal) وانتقل إلى تطبيقك http://localhost:8081/ ، يجب أن تشاهد ملف index.html الخاص معروضًا مع أي محتوى تضعه فيه ، هذه النقطة (ربما "Hello World" ، للتأكد من أنه يعمل).

  11. العميلjs/game.js​ var game = new Phaser.Game(16*32, 600, Phaser.AUTO, document.getElementById('game')); game.state.add('Game',Game); game.state.start('Game'); var Game = {}; ​ يمكننا الآن المضي قدمًا من خلال إعداد game canvas (بافتراض أن لدينا كتلة div (id ‘game‘) والإعلان عن حالة لعبة واحدة ، تسمى "Game" ، تتوافق مع كائن Javascriptيحمل نفس الاسم.​

  12. العميلjs/game.js​ Game.init = function(){ game.stage.disableVisibilityChange = true; }; ​ في Game.init () ، يوجد متغير واحد فقط لتعيينه:​ هذا ليس إلزاميًا ، ولكنه مفيد ، لأنه سيجعل اللعبة تستمر في الرد على الرسائل الواردة من الخادم حتى عندما لا يكون التركيز على نافذة اللعبة (وهو سلوك مرغوب في معظم الألعاب).​

  13. العميلjs/game.js​ Game.preload = function() { game.load.tilemap('map', 'assets/map/example_map.json', null, Phaser.Tilemap.TILED_JSON); game.load.spritesheet('tileset', 'assets/map/tilesheet.png',32,32); game.load.image('sprite','assets/sprites/sprite.png'); // this will be the sprite of the players };​ في Game.preload () ، نقوم بتحميل الأصول التي سنحتاجها ، بما في ذلك Tilemapبتنسيق JSON،

  14. العميلjs/game.js​ Game.create = function(){ var map = game.add.tilemap('map'); map.addTilesetImage('tilesheet', 'tileset’); // tilesheet is the key of the tileset in map's JSON file var layer; for(var i = 0; i < map.layers.length; i++) { layer = map.createLayer(i); } layer.inputEnabled = true; // Allows clicking on the map }; في Game.create () ، نبدأ بإنشاء وعرض خريطتنا.​

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

  16. العميلjs/client.js​ var Client = {}; Client.socket = io.connect() في index.html ، قم بتضمين ملف Javascriptجديد ، client.js ،  والذي سيحتوي على كائن عميل يعمل كواجهة بين الخادم واللعبة نفسها.​ ........الجزء المهم هنا هو السطر الثاني ، حيث نبدأ الاتصال بالخادم في كل مرة ينتقل فيها اللاعب إلى التطبيق، سيتم إنشاء اتصال بالخادم. سيؤدي ذلك إلى إنشاء مقبس. وهو نقاط نهاية في تدفق الاتصال بين الخادم والعميل. باستخدام socket.io يمكننا إرسال واستقبال الرسائل من خلاله، والذي يشكل الطريقة الأساسية التي سيتفاعل بها العميل والخادم. يتم تخزين مقبس العميل في Client.socket للاستخدام في المستقبل.​

  17. تفاعلات في الوقت الفعلي​ نحتاج إلى جعل الخادم على دراية بما يفعله اللاعبون ، وكذلك جعل العملاء يتفاعلون مع الرسائل القادمة من الخادم. عندما يقوم اللاعب بتنفيذ إجراء (اتصال أو فصل أو نقل) ،  سنستخدم Socket.io لإرسال رسالة إلى الخادم لإخطاره بهذا الإجراء. في المقابل ، سيستخدم الخادم نفس واجهة برمجة التطبيقات لإرسال رسائل إلى العملاء عندما يحتاجون إلى إبلاغهم بإجراءات لاعب آخر.

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

  19. js/game.js Game.create = function(){ Game.playerMap = {}; var map = game.add.tilemap('map'); map.addTilesetImage('tilesheet', 'tileset'); // tilesheet is the key of the tileset in map's JSON file var layer; for(var i = 0; i < map.layers.length; i++) { layer = map.createLayer(i); } layer.inputEnabled = true; // Allows clicking on the map Client.askNewPlayer();}; أولاً ، لنعدّل Game.create () في game.js بحيث يقوم العميل بإخطار الخادم بأنه يجب إنشاء لاعب جديد. تحقيقا لهذه الغاية ، نضيف Client.askNewPlayer () ؛ في نهاية Game.create (). في البداية ، نضيف أيضًا Game.playerMap = {}؛ : هذا الكائن الفارغ سيكون مفيدًا لاحقًا لتتبع اللاعبين.

  20. js/client.js الآن في client.js ، نحتاج إلى تحديد الطريقة Client.askNewPlayer (): Client.askNewPlayer = function(){ Client.socket.emit('newplayer'); }; ستستخدم هذه الطريقة كائن المقبس الخاص بنا ، وإرسال رسالة من خلاله إلى الخادم. ستحمل هذه الرسالة تسمية "newplayer" ، وهي تشرح نفسها بنفسها. يمكن إضافة وسيطة ثانية لتمرير بيانات إضافية ، ولكنها لن تكون ضرورية في هذه الحالة.

  21. server.js في server.js ، نحتاج إلى الرد على الرسائل الواردة من العميل. الكود التالي:

  22. server.js server.lastPlayderID = 0; // Keep track of the last id assigned to a new player io.on('connection',function(socket){ socket.on('newplayer',function(){ socket.player = { id: server.lastPlayderID++, x: randomInt(100,400), y: randomInt(100,400) }; socket.emit('allplayers',getAllPlayers()); socket.broadcast.emit('newplayer',socket.player); }); }); function getAllPlayers(){ var players = []; Object.keys(io.sockets.connected).forEach(function(socketID){ var player = io.sockets.connected[socketID].player; if(player) players.push(player); }); return players; } function randomInt (low, high) { return Math.floor(Math.random() * (high - low) + low); }

  23. server.js نطلب من Socket.io الاستماع إلى حدث "الاتصال callback" ، والذي يتم إطلاقه في كل مرة يتصل فيها العميل بالخادم (باستخدام io.connect () ) عندما يحدث هذا ، يجب أن يستدعي رد النداء المحدد كمتغير ثانٍ. يستقبل رد الاتصال هذا كوسيطة أولى للمقبس المستخدم لتأسيس الاتصال ، والذي ، تمامًا مثل مقبس العميل ، يمكن استخدامه لتمرير الرسائل. باستخدام طريقة socket.on () من كائنات مأخذ التوصيل ، من الممكن تحديد عمليات الاسترجاعات للتعامل مع الرسائل المختلفة. لذلك ، في كل مرة يرسل فيها عميل معين رسالة محددة عبر المقبس الخاص به ، سيتم استدعاء رد اتصال معين كرد فعل. في هذه الحالة ، نحدد رد نداء للرد على رسالة "newplayer"

  24. server.js تحليل الكود بالتفصيل وبشكل أعمق:

  25. server.js socket.on('newplayer',function(){ socket.player = { id: server.lastPlayderID++, x: randomInt(100,400), y: randomInt(100,400) }; socket.emit('allplayers',getAllPlayers()); socket.broadcast.emit('newplayer',socket.player); }); أولاً ، نقوم بإنشاء كائن مخصص جديد ، يستخدم لتمثيل لاعب ، وتخزينه في كائن المقبس. من الممكن إضافة خصائص "عشوائية خاصة بالعميل" إلى كائن المقبس ، مما يجعلها ملائمة للوصول إليها. في هذا الكائن ، نعطي اللاعب معرفًا فريدًا (سيتم استخدامه من جانب العميل) ، ونحدد بشكل عشوائي موضع الكائن. بعد ذلك ، نريد أن نرسل إلى اللاعب الجديد قائمة اللاعبين المتصلين بالفعل:

  26. server.js socket.emit('allplayers',getAllPlayers()); يرسل Socket.emit () رسالة إلى مأخذ توصيل واحد محدد. هنا ، ، ، نرسل إلى العميل المتصل حديثًا رسالة باسم "allplayers" ، وكوسيطة ثانية ، ناتج Client.getAllPlayers () والذي سيكون مصفوفة من اللاعبين المتصلين حاليًا. يسمح هذا للاعبين المتصلين حديثًا بالاطلاع على عدد ومواقع اللاعبين المتصلين بالفعل. لنلق نظرة سريعة على Client.getAllPlayers ():

  27. server.js function getAllPlayers(){ var players = []; Object.keys(io.sockets.connected).forEach(function(socketID){ var player = io.sockets.connected[socketID].player; if(player) players.push(player); }); return players; } io.sockets.connectedعبارة عن مجموعة Socket.io داخلية للمآخذ المتصلة حاليًا بالخادم. يمكننا استخدامه للتكرار عبر جميع المقابس ، والحصول على خاصية اللاعب التي أضفناها إليهم (إن وجدت) ، ودفعهم إلى قائمة ، مع إدراج اللاعبين المتصلين بشكل فعال.

  28. server.js socket.broadcast.emit('newplayer',socket.player); يرسل socket.emit.broadcast () رسالة إلى جميع المقابس المتصلة ، باستثناء المقبس الذي قام بتشغيل رد الاتصال. يسمح ببث الأحداث من العميل إلى جميع العملاء الآخرين دون تكرارها مرة أخرى إلى العميل البادئ. هنا ، نبث رسالة "اللاعب الجديد" ، ونرسل بيانات كائن اللاعب الجديد.

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

  30. حتى الآن ، يتفاعل خادمنا مع رسالة واحدة من العملاء. نحتاج الآن إلى تكييف العملاء حتى يتمكنوا من معالجة رسائل "allplayers" و"newplayer" من الخادم في المقابل ، وبذلك تكتمل الحلقة connect - notify the server I'm here- get info in return - display it لاحظ أن رسالة "newplayer" التي أرسلها العميل والرسالة المرسلة من الخادم ليستا متطابقتين ؛ أعطيت لهم نفس التسمية لأنهم ينقلون نفس النوع من المعلومات ، ولكن سيتم التعامل معهم بشكل منفصل لأن لديهم نقاط نهاية مختلفة (الخادم مذكور أولا ، والعميل للأخير).

  31. js/client.js Client.socket.on('newplayer',function(data){ Game.addNewPlayer(data.id,data.x,data.y); }); Client.socket.on('allplayers',function(data){ console.log(data); for(var i = 0; i < data.length; i++){ Game.addNewPlayer(data[i].id,data[i].x,data[i].y); } }); كما ترى ، يمكن استخدام نفس بناء الجملة للتعامل مع الرسائل من جانب العميل. عندما يتم إرسال البيانات على طول رسالة ، يمكن استرجاعها كأول وسيطة لرد الاتصال على الطرف المستلم. كائن "البيانات" الذي تم تغذيته إلى رد الاتصال "newplayer" يتوافق مع بيانات socket.playerالتي يرسلها الخادم. بالنسبة لرسالة "allplayers" ، فهي عبارة عن قائمة من كائنات socket.player. في كلتا الحالتين ، تتم معالجة هذه البيانات عن طريق استدعاء Game.addNewPlayer () ، والذي يمكننا الآن تحديده في game.js:

  32. js/game.js Game.addNewPlayer = function(id,x,y){ Game.playerMap[id] = game.add.sprite(x,y,'sprite'); }; تنشئ هذه الطريقة كائنًا جديدًا في الإحداثيات المحددة ، وتخزن كائن Sprite المقابل في مصفوفة ترابطية مُعلنة في Game.create () ، مع المعرف المقدم كمفتاح. يتيح ذلك الوصول بسهولة إلى الكائن المقابل لمشغل معين ، على سبيل المثال عندما نحتاج إلى نقله أو إزالته في هذه المرحلة ، إذا قمت بإعادة تشغيل الخادم (بحيث يتم أخذ التعديلات الأخيرة في الاعتبار) وانتقلت إلى لعبتك ، يجب أن ترى شخصية صغيرة معروضة ، تتوافق مع كائنك. إذا قمت بالاتصال بمتصفحات أخرى ، فيجب أن تظهر شخصيات إضافية على الشاشة.

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

  34. server.js io.on('connection',function(socket){ socket.on('newplayer',function(){ socket.player = { id: server.lastPlayderID++, x: randomInt(100,400), y: randomInt(100,400) }; socket.emit('allplayers',getAllPlayers()); socket.broadcast.emit('newplayer',socket.player); socket.on('disconnect',function(){ io.emit('remove',socket.player.id); }); }); });

  35. server.js كرد فعل لرسالة "قطع الاتصال" ، نستخدم io.emit () ، التي ترسل رسالة إلى جميع العملاء المتصلين. نرسل رسالة "إزالة" ، ونرسل معرف اللاعب غير المتصل للإزالة. يجب تسجيل رد الاتصال "disconnect" في رد الاتصال "newplayer" ؛ إذا لم يكن الأمر كذلك ، فسيتم استدعاء "disconnect" بطريقة ما قبل "newplayer" ، سيتعطل الخادم!

  36. js/client.js Client.socket.on('remove',function(id){ Game.removePlayer(id); }); js/game.js Game.removePlayer = function(id){ Game.playerMap[id].destroy(); delete Game.playerMap[id]; };

  37. يوضح هذا استخدام بنية بيانات Game.playerMap ، لا حاجة لتكرار الشخصيات ؛ يسمح لك المعرف بجلبه على الفور. الآن كل ما يتبقى هو معالجة وبث حركات اللاعب.

  38. التعامل مع حركات اللاعب

  39. js/game.js layer.events.onInputUp.add(Game.getCoordinates, this); حان الوقت لإكمال Game.create (). نريد أساسًا أنه عند النقر على الخريطة ، يتم إرسال الإحداثيات إلى الخادم ، بحيث يمكن تحديث موضع اللاعب الذي نقر للجميع. عند إضافة السطر السابق إلى Game.create (): الآن،ستتفاعل الخريطة مع النقرات عن طريق استدعاء Game.getCoordinates () ، والتي يمكننا تحديدها على النحو التالي:

  40. js/game.js Game.getCoordinates = function(layer,pointer){ Client.sendClick(pointer.worldX,pointer.worldY); }; رد نداء أحداث onInputUpفي Phaser يتلقى كوسيطة ثانية كائن المؤشر المقابل ، والذي يحتوي على خاصيتين worldXو worldY يمكننا استخدامها لمعرفة مكان حدوث النقرة على خريطة اللعبة. يمكننا بعد ذلك تمرير هذه الإحداثيات إلى Client.sendClick () في client.js:

  41. js/client.js Client.sendClick = function(x,y){ Client.socket.emit('click',{x:x,y:y}); }; الذي يرسل ببساطة الإحداثيات إلى الخادم ، مع تسمية "انقر". لا حاجة لإرسال أي هوية لاعب ، لأن المقبس خاص بالعميل ومرتبط بلاعب واحد فقط.

  42. server.js io.on('connection',function(socket){ socket.on('newplayer',function(){ socket.player = { id: server.lastPlayderID++, x: randomInt(100,400), y: randomInt(100,400) }; socket.emit('allplayers',getAllPlayers()); socket.broadcast.emit('newplayer',socket.player); socket.on('click',function(data){ console.log('click to '+data.x+', '+data.y); socket.player.x = data.x; socket.player.y = data.y; io.emit('move',socket.player); }); socket.on('disconnect',function(){ io.emit('remove',socket.player.id); }); }); });

  43. server.js يتم تحديث حقلي x و y لخاصية المشغل للمقبس بالإحداثيات الجديدة ، ثم البث فورًا للجميع حتى يتمكنوا من رؤية التغيير. الآن يتم إرسال كائن socket.playerالكامل ، لأن العملاء الآخرين بحاجة إلى معرفة هوية اللاعب الذي يتحرك ، لتحريك الكائن المناسب على الشاشة (على الرغم من أنه في هذه اللعبة المبسطة ، لا توجد طريقة فعلية لتمييز اللاعبين).

  44. js/client.js Client.socket.on('move',function(data){ Game.movePlayer(data.id,data.x,data.y); }); بالعودة إلى client.js ، نحتاج إلى معالجة رسالة "النقل" من الخادم ، بحيث يمكن للعملاء الرد على نقل لاعب آخر: يجب أن تبدأ العملية في التعرف عليك.

  45. js/game.js Game.movePlayer = function(id,x,y){ var player = Game.playerMap[id]; var distance = Phaser.Math.distance(player.x,player.y,x,y); var duration = distance*10; var tween = game.add.tween(player); tween.to({x:x,y:y}, duration); tween.start(); }; نستفيد مرة أخرى من بنية Game.playerMap لاسترداد الكائن المناسب ، ثم نقوم بترتيبها لجعل الحركة تقدمية.

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

More Related