Só estou exposto à tela há mais de um mês. É a primeira vez que implemento completamente um processo de jogo e a colheita é bastante grande.
Capturas de tela do jogo de tiro
Vá primeiro para a demonstração: https://littleyljy.github.io/demo/shootgame/
regras do jogoO jogador é obrigado a controlar a aeronave para disparar balas e destruir os monstros em movimento. Se todos os monstros forem destruídos, o jogo será bem-sucedido. Se os monstros forem para o fundo, o jogo falha.
O jogo está dividido em várias cenas:
Para conseguir a troca de cena, você realmente precisa primeiro exibir: nenhum para todas as cenas e, em seguida, controlar o status dos dados por meio de js para iniciar, reproduzir, falhar, sucesso, sucesso total e parar para implementar a exibição de cena correspondente: bloco.
O HTML e CSS são os seguintes:
<div id=game data-status=start> <div class=game-panel> <section class=game-intro game-ui> <h1 class=section-title>Jogo de tiro</h1> <p class=game- desc>Este é um jogo de tiro viciante Use ← e → para operar seu avião, use o espaço para atirar e use Enter para pausar o jogo. Vamos destruir os monstros espaciais juntos! </p> <p class=game-level>Nível atual: 1</p> <button class=js-play button>Iniciar jogo</button> </section> <section class=game-failed game-ui> <h1 class=section-title>Fim de jogo</h1> <p class=game-info-text>Pontuação final: <span class=score></span></p> <button class=js-replay button> Começar de novo</button> </section> <section class=game-success game-ui> <h1 class=section-title>Sucesso no jogo</h1> <p class=game-next-level game-info-text></p> <button class=js-next button>Continuar jogo</button> </section> <section class=game-all-success game-ui> <h1 class=section-title>Aprovado com sucesso</h1> <p class=game- próximo nível game-info-text>Você se defendeu com sucesso contra todos os ataques de monstros. </p> <button class=js-replay button>Jogar novamente</button> </section> <section class=game-stop game-ui> <h1 class=section-title>Pausa do jogo</h1> < button class=js-stop button>O jogo continua</button> </section> </div> <div class=game-info game-ui> <span class=title>Pontuação:</span> <span class=score > </span> </div> <canvas id=canvas width=700 height=600> <!-- Prancheta de desenho de animação --> </canvas> </div>
#game{largura: 700px; altura: 600px; posição relativa à esquerda: 50%; margem superior: 0 0 0 -350px; );}.game-ui{ display: nenhum; preenchimento: 55px; border-box; altura: 100%;}[data-status=start] .game-intro { display: bloco; padding-top: 180px; tamanho de fundo: 200px;}[data-status=playing] .game-info { display: posição do bloco: absoluto topo:0; esquerda:0; preenchimento:20px;}[data-status=failed] .game-failed,[data-status=success] .game-success,[data-status=all-success] .game-all-success,[ data-status=stop] .game-stop{ display: bloco; padding-top: 180px; url(./img/bg-end.png) sem repetição 380px 190px; tamanho de fundo: 250px;} orientado a objetosTodo o jogo pode tratar monstros (Inimigo), aviões (Avião) e balas (Balas) como objetos, bem como objetos de configuração (CONFIG) e objetos de jogo (GAME) que controlam a lógica do jogo.
Configuração relacionada ao jogo /** * Configuração relacionada ao jogo* @type {Object} */var CONFIG = { status: 'start', // O jogo começa por padrão como nível inicial: 1, // O nível padrão do jogo totalLevel: 6, // Um total de 6 Off numPerLine: 7, // O número padrão de monstros por linha do jogo canvasPadding: 30, // O intervalo de tela padrão bulletSize: 10, // O comprimento padrão do marcador bulletSpeed: 10, // Velocidade de movimento da bala padrão inimigoSpeed: 2, // Distância de movimento padrão do inimigo inimigoSize: 50, // Tamanho padrão do inimigo inimigoGap: 10, // Distância padrão entre inimigos inimigoIcon: './img/enemy.png', // A imagem de o monstro inimigoBoomIcon: './img/boom.png', // A imagem da morte do monstro inimigoDirection: 'right', // O inimigo padrão se move para a direita no início planeSpeed: 5, // A distância padrão que o avião se move em cada passo planeSize: { width: 60, height: 100 }, // O tamanho padrão do avião, planeIcon: '. /img/plane.png' }; Definir classe paiComo monstros (Inimigo), aviões (Avião) e balas (Balas) têm todos os mesmos atributos x, y, tamanho, velocidade e método move(), você pode definir um Elemento de classe pai e implementá-lo herdando a classe pai de a subclasse.
/*Classe pai: Contém velocidade xy move() draw()*/var Element = function (opts) { this.opts = opts ||Definir coordenadas, tamanho, velocidade 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 var 0; addY = y || 0; this.x += addX; this.y += addY;};//Função que herda o protótipo function inheritPrototype(subType, superType) { var proto = Object.create(superType.prototype); proto .construtor = subTipo; subTipo.prototype = proto;}O método move(x, y) se empilha com base no valor (x, y) passado.
Definir monstroOs monstros incluem atributos exclusivos: status do monstro, imagem, boomCount que controla a duração do estado de explosão e métodos draw(), down(), direction() e booming().
/*Inimigo*/var Inimigo = function (opts) { this.opts = opts || {} //Chama o atributo da classe pai Element.call(this, opts); 'normal';//normal, em expansão, noomed this.enemyIcon = opts.enemyIcon; this.enemyBoomIcon = opts.enemyBoomIcon = this.boomCount; 0;};//Herdar o método Element inheritPrototype(Enemy, Element);//Método: Desenhar o inimigo Enemy.prototype.draw = function () { if (this.enemyIcon && this.enemyBoomIcon) { switch (this.status ) { case 'normal': var inimigoIcon = new Image(); this.x, this.y, this.size, this.size); break case 'booming': var inimigoBoomIcon = new inimigoBoomIcon.src = this.enemyBoomIcon; this.y, this.size, this.size); break; case 'boomed': ctx.clearRect (this.x, this.y, this.size, this.size); break; default: break; } } return this;};//Método: down Move down Enemy.prototype.down = function () { this.move(0, this.size }); ;//Método: Mover para a esquerda ou para a direita Enemy.prototype.direction = function (direction) { if (direction === 'right') { this.move(this.speed, 0 } else {); this.move(-this.speed, 0); } return this;};//Método: Inimigo explode Enemy.prototype.booming = function () { this.status = 'booming'; (this.boomCount > 4) { this.status = 'boomed' } retornar isto;} Os marcadores têm métodos fly() e draw().
/*Bullet*/var Bullet = function (opts) { this.opts = opts || Element.call(this, opts);};inheritPrototype(Bullet, Element);//Método: Deixe a bala voar Bullet .prototype.fly = function () { this.move(0, -this.speed); return this;};//Método: Desenhar um marcador Bullet.prototype.draw = function () { ctx.beginPath(); ctx.strokeStyle = '#fff'; ;ctx.stroke(); retorne isto;}; O objeto aeronave contém atributos exclusivos: status, largura e altura, imagem, valores máximo e mínimo de abcissas e métodos haveHit(), draw(), direction(), shoot() e drawBullets().
/*Plane*/var Plane = function (opts) { this.opts = opts || Element.call(this, opts); = opts.width; this.height = opts.height; this.planeIcon = opts.planeIcon; //Bullets relacionados this.bullets = []; this.bulletSpeed = opts.bulletSpeed || CONFIG.bulletSpeed; this.bulletSize = opts.bulletSize || (Plane, Element) ;//Método: O marcador atinge o alvo Plane.prototype.hasHit = function (enemy) { var bullets = this.bullets; for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; < (inimigo.x + inimigo.tamanho)); var isHitPosY = (inimigo.y < bala.y) && (bullet.y < (inimigo.y + inimigo.tamanho)); (isHitPosX && isHitPosY) { this.bullets.splice(i, 1); return true; } } return false;};//Método: Desenhar o plano Plane.prototype.draw = function () { this.drawBullets(); var planeIcon = new Image(); this.width, this.height); return this;};//Método: Direção do plano Plane.prototype.direction = function (direção) { var speed = this.speed; ) { planeSpeed = this.x < this.minX 0 : -speed } else { planeSpeed = this.x > this.maxX 0 : velocidade } 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;//Chamada de cadeia conveniente};//Método: lançar marcadores Plane.prototype.shoot = function () { var bulletPosX = this.x + this.width / 2; this.bullets.push(new Bullet({ x: bulletPosX, y: this.y, tamanho: this.bulletSize, velocidade: this.bulletSpeed })); this;//Método: desenhar marcadores Plane.prototype.drawBullets = function () { var bullets = this.bullets; (i--) { var bullet = bullets[i]; bullet.fly(); Os eventos de teclado têm os seguintes estados:
Porque a aeronave precisa continuar se movendo quando o botão esquerdo (keyCode=37) e o botão direito (keyCode=39) são pressionados (keydown), e o keyup não se move quando liberado. Quando a tecla de espaço (keyCode=32) ou de seta para cima (keyCode=38) é pressionada (keydown), os marcadores são disparados e, quando liberados, o keyup para de disparar. Pressione também a tecla Enter (keyCode=13) para pausar o jogo. Portanto, você precisa definir um objeto KeyBoard para monitorar se onkeydown e onkeyup estão pressionando ou liberando uma tecla.
Como as teclas esquerda e direita são contraditórias, por segurança, você precisa definir a tecla direita como falsa ao pressionar a tecla esquerda. O mesmo vale para clicar com o botão direito.
//Evento de teclado var KeyBoard = function () { document.onkeydown = this.keydown.bind(this); document.onkeyup = this.keyup.bind(this);} //Objeto KeyBoard KeyBoard.prototype = { pressionadoLeft: falso, pressionadoRight: falso, pressionadoUp: falso, holdLeft: falso, holdRight: falso, pressionadoEspaço: falso, pressionadoEnter: falso, keydown: function (e) { var key = e.keyCode; switch (chave) { case 32://Space - dispara balas this.pressedSpace = true; false; this.heldRight = false break; case 38://tecla de seta para cima - lançar marcadores this.heldLeft = true; = false; this.pressedRight = true; this.heldRight = true; break case 13://Tecla Enter - pausa o jogo this.pressedEnter = true; .keyCode; switch (chave) { case 32: this.pressedSpace = false; case 37: this.heldLeft = false; this.pressedLeft = false; this.pressedUp = false; break case 39: this.heldRight = false; this.pressedRight = false; Lógica do jogoO objeto do jogo (GAME) contém a lógica de todo o jogo, incluindo init (inicialização), bindEvent (botão de ligação), setStatus (atualização do status do jogo), play (no jogo), stop (pausa), end (fim), etc. ., em Isso não expande a descrição. Também inclui funções como gerar monstros e desenhar elementos de jogo.
//O objeto inteiro do jogo var GAME = { //Uma série de funções lógicas // Funções dos elementos do jogo} 1. InicializaçãoA função de inicialização define principalmente as coordenadas iniciais da aeronave, a faixa de movimento da aeronave, a faixa de movimento do monstro, inicializa a pontuação, a matriz do monstro, cria o objeto KeyBoard e o executa apenas uma vez.
/** * Função de inicialização, esta função é executada apenas uma vez * @param {object} opts * @return {[type]} [description] */init: function (opts) { //Set opts var opts = Object.assign ( {}, opts, CONFIG);//Mescla todos os parâmetros this.opts = opts; this.status = 'start' //Calcula as coordenadas iniciais do objeto aeronave this.planePosX = canvasWidth / 2; - opts.planeSize.width; this.planePosY = canvasHeight - opts.planeSize.height - opts.canvasPadding; //Coordenadas do limite do plano this.planeMinX = opts.canvasPadding; this.planeMaxX = canvasWidth - opts.canvasPadding - opts.planeSize. width; //Calcula a área de movimento do inimigo this.enemyMinX = opts.canvasPadding; canvasWidth - opts.canvasPadding - opts.enemySize; //A pontuação é definida como 0 this.score = 0; this.enemies = []; ); 2. Vincular eventos de botãoPorque várias cenas do jogo incluem botões para iniciar o jogo (playBtn), reiniciar (replayBtn), próximo nível do jogo (nextBtn) e pausar o jogo para continuar (stopBtn). Precisamos realizar eventos diferentes para botões diferentes.
A razão para definir var self = this em primeiro lugar é o uso de this; Na função bindEvent, isso aponta para o objeto GAME, e em playBtn.onclick = function () {}; isso aponta para playBtn. Obviamente, isso não é o que queremos, porque playBtn não tem um evento play(), apenas o evento. O objeto GAME possui. Portanto, o objeto GAME precisa ser atribuído a uma variável self, e então o evento play() pode ser chamado em playBtn.onclick = function () {};.
Deve-se observar que o botão replayBtn aparece tanto em cenários de falha quanto de liberação, portanto o que se obtém é a coleção de todos os .js-replay. Em seguida, forEach percorre cada botão replayBtn, redefine o nível e a pontuação e chama o evento play().
bindEvent: function () { var self = this; var playBtn = document.querySelector('.js-play'); js-next'); var stopBtn = document.querySelector('.js-stop'); Ligação do botão de início do jogo playBtn.onclick = function () { self.play() } //Reiniciar ligação do botão do jogo replayBtn.forEach(function (e) { e.onclick = function () { self.opts.level = 1 ; self.play(); self.score = 0; totalScoreText.innerText = self.score }); Ligação do botão do próximo nível nextBtn.onclick = function () { self.opts.level += 1; self.play() } // Pause o jogo e continue a ligação do botão stopBtn.onclick = function () { self. ('jogando'); self.updateElement(); 3. Gerar aeronaves createPlane: function () { var opts = this.opts; this.plane = new Plane ({ x: this.planePosX, y: this.planePosY, largura: opts.planeSize.width, altura: opts.planeSize.height, minX : this.planeMinX, velocidade: opts.planeSpeed, maxX: this.planeMaxX, planeIcon: opts.planeIcon });} 4. Gere um grupo de monstrosComo os monstros aparecem em grupos, e o número de monstros em cada nível também é diferente, a função dos dois loops for é gerar uma linha de monstros e aumentar a linha de monstros de nível de acordo com o número de níveis. Ou aumente a velocidade do monstro (velocidade: velocidade + i) para aumentar a dificuldade de cada nível, etc.
//Gerar inimigos createEnemy: function (enemyType) { var opts = this.opts; var level = opts.level; .enemyGap; var tamanho = opts.enemySize var velocidade = opts.enemySpeed; //Adiciona uma linha ao inimigo para cada nível que você atualiza (var i = 0; i < level; i++) { for (var j = 0; j < numPerLine; j++) { //Parâmetros para elementos abrangentes var initOpt = { x: preenchimento + j * (tamanho + lacuna), y: preenchimento + i * (tamanho + lacuna), tamanho: tamanho, velocidade: velocidade, status: inimigoType, inimigoIcon: opts.enemyIcon, inimigoBoomIcon: opts.enemyBoomIcon }; inimigos.push(new Enemy(initOpt)); 5. Atualize monstrosObtenha o valor x da matriz do monstro e determine se ele atinge a borda da tela. Se atingir a borda, o monstro se move para baixo. Ao mesmo tempo, o status dos monstros também deve ser monitorado, se monstros em status normal foram atingidos, monstros em status explosivo e monstros que desapareceram devem ser removidos da matriz e pontuados ao mesmo tempo.
//Atualizar o status do inimigo updateEnemeis: function () { var opts = this.opts;/O paradeiro dos inimigos; var inimigosX = getHorizontalBoundary(inimigos if (enemiesX.minX < this.enemyMinX || inimigosX.maxX >= this.enemyMaxX) { console.log('enemiesX.minX', inimigosX.minX); console.log('enemiesX.maxX', inimigosX.maxX); '; console.log('opts.enemyDirection', opts.enemyDirection); isFall = true } //Loop atualiza inimigo while (i--) { var inimigo; = inimigos[i]; if (isFall) {inimigo.down(); inimigo.direction(opts.enemyDirection) {caso 'normal': if (plane.hasHit(enemy)) { inimigo .booming(); } break; case 'booming': inimigo.booming(); padrão: pausa; } } },A função da função getHorizontalBoundary é percorrer o valor x de cada elemento do array, filtrar valores maiores ou menores e obter o valor x máximo e mínimo do array.
//Obter a função de limite horizontal do array getHorizontalBoundary(array) { var min, max; max = item.x; } else { if (item.x < min) { min = item.x } if (item.x > max) { max = item.x } } }); máximo }} 6. Atualize o painel do tecladoPressione a tecla Enter para executar a função stop(), pressione o botão esquerdo para mover a aeronave para a esquerda, pressione o botão direito para mover a aeronave para a direita e pressione o botão de espaço para executar a aeronave disparando balas. de conectar em linha reta, defina keyBoard aqui pressionado e keyBoard.pressedSpace são falsos.
updatePanel: function () { var plane = this.plane; var keyBoard = this.keyBoard; if (keyBoard.pressedEnter) { this.stop () return; direção('esquerda'); } if (keyBoard.pressedRight || keyBoard.heldRight) { plane.direction('direita' }); (keyBoard.pressedUp || keyBoard.pressedSpace) { keyBoard.pressedUp = false; keyBoard.pressedSpace = false; 7. Desenhe todos os elementos desenhar: function () { this.renderScore(); this.plane.draw(); this.enemies.forEach(function (inimigo) { //console.log('draw:this.enemy',enemy); inimigo. empate(); }); }, 8. Atualize todos os elementosPrimeiro, determine se o comprimento da matriz de monstros é 0. Se for 0 e o nível for igual a totalLevel, significa que o nível foi aprovado. Caso contrário, a tela de preparação do jogo para o próximo nível será exibida se a coordenada y; da matriz do monstro for maior que a coordenada y da aeronave mais a altura do monstro, o jogo falhará.
O princípio da animação em tela é desenhar, atualizar e limpar continuamente a tela.
O princípio da pausa do jogo é impedir a execução da função requestAnimationFrame(), mas não redefinir o elemento. Portanto, quando o status do status for considerado interrompido, a função será interrompida.
//Atualizar o status de todos os elementos updateElement: function () { var self = this var opts = this.opts = this.enemies; == opts.totalLevel) { this.end('todos-sucesso'); else { this.end('sucesso'); - 1].y >= this.planePosY - opts.enemySize) { this.end('failed'); //Desenhe a tela ctx.clearRect(0, 0, canvasWidth, canvasHeight); canvas this .draw(); //Atualiza o status do elemento this.updatePanel(); this.updateEnemeis(); //Loop continuamente updateElement requestAnimationFrame(function () { if(self.status === 'parar'){ return }else{ self.updateElement(); escreva no finalAtravés das etapas acima, as funções básicas do jogo são concluídas. Outros controles do processo do jogo, incluindo início, fim, cálculo de pontuação, etc., não serão descritos aqui.
O que pode ser otimizado: Ao manter pressionada a barra de espaço, as balas podem ser disparadas continuamente. No entanto, quando pressionei a tecla de direção novamente, descobri que não conseguia mais disparar balas. É melhor poder se mover enquanto ainda dispara balas.
É bastante interessante jogar jogos com tela. Além disso, este jogo pode ser expandido e alterado para uma versão móvel. O tamanho da tela é determinado pela obtenção da largura e altura da tela. A parte do teclado é alterada para eventos de toque (touchstart, touchmove). , touchend). A aparência dos monstros também pode ser alterada para cair aleatoriamente do topo da tela, e o monstro aumentará sua saúde (por exemplo, desaparecerá após atirar 4 vezes), etc.
Endereço para download: https://github.com/littleyljy/shoot
O texto acima é todo o conteúdo deste artigo. Espero que seja útil para o estudo de todos. Também espero que todos apoiem a Rede VeVb Wulin.