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 objetos
Todo 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 pai
Como 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 jogo
O 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ção
A 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ão
Porque 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 monstros
Como 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 monstros
Obtenha 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 teclado
Pressione 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 elementos
Primeiro, 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 final
Atravé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.