لقد تعرضت للقماش منذ أكثر من شهر فقط، وهذه هي المرة الأولى التي أقوم فيها بتنفيذ عملية اللعبة بشكل كامل، وكان الحصاد كبيرًا جدًا.
لقطات من لعبة إطلاق النار
انتقل إلى العرض التوضيحي أولاً: https://littleyljy.github.io/demo/shootgame/
قواعد اللعبةيتعين على اللاعب التحكم في الطائرة لإطلاق الرصاص وتدمير الوحوش المتحركة. إذا تم تدمير جميع الوحوش، تكون اللعبة ناجحة. إذا انتقلت الوحوش إلى الأسفل، فستفشل اللعبة.
اللعبة مقسمة إلى عدة مشاهد:
لتحقيق تبديل المشهد، تحتاج في الواقع إلى العرض أولاً: لا شيء لجميع المشاهد، ثم التحكم في حالة البيانات من خلال js للبدء، والتشغيل، والفشل، والنجاح، والنجاح الكامل، والتوقف لتنفيذ عرض المشهد المقابل: block.
HTML و CSS هي كما يلي:
<div id=game data-status=start> <div class=game-panel> <section class=game-intro game-ui> <h1 class=section-title>لعبة الرماية</h1> <p class=game- desc>هذه لعبة إطلاق نار مسببة للإدمان، استخدم ← و← لتشغيل طائرتك، واستخدم المساحة لإطلاق النار، واستخدم مفتاح الإدخال لإيقاف اللعبة مؤقتًا. دعونا ندمر وحوش الفضاء معًا! </p> <p class=game-level>المستوى الحالي: 1</p> <button class=js-play Button>بدء اللعبة</button> </section> <section class=game-failed game-ui> <h1 class=section-title>انتهت اللعبة</h1> <p class=game-info-text>النتيجة النهائية: <span class=score></span></p> <button class=js-replay Button> ابدأ من جديد</button> </section> <section class=game-success game-ui> <h1 class=section-title>نجاح اللعبة</h1> <p class=game-next-level game-info-text></p> <button class=js-next Button>مواصلة اللعبة</button> </section> <section class=game-all-success game-ui> <h1 class=section-title>تم النجاح</h1> <p class=game- المستوى التالي game-info-text>لقد دافعت بنجاح ضد جميع هجمات الوحوش. </p> <button class=js-replay Button>التشغيل مرة أخرى</button> </section> <section class=game-stop game-ui> <h1 class=section-title>إيقاف اللعبة مؤقتًا</h1> < Button class=js-stop Button>تستمر اللعبة</button> </section> </div> <div class=game-info game-ui> <span class=title>النتيجة:</span> <span class=score > </span> </div> <canvas id=canvas width=700 height=600> <!-- لوحة رسم الرسوم المتحركة --> </canvas> </div>
#game{ العرض: 700 بكسل; الارتفاع: 600 بكسل; الموضع: نسبي; اليسار: 50%; الهامش: 0 0 0 -350 بكسل; );.game-ui{ العرض: لا شيء؛ حجم الصندوق: 55 بكسل؛ ارتفاع مربع الحدود: 100%;[data-status=start] .game-intro { العرض: كتلة؛ الحشوة العلوية: 180 بكسل؛ الخلفية: url(./img/bg.png) بدون تكرار 430 بكسل 180 بكسل; حجم الخلفية: 200 بكسل;[data-status=playing] .game-info { العرض: كتلة الموضع: أعلى: 0; left:0; data-status=stop] .game-stop{ العرض: كتلة؛ url(./img/bg-end.png) بدون تكرار 380 بكسل 190 بكسل؛ حجم الخلفية: 250 بكسل؛}كائن المنحى
يمكن للعبة بأكملها التعامل مع الوحوش (العدو)، والطائرات (الطائرة)، والرصاص (الرصاص) ككائنات، بالإضافة إلى كائنات التكوين (CONFIG) وكائنات اللعبة (GAME) التي تتحكم في منطق اللعبة.
التكوين المتعلق باللعبة/** * التكوين المتعلق باللعبة* @type {Object} */var CONFIG = { الحالة: 'start'، // تبدأ اللعبة افتراضيًا كمستوى البداية: 1، // المستوى الافتراضي للعبة TotalLevel: 6، // إجمالي 6 Off numPerLine: 7، // العدد الافتراضي للعبة من الوحوش في كل سطر CanvasPadding: 30، // الفاصل الزمني الافتراضي للوحة الرسم BulletSize: 10، // طول الرصاصة الافتراضي BullSpeed: 10، // سرعة حركة الرصاصة الافتراضية desertSpeed: 2، // مسافة حركة العدو الافتراضية desertSize: 50، // حجم العدو الافتراضي EnemyGap: 10، // المسافة الافتراضية بين الأعداء EnemyIcon: './img/enemy.png'، // صورة العدو الوحشي BoomIcon: './img/boom.png'، // صورة عدو الموت للوحش: 'يمين'، // يتحرك العدو الافتراضي إلى اليمين عند بداية الطائرة Speed: 5, // المسافة الافتراضية التي تتحركها الطائرة في كل خطوة jetSize: { width: 60, height: 100 }, // الحجم الافتراضي للطائرة،planeIcon: '. /img/plane.png' };تحديد فئة الوالدين
نظرًا لأن الوحوش (العدو) والطائرات (الطائرة) والرصاص (الرصاص) جميعها لها نفس سمات x وy والحجم والسرعة وطريقة التحرك ()، يمكنك تحديد عنصر فئة أصل وتنفيذه عن طريق وراثة الفئة الأصلية من الفئة الفرعية .
/*الفئة الأصلية: تحتوي على xy speed move() draw()*/var Element = function (opts) { this.opts = opts {}; // تعيين الإحداثيات والحجم والسرعة this.x = opts.x; this.y = opts.y; this.size = opts.size; this.speed = opts.speed;};Element.prototype.move = function (x, y) { var addX = x || 0; addY = y ||.0; this.x += addX; this.y += addY;};// الوظيفة التي ترث النموذج الأولي function inheritPrototype(subType, superType) { var proto = Object.create(superType.prototype); proto .constructor = subType; subType.prototype = proto;}
تقوم طريقة النقل (x، y) بتكديس نفسها بناءً على القيمة (x، y) التي تم تمريرها.
تعريف الوحشتشتمل الوحوش على سمات فريدة: حالة الوحش، والصورة، وboomCount التي تتحكم في مدة حالة الانفجار، وطرق السحب ()، والأسفل ()، والاتجاه ()، والازدهار ().
/*Enemy*/var Enemy = function (opts) { this.opts = opts {}; // استدعاء سمة الفئة الأصلية Element.call(this, opts); ' عادي';// عادي مزدهر this.enemyIcon = opts.enemyIcon; this.enemyBoomIcon = opts.enemyBoomIcon = this.boomCount; 0;};// وراثة طريقة العنصر inheritPrototype(Enemy, Element);// الطريقة: ارسم العدو Enemy.prototype.draw = function () { if (this.enemyIcon && this.enemyBoomIcon) { Switch (this.status) ) { الحالة 'طبيعية': var EnemyIcon = new Image(); EnemyIcon.src = this.enemyIcon; this.x, this.y, this.size, this.size); this.y, this.size, this.size); this.size); return this; ;// الطريقة: التحرك لليسار أو لليمين Enemy.prototype.direction = function (direction) { if (direction === 'right') { this.move(this.speed, 0 } else {); this.move(-this.speed, 0); } return this;};// الطريقة: ينفجر العدو Enemy.prototype.booming = function () { this.status = 'booming'; (this.boomCount > 4) { this.status = 'boomed' } return this;}
يحتوي التعداد النقطي على أساليب fly() و draw().
/*Bullet*/var Bullet = function (opts) { this.opts = opts {}; Element.call(this, opts);};inheritPrototype(Bullet, Element);// الطريقة: دع الرصاصة تطير Bullet .prototype.fly = function () { this.move(0, -this.speed); return this;};// الطريقة: ارسم رصاصة Bullet.prototype.draw = function () { ctx.beginPath(); ctx.strokeStyle = '#fff'; ctx.moveTo(this.x, this.y); ctx.stroke(); إرجاع هذا;};
يحتوي كائن الطائرة على سمات فريدة: الحالة والعرض والارتفاع والصورة والحد الأقصى والحد الأدنى لقيم الإحداثي المحوري والطرق hasHit () وdraw () والاتجاه () وshoot () و drawBullets ().
/*Plane*/var Plane = function (opts) { this.opts = opts {}; Element.call(this, opts); // حالة السمة الفريدة وصورة this.status = 'normal'; = opts.width; this.height = opts.height; this.planeIcon = opts.planeIcon; // التعداد النقطي المرتبط this.bullets = []; this.bulletSpeed = opts.bulletSpeed || (المستوى، العنصر) ;// الطريقة: الرصاصة تصيب الهدف Plane.prototype.hasHit = function (enemy) { var Bullets = this.bullets for (var i = Bullets. length - 1; i >= 0; i--) { var Bullet = Bullets[i]; var isHitPosX = (enemy.x < Bullet.x) && (bullet.x < (enemy.x + Enemy.size)); var isHitPosY = (enemy.y < Bullet.y) && (bullet.y < (enemy.y + Enemy.size)); (isHitPosX && isHitPosY) { this.bullets.splice(i, 1); return true; };// الطريقة: ارسم المستوى Plane.prototype.draw = function () { this.drawBullets(); varplaneIcon = new Image();planeIcon.src = this.planeIcon; this.width, this.height); return this;};// الطريقة: اتجاه المستوى Plane.prototype.direction = function (direction) { var speed = this.speed; ) {planeSpeed = this.x < this.minX 0 : -speed } else {planeSpeed = this.x > this.maxX 0 : السرعة } console.log('planeSpeed:',planeSpeed); console.log('this.x:', this.x); console.log('this.minX:', this.minX); .maxX:', this.maxX); this.move(planeSpeed, 0); return this;// استدعاء سلسلة مناسب};// الطريقة: إطلاق الرصاص Plane.prototype.shoot = function () { var BulletPosX = this.x + this.width / 2; this.bullets.push(new Bullet({ x: BulletPosX, y: this.y, size: this.bulletSize, speed: this.bulletSpeed })); return this };// الطريقة: رسم الرصاص Plane.prototype.drawBullets = function () { var Bullets = this.bullets; var i = Bullets.length; (i--) { var Bullet = Bullets[i];
أحداث لوحة المفاتيح لها الحالات التالية:
لأن الطائرة تحتاج إلى الاستمرار في التحرك عند الضغط على الزر الأيسر (رمز المفتاح = 37) والزر الأيمن (رمز المفتاح = 39) (مفتاح لأسفل)، ولا يتحرك زر التشغيل عند تحريره. عند الضغط على المسافة (keyCode=32) أو مفتاح السهم لأعلى (keyCode=38) (مفتاح لأسفل)، يتم إطلاق الرموز النقطية، وعند تحريرها، يتوقف keyup عن إطلاق النار. اضغط أيضًا على مفتاح Enter (رمز المفتاح = 13) لإيقاف اللعبة مؤقتًا. لذلك، تحتاج إلى تحديد كائن KeyBoard لمراقبة ما إذا كان onkeydown وonkeyup يضغطان على المفتاح أو يحررانه.
نظرًا لأن المفتاحين الأيسر والأيمن متناقضان، لكي تكون في الجانب الآمن، تحتاج إلى ضبط المفتاح الأيمن على خطأ عند الضغط على المفتاح الأيسر. الشيء نفسه ينطبق على النقر بزر الماوس الأيمن.
// حدث لوحة المفاتيح var KeyBoard = function () { document.onkeydown = this.keydown.bind(this); document.onkeyup = this.keyup.bind(this);}; // كائن KeyBoard KeyBoard.prototype = { pressedLeft: false، pressedRight: false، pressedUp: false، HoldLeft: false، HoldRight: false، pressedSpace: false، pressedEnter: false، keydown: الوظيفة (e) { var key = e.keyCode; Switch (key) { case 32://Space - إطلاق الرصاص this.pressedSpace = true; false; this.heldRight = false; case 38:// مفتاح السهم لأعلى - إطلاق الرصاص this.pressedUp = true; = false; this.pressedRight = true; this.heldRight = true; .keyCode; this.pressedUp = false;منطق اللعبة
يحتوي كائن اللعبة (GAME) على منطق اللعبة بأكملها، بما في ذلك init (التهيئة)، وbindEvent (زر الربط)، وsetStatus (تحديث حالة اللعبة)، واللعب (داخل اللعبة)، والإيقاف (الإيقاف المؤقت)، والنهاية (النهاية)، وما إلى ذلك. .، في هذا لا يوسع الوصف. ويتضمن أيضًا وظائف مثل إنشاء الوحوش ورسم عناصر اللعبة.
// كائن اللعبة بأكمله var GAME = { // سلسلة من الوظائف المنطقية // وظائف عناصر اللعبة}1. التهيئة
تحدد وظيفة التهيئة بشكل أساسي الإحداثيات الأولية للطائرة، ونطاق حركة الطائرة، ونطاق حركة الوحش، وتهيئة النتيجة، ومصفوفة الوحش، وإنشاء كائن KeyBoard، وتنفيذه مرة واحدة فقط.
/** * وظيفة التهيئة، يتم تنفيذ هذه الوظيفة مرة واحدة فقط * @param {object} opts * @return {[type]} [description] */init: function (opts) { //Set opts var opts = Object.sign ( {}, opts, CONFIG);// دمج جميع المعلمات this.opts = opts; this.status = 'start'; // احسب الإحداثيات الأولية لكائن الطائرة this.planePosX = CanvasWidth / 2 - opts.planeSize.width; this.planePosY = CanvasHeight - opts.planeSize.height - opts.canvasPadding; // إحداثيات حد الطائرة this.planeMinX = opts.canvasPadding; width; // احسب منطقة حركة العدو this.enemyMinX = opts.canvasPadding; this.enemyMaxX = CanvasWidth - opts.canvasPadding - opts.enemySize; // تم تعيين النتيجة على 0 this.score = 0; this.enemies = []; ); },2. ربط أحداث الزر
لأن العديد من مشاهد اللعبة تتضمن أزرارًا لبدء اللعبة (playBtn)، وإعادة التشغيل (replayBtn)، والمستوى التالي من اللعبة (nextBtn)، وإيقاف اللعبة مؤقتًا للمتابعة (stopBtn). نحتاج إلى تنفيذ أحداث مختلفة لأزرار مختلفة.
سبب تعريف var self = this في المقام الأول هو استخدام هذا. في الدالة bindEvent، يشير هذا إلى كائن GAME، وفي playBtn.onclick = function () {}؛ من الواضح أن هذا ليس ما نريده، لأن playBtn لا يحتوي على حدث play()، فقط كائن GAME لديه ذلك. لذلك، يجب تعيين كائن GAME إلى متغير ذاتي، ومن ثم يمكن استدعاء حدث play() في playBtn.onclick = function () {};.
تجدر الإشارة إلى أن زر replayBtn يظهر في سيناريوهات الفشل والتخليص، لذلك يتم الحصول على مجموعة من كل .js-replay. ثم يتكرر forEach خلال كل زر replayBtn، ويعيد تعيين المستوى والنتيجة، ويستدعي حدث play().
bindEvent: function () { var self = this; var playBtn = document.querySelector('.js-play'); var replayBtn = document.querySelectorAll('.js-replay'); js-next'); var stopBtn = document.querySelector('.js-stop'); ربط زر بدء اللعبة playBtn.onclick = function () { self.play() }; // ربط زر إعادة تشغيل اللعبة replayBtn.forEach(function (e) { e.onclick = function () { self.opts.level = 1 ; self.play(); self.score = 0; ربط زر اللعبة في المستوى التالي nextBtn.onclick = function () { self.opts.level += 1; self.play() }; ('playing')؛ self.updateElement() };3. توليد الطائرات
createPlane: function () { var opts = this.opts; : this.planeMinX، السرعة: opts.planeSpeed، maxX: this.planeMaxX،planeIcon: opts.planeIcon });}4. إنشاء مجموعة من الوحوش
نظرًا لأن الوحوش تظهر في مجموعات، ويختلف أيضًا عدد الوحوش في كل مستوى، فإن وظيفة حلقتي for هي إنشاء صف من الوحوش، وزيادة صف المستوى من الوحوش وفقًا لعدد المستويات. أو زيادة سرعة الوحش (السرعة: السرعة + i،) لزيادة صعوبة كل مستوى، إلخ.
// إنشاء أعداء createEnemy: function (enemyType) { var opts = this.opts; varlevel = opts.level; .enemyGap var size = opts.enemySize var speed = opts.enemySpeed; // أضف خطًا للعدو لكل مستوى تقوم بترقيته for (var i = 0; i <level; i++) { for (var j = 0; j < numPerLine; j++) { // معلمات العناصر الشاملة var initOpt = {x: الحشو + j * (الحجم + الفجوة)، y: الحشو + i * (الحجم + الفجوة)، الحجم: الحجم، السرعة: السرعة، الحالة: عدو نوع، عدو أيقونة: opts.enemyIcon، عدو BoomIcon: opts.enemyBoomIcon } الأعداء.push(new Enemy(initOpt));5. تحديث الوحوش
احصل على قيمة x لمصفوفة الوحش وحدد ما إذا كانت تصل إلى حدود اللوحة القماشية. إذا وصلت إلى الحدود، يتحرك الوحش للأسفل. في الوقت نفسه، يجب أيضًا مراقبة حالة الوحوش، سواء تم ضرب الوحوش في الحالة العادية، أو الوحوش في الحالة المتفجرة، أو الوحوش التي اختفت، ويجب إزالتها من المصفوفة وسجلها في نفس الوقت.
// تحديث حالة العدو updateEnemeis: function () { var opts = this.opts; var الطائرة = this.plane; var الأعداء = this.enemies; var الأعداءX = getHorizontalBoundary(enemies); if (enemiesX.minX < this.enemyMinX || الأعداءX.maxX >= this.enemyMaxX) { console.log('enemiesX.minX', الأعداءX.minX); console.log('enemiesX.maxX', الأعداءX.maxX); opts.enemyDirection = opts.enemyDirection === 'right' ? '; console.log('opts.enemyDirection', opts.enemyDirection); isFall = true } // تكرار تحديث العدو while (i--) { var العدو = أعداء[i]; if (isFall) { العدو.down(); } العدو.direction(opts.enemyDirection); .booming(); } استراحة; الافتراضي: استراحة } } }،
وظيفة وظيفة getHorizontalBoundary هي اجتياز قيمة x لكل عنصر في المصفوفة، وتصفية القيم الأكبر أو الأصغر، والحصول على الحد الأقصى والحد الأدنى لقيمة x للمصفوفة.
// احصل على وظيفة الحدود الأفقية للمصفوفة getHorizontalBoundary(array) { var min, max; array.forEach(function (item) { if (!min && !max) { min = item.x; max = item.x; } else { if (item.x < min) { min = item.x } if (item.x > max) { max = item.x } } }); الأعلى }}6. قم بتحديث لوحة المفاتيح
اضغط على مفتاح Enter لتنفيذ وظيفة stop()، واضغط على الزر الأيسر لتحريك الطائرة إلى اليسار، واضغط على الزر الأيمن لتحريك الطائرة إلى اليمين، واضغط على زر المسافة لتنفيذ إطلاق الطائرة للرصاص من أجل منع الرصاص من الاتصال في خط مستقيم، قم بتعيين لوحة المفاتيح هنا pressedUp وkeyBoard.pressedSpace كاذبة.
updatePanel: function () { var jet = this.plane; var keyBoard = this.keyBoard; if (keyBoard.pressedEnter) { this.stop(); return; Direction('left'); } if (keyBoard.pressedRight || keyBoard.heldRight) { الطائرة (keyBoard.pressedUp || keyBoard.pressedSpace) { keyBoard.pressedUp = false؛ keyBoard.pressedSpace = false.shoot() } };7. ارسم جميع العناصر
draw: function () { this.renderScore(); this.plane.draw(); this.enemies.forEach(function (enemy) { //console.log('draw:this.enemy',enemy); العدو. يرسم()؛ })؛ }،8. تحديث كافة العناصر
أولاً، حدد ما إذا كان طول مصفوفة الوحش هو 0. إذا كان 0 والمستوى يساوي المستوى الإجمالي، فهذا يعني أنه تم اجتياز المستوى. وإلا، سيتم عرض شاشة إعداد اللعبة للمستوى التالي؛ مصفوفة الوحش أكبر من الإحداثي y للطائرة بالإضافة إلى ارتفاع الوحش، ستفشل اللعبة.
مبدأ الرسوم المتحركة على القماش هو رسم اللوحة القماشية وتحديثها ومسحها بشكل مستمر.
مبدأ إيقاف اللعبة مؤقتًا هو منع تنفيذ وظيفة requestAnimationFrame()، ولكن ليس إعادة تعيين العنصر. لذلك، عندما يتم الحكم على حالة التوقف، سيتم قفز الوظيفة.
// تحديث حالة جميع العناصر updateElement: function () { var self = this; var opts = this.opts; var الأعداء = this.enemies; == opts.totalLevel) { this.end('all-success'); } else { this.end('success' } return } if (enemies[enemies.length - 1].y >= this.planePosY - opts.enemySize) { this.end('failed'); return } // امسح اللوحة القماشية ctx.clearRect(0, 0, CanvasWidth, CanvasHeight); Canvas this .draw(); // تحديث حالة العنصر this.updatePanel(); this.updateEnemeis(); // تكرار التحديث بشكل مستمر requestAnimationFrame(function () { if(self.status === 'stop'){ return }else{ self.updateElement() } });اكتب في النهاية
من خلال الخطوات المذكورة أعلاه، يتم إكمال الوظائف الأساسية للعبة، ولن يتم هنا وصف عناصر التحكم الأخرى في عملية اللعبة، بما في ذلك البداية والنهاية وحساب النتيجة وما إلى ذلك.
ما يمكن تحسينه: عند الضغط باستمرار على شريط المسافة، يمكن إطلاق الرصاص بشكل مستمر. ومع ذلك، عندما ضغطت على مفتاح الاتجاه مرة أخرى، وجدت أنه لم يعد بإمكاني إطلاق الرصاص. من الأفضل أن تكون قادرًا على التحرك أثناء إطلاق الرصاص.
من المثير جدًا ممارسة الألعاب باستخدام اللوحة القماشية. بالإضافة إلى ذلك، يمكن توسيع هذه اللعبة وتغييرها إلى إصدار محمول. يتم تحديد حجم اللوحة القماشية من خلال الحصول على عرض الشاشة وارتفاعها. يتم تغيير جزء لوحة المفاتيح إلى أحداث اللمس (touchstart، touchmove ، اللمس). يمكن أيضًا تغيير مظهر الوحوش إلى السقوط العشوائي من أعلى الشاشة، وسيزيد الوحش من صحته (على سبيل المثال، سيختفي بعد إطلاق النار 4 مرات)، إلخ.
عنوان التحميل: https://github.com/littleyljy/shoot
ما ورد أعلاه هو المحتوى الكامل لهذه المقالة وآمل أن يكون مفيدًا لدراسة الجميع وآمل أيضًا أن يدعم الجميع شبكة VeVb Wulin.