Ich bin erst seit mehr als einem Monat mit Canvas konfrontiert. Es ist das erste Mal, dass ich einen Spielprozess vollständig implementiert habe, und die Ernte ist ziemlich groß.
Screenshots von Schießspielen
Gehen Sie zuerst zur Demo: https://littleyljy.github.io/demo/shootgame/
SpielregelnDer Spieler muss das Flugzeug steuern, um Kugeln abzufeuern und die sich bewegenden Monster zu zerstören. Wenn alle Monster zerstört werden, ist das Spiel erfolgreich. Wenn sich die Monster nach unten bewegen, schlägt das Spiel fehl.
Das Spiel ist in mehrere Szenen unterteilt:
Um einen Szenenwechsel zu erreichen, müssen Sie tatsächlich zunächst für alle Szenen „keine“ anzeigen und dann den Datenstatus über js steuern, um zu starten, abzuspielen, fehlgeschlagen, erfolgreich, vollständig erfolgreich zu sein und zu stoppen, um den entsprechenden Szenenanzeigeblock zu implementieren.
HTML und CSS lauten wie folgt:
<div id=game data-status=start> <div class=game-panel> <section class=game-intro game-ui> <h1 class=section-title>Shooter-Spiel</h1> <p class=game- desc>Dies ist ein süchtig machendes Schießspiel. Verwenden Sie ← und →, um Ihr Flugzeug zu steuern, nutzen Sie die Leertaste zum Schießen und drücken Sie die Eingabetaste, um das Spiel zu pausieren. Lasst uns gemeinsam die Weltraummonster vernichten! </p> <p class=game-level>Aktuelles Level: 1</p> <button class=js-play button>Spiel starten</button> </section> <section class=game-failed game-ui> <h1 class=section-title>Spiel vorbei</h1> <p class=game-info-text>Endergebnis: <span class=score></span></p> <button class=js-replay button> Beginnen Sie noch einmal</button> </section> <section class=game-success game-ui> <h1 class=section-title>Game success</h1> <p class=game-next-level game-info-text></p> <button class=js-next button>Spiel fortsetzen</button> </section> <section class=game-all-success game-ui> <h1 class=section-title>Erfolgreich bestanden</h1> <p class=game- nächste Stufe game-info-text>Du hast dich erfolgreich gegen alle Monsterangriffe verteidigt. </p> <button class=js-replay button>Erneut spielen</button> </section> <section class=game-stop game-ui> <h1 class=section-title>Spielpause</h1> < button class=js-stop button>Spiel geht weiter</button> </section> </div> <div class=game-info game-ui> <span class=title>Ergebnis:</span> <span class=score > </span> </div> <canvas id=canvas width=700 height=600> <!-- Zeichenbrett für Animationen --> </canvas> </div>
#game{ width: 700px; position: relative; );}.game-ui{ display: none; padding: 55px; border-box; height: 100%;}[data-status=start] .game-intro { display: block; padding-top: 180px; background: url(./img/bg.png) no-repeat 430px 180px; Hintergrundgröße: 200px;}[data-status=playing] .game-info { display: block; top:0; left:0; padding:20px;}[data-status=failed] .game-failed,[data-status=success] .game-success,[data-status=all-success] .game-all-success,[ data-status=stop] .game-stop{ display: block; padding-top: 180px; URL(./img/bg-end.png) no-repeat 380px 190px;objektorientiert
Das gesamte Spiel kann Monster (Enemy), Flugzeuge (Plane) und Kugeln (Bullets) als Objekte sowie Konfigurationsobjekte (CONFIG) und Spielobjekte (GAME) behandeln, die die Spiellogik steuern.
Spielbezogene Konfiguration/** * Spielbezogene Konfiguration* @type {Object} */var CONFIG = { status: 'start', // Das Spiel startet standardmäßig mit der Startstufe: 1, // Die Standardstufe des Spiels totalLevel: 6, // Insgesamt 6 Off numPerLine: 7, // Die Standardanzahl der Monster pro Zeile des Spiels, canvasPadding: 30, // Das Standard-Canvas-Intervall, BulletSize: 10, // Die Standard-Bullet-Länge, BulletSpeed: 10, // Standardgeschwindigkeit der Geschossbewegungen, feindlichGeschwindigkeit: 2, // Standardmäßiger Bewegungsabstand des Feindes, Feindgröße: 50, // Standardgröße des Feindes, FeindGap: 10, // Standardabstand zwischen Feinden, FeindIcon: './img/enemy.png', / / Das Bild von der MonsterfeindBoomIcon: './img/boom.png', // Das Bild des Todesfeindes des MonstersDirection: 'right', // Der Standardgegner bewegt sich zu Beginn nach rechts planeSpeed: 5, // Die Standardentfernung, um die sich das Flugzeug bei jedem Schritt bewegt planeSize: { width: 60, height: 100 }, // Die Standardgröße des Flugzeugs, planeIcon: '. /img/plane.png' };Definieren Sie die übergeordnete Klasse
Da Monster (Enemy), Flugzeuge (Plane) und Kugeln (Bullet) alle die gleichen x-, y-, Größen- und Geschwindigkeitsattribute und die gleiche move()-Methode haben, können Sie ein übergeordnetes Klassenelement definieren und es implementieren, indem Sie die übergeordnete Klasse von erben die Unterklasse.
/*Übergeordnete Klasse: Enthält xy speed move() draw()*/var Element = function (opts) { this.opts = opts || //Setze Koordinaten, Größe, Geschwindigkeit this.x = opts.x; this.y = opts.y; this.size = opts.speed;};Element.prototype.move = function (x, y) { var addX = x var addY = y ||. 0; this.x += addX; this.y += addY;};//Funktion, die die Prototypfunktion erbt inheritPrototype(subType, superType) { var proto = Object.create(superType.prototype); proto.constructor = subType; subType.prototype = proto;}
Die Methode move(x, y) stapelt sich basierend auf dem übergebenen (x, y)-Wert.
Monster definierenMonster umfassen einzigartige Attribute: Monsterstatus, Bild, boomCount, der die Dauer des Explosionszustands steuert, sowie die Methoden draw(), down(), Direction() und booming().
/*Enemy*/var Enemy = function (opts) { this.opts = opts || ' normal';//normal, boomend, noomed this.enemyIcon = opts.enemyBoomIcon = opts.enemyBoomIcon = 0;};//Erben Sie die Element-Methode inheritPrototype(Enemy, Element);//Methode: Zeichnen Sie den Feind Enemy.prototype.draw = function () { if (this.enemyIcon && this.enemyBoomIcon) { switch (this.status ) { case 'normal': var feindIcon = new Image(); feindlichIcon.src = this.enemyIcon ctx.drawImage(enemyIcon, this.x, this.y, this.size, this.size); this.y, this.size, this.size); break; case 'boomed': ctx.clearRect(this.x, this.y, this.size, this.size); default: break; } } return this;};//Methode: down Move down Enemy.prototype.down = function () { this.move(0, this.size); ;//Methode: Nach links oder rechts bewegen Enemy.prototype.direction = function (direction) { if (direction === 'right') { this.move(this.speed, 0 } else { this.move(-this.speed, 0); } return this;};//Methode: Enemy explodiert Enemy.prototype.booming = function () { this.status = 'booming'; this.boomCount += 1; (this.boomCount > 4) { this.status = 'boomed' } return this;}
Aufzählungszeichen verfügen über die Methoden fly() und draw().
/*Bullet*/var Bullet = function (opts) { this.opts = opts || {};inheritPrototype(Bullet, Element);//Methode: Lass die Kugel fliegen .prototype.fly = function () { this.move(0, -this.speed); return this;};//Methode: Zeichne eine Kugel Bullet.prototype.draw = function () { ctx.beginPath(); ctx.StrokeStyle = '#fff'; ctx.lineTo(this.x, this.y - CONFIG.bulletSize); ; ctx.Stroke(); return this;};
Das Flugzeugobjekt enthält eindeutige Attribute: Status, Breite und Höhe, Bild, maximale und minimale Abszissenwerte sowie die Methoden haveHit(), draw(), Direction(), Shoot() und drawBullets().
/*Plane*/var Plane = function (opts) { this.opts = opts || = opts.width; this.height = opts.height; this.planeIcon = opts.planeIcon = opts.maxX; //Bullets bezogen auf this.bulletSpeed = []; this.bulletSpeed = opts.bulletSpeed || (Ebene, Element) ;//Methode: Die Kugel trifft das Ziel Plane.prototype.hasHit = function (enemy) { varbullets = this.bullets; for (var i = Bullets.length - 1; i >= 0; i--) { var Bullet = Bullets[i]; < (Feind.x + Feind.Größe)); (isHitPosX && isHitPosY) { this.bullets.splice(i, 1); return true } } return false;};//Methode: Zeichne die Ebene Plane.prototype.draw = function () { this.drawBullets(); var planeIcon = new Image(); planeIcon.src = this.planeIcon ctx.drawImage(planeIcon, this.x, this.y, this.width, this.height); return this;};//Methode: Ebenenrichtung Plane.prototype.direction = function (direction) { var speed = this.speed; if (direction === 'left' ) { planeSpeed = this.x < this.minX ? 0 : -speed } else { planeSpeed = this.x > this.maxX ? 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;//Bequemer Kettenaufruf};//Methode: Launch Bullets Plane.prototype.shoot = function () { var BulletPosX = This this; };//Methode: Aufzählungszeichen zeichnen Plane.prototype.drawBullets = function () { varbullets = this.bullets; while (i--) { var Bullet = Bullets[i]; if (Bullet.y <= 0) { Bullets.Splice(i, }};
Tastaturereignisse haben die folgenden Zustände:
Denn das Flugzeug muss in Bewegung bleiben, wenn die linke Taste (keyCode=37) und die rechte Taste (keyCode=39) gedrückt werden (Taste nach unten), und die Taste nach oben bewegt sich nicht, wenn sie losgelassen wird. Wenn die Leertaste (keyCode=32) oder die Aufwärtspfeiltaste (keyCode=38) gedrückt wird (keydown), werden Kugeln abgefeuert, und wenn sie losgelassen wird, stoppt die Taste up das Feuern. Drücken Sie außerdem die Eingabetaste (keyCode=13), um das Spiel anzuhalten. Daher müssen Sie ein KeyBoard-Objekt definieren, um zu überwachen, ob onkeydown und onkeyup eine Taste drücken oder loslassen.
Da die linke und die rechte Taste widersprüchlich sind, müssen Sie sicherheitshalber beim Drücken der linken Taste die rechte Taste auf „false“ setzen. Das Gleiche gilt für einen Rechtsklick.
//Tastaturereignis var KeyBoard = function () { document.onkeydown = this.keydown.bind(this); document.onkeyup = this.keyup.bind(this);}; //KeyBoard object KeyBoard.prototype = {pressedLeft: falsch, gedrücktRechts: falsch, gedrücktUp: falsch, gehaltenLinks: falsch, gehaltenRechts: falsch, gedrücktLeertaste: falsch, gedrücktEnter: falsch, Taste nach unten: Funktion (e) { var key = e.keyCode; switch (key) { case 32://Space - feuert Kugeln ab this.pressedSpace = true; false; this.heldRight = false; break; = false; this.heldRight = true; break the game this.pressedEnter = true }, keyup: function (e) { var key = e .keyCode; switch (key) { case 32: this.pressedSpace = false; this.pressedUp = false; case 39: this.gedRight = false case 13: this.pressedEnter = false;Spiellogik
Das Spielobjekt (GAME) enthält die Logik des gesamten Spiels, einschließlich init (Initialisierung), bindEvent (Bindungsschaltfläche), setStatus (Spielstatus aktualisieren), Play (im Spiel), Stop (Pause), Ende (Ende) usw ., in Dies erweitert die Beschreibung nicht. Es enthält auch Funktionen wie das Erzeugen von Monstern und das Zeichnen von Spielelementen.
//Das gesamte Spielobjekt var GAME = { //Eine Reihe logischer Funktionen // Spielelementfunktionen}1. Initialisierung
Die Initialisierungsfunktion definiert hauptsächlich die Anfangskoordinaten des Flugzeugs, den Bewegungsbereich des Flugzeugs und den Bewegungsbereich des Monsters, initialisiert die Punktzahl und das Monster-Array, erstellt das KeyBoard-Objekt und führt es nur einmal aus.
/** * Initialisierungsfunktion, diese Funktion wird nur einmal ausgeführt * @param {object} opts * @return {[type]} [description] */init: function (opts) { //Set opts var opts = Object.assign ( {}, opts, CONFIG);//Alle Parameter zusammenführen this.opts = opts; this.status = 'start'; //Berechnen Sie die Anfangskoordinaten des Flugzeugobjekts this.planePosX = canvasWidth / 2 - opts.planeSize.width; this.planePosY = canvasHeight - opts.canvasPadding; //Ebenenbegrenzungskoordinaten this.planeMinX = opts.canvasPadding; this.planeMaxX = canvasWidth - opts.canvasPadding - opts.planeSize. width; //Berechnung des Bewegungsbereichs des Feindes this.enemyMinX = opts.canvasPadding = canvasWidth - opts.canvasPadding - opts.enemySize; //Die Punktzahl ist auf 0 gesetzt this.enemies = []; this.bindEvent(); ); },2. Binden Sie Schaltflächenereignisse
Denn mehrere Spielszenen enthalten Schaltflächen zum Starten des Spiels (playBtn), Neustarten (replayBtn), zum nächsten Level des Spiels (nextBtn) und zum Anhalten des Spiels, um fortzufahren (stopBtn). Wir müssen unterschiedliche Ereignisse für unterschiedliche Schaltflächen ausführen.
Der Grund für die Definition von var self = this; ist in erster Linie die Verwendung von this. In der bindEvent-Funktion zeigt dies auf das GAME-Objekt, und in playBtn.onclick zeigt dies auf playBtn. Dies ist offensichtlich nicht das, was wir wollen, da playBtn kein play()-Ereignis hat, sondern nur das Das GAME-Objekt hat es. Daher muss das GAME-Objekt einer Variablen self zugewiesen werden, und dann kann das Ereignis play() in playBtn.onclick = function () {}; aufgerufen werden.
Es ist zu beachten, dass die replayBtn-Schaltfläche sowohl im Fehler- als auch im Freigabeszenario angezeigt wird. Was erhalten wird, ist also die Sammlung aller .js-replay-Dateien. Dann durchläuft forEach jede replayBtn-Schaltfläche, setzt das Level und die Punktzahl zurück und ruft das play()-Ereignis auf.
bindEvent: function () { var self = this; var playBtn = document.querySelector('.js-play'); var replayBtn = document.querySelector('. js-next'); var stopBtn = document.querySelector('.js-stop'); Schaltflächenbindung des Spiels starten playBtn.onclick = function () { self.play(); }; // Schaltflächenbindung des Spiels neu starten replayBtn.forEach(function (e) { e.onclick = function () { self.opts. level = 1 ; self.play(); self.score = 0; totalScoreText.innerText = self.score }; Buttonbindung der nächsten Ebene nextBtn.onclick = function () { self.play( }); // Spiel anhalten und Buttonbindung fortsetzen stopBtn.onclick = function () { self ('playing'); self.updateElement( };3. Flugzeuge generieren
createPlane: function () { var opts = this.opts; this.plane = new Plane({ x: this.planePosX, y: this.planePosY, width: opts.planeSize.width, height: opts.planeSize.height, minX : this.planeMinX, Geschwindigkeit: opts.planeSpeed, maxX: this.planeMaxX, planeIcon: opts.planeIcon });}4. Erstelle eine Gruppe von Monstern
Da Monster in Gruppen auftreten und auch die Anzahl der Monster in jedem Level unterschiedlich ist, besteht die Funktion der beiden for-Schleifen darin, eine Reihe von Monstern zu generieren und die Levelreihe der Monster entsprechend der Anzahl der Level zu erhöhen. Oder erhöhen Sie die Geschwindigkeit des Monsters (Geschwindigkeit: Geschwindigkeit + i,), um den Schwierigkeitsgrad jedes Levels usw. zu erhöhen.
//Generiere Feinde createEnemy: function (enemyType) { var level = opts.level; var numPerLine = opts.numPerLine; var gap = opts .enemyGap; var size = opts.enemySize; var speed = opts.enemySpeed; //Für jede Ebene der Feindebene eine Zeile hinzufügen for (var i = 0; i < level; i++) { for (var j = 0; j < numPerLine; j++) { //Parameter umfassender Elemente var initOpt = { x : Polsterung + j * (Größe + Lücke), y: Polsterung + i * (Größe + Lücke), Größe: Größe, Geschwindigkeit: Geschwindigkeit, Status: Feindtyp, FeindIcon: opts.enemyIcon, FeindBoomIcon: opts.enemyBoomIcon }; Feinde.push(new Enemy(initOpt));5. Monster aktualisieren
Ermitteln Sie den x-Wert des Monster-Arrays und bestimmen Sie, ob es den Rand der Leinwand erreicht. Wenn es den Rand erreicht, bewegt sich das Monster nach unten. Gleichzeitig muss auch der Status der Monster überwacht werden, ob Monster im Normalstatus getroffen wurden, Monster im Explosionsstatus und verschwundene Monster aus der Reihe entfernt und gleichzeitig gewertet werden müssen.
//Feindstatus aktualisieren updateEnemeis: function () { var plane = this.enemies; var i = terribles.length;//Der Aufenthaltsort von Feinden varFeindeX = getHorizontalBoundary(Feinde); if (FeindeX.minX <this.enemyMinX ||FeindeX.maxX >= this.enemyMaxX) { console.log('enemiesX.minX',FeindeX.minX); console.log('EnemiesX.maxX',FeindeX.maxX); opts.enemyDirection === 'right' : 'right '; console.log('opts.enemyDirection', opts.enemyDirection); isFall = true; =Feinde[i]; if (isFall) {Feind.down(); .booming(); } break; case 'booming(); Standard: break; } } },
Die Funktion der getHorizontalBoundary-Funktion besteht darin, den x-Wert jedes Elements des Arrays zu durchlaufen, größere oder kleinere Werte herauszufiltern und den maximalen und minimalen x-Wert des Arrays zu ermitteln.
//Holen Sie sich die horizontale Grenzfunktion des Arrays 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 } } }); max }}6. Tastaturfeld aktualisieren
Drücken Sie die Eingabetaste, um die stop()-Funktion auszuführen, drücken Sie die linke Taste, um das Flugzeug nach links zu bewegen, drücken Sie die rechte Taste, um das Flugzeug nach rechts zu bewegen, und drücken Sie die Leertaste, um das Flugzeug abzufeuern, um Kugeln zu verhindern Um eine Verbindung in einer geraden Linie herzustellen, legen Sie fest, dass „keyBoardpressedUp“ und „keyBoard.pressedSpace“ auf „false“ gesetzt sind.
updatePanel: function () { var plane = this.keyBoard; if (keyBoard.pressedEnter) { this.stop(); if (keyBoard.pressedLeft || keyBoard.heldLeft) { plane. Direction('left'); } if (keyBoard.pressedRight || keyBoard.heldRight) { plane.direction('right' } if (keyBoard.pressedUp || keyBoard.pressedSpace) { keyBoard.pressedUp = false; keyBoard.pressedSpace = false },7. Zeichnen Sie alle Elemente
draw: function () { this.renderScore(); this.enemies.forEach(function (enemy) { //console.log('draw:this.enemy',enemy); feind. ziehen(); }); },8. Aktualisieren Sie alle Elemente
Stellen Sie zunächst fest, ob die Länge des Monster-Arrays 0 ist. Wenn sie 0 ist und das Level gleich totalLevel ist, bedeutet dies, dass das Level bestanden wurde. Andernfalls wird der Spielvorbereitungsbildschirm für das nächste Level angezeigt Ist die Größe der Monsteranordnung größer als die Y-Koordinate des Flugzeugs plus die Höhe des Monsters, weist dies darauf hin, dass das Spiel fehlgeschlagen ist.
Das Prinzip der Leinwandanimation besteht darin, die Leinwand kontinuierlich zu zeichnen, zu aktualisieren und zu löschen.
Das Prinzip der Spielpause besteht darin, die Ausführung der Funktion requestAnimationFrame() zu verhindern, das Element jedoch nicht zurückzusetzen. Wenn daher festgestellt wird, dass der Status des Status gestoppt ist, wird die Funktion übersprungen.
//Status aller Elemente aktualisieren: function () { var self = this; var feinds = this.enemies; if (enemies.length === 0) { if (opts.level = == opts.totalLevel) { this.end('all-success'); } else { this.end('success' } return; - 1].y >= this.planePosY - opts.enemySize) { this.end('failed'); //Leichte die Leinwand canvas this .draw(); //Elementstatus aktualisieren this.updatePanel(); //Kontinuierliche Schleife updateElement requestAnimationFrame(funktion) { if(self.status === 'stop'){ return; }else{ self.updateElement( } });schreibe am Ende
Durch die oben genannten Schritte werden die Grundfunktionen des Spiels abgeschlossen. Weitere Steuerungen des Spielablaufs, einschließlich Start, Ende, Punkteberechnung usw., werden hier nicht beschrieben.
Was optimiert werden kann: Bei gedrückter Leertaste können kontinuierlich Kugeln abgefeuert werden. Als ich jedoch die Richtungstaste erneut drückte, stellte ich fest, dass ich keine Kugeln mehr abfeuern konnte. Am besten ist es, weiterhin Kugeln abfeuern zu können, wenn man sich bewegen kann.
Es ist sehr interessant, Spiele mit Canvas zu spielen. Darüber hinaus kann dieses Spiel erweitert und in eine mobile Version geändert werden. Die Canvas-Größe wird durch Ermitteln der Bildschirmbreite und -höhe bestimmt , touchend). Das Aussehen von Monstern kann auch so geändert werden, dass sie zufällig vom oberen Bildschirmrand fallen, und das Monster wird seine Gesundheit erhöhen (z. B. verschwindet es, nachdem es viermal geschossen hat) usw.
Download-Adresse: https://github.com/littleyljy/shoot
Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass er für das Studium aller hilfreich ist. Ich hoffe auch, dass jeder das VeVb Wulin Network unterstützt.