Solo he estado expuesto al lienzo durante más de un mes. Es la primera vez que implemento completamente un proceso de juego y la cosecha es bastante grande.
Capturas de pantalla del juego de disparos
Vaya primero a la demostración: https://littleyljy.github.io/demo/shootgame/
reglas del juegoEl jugador debe controlar el avión para disparar balas y destruir los monstruos en movimiento. Si todos los monstruos son destruidos, el juego tiene éxito. Si los monstruos se mueven hasta el fondo, el juego falla.
El juego se divide en varias escenas:
Para lograr el cambio de escena, primero debe mostrar: ninguno para todas las escenas, y luego controlar el estado de los datos a través de js para iniciar, reproducir, fallar, éxito, todo éxito y detenerse para implementar la visualización de escena correspondiente: bloque.
El HTML y CSS son los siguientes:
<div id=game data-status=start> <div class=game-panel> <section class=game-intro game-ui> <h1 class=section-title>Juego de disparos</h1> <p class=game- desc>Este es un adictivo juego de disparos. Usa ← y → para operar tu avión, usa el espacio para disparar y usa enter para pausar el juego. ¡Destruyamos juntos a los monstruos espaciales! </p> <p class=game-level>Nivel actual: 1</p> <button class=js-play button>Iniciar juego</button> </section> <section class=game-failed game-ui> <h1 class=section-title>Juego terminado</h1> <p class=game-info-text>Puntuación final: <span class=score></span></p> <button class=js-replay button> Empezar de nuevo</botón> </section> <section class=juego-éxito juego-ui> <h1 class=section-title>Éxito del juego</h1> <p clase=juego-siguiente-nivel juego-info-text></p> <botón class=js-next button>Continuar juego</button> </section> <section class=game-all-success game-ui> <h1 class=section-title>Pasado con éxito</h1> <p class=game- siguiente nivel game-info-text>Te has defendido con éxito contra todos los ataques de monstruos. </p> <button class=js-replay button>Jugar de nuevo</button> </section> <section class=game-stop game-ui> <h1 class=section-title>Pausa del juego</h1> < botón class=js-stop button>El juego continúa</button> </section> </div> <div class=game-info game-ui> <span class=title>Puntuación:</span> <span class=score > </span> </div> <canvas id=canvas width=700 height=600> <!-- Tablero de dibujo de animación --> </canvas> </div>
#juego{ ancho: 700px; alto: 600px; posición: relativa; izquierda: 50%; arriba: 40px; margen: 0 0 0 -350px; );}.game-ui{ pantalla: ninguno; relleno: 55px; border-box; altura: 100%;}[data-status=start] .game-intro { display: block; padding-top: 180px; fondo: url(./img/bg.png) sin repetición 430px 180px; tamaño de fondo: 200px;}[estado de datos = jugando] .info del juego { pantalla: bloque; posición: superior: 0; izquierda:0; relleno:20px;}[estado-datos=fallido] .juego-fallido,[estado-datos=éxito] .juego-éxito,[estado-datos=todo-éxito] .juego-todo-éxito,[ estado-datos=detener] .game-stop{ pantalla: bloque; padding-top: 180px fondo: url(./img/bg-end.png) sin repetición 380px 190px; tamaño de fondo: 250px;}orientado a objetos
Todo el juego puede tratar monstruos (Enemy), aviones (Plane) y balas (Bullets) como objetos, así como objetos de configuración (CONFIG) y objetos del juego (GAME) que controlan la lógica del juego.
Configuración relacionada con el juego/** * Configuración relacionada con el juego* @type {Object} */var CONFIG = { status: 'start', // El juego comienza de forma predeterminada con el nivel inicial: 1, // El nivel predeterminado del juego totalLevel: 6, // Un total de 6 Off numPerLine: 7, // El número predeterminado de monstruos por línea del juego canvasPadding: 30, // El intervalo de lienzo predeterminado BulletSize: 10, // La longitud de bala predeterminada BulletSpeed: 10, // Velocidad de movimiento de bala predeterminada enemigoVelocidad: 2, // Distancia de movimiento del enemigo predeterminada enemigoTamaño: 50, // Tamaño del enemigo predeterminado enemigoGap: 10, // Distancia predeterminada entre enemigos enemigoIcon: './img/enemy.png', // La imagen de el monstruo enemigoBoomIcon: './img/boom.png', // La imagen del enemigo mortal del monstruoDirection: 'right', // El enemigo predeterminado se mueve hacia la derecha al principio planeSpeed: 5, // La distancia predeterminada que el avión se mueve en cada paso planeSize: { width: 60, height: 100 }, // El tamaño predeterminado del avión, planeIcon: '. /img/avión.png' };Definir clase padre
Debido a que los monstruos (Enemy), los aviones (Plane) y las balas (Bullet) tienen los mismos atributos x, y, tamaño, velocidad y método move(), puedes definir un elemento de clase principal e implementarlo heredando la clase principal de la subclase.
/*Clase principal: contiene xy speed move() draw()*/var Elemento = function (opts) { this.opts = opts || Establece coordenadas, tamaño, velocidad this.x = opts.x; this.y = opts.y; this.size = opts.size; this.speed = opts.speed;};Elemento.prototype.move = función (x, y) { var addX = x 0; addY = y || 0; this.x += addX; this.y += addY;};//Función que hereda la función prototipo heredarPrototype(subType, superType) { var proto = Object.create(superType.prototype); proto .constructor = subtipo; subtipo.prototipo = proto;}
El método move(x, y) se apila según el valor (x, y) pasado.
definir monstruoLos monstruos incluyen atributos únicos: estado del monstruo, imagen, boomCount que controla la duración del estado de explosión y métodos draw(), down(), direction() y booming().
/*Enemy*/var Enemy = function (opts) { this.opts = opts || 'normal';//normal, en auge, noomed this.enemyIcon = opts.enemyIcon; this.enemyBoomIcon = opts.enemyBoomIcon = this.boomCount; 0;};//Heredar el método Elemento heredarPrototype(Enemy, Element);//Método: Dibujar al enemigo Enemy.prototype.draw = function () { if (this.enemyIcon && this.enemyBoomIcon) { switch (this.status ) { caso 'normal': var enemigoIcon = nueva Imagen(); enemigoIcon.src = this.enemyIcon;drawImage(enemyIcon, this.x, this.y, this.size, this.size); break; caso 'en auge': var enemigoBoomIcon = new Image(); enemigoBoomIcon.src = this.enemyBoomIcon; this.y, this.size, this.size); break; caso 'boomed': ctx.clearRect(this.x, this.y, this.size, this.size); break; default: break; } } return this;};//Método: bajar Enemy.prototype.down = function () { this.move(0, this.size); ;//Método: Mover hacia la izquierda o hacia la derecha Enemy.prototype.direction = function (dirección) { if (dirección === 'derecha') { this.move(this.speed, 0); this.move(-this.speed, 0); } return this;};//Método: el enemigo explota Enemy.prototype.booming = function () { this.status = 'booming'; (this.boomCount > 4) { this.status = 'boomed' } devuelve esto;}
Las viñetas tienen métodos fly() y draw().
/*Bullet*/var Bullet = function (opts) { this.opts = opts || Element.call(this, opts);};inheritPrototype(Bullet, Element);//Método: dejar volar la bala Bullet .prototype.fly = function () { this.move(0, -this.speed); return this;};//Método: dibujar una viñeta Bullet.prototype.draw = function () { ctx.beginPath(); ctx.strokeStyle = '#fff'; ctx.moveTo(this.x, this.y); ctx.lineTo(this.x, this.y - CONFIG.bulletSize()); ; ctx.stroke(); devolver esto;};
El objeto de la aeronave contiene atributos únicos: estado, ancho y alto, imagen, valores máximos y mínimos de abscisas, y los métodos haveHit(), draw(), direction(), shoot() y drawBullets().
/*Plane*/var Plane = function (opts) { this.opts = opts {}; Element.call(this, opts); // Estado e imagen de atributo únicos this.status = 'normal'; = opts.width; this.height = opts.height; this.planeIcon = opts.planeIcon = this.minX = this.maxX = opts.maxX; //Viñetas relacionadas this.bullets = []; this.bulletSpeed = opts.bulletSpeed || Heredar el método del elemento heredarPrototype (Plano, Elemento) ;//Método: La bala alcanza el objetivo Plane.prototype.hasHit = function (enemy) { var bullets = this.bullets; for (var i = balas.length - 1; i >= 0; i--) { var bala = balas[i]; < (enemigo.x + enemigo.tamaño)); var isHitPosY = (enemigo.y < bala.y) && (bala.y < (enemigo.y + enemigo.tamaño)); (isHitPosX && isHitPosY) { this.bullets.splice(i, 1); return true; } } return false;};// Método: dibujar el avión Plane.prototype.draw = function () { this.drawBullets(); var planeIcon = nueva Imagen(); planeIcon.src = this.planeIcon; ctx.drawImage(planeIcon, this.x, this.y, this.width, this.height); return this;};//Método: Dirección del plano Plane.prototype.direction = function (dirección) { var speed = this.speed var planeSpeed if (dirección === 'izquierda' ) {planeSpeed = this.x < this.minX 0: -speed } else { planeSpeed = this.x> this.maxX 0: velocidad; console.log('planeSpeed:', planeSpeed); console.log('this.x:', this.x).log('this.minX:', this.minX); .maxX:', this.maxX); this.move(planeSpeed, 0); return this;//Llamada en cadena conveniente};//Método: lanzar balas Plane.prototype.shoot = function () { var BulletPosX = this.x + this.width / 2; this.bullets.push(new Bullet({ x: bulletPosX, y: this.y, tamaño: this.bulletSize, velocidad: this.bulletSpeed })); this; };//Método: dibujar viñetas Plane.prototype.drawBullets = function () { var balas = this.bullets var i = balas.longitud; (i--) { var bala = balas[i]; bala.fly(); if (bullet.y <= 0) { balas.splice(i, 1 } bala.draw());
Los eventos de teclado tienen los siguientes estados:
Porque la aeronave necesita seguir moviéndose cuando se presionan el botón izquierdo (keyCode=37) y el botón derecho (keyCode=39) (keydown), y el keyup no se mueve cuando se suelta. Cuando se presiona el espacio (keyCode=32) o la tecla de flecha hacia arriba (keyCode=38) (keydown), se disparan balas y, cuando se suelta, keyup deja de disparar. También presione la tecla Enter (keyCode=13) para pausar el juego. Por lo tanto, necesita definir un objeto KeyBoard para monitorear si onkeydown y onkeyup están presionando o soltando una tecla.
Debido a que las teclas izquierda y derecha son contradictorias, para estar seguro, debe configurar la tecla derecha en falso al presionar la tecla izquierda. Lo mismo ocurre con hacer clic derecho.
//Evento de teclado var KeyBoard = function () { document.onkeydown = this.keydown.bind(this); document.onkeyup = this.keyup.bind(this);} //Objeto de teclado KeyBoard.prototype = { presionadoIzquierda: falso, presionadoDerecha: falso, presionadoArriba: falso, retenidoIzquierda: falso, retenidoDerecha: falso, presionadoEspacio: falso, presionadoEntrar: falso, tecla presionada: función (e) { var clave = e.keyCode; switch (tecla) { case 32://Espacio - disparar balas this.pressedSpace = true; case 37://Tecla de flecha izquierda this.pressedLeft = true; false; this.heldRight = false; case 38://Tecla de flecha hacia arriba - iniciar viñetas this.pressedUp = true case 39://Tecla de flecha derecha this.pressedLeft = false; = false; this.pressedRight = true; this.heldRight = true; case 13://Tecla Enter - pausar el juego this.pressedEnter = true; .keyCode; cambiar (clave) { caso 32: this.pressedSpace = falso; caso 37: this.heldLeft = falso; this.pressedUp = falso; caso 39: this.heldRight = falso; this.pressedRight = falso caso 13: this.pressedEnter = falso;Lógica del juego
El objeto del juego (GAME) contiene la lógica de todo el juego, incluido init (inicialización), bindEvent (botón de vinculación), setStatus (actualización del estado del juego), play (en el juego), stop (pausa), end (final), etc. ., en Esto no amplía la descripción. También incluye funciones como generar monstruos y dibujar elementos del juego.
//El objeto completo del juego var GAME = { //Una serie de funciones lógicas // Funciones de elementos del juego}1. Inicialización
La función de inicialización define principalmente las coordenadas iniciales de la aeronave, el rango de movimiento de la aeronave, el rango de movimiento del monstruo, inicializa la puntuación, la matriz de monstruos, crea el objeto KeyBoard y lo ejecuta solo una vez.
/** * Función de inicialización, esta función se ejecuta solo una vez * @param {object} opts * @return {[type]} [description] */init: function (opts) { //Set opts var opts = Object.assign ( {}, opts, CONFIG);//Fusionar todos los parámetros this.opts = opts; this.status = 'start'; //Calcular las coordenadas iniciales del objeto de la aeronave this.planePosX = canvasWidth / 2 - opts.planeSize.width; this.planePosY = canvasHeight - opts.planeSize.height - opts.canvasPadding; //Coordenadas del límite del plano this.planeMinX = opts.canvasPadding; ancho; //Calcular el área de movimiento del enemigo this.enemyMinX = opts.canvasPadding this.enemyMaxX = canvasWidth - opts.canvasPadding - opts.enemySize; //La puntuación se establece en 0 this.score = 0; this.enemies = []; this.keyBoard = new KeyBoard(); );2. Vincular eventos de botones
Porque varias escenas del juego incluyen botones para iniciar el juego (playBtn), reiniciar (replayBtn), siguiente nivel del juego (nextBtn) y pausar el juego para continuar (stopBtn). Necesitamos realizar diferentes eventos para diferentes botones.
La razón para definir var self = this en primer lugar es el uso de this. En la función bindEvent, esto apunta al objeto GAME, y en playBtn.onclick = function () {}; esto apunta a playBtn. Esto obviamente no es lo que queremos, porque playBtn no tiene un evento play(), solo el. El objeto GAME lo tiene. Por lo tanto, el objeto GAME debe asignarse a una variable self y luego se puede llamar al evento play() en playBtn.onclick = function () {};.
Cabe señalar que el botón replayBtn aparece tanto en escenarios de falla como de autorización, por lo que lo que se obtiene es la recopilación de todos los .js-replay. Luego, forEach itera a través de cada botón replayBtn, restablece el nivel y la puntuación y llama al evento play().
bindEvent: function () { var self = this; var playBtn = document.querySelector('.js-play'); var replayBtn = document.querySelectorAll('.js-replay'); js-siguiente'); var stopBtn = document.querySelector('.js-stop'); Iniciar el enlace del botón del juego playBtn.onclick = function () { self.play() }; //Reiniciar el enlace del botón del juego replayBtn.forEach(function (e) { e.onclick = function () { self.opts.level = 1 ; self.play(); self.puntuación = 0; totalScoreText.innerText = self.puntuación }); Enlace del botón del juego del siguiente nivel nextBtn.onclick = function () { self.opts.level += 1; self.play() } // Pausar el juego y continuar enlace del botón stopBtn.onclick = function () { setStatus. ('jugando'); self.updateElement(); } };3. Generar aviones
createPlane: function () { var opts = this.opts; this.plane = new Plane({ x: this.planePosX, y: this.planePosY, ancho: opts.planeSize.width, alto: opts.planeSize.height, minX : this.planeMinX, velocidad: opts.planeSpeed, maxX: this.planeMaxX, planeIcon: opts.planeIcon });}4. Genera un grupo de monstruos.
Debido a que los monstruos aparecen en grupos y la cantidad de monstruos en cada nivel también es diferente, la función de los dos bucles for es generar una fila de monstruos y aumentar la fila de niveles de monstruos de acuerdo con la cantidad de niveles. O aumentar la velocidad del monstruo (velocidad: velocidad + i,) para aumentar la dificultad de cada nivel, etc.
// Generar enemigos createEnemy: function (enemyType) { var opts = this.opts; var nivel = opts.level; var enemigos = this.enemies; var numPerLine = opts.numPerLine; .enemyGap; var tamaño = opts.enemySize; var velocidad = opts.enemySpeed; //Agrega una línea para cada nivel de enemigo for (var i = 0; i < nivel; i++) { for (var j = 0; j < numPerLine; j++) { //Parámetros de elementos completos var initOpt = { x : relleno + j * (tamaño + espacio), y: relleno + i * (tamaño + espacio), tamaño: tamaño, velocidad: velocidad, estado: enemigoTipo, enemigoIcon: opts.enemyIcon, enemigoBoomIcon: opts.enemyBoomIcon }; enemigos.push(new Enemy(initOpt)); devolver enemigos },5. Actualizar monstruos
Obtenga el valor x de la matriz de monstruos y determine si llega al borde del lienzo. Si llega al borde, el monstruo se mueve hacia abajo. Al mismo tiempo, también se debe monitorear el estado de los monstruos, si los monstruos en estado normal han sido golpeados, los monstruos en estado explosivo y los monstruos que han desaparecido deben eliminarse de la matriz y puntuarse al mismo tiempo.
// Actualizar el estado del enemigo updateEnemeis: function () { var opts = this.opts; var plane = this.plane; var enemigos = this.enemies; var i = enemigos.length;// El paradero de los enemigos var enemigosX = getHorizontalBoundary(enemigos); if (enemigosX.minX < this.enemyMinX || enemigosX.maxX >= this.enemyMaxX) { console.log('enemiesX.minX', enemigosX.minX); console.log('enemiesX.maxX', enemigosX.maxX); opts.enemyDirection = opts.enemyDirection === 'derecha' ? '; console.log('opts.enemyDirection', opts.enemyDirection); isFall = true } // Actualización en bucle del enemigo while (i--) { var enemigo; = enemigos[i]; if (isFall) { enemigo.down(); } enemigo.dirección(opts.enemyDirection); switch (enemy.status) { case 'normal': if (plane.hasHit(enemy)) { enemigo .booming(); } break; caso 'en auge': enemigo.booming(); caso 'en auge': enemigos.splice(i, 1); predeterminado: romper; } } },
La función de la función getHorizontalBoundary es recorrer el valor x de cada elemento de la matriz, filtrar valores mayores o menores y obtener el valor x máximo y mínimo de la matriz.
//Obtener la función de límite horizontal de la matriz 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 } } }); máximo }}6. Actualizar el panel del teclado
Presione la tecla Enter para ejecutar la función stop(), presione el botón izquierdo para mover el avión hacia la izquierda, presione el botón derecho para mover el avión hacia la derecha y presione el botón espacio para ejecutar el avión disparando balas para evitar las balas. Para que no se conecte en línea recta, configure el teclado presionadoArriba y el teclado.presionadoSpace son falsos.
updatePanel: function () { var plane = this.plane; var keyBoard = this.keyBoard; if (keyBoard.pressedEnter) { this.stop() } if (keyBoard.pressedLeft || keyBoard.heldLeft) { avión. dirección('izquierda'); } if (keyBoard.pressedRight || keyBoard.heldRight) { plane.direction('derecha'); (keyBoard.pressedUp || keyBoard.pressedSpace) { keyBoard.pressedUp = false; keyBoard.pressedSpace = false;7. Dibuja todos los elementos.
dibujar: función () { this.renderScore(); this.plane.draw(); this.enemies.forEach(function (enemigo) { //console.log('draw:this.enemy',enemy); enemigo. dibujar(); }); },8. Actualiza todos los elementos.
Primero, determine si la longitud de la matriz de monstruos es 0. Si es 0 y el nivel es igual a totalLevel, significa que se pasa el nivel. De lo contrario, se mostrará la pantalla de preparación del juego para el siguiente nivel si la coordenada y; del conjunto de monstruos es mayor que la coordenada y del avión más la altura del monstruo, indicará que el juego ha fallado.
El principio de la animación del lienzo es dibujar, actualizar y borrar continuamente el lienzo.
El principio de pausa del juego es evitar que se ejecute la función requestAnimationFrame(), pero no restablecer el elemento. Por lo tanto, cuando se considere que el estado está detenido, la función saltará.
//Actualiza el estado de todos los elementos updateElement: function () { var self = this; var opts = this.opts; var enemigos = this.enemies if (enemies.length === 0) { if (opts.level = == opts.totalLevel) { this.end('todo-éxito'); } else { this.end('éxito') return } if (enemigos[enemigos.longitud] - 1].y >= this.planePosY - opts.enemySize) { this.end('failed'); //Borrar el lienzo ctx.clearRect(0, 0, canvasWidth, canvasHeight); lienzo this .draw(); // Actualizar el estado del elemento this.updatePanel(); this.updateEnemeis() // Actualizar continuamente el elemento requestAnimationFrame(function () { if(self.status === 'detener'){ return }else{ self.updateElement();escribe al final
A través de los pasos anteriores, se completan las funciones básicas del juego. Otros controles del proceso del juego, incluido el inicio, el final, el cálculo de la puntuación, etc., no se describirán aquí.
Qué se puede optimizar: Al mantener presionada la barra espaciadora, se pueden disparar balas continuamente. Sin embargo, cuando presioné la tecla de dirección nuevamente, descubrí que ya no podía disparar balas. Es mejor poder seguir disparando balas cuando puedas moverte.
Es bastante interesante jugar juegos con lienzo. Además, este juego se puede ampliar y cambiar a una versión móvil. El tamaño del lienzo se determina obteniendo el ancho y alto de la pantalla. La parte del teclado se cambia a eventos táctiles (touchstart, touchmove). , touchend) La apariencia de los monstruos también se puede cambiar para que caigan aleatoriamente desde la parte superior de la pantalla y el monstruo aumentará su salud (por ejemplo, desaparecerá después de disparar 4 veces), etc.
Dirección de descarga: https://github.com/littleyljy/shoot
Lo anterior es el contenido completo de este artículo. Espero que sea útil para el estudio de todos. También espero que todos apoyen VeVb Wulin Network.