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.