Crie um jogo de física animada com JavaScript | Frank Dvorak | Skillshare
Pesquisar

Velocidade de reprodução


1.0x


  • 0.5x
  • 0.75x
  • 1x (Normal)
  • 1.25x
  • 1.5x
  • 1.75x
  • 2x

Crie um jogo de física animada com JavaScript

teacher avatar Frank Dvorak, Creative Coding

Assista a este curso e milhares de outros

Tenha acesso ilimitado a todos os cursos
Oferecidos por líderes do setor e profissionais do mercado
Os temas incluem ilustração, design, fotografia e muito mais

Assista a este curso e milhares de outros

Tenha acesso ilimitado a todos os cursos
Oferecidos por líderes do setor e profissionais do mercado
Os temas incluem ilustração, design, fotografia e muito mais

Aulas neste curso

    • 1.

      Introdução

      0:46

    • 2.

      Configuração básica

      3:51

    • 3.

      Programação orientada a objetos em JavaScript

      4:21

    • 4.

      Desenhando o jogador

      7:10

    • 5.

      Controles de mouse

      5:59

    • 6.

      Fazendo o jogador se mover

      7:39

    • 7.

      Criando barreiras

      6:33

    • 8.

      Objetos não sobrepostos

      6:35

    • 9.

      Imagens randomizadas de uma folha de sprite

      5:14

    • 10.

      Regras de posicionamento

      5:59

    • 11.

      Método de detecção de colisão reutilizável

      4:04

    • 12.

      Física

      8:07

    • 13.

      Animação de sprite 8 direcional

      4:13

    • 14.

      Ângulos de animação

      7:05

    • 15.

      Modo de depuração

      3:33

    • 16.

      Limites de movimento do jogador

      2:56

    • 17.

      FPS

      9:39

    • 18.

      Aula de ovo

      4:53

    • 19.

      Adicionando novos ovos periodicamente

      6:59

    • 20.

      Física do ovo

      5:34

    • 21.

      Ordem de desenho

      8:17

    • 22.

      curso do inimigo

      11:19

    • 23.

      Larva curso

      3:26

    • 24.

      Hachura de ovo

      9:58

    • 25.

      Sprites de larva e colisões

      4:37

    • 26.

      Ganhando pontos de partitura

      2:40

    • 27.

      Efeitos de particleira

      8:55

    • 28.

      Movimento de particle

      7:04

    • 29.

      Skins de inimigos randomizados

      4:24

    • 30.

      Condição de vitória e perda

      10:31

    • 31.

      Reiniciar o jogo

      6:32

    • 32.

      Estendendo o curso do inimigo

      5:55

    • 33.

      Modo de tela cheia simples

      3:07

    • 34.

      Animação completa de folha de sprite de jogador

      2:36

    • 35.

      Animação completa de folha de sprite de larva

      1:43

    • 36.

      Animação completa de folha de sprite de inimigos

      6:38

    • 37.

      Projeto de bônus (opcional)

      0:46

    • 38.

      Configuração de projeto de bônus

      1:36

    • 39.

      Aprimorando os mundos de jogos

      6:56

    • 40.

      Configuração de JavaScript

      5:52

    • 41.

      Controles de teclado

      7:27

    • 42.

      Personagem do jogador

      11:21

    • 43.

      4 folhas de sprite direcional

      12:00

    • 44.

      Como controlar o FPS

      9:02

    • 45.

      Objetos de jogo posicionados aleatoriamente

      8:19

    • 46.

      Ordem de camadas e desenho em jogos 2D

      5:33

  • --
  • Nível iniciante
  • Nível intermediário
  • Nível avançado
  • Todos os níveis

Gerado pela comunidade

O nível é determinado pela opinião da maioria dos estudantes que avaliaram este curso. Mostramos a recomendação do professor até que sejam coletadas as respostas de pelo menos 5 estudantes.

164

Estudantes

1

Projetos

Sobre este curso

O que faz um ótimo jogo? É sobre visuais bonitos e polidos ou sobre jogabilidade que é boa e responsiva? É sobre ideias únicas, ou talvez sejam os pequenos detalhes, segredos especiais e ovos de páscoa? Quais são os ingredientes em uma receita de desenvolvimento de jogos perfeita?

 

Neste curso, vamos mergulhar profundamente na animação de sprites, interatividade e física 2D. Vamos aprender 10 técnicas importantes que todos os desenvolvedores de jogos precisam conhecer e vamos aplicá-las em um projeto real. 

 

Os alunos deste curso terão muita arte de jogo profissional de alta resolução 2D de graça. Eu forneço programas de arte ambiental e de personagem na forma de folhas de sprite prontas para usar, bem como arquivos de origem com peças separadas para aqueles que querem editar as cores, criar seus próprios cogumelos e criaturas ou criar suas próprias animações. 

 

Hoje vamos aprender:

- Como usar o método embutido do drawImage para desenhar ambientes de jogo aleatórios e personagens animados a partir de uma folha de sprite

- Como controlar o FPS do nosso jogo e como medir o tempo para desencadear eventos periódicos

- Como reiniciar o jogo pressionando um button

- Como aplicar detecção de colisão, resolver colisões e usar isso para simular a física

- Como implementar uma IA muito simples para fazer as criaturas se sentirem vivas.

- Como capturar a posição do mouse e animar uma folha de sprite de 8 direcional com base na posição relativa entre o mouse e o personagem do jogador

- Como usar HTML5, CSS3 e JavaScript de baunilha simples para criar um jogo do zero. Vamos escrever e entender cada linha de código, não vamos depender de nenhum framework ou bibliotecas externas

... e muito mais

 

O ritmo e as técnicas deste tutorial são amigáveis para iniciantes. O conhecimento existente de HTML, CSS e JavaScript é necessário para seguir o curso. Se você entende o básico de JavaScript e sabe quais são as funções, para loops e matizes, será capaz de obter o valor máximo deste curso.

 

Divirta-se! :)

Conheça seu professor

Teacher Profile Image

Frank Dvorak

Creative Coding

Professor

Hello, I'm Frank. I'm a front-end web developer, owner of Frank's Laboratory YouTube channel. Come explore creative coding, game development and generative art with me.

Visualizar o perfil completo

Level: Beginner

Nota do curso

As expectativas foram atingidas?
    Superou!
  • 0%
  • Sim
  • 0%
  • Um pouco
  • 0%
  • Não
  • 0%

Por que fazer parte da Skillshare?

Faça cursos premiados Skillshare Original

Cada curso possui aulas curtas e projetos práticos

Sua assinatura apoia os professores da Skillshare

Aprenda em qualquer lugar

Faça cursos em qualquer lugar com o aplicativo da Skillshare. Assista no avião, no metrô ou em qualquer lugar que funcione melhor para você, por streaming ou download.

Transcrições

1. Apresentação: O que torna um jogo perfeito? Visuais detalhados e feitos à mão, ambientes de fantasia e uma grande variedade de personagens animados? Ou é mais sobre a mecânica do jogo, física para interações e a IA para fazer com que as criaturas se sintam vivas. Qual é o ingrediente especial na receita de desenvolvimento de jogos? Nesta aula, vamos nos aprofundar nos segredos do JavaScript, animação web e desenvolvimento web de front-end. Vamos tentar descobrir o que faz um ótimo jogo e como podemos construí-lo do início ao fim usando apenas nosso próprio código JavaScript, sem estruturas e bibliotecas. Esta aula é para iniciantes, mas é necessário algum conhecimento básico de desenvolvimento web de front-end para obter o valor máximo. Vamos embora. 2. Configuração básica: Vou dar uma tonelada de arte de jogos grátis nesta aula, vou ser assim. Vamos controlar a bola azul. Seu trabalho é proteger os ovos incubados de ondas de inimigos famintos. jogador pode posicionar todos os objetos do jogo empurrando-os. Podemos colocar o X e os filhotes em segurança, ou podemos tirar os inimigos do caminho enquanto construímos este projeto. Mostrarei como usar HTML, CSS e JavaScript simples para implementar muitas técnicas importantes de animação na web e muitas técnicas importantes de animação na web desenvolvimento de jogos Aplicaremos a física para fazer objetos do jogo interajam uns com os outros. Aprenderemos como reiniciar nosso jogo pressionando um botão, como controlar o FBS de todo o jogo, como acionar eventos periódicos. Aplicaremos a maioria dos controles. Aprenda a gerenciar e animar uma planilha direcional. Vamos acionar e animar partículas quando um determinado evento acontecer e muito mais. Vamos analisar isso passo a passo para garantir que realmente entendemos o código. E até o final desta aula, você terá todas as habilidades necessárias para criar seus próprios jogos e projetos de animação. Eu crio um elemento IMG com um ID de sobreposição. A fonte será sobreposição de pontos PNG. Você pode baixar todos os recursos do projeto na seção de recursos abaixo, existem imagens e sprites individuais que eu os estou usando nesta aula, bem como uma pasta bônus com arquivos de origem onde cada objeto do jogo Gmb dividido em peças de alta resolução. Você pode editar e animar. Então, se você quiser, você pode misturá-los e combiná-los com a arte que eu lhe dei para outras aulas em que estamos usando esse tema de floresta de cogumelos. E você pode criar seus próprios ambientes de jogo exclusivos. Toda a arte desta aula é livre de direitos autorais. Sinta-se à vontade para baixá-los, modificá-los e reutilizá-los em seus próprios projetos da maneira que quiser. Já estou lhe dando toda a arte de que você precisará para seguir este curso. Mas para vocês, quero ir mais longe. Se você tiver um editor gráfico como o Photoshop, você pode, por exemplo mudar a cor das imagens para criar ainda mais variedade visual. Os arquivos de origem dos personagens também podem ser estuprados e animados em ferramentas de sprite 2D, como ossos de dragão ou espinha Confira os arquivos de origem e use-os como quiser. É meu presente para você como agradecimento por passar seu tempo comigo. Portanto, temos uma configuração básica em index.HTML e um estilo CSS. padrão Gammas é esse tamanho pequeno de 300.150 pixels. Se eu definisse seu tamanho com CSS, eu estaria definindo apenas o tamanho do elemento. E isso ampliaria meus desenhos. A tela HTML tem , na verdade, dois tamanhos. Tamanho do elemento, o tamanho da superfície de desenho que pode ser definido de forma independente. Quero que os dois tamanhos sejam iguais para evitar qualquer distorção. Então, vou dimensionar minha tela com o JavaScript aqui. Eu envolvo tudo dentro do load event listener porque usaremos muitos recursos artísticos. E eu queria ter certeza de que todas as minhas imagens estão totalmente carregadas e disponíveis antes que qualquer código JavaScript seja executado. Primeiro, precisamos apontar o JavaScript para nosso elemento de tela usando get element by ID. Salvamos essa referência nessa variável personalizada que chamo , por exemplo, canvas. Em seguida, pego essa variável e, a partir dela chamo o método getContext embutido, passando-a para D como argumento do tipo de contexto. Isso inicializará um objeto embutido que contém todas as propriedades e métodos de desenho do Canvas. Agora podemos chamá-los a partir dessa variável CD x. Então, como eu disse antes, quero definir o tamanho da tela, o tamanho dos elementos e o tamanho da superfície do desenho com o mesmo valor. Nós podemos fazer isso assim. largura dos pontos da tela é de 1.280 pixels e a altura da tela é de 720 pixels. Agora, a arte de fundo completa que preparei para você é revelada perfeita. 3. Programação orientada para objetos no JavaScript: Eu queria escrever este jogo como base de código orientada a objetos para torná-lo mais modular Teremos uma classe para jogador e outra classe para jogo para gerenciar toda a lógica do jogo. O cérebro principal dessa base de código. Também precisaremos de um loop de animação para desenhar e atualizar nosso jogo repetidamente para criar uma ilusão de movimento. construtor da classe de jogo espera uma referência ao elemento canvas como um argumento como este. No interior, nós a convertemos em uma propriedade de classe e precisaremos da propriedade de largura. E o valor será esse ponto Canvas da linha 15, largura do ponto. Assim. Isso nos dará 1.280 pixels, pois os configuramos on-line Fazemos a mesma coisa com essa altura de ponto. Estamos fazendo uma referência ao elemento canvas e definindo a largura e a altura do nosso jogo para serem iguais à largura e altura do Canvas. Concluiremos essa configuração conectando esse argumento de tela a essa variável de tela um pouco mais tarde, quando criarmos uma instância de nossa classe de jogo usando a nova palavra-chave, chegaremos lá mais cedo. Eu vou te mostrar. Antes de fazermos isso, eu também preciso que o jogador tenha acesso às propriedades de largura e altura do nosso jogo, porque ele precisa saber, por exemplo, quando ele se move para fora da área de jogo e assim por diante. Eu lhe darei acesso a toda a classe do jogo e todas as suas propriedades e métodos, passando uma referência a essa classe de jogo como um argumento como este interno, nós a convertemos em uma propriedade de classe. Lembre-se de que não estou criando uma cópia do Game Object quando crio objetos de jogador em JavaScript que crio objetos de jogador em são chamados de tipos de dados de referência. Portanto, esse jogo de pontos aqui na linha nove não cria uma cópia. Ele apenas aponta para um espaço na memória onde nosso objeto principal do jogo está armazenado. O código dentro de um construtor de classe. Ele é acionado quando criamos uma instância de uma classe usando a nova palavra-chave, faremos isso em um minuto. Quero que nossa base de código crie automaticamente um jogador quando criarmos uma instância do nosso objeto principal do jogo. Então, eu posso fazer isso dentro do construtor da classe. Eu crio uma propriedade chamada dot Player e a defino como um novo player como este. Eu posso ver que o construtor de classes de jogadores, a ajuda on-line, espera o jogo como argumento. Então eu passo essa palavra-chave, já que aqui estamos dentro dessa classe de jogo. Essa palavra-chave aqui se refere a todo o objeto do jogo. Aqui na linha 18, estamos criando uma instância da classe do jogador e a salvando, pois essa propriedade dot Player na estrutura da classe do jogo em nosso código, dessa forma, criará automaticamente o jogador. Quando criamos um jogo, criamos uma instância do objeto do jogo como essa variável personalizada que chamo de, por exemplo , jogo, e eu a defino como novo jogo. Na linha 14, posso ver que o construtor da classe do jogo espera o Canvas como argumento. Então, eu passo a variável canvas da linha para que essa variável seja convertida em uma propriedade de classe e a largura e a altura da área do jogo serão extraídas dela conforme planejamos. Vamos verificar se tudo funcionou consolidando essa variável do jogo. Bom, eu posso ver as propriedades corretas de largura e altura e também temos uma instância da classe do jogador lá. Essa é uma das maneiras pelas quais você pode organizar e conectar seus objetos em uma base de código JavaScript orientada a objetos. Lembre-se de que a ordem em que definimos as classes é importante. O arquivo Javascript é lido linha por linha de cima para baixo. As classes Javascript são levantadas, mas não são inicializadas até que essa linha específica esteja correta. Portanto, a classe do jogador precisa ser definida antes de ser usada. Uma ideia muito boa seria dividir nosso JavaScript em módulos individuais e importar e exportar nossas classes entre arquivos conforme necessário. Para este projeto, escreverei todo o código em um único arquivo JavaScript para mantê-lo o mais amigável possível para iniciantes Usar módulos JavaScript exigiria que executássemos esse código por meio de um servidor local. Ele não executaria mais o código simplesmente abrindo um arquivo HTML de índice em um navegador da web Se você tiver mais experiência, será muito fácil concluir o projeto comigo. E então, se você quiser, você mesmo pode dividir as aulas individuais em módulos separados. Esta aula é para iniciantes, então vamos nos concentrar em princípios orientados a objetos e técnicas de animação. 4. Desenhar o jogador: A função que fica em um objeto é chamada de método. O jogador precisará do método de desenho para desenhar e animar. Esperará que o contexto seja um argumento para especificar em que tipo de nós queremos nos basear. Conectaremos esse argumento de contexto à nossa variável CTX da linha três Quando chamarmos esse método de desenho um pouco mais tarde, mostrarei a você. Eu queria desenhar um círculo simples no início, representando nosso jogador. Para desenhar um círculo no Canvas, pegamos contextos e chamamos start path para dizer ao JavaScript que queremos começar a desenhar uma nova forma. Queremos fechar a forma anterior. Se houver algum, chamamos o método de arco embutido, que espera pelo menos cinco argumentos. Ele espera as coordenadas x e y do ponto central do círculo. Seu ângulo inicial de raio em radianos medido a partir do eixo x positivo e um ângulo onde o arco termina, novamente em radianos medidos a partir do eixo x positivo. Há um sexto argumento opcional para o sentido anti-horário. Se não o definirmos, o padrão será false, que significa que o arco será desenhado no sentido horário. Então, o ângulo inicial é 0 rad e o ângulo final é matemático. Pi vezes dois, é um círculo completo. Agora podemos escolher chamar de preenchimento, preencher a forma com cor, traçar apenas para delinear a forma. Ou poderíamos usar os dois. Faremos isso em breve. Como podemos realmente desenhar o player no Canvas? Agora, na classe do jogo, eu crio um método que chamo, por exemplo, a. Render. Esse método desenhará e atualizará todos os objetos em nosso jogo. Ele espera contextos como argumento. Lá dentro, eu pego esse ponto Player da linha 23 e, por meio dessa referência, acessamos o método de desenho na classe de jogador que acabamos de definir na linha 11. Esse método contém o código para desenhar um círculo representando o jogador. Eu posso ver que ele espera contextos como argumento, então eu passo esse contexto, passamos para o método render. Agora posso usar essa variável de jogo que contém uma instância de toda a classe do jogo. E a partir daí eu chamo render e passo para o CTX. A partir da linha três, esse CTX receberá um contexto de nome variável aqui e será passado para método de desenho na classe do jogador. Ótimo, estamos desenhando um círculo preto representando que o jogador está aqui. Talvez você não consiga ver isso. Então, vamos dar a ele coordenadas x e y diferentes para movê-lo. Em vez de codificar todos esses valores, quero que você crie propriedades na classe Player. Então use-os aqui. Precisaremos das coordenadas x e y para a posição do jogador. Mas porque nesta aula estamos aprendendo sobre posições, caixas de sucesso e imagens de personagens que podem ter formas e tamanhos muito diferentes. Terei que criar uma propriedade para as posições X e Y da caixa de identificação do jogador. E eu precisarei ter uma propriedade x e y diferente para a imagem da folha de sprite do jogador. Fará mais sentido à medida que o construirmos. Quero ser muito explícito com meus nomes de variáveis para deixar absolutamente claro qual valor é a posição da caixa de colisão e qual valor é a posição de uma planilha. Então, em vez de nomear essas propriedades apenas x e y, vou chamá-las de colisão x e faculdade e posição Y, X e Y da caixa de impacto de colisão do ponto central do círculo de colisão. Todos os objetos em nosso jogo de hoje terão caixas de sucesso circulares porque eu queria mostrar como fazê-los se empurrar e deslizar um ao lado do outro na posição inicial do jogador para ficarem exatamente no meio da área do jogo. Então colision x será esse jogo de pontos das linhas nove. E dessa propriedade, extrairei a largura da linha 23. E no meio, então vezes 0,5 colisão y será o mesmo. Este ponto joga com altura vezes 0,5. Agora posso usar esses valores como argumentos x e y para o método de arco Canvas como este. Agora podemos mover o jogador alterando os valores de colisão e colisão. Por que propriedades? Também precisaremos de uma propriedade chamada raio de colisão, que definirá o tamanho desse jogador circular. Ele tinha livros. Eu também o uso aqui dentro do método do arco. cor de preenchimento padrão é sempre preta. Eu posso substituir, aderir a certos tipos de propriedade fillStyle ao branco como este. Em vez de preencher a forma com cor, também poderíamos simplesmente traçá-la. Novamente. Por padrão, a largura da linha do traçado é de um pixel e a cor é preta. Defini a largura da linha para três pixels e disse estilo de traçado para branco. Observe que estou definindo essas propriedades do Canvas fora de qualquer classe ou método. Eu faço isso de propósito porque esse código só será executado uma vez no carregamento inicial da página. Você nem sempre pode fazer isso se tiver vários objetos com campos, estilos e cores de traçado diferentes . Nesse caso, você terá que definir essas propriedades dentro do método de desenho e alternar entre elas repetidamente. O problema com isso é que o método de desenho será chamado 60 vezes por segundo. E uma mudança em um estado como esse pode tornar o desempenho caro. É uma boa ideia estruturar o código de forma que você altere um estado o mínimo possível. E quando digo estado da tela, quero dizer qualquer coisa, desde transformações até mudanças de cores de FillStyle e estilo de traçado. É por isso que coloquei esse código aqui em vez de colocá-lo diretamente dentro do método de desenho para que ele funcione o mínimo possível ao mesmo tempo, aplique as cores e configurações conforme necessário. Também posso ligar para preencher aqui. Então, agora estamos preenchendo e traçando o mesmo caminho definido pelo método do arco. Eu quero que o preenchimento seja branco, mas eu queria ser um pouco transparente. Kind of us tem uma propriedade alfa global para definir a opacidade das formas que estamos desenhando. O problema é que quando eu disse alfa global com um valor diferente, tudo desenhado depois disso será semitransparente. Quero que a transparência se aplique apenas à cor de preenchimento do círculo de colisão do jogador para limitar certos tipos de configurações apenas a chamadas de sorteio específicas. Podemos agrupar esse código de desenho entre os métodos de segurança e restauração integrados do Canvas. se eu dissesse alfa global para 0,5, isso afetará apenas aquela ação específica de desenho. No nosso caso, isso afetará apenas o preenchimento desse círculo. Portanto, o método save cria um instantâneo do estado atual, incluindo seu fillStyle, opacidade da largura da linha, bem como as transformações e o dimensionamento. Se estivermos fazendo isso, então eu posso fazer qualquer mudança nesse tipo de estado que eu quero. Nesse caso, acabei de definir a opacidade para 0,5. Essa cor de preenchimento será afetada por essa alteração na opacidade. E então chamamos de restauração, restaurando todos os tipos de configurações para o que eram quando chamamos pela primeira vez o método de salvamento associado. Por esse motivo, o traçado não será afetado por uma opacidade reduzida. Os métodos de salvar e restaurar nos permitem aplicar configurações específicas de desenho apenas para selecionar as formas sem afetar o restante de nossos desenhos em Canvas. 5. Controles do mouse: Eu quero mover o jogador usando o mouse. Já sabemos que o código dentro do construtor da classe do jogo será executado no ponto em que criarmos uma instância dessa classe usando a nova palavra-chave. Estamos aproveitando os dados criando automaticamente uma instância da classe player aqui. Na verdade, podemos executar qualquer código JavaScript aqui. Posso até mesmo colocar ouvintes de eventos aqui para garantir que eles sejam aplicados automaticamente. Quando eu crio uma instância da classe de jogo, eu crio um ouvinte de eventos para o evento mouse down. Quando o botão do mouse é clicado, o código dentro dessa função de retorno de chamada será executado. Eu testei apenas registrando a palavra mouse no console. Se eu salvar as alterações porque já estou instanciando essa classe online para D6. ouvinte D7 é aplicado automaticamente. Agora, quando clico no Canvas, log do console está acionando boa função de retorno de chamada no EventListener gerando automaticamente um objeto de evento que contém todos os tipos de informações sobre o evento que acabou de acontecer. Para ter acesso a esse objeto, basta dar a ele um nome de variável. Você pode dar o nome que quiser. Mas a convenção geralmente é evento ou E. Vamos registrar esse evento no console. Eu clico no Canvas e vejo isso aqui. Eu inspecionei. Você pode ver que ele contém muitas informações sobre esse clique do mouse, por exemplo, vemos as coordenadas x e y desse clique aqui. Existem muitas outras propriedades que nos dizem qual botão do mouse foi pressionado e muitas outras coisas. Quero que você pegue a coordenada do clique e a salve como propriedades no objeto principal do jogo. E a partir daí, também poderemos acessá-los a partir do nosso objeto de jogador. Eu crio uma nova propriedade na classe de jogo chamada dot mouse. Será um objeto com a propriedade x com o valor padrão dessa largura de ponto vezes 0,5. E y será essa altura do ponto vezes 0,5. Então, no meio da tela, horizontal e verticalmente. Também queremos monitorar quando o botão do mouse é pressionado. Inicialmente, ele será definido como falso. Se eu consola log e dot x e y, você pode ver que estamos obtendo as coordenadas x e y à medida que clicamos ao nosso redor. O problema é que as coordenadas estão nas bordas superiores esquerdas da janela do navegador. gostaria de medir as coordenadas do clipe Em vez disso, gostaria de medir as coordenadas do clipe no canto superior esquerdo do Canvas. Então, quando clicamos aqui no canto superior esquerdo da tela, obtemos x e y zero-zero. Para isso, podemos usar uma propriedade diferente nesse objeto de evento gerado automaticamente chamada offset x, que nos dará a coordenada horizontal do clique no nó de destino. No nosso caso, o nó alvo, o alvo do clique, é uma espécie de elemento. Agora você pode ver os valores ficarem muito próximos zero quando eu clico perto da borda do Canvas. Eu também poderia adicionar o ouvinte de eventos ao próprio elemento de tela, em vez de todo o objeto da janela do navegador. Se eu clicar mais perto do canto superior esquerdo, obteremos valores próximos a 00. Perfeito. Provavelmente faria sentido se eu usasse essa propriedade dot Canvas aqui da linha 31, já que estamos dentro de uma classe e temos a referência ao Canvas disponível aqui. Agora estamos obtendo a coordenada do clique medida na distância em pixels do canto superior esquerdo do Canvas. Mesmo quando redimensionamos a janela do navegador, isso está funcionando bem. Quero que você salve as coordenadas cíclicas dentro da propriedade do mouse do nosso cliente para que elas estejam disponíveis para outros objetos em nossa base de código, como o player dentro do ouvinte de eventos do mouse. Eu pego essa propriedade do ponto x do mouse do cão da linha 36 e a defino igual ao deslocamento do ponto E x. Esse ponto y do mouse será e ponto deslocado. Por quê? Eu crio outro log do console e examinarei essas propriedades do mouse recém-atualizadas. Quando clico no Canvas, recebemos um erro que diz que não é possível definir propriedades em um determinado x indefinido online para D3, está me dizendo que não consigo definir propriedade x em algo indefinido. Por algum motivo, esse mouse pontilhado é indefinido quando acessado de dentro do Event Listener. É porque quando essa função de retorno de chamada no ouvinte de eventos é executada, ela esqueceu que foi originalmente definida dentro desse construtor de classes de jogo. Ele esquece o que essa palavra-chave significa. Espera-se que isso faça com que o ouvinte do evento se lembre de onde ele foi definido pela primeira vez, onde está no escopo léxico de nossa base de código. Em vez disso, podemos simplesmente usar a função de seta ES6 aqui. Uma das características especiais das funções de seta do ES6 é que elas herdaram automaticamente a referência a essa palavra-chave do escopo principal e todas elas são funções. Lembre-se de onde, na base de código , eles foram originalmente declarados lexicamente. E eles ajustam a palavra-chave da mesa para apontar para o objeto correto, para o objeto principal. Agora, esse ponto x do mouse e esse visual ponto y do mouse são atualizados corretamente para os novos valores, disponibilizando as coordenadas atuais do mouse em toda a nossa base de código sempre que necessário. Posteriormente, excluo os registros do console. Quando o evento de mouse para baixo acontece. Eu disse que o preço era da linha 38 até o valor verdadeiro. Eu copiei esse ouvinte do evento. Este será para o evento Mouse Up. Quando o botão do mouse for solto, tudo aqui permanecerá o mesmo e voltará a ser falso. Eu também crio um ouvinte de eventos para eventos de movimento do mouse. Vamos consultar para verificar. Sim, isso está funcionando. Vamos fazer o jogador se mover. 6. Fazer o jogador se mover: Crie um método personalizado que eu chamo de update. Lá dentro, eu disse colisão x da linha 14 até a posição atual do mouse X. E a colisão y será a posição Y atual do mouse, assim. Para executar esse código, na verdade precisamos chamar o método de atualização. Vou chamá-lo de dentro de renderização aqui embaixo. Eu excluo esse log do console. Se quisermos ver algum movimento, precisamos chamar render repetidamente. Então, vamos colocá-lo dentro do loop de animação aqui. Eu chamo o Request Animation Frame embutido de um método que fica no objeto da janela do navegador, mas também podemos chamá-lo diretamente assim. Se quisermos. Eu passo, animo o nome de sua função principal para criar um loop de animação infinito. Agora eu preciso chamar animate para realmente iniciar a animação. Quando eu passo o mouse sobre nós, obtemos trilhas. Eu só queria ver os quadros de animação atuais. Então, entre cada laço, eu uso o método retangular transparente embutido para limpar a tinta antiga. Eu queria limpar toda a área da tela da coordenada zero até a largura e a altura da tela. Agora, o jogador coloca o mouse enquanto movemos pela cannabis, perfeito. Eu queria criar uma linha entre o mouse e o jogador para mostrar claramente a direção na qual o jogador se moverá. Dentro do método de sorteio na classe do jogador, iniciamos uma nova forma de Colin para começar o banho. Move to method definirá as coordenadas iniciais x e y da linha. Nesse caso, eu queria que a linha começasse a partir das coordenadas do objeto do jogador. Métodos de linha dois, definiremos o final nas coordenadas x e y da linha. Nesse caso, serão as coordenadas x e y do mouse. Em seguida, chamamos o traço para realmente traçar a linha. Isso funciona, mas como o jogador sempre consegue alcançar o cursor do mouse tão rápido, mal conseguimos ver a linha. Vamos dar ao jogador velocidade, velocidade X velocidade horizontal. Inicialmente, eu o configurei para velocidade zero. Por que a velocidade vertical também a definiu inicialmente como zero. Dentro do método de atualização, calcularemos a velocidade x. Primeiro, eu a defino para um pixel codificado por quadro de animação. E eu aumento a exposição dos jogadores pela velocidade horizontal. Isso funcionou. Eu também faço isso para a posição vertical. Existem duas maneiras de fazer o jogador seguir o mouse. Uma maneira seria simplesmente fazer a diferença entre a posição atual do mouse e a posição do jogador no eixo x horizontal. E defina essa diferença como velocidade horizontal. E também fazemos isso para movimentos verticais. Agora, a posição do jogador está corrigindo a diferença em toda a distância. Então, isso torna o movimento instantâneo. E se eu fizer com que ele se mova apenas um vigésimo quinto da diferença entre posição do jogador e do mouse para o quadro de animação na horizontal. E também verticalmente. Eu crio uma classe de propriedades para dx, distância entre o mouse e o jogador horizontalmente e distância vertical. Eu substituo esses valores aqui. É mais fácil lê-lo dessa maneira. Não quero que o jogador acompanhe o tempo todo enquanto passamos o mouse sobre o Canvas. Só quero quando clicamos em algum lugar ou quando pressionamos o botão do mouse e nos movemos. Dentro do ouvinte de eventos de movimento do mouse, digo que apenas atualize a posição x e y. Se o mouse for pressionado. Agora eu posso clicar para fazer o jogador se mover para aquele local, ou eu posso arrastar esse ponto enquanto mantenho o mouse pressionado. Perfeito. O problema com essa técnica é que a velocidade não é constante. Move-se muito rápido no início, porque um vigésimo da distância é princípio, uma grande parte quando estão distantes. Mas à medida que se aproximam, um vigésimo dessa distância se torna cada vez menor a quantidade de pixels a serem percorridos por quadro de animação. Você pode querer essa emoção específica para o seu projeto, mas para o jogo que estamos construindo hoje, eu queria que o jogador se movesse a uma velocidade constante. Teremos que usar a segunda técnica para esse método de atualização de insights. Eu calculei a distância. Já temos dx, a distância entre o mouse e o player na horizontal. Também temos d, a distância entre o mouse e o jogador na vertical. Queremos calcular a distância entre esses dois pontos. Podemos fazer isso calculando a hipotenusa, o lado mais longo desse triângulo retângulo retângulo imaginário. Podemos usar a fórmula do teorema de Pitágoras ou, em JavaScript, temos esse método matemático de hipotenusa de pontos embutido. Esse método calculará o comprimento do lado mais longo para nós se o passarmos para os outros lados do triângulo como argumentos Lembre-se de que ele espera d, dy e dx segundos, que pode ser um pouco inesperado se você nunca viu isso antes. velocidade horizontal é a relação entre dx, distância horizontal entre o mouse e o jogador e a distância real. Velocidade do sanduíche. Por quê? Será a razão entre a distância no eixo y vertical e a distância real entre os dois pontos. Como backup, quando alguns desses valores são indefinidos no início, dizemos ou zero, assim, somos divididos em distância horizontal e vertical, esses lados pela distância real representada pelo lado mais longo de um triângulo retângulo. Dx e DY são sempre um número menor do que a distância porque a distância é hipotenusa, o lado mais longo. Por esse motivo, os valores que obtemos como velocidade x e velocidade y estarão em algum lugar de 0 a 1. Isso nos dará a direção correta do movimento a uma velocidade constante. Há muito mais a ser dito sobre essa técnica. Mas, por enquanto, isso é tudo que precisamos saber. Vou voltar a isso. Agora. O jogador está se movendo a uma velocidade constante em direção ao mouse. Eu posso ter um modificador de velocidade. Eu o defino como cinco, por exemplo, I. Use-o aqui embaixo e multiplico a velocidade x pela velocidade. Por que, com esse modificador, depois de adicionarmos o modificador de velocidade, o círculo do jogador nunca mais ficará parado. Ele estará balançando para frente e para trás, neste caso em 50 pixels, porque o modificador de velocidade o empurra muito nas duas direções. Posso corrigir isso dizendo que só mova o jogador quando a distância entre o mouse e o jogador for maior que o modificador de velocidade. Else disse velocidade x a zero e velocidade y a zero também. Isso funciona perfeitamente. Então, abordamos uma técnica simples e outra avançada para fazer o jogador se mover em direção ao mouse. Agora é hora de adicionar obstáculos sólidos, aleatórios e não sobrepostos. 7. Criando obstáculos: Eu crio uma classe que chamo de obstáculo. construtor espera o jogo como argumento. E por dentro eu converti essa referência em uma propriedade de classe, a mesma de antes. Ele apontará para o objeto principal do jogo. E precisávamos disso porque, por meio dessa referência, temos acesso à largura e altura do jogo, posições do mouse e algumas outras propriedades. Seremos adicionados posteriormente. Teremos acesso a todos esses valores de dentro da classe de obstáculos por meio da referência.name da linha 54. Como expliquei antes, os objetos em nosso jogo, teremos uma caixa circular aquecida por colisão e uma planilha retangular separada. Por esse motivo, chamarei essas propriedades com nomes muito descritivos para garantir que fique bem claro o que acontece quando estamos movendo e animando tudo mais tarde. Colisão x, o ponto central do círculo de colisão de cada obstáculo, será um valor aleatório entre zero e a largura do jogo. Essa largura vem da linha 62 aqui. E estamos acessando isso por meio dessa referência de jogo de pontos que criamos na linha 54. Também precisaremos de uma colisão. Por que o ponto central vertical do círculo da área de colisão. Será um valor aleatório entre zero e a altura do jogo. Esse valor do raio de colisão será 60. Também precisaremos desenhar um método que espere contextos como argumento. Quero que o círculo que representa a área da caixa de sucesso de cada obstáculo tenha a mesma aparência do círculo que representa o jogador. Então eu pego o código do desenho daqui de cima, só para o círculo. Então, esse bloco de código, eu copio e colo aqui embaixo. Esse código funcionará aqui porque a mesma do jogador que atribuímos aos propriedade do jogador que atribuímos aos nossos obstáculos é chamada de colisão x, colisão e raio de colisão. Também precisaremos do mesmo nome em todas essas propriedades entre diferentes tipos de objetos. Caso desejemos ter uma função de detecção de colisão reutilizável. Eu vou te mostrar como usar isso mais tarde. É simples. De qualquer forma, aqui temos um código para desenhar um círculo com um raio de 60 pixels com 50% de opacidade, preenchimento branco e branco, totalmente visível, traçado de opacidade total. Essa classe de obstáculos aqui é um modelo. Nós o usaremos para criar objetos de obstáculos individuais. A lógica real para criar e gerenciar esses objetos estará aqui dentro da classe principal do jogo, que é o cérebro principal de nossa base de código. Eu crio uma propriedade chamada “obstáculos de pontos”. Será uma matriz que contém todos os objetos de obstáculos atualmente ativos. Inicialmente, ele começará como uma matriz vazia, o número de obstáculos será, por exemplo, cinco. Eu crio um método personalizado em nossa classe de jogo, chamo, por exemplo , em TI, inicializar seu trabalho, por enquanto, será criar cinco objetos de obstáculos aleatórios e colocá-los dentro da matriz de obstáculos que acabamos de definir. No interior, eu crio um loop for. Ele funcionará cinco vezes porque dissemos número de obstáculos para cinco na linha 76. Cada vez que ele é executado, ele pega essa matriz de obstáculos de pontos da linha 77 e, nela, chama o método embutido de push de matriz para empurrar o método como um ou mais elementos até o final de uma matriz e retorna o novo comprimento da matriz. Vou superar um novo obstáculo como esse. A nova palavra-chave procurará uma classe com o nome obstáculo e acionará seu construtor de classes online 53 Eu posso ver que o construtor de classes de obstáculos espera o jogo como argumento. Aqui embaixo, o método init fica dentro dessa classe de jogo, então eu passo a palavra-chave, que aqui representa todo o objeto do jogo com todas as suas propriedades e métodos associados, disponibilizando todos eles de dentro da classe de obstáculos. Agora, no jogo de registro do console, posso ver que a matriz de obstáculos está completamente vazia. Para preenchê-lo, tudo o que preciso fazer é chamado de método init. Nós apenas escrevemos assim. Agora eu posso ver que a matriz contém cinco objetos de obstáculo. Eu verifico duas vezes para ter certeza de que todas as propriedades têm valores. Se você ver indefinido em qualquer um deles, isso significa que há um problema em sua base de código. Tudo está bem aqui. Assim como eu estou desenhando uma atualização e o jogador de dentro do método de renderização do jogo aqui, eu gostaria de desenhar todos os cinco objetos de obstáculo no Canvas. Eu pego a matriz de obstáculos de -77. Já sabemos que ele contém cinco objetos e que cada um desses objetos foi criado usando nossa classe de obstáculos personalizada de I'm 52. Todos eles têm acesso a esse método de desenho que definimos na linha 59. Então, aqui dentro da renderização, eu pego essa matriz de obstáculos, chamo matriz embutida para cada método, o método forEach executa uma função fornecida uma vez para cada elemento da matriz. Primeiro, precisamos definir um nome de variável que será usado nesse método forEach para se referir a objetos individuais nessa matriz. Eu chamarei cada objeto de obstáculo. Portanto, para cada objeto de obstáculo na matriz de obstáculos, chamo o método de desenho associado de 1959 online 59, posso ver que ele espera uma referência ao contexto como argumento para especificar que tipo de elemento Us queremos nos basear. Eu simplesmente transmito esse contexto que foi passado para o método de renderização principal. Bom. Estamos desenhando um jogador e 12345 obstáculos posicionados aleatoriamente. Eu subo até aqui e faço com que os obstáculos sejam um pouco maiores. Toda vez que eu atualizo a janela do navegador, eles são posicionados aleatoriamente em algum lugar dentro da área da tela , porque é assim que definimos sua posição nas linhas 55,56. E se eu quisesse ter certeza de que os obstáculos nunca se sobreponham desse jeito e talvez ir ainda mais longe, já que esses serão obstáculos sólidos pelos quais o jogador não conseguirá superar e terá que contorná-los. Eu também gostaria que houvesse um espaçamento mínimo entre eles e também entre as bordas da área do jogo. Só para garantir que todas as criaturas que logo estarão rastejando até aqui não fiquem presas e possam, eventualmente contornar automaticamente cada obstáculo. Na verdade, é mais fácil implementar tudo isso , do que você pode pensar, mas temos que seguir isso passo a passo e explicar alguns truques podemos usar aqui para conseguir isso. 8. obstáculos não sobrepostos: Dentro do método init, estamos simplesmente adicionando cinco obstáculos posicionados aleatoriamente. Certo? Agora. Eu tenho que deletar isso. Precisaremos que essa estrutura seja chamada de forma um pouco diferente aqui. Então, primeiro, eu queria ter certeza de que os obstáculos não se tocassem, que eles não se sobreponham assim. Também podemos ajustar o número de obstáculos para que seja o número máximo de círculos possíveis que cabem em uma determinada área sem que dois deles se sobreponham. Às vezes chamamos isso de embalagem circular. Então, vamos escrever um círculo muito simples no algoritmo aqui. Vou usar a técnica básica em que você tenta jogar círculos em posições aleatórias muitas vezes. E somente aqueles que não colidirem com círculos já existentes serão realmente transformados em objetos de obstáculo e desenhados. Isso também é chamado de algoritmo de força bruta. Não é muito inteligente , apenas tenta várias e muitas vezes. Vou criar uma variável de lead chamada tentativas. Será minha medida de segurança. Contaremos quantas vezes tentamos desenhar um círculo e desistiremos depois de um certo número de tentativas. A suposição é que já deve ter havido oportunidades suficientes para colocar os obstáculos. Vou usar um loop de tempo. Você tem que ter cuidado com este. Se você criar um loop de tempo infinito, você diminuirá a velocidade do seu navegador e precisará reiniciá-lo. Computadores muito antigos podem até congelar se você usar o while loop Rome; novos navegadores geralmente podem lidar com isso. Meu objetivo aqui é colocar círculos aleatoriamente repetidamente. E antes de realmente transformarmos esse círculo em objeto de obstáculo, verificamos se ele se sobrepõe aos círculos de existência. Se não se sobrepor, adicionamos à matriz de obstáculos. Eu quero que esse circuito inteiro funcione , desde que o comprimento da matriz de obstáculos seja menor que, número de obstáculos, menor que cinco. Definimos essa matriz aqui, e o número de obstáculos foi definido aqui. Como backup, também defini uma condição secundária : continue executando esse loop enquanto as tentativas forem inferiores a 500. Isso é importante porque se eu dissesse raio de um obstáculo é um número muito grande, ou definisse que o número de obstáculos fosse tão grande que eles não pudessem caber fisicamente na área disponível, obteríamos um loop infinito. Mas quando a condição secundária for JavaScript, tentaremos apenas 500 vezes. E se a essa altura eles não conseguissem encontrar um lugar para todos os obstáculos, eles desistirão. Acho que 500 tentativas são mais do que suficientes. Toda vez que o circuito é executado, precisamos aumentar em uma as tentativas para que nosso plano de backup de segurança funcione. Toda vez que esse loop é executado, criamos um objeto temporário que eu chamo, por exemplo, obstáculo de teste. Será igual ao novo obstáculo e eu passo um jogo, essa palavra-chave como argumento, como fizemos antes. Vamos registrar o console desse obstáculo de teste. Bom. Temos 500 obstáculos de teste no console. Agora, você pode ver que eles têm propriedades de colisão, colisão y e raio de colisão como deveriam. Meu objetivo agora é pegar esse objeto de obstáculo de teste temporário e compará-lo com todos os outros obstáculos na matriz de obstáculos. É claro que, a princípio, essa matriz está vazia, então o primeiro obstáculo sempre deve ser colocado sem problemas. O segundo obstáculo do teste se comparará com o primeiro que já está na matriz, e assim por diante. Então, para cada obstáculo, matriz de obstáculos, vou executar uma fórmula de detecção de colisão circular. detecção de colisão circular em JavaScript é bem simples. Basicamente, precisamos calcular a distância entre os dois pontos centrais desses dois círculos. Em seguida, comparamos a distância entre dois pontos centrais com a soma dos raios. Se a distância for menor que o raio do círculo um mais o raio do círculo também, eles se sobrepõem. Se for exatamente o mesmo, os círculos estão se tocando. A distância é maior do que a soma dos raios. Não há colisão. Já fizemos isso ao medir a distância entre o jogador e o mouse. Desta vez, os dois pontos que queremos medir a distância entre eles são o ponto central do círculo de obstáculos. E o ponto central dos obstáculos também circula. Então, novamente, estamos criando esse triângulo retângulo imaginário em que dx é a diferença entre dois pontos horizontalmente. D é a diferença entre os dois pontos verticalmente. E a distância real é a hipotenusa desse triângulo. Então, aqui usamos a fórmula do teorema de Pitágoras ou um método matemático de hipotenusa de pontos embutido, passando um DY primeiro e o segundo. Agora sabemos qual é a distância entre os dois pontos centrais. soma da ADI é o raio do círculo um, neste caso, o raio do obstáculo de teste. E o segundo é o raio de qualquer objeto de obstáculo dentro da matriz de obstáculos, atualmente estamos pedalando e terminando. Como dissemos, se a distância for menor que a soma dos raios, como farei isso? Fora do método for each, eu crio uma variável de comprimento de bandeira que chamo de sobreposição e, inicialmente, a defino como false. A distância é menor que a soma dos raios. Definimos sobreposição como verdadeira porque a colisão foi detectada fora do para cada método. Se a sobreposição ainda for falsa depois de criarmos o obstáculo de mesa e depois compará-lo usando a fórmula de detecção de colisão com todos os outros obstáculos existentes na matriz. Se não colidir com nenhum deles. E a variável de sobreposição ainda é falsa. Depois de todas essas verificações, pegamos a matriz de obstáculos e empurraremos esse obstáculo de teste que passou por nossas verificações para dentro da matriz. Agora, quando eu atualizar o jogo, cinco obstáculos serão posicionados aleatoriamente e não se sobreporão porque aqueles que se sobrepõem são descartados. E somente círculos não sobrepostos são usados. Como eu tenho minha medida de segurança aqui na linha 10008, e sempre paramos esse ciclo quando atingimos 500 tentativas, eu posso realmente ir até aqui e definir o número de obstáculos para um grande número que eu sei que nunca se encaixará em nosso código. Vamos colocar o maior número possível de obstáculos e então ele deixará de tentar. Sabemos que isso está funcionando porque, se eu continuar atualizando meu projeto repetidamente, nunca veremos círculos sobrepostos. Quero dizer, nenhum obstáculo se sobrepõe um ao outro. O jogador pode se sobrepor neste momento com obstáculos. Não nos importamos com isso agora. Eu disse uma série de obstáculos para dez. 9. Imagens aleatoriamente de uma folha de sprite: Agora, deixe-me mostrar como anexaremos imagens às caixas de impacto de colisão circular e como posicionar a imagem em relação à caixa principal para que ela faça sentido visual e crie ilusão de que não se trata de uma tela plana, mas de um ambiente tridimensional onde o jogador pode realmente contornar esses obstáculos. Você pode baixar todos os projetos dos ativos na seção de recursos abaixo. Em index.HTML, eu crio outro elemento de imagem com uma ID de obstáculos e a fonte será obstaculos dot PNG. Essa imagem é uma folha de sprite. Vamos colocar aleatoriamente uma dessas molduras para cada objeto de obstáculo. Eu realmente não quero desenhar o elemento real da imagem, então eu o escondo com CSS dentro do construtor de classes de obstáculos, eu crio uma nova propriedade, chamo essa imagem de ponto. Eu apontei para aquela planilha de obstáculos usando get element by ID como esta. Vamos definir o número de obstáculos para um por enquanto. Dentro do método de desenho na classe de obstáculos, chamo de método de imagem de desenho em tela embutido. Esse método precisa de pelo menos três argumentos, a imagem que queremos desenhar. Então, essa imagem pontilhada da linha 58 e as coordenadas x e y, onde desenhá-la? Vou desenhá-lo nesta colisão de pontos x e nesta colisão de pontos. Por quê? Ao fazer isso, vamos simplesmente desenhar toda a folha de sprite da imagem. E o canto superior esquerdo desta planilha começará ponto central do círculo de obstáculos, porque é assim que, por padrão, imagens e círculos são desenhados na tela HTML. Se eu atualizar o projeto ou um novo obstáculo for posicionado aleatoriamente em algum lugar do Canvas, criei esta planilha para você. Então, eu sei que os quadros individuais têm 250 pixels de largura. Eu salvo esse valor, pois o sprite com altura variável do sprite também será de 250 pixels. Se você estiver usando uma planilha diferente, poderá obter a largura dividindo a largura da planilha inteira pelo número de colunas. E a altura é a altura da folha de sprite dividida pelo número de linhas, caso queiramos adicionar escala. Mais tarde, também criarei propriedades independentes de largura e altura. Por enquanto, serão iguais à largura e altura do Sprite porque eu dimensionei as molduras do sprite exatamente do mesmo tamanho que eu quero que elas sejam desenhadas no jogo. A imagem Draw também pode aceitar quarto e o quinto argumentos opcionais definidos na largura e altura. A imagem inteira será comprimida ou esticada até a área que definimos por esses valores. Ficará assim. O que eu realmente quero fazer é pegar um desses 12 obstáculos e desenhar apenas aquele do tamanho de 250 vezes 250 pixels. Para isso, preciso usar a versão mais longa do método de desenho de imagem que espera nove argumentos. Esses nove argumentos são a imagem que queríamos desenhar, fonte x, y, fonte com a altura da fonte da área que queremos recortar da imagem de origem. E destino x, destino. Por que destino com e altura do destino? Para definir onde estamos no destino, quero colocar esse pedaço de imagem recortado. Então, se eu passar um zero como fonte x e zero como altura da fonte. Sprite com altura de sprite como essa como largura e altura da fonte, preciso soletrar corretamente. Agora estamos desenhando o quadro superior esquerdo em nossa planilha. Como eu disse antes, vou definir posições x e y separadas para a planilha. Há várias maneiras diferentes de fazer isso. Eu posso simplesmente posicionar a imagem diretamente em cima da colisão x, que é o ponto central do círculo de colisão, menos a largura da imagem vezes 0,5. Isso centralizará a imagem horizontalmente exatamente sobre o círculo de colisão. Para realmente aplicar isso, preciso usar o sprite X como propriedade de destino x passada para desenhar o método de imagem aqui, tenha cuidado ao passar argumentos para o método de desenho de imagem. A ordem na qual você passa esses argumentos é muito importante. Ok, se eu atualizar a página, posso ver que ela foi centralizada corretamente na horizontal. Eu faço a mesma coisa com o Sprite y e o uso como destino. Por que a propriedade passou para o método de desenho de imagem. Agora, a planilha está diretamente no topo do círculo de colisão. Eu disse raio de colisão para um valor menor. Quero que essa pequena área de colisão seja posicionada na base da planta onde está a pedra. Porque essa será a área sólida que tocará o chão pela qual os personagens do jogo terão que andar. Como nossos sprites definem o tamanho de 250 vezes 250 pixels, posso realmente usar um valor codificado aqui. Além disso, 40 o aumentarão. -40, -50 -60 -70. Sim, isso parece bom. Eu disse que o número de obstáculos é dez. 10. Regras de posicionamento: E se eu quisesse garantir que não apenas os obstáculos não se sobreponham, mas também que haja um espaço mínimo adicional de 100 pixels no meio. Então, eles estão mais uniformemente espaçados ao redor da área de jogo disponível, além de permitir espaço suficiente entre os obstáculos. Os personagens do jogo podem facilmente andar ao redor deles. Eu crio uma variável auxiliar que chamo, por exemplo , buffer de distância e a defino para 100 pixels assim. Em seguida, incluí o amortecedor de distância aqui em alguns raios para aplicar esse amortecedor entre os obstáculos quando os colocamos. Bom. Para ter certeza de que isso está funcionando, eu aumento o buffer de distância em 250 pixels. Isso deve tornar tudo ainda mais aparente. Sim, então é assim que podemos controlar facilmente o espaçamento dos obstáculos. Também quero ter certeza de que as imagens do sprite de obstáculos sejam inteiramente desenhadas dentro da área do jogo e não parcialmente escondidas atrás das bordas. Eu poderia ter feito esse construtor de classes de obstáculos de visão ao definir esses valores inicialmente. Ou também posso fazer isso aqui, já que não estamos desenhando muitos obstáculos e suas posições são calculadas apenas uma vez no primeiro carregamento da página. De qualquer forma, garanto que a borda esquerda da planilha de obstáculos seja maior que zero, para que não fique escondida atrás da borda esquerda do Canvas. Ao mesmo tempo, garanto que a borda direita não esteja oculta. Portanto, o sprite X deve ser menor que a largura da área do jogo menos a largura do obstáculo. Bom. Quando eu atualizo a página, posso ver horizontalmente que os obstáculos estão sempre totalmente visíveis. Para a posição vertical, quero verificar se na imagem, o ponto central do círculo de colisão maior que zero na vertical não será suficiente. Quero definir uma área reservada para essa arte de fundo. Não quero que obstáculos apareçam nesta área. Eu crio uma propriedade chamada margem superior. Acho que nesta área superior há cerca de 260 pixels de altura. Vamos conferir aqui. Sim, o 2.6D parece bom, porque eu queria ter certeza de que a base dos obstáculos não se sobreponha a essa área superior, mas não me importo se a parte superior das planilhas de obstáculos se sobreponha assim, porque parece que a planta de obstáculos está na frente do fundo para que possamos ver. Então está tudo bem. Também verificarei se o ponto central do círculo da área de conluio de obstáculos é menor que a altura da área do jogo. Eu quero algumas margens. Posso, por exemplo, criar uma variável auxiliar que seja igual ao raio de colisão do obstáculo de teste vezes dois. Eu substituo esse valor codificado por essa propriedade de margem superior pontilhada que definimos. Além disso, quero dar uma margem superior adicional para que os personagens e especialmente os inimigos possam se espremer entre os obstáculos e ganhar limites. Caminhando pelo campo de jogo horizontalmente da direita para a esquerda. Também vou contabilizar a margem da parte inferior da área do jogo para criar algum espaço lá. Escrevemos um código que coloca obstáculos automaticamente em nosso mundo de jogo. Esses obstáculos nunca se sobrepõem e suas áreas de correlação são colocadas para permitir espaço suficiente entre eles. Isso fará com que a próxima etapa seja muito mais fácil porque precisamos de inimigos e NPCs amigáveis para podermos contorná-los automaticamente usando uma inteligência artificial muito simples. Temos imagens diferentes para obstáculos, mas agora estamos desenhando apenas o primeiro quadro superior esquerdo na coordenada zero. Podemos recortar para diferentes áreas a partir da planilha de obstáculos, área de corte horizontal. Começaremos da posição que passamos como argumento fonte x para desenhar o método de imagem aqui, zero vezes sprite com esse quadro. Um. largura do sprite será essa moldura. Dois é esse 13, é esse um. De volta ao zero. Para selecionar de qual linha estamos recortando, usamos a fonte Por que argumento aqui? Novamente, multiplicamos o número da regra pela altura real das molduras brilhantes dos indivíduos, zero vezes a altura do sprite é esse único termo : altura do sprite é esse. Agora desenrolamos dois e não há linha três porque começamos da linha zero. As imagens são desenhadas e recortadas da parte superior. Então, em vez de codificar esses valores que estão atualmente definidos como 00, vamos transformá-los em propriedades de classe para maior clareza. E é um controle. Esse quadro de aparência x determinará qual coluna estamos em nossa planilha de obstáculos. Se eu fizer um número aleatório de 0 a 4, isso não funcionará. Não há coluna 1.74, por exemplo, precisamos de números inteiros, números sem os pontos decimais. Então, eu o envolvo em Math.floor para arredondar valor aleatório gerado por Math.random para o número inteiro inferior mais próximo. Esse código me dará zero ou um, ou dois ou três. Então, em uma de nossas colunas de sprite, quando multiplicamos esses números inteiros pela largura de um único quadro de sprite e passamos esse valor como argumento fonte para o método de desenho de imagem, estamos definindo a coordenada de corte horizontal. Isso funcionou perfeitamente. Eu farei o mesmo com o quadro y, que determinará a linha do sprite. Temos apenas três funções. Essa linha de código me dará números inteiros, zero ou um ou dois, correspondendo ao número de linhas que temos disponíveis em nossa planilha de obstáculos. Agora podemos substituir esse zero codificado por esse quadro de pontos y. Então, o método fonte Why argument but asked to draw image será esse quadro de pontos y vezes essa altura do sprite de pontos. Eu vou fazer isso em um segundo. Valores aleatórios no quadro x e no quadro por que combinados nos darão uma imagem aleatória desses 12 obstáculos disponíveis Cada objeto de obstáculo terá um quadro aleatório desta planilha atribuída a ele. Vou terminar isso um pouco mais tarde. 11. Física: No objeto do feixe principal, eu crio um método que chamo de verificação de colisão. Quero que esse seja um método utilitário reutilizável que pegue o objeto e o objeto B e os compare e verifique se estão colidindo ou não. Poderemos usar isso em toda a base de código sempre que for necessária a detecção de colisão entre dois círculos. Do jeito que estou construindo meu jogo, todos os personagens e objetos terão uma área de colisão circular, que será uma base sólida pela qual nada poderá passar e tudo reagirá e contornará tudo. Usando isso, também podemos mudar as coisas. Eu vou te mostrar a colisão entre dois círculos. Temos o círculo a e o círculo B. Aqui. Precisamos verificar dx primeiro, a distância entre o ponto central do círculo a e o ponto central do círculo B no eixo horizontal x. Esses métodos reutilizáveis funcionarão somente se todos os objetos envolvidos tiverem propriedades com a mesma convenção de nomenclatura. Portanto, garantiremos que nomeamos as posições x e y em cada objeto como colisão x e colisão por quê? Estou usando esses nomes de propriedades excessivamente descritivos para que fique bem claro quando as coordenadas x e y se relacionam com o círculo da área de colisão e quando se relacionam com as posições da folha de sprite da imagem. Este é um tutorial, então eu quero que as coisas sejam muito claras e fáceis de entender. Também precisaremos de um D, Por que a diferença entre o ponto central do círculo a e o ponto central do círculo B no eixo vertical y. Então, queremos saber a distância entre esses dois pontos centrais. Então, a hipotenusa, o lado mais longo desse triângulo retângulo imaginário, ângulo reto de 90 graus, está aqui. E esta é a distância que a adaga tem fórmula do teorema ou alternativamente, a matemática embutida seria o método da hipotenusa. E passamos primeiro o Dui e o x como segundo argumento. Para determinar se há ou não uma colisão, comparamos a distância entre esses dois pontos centrais com o raio do círculo a mais o raio do círculo B, vou salvar esse valor como uma variável personalizada, chamo, por exemplo, alguns dos raios. Então, se a distância for menor que a soma dos raios, sabemos que o circo colide. Se a distância for igual à soma ADI, os círculos estão se tocando. Se a distância for maior que a soma dos raios, sabemos que não há colisão. Essa função simplesmente retornará verdadeiro se houver colisão e falso se não houver colisão. Vamos usar nossa função personalizada de verificação de colisão. Aqui, dentro do método de atualização da classe do jogador, verificamos a colisão entre o jogador e os obstáculos. Temos um objeto para um jogador e vários objetos de obstáculo. Portanto, para comparar tudo, chamaremos cada um deles na matriz de obstáculos, que contém todos os objetos de obstáculos atualmente ativos. Vou chamar cada objeto na matriz com um nome de variável auxiliar, obstáculo e nosso método de colisão de verificação de log do console que acabamos de definir. Sabemos que ele espera o objeto circular a e o objeto B como argumentos para comparar a distância de seus pontos centrais com a soma dos raios. Então eu passo isso, que significa que esse jogador, objeto, círculo um e obstáculo que estamos atravessando atualmente com esse método forEach como círculo B. Lembre-se de que esse método Lembre-se de que de verificação de colisão reutilizável só pode comparar objetos que têm colisão x, colisão e raio de colisão propriedade de raio de colisão e colisão está definida em seu construtor de classe. Então, vou me certificar de manter as mesmas convenções de nomenclatura para todos os objetos no jogo que estamos construindo hoje. Conforme o jogador se move, estamos ficando falsos e verdadeiros no console. Parece que isso está funcionando. Na verdade, só o console registra a palavra colisão quando ocorre uma colisão entre o jogador e o obstáculo. Agora, é ainda mais fácil ver que nosso código está funcionando. Perfeito. 12. Método de detecção de colisão reutilizável: Dentro do método de renderização, desenharei os obstáculos primeiro, depois atrás e depois deles, para que sejam desenhados por cima. E se eu também quiser resolver nossas colisões? O que quero dizer é que, se eles colidirem com um obstáculo, não quero que ele seja capaz de atravessá-lo assim. Quero que o círculo do jogador seja empurrado um pixel para trás do círculo do obstáculo na direção que aponta diretamente para longe do ponto central do obstáculo. Essa coisa simples, vamos tornar os obstáculos sólidos. O jogador realmente deslizará pelos obstáculos e isso criará uma boa física. Deixa eu te mostrar. Já calculei tudo o que preciso para obter essa visão, nosso método personalizado de verificação de colisão, mas essa função atualmente retorna apenas verdadeiro ou falso. Preciso que esse método reutilizável retorne mais valores para que possamos usá-los dentro da classe player para calcular a resolução de colisão de funções vetoriais e os métodos em JavaScript podem retornar um valor como esse, mas também podem retornar uma matriz que contém vários valores. Quero retornar o status de colisão verdadeiro ou falso como o primeiro elemento no elemento da matriz com um índice zero. Também queremos retornar a distância que estamos calculando na linha 127. Também precisaremos de alguns raios da linha 128. E precisaremos de DX e DY das linhas 100.2526. Portanto, agora nosso método personalizado de verificação de colisão não apenas verifica se a colisão está acontecendo ou não, mas também nos fornece outros valores dos cálculos que aconteceram ao longo do caminho. É importante que nos lembremos da ordem em que somos retornados nesses valores. O elemento com um índice zero é o status da colisão verdadeiro ou falso? Elemento com um índice de um é a distância. Alguns dos raios são indexados a. Dx é o índice três e d y é indexado para. Vou apenas copiar a matriz que é retornada aqui. E eu comentei apenas para poder ver isso como uma referência auxiliar. Agora, quero pegar cada um desses valores e salvá-los como variáveis separadas para que possamos usá-los para calcular a resolução da colisão aqui e empurrar dois jogadores na direção correta , longe do obstáculo em que está atualmente colidindo e da largura Vou usar algo chamado atribuição de reestruturação. Vamos apenas escrevê-lo e eu explicarei quando vermos a coisa toda. Eu digo que a variável let é essa matriz. E é igual a verificar a colisão entre objeto desse jogador e o obstáculo que, para cada método, está passando por cima. Eu tenho que substituir essa primeira expressão por um nome de variável. Eu queria chamar isso, eu quero chamar isso de colisão. Será esse valor verdadeiro ou falso dependendo da distância entre o ponto central do círculo. Então, se isso for verdade, há colisão. Se isso for falso, não há colisão. Essa estrutura e sintaxe de atribuição é uma expressão JavaScript que possibilita descompactar valores de matrizes ou propriedades de objetos em variáveis distintas. Basicamente, dizendo aqui, crie cinco variáveis para mim. A primeira variável chamada colisão é a matriz retornada quando chamamos o método de verificação de colisão entre esse jogador e o índice de obstáculo zero. A variável de distância é aquela matriz de índice um, e assim por diante. Essa estrutura e atribuição fazem isso automaticamente nos bastidores. Ele cria essas cinco variáveis e as emparelha. Os valores que estão nesses índices na matriz retornados pelo método de colisão de verificação. Isso pode ser um pouco estranho se você nunca o viu antes do JavaScript É bom se familiarizar com essa estruturação. Frameworks modernos, use-os muito e, basicamente , pegue a matriz retornada pelo método de colisão de verificação. E estou atribuindo cada valor aos seus nomes de variáveis separados para que eu possa usá-los aqui. Então, aqui estamos dentro do método de atualização na classe do jogador. Estamos percorrendo a matriz de obstáculos comparando o jogador com cada objeto de obstáculo. Se houver colisão entre o jogador e o obstáculo, se a variável de colisão for verdadeira, consola o log AAA. Isso funciona. Eu queria criar um vetor, uma espécie de linha pequena, 0 a 1 pixels de comprimento. Essa linha apontará na direção em que queremos que o jogador seja empurrado para resolver a colisão circular, para garantir que a colisão e o jogador e obstáculo se repelam, fazendo com que o jogador deslize ao longo do raio do obstáculo em vez de passar diretamente por ele. vetor horizontal será a razão entre Dx, distância entre o jogador e o ponto central do obstáculo no eixo x horizontal e a distância real entre esses dois pontos que calculamos antes de usar o método de verificação de colisão. Porque dx sempre será menor que a distância, porque a distância é hipotenusa é sempre o lado mais longo do triângulo retângulo retângulo imaginário. A unidade x sempre será um valor 0-1, porque estamos dividindo um valor menor por um valor maior. unidade y será a razão entre Dui, a distância entre os pontos centrais no eixo y vertical e a distância real entre os dois pontos centrais. Novamente, será um valor em algum lugar de 0 a 1. Isso também pode ser valores negativos dependendo de como nossos objetos se posicionam em relação uns aos outros nos eixos horizontal e vertical. Então, na verdade, a unidade x e a unidade y serão um valor entre menos um e mais um. Se eu consola a unidade x e a unidade, por que podemos ver esses valores, a combinação desses dois valores adicionados à posição horizontal e vertical do jogador posição horizontal e vertical para cada quadro de animação fará com que ele se mova em uma determinada direção e certa velocidade para longe do ponto central do obstáculo. Eu faço isso pegando a exposição de colisão do jogador, o ponto central do círculo de colisão do jogador, para empurrá-lo para fora do raio do obstáculo com o qual ele está colidindo. Eu o movo horizontalmente para a posição do ponto central do círculo de obstáculos mais a soma dos raios do círculo do jogador e do círculo do obstáculo mais um pixel adicional fora dos termos, essa razão unitária x para dar a direção certa, longe do ponto central do obstáculo. Nós fazemos a mesma coisa verticalmente. ponto central do círculo de colisão do jogador será movido para a posição do centro de colisão do círculo do obstáculo mais a soma dos raios do obstáculo e do círculo do jogador mais unidade Y da cidade de um pixel para dar a direção correta do empurrão. Estou tentando explicar isso uma forma muito amigável para iniciantes, mas não se preocupe se ainda não estiver claro. Essa é uma técnica importante e toda vez que você a usar, você se sentirá cada vez mais familiarizado com esse casaco. Eventualmente, você verá como funciona. Tudo o que você precisa entender aqui é que esse código está empurrando o jogador um pixel fora do raio de colisão do obstáculo na direção ao ponto central. E é assim que você cria uma muito simples, mas muito eficaz simulação de física muito simples, mas muito eficaz, em seu jogo. Tente mover o jogador de um lado para o outro lado. Isso parece muito bom, não é? De repente, nossos círculos de obstáculos se transformaram em objetos sólidos e impossíveis. Muito bem, se você seguiu até aqui, esse é o truque principal que estamos usando hoje em nosso jogo de física. Eu ajusto o modificador de velocidade para um valor menor. Aprendemos como fazer o jogador se mover em direção um mouse ou em direção a um ponto específico em um espaço 2D e como fazer com que ele navegue automaticamente em torno de obstáculos sólidos. Essa é uma técnica poderosa e você pode fazer com ela mais do que imagina. Vamos explorar um pouco disso hoje. Espero que você esteja se divertindo. 13. 8 animação de sprite direcional: Eu preparei uma folha de sprite de jogador direcional de ajuda especial para esta aula. Você pode baixá-lo na seção de recursos abaixo. Incluirei algumas cores alternativas. Provavelmente o meu é azul demais para cogumelos na minha arte de jogo. Vou esconder isso com CSS aqui. Construtor interno da classe do jogador. Eu crio uma referência para essa imagem usando get element by ID e a salvo como essa propriedade de imagem de ponto. Dentro do nosso método de desenho personalizado, eu pego contextos e chamo de método de desenho de imagens de telas embutidas que já usamos antes. Já dissemos que o método de desenho de imagem precisa de pelo menos três argumentos. A imagem que queremos desenhar e x e y deveriam desenhá-la. Isso apenas desenhará a planilha inteira. Podemos passar a largura e a altura para comprimir toda a planilha na área especificada. Na verdade, não temos essas propriedades definidas. sprite com a largura de um único quadro será de 255 pixels. altura do sprite também é 255. Em seguida, criamos propriedades separadas de largura e altura para permitir o potencial se quisermos apresentá-lo posteriormente. Agora, estamos comprimindo a planilha inteira na área de um quadro de sprite. Você provavelmente já sabe que precisaremos da versão mais longa do método de desenho de imagem, onde adicionaremos a fonte x, y, largura e altura da fonte. Esses valores primeiro recortarão uma parte da imagem, no nosso caso, uma única moldura de sprite. Depois disso, desenhamos esse quadro, a posição definida pelos últimos quatro argumentos. Desenhar o quadro superior esquerdo na coordenada zero-zero é simples. Acabamos de fazer isso com a planilha de obstáculos, fonte x, y, z, zero para definir o início do corte do retângulo. E Sprite com altura do Sprite como argumentos de largura e altura de origem para definir seu tamanho. Agora vemos apenas um quadro. Vou calcular a posição da imagem da planilha em relação às coordenadas y de colisão e colisão do jogador. Ele encaixotava essas duas propriedades separadas apenas para maior clareza. Portanto, lembre-se de que essas propriedades definem o ponto central do círculo de colisões do jogador. Nessas duas propriedades, definiremos o canto superior esquerdo da imagem da moldura da planilha que estamos desenhando atualmente para representar o jogador. Sobre nós. As coordenadas esféricas r do ponto central, retângulo e coordenadas da imagem estão no canto superior esquerdo. E a imagem e o retângulo vão para a parte inferior direita, dependendo de sua largura e altura a partir daí. Temos que considerar isso ao escrever o código a seguir. Sprite X será posicionado em relação à área de colisão. Será a colisão x, o ponto central do círculo de colisão menos metade da largura do quadro do jogador. Assim. Preciso usar o sprite X dentro do sorteio como argumento de destino x aqui. Para que isso funcione, precisamos recalcular esse valor toda vez que uma colisão x é atualizada. Então, eu preciso colocar isso dentro do método de atualização aqui. E eu também faço isso para Sprite y, que será a colisão y menos a metade da altura do jogador. Eu posso excluí-lo aqui. E eu uso sprite. Por que a propriedade aqui como argumento Y de destino foi passada para o método de desenho de imagem. Agora está posicionado na parte superior. Na verdade, quero que esse círculo de colisão corresponda à pequena sombra no chão abaixo do jogador o mais próximo possível, porque esse é o ponto de contato que usaremos ao interagir com outros objetos em nosso jogo. Como o player tem um tamanho fixo de pixel, posso compensá-lo com um valor codificado. Se estivéssemos escalando nossos personagens neste jogo, eu usaria um valor relativo aqui. -100 move a imagem do jogador para cima. Isso está bem por enquanto. 14. Ângulos de animação: O mesmo que fizemos com a planilha de obstáculos. Eu queria navegar em nossa planilha trocando de quadro para quadro. A navegação horizontal é feita multiplicando o sprite por um inteiro representando a coluna no passado da planilha como argumento de origem aqui. Quando percorrermos isso, animaremos direções individuais, loops de animação individuais para alternar loops de animação individuais para entre as direções na planilha, da forma como nossa planilha específica está organizada hoje, temos que multiplicar a altura do sprite por um número inteiro representando a função do sprite, você pode ver uma linha, zero é jogador voltado para cima, longe de nós. Rho one está no canto superior direito ou o jogador está voltado para a direita? Três está no canto inferior direito, pois está voltado para baixo em direção à câmera. A linha cinco em nossa planilha é a face do jogador no canto inferior esquerdo, seis está voltada para a esquerda. Acho que você entendeu a ideia. Eu coloquei esses números inteiros nas propriedades da classe. Quadro x para navegação horizontal espalhada. Quadro. Por que, para vertical, substituo esses valores codificados por minhas novas variáveis e agora posso alterar o quadro que estamos recortando atualmente da planilha de sprite do player fornecendo valores diferentes para frame x e frame why? Eu queria mudar o quadro, por que a linha que estamos animando atualmente a partir da planilha de sprite, que determinará para onde o jogador está voltado. Eu quero que isso dependa do ângulo atual entre o mouse e o jogador na posição em que eles estão atualmente em relação um ao outro. Para isso, temos um método embutido chamado Math dot eta. E para Martha, atan2 retorna um ângulo em radianos entre o eixo x positivo e a linha projetada de zero zero em direção a um ponto específico. Vamos usá-lo para calcular o ângulo entre o jogador e o cursor do mouse. E com base nesse ângulo, selecionaremos qual linha da planilha queremos animar para que o jogador esteja sempre voltado na direção em que está se movendo. Em direção ao cursor do mouse. Precisaremos de DX e DY. Então eu os mudei para cá. Portanto, esses valores calcularam a distância entre o jogador e o cursor do mouse horizontal e verticalmente. Lembre-se de que faz diferença usar o mouse primeiro ou o jogador primeiro. Neste cálculo, eu já escrevi esse código aqui antes. Sem pensar, nós o usaríamos para isso, então talvez tivéssemos que nos ajustar um pouco a ele. Vou te mostrar exatamente o que quero dizer. Martha espera que dy, dy e dx sejam o segundo argumento. Vou consultar o ângulo que estamos calculando e posso ver que está mudando. E os valores são de -3,14 menos pi a mais 3,14 mais Pi. Isso se verifica porque sabemos que o círculo completo é dois pi, aproximadamente 6,28 rad, que se converte em 360 graus. Vou repetir esse método. Atan2 retorna um ângulo em radianos entre o eixo x positivo e o alinhamento projetado de zero a um ponto específico. Como estou usando o mouse primeiro e a posição do jogador em segundo lugar, ao calcular DX e DY, estou obtendo valores de um dado mestre e, para aprender, a posição atual do mouse representa 0,00 e posição do jogador é o ponto em que estamos projetando uma linha. Para fins de um bom visual, funcionaria muito melhor se o Player fosse os zeros estáticos, zeros, um importante, mas vou deixar como está. E a partir dos valores que estou obtendo no console, criarei esse gráfico com pontos de interrupção em radianos. Na verdade, foi fácil de fazer porque eu só precisava de um valor como âncora. E eu sabia que toda a área é de -3,14, dois mais 3,14. E temos oito direções de jogadores. Então, cada fatia foi 6,20 8/8. De qualquer forma, você não precisa necessariamente entender tudo isso, certo? Agora, quando tivermos o código completo, você poderá jogar com os valores que, esperançosamente, trarão mais clareza. Levei um tempo usando matemática , mas atan2 até que eu entendesse totalmente. Então, se esta é a primeira vez que você vê isso, não coloque muita pressão sobre si mesmo. Se eu pausar a tela, esse é o 0,00, essa é a linha projetada em direção a outro ponto. E os dados de Martha e dois nos dão um ângulo em radianos entre esse eixo x positivo e essa linha. Então, usando esse log do console para obter um ponto de ancoragem para que eu possa ver quais valores de ângulo estamos obtendo. Eu construí esse visual auxiliar, que agora usarei para trocar corretamente as linhas em nossa planilha para fazer com que o jogador sempre fique de frente para o mouse. Se o ângulo for menor que -1,17, defina o quadro y como zero. Vou copiar isso algumas vezes. -0,2, 39 é um quadro y um mais 0,39 é um quadro. luminosidade de Y a 1,17 é emoldurada. Y3, 1,96 é o quadro Y4. Vamos ver. Até agora, isso está funcionando muito bem. Acho que entendemos. Eu posso deletar o console. log 2.74 é o quadro cinco. Essa área é um pouco estranha, pois o círculo termina e começa. Devo dizer se o ângulo é menor que -2,74 ou se o ângulo é maior que mais 274. Eu quero usar o Frameworks six. Se o ângulo for menor que -1,96, quadro y é sete. Preste atenção aos colchetes aqui, menos n mais valores e menos que operadores. Se você estiver tendo algum comportamento inesperado, certifique-se de que todo esse código seja igual ao meu. É fácil cometer um pequeno erro aqui e quebrar seu código acidentalmente. Para que isso funcione em todas as direções, preciso ajustá-lo um pouco. Posso, por exemplo, pegar esses slides e colocá-los aqui, porque com a declaração else-if, importa qual deles é verificado primeiro. Às vezes, reduzi o modificador de velocidade do jogador para três para que possamos ver claramente como ele gira. Enquanto Destin. Agora podemos virar o jogador em todas as oito direções. Perfeito. Aprenderemos mais sobre animação de sprites mais tarde na aula. Por enquanto, estou feliz com isso. 15. Modo de depuração: Como este é um jogo de física em que colocamos áreas de colisão na base de cada objeto. Queremos poder alternar rapidamente entre a visão essas áreas de colisão são visíveis e invisíveis. Isso nos ajudará a ajustar os elementos de física e jogabilidade à medida que os desenvolvemos , ao mesmo tempo em que nos oferece uma maneira fácil de verificar como as mudanças que acabamos fazer ficarão visíveis para o jogador que não verá essas caixas. Quero criar um modo de depuração pressionando a letra D no teclado, vamos ativar e desativar todos os elementos auxiliares de colisão na classe principal do jogo Eu crio uma propriedade chamada essa para depurar. E inicialmente eu defini como verdadeiro aqui embaixo, onde colocamos nossos ouvintes do evento, eu crio outro. Vamos ouvir o evento chave de down. Vamos apenas o objeto de evento de registro do console. Quando seleciono o Canvas clicando nele e pressiono qualquer tecla do teclado, obtemos esse objeto de evento de teclado gerado automaticamente. Dentro, temos uma propriedade chamada chave. Você pode ver que eu pressiono a letra R, então a chave que foi precificada fica dentro da propriedade da chave E dot. Eu digo que se a chave de ponto E for D, defina a propriedade de depuração da linha 123 para seu valor oposto. Portanto, se for verdadeiro no momento, defina-o como falso. Se for falso, defina-o como verdadeiro. Dessa forma, apresentar a mesma letra de tecla D ativará e desativará o modo de depuração. Eu testei pelo teste de login ou depuração do console. Eu pressiono o d repetidamente. E no console, vejo que ele alterna entre verdadeiro e falso. Perfeito. Eu removo o registro do console, apareço dentro do método de desenho na classe de obstáculos. Eu digo que se esse cachorro veio para depurar é verdadeiro em uma lanchonete, desenhe o círculo da área de colisão. Agora eu posso pressionar a letra D, o show e escondê-los. Isso funciona bem. Eu quero fazer o mesmo com o jogador. Se o jogo de depuração for verdadeiro em uma lanchonete, desenhe o círculo de colisão e também a linha entre o jogador e o mouse. Nesse caso, a posição e o tamanho das caixas principais ainda não são perfeitos, mas temos toda a lógica estabelecida agora, ótimo trabalho. Posso diminuir o raio de colisão de cada obstáculo para melhor corresponder à parte em que ele toca o solo. Temos diferentes tipos de obstáculos aqui. O cogumelo e esta grande planta carnívora provavelmente devem ter círculos de colisão diferentes , o que pode ser feito facilmente. Mas, por enquanto, estou feliz com isso. Você pode ver que a fórmula de colisão escrevemos antes é tudo o que precisamos para dar ao jogador uma habilidade muito simples de encontrar caminhos. Ele simplesmente contornará obstáculos automaticamente. E como colocamos os obstáculos de uma forma que eles são sempre espaços intermediários, é improvável que o jogador fique preso. Também posso ver que estou obtendo apenas as primeiras quatro imagens de obstáculos, o que me lembra de ir até a linha 10008 e incluir o valor aleatório do quadro y como fonte. Por que argumentar no método de desenho de imagem de obstáculos. Agora estou recebendo aleatoriamente uma de todas as imagens do 12º obstáculo. Você pode jogar com isso e com a posição e o tamanho. Seu manual diz o contrário, se você quiser. Estou feliz com o que fizemos até agora. 16. Fronteiras do movimento do jogador: Eu queria ter certeza de que o jogador não conseguia andar tão alto a ponto de ficar acima dessa área de arte de fundo. Vamos criar alguns limites horizontais. Primeiro. Se o ponto central círculo de colisão do jogador for menor que a coordenada x zero mais o raio de colisão, defina-o como zero mais o raio de colisão. Portanto, quando a borda esquerda do círculo estiver tocando a borda esquerda da área da tela, não permita que ela vá mais à esquerda. Eu quero fazer a mesma coisa com a borda direita. Claro, podemos excluir esse zero mais aqui e aqui. Se o ponto central do círculo de colisão do jogador for maior que a largura do jogo menos o raio do círculo de colisão do jogador. Certifique-se de que não possa ir mais longe, certo? Bom. Limites verticais. Se a colisão y for menor que coordenada vertical zero mais a margem superior, definimos como sendo 260 pixels da parte superior mais o raio de colisão. Certifique-se de que o jogador não possa mais subir. Isso funciona bem. Sim, isso é o que eu queria. Novamente, podemos excluir zero mais aqui e aqui. Limite inferior. Isso é simples. Se a colisão y for maior que a altura do jogo menos o raio de colisão, certifique-se de que ela pare aí assim. Isso funciona se eu diminuir o raio de colisão do jogador do que o raio do obstáculo. Devido à margem que definimos ao posicionar os obstáculos, sempre haverá um lugar para jogador se espremer entre os obstáculos. As bordas da área do jogo. Não há. Então, aqui embaixo, eu aumento essa margem. Eles deveriam fazer isso. Você pode comparar seu código com código-fonte em andamento que incluirei para download na seção do projeto abaixo em vários pontos durante esta aula à medida que avançamos com nosso projeto. 17. FPS: Eu posso ver que a parte inferior da planilha está sendo cortada nas linhas inferiores. É porque a altura de um único quadro é, na verdade, 256 pixels. Agora está corrigido. No editor de código do Visual Studio, seleciono a opção Exibir quebra de palavra para transformá-la em uma quebra de código em outra linha, caso não caiba. Se for muito longo. Eu queria configurar o FBS para o jogo inteiro porque , em meus projetos anteriores, muitos de vocês mencionaram que os jogos rodam muito rápido em seu jogo em telas de alta taxa de atualização quadro de animação ou o método que estamos usando aqui se ajustará automaticamente à taxa de atualização da tela. Portanto, para telas normais, isso será em torno de 60 quadros por segundo. Mas em algumas das novas telas de jogos que as pessoas usam, essa velocidade seria o dobro, ou talvez não exatamente o dobro, mas muito mais rápida. Vamos calcular o tempo delta, a quantidade de milissegundos que passaram entre cada meta do quadro de animação da solicitação. E só permitiremos que o jogo sirva o próximo quadro de animação quando um número específico de milissegundos tiver passado. Podemos calcular o tempo delta aqui dentro de nossa função de animação personalizada. Primeiro, defini a última vez fora da função assim. Inicialmente, eu o configurei como zero. Essa variável sempre conterá uma referência da data e hora do loop de animação anterior para que possamos compará-la com a data e hora atual. E a diferença entre eles será o tempo delta. quadro de animação de solicitação tem dois recursos especiais. Como dissemos, ele tentará se ajustar automaticamente à taxa de atualização da tela, na maioria dos casos, 60 quadros por segundo. Ele também gerará automaticamente um carimbo de data/hora para nós que podemos usar. E ele passará esse timestamp como argumento para a função que chama. No nosso caso, anime. Imagine que está passando esse carimbo de data/hora aqui, assim. Automaticamente, é gerado automaticamente. Tudo o que precisamos fazer para usá-lo é atribuir um nome de variável aqui. Vou chamá-lo de carimbo de data/hora, escrito assim, tenha cuidado com as letras minúsculas e maiúsculas ao definir seus nomes de variáveis em JavaScript, isso importa. Vamos fazer um registro no console dessa variável temporária de carimbos gerada automaticamente, aquele quadro de animação de solicitação nos forneceu apenas para ver em qual formato ela é n. Você pode ver que ela nos dá milissegundos, já que a primeira animação foi chamada de 1 s é 1.000 milissegundos. Então, aqui eu posso literalmente ver que o jogo começou 9101112, 13 s atrás. Eu excluo esse log do console. Sabemos que temos acesso ao registro de data e hora atual, então vamos usá-lo para calcular a hora delta. Será a diferença entre o timestamp desse loop de animação e o timestamp do loop de animação anterior. tempo delta é o número de milissegundos que nosso computador levou para servir o próximo quadro de animação. Uma vez que usamos a última vez para calcular a hora delta, nós a atribuímos ao carimbo de data/hora atual. Dessa forma, o timestamp atual pode ser usado no próximo loop de animação como o carimbo de data/hora antigo. No primeiro loop de animação, última vez será zero, mas depois disso, ele sempre manterá o valor do timestamp do quadro de animação anterior para que possamos compará-lo com o valor do timestamp desse quadro de animação em execução no momento. E a diferença entre eles é o tempo delta. Então, vamos registrar o tempo delta no console para ver se funcionou. Meu tempo delta é de cerca de 16,6 milissegundos. Mil milissegundos divididos por 60 são 16,6. Então, isso se verifica. Eu me pergunto quantos de vocês têm o mesmo horário Delta e quantos de vocês têm um número diferente. Se você tiver uma tela de alta atualização, seu tempo delta será um número muito menor. Se você tem um computador antigo que está com dificuldades para animar nosso jogo, seu tempo delta pode ser muito maior se você tiver um segundo aqui, tempo delta nos comentários. Portanto, sabemos que se a maioria das pessoas obtiver valores iguais ou muito diferentes dos meus, isso me ajudará a otimizar melhor meus cursos futuros. Se eu rolar o console até o primeiro carimbo de data/hora, você pode ver que os dois primeiros valores do horário delta não são, não são um número. É porque o primeiro timestamp é indefinido porque somente no segundo loop, esse valor de timestamp é gerado automaticamente pelo quadro de animação da solicitação. O primeiro loop não é acionado pelo quadro de animação de solicitação, ele é acionado por essa linha. Então, no começo, quando dizemos que o tempo delta é indefinido menos zero, aqui não temos nenhum, nem um número. Ele se corrige automaticamente à medida que o loop é executado. Mas esses dois valores iniciais, não numéricos podem quebrar seu código. Isso depende do tempo delta, a menos que você o considere com algum tipo de declaração oral, por exemplo , a maneira mais fácil de corrigir isso é passar zero aqui como a data e hora do primeiro loop de animação. A partir do segundo loop, o valor se tornará o carimbo tau gerado automaticamente. Porque depois disso, o animate será chamado por solicitação de quadro de animação. Como você pode ver, obtemos números aqui e não há mais valores. Perfeito. Eu excluo esse log do console. Vamos usar o tempo delta para definir a taxa de quadros do nosso jogo. Precisaremos de algumas variáveis auxiliares. Fps, quadros por segundo serão, por exemplo, 20. O cronômetro contará repetidamente a partir de zero. Rumo a um valor específico. Quando atingir esse valor, ele acionará o próximo quadro de animação e voltará a zero. O intervalo será aquele valor do ponto de interrupção que, quando atingido, reiniciará o cronômetro. Serão mil milissegundos, 1 s dividido pelo FBS. Isso nos dará a quantidade de milissegundos necessária para atingir esse FBS específico. Gerenciaremos essa lógica de tratamento de quadros aqui dentro do método de renderização. Se o cronômetro for maior que o intervalo, faça alguma coisa. Ao mesmo tempo, continue aumentando o cronômetro pelo valor do tempo delta repetidamente. Quando o cronômetro acumulou tempo delta suficiente, milissegundos suficientes para que seu valor seja maior que o intervalo. Vamos animar o próximo quadro. Também redefiniremos o cronômetro para zeros para que ele possa contar novamente para a futura atualização do quadro. Vou pegar todo esse código e colocá-lo dentro da instrução if. Assim. Estamos usando o valor do tempo delta na linha 173. O valor do ponto será passado como um argumento para o método de renderização aqui. E dentro do loop de animação, estamos calculando o tempo delta aqui e o passaremos para renderizar assim. Ok, então algo está acontecendo. A razão pela qual tudo está piscando é que estamos excluindo a tinta antiga o tempo todo, mas apenas redesenhando nosso jogo quando o cronômetro atinge o intervalo. Eu tenho esse retângulo transparente daqui. Portanto, agora não estamos nem um pouco claros em tinta antiga. E tudo é animado a 20 quadros por segundo. E tudo está deixando rastros. E eu vou limpar a pintura antiga somente quando estivermos prontos para redesenhar o próximo jogo atualizado A-frame aqui. Portanto, contextualize o retângulo das coordenadas zero a essa largura de ponto, essa altura do ponto para limpar toda a área do jogo, uma otimização seria desenhar nosso jogo em vários elementos de cannabis e limpar apenas as partes de cannabis que realmente o atualizam. Dessa forma, não precisaríamos redesenhar tudo o tempo todo. Por enquanto, isso funcionará bem. Você deve conseguir ver uma diminuição na velocidade da animação do jogo porque estamos definindo o FPS aqui para 20. Para tornar isso ainda mais óbvio, talvez eu só queira animar cinco quadros por segundo. 30405060. Tenho certeza de que o FBS tem 60 quadros por segundo, mas na verdade não estamos animando nosso jogo 60 FPS porque toda vez que eu reinicio o cronômetro para zero, algum tempo delta restante não estou contabilizando algum tempo delta restante. Então, embora eu diga 60, o FPS real é um pouco menor. Eu posso descer e explicar o tempo delta restante. Mas talvez eu queira manter esse código leve. Talvez eu não queira fazer com que o JavaScript faça ainda mais cálculos repetidamente. Portanto, mantendo isso em mente, saberei que preciso definir o FBS para um valor um pouco maior aqui para realmente obter algo em torno de 60 FPS. Se eu definir isso como 70, acho que obteremos movimentos suaves o suficiente e não estamos fazendo o JavaScript calcular o delta tan restante, o que aumentaria um pouco a demanda de desempenho em nosso jogo. Acabei de pensar nisso agora, não tenho certeza de qual é a melhor solução. Vou deixar meu código assim por enquanto, mas acho que a solução certa aqui dependerá da preferência pessoal de todos. Então, três para discutir isso nos comentários. Considerarei seus comentários em meus projetos futuros, agora que sabemos como controlar a velocidade de animação do nosso jogo usando o tempo delta O jogo será executado em uma velocidade semelhante em todas as máquinas, mesmo para aqueles de nós que estão usando telas de alta taxa de atualização. 18. Curso de ovos: Eu queria adicionar ovos que pudessem ser empurrados para incluir ainda mais física em nosso jogo Esses x se transformarão em criaturas após um tempo específico ou tiverem passado. E o trabalho dos jogadores será proteger as criaturas que Hodge. O x pode ser empurrado pelos inimigos, mas eles não serão destruídos. O desafio para o jogador é que, quando eclodirem, os inimigos comerão os filhotes. Portanto, o trabalho dos jogadores em nosso jogo será posicionar o x, protegê-los ou afastar os inimigos do caminho das criaturas recém-nascidas. A larva que sai de cada ovo sempre tentará se arrastar para um local seguro e se esconder nos arbustos dentro da floresta de cogumelos no topo. Isso introduzirá muitas opções de jogadores e suas opções em nosso jogo Ao usar a física que implementamos Tive uma ideia para essa mecânica de jogo enquanto assistia a um documentário sobre a natureza em que filhotes de tartarugas nascem na praia e tentam chegar ao mar em busca de segurança. Em nosso jogo, controlamos a bola azul. Seu trabalho é proteger os filhotes empurrando x, lavas e inimigos ao redor. Este jogo tem tudo a ver com física e posicionamento. Eu terei uma classe personalizada que chamarei, por exemplo. construtor espera uma referência ao objeto principal do jogo, como de costume, para garantir que a classe Ec tenha acesso a muitas propriedades importantes mantidas no objeto do jogo. X fará parte da física do jogo. Portanto, preciso ter certeza de definir x e y separadas coordenadas x e y separadas para o ponto central do círculo de colisão. E para a folha de sprite. Vamos começar com a propriedade colisão x. Será um valor aleatório entre zero e a largura da área do jogo. A colisão y estará entre zero e a altura do jogo, assim, raio de colisão, por exemplo, 40 pixels. Essa imagem de carregamento será um documento, ponto a ponto, elemento por ID, e o ID está em index.HTML. Na verdade, eu tenho que criar esse elemento de imagem. Como sempre, as imagens podem ser baixadas na seção de recursos abaixo. Eu escondo isso com CSS aqui. Estou mantendo as mesmas convenções de nomenclatura em meus objetos. É uma boa prática. Portanto, a largura do sprite será 110 pixels e a altura do sprite será de 135. largura e a altura serão definidas com os mesmos valores. Em nosso jogo, cada objeto tem colisão x e colisão. Por que propriedades que representam o ponto central da área de colisão circulam, até mesmo jogadores e obstáculos têm as propriedades assim nomeadas para que possamos usar o método reutilizável de detecção de colisão para implementar nossa física em tudo. O mesmo vale para Sprite x e Sprite. Por que propriedades? Eles representam a posição do canto superior esquerdo a partir da qual a imagem do objeto será desenhada. sprite X da imagem será colisão x mais metade da largura da imagem. Para centralizar a imagem sobre o círculo de colisão horizontalmente. Sprite y será a colisão y mais metade da altura da imagem. Talvez tenhamos que ajustar isso um pouco mais tarde porque, verticalmente, queremos que a área de colisão esteja na base do pescoço, não no meio. Chegaremos a isso em breve. O método Draw espera contexto como argumento. Como sempre, chamamos de desenho de imagem e, desta vez, precisamos apenas de três argumentos. A imagem que queremos desenhar e as coordenadas x e y onde desenhá-la. Se estivéssemos escalando, também incluiríamos argumentos opcionais de largura e altura como este. Mas estou te dando todas as imagens no mesmo tamanho. Nós os estamos desenhando no jogo. Então, na verdade, não é necessário. Como acontece com todos os objetos do jogo, não estamos apenas desenhando a imagem representando o objeto, também desenhando um círculo de colisão se o modo de depuração estiver ativo, pois estamos mantendo as mesmas convenções de nomenclatura para propriedades em toda a nossa base de código, facilitando nossa vida. Eu posso simplesmente copiar todo esse bloco de código e usá-lo aqui. Então, estamos desenhando x MHz e se o modo de depuração estiver ativado, estamos desenhando a área de colisão. Sim, provavelmente é uma boa ideia colocar esse código em métodos reutilizáveis, já que estamos usando o mesmo código para desenhar círculos de colisão para tudo. Talvez eu faça isso mais tarde. Por enquanto, eu queria criar um método que adicionasse periodicamente um novo ovo ao nosso jogo. Na classe do jogo, teremos uma matriz que conterá todos os objetos de ovos atualmente ativos. Também terei outra propriedade chamada número de ovos. Ou um max x o descreve ainda melhor. Só adicionaremos novidades ao jogo até que o total seja menor ou igual ao valor máximo de x. 19. Adicionando periodicamente novos ovos: Dentro do método de renderização, aqui, lidaremos com a lógica de adicionar x periodicamente. Nós já fizemos isso. Fizemos um evento periódico nesta base de código em que usamos o tempo delta e acionamos um novo quadro de jogo somente quando um determinado valor de intervalo foi atingido. Na verdade, faremos a mesma coisa aqui. Precisaremos de algumas variáveis auxiliares para isso. cronômetro de ovos passará do valor do intervalo zero para x. Em seguida, ele adicionará um novo ovo e será reiniciado para que possa contar. De novo. Estamos operando com o tempo delta, então, milissegundos, eu quero adicionar novos ovos, digamos, a cada 500 milissegundos. Aqui embaixo, vou verificar se o cronômetro de ovos é maior que o intervalo x. Adicionamos um novo ovo chamando esse método da linha 220. Lá dentro, eu apenas pego a matriz de ovos, eu chamo de método embutido de push de matriz. Vou inserir uma nova instância de nossos clientes, por exemplo, uma classe. Como sempre, sabemos que o copo de ovo espera que o jogo seja uma discussão. Então eu passo essa palavra-chave porque estamos dentro desse objeto do jogo aqui. Portanto, se o cronômetro de ovos for maior que o intervalo x, chamamos de adição de ovo. Também redefiniremos o cronômetro para zeros que ele possa contar novamente até o próximo ovo. Caso contrário, continuamos aumentando o temporizador de ovos em tempo delta, que já estamos passando aqui para o método de renderização anterior. Eu registro esse ponto x para ver se os objetos estão sendo adicionados. Desculpe, isso apenas criará um crescimento infinito na matriz. Eu preciso criar uma condição adicional aqui, apenas adicionar um novo x, desde que o comprimento da matriz x seja menor que max x, isso é melhor. Eu inspeciono um dos objetos. Preciso ter certeza de que todas as propriedades têm valores. Se, por exemplo eu tivesse indefinido como coordenada x de colisão, meu x não seria desenhado no Canvas porque o JavaScript não saberia onde desenhá-lo. Eu vejo valores em tudo. Isso parece ótimo. Dentro do método de renderização, eu também gostaria de desenhar meu x. Eu apenas copio essa linha e ajustei para cada elemento em raio-x, vamos chamá-lo de exemplo. Vamos chamá-lo de método de sorteio. Perfeito. Agora estamos recebendo alguns recursos visuais que tornarão ainda mais fácil ajustar os detalhes e aprimorá-los. A primeira coisa que noto é que todos os ovos são adicionados quase instantaneamente, embora eu tenha dito que quero um ovo a cada 500 milissegundos. Se eu aumentar o intervalo x para 1 s aqui, posso ver que algo está errado com meu código. Eu sei que o problema deve estar dentro desse bloco de código. E é porque o cronômetro de ovos é mais do que um intervalo e só depois adicionar um novo ovo, precisamos usar esse operador de comparação aqui. Desculpe pelo erro de digitação. Você provavelmente já percebeu isso antes. Agora funciona. Recebemos um novo ovo adicionado a cada 1 s, desde que tenhamos menos de dez x. Também posso ver que as imagens estão posicionadas fora dos círculos de colisão. Eu mudo esse sinal de mais para menos aqui e também aqui. Agora está melhor. Quero ajustar a planilha em relação ao seu círculo de colisão. Mais 35 vai movê-lo para cima -35, vamos movê-lo para baixo -30. Eu queria combinar a parte inferior da imagem o mais próximo possível, mas também não quero que o círculo de colisão seja muito alto porque quero que o jogador consiga andar atrás do ovo sem empurrá-lo, tenha a ilusão de que nossa área de jogo está em 3D e que tem alguma profundidade, mesmo que na realidade seja apenas uma superfície plana. Faremos mais para impor isso em um momento. Agora, quero criar uma margem para garantir que o x apareça a certa distância mínima das bordas da área do jogo, para que possamos fazer com que o jogador entre o ovo e a borda o empurre para onde quisermos. Eu quero que a margem seja, digamos que os termos do raio de colisão em relação à posição inicial x do círculo de colisão sejam um valor aleatório começando da esquerda. A partir desse valor de margem, acabamos de definir e jogar com menos essa margem. Aqui embaixo eu defino o intervalo de 200 milissegundos. Eu quero 50 x. Só para ter uma ideia melhor de onde eles podem aparecer em nosso jogo. Temos a margem esquerda que eu queria, mas posso ver que o x está muito longe da borda direita. Então aqui eu aumento essa margem direita assim. Sim, estou feliz com isso. Agora os Dx estão sempre totalmente visíveis horizontalmente, com algum espaço extra à esquerda e à direita entre as idades da área do jogo. Verticalmente, farei algo semelhante. A posição aleatória precisa começar abaixo da área da margem superior que definimos anteriormente. Eu não quero nenhum x na frente desta arte de fundo. Do jeito que o Math.random funciona, eu apenas aumentei o alcance daqui para aqui. Também preciso reduzir o intervalo reduzindo a amplitude de valores aleatórios que obtemos. Portanto, altura do jogo menos margem superior. Agora x pode aparecer daqui até aqui. Vou reduzir o valor aleatório pela margem para dar a ele algum espaço na parte inferior, assim. Perfeito. Se você é iniciante, Math.random pode não ser intuitivo. Na verdade, é muito simples. Isso pode exigir um pouco de prática. Então, aqui estou dizendo Set collision. Por que usar um valor aleatório a partir da margem superior aqui? Porque vamos do topo e a faixa de valores aleatórios é a altura do jogo menos essa margem, menos essa outra margem. Então x pode aparecer em qualquer lugar entre aqui e aqui. Verticalmente. Eu defino o intervalo para 500 milissegundos e o máximo de x para ser, por exemplo, dez. Por enquanto, isso significa que um ovo aparecerá a cada meio segundo até que a contagem total seja dez. Este é um jogo de física, então queríamos que o jogador fosse capaz de empurrar os ovos para afastá-los do caminho dos inimigos ou posicioná-los estrategicamente para dar à larva que nascerá deles a melhor chance de sobrevivência neste jogo Eu escolho que x seja indestrutível. Os inimigos simplesmente empurrarão o x para fora do caminho se colidirem, mas a larva que eclode será comida se o inimigo a pegar. Portanto, posicionar e empurrar o x é muito importante. Vamos dar ao x um pouco de física. 20. Física do ovo: Eu forneço cada ovo e método de atualização interno, criaremos uma matriz auxiliar temporária chamada objeto de colisão. Isso conterá todos os objetos em nosso jogo com os quais x interagirá. Verificaremos a colisão entre cada ovo e esses objetos. Então, precisaremos de um jogador aqui, esse jogo de pontos, um jogador de pontos como esse. E, claro, precisamos de obstáculos sólidos aqui. Eu quero que todos esses elementos estejam no mesmo nível. Na matriz de objetos de colisão. Temos o objetivo do jogador aqui. E espalharemos a matriz de obstáculos na matriz objetos de colisão usando o operador de propagação. Assim. O operador de dispersão nos permite expandir rapidamente os elementos de uma matriz para outra matriz. Na verdade, vou chamar isso de objeto de colisão com gelo. Vou chamar cada um deles, para cada um desses objetos. Portanto, neste caso, para o jogador e todos os obstáculos individuais que temos aqui, quero usar esse método de verificação de colisão que definimos na linha 225. Se você se lembrar, ele usa o objeto a e o objeto B como argumentos. E ele retorna uma matriz que nos dá verdadeiro ou falso para a distância de colisão entre dois pontos centrais do círculo de colisões, alguns de seus raios e a distância horizontal e vertical entre os dois pontos centrais. Eu copio essa matriz. Eu colo isso aqui. E, novamente, usaremos essa estrutura para definir rapidamente essas cinco variáveis usando uma única linha de código Preciso de um nome de variável aqui, então chamarei a primeira colisão de verdadeira ou falsa, independentemente de colidir com o jogador ou com qualquer obstáculo. Fizemos isso antes. Eu quero que essas sejam variáveis principais e elas sejam iguais a esse jogo de pontos. Não verifique a colisão entre esse ovo porque estamos dentro do método de atualização na classe Ec e um dos objetos na matriz de objetos de colisões. Portanto, à medida que o método forEach é executado, estamos executando o método de verificação de colisão entre esse ovo e cada elemento nessa matriz. E estamos obtendo cinco variáveis que nos dão mais detalhes sobre a posição e a colisão desses dois objetos que estamos comparando. Se a colisão for detectada, na verdade usaremos todos esses valores para calcular até onde e em que direção queremos empurrar o ovo. direção horizontal do impulso será a razão entre o x, a distância horizontal entre os dois pontos centrais e a distância hipotenusa desse triângulo retângulo retângulo imaginário. Como dx é um local desse triângulo e a distância é hipotenusa, o lado mais longo, somos divididos em valor menor por um valor maior. Por causa dessa unidade, x será algo entre menos um e mais um. D x pode ser positivo ou negativo. Unidade y. direção de impulso vertical será a relação entre D e distância. Agora, vamos usar isso para mover o ovo. Estamos dentro de um bloco de código que só será executado se x estiver colidindo com o jogador ou com qualquer um dos obstáculos. Então, quando a colisão acontece, pegamos a colisão x do ovo e a empurramos um pixel para fora do raio do objeto. É colisão e largura. Portanto, colisão x do objeto, o ponto central, mais um pixel extra para mover o ovo para fora da área de colisão. Então, essa é a distância e a direção horizontal desse movimento serão definidas multiplicando-o pela unidade x, que, como dissemos, pode ser positiva ou negativa. Também precisamos mover a colisão e o ovo verticalmente. ponto central de colisão y do ovo será a colisão y do objeto. Está colidindo com o raio positivo do ovo mais o raio do objeto mais um pixel extra. Então, no próximo loop, a condição é falsa. Termos direção vertical definidos pela unidade y. Observe que aqui estamos detectando colisão e eu estou movendo colisão e colisão. Por que do ovo com base na posição do obstáculo? Essa é apenas minha escolha e fazer isso fará com que o ovo se mova e todos esses elementos serão sólidos nessa interação. Quando a interação acontecer, moveremos o jogador e os obstáculos permanecerão sólidos. Eles não serão empurrados pelo ovo. Aqui embaixo chamamos de método de sorteio em cada ovo. Também queremos chamar esse novo método de atualização que acabamos de escrever. Vamos testar isso. Estamos empurrando o círculo de colisão desse ovo. Preciso ter certeza de que a área de colisão e o Sprite fiquem juntos. A maneira mais simples seria pegar esse código que posicione o sprite X e o Sprite. Por que, em relação à colisão e colisão y e eu o chamaremos de método de atualização de insights. Eu posso remover os valores aqui e agora podemos empurrar dx usando o player, e eles também colidirão e deslizarão em torno de nossos cogumelos e plantas, em torno de obstáculos sólidos. Os Dx não interagem uns com os outros. Essa é apenas minha escolha. Quero que o x se sobreponha para que um jogador habilidoso possa empurrar vários ovos, meio que machucá-los juntos e empurrar todos ao mesmo tempo. Essa escolha também manterá nosso código mais simples porque estamos aprendendo. 21. Desenhe uma ordem: Estamos desenhando tudo em um único elemento de tela. Portanto, a ordem de desenho dos objetos do nosso jogo, o que é o drone por trás e o que está na frente dependerão da ordem em que chamamos métodos de desenho em cada objeto de dentro do método de renderização aqui. Neste momento, desenhei obstáculos, então cogumelos e plantas. Então eu desenho x. Então x são desenhados em cima de obstáculos, como você pode ver aqui. E depois desenhamos o jogador. O jogador é sorteado em cima de todo o resto. Se eu pegar o x e desenhá-lo primeiro, sempre haverá um sorteio atrás dos obstáculos atrás do jogador. E aqui podemos ver que isso realmente não faz sentido visual. Eu queria criar uma ilusão de profundidade, 3D falso ou talvez dois dias e meio. Objetos baixos deveriam estar na frente. À medida que subimos, os objetos desenhados nessa linha de base vertical devem ser desenhados para trás, por exemplo, esse ovo deve ser desenhado na frente desta planta, não atrás dela. Nós realmente não podemos conseguir isso. Se desenharmos todos os x, depois todos os obstáculos e depois o jogador, precisamos ajustar nosso código um pouco. Terei que colocar todos esses objetos em uma única matriz e classificarei essa matriz com base nas coordenadas verticais. Deixe-me mostrar o que quero dizer. Eu crio uma nova propriedade na classe principal do jogo. Vou chamá-lo de objetos de jogo. No início, essa será uma matriz vazia. Dentro do método aleatório, pegarei os objetos do jogo e usarei o operador de propagação para expandir toda a matriz interna. Também expandirei toda a gama de obstáculos aqui. E eu vou adicionar o player. Agora, estamos mantendo todos os objetos do jogo em uma única matriz. Essa matriz será recriada toda vez método de renderização exigir uma boa otimização. A dica aqui seria fazer essa operação apenas uma coordenada vertical de qualquer alteração de elemento. Ou quando adicionarmos ou removermos um ovo, reutilizarei esse bloco de código. Em vez de chamar cada método na matriz, vou chamá-lo nesta nova matriz de objetos do jogo. Para cada elemento na matriz, atribuirei a ele um nome de variável temporário, por exemplo objeto para cada objeto na matriz de objetos do jogo , chamarei seus métodos de desenho e atualização associados. Para que isso funcione, é importante que todos os objetos que adicionamos à matriz de objetos do jogo na linha 217 tenham métodos de desenho e atualização definidos em suas classes. Caso contrário, o código seria interrompido porque o JavaScript não conseguiria encontrar esse método. Então, estamos chamando de empate e atualização em todos os objetos do jogo, em todos os x, em todos os obstáculos e no jogador. Isso significa que agora posso excluir essas linhas de código. digitação aqui, escrevi objetos do jogo com um S. Agora desenhamos o primeiro obstáculo e depois recebemos um erro. Chamamos o empate do primeiro obstáculo. E quando tentamos chamar update nele, recebemos um erro de tipo que diz que atualização do ponto do objeto não é uma função. Eu vou para minha classe de obstáculos e você pode ver, como eu disse, que temos o método de desenho aqui, mas não existe um método de atualização. Estamos pedindo ao JavaScript que chame um método que não existe nesse objeto Vou criar um método de atualização aqui. Dentro desse método, poderíamos, por exemplo animar planilhas dessas plantas e cogumelos toda vez que o jogador colidir com eles ou algo assim, talvez eu adicione alguns obstáculos interativos posteriormente. Por enquanto, vamos deixar esse método vazio. Agora está funcionando e estamos chamando de sorteio e atualização em todos os x, todos os obstáculos e no jogador. Como alternativa, eu também poderia ter resolvido a falta de um método de atualização dos obstáculos algum tipo de afirmação, caso contrário, aqui embaixo, sempre há várias maneiras de fazer alguma coisa. Sinta-se à vontade para discutir como você abordaria isso de forma diferente. Talvez consigamos algumas soluções melhores nos comentários que eu possa usar em aulas futuras. Agradeço quando você me dá feedback sobre meu código e quando sugere melhorias. Ok, agora estamos chamando de empate e atualização à medida que percorremos os objetos do jogo, o que significa que x é desenhado primeiro, os obstáculos estão em cima de x e jogador é desenhado em cima de tudo. Se eu mudar a ordem desses elementos aqui, o jogador está atrasado agora do que x e os obstáculos estão no topo de tudo. Então, agora podemos entender como as camadas funcionam. Depende da ordem em que chamamos de método de desenho em cada objeto para desenhar nossos objetos de jogo na ordem que faça sentido visual. Agora posso classificar a matriz de objetos do jogo com base na posição vertical de cada objeto. Eu pego a matriz de objetos do jogo e chamo o método de classificação de matriz embutido. Esse método classifica os elementos em uma matriz e retorna a mesma matriz agora classificada. Por padrão, se o chamarmos sem passar nenhum argumento, ele apenas converterá elementos da matriz em strings. E ele os classificará com base em seus valores Unicode. Nós realmente não queremos isso, então vamos apresentar uma discussão. Esse argumento opcional que podemos passar para o método de classificação de matriz é uma função de comparação, uma função personalizada que define alguma lógica. Ele define uma ordem de classificação específica que queremos. Essa função especial receberá dois argumentos, elemento a e o elemento serão todos os elementos da matriz. No nosso caso, em objetos de jogo, a matriz será representada por isso a e B usados nesta lógica que definimos aqui, eu posso retornar assim e quero classificar os elementos em ordem crescente com base no valor de cada colisão de objeto. Por que propriedade? Portanto, estamos classificando com base no ponto central vertical da área do círculo de colisão. Como alternativa, também poderíamos classificar pela parte inferior da imagem do sprite, que seria classificada pelo valor do Sprite y mais a altura do sprite. O método de classificação pode ser complicado entender se você for iniciante, tudo o que você precisa entender aqui é que estou colocando todos os meus elementos na matriz de objetos do jogo. Então, eu sou o método de classificação embutido de Colin nessa matriz. E eu estou organizando esses elementos com base em sua posição vertical. Não funciona agora porque estou criando uma matriz que estou desenhando e depois classificando. Preciso classificar os elementos depois de criar a matriz, mas antes de desenhá-los. Então, assim. Perfeito. Agora isso é desenhado na frente da planta, mas se eu usar o jogador para empurrá-lo para cima, a posição vertical dos ovos se torna menor que a posição vertical da planta. Agora, o ovo é puxado para trás da planta. Essa é uma maneira simples de desenhar elementos em um jogo 2D de uma forma que faça mais sentido visual. É uma técnica muito poderosa para adicionar ao seu kit de ferramentas de codificação. Você tem alguma pergunta? Deixe um comentário. Você não precisa fazer o seguinte que farei agora. Eu defino o intervalo para 20 milissegundos e o máximo de x será 150. Apenas testando se há algum problema que precisamos resolver. À medida que eu os percorro, a física parece muito boa. Tudo está funcionando bem. Ter tantos ovos para testar também me dá uma boa ideia de onde eles podem potencialmente desovar. Eles aparecem na área que especificamos com uma margem superior grande e uma margem menor da esquerda, inferior e direita. Temos x, bons programadores. Eu disse intervalo de 2000 milissegundos, um ovo a cada segundo, e o máximo de x será 20. Como temos essa fórmula de colisão em vigor, se ela aparecer em cima do obstáculo ou do jogador, ela é automaticamente empurrada para fora do raio de colisão. Eu removo este log do console na linha 237. Temos nosso mundo de jogo. Nós classificamos obstáculos sólidos aleatórios. Temos um personagem de jogador controlado pelo mouse que pode se mover em uma direção. E temos x que aparecem em um intervalo específico. Além disso, tudo colide e reage a tudo. Podemos fazer muitos jogos diferentes com isso. Vamos adicionar inimigos. 22. Curso de inimigos: Eu crio uma classe personalizada que chamo, por exemplo, inimiga. Como de costume, o construtor usa um jogo como argumento e convertemos essa referência em uma propriedade de classe. Fazemos isso para obter um acesso fácil a todas as propriedades principal do jogo, de dentro da classe inimiga, por meio desta referência de jogo de cães. Precisamos manter as mesmas convenções de nomenclatura aqui. Portanto, raio de colisão de 30 pixels, colisão x, ponto central do círculo de colisão será a borda direita do Canvas. Este jogo de pontos tem a largura dos pontos. A colisão y será um valor aleatório entre zero e o valor da altura do jogo vindo daqui. Velocidade X a velocidade horizontal será um valor aleatório entre 0,5 e 3,5. Você pode baixar a imagem do inimigo na descrição do vídeo. No início, começaremos com o quadro único estático. O ID será rebocado, fonte será informada do ponto PNG. Eu o escondo com CSS porque queremos desenhá-lo onde está o JavaScript no Canvas, essa propriedade de imagem de pontos será obtida elemento por ID. E a identidade que demos foi rebocada. Sprite com, neste caso 140 pixels e a altura do Sprite é 260. Também criarei propriedades de largura e altura. E definiremos as posições do sprite X e do Sprite y da imagem da planilha que será posicionada em relação à coordenada do círculo de colisão. O método Draw tomará o contexto como um método de desenho de imagem embutido no argumento. E porque o arquivo de imagem que estou usando já tem a mesma resolução do tamanho. Eu quero que ele o exiba no jogo. Eu só preciso passar três argumentos. A imagem que eu queria desenhar e x e y deveriam desenhá-la. Também copiarei o visual do círculo de colisão que aparecerá somente quando a propriedade do modo de depuração estiver definida como verdadeira. Somente quando o modo de depuração está ativo. Eu apenas copio e colo aqui. Uma vez que esse código é o mesmo para todos os nossos objetos. Método de atualização. Lá, quero que os inimigos se movam para a esquerda na direção negativa no eixo x horizontal pela velocidade x, um valor que definimos na linha 179. Portanto, estamos movendo os inimigos para a esquerda se a borda direita da planilha estiver escondida atrás da borda esquerda da cannabis. Podemos ajustar sua posição x de volta à largura do jogo para que ele possa andar para a esquerda pela tela novamente. Também vamos randomizar sua posição vertical y para fazer com que os inimigos andem em pistas diferentes. Então, terei inimigos que andam da direita para a esquerda e depois reiniciam e andam novamente. Como alternativa, eu também poderia ter criado novos objetos animados repetidamente e destruí-los, descartando-os quando saírem da tela. O código seria simples, mas reutilizar objetos redefinindo sua posição em vez de criar novos e descartá-los posteriormente é uma boa técnica de otimização. Vamos reutilizar seus objetos e redefinir suas posições, se possível, em vez de criar novos, usar a nova palavra-chave para criar um novo objeto é mais caro do que redefinir um objeto existente. Na classe principal do jogo, eu crio um método personalizado que chamo, por exemplo , at, NME. Aqui em cima, teremos uma matriz que conterá todos os objetos animados ativos no momento. Sempre que um inimigo correr, ele empurrará um novo objeto animal para a matriz de inimigos. No 174 online, posso ver que construtor da classe inimiga espera o jogo como argumento. Então eu passo essa palavra-chave, porque aqui estamos dentro dessa classe de jogo. Fazendo isso, estou apenas passando uma referência que aponta para um espaço na memória onde um objeto do jogo está armazenado. Não estou criando uma cópia do objeto do jogo. Cada vez que eu crio um novo inimigo. método Init será executado. Só quero inicializar nosso jogo e configurar tudo lá dentro. Já estamos criando obstáculos. Também criaremos inimigos aqui no loop FOR que serão executados, digamos, três vezes. E cada vez que for executado, ele gritará. E então, no método que acabamos de definir, eu posso selecionar esses inimigos pontiagudos. E, como esperado, ele contém três objetos animados. Eu inspeciono um deles apenas para ter certeza de que nada está indefinido, o que pode ser um problema. Tudo está bem aqui. Eu posso excluir o registro do console. É uma boa ideia sempre verificar seus objetos e matrizes com os registros do console enquanto você cria seus projetos passo a passo para detectar possíveis bugs e erros de digitação a tempo. Para desenhar e atualizar inimigos, eu só preciso expandir a matriz de inimigos no objeto do jogo usando o operador de propagação. Fizemos isso antes. Eu vejo apenas caixas de colisão, preciso fornecer alguns valores para o sprite X e o Sprite y para que o JavaScript saiba onde desenhar a posição das imagens do sprite. Vamos nos mudar. À medida que os círculos de colisão se movem. Então eu coloquei esse código dentro do método de atualização. Vamos primeiro o centróide, depois o compensamos. Sprite X é colisão x menos metade de Sprite com um sprite como esse. Por que a colisão é y menos metade da altura? Quero que os círculos de colisão coincidam com a sombra no chão abaixo. São inimigos flutuantes o mais próximo possível. Precisamos ajustar as posições da imagem verticalmente. -40 -60. E quanto à altura negativa? Mais 40? Eu posso ativar e desativar modo de depuração para mostrar e ocultar caixas de sucesso pressionando a letra d. Temos inimigos vindo da direita para a esquerda e depois reiniciando. Quero que eles se reiniciem totalmente atrás da borda direita, para que não os vejamos simplesmente surgindo. Então, nós os redefinimos para a largura dos pontos do jogo mais a largura do inimigo. Além disso, outro jogo com o termo 0.5 para dar a cada um um um atraso aleatório diferente. Por que isso está aqui? Eu excluo isso. Só para testar. Vamos aumentar a velocidade. Redefini-los aleatoriamente atrás da borda direita funciona bem, talvez seja um pouco rápido demais. Preciso restringir suas posições verticais. Digamos que comece pela margem superior. E a partir daí, um intervalo aleatório entre zero e a altura do jogo menos a margem superior. Eu copio esse valor para ser usado como a posição inicial da colisão x dentro do construtor. E eu entendo essa nova colisão, por que a uso dentro do método de atualização de verificação do receptor. Isso não funcionará porque aqui na linha 176, estou usando essa largura de ponto, mas ela não está definida até mais tarde aqui na linha 183. A ordem aqui é importante. Eu apenas pego essas duas linhas e as coloco aqui. Agora temos inimigos andando da direita para a esquerda em um corredor correto e reiniciando atrás da borda direita. Dessa forma, podemos reutilizar os mesmos inimigos repetidamente, a menos que eu decida que queremos adicionar outros recursos, como permitir que o jogador destrua inimigos ou algo parecido. Vamos ver. Novamente, você não precisa fazer essa parte. Estou apenas testando nosso código. Então eu crio 30 inimigos. Ter tantos inimigos me dá uma ideia melhor de como eles reiniciam e eu posso identificar possíveis problemas mais rapidamente. Tudo parece estar funcionando bem. Essa seria uma floresta muito perigosa. Felizmente para nossas criaturas filhotes, o jogo final não terá enxames de inimigos como esse, a menos que você queira criar uma onda periódica de inimigos como essa. Como parte do jogo e da mecânica, o jogador deve se preparar e evitar. Podemos fazer muitas coisas em termos de design de jogos aqui, estou apenas fornecendo as ferramentas e técnicas e mostrando algumas das minhas ideias. Sinta-se à vontade para expandir este jogo com suas ideias criativas. Quando o curso terminar, vamos voltar para três inimigos. Agora mesmo. Os inimigos estão apenas voando pela floresta sem interagir com nada. Primeiro, quero que eles reajam a obstáculos sólidos e, o jogador, já temos um código que faz esse método de atualização de insights na classe EKG. Eu posso literalmente usar o mesmo bloco de código e colocá-lo dentro do método de atualização na classe inimiga. Isso transformará os inimigos, os obstáculos e o jogador em objetos sólidos e impossíveis e eles deslizarão ao redor deles. Estamos dentro do método de atualização da classe inimiga, e estamos passando o inimigo como objeto a, jogador e os obstáculos como objeto B. E aqui estamos dizendo que ajuste posição de a com base na posição de B. jogador e os obstáculos serão sólidos e dominantes nessa interação, e os inimigos serão empurrados ao redor deles. Esse simples check-in de colisão também criará uma inteligência artificial muito básica. Como você pode ver, os inimigos estão andando da direita para a esquerda pela área do jogo. E eles deslizam automaticamente e evitam obstáculos e o jogador, porque evitaram o jogador dessa forma, dentro e evitam obstáculos e o jogador, porque evitaram o jogador dessa forma, também podemos usar o jogador para empurrar os inimigos, mesma forma que podemos empurrar o x. Eu ajusto a velocidade do inimigo. Se eu adicionar x à matriz de objetos de colisão na classe inimiga, x também se tornará obstáculos sólidos e impossíveis para os inimigos. E os inimigos terão que contorná-los. Pode ser uma coisa boa, dependendo de como você está projetando seu jogo. A propósito, eu quero que meu jogo seja, eu realmente queria remover o x daqui. Em vez disso, vou até a classe e, dentro do método de atualização, adicionarei inimigos aos objetos de colisão. Aqui. Isso fará com que os ovos se tornem um caminho para verificar o método de colisões. E os inimigos serão sólidos, objetos impossíveis, pois os inimigos empurrarão o x ao redor. Se eu fizer isso dessa maneira, implementamos muitos recursos. Então, vou deixar o código-fonte desta etapa na seção de recursos abaixo, você pode baixá-lo e compará-lo com seus arquivos. Se você estiver codificando sozinho. Espero que você esteja se divertindo. 23. Curso de Larva: Aqui embaixo, entre as classes de ovos e inimigos. E eu crio uma nova classe que chamo, por exemplo, larva. Eu quero que x tenha um cronômetro oculto e, após um certo intervalo de tempo, cada ovo eclodirá em larvas menores. Essa larva tentará se esconder na floresta. E nosso trabalho como jogador é protegê-lo empurrando-o para mais perto da zona de segurança ou afastando os inimigos dela. O construtor da classe de larvas será um pouco diferente porque eu quero que cada larva apareça na mesma posição em que o ovo eclodiu. Então, passaremos uma referência ao objeto principal do jogo, como de costume, mas também passaremos as coordenadas x e y. Agora, eu converto todos esses argumentos em propriedades de classe. Preciso nomear essas coordenadas x e y, colisão e colisão, por que essas variáveis estarão envolvidas na detecção de colisões. Portanto, precisamos nomeá-los da mesma forma que fizemos outros objetos em nosso jogo de física, para que também possamos usar nosso método reutilizável de colisão de verificação usar nosso método reutilizável de colisão e envolver esses filhotes de larva no jogo de loop de física. Também precisaremos do raio de colisão aqui, digamos 30 pixels por enquanto. Você pode baixar a imagem da larva na descrição do vídeo. Eu adicionei aqui com um ID de larva e fonte larva dot PNG. É uma planilha com dois quadros. Nesse papel, temos uma larva ousada e esta tem um pouco de penugem atrás do pescoço, só para dar uma certa variedade visual. A penugem faz mais sentido se você souber no que esses filhotes se transformam quando são adultos. Eu trago a imagem da larva para o projeto usando get element by ID, assim. Largura e altura de uma única moldura. Agora, o sprite com é de 150 pixels, altura do sprite também é de 150 pixels. Se você é iniciante, sugiro fortemente que siga exatamente meu código e use as mesmas imagens. Divertido. Quando o projeto estiver concluído, será mais fácil fazer suas próprias alterações personalizadas. Alterar o código à medida que você segue este tutorial não é uma boa ideia, a menos que você seja um desenvolvedor experiente em JavaScript fazendo suas próprias alterações antes da conclusão do projeto Nós dificultaremos a depuração. Também precisaremos das coordenadas do sprite X e do Sprite y da imagem da planilha. Cada larva precisará desenhar e atualizar métodos. Como sempre. O método Draw tomará contextos como argumento. No site, chamamos de imagem embutida em uma gaveta e passamos a imagem que queremos desenhar e as coordenadas x e y onde desenhá-la. Quero que cada larva suba em direção à segurança da floresta de cogumelos. Essa área aberta está cheia de inimigos e é muito perigosa. Velocidade, por que a velocidade vertical será um valor aleatório, 1-2, por exemplo, definimos sprite X e Sprite. Por que propriedades que determinam onde a folha de sprite será desenhada. Mas como eles terão que se mover toda vez que a larva se mover, preciso atualizá-los repetidamente a partir do método de atualização interno. Sprite X será colisão x menos metade da largura. Sprite y será a colisão y menos metade da altura. Eu escondo essa imagem com CSS. Nós lhe demos uma identificação da larva. 24. Ovos: Portanto, temos uma classe que podemos usar como modelo para criar uma larva toda vez que um ovo eclode Vamos escrever a eclosão do ovo na lógica, precisaremos ajudar nossas variáveis. O cronômetro de hachura passará de zero para o intervalo de hachura. Ele contará milissegundos. Então, digamos que eu queira que o ovo ecloda após cinco milissegundos após 5 s. Dentro do método de atualização, lidamos com colisões nessa área. E aqui embaixo vamos lidar com a eclosão. Se o cronômetro de hachura que acabamos definir for maior do que o intervalo de hachura, fazemos outra coisa. Continuamos aumentando o cronômetro de eclosão em tempo delta. Contando e acumulando em milissegundos. O termo delta será passado para o método de atualização aqui na linha 159. Esse método de atualização o receberá aqui. Renderização interna. Calculamos o valor do tempo Delta antes do loop de animação, tempo delta contém o número de milissegundos que aconteceram entre esse quadro de animação e o quadro de animação anterior. Portanto, o temporizador de hachura está acumulando esse tempo delta em milissegundos entre os quadros. Se o cronômetro de eclosão for maior que o intervalo de eclosão, excluiremos o ovo e o substituiremos por uma larva. Aqui eu crio uma propriedade chamada marcada para exclusão. Aqui embaixo, definimos marcado para exclusão como verdadeiro. Posso verificar e remover esses objetos marcados para exclusão de cada quadro de animação sobre ele pode ser um pouco mais eficiente reestruturar nosso apagamento apenas quando algo é realmente marcado. Então, aqui chamaremos um método personalizado. Vou chamar, por exemplo, remover objetos de um jogo. Aqui na classe principal do jogo, vou definir esse método. Até agora, estamos marcando apenas X para exclusão. Portanto, sempre que esse método for executado, pego a matriz inteira e chamarei o método de filtro de matriz embutido nela. Ou um método de filtro apenas criará uma cópia dessa matriz. Mas essa nova matriz filtrada conterá apenas elementos que passem pela verificação fornecida na função de retorno de chamada. A verificação neste caso será eu só quero que a matriz X contenha elementos que tenham a propriedade marcada para exclusão definida como falsa. Se alguma coisa estiver marcada para exclusão definida como verdadeira, ela será filtrada para fora da matriz. Essa é a chamada sintaxe da função de seta ES6. Estou dizendo que pegue os elementos da matriz um por um, atribua a cada um um um objeto de nome de variável temporário. E em cada um desses objetos, verifique se eles estão marcados para exclusão. A propriedade é falsa. exclamação significa falso. Se a marcação para exclusão neste ovo for verdadeira, esse ovo será filtrado para fora dessa nova matriz. E essa nova matriz filtrada é atribuída ao raio-x original e substitui. Estou explicando demais? E, novamente, vamos registrar esse jogo de pontos, ponto X, para ver se x está sendo removido e adicionado. Sim, isso está funcionando perfeitamente. Quando estamos no modo de depuração, quero que o cronômetro do Hajj fique visível como um número flutuando acima cada ovo dentro do método de desenho aqui que chamo de método filtText integrado . Esse método precisa de pelo menos três argumentos, texto que queremos desenhar e coordenadas x e y onde desenhá-lo. Alterar o tamanho da fonte, em vez de alterar o tamanho da fonte toda vez que o método de desenho for executado em cada ovo, vou configurá-lo aqui na linha dez no primeiro carregamento da página. Essa é uma técnica de otimização poderosa. A fonte faz parte de um estado e mudanças frequentes em um estado podem afetar o desempenho. Definiu propriedades do Canvas, como fillStyle, largura da linha, estilo de traçado e telefonou em um bloco de código que executa o mínimo possível. Idealmente, não faça isso em um método executado 60 vezes por segundo em vários objetos ao mesmo tempo. Embora em alguns casos isso possa ser inevitável. Tudo o que estou dizendo aqui é que se você puder definir as propriedades da tela na primeira página, carregue assim, todo esse código será executado apenas uma vez. Eu disse que gostava de 40 pixels. Helvética. Eu também poderia ter definido a fonte Canvas aqui neste método de desenho. Mas, como eu disse, essa linha de código seria executada 60 vezes por segundo em cada ovo ativo. São muitas operações que podem ser facilmente evitadas fazendo o que acabei de dizer. Em dímero é esse número ridículo com tantos dígitos após o ponto decimal. Vamos limpar isso. Eu crio uma variável auxiliar chamada display timer. Aqui, formataremos esse número um pouco antes de desenhá-lo. Posso, por exemplo, pegar o cronômetro de hachura e chamar o método embutido para fixo. Javascript para método fixo converte um número em uma string e arredonda a string para um número especificado de decimais. Na verdade, eu quero zero decimais. Eu só quero ver os milissegundos. Eu uso essa variável do temporizador de exibição aqui para realmente desenhá-la. Eu posso ajustar a posição vertical do texto, talvez por raio de colisão como este. Aqui em cima, vou colocar a linha do texto no centro. Isso alinhará os temporizadores e x horizontalmente. Bom. Eu queria empurrar os cronômetros ainda mais acima dos ovos. Talvez até mais. Vamos usar 2,5. Em vez de milissegundos, eu só quero ver os segundos. Então eu fecho o cronômetro de hachura entre colchetes e multiplico por 0,001. Agora isso é mais legível. Podemos ver o número de segundos acima de cada ovo. Vamos fazer cada ovo eclodir depois de 3 s. Ótimo. Quando o ovo eclode, ele é removido pelo método de filtro. Na classe principal do jogo, eu crio uma propriedade chamada filhotes e, a princípio, a defino como uma matriz vazia. Sempre que um ovo eclode, colocaremos novas larvas nessa matriz. Aqui na linha 190, posso ver que a larva espera o jogo e as coordenadas x e y. Então, quando um ovo eclode, eu o levo para a matriz de filhotes de pontos e empurro uma nova larva para dentro. Eu passo isso para distorcer o jogo. E eu passo as coordenadas x e y desses ovos que eclodiram Eu removo este registro do console. Dentro do método de renderização, eu uso o operador de propagação ou para expandir filhotes em objetos de jogo, matrizes para que possam ser classificados, desenhados e atualizados. Perfeito, estamos progredindo. Agora que podemos ver a aparência de nossos filhotes, podemos ir até a aula de larvas e limpar isso. Primeiro, verificaremos se a larva se move para um local seguro. Eles estão seguros assim que chegam a esta área de cogumelos e arbustos, onde podem se esconder. Portanto, se a colisão y for menor do que a margem superior do ponto do jogo de pontos, se a larva passar desse ponto, podemos definir a opção marcada para exclusão como verdadeira. Vamos deletar essa larva porque não queremos mais desenhá-la. E chamaremos de remover objetos do jogo online 350 insight, remover o método do objeto do jogo. Eu faço a mesma coisa que fizemos com x, filtro a matriz dos filhotes. E se algum objeto larva nessa matriz tiver marcado a propriedade de exclusão estiver definido como verdadeiro, filtre-o. Só para verificar o registro de objetos do jogo em nosso console. Toda vez que removemos um ovo ou uma larva, recebo um erro. E é porque esse precisa ser esse jogo de pontos para remover objetos do jogo, porque esse método em nossa classe de jogo principal. Eu quero que você desenhe círculos de colisão em filhotes. Eu copio esse bloco de código da classe inimiga. E acabei de colar aqui porque nossas classes têm as mesmas convenções de nomenclatura em suas propriedades. É fácil reutilizar um código como esse. No momento, estamos desenhando a planilha inteira para cada larva. Eu queria recortar apenas um único quadro da coordenada zero-zero dois. Parece uma largura brilhante e essa altura de sprite de pontos. E queremos desenhar essa imagem recortada na posição sprite, sprite y. E precisamos dar a ela uma largura e uma altura como estas. Nove argumentos. Eu queria posicionar a imagem da planilha da larva em relação ao seu círculo de colisão. Eu tento -50. Sim, isso vai funcionar. Quero que o círculo de colisão se alinhe com a parte inferior do corpo. Acho que isso fará mais sentido visual ao interagir com ambientes e personagens de jogos. Eu removo este log do console na linha 362. 25. Larva sprites e colisões: planilha Larva tem dois quadros, mas estamos desenhando apenas o de cima. Vamos randomizar esse quadro. Y será um valor aleatório 0-2. E como não há linha 1.5, por exemplo, precisamos de números inteiros, números inteiros sem pontos decimais. Então eu embrulho isso em Math.floor. Essa linha de código nos dará zero ou um, porque Math.floor arredondará o valor até o número inteiro mais próximo. quadro X sempre será zero até o final da aula, onde eu lhe forneço uma planilha avançada. Para animação. Usaremos frame x times sprite com aqui como argumento fonte x passado para desenhar imagem. E também precisamos definir uma fonte. Por que as coordenadas x e y da fonte combinadas determinarão qual área da planilha recortada da fonte Y será emoldurada y vezes a altura do sprite. Bom. Uma vez que o frame why é aleatório para ser zero ou um. Alguns filhotes usam a imagem do sprite da linha zero e alguns usam a outra da linha um. A colisão entre filhotes e objetos do jogo será tratada aqui. Eu entro aqui dentro do método de atualização em uma classe e copio esse bloco de código, faremos algumas alterações nele, mas a maioria será a mesma. Então, podemos muito bem copiá-lo para que não tenhamos que escrever tudo isso. Novamente. Eu colo aqui dentro do método de atualização na classe larva. Eu removerei os inimigos dos objetos de colisão porque os inimigos terão um tipo diferente de interação. Assim, a larva evitará automaticamente jogadores e obstáculos. E também podemos usar o jogador para empurrar larvas desse jeito, o que será uma importante mecânica de jogo. A colisão com inimigos será tratada aqui embaixo. Eu escolho os inimigos e chamo cada um deles. Para cada objeto inimigo, eu os chamarei de inimigos, por exemplo, para cada um deles, chamarei essa função de retorno de chamada. Basicamente, quero dizer se essa larva e esse inimigo que estamos atacando atualmente com esse método forEach colidirem. Uma forma de verificar facilmente se os objetos do jogo colidem é usar nosso método personalizado de verificação de colisão. Novamente, quero verificar a colisão entre essa larva. Estamos dentro da classe das larvas e de cada inimigo individual. Como isso acontece com cada método executado, sabemos que nosso método personalizado de verificação de colisão retorna uma matriz de valores. Isso nos dá a distância de colisão de alguns raios, DX e DY. Aqui eu só quero o primeiro argumento, o colágeno, que será verdadeiro ou falso, dependendo se essa larva e esse inimigo colidirem ou não. Verificar colisão retorna uma matriz e eu quero o status da colisão verdadeiro ou falso. Eu quero o primeiro elemento no índice zero da matriz, assim. Então, aqui estamos usando a estruturação para extrair todas as cinco variáveis individuais do método de colisão de verificação. Aqui estou acessando diretamente apenas o primeiro valor com índice zero porque não preciso dos outros. A razão pela qual eu não preciso desses outros valores quando a larva e o inimigo colidem é porque não haverá interferência nem física. A larva acabará de ser comida. Então eu disse marcado para exclusão nesta larva. Oponha-se ao verdadeiro. Também chamarei a remoção de objetos do jogo para filtrar essa larva da matriz de filhotes. Também vou acompanhar quantos filhotes perdemos de nojo e de forma variável. Este jogo de pontos em que filhotes perdidos será aumentado em um. E aqui, onde verificamos se a Flórida move a segurança da floresta de cogumelos, aumentamos a pontuação em um. Então, se protegermos a larva pela posição e x, afastando os inimigos ou empurrando a larva em direção à floresta. Se a larva atingir os arbustos com sucesso, obteremos um ponto de pontuação. Se a larva for comida, variável de filhotes perdidos aumentará em um. Aqui em nossa classe de jogo principal criamos essas propriedades. pontuação será inicialmente definida como zero e os filhotes perdidos também serão inicialmente definidos como zero. 26. Ganhar pontos de pontuação: Posso usar os registros do console ou algo parecido para verificar se o código que acabamos de escrever funciona, mas precisávamos que esse texto fosse exibido para o player de qualquer maneira. Então, aqui embaixo, desenharemos o texto do status do jogo. Novamente. Usaremos o método de preenchimento de texto de cannabis integrado, que espera o texto que queremos desenhar e as coordenadas x e y onde desenhá-lo? Eu quero desenhar a palavra pontuação nas coordenadas 25 horizontal e verticalmente. O problema que temos agora é que eu quero que esse texto seja alinhado à esquerda. E se você se lembrar do texto acima de x, esse cronômetro está alinhado ao centro, terei que isolar esse FiltText chamado envolvendo-o entre os métodos integrados de segurança e restauração do Canvas. Depois, posso definir o alinhamento do texto para a esquerda. E eu chamo de restauração. Os métodos de salvar e restaurar funcionam juntos e os usamos quando queremos aplicar configurações específicas somente a uma determinada chamada de desenho na tela, sem afetar o resto do nosso projeto. Eu salvo o estado da tela. Eu defino o alinhamento de texto para a esquerda, que afetará apenas essa chamada filtText qual extraímos a pontuação e , em seguida, o método de restauração redefinirá todos os tipos de configurações para o que eram. cofre foi chamado. Nesse caso, ele só reverterá para a linha x voltando da esquerda para o centro. Isso faz sentido. Essa é uma técnica muito útil, especialmente se você quiser dimensionar e girar seus desenhos em tela. Eu faço isso em algumas outras aulas, por exemplo, na minha aula de codificação fractal criativa, se você quiser saber mais sobre isso. Agora, a pontuação está alinhada à esquerda e os cronômetros acima dos ovos estão alinhados ao centro. Perfeito. Eu ajustei o texto para desenhar, quero dizer, marcar espaço de dois entre aspas mais essa variável de pontuação de pontos para torná-lo dinâmico. Agora, à medida que os filhotes se movem para um local seguro e se escondem na floresta, podemos ver nossa pontuação aumentar no modo de depuração. Também quero poder ver filhotes perdidos. Então, se esse jogo para depurar for verdadeiro, eu copio essa linha de código. O texto dirá perdido mais essa variável de filhotes perdidos com pontos. Na verdade, estamos dentro da classe de jogos aqui, então preciso dizer apenas essa depuração de pontos. Sim, eu mudo a coordenada vertical aqui. E agora, no modo de depuração, também estamos acompanhando quantos filhotes foram comidos, quantos filhotes colidiram com os inimigos. 27. Efeitos de partícula: Às vezes, a larva eclode rapidamente e está comendo muito rápido, ou às vezes a larva desaparece muito perto da borda superior enquanto o inimigo está por perto. Portanto, pode não estar 100% claro se ele conseguiu chegar ao local seguro ou se foi comido. Eu gostaria de adicionar um efeito visual adicional que nos ajudará a tornar nosso jogo claro e fácil de ler. Eu queria adicionar dois tipos de efeitos de partículas. Quando a larva se eleva nos arbustos, ela interrompe um enxame de vaga-lumes que estavam sentados nos galhos e eles voarão no ar. Um jogador vê aqueles vaga-lumes. Eles sabem que a larva é segura. Se a Flórida for comida, tentarei criar um movimento de partículas diferente e muito distinto. Portanto, se houver muitos objetos de jogo na mesma área, ainda podemos saber o que está acontecendo vendo que tipo de partículas estão voando de lá. Também usarei essa oportunidade para falar sobre subclasses em JavaScript Teremos uma classe de partícula principal que conterá todas as propriedades e métodos compartilhados entre todos os tipos de partículas. Essa é a chamada classe mãe, também chamada de classe de superclasse. O Firefly estende essa classe de partículas. Esta é uma classe infantil, também chamada de subclasse. Também teremos uma classe que chamo de spark que estende a classe de partículas como esta. Temos uma turma para pais e duas aulas para crianças. As classes secundárias herdarão automaticamente propriedades e métodos da classe principal, o que nos poupará algumas repetições frias. Vamos escrever o código e falar um pouco mais sobre ele à medida que avançamos. O construtor da classe de partículas original espera as posições X e Y do jogo porque vaga-lumes e faíscas sempre voarão para fora da posição em que a larva desapareceu. E vamos adicionar mais uma, por exemplo, cor aqui. Talvez eu queira que um tipo de partícula seja dourado e outro azul, dependendo de onde em nossa base de código essa partícula foi criada. Vou te mostrar o que quero dizer. Eu converto todos esses argumentos em propriedades de classe, como de costume. Estou dizendo que pegue a cor que foi passada como argumento aqui e salve-a como propriedade escalar nesta instância da classe de partículas. Você já sabe como isso funciona, raio será um valor aleatório de 5 a 15. Mas eu o envolvo em Math.floor para permitir apenas números inteiros, então sem pontos decimais. Se estivermos criando muitas partículas, você notará uma grande diferença no desempenho ao usar valores aleatórios como esse, em comparação com a configuração do raio inicial para um valor fixo de, digamos, dez pixels para todas as partículas. Randomizar os valores de um objeto ao criar vários objetos é muito caro em termos de desempenho. Você pode melhorar o desempenho do seu jogo tentando evitar o Math.random o máximo possível. Se você souber, criará muitas cópias desse objeto específico. Sei que criaremos muitas partículas. Só temos um objeto de jogador, então aí, isso realmente não importa. Ele é criado uma vez. Mas ao longo do nosso jogo, provavelmente criaremos milhares de partículas. Uma forma de evitar isso seria uma técnica chamada agrupamento de objetos. Você cria um conjunto de objetos de partículas e só os desenha e os usa quando necessário. Quando se trata de desempenho, isso será muito melhor do que criar novos e excluí-los constantemente . A velocidade x será um valor aleatório entre menos três e mais três. Isso significa que algumas partículas se moverão para a direita na direção positiva no eixo x horizontal, algumas partículas se moverão para a esquerda se sua velocidade x for um valor negativo. Para velocidade vertical, será um valor aleatório entre 0,5 e 2,5 pixels por quadro de animação. Quero usar um pouco de trigonometria para fazer as partículas girarem, flutuarem e girarem. Portanto, precisaremos de um valor de ângulo inicialmente, eu o defino como zero. V. A velocidade do ângulo determinará a rapidez com que o valor do ângulo aumenta. Eu vou te mostrar exatamente como usar isso em um minuto. Não se preocupe. O a será um valor aleatório entre esses dois números muito pequenos. Excluiremos as partículas que saem da tela. Inicialmente, eu disse que eles estão marcados para serem excluídos dramas falsos que também estarão na classe de partículas-mãe. Então, ele será compartilhado por todos os Fireflies e Sparks. Quero ter certeza de que as mudanças em um estado que fazemos aqui permaneçam isoladas para essa partícula em particular. Então, eu coloquei o código do desenho entre os métodos de segurança e restauração integrados do Canvas. Dissemos fillStyle para essa cor de ponto a partir da linha de 3/3. Eu chamo start path para iniciar uma nova forma. O método de arco embutido assumirá horizontal, ponto central, vertical, ponto central , raio, ângulo inicial e ângulo final. Eu quero que você desenhe um círculo completo. Então, de zero a math.pi vezes a math.pi vezes dois é um valor em radianos e ele se converte em 360 graus. É um círculo completo. Temos que usar valores em radianos aqui ao passá-los para o método do arco. Então eu chamo fill para preencher a cor da largura do caminho. E eu também vou acariciá-lo porque em nosso jogo, tudo tem esse estilo de arte vetorial com contornos pretos. Eu quero que as partículas combinem com isso. Além disso, o Firefly conterá apenas o método de atualização. O mesmo acontece com o Spark, mais tarde, quando chamamos o método de desenho na classe Firefly. Como essa classe estende a partícula, o JavaScript procurará automaticamente método de desenho e construtor na classe de partícula pai. Fazer isso nos poupará da repetição de código. Não preciso definir o construtor e o método de desenho duas vezes se ele for compartilhado para vaga-lumes e faíscas, só podemos defini-lo uma vez na classe pai e o JavaScript o encontrará, ele será herdado. vaga-lumes terão um movimento único, acho que flutuam para cima e balançam, para a esquerda e para a direita, o que é muito fácil de implementar. Primeiro, aumentaremos o ângulo em VA, velocidade angular para cada quadro de animação. Em seguida, aumentaremos a colisão x pela velocidade x. Como a velocidade x pode ser positiva ou negativa, eles podem começar a ir para a esquerda ou para a direita aleatoriamente, a colisão y será menos igual à velocidade. Por quê? Porque eu quero que eles flutuem na direção negativa no eixo y vertical. Se um vaga-lume se mover totalmente para cima e estiver escondido acima da borda superior da área de jogo. Então, se for colisão, o ponto central y é menor que zero menos seu raio. Dissemos que está marcado para exclusão como verdadeiro e chamaremos de remover objetos do jogo. A classe principal do jogo eu crio uma propriedade chamada partículas. Será uma matriz e conterá todos os objetos de partículas atualmente ativos. Eu expando partículas em objetos do jogo para que elas possam ser classificadas, desenhadas e atualizadas. Dentro de remover objetos do jogo, eu adiciono mais uma linha de código para filtrar objetos de partículas com a propriedade marcada para exclusão definida como verdadeira assim. Vamos tentar criar algumas partículas. Vou criá-los aqui na aula de larvas. Neste bloco de código que é executado quando a larva se eleva na floresta, obtemos um ponto de pontuação. Quero que um enxame de vaga-lumes voe para fora dos arbustos. Eu pego essa matriz de partículas com a melhor aparência do jogo e coloco um novo Firefly para dentro. Lembro-me de que o construtor de classes de partículas espera argumentos, uma referência ao objeto principal do jogo, como sempre. Então, acabei de passar para este jogo de pontos junto com o construtor de larvas. x e y iniciais dessa partícula serão as últimas posições X e Y da larva que acabamos excluir e a cor será vermelha, por exemplo, apenas para testá-la. Ótimo, temos partículas vermelhas com contornos brancos. Se eu quiser mais de um, acabei de criar um for-loop. Digamos que eu queira três vaga-lumes de cada vez. Vou mudar de cor para amarelo. Vamos ver como isso parece. Partículas bonitas precisam de traços pretos. Posso configurá-lo dentro do método de desenho em cada partícula. Mas neste projeto, não estou usando um derrame em muitas outras coisas. Posso simplesmente definir o traço como preto globalmente aqui na linha nove, isso será mais eficiente em termos de desempenho. Também fornecerá traços pretos para círculos de colisão enquanto estivermos no modo de depuração. Mas eu realmente não acho que seja um problema. Pode parecer ainda melhor assim. 28. Movimento de partículas: Os vaga-lumes têm um movimento ascendente muito básico para a esquerda e para a direita. Não estamos usando esse ângulo. Estamos aumentando sem parar na linha 327. Uma maneira fácil de fazer algo girar para frente e para trás é passar cada aumento no valor do ângulo para o método do seno ou cosseno de Masdar . Quando seu código é assim, va, quantidade de aumento de ângulo para cada quadro de animação determinará a velocidade de oscilação. E essa velocidade de ponto x determinará a curva, o raio, até onde, esquerda e para a direita, o movimento vai. Perfeito. Sempre que a larva cresce com sucesso na floresta, obtemos um ponto de pontuação e obtemos confirmação ao ver esse efeito Firefly. Quero que as faíscas tenham uma aparência diferente para que, caso larva e o inimigo estejam obscurecidos atrás de obstáculos ou algo assim, você ainda possa ver que a larva foi comida com base no efeito de partícula que acontece lá. Aqui, vou definir um método de atualização diferente com um código diferente dentro. Portanto, firefly e Spark são ambas classes filhas da partícula mãe, além de ambos herdarem código no construtor de classes de partículas, e ambos compartilham seu método de desenho, mas cada um terá um método de atualização exclusivo no qual lidamos com o movimento. Vou aumentar o ângulo pela velocidade angular VA novamente, mas vou diminuí-lo. Então, vezes 0,5, a posição horizontal será menos igual ao cosseno dominado. Passamos esse valor de ângulo cada vez maior para mapeá-lo ao longo de uma curva de cosseno. E definimos o raio dessa curva usando a velocidade x. A colisão y será menos igual ao sinal do método e eu passo o mesmo valor do ângulo e raio da curva é o valor da velocidade y. Voltamos para a aula de larvas. E aqui, se a Flórida colidir com um inimigo, nós o excluímos. E eu queria criar um redemoinho de partículas. Vou apenas copiar esse bloco de código que usamos para criar três vaga-lumes e copio aqui. Nesse caso, queremos criar três faíscas e a cor será azul. Como eu faço isso? Agora preciso empurrar a larva na frente de um MA, ver o que acontece quando ela é comida. Ótimo, temos faíscas azuis e algum tipo de movimento circular. Apenas para fins de teste, criarei faíscas mesmo quando larva estiver segura, para que a animação aconteça automaticamente com mais frequência, sem que eu precise persegui-las. Preciso ver isso para poder ajustar o movimento. O que podemos fazer com essas partículas de azeitona? Você pode fazer muitos padrões de movimento diferentes com tanta facilidade. Por exemplo, vamos fazê-los encolher. Se você tentar desenhar um círculo usando o método de arco com raio menor que zero, você receberá um erro. Portanto, temos que ter muito cuidado aqui. Eu digo, se o raio for maior que 0,1, continue reduzindo o raio em um valor pequeno. Esse valor precisa ser menor que 0,1 para garantir que não possamos ficar abaixo de zero. Além disso, se você se lembrar, só removeremos partículas antigas se elas passarem pela borda superior do Canvas e as partículas giratórias nunca o fizerem. Então, eu preciso de outra condição aqui. Se o raio for menor que 0,2, por segurança, digamos, ele é marcado para exclusão verdadeiro e chamado de método de remoção de objetos do jogo. Bom, isso parece interessante. Eu quero que eles sejam meio que explorados pelos dois lados. Eu não mencionei isso antes porque não é necessário entender completamente a trigonometria para este curso. Seno e cosseno e trabalham juntos para mapear um caminho circular. Se dermos a eles os mesmos valores, veja o que acontece se eu trocar seno e cosseno. Tudo o que você precisa entender sobre seno e cosseno para fins de animação é que, se você os passar, sempre aumente o valor do ângulo. E quando você anexa esses valores às posições vertical e horizontal do seu objeto, você obterá um movimento circular. A melhor maneira de obter alguma clareza é brincar com os valores tentados para quebrá-lo, ajustar o código e ver o que acontece. Não se preocupe em entender completamente a trigonometria hoje, ela pode ser complicada para iniciantes e demorar um pouco para dominá-la. Estou desenhando 30 partículas aqui para me dar uma ideia melhor sobre o movimento que acontece. E se aparecerem casos extremos que precisem ser contabilizados. Você não precisa fazer isso. Eu recomendo usar apenas três partículas por termo por motivos de desempenho Você pode ver que as faíscas são uma espécie de explosão em espiral. Eu gosto disso. O movimento é muito diferente dos vaga-lumes. Eu lhes dou a cor amarela aqui e voltarei a usar vaga-lumes. Vamos ver como isso parece. Então, sabemos como estender a classe JavaScript para evitar a repetição do código Criamos uma classe de partículas mãe e uma classe secundária, Firefly, que indica ao jogador que a larva conseguiu se esconder na floresta por meio de um enxame perturbador de insetos em crescimento que flutuam para cima. E quando a larva é comida por um inimigo, ela se transforma em um redemoinho de faíscas mágicas que lentamente encolhem e desaparecem. Vou voltar para três vaga-lumes aqui. E aqui embaixo. Vamos criar três faíscas, ou talvez cinco, porque elas encolhem muito rápido e geralmente são obscurecidas por obstáculos e outros objetos do jogo. Queremos garantir que eles sejam notados quando aparecerem. Eu também gostaria de permitir que o jogador movesse os xs para a floresta para obter pontos de pontuação imediatamente Quero que X ecloda instantaneamente quando os empurrarmos alto o suficiente para a área segura, esse ovo se transformará automaticamente em larva e em vaga-lumes instantaneamente. E obteremos um ponto de pontuação. Posso fazer isso aqui na linha 179, onde verificamos se cronômetro de Hodge é maior do que o intervalo de gaiola para voltar a ser uma larva Quero que esse bloco de código também funcione se o ovo for empurrado para a zona de segurança. Se uma colisão de posição vertical y for menor do que a margem superior pontilhada deste jogo de pontos. Para testá-lo, talvez eu deva aumentar o intervalo de incubação. Caso contrário, esses ovos eclodem muito rápido. Eu vejo um ovo aqui, não aqui, e eu empurro para a medula. Ok, agora vamos lá. Ele eclodiu instantaneamente enquanto o cronômetro de eclosão era de apenas 7 s. Eu tento novamente com este ovo, e sim, isso funciona e também estamos ganhando pontos por isso. Perfeito. 29. Pelotas inimigas aleatórias: Eu crio muitos inimigos diferentes para você. Você sabe como temos um único arquivo de imagem com vários tipos de obstáculos. Também preparei muitos ganhos diferentes de sapos para você, que desempenharão o papel de inimigos em nosso jogo. Eu me diverti muito fazendo variações diferentes e imaginei quais tipos de habilidades especiais e movimentos especiais eles poderiam ter. Digamos que, por motivos de desempenho, queiramos usar apenas um conjunto de imagens estáticas para inimigos. Também os animaremos mais tarde. Se é isso que você quer na versão final do jogo, tudo será animado. Todas as imagens do projeto podem ser baixadas da seção de recursos abaixo. Como sempre, estou te dando toda essa arte de jogo premium de graça. Este curso foi desenvolvido para oferecer a você o valor máximo possível. Sinta-se à vontade para usar esses recursos de arte seus próprios projetos, se quiser. Então, eu trago essa planilha com quatro quadros para tipos de inimigos para o projeto. Id é sapos e a fonte está voltada para o ponto PNG. Eu escondo isso com CSS, como de costume. Em script.js, dentro do construtor de classes inimigas, criamos duas variáveis auxiliares que nos ajudarão a navegar pela planilha e recortar um quadro aleatório para cada objeto inimigo, quadro x. Navegaremos pela planilha horizontalmente. E ele será inicialmente definido como zero, pois MY navegará verticalmente e também começará em zero. Vou precisar da versão mais longa do método de desenho de imagem para definir qual imagem eu queria desenhar, fonte x, y, largura e altura da fonte para especificar qual área retangular eu queria recortar da imagem de origem e do destino. Destino. Por que destino com e altura de destino para definir por que colocamos esse pedaço de imagem recortado na tela de destino. Nove argumentos no total. Imagem para desenhar a área de corte. E onde colocar essa imagem recortada. Vamos começar recortando apenas a moldura superior esquerda. Então, recortamos da coordenada zero-zero, duas coordenadas, sprite com pele de sprite. Essa planilha tem apenas uma coluna, então o quadro x permanecerá em zero. Quero recortar uma moldura vertical aleatoriamente. Então, a fonte da razão pela qual a área de corte será a altura do sprite empurrando o início da área de corte aqui, dada esta moldura, para realmente vê-la, preciso usar a nova imagem com uma identificação de sapos. Então, para ser claro, zero vezes a altura do sprite nos dará essa moldura. Duas vezes a altura do sprite recortará essa moldura. O valor que transmitimos a uma fonte. método de por que desenhar uma imagem determinará onde começaremos a cortar verticalmente. Em vez desse valor codificado, podemos usar frame. Por quê? Agora, quando eu dou um quadro, por que um valor diferente? Isso nos dá diferentes tipos de inimigos. Quero que cada objeto inimigo seja atribuído aleatoriamente a uma dessas imagens quando ela for criada. Então, temos quatro linhas, Math.random times four. E se eu embrulhar em matemática. A estrutura do piso y será zero ou um, ou dois ou três. Também substituirei a fonte X um quadro de pontos de disco x vezes sprite, pois isso se tornará relevante um pouco mais tarde, quando eu fornecer a próxima planilha. Por enquanto, vamos deixar o quadro x em zero. Também seria bom reatribuir aleatoriamente a imagem toda vez e, em seguida, ela sair da tela e reiniciá-la . Por isso, fazemos uso de todas as imagens disponíveis. Eu apenas randomizo o quadro y aqui, assim para conseguir isso. Para testar isso rapidamente ou aumentar a velocidade do inimigo para um valor aleatório, 5 a 8 pixels por quadro de animação. Os inimigos estão sendo reiniciados e as imagens mudam aleatoriamente toda vez que são reiniciadas. Perfeito. Vamos voltar para uma velocidade mais baixa. Eu defini x como cinco, já que estamos usando imagens estáticas para inimigos neste estágio, desenhá-las é muito barato em termos de desempenho, talvez eu também possa aumentar o número de inimigos para cinco. Isso está começando a parecer muito bom. 30. Ganhar e perder condições: Eu queria exibir uma mensagem de vitória e derrota na classe principal do jogo, criei uma propriedade que chamo, por exemplo, pontuação vencedora. Para fins de teste, eu o configurei para cinco. Vamos desenhar a mensagem de vitória e derrota aqui embaixo. Se a pontuação for maior ou igual à pontuação da vitória, vou embrulhá-la em um cofre e restaurá-la. Para restringir algumas configurações de sorteio, usaremos agora apenas duas mensagens que ganham ou perdem. Quero desenhar uma camada semitransparente para indicar que o jogo acabou e também para tornar a mensagem mais contrastante com o fundo. Então eu disse FillStyle para bloquear zeros, zeros, 0,0, 0,5 opacidade. Em seguida, desenharemos esse retângulo preto semitransparente sobre toda a tela, da coordenada 00 até a largura e a altura da área do jogo. Vamos executar esse código para testá-lo. Quando atingirmos a pontuação três, devemos obter a camada semitransparente holandesa. Sim. Depois que o retângulo for desenhado, mudarei fillStyle para branco para garantir que o texto esteja contrastando com o fundo escuro. Eu coloco o alinhamento do texto no centro. Teremos uma mensagem principal em letras grandes e uma mensagem secundária em fonte menor. Vou apenas defini-los assim como variáveis principais. E os valores dessas mensagens receberão dependerão de quão bem jogamos. Em qualquer caso, esta mensagem só será exibida após a pontuação ser maior ou igual à pontuação da vitória. Mas digamos que, se jogarmos bem e perdermos apenas cinco ou menos filhotes, receberemos uma mensagem vencedora. Se perdermos mais de cinco, receberemos uma mensagem perdida. O objetivo do jogo é empurrar ovos, elos e inimigos para garantir que protejamos o maior número possível. Então, se jogarmos bem, primeira mensagem dirá: vamos brincar um pouco com as palavras. Você pode escrever o que quiser como uma mensagem de vitória e derrota aqui. Então, se ganharmos a mensagem principal em letras grandes na tela, diremos alvo e alguns pontos de exclamação. A mensagem secundária menor dirá: você bala os agressores. Se perdermos, a mensagem principal dirá: mensagem secundária menor de Borlaug dirá que você perdeu. Além disso, exibiremos valor da variável dinâmica para mostrar quantos filhotes foram comidos. Além do coma dos filhotes. Não seja fácil. Este é um jogo de física sobre como empurrar as coisas. Precisamos aprender a pressionar melhor. Estou usando uma boa e velha concatenação de strings para construir essa mensagem dinâmica Melhor ainda seria usar um modelo literal aqui, se você estiver familiarizado com a sintaxe moderna do JavaScript. Agora, na verdade, queremos desenhar essas mensagens. A mensagem grande terá, por exemplo, 130 pixels. Helvética. O método Filttext pegará o texto que queremos desenhar na mensagem 1 e as coordenadas x e y estarão no meio da tela horizontal e verticalmente , assim. A segunda mensagem será menor para os pixels. Helvética. O Filttext também desenhará a segunda mensagem no meio da cannabis. Mais uma meta do FiltText: daremos espaço de pontuação final e exibiremos o valor da pontuação final lá, além de ponto final para finalizar a frase. E outra frase dirá: pressione R para bater de cabeça novamente. Quero mostrar como implementar uma funcionalidade de reinicialização muito simples. Sempre que pressionamos a letra R no teclado, eu também a desenharei no meio , na vertical e na horizontal. Agora eu tenho que espaçar essas mensagens para que elas não sejam desenhadas umas sobre as outras. Eu ajusto a posição vertical da mensagem um em -20 pixels, movendo-a para cima na direção negativa no eixo y vertical. A mensagem dois será mais 30, empurrando-a para baixo. A mensagem três será mais 80. Vamos ver como isso parece. Sim, esse espaço é muito melhor. Temos nossa mensagem vencedora. Quando a pontuação é maior do que a pontuação da vitória, dizemos que o jogo acabou como verdadeiro. No objeto principal do jogo, defino a propriedade game over e inicialmente a defino como false. A coisa mais simples que podemos fazer aqui é congelar a animação quando o Game Over acontece, eu posso fazer isso, por exemplo , aqui embaixo, digo que sirva apenas o próximo quadro de animação. Se o fim do jogo for falso, quando o fim do jogo se tornar verdadeiro, o jogo congelará. Eu disse ganhar uma pontuação para uma só para testar. E exibirá uma mensagem de vitória ou derrota. E vai congelar assim. Eu desço até a linha 519 e excluo esse bit. Eu gostaria que o jogo continuasse rodando, mas faremos com que o novo X pare de aparecer e eu evitarei que os inimigos reiniciem. E então eles simplesmente saem da tela e ficam lá. Então, sempre que o jogo acabar, os objetos que já estão na área de jogo terminaremos a ação que eles estão realizando, mas novos objetos não aparecerão mais no jogo, nós apenas terminaremos de jogar atrás da mensagem de vitória ou derrota, mas o novo x não aparecerá mais e os inimigos pararão, entrem. O jogador ainda será interativo e ainda poderemos movê-lo no mundo vazio do jogo. Como implementamos isso? É simples. Em primeiro lugar, digamos que uma mensagem de vitória ou derrota apareça e ainda temos alguns ovos da classe de larvas eclodindo Garantiremos que obtenhamos pontos somente se o fim do jogo for falso. Quando o Game Over acontecer, os filhotes ainda aparecerão do x restante, mas o que quer que aconteça não afetará mais nossos pontos de pontuação. Na classe inimiga. Eu só quero que os inimigos reiniciem se o fim do jogo for falso, assim, quando o GameOver acontecer, e isso terminará seu movimento pela área do jogo, mas eles não serão mais reiniciados para a direita. Então, novos inimigos deixarão de chegar. Aqui embaixo, só queremos adicionar um novo x ao jogo se o fim do jogo for falso, mensagem de vitória ou derrota aparecer, o x existente, ainda terminaremos a eclosão, mas o novo x deixará de aparecer. Vamos testá-lo. Eu tenho uma mensagem vencedora. Eu tenho alguns ovos que sobraram, mas esses filhotes não aumentam mais nossa pontuação. temos alguns inimigos, mas eles saem lentamente da tela e não voltam novamente. O jogador permanece interativo e ainda podemos movê-lo. Perfeito. Eu queria testar a mensagem de afrouxamento, em vez de empurrar intencionalmente filhotes na frente dos inimigos para serem comidos, vou colocar manualmente os filhotes perdidos, os dez aqui na linha 380. Sim, recebemos que estamos perdendo a mensagem. Isso é ótimo. Eu disse mais links de toque de volta a zero. Adicionar uma fonte personalizada é fácil. Você pode escolher qualquer fonte que quiser. Eu vou acessar as fontes do Google. Eles têm milhares de fontes disponíveis lá. Quero usar essa fonte inspirada em quadrinhos chamada bankers. Eu clico aqui para selecionar a fonte e clico aqui, o que gerará algum código que eu possa usar para incluir essa fonte em meu projeto. Eu copio essas três tags de link e as colo aqui no cabeçalho do meu arquivo HTML de índice, antes das minhas folhas de estilo personalizadas, para que a fonte fique disponível a partir daí. Em seguida, copio a regra CSS. Eu o uso aqui. No caso deste projeto, posso simplesmente aplicar essa família de fontes a todos os elementos. Talvez você queira ser mais específico e aplicado apenas ao elemento de tela, por exemplo , agora, a fonte está disponível em nosso projeto. Então, aqui na linha dez, eu disse tela para banqueiros de 40 pixels. Isso afetará o texto que exibe pontuação e os temporizadores acima dos ovos incubados. Também queremos colocar banqueiros aqui e aqui Se quisermos usá-lo para mensagens de fim de jogo, vamos rapidamente quando o jogo estiver pronto. Isso parece melhor, não é? Porque usamos segurança e restauração. Ouvi dizer que tudo o que eu faço nesta área permanecerá restrito ao jogo. A mensagem é Canvas. sombras custam caro em termos de desempenho e alguns navegadores não lidam bem com elas, mas se eu as limitar a esse trecho de texto, ficaremos bem. Vamos tentar. Precisamos definir a distância horizontal entre a forma e a sombra. Pode ser um valor positivo ou negativo, dependendo se você deseja que a sombra fique à esquerda ou à direita. deslocamento da sombra y também pode ser positivo ou negativo. Ele determinará a posição vertical da sombra em relação à sua forma original. A cor da sombra será preta. Também podemos definir o desfoque de sombra, se quisermos. Esse é opcional, então vou deixá-lo no zero padrão, o que significa mais nobre. Lembre-se de que tipos de sombras só serão desenhadas se a cor da sombra for definida como um valor não transparente. E se pelo menos uma das propriedades desfoque de sombra, deslocamento de sombra X ou deslocamento de sombra y for definida como um valor diferente de zero. As sombras dão um bom destaque ao texto e fazem com que ele se destaque um pouco mais do fundo. Eu disse filhotes perdidos para dez novamente, só para ver a mensagem solta. Qual é a aparência com novas fontes e sombras. Ótimo, isso parece ótimo. Eu disse que os filhotes que usaram fio dental voltaram ao zero. 31. Reiniciar o jogo: Temos uma mensagem aqui que diz pressionar R para bater de cabeça novamente, o que significa pressionar R para reiniciar o jogo. O que estou prestes a mostrar é a maneira mais simples de implementar a funcionalidade de reinicialização do jogo na classe de jogadores. Eu crio um método personalizado que chamo, por exemplo, restart. Por dentro, quero definir as propriedades do jogador como elas são quando um novo jogo começa. Vamos dar uma olhada rápida no que temos aqui. Quero que o jogador se mova para a posição inicial. Vamos redefinir a colisão x e a colisão por quê? Também garantiremos que imagem do sprite se mova junto com o círculo de colisão. Então, precisarei dessas duas linhas de código. Portanto, o método de reinício do jogo moverá a área de colisão do jogador e a folha de sprite do jogador de volta à sua posição inicial. Vamos até a classe principal do jogo e aqui dentro do ouvinte do evento key down, eu digo que se a tecla que foi pressionada é nosso método de reinício de chamada em uma classe de jogo como esta. Eu defino esse método aqui embaixo. E aqui, definiremos todas as propriedades em seu estado inicial. Isso será diferente de projeto para projeto. Acabamos de escrever essa base de código para estarmos familiarizados com ela e entendermos o que precisa ser alterado para reiniciar o jogo ao seu estado inicial. Primeiro, chamaremos o método de reinicialização que acabamos de definir na classe do jogador. Vamos testá-lo. Eu movo o jogador e quando pressiono a letra R no teclado, o jogador volta à sua posição inicial. Até agora tudo bem. Podemos usar uma técnica semelhante para dar ao jogador a capacidade de retroceder no tempo. Por exemplo, acabamos de definir estado e a posição em x e n são n filhotes do que eram, digamos, 5 segundos atrás. Isso pode nos dar uma jogabilidade interessante. De qualquer forma, queríamos reiniciar o jogo. Então, aqui no construtor, eu verifico o que precisa mudar. Sei que terei que redefinir o conteúdo das minhas matrizes. Eu pego obstáculos, x , inimigos, filhotes e partículas. E eu disse tudo isso de volta para matrizes vazias como esta. Eu testo isso. Ok, então quando eu pressiono R, agora isso acontece e é necessário executar o método init novamente para recriar inimigos e obstáculos, já que os excluímos. Sim, agora, quando eu reinicio, os obstáculos são regenerados em posições aleatórias e também geramos novos inimigos. Eu posso ver que também preciso redefinir a posição atual do mouse. Eu pego esse mouse de pontos e o defino com esses valores iniciais, na verdade, então eu posso simplesmente copiar isso. Eu também redefini a pontuação para zero e também perdi filhotes. Também preciso definir o fim do jogo como falso para garantir que x continue aparecendo e os inimigos continuem sendo reiniciados. Vamos testá-lo. Eu jogo. Eu ganho o jogo. Eu pressiono o lado da artéria. Eu jogo. Eu, quando redimensiono, jogo, ganho, parece estar funcionando. Tudo bem. Eu disse que ganhei até 30 pontos. Ok, eu passo algum tempo testando a jogabilidade e há mais algumas coisas que podemos ajustar. Como você sabe, podemos ativar e desativar o modo de depuração pressionando letra D. Estou aqui em nossa classe de larva personalizada. Eu gostaria que nossos links de hachura de larva interagissem com o método de atualização x inside na classe larva. Usei o operador de propagação para expandir x em uma matriz de objetos de colisões. Isso tornará o X obstáculos sólidos e impossíveis e os filhotes terão que rastejar ao redor deles. Isso tornará o jogo um pouco mais difícil e o jogador terá que se envolver mais para tirar o x do caminho e garantir que os filhotes cheguem a uma área segura o mais rápido possível. Às vezes, os filhotes podem até ficar presos entre vários ovos e obstáculos. E se não tivermos liberdade a tempo, eles serão comidos pelos inimigos. Como alternativa, posso remover X daqui. Eu vou até a aula de ovos e o método de atualização de insights. Em uma aula, vou expandir os filhotes em uma matriz de objetos de colisão. Isso, por outro lado, facilitará o jogo, porque os filhotes não apenas conseguirão empurrar o x para fora do caminho. Às vezes, eles também colocam o x na frente deles, mais perto da zona de segurança. Você mesmo pode escolher o que quer fazer aqui, dependendo se você quer que seu jogo seja fácil ou mais desafiador neste momento, você conhece bem esse código. Então, se você quiser, você pode ter um pouco de liberdade e ajustar a jogabilidade como quiser. Também quero que os círculos de colisão dos filhotes correspondam melhor à folha de sprite -40 aqui funcionarão bem Acho que para alinhar melhor a parte inferior dos círculos de colisão, onde está a parte inferior dos corpos dos filhotes. Quero que essas áreas de colisão correspondam o máximo possível à obra de arte. Estamos no modo de depuração agora, mas lembre-se de que o jogo foi feito para ser jogado com círculos de colisão ocultos. Quando o modo de depuração está desligado. Você pode usar a mesma técnica para alinhar melhor os círculos de colisão nos inimigos com a pequena sombra no chão abaixo de cada inimigo. Eu farei isso em um minuto porque na próxima lição vamos nos concentrar em adicionar novos tipos de inimigos. Também lhe darei todas as planilhas de personagens e adicionaremos muitas animações a este projeto. Eu me diverti muito projetando essas criaturas e fazendo-as se mover. Espero que você goste. Percebi uma pequena vantagem, quando recebemos uma mensagem de fim de jogo e os inimigos restantes comem alguns filhotes que sobraram. As variáveis de filhotes perdidos ainda aumentam. Precisamos garantir que o valor dos filhotes perdidos permaneça o mesmo após a exibição da mensagem de fim de jogo. Portanto, só executarei esse bloco de código se o fim do jogo for falso. Isso também deixará os filhotes vulneráveis na tela do jogo. Se você não quiser isso, você pode aplicar essa instrução if somente a uma linha de código que aumente a perda de filhotes. Se o fim do jogo for falso, filhotes perdidos mais, mais. 32. Ampliar o curso do inimigo: Ok, vários tipos de inimigos. Quero criar duas turmas infantis que se estendam. Classe de pais e inimigos. Dito que a pele será verde viscosa, varredura da casca será a casca oca de madeira com chifres, espinhos e folhas. Estamos fazendo um tema mágico da floresta. Então, eu estava me divertindo com o design de criaturas desta vez. Então, novamente, estamos estendendo a classe JavaScript aqui. Fazemos isso para evitar a repetição a frio. Quaisquer propriedades e métodos compartilhados entre todos os tipos de inimigos estarão na classe endêmica principal. Somente as propriedades específicas para tipos individuais de inimigos estarão nas classes infantis. Você pode baixar todos esses recursos premium ou os ativos na seção do projeto abaixo. Começaremos adicionando oito escaneamentos de casca diferentes. O que você acha demais dos meus designs? Eu queria que este fosse feito de madeira e fosse oco por dentro, mas a luz saísse dos olhos e da boca, como uma abóbora de Halloween. Eu escondo a imagem com CSS. Eu também substituí a imagem do sapo. No início, tínhamos quatro personagens. Agora ele tem oito versões diferentes. Muitas palavras, lodo, olhos e tentáculos. Aqui na classe principal de inimigos, posso ver que o construtor espera o jogo como argumento. Quero executar o construtor na classe inimiga principal e adicionar um pouco a ela de dentro da classe de skin TO. Principalmente as propriedades que serão específicas apenas para o tipo inimigo da pele de sapo. Portanto, no construtor da classe infantil, esperaremos o jogo como um argumento. E então primeiro temos que executar construtor na classe inimiga principal, também chamada de superclasse. Chamamos de construtor de superclasse. E passamos a referência, o objeto principal do jogo, porque sabemos o esperado aqui na linha 264. Todas essas propriedades serão específicas apenas para o NMAI da pele da bolsa porque a imagem será diferente. O tamanho dos sprites também, e a largura e a altura dependerão desse tamanho do sprite. E a colisão x usa a largura e sua fórmula. Então eu recortei tudo isso, baseei na classe infantil aqui. Portanto, essas propriedades terão um valor diferente para cada classe secundária. Eles não serão compartilhados. Vou copiar esse bloco de código e colocá-lo em casca de árvore. Aqui teremos que ajustar os valores. largura do sprite aqui é de 183 pixels. altura do sprite será 280, largura e a altura de um único quadro. Esse inimigo será maior. Esta imagem pontilhada será Id of Barack. Demos a ele um índice HTML? Então, essas são as propriedades compartilhadas por todas as classes inimigas. Eles também compartilharão essa gaveta e esse método de atualização. As propriedades das classes infantis serão exclusivas para cada tipo de inimigo, temos dois tipos de inimigos. Eu gostaria de adicionar aleatoriamente um ou outro. Eu cuido de adicionar inimigos aqui dentro do método de adição de enema na classe de jogo de domínio. Aqui, posso dizer que se Math.random for menor que 0,5, Math.random chamado sozinho dessa forma retornará um número aleatório maior ou igual a zero e menor que um. Portanto, podemos dizer que serão menos de 0,5 e cerca de 50 por cento dos casos. Se for menor que 0,5, colocaremos a nova pele de sapo em nossa matriz de inimigos. Caso contrário, nos outros 50 por cento dos casos, vamos produzir uma nova pele de camurça. Bom. Agora, adicionamos aleatoriamente os dois tipos de inimigos. quadro x está definido como zero, então estamos obtendo apenas os quadros da primeira coluna. Eu quero que seja zero ou um. Então Math.random times two embrulhado em Math.floor assim. Agora estamos recebendo todos os quadros. Também mostrarei como animar essas planilhas, mas isso tornará o jogo mais caro, pois a arte do personagem que escolhi usar neste projeto é muito detalhada e as imagens são bem grandes. Existem técnicas de otimização que podem ser usadas para animação de sprites, mas isso está além do escopo desta aula para iniciantes. Falaremos sobre isso um pouco mais em breve. Quando os inimigos são reiniciados, somos aleatórios no quadro y, mas não no quadro x. Vamos fazer isso copiando a linha 270 e colocando-a aqui dentro desse bloco de instruções if. Agora que estamos totalmente aleatórios na imagem quando os inimigos reiniciam a posição, posso colocar essas linhas que aleatoriamente classificaram o quadro x e o quadro y no método de atualização compartilhada. Porque meus dois tipos de inimigos têm a mesma quantidade de molduras para linhas e duas colunas. Se cada folha de sprite inimiga tivesse um número diferente de quadros, eu teria que estender esse método de atualização. Em cada subclasse do NMS, estamos adicionando e excluindo objetos como partículas, filhotes e x. É sempre bom verificar nossos objetos para garantir que os objetos antigos estejam sendo removidos corretamente e que não temos um crescimento infinito na matriz. Eu poderia registrá-lo no meu método de atualização, por exemplo, mas executar um log de console como esse, 60 vezes por segundo, custa muito caro em termos de desempenho. Poderíamos simplesmente registrar objetos do console sob demanda sempre que pressionarmos um botão dentro do nosso ouvinte de eventos com chave para baixo, eu digo que se pressionarmos C no teclado, só então o console registrará esses objetos de jogo de pontos. Agora eu posso jogar o jogo e ocasionalmente, pressiono C para verificar se a matriz de objetos do jogo contém apenas os objetos corretos que estão atualmente ativos no jogo. Se você, por exemplo, notar que está aumentando infinitamente e contém mais e mais partículas. Sabemos que não estamos removendo corretamente os objetos de partículas antigos. Tudo parece bom nesse caso. 33. Modo de tela cheia simples: Portanto, em nosso jogo, podemos pressionar a letra D para ativar e desativar a letra R do modo de depuração para reiniciar o jogo, C para executar um registro personalizado do console. E também mostrarei como ativar e desativar um modo de tela cheia muito simples pressionando a letra F. Vamos discutir o método , por exemplo, alternar em tela cheia. Vamos defini-lo aqui na classe principal do jogo. O Javascript tem uma funcionalidade nativa de tela cheia. Essa API integrada contém um conjunto de métodos que nos permitem apresentar um elemento específico e todos os seus descendentes em um modo de tela cheia, removendo todos os elementos da interface do usuário do navegador e outros aplicativos da tela. Quando eu chamo o elemento de tela cheia do ponto do documento assim, ele retorna o elemento que está sendo apresentado atualmente no modo de tela cheia. Se retornar nulo, significa que o modo de tela cheia não está em uso no momento. Então, aqui estou dizendo que, se atualmente não estamos sendo usados no modo de tela cheia nesta página, quero entrar no modo de tela cheia. método de solicitação em tela cheia exibirá o elemento em que ele é chamado no modo de tela cheia. Nesse caso, eu queria exibir toda a página da web. Portanto, documento, que representa o elemento do documento DOD da página da web, que representa o elemento raiz do documento, neste caso, a tag HTML. Eu quero que ele exiba a tela inteira de toda a página da web, a partir do código do elemento raiz, para sair da tela cheia. Isso é simples, então dizemos que se o documento não sair da tela cheia, Executar, documento que sai do método de tela cheia. É um pouco estranho, mas vai funcionar. Durante isso, toda a página da web será transformada. Fullscreen Canvas não ocupará toda a largura da tela. Estamos movendo o player usando o mouse, então temos que ter cuidado ao redimensionar o Canvas neste projeto, pois as coordenadas do mouse são calculadas de uma determinada maneira. Se eu fornecer largura dinâmica ao Canvas com CSS, ele começará a nos fornecer coordenadas erradas. No navegador Google Chrome. Se eu entrar em tela cheia assim e ampliar para que o jogo cubra a tela inteira, as coordenadas do mouse ainda serão calculadas corretamente e o ganho poderá ser jogado. Você pode testá-lo por si mesmo pressionando F para entrar em tela cheia e, em seguida, mantenha pressionada a tecla Control e toque nas teclas de mais ou menos para ampliar e reduzir. No Mac, usamos a tecla Command em vez disso. Estamos prestes a animar as planilhas. Essas imagens são grandes e isso prejudicará o desempenho do computador quando começarmos a trocá-las prejudicará o desempenho do computador quando começarmos muito rapidamente em cada objeto animado ativo Talvez você queira salvar a base de código como está agora e continuar em uma cópia desta pasta para que você possa manter bases de código estáticas e totalmente animadas e compará-las lado a lado em termos de desempenho em sua máquina. Depende de você. Também deixarei o código-fonte completo desta etapa do projeto disponível para download na seção de recursos abaixo. A pasta com o código-fonte completo desse estágio é chamada de código-fonte versão cinco. 34. Animação completa da folha de sprite do jogador: É hora de animar totalmente a planilha de sprite do jogador. Já estamos usando a folha de sprite, mas estamos recortando apenas o primeiro quadro em cada linha para virar o jogador em direções diferentes. Temos variáveis auxiliares aqui, em que um quadro x circulará na mesma linha da esquerda para a direita repetidamente nessa animação específica. E o quadro y determinará qual linha estamos animando. No caso da folha de sprite do jogador, linhas diferentes têm o mesmo personagem do jogador voltado em direções diferentes. Estamos usando o método de desenho para desenhar esta planilha no Canvas. Estamos usando a versão mais longa com uma imagem de nove argumentos que queremos desenhar. Em seguida, definimos a área de corte com os quatro argumentos a seguir. Fonte x, y, largura da fonte e altura da fonte. E os últimos quatro argumentos e definimos onde no destino da cannabis, colocamos essa imagem recortada no destino x, destino. Por que largura e altura do destino? Eu expliquei isso algumas vezes neste curso porque é muito importante entender se você deseja usar a imagem de desenho para uma animação de sprite. Já temos uma funcionalidade que troca o quadro y por uma linha diferente com base em onde queremos que o jogador fique voltado. Tudo o que preciso fazer agora para ficar totalmente animado é fazer o quadro X alternar entre o quadro zero e o quadro máximo repetidamente. Vamos lidar com a animação de sprites aqui dentro do método de atualização. E também reutilizaremos esse código para outros objetos. Precisaremos de mais uma propriedade auxiliar chamada max frame. folha Sprite tem 58 quadros por linha. É uma imagem de planilha enorme. Estaremos voando por ela, recortando diferentes molduras para criar uma ilusão de movimento e animação. Deixe-me te mostrar. É fácil. Se o quadro x for menor que o quadro máximo, quero que o quadro x aumente em um à medida que o método de atualização é executado repetidamente. Isso percorrerá a linha de animação da esquerda para a direita. Quando atinge o quadro final, x não é mais do que o quadro máximo. Então, caso contrário, a declaração será executada. Ele redefinirá o quadro x de volta para zeros para que ele possa alternar novamente. É isso. Estamos animando o jogador. Eu tento andar por aí, virar o jogador em todas as direções possíveis para ver se está tudo bem. Agora, nosso jogador está devidamente vivo. Quando você prepara sua planilha você joga a arte corretamente, como eu a preparei para você. Animá-lo com código é muito fácil. Tudo que você precisa é desse bloco de código simples. 35. Animação completa da folha de sprite da Larva: Eu copio esse bloco de código. Vou reutilizá-lo na aula de larvas. Eu vou para a aula de larvas. Aqui. Também precisaremos da propriedade auxiliar de quadro máximo. Nesse caso, o quadro máximo é 38. planilha Larva tem 38 quadros por linha. Como de costume, você pode baixar todas as planilhas na seção de recursos. Eu o coloco na pasta do meu projeto e o trago para o projeto aqui. Id será larva sublinhada sprite e fonte será a mesma larva sprite dot PNG. Em script.js, eu aponto essa propriedade de imagem de ponto para esse novo ID. Tem 38 quadros por linha. E, novamente, quero alternar entre o quadro zero e o quadro máximo. Eu copiei aquela animação de sprite chamada blog do método de atualização na classe player e colei aqui. É muito simples, então não há necessidade de fazer alterações. Se o quadro x for menor que o quadro máximo, continue aumentando o quadro x em um. Caso contrário, redefina o quadro x para zero para que ele possa alternar novamente. Todas as planilhas são configuradas de forma que o último quadro e o primeiro quadro se conectem em um loop de animação contínuo. Então, passar por cima deles repetidamente dessa forma criará uma sequência de animação perfeita. Agora você pode ver nossos filhotes rastejando com seu Antonio, balançando para cima e para baixo, e são corpos moles se movendo enquanto rastejam para a segurança de uma floresta de cogumelos. Perfeito. 36. Inimigos sprite sheet completa animação: Também preparei planilhas de animação para os inimigos. Eu dou a ele um ID de casca de casca e o ID será bark skin dot PNG. O lodo verde. Um será o ID da pele adulta e a fonte disse à pele PNG. Eu tenho uma pele de Hyde Park com CSS. Também disse à pele. E a larva sprite. Copiaremos novamente esse bloco de código. É importante observar como as planilhas desses inimigos são organizadas. Precisamos considerar que, ao escrever nosso código, posso ver que temos quatro tipos de sapos e quatro tipos de pele escura, cada tipo em uma fileira separada. Nesse caso, sabemos que o quadro y controlará qual tipo de inimigo será exibido. quadro x percorrerá novamente a linha de animação, dando vida à criatura. Também é importante notar que eu tenho a mesma estrutura para sprites de pele escura e sacola. Ambos têm quatro linhas, 38 quadros por linha. Assim, posso lidar com a animação na classe inimiga principal , pois a lógica será compartilhada para as duas subclasses de animus. Eu disse quadro x a zero aqui. Mostre por que escolheremos aleatoriamente um dos quatro inimigos disponíveis para animar. quadro máximo será 38. No método de atualização, eu manipulei a animação de sprite aqui, colo o bloco de código que copiei. Em primeiro lugar, eu queria usar a planilha de pele de sapo aqui e a casca de casca aqui assim. Quando você vê uma falha de animação como essa, geralmente significa que o tamanho do quadro que definimos está errado. Então, as molduras estão meio que deslizando para dentro. As peles escuras estão animadas corretamente. Eu verifico o tamanho da moldura em capas incontáveis e posso ver que a única moldura na planilha tem 154 pixels de largura e 238 pixels de altura. Você obtém a largura de um único quadro dividindo a largura da planilha inteira pelo número de colunas, pelo número de quadros por linha. E você obterá a altura de um único quadro dividindo a altura de toda a planilha pelo número de linhas. Perfeito, estamos animando os filhotes dos jogadores e oito tipos diferentes de inimigos. Estamos vendo áreas circulares de colágeno, mas o jogo foi feito para ser jogado com o modo de depuração desativado. Precisamos garantir que as áreas de colisão correspondam às pequenas sombras sob nossa bóia e inimigos, pois quero que elas tenham um pixel perfeito aqui. E sapos, canas e peles de veado têm tamanhos diferentes de sprites. Não consigo compensar o sprite X e o Sprite. Por que aqui no método de atualização compartilhada por um determinado valor e alinhe os dois do mesmo lugar. Em uma situação como essa, quero comentar essa linha e estender esse método de atualização em cada subclasse para fornecer valores ligeiramente diferentes para cada tipo de elemento. Para fazer isso da maneira mais simples. Em delitos, pode uma subclasse que estende classe mãe da superclasse NME, que chamamos de atualização. Se eu apenas definir atualização dessa forma, esse método teria prioridade e substituiria completamente o método com o mesmo nome na classe principal. Eu realmente não quero isso. Quero que o código dentro do método de atualização na classe inimiga seja executado. E eu queria adicionar um código personalizado específico apenas para a classe de pele de sapo aqui dentro desse método de atualização em uma classe infantil. Eu faço isso usando a super palavra-chave que representa o pai. E depois minha aula. Aqui estou dizendo que execute todo o código dentro do método de atualização no pai e depois a classe na superclasse. Então eu quero pegar essa linha de código, e essa linha só executará quatro escaneamentos. Eu tento mais 50. Meu objetivo é alinhar círculo de colisão com a pequena sombra no chão abaixo dos inimigos flutuantes o próximo possível Quebramos as peles de casca porque valor y do sprite não está atualizado agora Vamos corrigir isso em um minuto. Primeiro, eu quero alinhar em direção a, vou fazer a altura -100. Ok, que tal a altura do inimigo dividida por dois -100? Isso está se aproximando do que eu quero. Talvez -90. Sim. Quando pressiono o d para ativar e desativar o modo de depuração, posso ver que as pequenas capas de tom de sombra do solo se alinham muito estreitamente com a área do círculo de colisão. Perfeito. Eu copio esse bloco de código e coloco aqui em nossas capas de casca de árvore. Aqui eu vou fazer -100. Então, é assim que você estende um método que existe em uma classe principal, como esse método de atualização que temos aqui. E em cada subclasse, primeiro chamamos o código dentro do método update na classe principal da linha 302. E então podemos adicionar algum código adicional que será exclusivo apenas para essa subclasse específica. O uso dessa técnica pode dar cada tipo de inimigo um padrão de movimento diferente, comportamento diferente, ataques diferentes, animações diferentes e assim por diante. Você pode baixar a pasta final do código-fonte que chamei de versão seis do código-fonte na seção de recursos abaixo e compará-la com seu próprio código se estiver enfrentando algum problema. Agora é hora de você adicionar alguns recursos adicionais. Talvez tente verificar minhas outras classes de desenvolvimento de jogos, nas quais aplicamos sons ou gerenciamento de estado , e as usamos neste jogo. Confira a coleção bônus de ativos de arte disponível na seção de recursos abaixo Vou deixar muita arte em vários formatos com peças separadas para que você possa misturar, combinar e se divertir com ela, se quiser. Obrigado por passar seu tempo comigo. Espero que você tenha muito valor hoje. Parabéns por concluir o projeto. Te vejo mais tarde. 37. Projeto de bônus (opcional): Crie ambientes de jogo animados para personagens controlados por teclado direcional, obstáculos posicionados aleatoriamente e recursos de arte ambiental Vamos criar jogos usando HTML, CSS e Javascript simples, sem estruturas e sem bibliotecas Como sempre, se eu desenhar todos os meus elementos interativos em uma única tela, colocar camadas de elementos pode ser um desafio Podemos usar o método de classificação de matriz incorporado para reorganizar os objetos do jogo com base em sua coordenada y vertical e desenhá-los nessa Com esse truque simples, podemos ter um personagem que contorna obstáculos de uma forma que faça sentido visual. Vamos criar um jogo do zero e aprender sobre desenvolvimento em Front e Y e programação orientada a objetos. 38. Configuração de projeto de bônus: Trabalharemos com três arquivos em HTML de índice. Eu crio uma página web simples e genérica e dou um título a ela. Eu vinculo a folha de estilo CSS, crio cinco elementos de tela em HTML com um ID de tela um e vinculo o arquivo GS do script. Eu crio um mergulho com a identificação de um invólucro e coloco uma tela dentro Eu crio uma tag de imagem com sobreposição de origem, sobreposição Alt e sobreposição de ID Você pode baixar todos os recursos artísticos na descrição do vídeo na seção de arquivos do projeto. Também estou fornecendo todas as peças separadas em alta resolução para que você possa compor suas próprias imagens de fundo exclusivas Se você quiser a dica de otimização número um, use CSS simples. Para imagens de fundo grandes, posso desenhar e animar tudo em telas Eu costumo fazer isso hoje. Quero mostrar como você pode combinar CSS e método de desenho de imagem em tela para maximizar o desempenho. Isso eliminará a necessidade de desenhar essas imagens grandes na tela repetidamente para cada toque de animação E isso melhorará o desempenho de nossos jogos. As transformações CSS usam GPU. Eles são mais rápidos e têm uma marcação mais simples porque o CSS tem muitas palavras-chave incorporadas que facilita muito as transições animadas Talvez para o seu jogo, você precise que tudo seja desenhado em tela. Mas hoje vou combiná-lo com um pouco de CSS para aproveitar ao máximo as ferramentas e técnicas que o ambiente de desenvolvimento web de front-end oferece. 39. Aprimorando os mundos de jogos: Intalsíase. Eu dou ao Canvas uma imagem de fundo, você pode baixá-la na descrição do vídeo ou montar seu próprio plano de fundo a partir das peças individuais que estou fornecendo Como você pode ver, criamos duas camadas. Arte de fundo, sobre a qual nosso personagem será desenhado, e algumas sobreporão imagens, folhas e grama que estarão em frente à nossa cena florestal O personagem do jogador poderá se esconder atrás deles. A menos que definamos a largura e a altura da tela, padrão será sempre 300 vezes 150 pixels. Vamos torná-lo do tamanho certo com o script Java em um minuto para revelar a arte de fundo completa Eu também poderia ter colocado essa imagem de fundo no elemento do invólucro Eu dou uma borda ao elemento principal do invólucro. Meu navegador está com o zoom reduzido para que eu possa colocar tudo na tela As imagens que estou apresentando hoje são otimizadas para 1.280 vezes 720 pixels O jogo que estamos construindo é bem grande. Você pode ajustar os tamanhos se quiser. Se você é iniciante, pode ser uma boa ideia seguir exatamente meu código, usar o mesmo tamanho e as mesmas imagens e só fazer alterações quando o projeto inteiro Eu quero centralizar o contêiner, então eu dou a ele a posição absoluta. Você pode ver que o invólucro não começa no canto superior esquerdo Existem algumas margens e preenchimentos padrão para garantir que nossa página tenha a mesma aparência em todos os navegadores diferentes Farei uma redefinição global para todos os elementos da página e definirei a margem como zero, preenchimento como zero e o tamanho da caixa na caixa de borda Para garantir que o preenchimento e a borda dos elementos sejam incluídos em sua largura e altura totais, quero centralizar a corredeira no meio da página vertical Eu já lhe dei uma posição absoluta. Agora eu posso dar 50% para a esquerda para 50% e transformar a tradução -50% -50%. Também preciso alinhar a sobreposição Eu lhe dou a posição absoluta superior 50% esquerda 50% transform translate -50% -50% Isso o alinhará exatamente na parte superior do Há muitas outras coisas que podemos fazer aqui. Por exemplo, vamos trazer mais duas imagens para o projeto. Eu crio uma imagem chamada leaves underscore left. Eu dou a ele o mesmo atributo e ID antigos. Você pode baixar todas essas imagens na descrição do vídeo, eu crio outra imagem chamada leaves underscore, certo? Assim. Eles poderiam ter sido apenas uma parte da imagem de sobreposição principal, mas eu quero que sejam animados e interativos Como dissemos, as transformações CSS são processadas pela GPU. Vamos aproveitar isso para tornar nosso mundo de jogo um pouco mais vivo e interativo. Css faz algum trabalho para nós. Nem tudo precisa ser desenhado em tela. Tudo se misturará muito bem e nosso jogo ficará ótimo. Eu miro as folhas da esquerda na posição superior para 50 pixels da posição esquerda para -100 folhas à direita, serão posicionadas -150 pixels atrás da borda direita aqui no invólucro Defina overflow como oculto para recortar e ocultar tudo o que está fora do contêiner principal do jogo Podemos fazer muitas coisas com ele agora. Por exemplo, podemos fazer com que as folhas saiam do caminho. Quando passamos o mouse sobre a tela, somos apenas um observador silencioso escondido nos arbustos Vamos empurrar as folhas um pouco para fora do caminho para que possamos ver. Quando passo o mouse sobre a tela, quero aplicar um pouco de CSS às folhas à esquerda Vou mudar a posição esquerda deles e vou girá-los em mais 15 graus. Essa declaração CSS não funcionará. Funcionaria se leaves left fosse filha do elemento canvas, mas não é. Se olharmos aqui, podemos ver que eles são irmãos. Eles estão no mesmo nível no elemento três e ambos compartilham o invólucro como pai Para aplicar algo nas folhas à esquerda, quando a tela é colocada sobre ela, precisamos usar algo chamado geral de irmãos seletor geral de irmãos seleciona todos os elementos que são irmãos próximos Se eu observar nossa estrutura de elementos, temos uma imagem de sobreposição de elementos de tela diretamente sobre ela Na verdade, está impedindo que o evento de passar o mouse na tela seja acionado Quero que a imagem de sobreposição fique na parte superior, mas quero que seja invisível para todas as interações do mouse Para que eu possa interagir com a tela que está embaixo dela. Eu posso fazer isso definindo a propriedade de eventos de ponteiro na sobreposição para não ser assim momento, a tela está apenas nessa pequena área, mas em breve a redimensionaremos para preencher todo o contêiner e testar o evento de foco. Por enquanto, preciso mover o mouse sobre a pequena área da tela. Aqui funciona ao passar o mouse, eu defino a posição esquerda para -150 pixels Vou dar às folhas algum tempo de transição para que elas realmente sejam animadas Vamos tentar 0,3 segundos. Agradável. Eu faço a mesma coisa com as folhas, certo? Eu também faço uma transição, talvez apenas 0,2 segundos. Portanto, eles não estão completamente sincronizados. Nós podemos fazer muitas coisas. Por exemplo, aplique animação de quadros-chave para fazer parecer que as folhas estão se movendo com o vento. Vou chamá-lo de oscilação. Em 0%, ele começará no topo dos 50 pixels. Teremos um ponto de interrupção de 50% em 55 pixels. E em 100%, voltará para 50. Podemos aplicá-lo dando às folhas a propriedade de animação à esquerda. Nós chamamos isso de oscilação. O tempo diminuirá. O tempo do loop de animação será de 1,5 segundos. , desse jeito, ele vai rodar apenas uma vez, mas eu quero um loop infinito, não vejo nenhum movimento. Vamos definir isso para 65 pixels, certo? Preciso ter certeza de que tenho a sintaxe correta para os quadros-chave Esse ponto e vírgula tem que estar aqui, temos folhas se movendo com o vento Vamos tentar 56 pixels. Eu copio essa linha e a aplico também nas folhas, certo? Eu quero dessincronizá-los. loop neste será de 1,6 segundos, mesmo que fizemos com a sobreposição Preciso definir eventos de ponteiro nenhum nas duas folhas para garantir que eles não bloqueiem os eventos do mouse na tela que está abaixo dela É assim que podemos desenhar e aprimorar parte da cena mundial do seu jogo com CSS. É uma boa prática quando se trata de desempenho. Se o seu projeto permitir, sinta-se à vontade para usá-lo com mais frequência. 40. Configuração de JavaScript: Também desenharemos algumas imagens com Javascript, principalmente nosso personagem animado de Alber controlado por teclado Quando trabalhamos com imagens em Javascript, sempre precisamos garantir que a imagem esteja totalmente carregada e disponível antes que o código que depende dessa imagem seja executado. Caso contrário, receberemos um erro. Há muitas maneiras de fazer isso. Podemos usar promessas ou uma maneira muito simples é agrupar todo o código Javascript dentro do ouvinte de eventos de carregamento evento de carregamento é acionado quando a página inteira é carregada, incluindo todos os recursos dependentes , como folhas de estilo e imagens. Dessa forma, posso simplesmente colocar minha planilha como um elemento IMG normal no HTML do índice, e o Javascript aguardará que a imagem seja carregada dentro da função de retorno de chamada anônima Aqui eu monto minha tela. Precisamos de uma referência ao elemento canvas, uma variável constante que chamo de canvas e quero apontar o Javascript para esse elemento de tela que criamos na linha 12 no índice HGML Demos a ele um ID de canvas one Na verdade, não precisamos usar get element by ID aqui, porque todos os navegadores criam automaticamente variáveis Javascript para todos os elementos com um ID. O nome dessa variável automática é sempre o mesmo que ID. É bom para o bug, mas como os navegadores colocam todas essas variáveis no namespace global, fácil substituí-las acidentalmente É melhor usarmos get element by ID, assim. O contexto Ctx é canvas dot get context two D para gerar uma instância da API built-in canvas two D que contém estado da tela e todos os dois métodos de desenho O elemento do invólucro pai foi definido para 1.280 vezes 720 Vamos garantir que o elemento de tela tenha o mesmo tamanho. Isso revelará a arte de fundo completa. O que você acha disso? Como eu disse, também compartilharei peças individuais com você em alta resolução. Você pode misturá-los e combiná-los e criar seus próprios ambientes de jogo exclusivos, se quiser. Todos os recursos de arte que estou compartilhando neste vídeo são livres de direitos autorais. Eles foram desenhados e desenhados por mim ou por artistas que contratei. Você pode modificá-los e usá-los como quiser. Vou comentar as animações CSS nas linhas 39.31. Acho que será Temos alguns códigos de script Java para fazer agora para dar vida ao nosso personagem animado Vamos seguir os princípios da programação orientada a objetos hoje e dividir nosso código em classes individuais. Cada turma terá responsabilidades diferentes e faremos com que se comuniquem e trabalhem juntas para criar o produto final. classe personalizada que eu chamo, por exemplo, o manipulador de entrada, manipulará as entradas do teclado Quando uma tecla é pressionada em um teclado, essa classe processa isso e transforma e armazena esse valor para que possa ser usado por outras classes. Class Owl Bear será nosso principal personagem animado. O jogador poderá controlá-lo usando o teclado. objeto de classe será usado como uma planta para criar árvores, arbustos e grama gerados aleatoriamente Depois de colocá-los no mundo do jogo, aprenderemos como organizá-los de forma que sejam desenhados na frente e atrás do jogador, dependendo de onde o jogador está atualmente. Isso é muito importante porque, sem isso, em um jogo como esse, desenhar um jogador em cima de tudo não faria sentido visual. Precisamos de um pouco de lógica para que o jogador possa contornar esses dois obstáculos D e eu vou te mostrar uma maneira fácil de fazer isso. Finalmente, precisaremos de uma classe que eu chamo de jogo. É aqui que toda a lógica de todas as classes se reúne. Esse será o cérebro principal da nossa base de código. construtor de classes criará uma instância de uma classe para nós com base em um plano que definimos internamente Vamos definir um plano para nossa classe de jogo principal. Quero que essas aulas sejam independentes e modulares. Vamos considerá-la com e com a altura da área de jogo como argumentos como esse. Nós os convertemos em propriedades de classe. Tomamos a largura e a altura passadas como argumento para o construtor da classe e as convertemos propriedades de largura e altura nessa instância da classe do jogo Para criar uma instância da classe de jogo, eu crio uma variável constante. Eu chamo, por exemplo, jogo e uso a nova palavra-chave. A nova palavra-chave no script Java é usada para criar uma instância de um objeto que tem uma função construtora nova palavra-chave procurará uma classe com esse nome e acionará seu construtor Constructor criará um novo objeto Java script em branco e definirá automaticamente seu protótipo nos bastidores Ele vinculará essa palavra-chave para apontar para esse objeto recém-criado e atribuirá ao objeto todas as propriedades e valores conforme os definimos no esquema Até agora, nosso objeto de jogo só terá propriedades de largura e altura. Nesse caso, posso ver que essas propriedades são esperadas como valores que estão sendo passados de fora. Fazemos isso para manter nossas aulas independentes e modulares. Eu passo a largura da tela da linha quatro. Tal como acontece com a altura da tela a partir da linha cinco como altura. Como quero que o jogo tenha o mesmo tamanho da tela, quero que a área do jogo use todo o elemento da tela. É assim que você cria uma classe em Java script e como você cria uma instância dessa classe. Como você usa essa classe para criar um objeto com base no blueprint da classe Vamos analisar a variável de jogo da linha 26 para verificar se tudo correu bem no console verificar se tudo correu bem no Eu posso ver que a variável do jogo armazena uma referência apontando para um objeto do jogo com propriedades de altura e largura definidas com os valores corretos, perfeitos. Se você receber algum erro ao escrever sua base de código orientada a objetos, sempre poderá consolar seus objetos dessa forma e verificar se todas essas propriedades têm valores Se você ver indefinido aqui, você sabe que há um problema em seu código e pode rastrear esse valor na sua base de código para encontrar o problema 41. Controles de teclado: classe de manipulador de entrada está aqui para acompanhar as teclas do teclado à medida que elas são pressionadas e liberadas pelo usuário E os converterá em valores que podem ser usados por outros objetos em nossa base de código e os armazenará. Eu também dou a ele um construtor. É assim que faremos com que nossos objetos se comuniquem entre si. manipulador de entrada espera uma referência apontando para o objeto principal do jogo como argumento Todo esse objeto dentro do qual convertemos essa referência em uma propriedade de classe, como este jogo, que foi passado como argumento, é convertido em propriedade do jogo. Nessa instância do manipulador de entrada, objetos de classe no script Java são tipos de dados de referência Não estou criando uma cópia do objeto do jogo aqui, estou apenas criando uma variável que aponta para um espaço na memória onde o objeto principal do jogo está armazenado. Isso é ótimo porque quando os valores dos objetos do jogo são atualizados, essas mudanças são imediatamente visíveis a partir dessa referência de jogo de pontos. Aqui na classe principal do jogo, eu crio uma propriedade chamada dot last key input handle. A classe atualizará esse valor na classe do jogo conforme as teclas são pressionadas e liberadas. Inicialmente, eu disse isso para indefinido. Hoje, aprenderemos sobre os controles do teclado e manteremos apenas a última tecla pressionada ou liberada na memória. É tudo o que precisamos controlar, quatro direções para o personagem. Se eu quisesse acompanhar várias teclas pressionadas, em vez disso, criaria uma matriz aqui E eu adicionaria e removeria as chaves de lá. Eu já fiz isso antes e talvez eu faça isso mais tarde nesta série. Dependendo da complexidade e precisão dos controles de teclado que precisaremos. Também quero criar automaticamente uma instância do manipulador de entrada ao criar o objeto do jogo Vamos colocar um console aqui que diz manipulador de entrada criado dentro do construtor da classe de jogo Eu crio uma propriedade chamada essa entrada. Eu o defini igual a um novo manipulador de entrada como este. Na linha oito, posso ver que construtor da classe do manipulador de entrada espera uma referência ao objeto principal do jogo Aqui estou criando uma instância de manipulador de entrada dentro dessa classe de jogo Eu passo essa palavra-chave. Essa palavra-chave usada dentro da classe do jogo aponta para toda a classe do jogo em si. Ele representa o objeto dentro do qual foi definido. Bom porque eu coloco esse código dentro do construtor da classe de jogo quando crio uma instância da classe de jogo na linha 31, ele também cria automaticamente uma instância da classe do manipulador de entrada para mim crio uma instância da classe de jogo na linha 31, ele também cria automaticamente uma instância da classe do manipulador de entrada para Ele o salvará como uma propriedade de entrada de pontos na própria classe do jogo principal. Posso confirmar que o manipulador de entrada foi criado porque vejo esse console aqui. Perfeito. Essa é uma maneira simples de conectar suas classes e fazê-las trabalhar juntas para criar um jogo completo. Também é muito modular, então você pode adicionar posteriormente mais recursos e mais classes ao seu jogo, se quiser. Como vemos nesse console, todo o código dentro do construtor da classe é executado automaticamente quando uma instância dessa classe é criada Esse código nem sempre precisa ser definido apenas nas propriedades da classe. Podemos colocar qualquer código aqui. Nesse caso, quero aplicar ouvintes de eventos automaticamente Quando uma instância da classe de identificador de entrada for criada, vou colocá-la aqui. Ouviremos o evento key down na função de retorno de chamada Vou executar uma função de retorno de chamada do console no ouvinte de eventos em Javascript com acesso ao objeto de evento gerado automaticamente que contém todos os detalhes sobre o evento que contém todos os detalhes sobre o evento Posso atribuir a ela um nome de variável aqui, convenção geralmente é uma letra como esta. Já fiz isso antes, então sei que esse objeto de evento gerado automaticamente tem uma propriedade chamada key que nos dá o nome da tecla que foi pressionada tecla de console, preciso clicar no Canvas para ter certeza de que está selecionado E quando pressiono qualquer coisa no teclado, os nomes das teclas estão sendo impressos no console. Hoje vamos nos concentrar nas teclas de seta direcionais. Então seta para a esquerda, seta para a direita, seta para baixo e seta para cima. Quero salvar esses valores-chave na última propriedade da chave da linha 28 no objeto principal do jogo. Temos uma referência ao objeto principal do jogo aqui na linha nove. Mas quando eu consolog esse jogo de dentro do ouvinte de eventos, notamos que a referência É porque essa função de retorno de chamada esquece que foi inicialmente definida aqui dentro do método construtor na classe do manipulador de entrada Quero que a função se lembre de que quero vincular seu escopo para que uma referência a essa palavra-chave , quero vincular seu escopo para que neste caso, representando esse objeto manipulador de entrada, bem como acesso a essa propriedade do jogo, esteja disponível de dentro dessa função de retorno de chamada ao teclar o ouvinte de eventos sempre que esse ouvinte de eventos for executado teclar o ouvinte de eventos sempre que esse ouvinte de eventos Quero ter certeza de que essa função nunca esqueça que foi inicialmente definida dentro de um objeto Eu posso usar o método de vinculação Javascript para fazer isso, ou posso simplesmente usar a função ES six arrow As seis funções de seta do Es não se vinculam a isso, mas herdam aquela do escopo principal Chamamos isso de escopo lexical. O escopo da função de seta sempre dependerá de onde em nossa base de código ela foi originalmente definida em seu escopo léxico original No nosso caso, ele foi definido aqui dentro desse método construtor Portanto, ele sempre terá acesso a essa propriedade do jogo de pontos e a quaisquer outras propriedades que possamos definir dentro desse construtor Mais tarde, quando eu refatorar essa função de retorno de chamada em uma função restrita, podemos consolo E vemos que ele aponta para o objeto principal do jogo. E todas as suas propriedades estão visíveis lá, incluindo a última chave, se eu consolar o último valor da chave da linha 28, inicialmente ele é definido como indefinido Pegamos a propriedade chave do objeto de evento e eu a atribuo a ela desta forma. Agora, a propriedade da última chave na classe principal do jogo sempre acompanha a última tecla que foi pressionada. Também preciso saber quando a última chave foi lançada. Eu crio outro ouvinte de eventos, desta vez para o evento Key Up Agora estamos armazenando o nome da última tecla que foi pressionada ou liberada. O problema é que ele apenas registra o nome da chave. Preciso saber se essa tecla foi pressionada ou liberada. Vou concatenar a letra maiúscula P dentro do evento key down para especificar que a tecla desse nome Em seguida, adicionarei R maiúsculo na frente do nome da chave dentro do evento keyup para que saibamos que a chave foi liberada Agora que estamos obtendo esses valores no console, você pode ver o valor desse gameta de pontos A última propriedade da chave está mudando. Pressionamos a seta aqui e soltamos a seta aqui. Eu posso remover o console. Ok. Agora que sabemos que está funcionando no objeto principal do jogo, definimos a propriedade last key e criamos uma instância da classe manipuladora de entrada À medida que a classe do manipulador de entrada é inicializada, aplicamos uma chave para baixo e uma chave para cima para os ouvintes de eventos Isso atualizará dinamicamente última propriedade da chave para que nosso objeto de jogo e todos os objetos conectados a ele estejam sempre cientes de qual tecla foi pressionada ou liberada Usaremos esses valores para controlar o personagem do jogador, o urso-coruja. Vamos definir isso aqui. 42. Personagem do jogador: Primeiro, precisamos conectá-lo ao objeto principal do jogo. Garantimos que ele espere uma referência ao objeto do jogo como uma visão do argumento e, como de costume, a convertemos em uma propriedade de classe. Vamos começar dando a largura e a altura de 100 pixels. Sua posição inicial será de 200 pixels da borda esquerda e 200 pixels da parte superior. Eu lhe dou um método de desenho para desenhá-lo em tela. Ele espera que o contexto seja um argumento para especificar em qual elemento de tela queremos desenhar. Pegue essa variável de contexto e eu chamo o método retangular embutido de Phil nela retângulo de Phil espera x, y de largura e altura Então eu passo as propriedades do construtor owl bear. queremos desenhar um retângulo preto Primeiro, queremos desenhar um retângulo preto representando o urso-coruja, mesma forma que fizemos com a classe manipuladora de entrada Quero criar uma instância do objeto Owl bear automaticamente Quando eu crio uma instância da classe principal do jogo, eu instanciei aqui dentro do construtor da classe e a atribuo a uma propriedade Eu chamo essa coruja de urso na linha 20, vejo que ela espera o jogo como uma discussão Eu passo essa palavra-chave porque estamos dentro dessa classe de jogo. classe de jogo terá um método especial que eu chamo de render para atualizar e desenhar todos os objetos do jogo na tela. Eu pego o oil bear da linha 42 e chamo o método de desenho associado da linha 27. Esse método espera contexto como argumento, que é esse CTX da linha três Preciso repassá-la. Eu ainda vou chamar isso de contexto. Será esperado como argumento pelo método de renderização principal. Nós o passamos para o método draw no Owl Bear. Eu pego um jogo, chamo seu método de renderização da linha 44 e passo CTX da linha três como um argumento que receberá um contexto de nome de variável e será transmitido quando necessário Perfeito. Estamos desenhando nosso urso coruja Parece um quadrado preto simples porque é isso que definimos aqui no método draw na classe owl bear. Vamos fazer com que ele se mova. Eu ofereço um método de atualização personalizado para cada quadro de animação. Eu quero que X aumente em um, fazendo com que ele ande para a direita. Na verdade, temos que chamar esse método de dentro da renderização na classe do jogo. Aqui eu excluo esse console para realmente ver o movimento Precisamos chamar esse método de atualização repetidamente. Chamá-lo da renderização apenas uma vez no carregamento da primeira página, como estamos fazendo aqui, não animará nada Precisaremos de um loop de animação. Eu crio uma função personalizada. Eu chamo, por exemplo, animar por dentro. Eu coloquei essa chamada de renderização. Para fazer com que seja repetido, chamo o método de quadro de animação de solicitação incorporado . Esse método atualizará nossa página da web antes da próxima repintura executando um código em uma função, que nós o passamos como argumento Se eu passar para animar o nome de sua função principal, ela criará um loop de animação infinito O Animate é chamado. Ele chama render para desenhar e atualizar nossa solicitação de urso-coruja. quadro de animação acionará a animação novamente, para que o ciclo se repita. quadro de animação da solicitação fica no objeto da janela que representa a janela do navegador. Mas também pode ser chamado diretamente assim. Sem colocar a janela na frente dela. É melhor usar o quadro de animação de solicitação para animação do que usar a solicitação de intervalo definido. quadro de animação ajusta os quadros por segundo à taxa de atualização da tela e também gera automaticamente um registro de data e hora Talvez precisemos dele mais tarde para acionar a animação. Acabei de chamar animate. Como esse bom jogador estivesse se movendo para a direita , deixando rastros Precisamos ter certeza de excluir todos os desenhos em tela entre cada loop de animação. Eu faço isso chamando o método de retângulo transparente embutido. Quero limpar a tela inteira da coordenada 002, largura e altura da tela Agora temos um quadrado preto em movimento representando o personagem do jogador. Perfeito. Está se movendo para a direita porque aqui dentro do método de atualização na classe Alber, que é nosso player, estamos aumentando posição x horizontal em um para cada quadro de animação Eu atribuo a ela uma propriedade que chamo de velocidade X. Inicialmente, eu a defino como zero Também precisaremos da velocidade Y, velocidade vertical para movimentos para cima e para baixo. Lembre-se de que estamos construindo um personagem controlado pelo teclado que pode andar em quatro direções. Eu aumentarei esse ponto x pelo valor atual da velocidade x. Assim como essa coordenada y vertical do urso-coruja aumentará pelo valor da velocidade Y. Isso é ótimo porque eu posso dar um valor positivo para mover para a direita, um valor negativo para mover para a esquerda Eu posso fazer com que ele se mova mais rápido dando ele um valor maior por quadro de animação. Aqui, esse número significa pixels por quadro de animação. Um valor positivo na velocidade Y fará com que o jogador se mova para baixo. O valor negativo fará com que ele suba. O valor positivo em X e Y fará com que ele se mova para baixo, certo? Poderíamos facilmente usar isso para movimentos em oito direções. Como você pode ver aqui na linha sete, temos a classe Input handle que captura a entrada do teclado Ele transforma os nomes das teclas padrão para indicar se a tecla foi pressionada ou solta Por pré-pendente ou R na frente do nome real da chave. Ele salva o valor da última tecla que foi pressionada ou liberada aqui na linha 46. Dentro desse ponto, última propriedade chave na classe principal do jogo. Porque estou passando uma referência a toda essa classe de jogo quando crio Alber na linha 48 O objeto Alber tem acesso ao último valor chave por meio desta referência de jogo de pontos aqui na linha 21 Eu posso dizer que se este jogo funcionar, última chave é a seta para a esquerda. Se a seta esquerda foi pressionada, defina a velocidade x para menos um para fazer o personagem se mover para a esquerda na direção negativa no eixo x horizontal Se eu clicar na tela e pressionar a tecla de seta para a esquerda, estamos nos movendo perfeitamente. Quando eu solto a chave, o jogador não para aqui. Eu posso ver que estamos capturando o valor da imprensa e do comunicado Eu posso dizer L, definir a velocidade x para zero, agora podemos nos mover para a esquerda e podemos parar. Eu também poderia criar um método de velocidade definida que, sempre que for chamado, definirá a velocidade x e a velocidade Y. Elas serão passadas para ele como argumentos Pegamos esses argumentos e atualizamos as propriedades da classe a partir da linha 26.27. Agora posso substituir essa linha por essa velocidade definida E eu passo menos um como velocidade x e zero como velocidade y. Na declaração L, eu defino a velocidade horizontal e vertical como zero É melhor ter uma variável de velocidade máxima global em vez de codificar um e menos um aqui Caso a velocidade precise ser dinâmica, jogador pode ter algum aumento temporário de velocidade. Ou talvez o jogador esteja se movendo mais devagar enquanto rasteja Para fazer isso, definimos a propriedade de velocidade máxima. Nós o configuramos para três pixels por quadro de animação e o substituímos aqui. Vamos expandir essa declaração. Também verificarei L se essa última tecla de ponto e ponto for pressionada R para a direita, assim. Nesse caso, definimos a velocidade x como mais a velocidade máxima y como zero. Agora podemos nos mover para a esquerda e para a direita e paramos de nos mover quando a tecla RO é liberada. Aperfeiçoe outro L se bloquear. Tenha cuidado com a sintaxe e os colchetes. É muito fácil perder um colchete e quebrar toda a base de código ao fazer isso Quando pressionamos a tecla R, chamamos a velocidade definida. Definimos a velocidade x como zero e a velocidade Y como menos a velocidade máxima porque queremos subir na direção negativa no eixo Y vertical. Nós fazemos mais um L if block se última tecla for pressionar a seta para baixo assim. Definimos a velocidade x como zero e a velocidade y como mais a velocidade máxima. Também podemos nos mover para a esquerda, direita, para cima e para baixo. Quero ter certeza de que o jogador não pode sair da tela. Preciso definir alguns limites horizontais. Se a posição x horizontal dos jogadores for menor que zero, coloque-a novamente em zero, esse é o limite esquerdo Caso contrário, se a posição horizontal dos jogadores for maior do que este jogo com a largura da área de jogo, este jogo está vindo daqui. De lá, estamos navegando até essa propriedade. Na linha 64, a posição atual do jogador é maior do que a largura do jogo menos a largura do jogador que definimos na linha 22 O que significa que a borda direita do retângulo do jogador está tocando a borda direita da Certifique-se de que o jogador não possa mais se mover para a direita. Assim, aumentarei a velocidade do player para dez pixels por quadro de animação. Agora você pode se mover para a esquerda e para a direita para testar os limites horizontais. Os limites verticais serão um pouco diferentes porque, em nosso mundo de jogo, o chão termina aqui. Eu não quero que o jogador ande por essa área, a menos que seja uma criatura voadora. Acho que esse urso-coruja é definitivamente pesado demais para voar. Gostaria de saber se existe alguma espécie rara de urso-coruja voador Talvez possamos procurá-la mais tarde na série Temos essa área no topo onde eu não quero que o jogador possa andar. Eu crio uma propriedade de classe chamada margem superior. Inicialmente, eu o configurei para 200 pixels. Vamos colocá-lo aqui. Aqui em cima, definiremos limites verticais. Digamos que se esse ponto y for menor que zero, y é igual a zero. Isso criará um limite aqui. O jogador não pode ultrapassar a borda superior da tela. Na verdade, eu quero que o limite seja menor porque toda essa arte ambiental, eu digo zero mais essa margem de inclinação do jogo, como Eu posso deletar zero plus aqui. Claro que agora o limite superior está aqui, 200 pixels da borda superior Podemos andar por aí, mas não podemos mais andar até aqui. Perfeito, fará mais sentido visual quando o jogador for um personagem animado real, não apenas um quadrado preto, estamos chegando lá rapidamente. O limite inferior é simples se a posição Y vertical do jogador for maior que a altura do jogo menos a altura do O que significa que a parte inferior do retângulo do jogador está tocando a borda inferior da Não permita que desça mais desse jeito. Vamos testá-lo. Sim, temos todos os quatro limites. Perfeito. Vamos transformar esse simples retângulo preto animada de quatro caracteres direcionais 43. 4 folhas de sprite direcional: Começo adicionando a imagem da planilha ao índice HTML desta forma Fazemos isso dessa maneira porque todo o nosso Javascript está dentro do ouvinte de eventos de carregamento Se eu colocar minha planilha aqui, o Javascript aguardará que essas imagens sejam totalmente carregadas e estejam disponíveis antes de começar a animá-las Você pode baixar todos os ativos de arte do projeto na seção de recursos do projeto abaixo. Eu dei uma ideia de, vamos trazê-lo para fora da embalagem assim Você pode ver que temos oito linhas de animação na planilha, quatro direções, e cada direção tem inatividade e tem inatividade Analisaremos isso mais detalhadamente em um minuto, ocultaremos a imagem em si com CSS. Queremos desenhá-lo com o script Java na tela. Você pode ver que todo o código está dentro do ouvinte de eventos de carregamento Todo esse código interno será executado somente quando Owl bear e todas as outras imagens totalmente carregadas aqui dentro do construtor da classe Alber, que é nosso personagem de controle do jogador Adicionaremos uma referência apontando para a imagem da planilha como uma propriedade de classe Eu a chamo de imagem e a defino como igual a uma variável chamada al bear. Essa variável não foi declarada por nós, mas todos os navegadores criam referências automaticamente, variáveis globais geradas automaticamente para todos os elementos com um ID Esse elemento tem um ID. Portanto, sabemos que nosso navegador gerou automaticamente uma variável global para ele. Normalmente, usaríamos get element by ID aqui na linha 29, mas não precisamos. O único problema é que essa é uma variável global. Portanto, estamos inserindo variáveis globais em nossas classes agora, essa variável corre o risco constante de ser sobrescrita acidentalmente Vamos testar se funciona. De qualquer forma, eu tomo o contexto referindo-se à variável CTX da linha três Eu chamo o método de desenho de imagem embutido do Canvas. Desenhar imagem espera pelo menos três argumentos. Eu passo essa variável global gerada automaticamente que foi criada a partir do ID dos elementos como a imagem que queremos desenhar. E eu passo esse ponto x da linha 24 e esse ponto y da linha 25 para dizer ao script Java onde queremos desenhar a imagem na tela. Agora estamos desenhando a planilha inteira nessas coordenadas x e y. Podemos nos mover usando as teclas de seta. Por causa da lógica que escrevemos anteriormente, é mais seguro usar get element by ID aqui. Então, vamos fazer isso. Eu uso essa imagem de pontos aqui. Ok, isso ainda funciona. É bom animar qualquer planilha, precisamos saber as dimensões de um único quadro de animação dentro dessa planilha Vou colocá-los em variáveis separadas. Eu chamo a primeira largura do sprite e a defino para 200 pixels A altura do Sprite também será de 200 pixels. Se você estiver usando uma planilha de sprite diferente, poderá calcular a largura de toda a planilha de sprites e dividi-la pelo número altura do sprite é a altura de toda a imagem da planilha do sprite dividida pelo número de linhas Nesse caso, as propriedades de largura e altura serão iguais à largura e altura do sprite Mas mantê-los separados pode ser uma boa prática, pois podemos então usar escala ou randomizar os tamanhos aqui sem afetar esses valores que sempre precisam permanecer os Para recortar corretamente quadros individuais da folha de sprite, a imagem de desenho tem três versões A primeira versão espera três argumentos, acabamos de fazer isso aqui na linha 35. A segunda versão usa cinco argumentos, quarto e o quinto argumentos opcionais definem a largura e a altura da imagem e a imagem será esticada ou comprimida para caber nessas Agora estou comprimindo toda a enorme planilha em uma área de 200 vezes 200 pixels A terceira versão do método de desenho de imagem espera que nove argumentos nos dêem o maior controle sobre a imagem que queremos desenhar. Essa é a versão que precisamos usar para animar nossa planilha Esses nove argumentos são a imagem que queremos desenhar. Fonte x, fonte Y, largura e altura da fonte, a área que queremos recortar da imagem de origem. Os últimos quatro argumentos chamamos de destino X, destino Y, largura do destino e altura do destino. Isso definirá onde na tela queremos desenhar aquela parte recortada da imagem Digamos que eu queira apenas recortar a moldura do canto superior esquerdo. Vou cortar da coordenada 00 até a largura do sprite. E os valores de altura do sprite que definimos para serem exatamente do mesmo tamanho um único quadro em nossa planilha, nesse caso 200 vezes 200 pixels Bom, estamos usando o método de desenho de imagem para recortar um único quadro. Eu posso multiplicar a largura do sprite aqui por um número e a altura do sprite aqui Agora eu tenho uma maneira de pular a planilha de sprite de quadro em quadro 00 Isso é 1100? Isso é 1200? Isso é moldura? Esse número navega horizontalmente. Esse outro número determinará em qual linha da planilha de sprites estamos Você pode ver que podemos pular para uma linha de animação diferente toda vez que eu altero esse número. Vou colocar esses valores nas propriedades da classe. O Frame X tratará da navegação horizontal por sprites O quadro Y será para navegação vertical por sprites. Novamente, quando o Quadro X e o Quadro Y são 00, estamos mostrando o quadro superior esquerdo. Posso alterar esses valores para pular pela planilha. Agora que entendemos como esses argumentos usados método de desenho de imagem recortam molduras individuais, podemos realmente animar nossa planilha e realmente dar vida a esse poderoso urso-coruja Vamos começar animando apenas uma única linha para cada quadro de animação Se o quadro X for menor que o quadro máximo, nesse caso, o quadro máximo será o número de quadros por linha, 30. Se o quadro X for menor que 30, continue aumentando o quadro X em um L, o que significa que é igual ou maior que 30 Redefina o quadro X de volta a zero para que ele possa circular novamente. Bom, estamos animando essa linha, porque aqui definimos o quadro Y como três Se eu definir como zero, estamos animando essa linha Se eu mudar esse número, estamos animando linhas diferentes Queremos trocar essas linhas de animação dependendo de duas coisas Em que direção o jogador está andando e o jogador está andando ou apenas parado? Se tivéssemos um personagem complexo com vários movimentos de ataque e assim por diante, eu sugeriria fazer um padrão de design de estado aqui, como fiz na classe Endless Runner Nesse caso, a lógica de movimento e animação é simples o suficiente para ser tratada aqui dentro do método de atualização Sempre podemos expandi-lo para um padrão de design de estado posteriormente, se decidirmos adicionar mais movimentos de jogadores. Deixe-me mostrar, aqui estamos definindo a velocidade do jogador com base em quais das quatro teclas de seta foram pressionadas para a esquerda, direita, para cima ou para baixo. Vamos copiar esse bloco de código. Tenha muito cuidado com os colchetes aqui, é fácil perder um e decifrar seu código Se você se lembra aqui, estamos acompanhando as teclas que foram pressionadas e liberadas. As teclas pressionadas começam com P maiúsculo, as teclas liberadas começam com R. Como fazemos isso? Acho que isso deve funcionar. Quando pressionamos a tecla de seta para a esquerda, queremos animar essa linha Esse quadro de pontos y é igual a três linhas de animação andando à esquerda. Quando soltamos a seta para a esquerda desta forma, definimos o quadro y22 Queremos ficar parados para a esquerda. Vamos testá-lo. Preciso definir a velocidade para 00 quando lançarmos aqui. É melhor quando pressionamos a seta, certo? Eu quero animar a caminhada, certo, essa linha, esse quadro de pontos é cinco Eu copio este LF, cuidado com os colchetes. Nós mudamos para R aqui quando soltamos a seta, tecla direita. Definimos a velocidade para 00. E ajustamos o quadro Y24, Idle, certo. Animação. Perfeito. um personagem que pode andar para a esquerda e para a direita, e a lógica que acabamos de escrever corretamente navega pela planilha Estamos andando e estamos de pé. O player está animado e está reagindo corretamente às entradas do teclado. Bom trabalho. Também vamos fazer isso para movimentos verticais, para cima e para baixo. Quando pressionamos a tecla de seta para cima, queremos que o jogador se afaste do quadro da câmera às sete. Eu copio esse bloco de código. Quando soltamos a seta para cima, animamos o quadro y seis e ajustamos a velocidade para 00 Podemos andar para a esquerda, até aqui. Quando pressionamos a seta para baixo, queremos animar o quadro Y um. Eu copio esse bloco de código. Quando soltamos a seta para baixo, animamos o quadro Y zero e ajustamos a velocidade para zero Estamos caminhando e animando em quatro direções. Estamos alternando corretamente entre as animações em pé e andando para cada uma dessas direções Trabalho incrível para criar uma ideia de profundidade para fazer com que pareça que o jogador está afastando e se aproximando da câmera. Ao caminhar para cima e para baixo, farei com que ele se mova um pouco mais devagar nesse eixo Veja quanta diferença isso faz. Parece que há mais profundidade, como se o fundo estivesse mais longe da câmera do que o primeiro plano Nós andamos em uma velocidade normal, esquerda, certo? Mas subimos e descemos um pouco mais devagar. Vamos mudar a velocidade máxima para cinco. É uma criatura grande e pesada. Ele deve se mover mais devagar. Eu comento o retângulo preto, não precisamos dele agora Eu gosto muito disso. uma base para muitos jogos diferentes. Tudo o que você precisa agora são suas ideias criativas. Podemos fazer muitas coisas diferentes com isso. À medida que você aprende mais sobre Java Script Canvas e o desenvolvimento de jogos, ficará cada vez mais fácil transformar essas ideias em uma base de código funcional. Deixe-me te mostrar mais. Espero que você esteja obtendo algum valor hoje. Deixe-me saber nos comentários se você aprender algo novo porque estamos usando apenas a última tecla que foi pressionada ou liberada para controlar o personagem do jogador. Há um problema comum quando caminhamos em uma direção, digamos que estou andando para a direita, eu pressiono a tecla de seta para a esquerda. O jogador vira à esquerda e só então eu solto a tecla de seta direita. A última tecla é soltar para a direita, que fará com que o jogador pare brevemente antes segurar continuamente a tecla esquerda o faça andar para a esquerda novamente. Você pode tentar descobrir que teremos esse problema em qualquer direção, caminhe em algum lugar. E quando você pressiona uma tecla direcional diferente antes de soltar a anterior, isso fará com que o jogador pare um pouco antes continuar se movendo nessa nova Corrigir isso é fácil. Quando soltamos a seta para a esquerda. Queremos que esse código seja executado somente se a velocidade dos jogadores for menor que zero. Só pare o jogador e faça com que ele fique animado inativo para a esquerda se a velocidade atual do jogador for menor que zero, se o jogador estiver se movendo para a Dessa forma, podemos entrar inativo à esquerda somente andando no estado esquerdo Quando soltamos a tecla de seta para a direita, queremos apenas interromper a velocidade de movimento e animar o modo inativo, Se o jogador estiver se movendo para a direita, se sua velocidade x for maior que zero. Eu também tenho que remover essa declaração L aqui. E agora corrigimos o problema do movimento para a esquerda e para a direita. Vamos fazer a mesma coisa para o movimento para cima e para baixo. Execute esse código somente quando soltamos a seta para cima e, ao mesmo tempo, a velocidade de movimento dos jogadores y é menor que zero quando soltamos a seta para baixo. Execute esse código somente se, ao mesmo tempo, a velocidade Y for maior que zero. Somente quando o jogador está caminhando em direção à câmera. Agora estamos nos movendo e a falha desapareceu. Bem feito. 44. Como controlar o FPS: Se eu definir a velocidade máxima para dois jogadores, o jogador se move lentamente, mas a animação do sprite exibe quadros tão rápido que o jogador está andando na lua Os pés se animam de uma forma que não corresponde à velocidade de movimento e têm uma aparência muito ruim Uma maneira de resolver isso é controlar manualmente a rapidez com que a folha de sprite é animada No momento, estamos apenas servindo um novo quadro de animação por loop. Toda vez que o quadro de animação da solicitação é chamado. Vamos criar uma funcionalidade na qual podemos definir manualmente quadros de FPS por segundo para a velocidade de animação da nossa planilha de sprites Isso pode ser feito globalmente e podemos definir o FPS para todo o jogo Ou, como vou mostrar agora, você pode definir o FPS individualmente para o objeto do jogador Talvez tenhamos alguns inimigos mais tarde com diferentes folhas de sprites E talvez queiramos que essas planilhas de sprites sejam animadas em velocidades diferentes Por padrão, o quadro de animação da solicitação se ajustará à atualização da tela Tarifa. Nosso jogo rodará mais rápido para alguém com telas de alta taxa de atualização, como aquelas novas telas de jogos, podemos controlar que, se quisermos solicitar que o quadro de animação tenha um recurso especial em que gera um registro de data e hora toda vez que chama a função animada e passa esse carimbo hora toda vez que chama a função animada de data/hora como um argumento para a função animada que ele um argumento para a função animada que Vamos usá-lo para contar milissegundos entre os quadros para que possamos definir nossos próprios quadros FBS por segundo Eu criei uma variável que chamei da última vez, ela ficará fora do loop de animação como este. Inicialmente, eu o configurei para zero. Ele manterá o valor do timestamp do loop de animação anterior Para que possamos comparar os registros de data e hora atuais e determinar quantos milissegundos passaram O valor é chamado de tempo delta. tempo delta é o número de milissegundos que passaram entre o timestamp desse loop de animação e o timestamp do loop de animação anterior Para calcular o tempo delta, sabemos que precisamos comparar esses registros de data e hora timestamp do loop anterior será mantido na última variável de tempo aqui Inicialmente, definimos como zero antes de começarmos a gerar carimbos de data/hora Como eu disse, o quadro de animação da solicitação passa argumento de carimbo de data/hora gerado automaticamente para a função Se você é iniciante, imagine que ele está sendo gerado e passado aqui desta forma No momento em que o quadro de animação da solicitação aciona essa função, atribuímos a esse carimbo de data/hora gerado automaticamente Aqui, vou chamá-lo timestamp escrito com S maiúsculo . Vamos comentar isso e consoloc timestamp Só para que eu possa ver qual formato estamos obtendo, você pode ver que são milissegundos. O primeiro valor é literalmente o número de segundos desde o início do loop, 45678 Este é o carimbo de data/hora que eu excluo. A hora delta do console é a data e hora desse loop de animação menos a última hora da linha 113, que sempre manterá um valor para data e hora do loop de Depois de calcularmos o tempo delta, definimos a última hora como data e hora Para que esse novo carimbo de data/hora desse loop possa ser usado como valor de timestamp antigo no próximo loop de animação a seguir Agora temos o tempo delta. Ele nos diz quantos milissegundos são necessários para nosso computador servir um quadro de animação Minha tela é atualizada em cerca de 60 quadros por segundo. Meu tempo delta é de cerca de 16,6 milissegundos. Isso é perfeito. Você está obtendo o mesmo tempo delta ou está obtendo um valor completamente diferente? Deixe-me saber em um comentário. Isso dependerá da taxa de atualização da tela e, parcialmente também da potência da máquina Máquinas mais fracas terão maior tempo delta. Você pode ver que aqui os primeiros valores de tempo delta são nenhum, não um número. Como executamos o animate pela primeira vez aqui no primeiro loop, esse registro de data e hora é indefinido Não havia nenhuma chamada de quadro de animação de solicitação anterior para ser gerada automaticamente. Para consertarmos isso, preciso ter certeza de passar o primeiro carimbo de data/hora aqui, apenas para evitar um valor não numérico no loop inicial Caso contrário, isso poderá nos causar problemas posteriormente dependendo de como você deseja usar seus valores de tempo delta. O carimbo de data/hora inicial será zero. No início, você pode ver que os valores estão em todo lugar, mas depois eles rapidamente se estabilizam em torno 16,6 à medida que nosso loop de animação é repetido Isso é ideal. Eu excluo o console, passo esse valor de tempo delta para o método de renderização na classe do jogo Eu me certifico de que esse valor seja esperado aqui e que eu possa repassá-lo em qualquer lugar do nosso jogo. Agora, onde quer que precisemos. Eu o passo para o método de atualização na classe Alber porque queremos usar o tempo delta para controlar quadros de FPS por segundo da animação do sprite Aí estou passando o tempo delta para atualizar método e preciso ter certeza de que é o esperado. Aqui na linha 44, estamos na classe Alber agora e podemos usar o valor do tempo delta aqui para muitas coisas diferentes Para controlar o FPS da animação de sprite, criarei três variáveis auxiliares, propriedades de classe, o FPS Vamos tentar animar 30 quadros por segundo e ver como isso se parece O intervalo de quadros será de 1.000 milissegundos dividido por 30. Isso determinará quantos milissegundos devem passar antes de passarmos para o próximo quadro na planilha de sprites O cronômetro de quadros será o contador. Ele acumulará tempo delta repetidamente à medida que nosso loop de animação for executado Quando atinge o valor e o intervalo de quadros, quando milissegundos suficientes se acumulam, acionamos determinada ação periódica Nesse caso, exibiremos o próximo quadro de animação. A mesma técnica também pode ser usada para adicionar um novo inimigo ao jogo ou algum outro evento que você queira que aconteça em um intervalo específico. Essa é uma técnica importante de entender. Vamos escrever a lógica e explicá-la quando virmos o código. São apenas algumas linhas curtas. Eu só quero executar esse código que entre quadros de sprites quando passarem milissegundos suficientes Se o temporizador de quadros for maior que o intervalo de quadros, somente então anime a planilha de sprite Caso contrário, continue aumentando o cronômetro de quadros em tempo delta. Esse código animará a planilha a partir daqui. Quando isso é acionado, também redefinimos cronômetro de quadros para zero para que ele possa contar novamente para o próximo evento periódico dentro do loop de animação. Estamos calculando o tempo delta, Estamos calculando o tempo delta diferença entre o registro de data e hora do loop de animação atual e o anterior atual tempo delta é a quantidade de milissegundos que nosso computador leva para exibir um quadro de animação temporizador de quadros começa em zero e adiciona mais e mais tempo delta à medida que o loop de animação executado até que seja mais do que um intervalo de quadros O intervalo de quadros é o ponto de interrupção que, quando atingido, aciona nosso Quando o alcançamos, lidamos com animação de sprites e redefinimos o cronômetro de quadros para zero para que ele possa ser contado novamente até o próximo quadro de animação exibido periodicamente Eu sei que isso foi muito, mas essa técnica é muito importante. Será fácil de entender quando você o usar com mais frequência. Confie em mim agora, o personagem está animando mais lentamente, apenas a troca de sprites e não a velocidade real de movimento na tela Você pode realmente ver que, quando eu defino FPS para dois quadros por segundo, ele ainda desliza suavemente sobre a tela a 60 Mas estamos limitando a rapidez com os próximos quadros nas folhas de sprite Espero que isso deixe clara a diferença. Eu posso tentar outros valores como FPS. Eu tenho que experimentar um pouco aqui e encontrar o valor certo que fará com que o lex se mova na velocidade que parece natural à medida que o jogador se move sobre o terreno gramado 50 ainda está caindo. 60, na verdade, não estamos contabilizando os valores restantes do tempo delta toda vez que acionamos nosso evento periódico Mesmo que eu tenha dito FPS 60, o verdadeiro FPS seria menor do que Para os propósitos de um projeto como esse, está perfeitamente bem. Temos um valor que podemos aumentar e diminuir para controlar a animação dos sprites Ele serve bem ao seu propósito. Eu posso simplificar essa linha de código e usar o operador ternário para escrever tudo isso em uma única Deixe-me mostrar que é fácil. ternário é o único operador Java Script com três operandos Nós a usaremos como uma simples linha se tivermos condições para avaliar a declaração L. Verificamos se o quadro X é menor que o quadro máximo. Se for um ponto de interrogação, aumente o quadro X em um, L redefina o quadro X de volta a zero. Agora, essa única linha de código faz a mesma coisa que essa instrução L if de várias linhas Você pode usar qualquer sintaxe. Eu só queria te mostrar. 45. Objetos de jogo posicionados aleatoriamente: Vamos criar alguns objetos e espalhá-los aleatoriamente pelo mundo do jogo. Eu crio uma classe, eu chamo de objeto. Como de costume, ele esperará uma referência ao objeto principal do jogo da linha 111 como um argumento que temos acesso a todas as propriedades importantes. A partir daí, convertemos essa referência em uma propriedade de classe. O mesmo que fizemos antes. Método Draw, esperaremos contexto como argumento. No interior, chamamos o método de desenho de imagem incorporado. Quero mostrar como estender classes e tirar proveito da herança, que é um dos principais princípios da programação orientada a objetos Como podemos usá-lo para reduzir a repetição de código. Eu crio uma classe chamada Busch que estende a classe de objeto da linha 102 Também terá um construtor. Eu crio outra classe. Eu chamo de planta mais uma, eu chamo de grama. Temos a classe de objeto principal. Temos três classes, Bush, plant e Grass, que estendem essa classe de objeto. O objeto é chamado de classe mãe, também chamado de superclasse Bush, Plant e Grass são classes infantis, também chamadas de subclasses Se eu usar uma propriedade ou um método dentro de uma dessas classes filhas, essa propriedade não será definida nessa subclasse script Java o procurará automaticamente em sua classe mãe, no objeto. Dessa forma, eu só posso definir esse método de desenho de uma vez. E todas as classes secundárias que estendem essa classe principal terão acesso automático a ela, elas herdam esse método Podemos fazer algo semelhante com propriedades dentro do construtor usando a superpalavra-chave que mostrarei em um minuto classe de objeto conterá todas as propriedades e métodos compartilhados entre todas as classes secundárias. Busch, Plant e Grass conterão propriedades que são específicas somente para aquela subclasse em particular Por exemplo, cada subclasse terá uma imagem diferente, mas todas passarão essa imagem diferente para o mesmo método de desenho de 907. Vamos fazer isso. Antes que eu possa usar essa palavra-chave dentro do construtor da classe filha, preciso acionar o construtor em sua classe mãe Quando digo super assim aqui, significa que quero executar um construtor do pai dessa classe A classe principal também é chamada de superclasse. Na linha cento e três, posso ver que o construtor de classes de objetos espera o jogo como argumento Eu passo essa referência ao mesmo tempo que a converto em uma propriedade de classe na classe Bush. Assim, dentro do método de desenho de imagem compartilhado, eu passo que imagem didot x e dis.yi também pode fornecer largura e altura de distorção. Se eu quiser dimensionar as imagens como você pode ver, nenhuma delas está presente nesta classe. Quero que cada subclasse tenha uma imagem diferente e precisamos saber o tamanho dessa imagem para calcular sua posição aleatória Eu terei que colocar todas essas propriedades em cada subclasse separadamente. Vamos começar com Bush. Essa imagem pontilhada será o elemento dot get do documento por ID. Aqui no índice HTML, preciso criar esse elemento de imagem normalmente Você pode baixar as imagens na seção de recursos do projeto abaixo. Eu coloquei aqui, ID será Bush, outro com uma identificação de planta como esta. O último será grass no estilo CSS. Eu uso suas identidades para esconder essas imagens. Queremos desenhá-los na tela, não como elementos de imagem reais na página da web. O ID é Bush. Eu dupliquei esse ID de código. Aqui será planejado e aqui grama. Estou tentando manter a nomenclatura simples e limpa. Eu realmente não quero dimensionar as imagens, mas se quisermos deixar a opção de dimensionamento aberta, talvez seja melhor definir a largura da imagem e as propriedades de ocultação da imagem separadamente Mesmo que neste momento eles tenham o mesmo valor. Eu já dimensionei as imagens para ficarem do mesmo tamanho em que as desenharemos no Canvas, o que é uma boa prática. A imagem de Bush tem 216 100 pixels. Eu também poderia definir essas propriedades em uma linha como esta. Depende de você. posição horizontal será um valor aleatório entre zero e a largura da área de jogo menos a largura da própria imagem Isso provavelmente precisará de alguns ajustes. Vamos ver, a posição vertical y não começará do zero, ela começará da margem superior. E a partir daí, um valor aleatório entre zero e a altura da área do jogo menos a altura da imagem menos a margem superior Acho que quando começarmos a desenhá-los, veremos se preciso fazer algum ajuste aqui. Vamos testá-lo. Na classe principal do jogo, eu crio uma propriedade chamada Number of Plants. Vamos criar dez objetos vegetais posicionados aleatoriamente. Eu crio um método inicializado que será executado apenas uma vez no carregamento da primeira página e posicionará aleatoriamente os objetos dentro dela posicionará aleatoriamente os objetos Eu crio um loop de quatro. Ele será executado dez vezes. Toda vez que funciona, criamos uma planta. Eu crio uma matriz vazia aqui que conterá todos os objetos da planta. Toda vez que os quatro ciclos são executados, eu pego essa matriz de plantas, eu, novo objeto Bush dentro da linha 111, posso ver que o construtor da classe Bush espera o jogo como argumento Eu passo essa palavra-chave porque estamos dentro dessa classe de jogo aqui. Depois de criar uma instância da classe de jogo 160, posso chamar esse método init Ele funcionará e preencherá a matriz de plantas com dez instâncias aleatórias da classe Bush Se eu quiser exibi-los na tela, preciso chamar o método de desenho deles. Eu farei isso de dentro da renderização. Eu pego plantas e, para cada objeto vegetal dentro da matriz, chamo o método de desenho associado da linha 106. Esse método espera contexto. Eu passo adiante essa referência de contexto. Ótimo, estou desenhando dez arbustos posicionados aleatoriamente. Vou até aqui, copiarei todas essas propriedades que são específicas para a classe Bush e as copio aqui dentro da classe de plantas. Eu verifico que a largura da imagem é 212 pixels, a altura é 118. Eu copio novamente, e a largura da grama será 103 e a altura será 183 pixels Bom, definimos uma classe de objeto pai onde podemos colocar todas as propriedades e métodos compartilhados, e três subclasses chamadas Bush Plant e Grass que estendem a classe mãe Para verificar rapidamente se essas classes funcionam, posso simplesmente trocar o nome da classe aqui na linha 168 A grama está funcionando, a planta está funcionando. Como podemos randomizá-las e controlar qual parte dessas dez plantas geradas aleatoriamente serão arbustos, grama ou objetos vegetais Uma maneira simples de fazer isso é criar uma variável. Aqui eu chamo, por exemplo, randomizar. Será igual a mathodrandom. Sabemos que um código aleatório de métodos como esse, por si só, retorna um valor aleatório 0-1. Digamos que eu queira que cerca de 30% desses objetos sejam instâncias da classe plant Eu digo que se Ize aleatório for menor que 0,3 L, se randomizar for menor que 0,6 quando a matemática rolar aleatoriamente entre 0,3 e 0,6, criamos uma instância da classe L de Bush, ou seja, quando Maat rola aleatoriamente entre 0,6 e um, criamos grama Não funciona porque precisa estar dentro do loop quatro, porque queremos rolar toda vez que criamos um novo objeto, não apenas rolar uma vez para todos os dez objetos. 46. Ordem de camadas e desenho em jogos 2D: Perfeito. Estamos ampliando uma classe para criar três objetos de jogo diferentes posicionados aleatoriamente Bom trabalho. Como você pode ver, a forma esses objetos estão sendo posicionados e a forma como o jogo está camadas realmente não fazem muito sentido visual O urso-coruja é desenhado primeiro na linha 162 e, em seguida, todas as plantas são desenhadas depois que as plantas sempre estarão em cima de um Se eu trocar isso , as plantas estarão sempre atrás do urso Como estamos desenhando tudo no mesmo elemento da tela, o que é desenhado primeiro está por trás, e o que foi desenhado depois está no topo, na frente. Quero desenhar objetos em uma ordem específica com base em suas posições verticais. Mais especificamente, com base na posição vertical da borda inferior. Porque cada objeto tem uma altura diferente para garantir que estejam alinhados de uma forma que faça sentido lógico e visual E para que nosso personagem possa andar ao redor deles e ficar na frente e atrás dos objetos. Quando apropriado, estamos tentando simular um mundo virtual onde as plantas crescem do solo E onde esse urso-coruja está andando em terra firme o tempo todo, mesmo quando sobe e desce. As camadas devem corresponder visualmente a isso. Na verdade, é muito fácil conseguir isso. Só preciso ter certeza de que todos esses objetos, ou seja, todos os objetos de urso e todos os objetos vegetais, estejam na mesma matriz. Para que eu possa classificá-los com base em sua posição vertical. Eu crio outra matriz chamada de objetos do jogo. Toda vez que o método de renderização é executado, eu coloco o objeto Alber dentro dele Eu espalhei todos os objetos das plantas pequenas neste operador de distribuição de matriz Usado dessa forma, nos permite expandir uma matriz em outra. Nesse caso, não faz muito sentido fazer isso toda vez que o método de renderização é ativado para cada quadro de animação Como temos apenas Alber e dez plantas estáticas, poderíamos ter feito isso dentro do método init Faria sentido fazer isso periodicamente para construir essa matriz repetidamente. Se tivermos mais objetos sendo adicionados e removidos repetidamente, por exemplo, inimigos ou power-ups que vêm e vão. Agora temos alber, todas as dez plantas no mesmo nível dentro da mesma matriz Eu posso pegar essa matriz para cada objeto na matriz de objetos do jogo. Eu chamo de método de desenho deles. Quando fazemos isso e combinamos diferentes tipos de objetos em uma única matriz, precisamos garantir que todos esses objetos realmente tenham métodos de desenho definidos em suas classes. Tenha cuidado com os colchetes. Aqui posso excluir esse código. Terei que chamar o método de atualização e passar o tempo delta. Caso contrário, eu suportarei, não me moverei nem animarei. Quando eu chamo de atualização assim, nós desenhamos e atualizamos todos os ursos. Quando tento desenhar e atualizar a primeira de dez plantas, ela é desenhada, mas não há um método de atualização para ser executado, então recebemos um erro. Preciso adicionar o método de atualização na classe de objeto para que esse código seja executado. Na verdade, não estamos atualizando os objetos, mas aqui podemos ter algumas animações. Por exemplo, arbustos e grama podem se mover quando o jogador os toca, esse código seria tratado dentro desse método de atualização Vou deixar em branco por enquanto. Agora temos a mesma coisa que tínhamos antes, mas todos os elementos que precisam ser colocados em camadas dinamicamente estão sendo desenhados e atualizados de dentro da matriz de objetos do jogo Todos os ursos estão em primeiro lugar na matriz. É por isso que é desenhado antes das plantas. As plantas são desenhadas em cima dela. Se eu as trocar, as plantas ficam para trás e o sulco é desenhado por último em cima Porque essa é a ordem em que cada método percorre a matriz de objetos do jogo. Para chamar seus métodos de desenho, queremos classificar Alber e todas as dez plantas nessa matriz com base em sua coordenada y vertical, mas não com base em seu limite superior, mas em seu limite inferior Como temos objetos com alturas diferentes, podemos fazer isso usando o método de classificação de matriz incorporado. O método sort classifica os elementos de uma matriz no local e retorna a referência à mesma matriz. Agora classificado, ele usa um argumento opcional. Chamamos uma função de comparação, qual podemos definir a lógica que descreve a ordem de classificação. Se não definirmos a condição de classificação, por padrão, todos os elementos da matriz serão apenas convertidos strings e classificados com base em caracteres, valores Unicode A é o primeiro elemento para comparação, B representa o segundo elemento. Para comparação, método de classificação comparará automaticamente todos os elementos da matriz com todos os outros elementos. Para classificá-los com base em nossos requisitos. Queremos que a condição de classificação compare o limite inferior de cada elemento desenhado na tela Portanto, é a posição vertical y mais sua altura. Assim. Estou dizendo que classifique todos os elementos em ordem crescente com base na posição vertical mais a altura Depois que a matriz é classificada, desenhamos plantas e ursos-coruja nessa ordem na tela E agora tudo faz sentido visual. Podemos contornar obstáculos e ver que o personagem do jogador e as plantas agora estão sempre desenhados corretamente na frente ou atrás. Você pode expandir este jogo de várias maneiras diferentes, criar cogumelos que crescem na área jogável para fazer o urso-coruja crescer enquanto os come Ou você pode fazer com que ele evite inimigos perigosos ou contorne obstáculos pontiagudos Eu sugiro que você crie uma cópia dessa base de código e possa usá-la como base para diferentes experimentos de jogabilidade.