Computing">
Criar Jogos 3D em Javascript
Criar Jogos 3D em Javascript
Criar Jogos 3D em Javascript
Digitar o código manualmente faz você pensar mais sobre o que está digitando.
Você pode acreditar que também pode aprender lendo e copiando e colando, mas
isso não é 100% verdade. Mesmo se você estiver programando há 50 anos, nunca
copie e cole o código. Digitar dá tempo para pensar e entender o código que você
está adicionando. Isso é muito mais importante do que fazê-lo rapidamente. Cometer
erros faz parte da programação. Consertar códigos quebrados é tão importante
quanto programar. Então, cometa erros. Vai ser difícil e frustrante. E valerá a pena.
Isso significa que alcançamos a primeira dica do livro! Dicas e diretrizes,
especialmente importantes, são destacadas ao longo do livro. Preste atenção a eles,
porque eles podem realmente fazer a diferença enquanto você explora este mundo.
Mais uma coisa: se você ficar preso, sempre poderá obter ajuda!
Obtendo ajuda
Todo programador precisa de ajuda. Se você nunca programou em sua vida,
precisará de ajuda. Se você programa há 50 anos, ainda precisa de ajuda. As duas
regras para pedir ajuda:
1. Tente descobrir você mesmo primeiro.
2. Não tenha medo de pedir ajuda.
Conhecer uma linguagem de programação muito bem não é a habilidade mais
importante que um programador pode ter. A habilidade mais importante é a solução
de problemas.
Saber tudo sobre uma linguagem de computador não significa que você criará um
código 100% livre de problemas, ajuda, mas os problemas ainda acontecem, muito.
Portanto, tente descobrir problemas por conta própria. Mesmo que você não
consiga resolver um problema, ainda está aprimorando suas habilidades de
resolução de problemas.
Falando em habilidades de resolução de problemas, o Capítulo 2 - “Depuração:
corrigindo código quando as coisas dão errado” é o lugar para começar. Esse
capítulo descreve erros comuns, como resolver problemas usando o editor de código
e o navegador e como recuperar quando as coisas dão muito, muito errado.
O que é JavaScript?
Existem muitas linguagens de programação. Alguns programadores gostam de
discutir qual é o melhor, mas a verdade é que todos os idiomas oferecem coisas
únicas e que valem a pena. Neste livro, usamos a linguagem de programação
JavaScript.
Programamos em JavaScript porque é o idioma da web. É a única linguagem de
programação que todos os navegadores da Web entendem. Se você pode
programar em JavaScript, não apenas pode criar os tipos de jogos que aprenderá
neste livro, mas também pode programar praticamente todos os sites existentes.
O que há de novo nesta segunda edição deste
livro?
Esta é a segunda edição do 3D Game Programming for Kids.
A primeira edição foi incrível. Foi-me dito que sou tendencioso, mas não o vejo.
Tenho certeza de que a primeira edição foi quase perfeita.
Certo, Chris, se foi perfeito, por que fazer uma segunda edição? Bem, primeiro,
muita coisa aconteceu desde a primeira edição do livro. O mundo da programação
está sempre mudando. Sempre há novidades. A maioria dessas coisas novas não
nos ajuda a aprender, mas de vez em quando algumas delas realmente fazem a
diferença.
Três anos após o lançamento da primeira edição, havia muitas novidades úteis
que eu acreditava poder tornar a segunda edição ainda melhor que a primeira.
O outro motivo para uma segunda edição é que mudei muito desde a primeira
edição. Como programador, você nunca pode parar de aprender. Faço programação
há 15 anos e estou sempre trabalhando para aprender o máximo de coisas novas
que puder. Também trabalhei duro para me tornar um programador, professor e
escritor melhor. Esse aprendizado me ajuda a escrever livros ainda melhores!
Este é um livro muito diferente da primeira edição. Tirei alguns capítulos e
adicionei alguns completamente novos. Todos os capítulos e códigos restantes
foram reescritos significativamente. Muitas das alterações adicionam novos recursos
interessantes (controles de voo no Capítulo 5, Funções: Usar e usar novamente ou
acionar efeitos especiais).
Os novos recursos são divertidos, mas não são o principal motivo das alterações.
As mudanças são para você. Eles tornam o livro mais divertido. Eles facilitam o
aprendizado. É melhor ajudá-lo a se tornar um programador. Quais as novidades?
Praticamente tudo. Exceto pelas peças que realmente já eram perfeitas.
Vamos começar!
Introdução suficiente - vamos pular para a programação!
Notas de rodapé
https://talk.code3Dgames.com/
https://www.google.com/chrome/
http://get.webgl.org/
Essa coisa giratória e com vários lados é uma amostra de algumas das coisas em
que trabalharemos neste livro. Neste capítulo, criaremos um novo projeto chamado
Shapes (formas).
Crie seu primeiro projeto de programação
Para criar um novo projeto no 3DE Code Editor, clique no botão de menu (o botão
com três linhas horizontais) no canto superior direito da janela e selecione NOVO no
menu suspenso.
Digite o nome do projeto, Formas, no campo de texto e deixe o modelo definido
como 3D start project. Clique em SALVAR, conforme mostrado na figura.
Quando o 3DE abre um novo projeto 3D, vemos muito código já estar no arquivo
sem que os tenhamos digitados. Falaremos sobre todo esse outro código no
Capítulo 9, o que é todo esse outro código? Por enquanto, vamos começar nossa
aventura de programação na linha 22. Procure a linha que diz START CODING ON THE
NEXT LINE (iniciar o código na próxima linha).
Na linha 22, digite o seguinte:
var shape = new THREE.SphereGeometry(100);
var cover = new THREE.MeshNormalMaterial(flat);
var ball = new THREE.Mesh(shape, cover);
scene.add(ball);
Criando Esferas
As ball (bolas) são chamadas esferas na geometria e na programação 3D.
Existem duas maneiras de controlar esferas no JavaScript.
Size: SphereGeometry (100)
A primeira maneira de controlar uma esfera é descrever o tamanho dela. Quando
dissemos a new THREE.SphereGeometry (100), criamos uma bola cujo raio é 100.
O que acontece quando você altera o raio para 250?
Os computadores não conseguem fazer bola. Em vez disso, eles fingem juntando
um monte de quadrados (e às vezes triângulos) para fazer algo que se parece com
uma bola. Normalmente, teremos pedaços suficientes - os quadrados ou triângulos
que compõem a superfície - para que fiquem suficientemente próximos.
Às vezes, queremos que pareça um pouco mais suave. Para torná-lo mais suave,
adicione alguns números extras à linha SphereGeometry:
Vamos brincar!
Brinque com os números um pouco mais. Você já está aprendendo bastante aqui,
e brincar com os números é uma ótima maneira de continuar aprendendo! Apenas
não faça esses números muito altos. Qualquer coisa além de 1000 pode bloquear o
navegador! Não se preocupe se o navegador congelar ou parar de responder. Você
sempre pode corrigi-lo com as etapas descritas em: Recovering When 3DE Is
Broken. (Recuperando quando o 3DE está quebrado).
Quando terminar de jogar, mova a bola para fora do caminho, definindo sua
posição:
Os três números movem a bola para a esquerda, para cima e para trás. Isso libera
espaço para brincar com a nossa próxima forma!
Fazendo caixas com a Shape Cube (forma cubo)
Em seguida, criaremos um cubo, que é outro nome para uma caixa. Na
programação 3D, os lados de um cubo não precisam ter o mesmo tamanho.
Podemos controlar a largura, a altura e a profundidade.
Size: CubeGeometry (300, 100, 20) Para criar uma caixa, escreveremos mais
JavaScript abaixo de tudo o que usamos para criar nossa bola. Digite o seguinte
(talvez adicione uma linha em branco primeiro):
Bem, isso é chato. Por que vemos um quadrado em vez de uma caixa? A resposta
é que nossa câmera, nossa perspectiva, está olhando diretamente para um lado da
caixa. Se quisermos ver mais da caixa, precisamos mover a câmera ou girá-la.
Vamos virar a caixa girando-a:
var shape = new THREE.CubeGeometry(100, 100, 100);
var cover = new THREE.MeshNormalMaterial(flat);
var box = new THREE.Mesh(shape, cover);
scene.add(box);
» box.rotation.set(0.5, 0.5, 0);
Definir a rotação da caixa para (0,5, 0,5, 0) gira a forma para que possamos ver
que ele realmente é um cubo.
Até o momento, nossa caixa tem 100 de largura (da esquerda para a direita), 100
de altura (para cima e para baixo) e 100 de profundidade (da frente para trás).
Vamos mudar para ter 300 de largura, 100 de altura e apenas 20 de profundidade:
Se você girar um pouco o tubo (você se lembra de como fazer isso na última
seção, certo?), poderá ver algo assim:
Se você não conseguiu descobrir como girar o tubo, não se preocupe. Basta
digitar esta linha após a linha com scene.add (tube):
tube.rotation.set(0.5, 0, 0);
Vamos brincar!
Brinque com esses números e veja o que você pode criar! Se você alterar os dois
primeiros números para 100 e o último número para 20, o que acontece? O que
acontece se você criar o 1 superior, o inferior 100 e a altura 100?
O que você achou?
Um cilindro plano é um disco:
E um cilindro que tem a parte superior ou inferior do tamanho 1 é um cone:
Deve ficar claro que você pode fazer muito com cilindros, mas ainda não vimos
tudo. Ainda temos um truque.
Vamos brincar!
Brinque com números diferentes e veja o que você pode criar!
Pode parecer uma fraude fazer algo assim para criar uma pirâmide, mas isso nos
leva a uma dica muito importante em qualquer programação:
Você está indo muito bem até agora. Mova o tubo para fora do centro, como
fizemos com o cubo e a esfera:
Não se esqueça da rotação na última linha. Os planes são tão finos que você
pode não os ver quando os olha de lado.
Os números ao construir um plane são a largura e a profundidade. Um plane com
300 de largura e 100 de profundidade pode ser assim:
Isso é tudo o que há para saber sobre planes. Mova nosso plane para fora do
caminho:
var shape = new THREE.PlaneGeometry(300, 100);
var cover = new THREE.MeshNormalMaterial(flat);
var ground = new THREE.Mesh(shape, cover);
scene.add(ground);
» ground.position.set(-250, -250, -250);
Até agora você provavelmente já sabe como tornar a rosquinha menos grossa.
Isso deve fazer um donut meio comido! 3.14 parece um número estranho? Pode
ser, dependendo da quantidade de matemática que você aprendeu. Falaremos mais
sobre isso no Capítulo 7, uma análise mais aprofundada dos fundamentos do
JavaScript.
Agora temos cinco números que controlam a rosquinha. Os dois primeiros são o
tamanho da rosquinha e o tamanho do tubo. Os próximos dois controlam a
fragilidade. O último controla quanto de a rosquinha desenhar.
Animando as formas
Antes de terminar nossa primeira sessão de programação, vamos fazer algo legal.
Vamos fazer todas as nossas formas girarem como loucas. No 3DE, adicione o
seguinte código após todas as formas:
var clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
var t = clock.getElapsedTime();
ball.rotation.set(t, 2*t, 0);
box.rotation.set(t, 2*t, 0);
tube.rotation.set(t, 2*t, 0);
ground.rotation.set(t, 2*t, 0);
donut.rotation.set(t, 2*t, 0);
renderer.render(scene, camera);
}
animate();
Não se preocupe com o que tudo significa nesse código. Por enquanto, basta
saber que, em intervalos de tempo específicos, estamos alterando a rotação da
forma. Após cada alteração, dizemos ao representante - o que desenha a cena nas
telas de nossos computadores - para redesenhar as formas atuais em suas rotações
atualizadas.
Se o 3DE trava
Ao fazer animações e outras programações sofisticadas, é possível bloquear
completamente o 3DE Code Editor. Isso não é grande coisa. Se o 3DE parar de
responder, você precisará desfazer qualquer alteração feita por último. Instruções
sobre como fazer isso estão em Recovering When 3DE Is Broken (recuperando
quando o 3DE está quebrado).
Qual é o próximo
Whoa! Isso foi bem selvagem. Aprendemos muito e estamos apenas começando!
Já sabemos como codificar projetos no 3DE Code Editor. Sabemos como fazer
muitas formas diferentes. Nós até sabemos como mover e girar as coisas com
JavaScript. E o melhor de tudo, foram necessárias apenas quinze linhas de código
para criar uma animação bem legal depois de criar nossas formas. Esse é um bom
começo.
Agora que gostamos da programação em 3D, no próximo capítulo falaremos sobre
programação em navegadores da web.
Notas de Rodapé
[4] https://talk.code3Dgames.com/
[5] http://code3Dgames.com/3de
Capítulo 2
Introdução
Conheça o Editor de código
Ainda estamos usando o Editor de código 3DE que usamos no Capítulo 1,
Project: Creating Simple Shapes (Projeto: Criando formas simples). Se você ainda
não inicializou ou o 3DE, volte ao capítulo e familiarize-se com o editor.
Iniciar um novo projeto
Qualquer trabalho que você já tenha feito no 3DE é salvo automaticamente.
Assim, podemos criar um novo projeto para este capítulo e não perderemos as
formas legais do primeiro capítulo. Clique no botão de menu e escolha NOVO no
menu: vamos chamar o novo projeto de Breaking Things.
favoriteFood;
eat(favoriteFood);
Nesse caso, o 3DE nos dirá através do triângulo amarelo que a linha food não
está fazendo nada.
Para corrigir o problema, podemos alterar a linha food em uma assignment
(atribuição), que sets or assigns (define ou atribui) um valor.
O 3DE deve aceitar a nova linha favoriteFood e não exibir mais erros.
No entanto, embora o 3DE possa não relatar mais problemas, ainda há algo
errado com esse código. Para descobrir o que é isso, precisaremos do melhor amigo
do programador da Web, o console JavaScript.
Não há muita rima ou razão para as diferenças. Preste mais atenção à própria
mensagem de erro (‘Uncaught ReferenceError: eat não está definido‘) e ao primeiro
número após ‘code.html‘ (‘24‘).
Nesse momento, o 3DE ainda não deve ter triângulos vermelhos ou amarelos. E
agora nenhum erro deve aparecer no console do JavaScript. Corrigimos os erros
adicionando a função: function eat ().
Graças ao que está dentro da função eat(), o console JavaScript deve ter a
palavra "Cookie". Eventualmente, podemos querer que a função eat() crie um
monstro que coma qualquer alimento que enviarmos para a função. Por enquanto,
tudo o que eat() faz é enviar a comida para console.log (). Como você pode
imaginar, console.log () pega o que você envia e imprime no console JavaScript.
Você pode enviar mais de uma coisa para console.log () se colocar uma vírgula
entre as coisas.
function eat(food) {
console.log(food, '!!! Nom. Nom. Nom');
}
» var shape = new THREE.SpherGeometry(100);
» var cover = new Three.MeshNormalMaterial();
» var ball = new THREE.Mesh(shape, cover);
» scene.ad(ball);
Você notará que o editor não vê nenhum erro neste código. O navegador lê o
código JavaScript e diz: “Sim, isso parece perfeitamente perfeito para mim. Eu vou
carrega-lo agora!". No entanto, surgem problemas quando o código é realmente
carregado (executado) e você verá erros no console do JavaScript.
Esta mensagem está tentando nos dizer que SphereGeometry está escrito
incorretamente na linha 31. Verifique o código; acontece que esquecemos um e
digitando SpherGeometry. Não se preocupe com o resto da mensagem,
aprenderemos sobre os construtores mais tarde.
Aqui, o console JavaScript está nos dizendo que esquecemos que em THREE
devem sempre ser todas maiúsculas. Neste livro, estamos usando uma coleção de
códigos JavaScript chamada Three.js
Como os autores do Three.js adoram letras maiúsculas, não há three, apenas
THREE.
Este é um erro muito comum ao trabalhar com o código Three.js. Também é fácil
digitar outros códigos ou nomes JavaScript. Portanto, tente se lembrar de como o
JavaScript é exigente na próxima vez que você vir um erro "não definido".
Podemos corrigir esse problema substituindo o Three no código por THREE.
Capítulo 3
Projeto: Criando um avatar
Desenvolver jogos significa muitas partes - a área de jogo, os jogadores no jogo,
coisas que atrapalham os jogadores e muito, muito mais. Neste capítulo do projeto,
criaremos um jogador que poderemos usar em um jogo - um avatar. Vai acabar
parecendo algo assim:
Um avatar é quem você é no mundo do jogo. Isso mostra onde você está no jogo
e o que está fazendo. Como deveria representar você e eu, deve ter uma boa
sensação. Queremos algo mais do que uma simples caixa velha.
Introdução
Vamos abrir o 3DE Code Editor [6] novamente e criar um novo projeto chamado
My Avatar, marque: Start a New Project (Iniciar um novo projeto, se você não se
lembra como).
Deixe o modelo definido para o projeto inicial 3D. Com isso, estamos prontos para
começar a programar na linha após INICIAR O CODIFICAÇÃO NA PRÓXIMA
LINHA.
Robustez suave
Vamos começar nosso avatar com uma grande esfera para o corpo. Comece com
o mesmo código que usamos no Capítulo 1, Project: Creating Simple Shapes
(Projeto: Criando formas simples).
Já sabemos o que acontece quando digitamos isso - temos uma bola no centro da
cena.
O que isso faz? Quando flat estava lá, estávamos dizendo para a nossa capa usar
pedaços chatos. Removendo flat, nossa cover (cobertura) suaviza esses pedaços,
como mostra a figura.
Essa é uma bola bem suave! Melhor ainda, não precisamos mexer no número de
pedaços para obter essa suavidade. Nosso código 3D faz isso automaticamente
para nós. E se quisermos ainda mais suave, ainda podemos brincar com o número
de pedaços, como fizemos em Not Chunky: SphereGeometry (100, 20, 15).
O restante dos exemplos de avatar mostrará partes suaves. Mas a escolha entre
pedaços lisos e planos depende inteiramente de você, programador do jogo. Se
você deseja que seu avatar tenha uma aparência retrô, uma capa plana ( flat cover) é
uma ótima opção. E você sempre pode mudar mais tarde se mudar de ideia.
Então, vamos ver se podemos ficar ainda mais preguiçosos quando criamos a
mão esquerda para o nosso avatar:
Não apenas não fizemos uma nova capa para a mão esquerda, mas também não
criamos uma nova malha! Em vez disso, usamos a mesma malha para a mão
esquerda que usamos para a mão direita. Agora isso é preguiçoso!
Com isso, nosso avatar deve ficar assim:
OK, admito que não se parece muito com um corpo com as mãos. Será; apenas
tenha paciência comigo um pouco mais.
Vamos analisar rapidamente por que usamos esses números para as mãos.
Quando qualquer coisa é adicionada a uma cena, ela começa no centro. Então,
quando adicionamos o corpo e a mão, começa algo como isto:
Na programação 3D e matemática, esquerda e direita são chamadas de direção
X. Para cima e para baixo são chamadas de direção Y.
É por isso que mudamos a posição X das mãos:
Você entendeu?
function animate() {
requestAnimationFrame(animate);
avatar.rotation.z = avatar.rotation.z + 0.05;
renderer.render(scene, camera);
}
animate();
Se você digitou tudo corretamente, pode notar algo estranho. Apenas a cabeça
está girando, não o avatar inteiro.
Isso pode ser um efeito legal, mas não é o que queríamos. Então, como vamos
girar o avatar inteiro?
Se você respondeu que adicionamos mudanças de rotação.z nas mãos e nos
pés, você adivinhou. Mas isso não vai funcionar. As mãos e os pés girariam no lugar
exatamente como a cabeça.
A resposta para esse problema é uma técnica de programação 3D muito
poderosa. Agrupamos todas as partes do corpo e giramos o grupo. É uma ideia
simples, mas, como você descobrirá mais adiante, é surpreendentemente poderosa.
Para agrupar as partes do corpo, adicionamos as partes ao avatar em vez de à
cena. Se você olhar novamente para o código da mão direita, verá que o
adicionamos à cena. Vamos mudar essa linha.
Esta linha agora adiciona a mão direita ao avatar em vez de à cena. Agora, a mão
girará junto com o corpo do avatar.
Depois de fazer o mesmo com o leftHand, o rightFoot e o leftFoot, seu avatar
deve estar fazendo piruetas - sem perder nenhuma parte!
Algumas vezes, talvez, não desejemos que nosso avatar faça piruetas. Vamos
adicionar um pouco de código para controlar isso.
① var isCartwheeling = false;
function animate() {
requestAnimationFrame(animate);
② if (isCartwheeling) {
avatar.rotation.z = avatar.rotation.z + 0.05;
}
renderer.render(scene, camera);
}
animate();
① É aqui que dizemos se nosso avatar está rodando ou não. Se definirmos isso
como verdadeiro, nosso avatar estará fazendo piruetas. Se definirmos como falso
(como fizemos aqui), nosso avatar não funcionará.
② Coloque o avatar.rotation em um if, como mostrado. Não se esqueça das
chaves {} nesta linha e depois da linha avatar.rotation. Agora altere o valor de
isCartwheeling de false para true. O avatar começa a girar de novo?
Vamos brincar!
Agora que você tem o avatar girando, tente fazer o avatar virar também. Você
deve usar um valor como isFlipping para controlar o lançamento. Dica: em vez de
avatar.rotation.z, tente avatar.rotation.x ou avatar.rotation.y.
Você entendeu? Caso contrário, tudo bem. Realmente! Falaremos mais sobre isso
nos próximos capítulos, especialmente no Capítulo 8, Project: Turning Our Avatar
(Projeto: Transformando nosso avatar).
Capítulo 4
Sério, você sentirá que tem super poderes depois de terminar este capítulo. E
você não estará errado - a programação é um super poder!
Introdução
Este capítulo baseia-se no trabalho que fizemos no Capítulo 3, Projeto: Criando
um avatar. Se você ainda não fez os exercícios desse capítulo, volte e faça-os antes
de continuar. Em particular, você precisa revisar o exercício animado no final desse
capítulo.
Queremos desenvolver o código do capítulo anterior, mas não queremos perdê-lo.
Vamos copiar o projeto avatar do último capítulo para um novo projeto. Dessa forma,
nosso código antigo ainda estará lá, se precisarmos.
Para copiar um projeto, clique no botão de menu e escolha: fazer uma cópia no
menu:
Vamos chamar esse projeto de My Avatar: Keyboard Controls. Digite isso para
o nome do projeto.
Depois clique em SALVAR. Com isso, estamos prontos para adicionar controles
do teclado.
document.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
alert(event.code);
}
document.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
» var code = event.code;
» if (code == 'ArrowLeft') avatar.position.x = avatar.position.x - 5;
» if (code == 'ArrowRight') avatar.position.x = avatar.position.x + 5;
» if (code == 'ArrowUp') avatar.position.z = avatar.position.z - 5;
» if (code == 'ArrowDown') avatar.position.z = avatar.position.z + 5;
}
Falaremos sobre if, == (é igual?) e = (faça com que seja igual) no Capítulo 7: Uma
análise mais detalhada dos fundamentos do JavaScript. Mas esse código deve fazer
sentido mesmo sem detalhes. Estamos verificando se o código da chave vem de
uma tecla de seta. Se o código da chave for ArrowLeft, por exemplo, alteramos a
posição X do avatar subtraindo 5.
Vamos Brincar!
Clique no botão OCULTAR CÓDIGO e experimente. Use as setas do teclado para
mover o avatar. Funciona como você espera?
Lembre-se: se algo der errado, verifique o console do JavaScript!
Se tudo estiver funcionando corretamente, você poderá mover seu avatar para
longe, para perto, para a esquerda ou direita e até mesmo para fora da tela.
Você aprendeu a garantir que as mãos e os pés do avatar se movam com o corpo
quando adicionamos a capacidade de fazer piruetas no Doing Cartwheels (fazendo
piruetas). Como as mãos e os pés foram adicionados ao objeto avatar em vez da
cena, movê-lo significa que as mãos e os pés o acompanham.
Vamos ver o que acontece se uma das pernas não estiver anexada ao avatar.
Nesse caso, alteraremos o leftFoot para que seja adicionado à cena em vez do
avatar.
Não subestime o poder desse tipo de coisa. Faremos coisas loucas com isso mais
tarde.
Por enquanto, não se esqueça de recolocar o pé esquerdo no avatar!
Aqui está o código completo do teclado para mover, virar e girar o nosso avatar:
document.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
var code = event.code;
if (code == 'ArrowLeft') avatar.position.x = avatar.position.x - 5;
if (code == 'ArrowRight') avatar.position.x = avatar.position.x + 5;
if (code == 'ArrowUp') avatar.position.z = avatar.position.z - 5;
if (code == 'ArrowDown') avatar.position.z = avatar.position.z + 5;
if (code == 'KeyC') isCartwheeling = !isCartwheeling;
if (code == 'KeyF') isFlipping = !isFlipping;
}
Se você acertou, poderá ocultar seu código e fazer o avatar girar e girar à medida
que ele sai da tela.
Na verdade, é muito louco que o avatar possa sair da tela. Vamos corrigir isso
daqui a pouco, mas primeiro vamos adicionar um cenário para o nosso avatar
explorar.
Vamos dar ao nosso avatar algumas árvores para caminhar.
function makeTreeAt(x, z) {
var trunk = new THREE.Mesh(
new THREE.CylinderGeometry(50, 50, 200),
new THREE.MeshBasicMaterial({color: 'sienna'})
);
var top = new THREE.Mesh(
new THREE.SphereGeometry(150),
new THREE.MeshBasicMaterial({color: 'forestgreen'})
);
top.position.y = 175;
trunk.add(top);
trunk.position.set(x, -75, z);
scene.add(trunk);
}
Se você inseriu todo esse código corretamente, verá o avatar em frente a uma
floresta de quatro árvores.
Isso é muito legal, mas como fizemos isso?
Detalhamento
A maior parte do trabalho está na função makeTreeAt (). Como veremos no
Capítulo 5, Funções: usar e usar novamente, uma função JavaScript é uma
maneira de executar o mesmo código repetidamente. Nesse caso, a função realiza
todo o trabalho repetitivo de construir um tronco e uma copa de árvore. Poderíamos
ter nomeado qualquer coisa, mas damos a ele um nome que nos diz o que faz -
nesse caso, ele cria uma árvore nas coordenadas x left/right (esquerda / direita) e z
in/out (entrada / saída). O interior da função makeTreeAt () deve começar a parecer
familiar.
function makeTreeAt(x, z) {
① var trunk = new THREE.Mesh(
new THREE.CylinderGeometry(50, 50, 200),
new THREE.MeshBasicMaterial({color: 'sienna'})
);
② var top = new THREE.Mesh(
new THREE.SphereGeometry(150),
new THREE.MeshBasicMaterial({color: 'forestgreen'})
);
③ top.position.y = 175;
④ trunk.add(top);
⑤ trunk.position.set(x, -75, z);
⑥ scene.add(trunk);
}
É importante lembrar que precisamos adicionar a copa das árvores ao tronco e não
à cena. Se a copa das árvores e o tronco forem adicionados à cena, teremos que
lembrar de mover os dois. Com a copa das árvores adicionada ao tronco, movê-lo
também move a copa.
Uma função não é 100% necessária aqui. Você já é bom o suficiente para criar
formas agora e provavelmente pode criar quatro troncos de cilindro ao redor da cena
com quatro topos de esferas neles. Mas programadores preguiçosos (o melhor tipo!)
não gostam de digitar mais do que precisam.
Então, ensinamos uma função: construir uma árvore uma vez e depois usamos
essa função novamente. E se queremos adicionar outras 20 árvores, usamos essa
função 20 vezes mais.
Também novo aqui é a cor. Nós escolhemos essas cores na lista de nomes de
cores da Wikipedia. [7] O tronco da árvore é da cor sienna. Você pode experimentar
suas próprias cores, se quiser. A maioria dos nomes de cores funciona, basta
mantê-los em letras minúsculas e sem espaços ’forestgreen’ em vez de ’forest green’
("florestaverde" em vez de "floresta verde").
Uma vez que tenhamos essa função, é fácil usá-la. Criamos uma árvore com x de
500 e z de 0, chamando a função com os números entre parênteses como
makeTreeAt (500, 0).
Agora que temos uma floresta, vamos ver o que podemos fazer sobre o avatar sair
da tela.
Depois de ocultar o código, você verá que, quando o avatar é movido, a câmera
permanece bem na frente do avatar.
A câmera inicia 500 unidades na frente do avatar. O código no topo define sua
posição como camera.position.z = 500 ou 500 unidades na frente. Antes, eram 500
unidades em frente ao centro da cena. E ficou lá, mesmo que o avatar se movesse.
Agora que o adicionamos ao avatar, ele sempre permanece a mesma distância do
avatar.
Pode ajudar, pensar na câmera como sendo anexada ao avatar com um bastão de
selfie.
Onde quer que o avatar vá, a câmera também vai.
Muito legal, né? Bem, há um problema com essa abordagem. O que acontece se o
avatar começar a girar ou fazer pirueta? Experimente você mesmo (lembre-se de
que estamos usando as teclas C e F para isso)!
O avatar parece ficar parado, mas tudo começa a girar! Veja a figura.
Isso ocorre porque a câmera está presa no bastão invisível de selfie anexado ao
avatar. Se o avatar girar, a câmera vai junto com ele.
Não é exatamente isso que queremos. Em vez de travar a câmera no avatar, o que
realmente queremos é travar a câmera na posição do avatar.
Na programação 3D, não há uma maneira fácil de bloquear algo de forma confiável
apenas para a posição de outra coisa. Mas nem tudo está perdido.
Adicionaremos um marcador de posição de avatar ao jogo, como mostrado na
figura.
Agora, alteramos o avatar para que seja adicionado ao marcador em vez da cena:
marker.add(camera);
A última coisa que precisamos mudar é o atendente de eventos do teclado. Em vez
de mudar a posição do avatar, temos que mudar a posição do marcador.
document.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
Com isso, podemos mover a posição do avatar com o teclado, mas quando
giramos ou giramos a roda, a câmera permanece na posição vertical.
Notas de rodapé
https://en.wikipedia.org/wiki/Web_colors
Capítulo 5
Funções: usar e usar novamente
1. Conte parte da história (construa o jogador, mova um avatar quando uma tecla é
pressionada)
2. Faça algo repetidamente (faça quatro árvores, faça 100 planetas)
3. Calcule os valores para uso nos itens 1 e 2 (e em outros lugares)
Introdução
Crie um novo projeto no 3DE Code Editor. Use o modelo de projeto inicial 3D e
chame-o de Planet Functions.
O primeiro passo na criação de 100 planetas é criar um planeta. Após a linha que
diz INICIAR CODIFICAÇÃO NA PRÓXIMA LINHA, adicione o seguinte:
Neste ponto do livro, temos uma boa ideia do que esse código fará antes mesmo
de começarmos a digitar. Ele cria uma bola um tanto grande, envolve-a em material
azul e a move para o lado esquerdo da tela.
Esse é um planeta.
Pule uma linha e adicione este código:
Se tivermos que adicionar outros 98 planetas à cena como essa, digitaremos por
um longo tempo. E mesmo depois de toda essa digitação, ainda temos que trabalhar
em outras coisas legais, como naves espaciais voadoras.
De volta ao Capítulo 3, Projeto: Criando um avatar, usamos uma função para evitar
repetir o mesmo processo para criar uma árvore quatro vezes. Então você
provavelmente pode adivinhar o próximo passo.
Funções básicas
Vamos começar definindo uma função chamada makePlanet (). Após o código que
já temos para os dois planetas, adicione o seguinte:
function makePlanet() {
var size = 50;
var x = 0;
var y = 200;
var z = 0;
var surface = 'purple';
var shape = new THREE.SphereGeometry(size);
var cover = new THREE.MeshBasicMaterial({color: surface});
var planet = new THREE.Mesh(shape, cover);
planet.position.set(x, y, z);
scene.add(planet);
}
Este é um bom exemplo de uma função de fazer algo repetidamente. Como o nome
sugere, isso criará planetas repetidamente. Cem planetas para ser mais preciso.
O corpo da função - todo o código entre as chaves de abertura e fechamento -
parece semelhante ao código dos dois primeiros planetas. A diferença é que
atribuímos alguns valores na parte superior do corpo da função. Atribuir valores
como esse é bastante comum em funções - você verá o porquê em breve.
Mas primeiro, adicione a seguinte linha após a função makePlanet () - após o final
da chave:
makePlanet();
Uma linha de código como essa está chamando a função - está pedindo que a
função seja executada. Depois de definir uma função, podemos chamá-la sempre
que quisermos, digitando o nome e colocando parênteses depois dela.
Se tudo for digitado corretamente, você verá um planeta roxo acima dos dois
primeiros planetas, como mostra a figura.
Caso não veja isso, será necessário verificar se há erros no editor de código e no
console JavaScript, conforme descrito no Capítulo 2, Depurando: corrigindo código
quando ocorrerem problemas.
Quando o makePlanet () estiver funcionando, temos uma maneira de criar quantos
planetas quisermos. Mas ainda não terminamos. Adicione uma segunda chamada ao
makePlanet (), como mostrado:
makePlanet();
» makePlanet();
Depois de digitar esse código, nada parece ter mudado. Ainda temos três planetas.
O que está acontecendo?
Acontece que tudo está funcionando; simplesmente não está funcionando como o
esperado. Nós temos quatro planetas. Mas makePlanet () sempre coloca seus
planetas no mesmo lugar - em (0, 200, 0).
A história que estamos tentando contar é que os planetas estão espalhados pelo
espaço. Essa é outra maneira de dizer que queremos planetas com posições
aleatórias.
Podemos obter um número aleatório com uma função incorporada diretamente ao
JavaScript: Math.random (). Vamos usar console.log () - a função que descobrimos
pela primeira vez no Capítulo 2, Depuração: corrigindo código quando as coisas dão
errado - para explorar como o Math.random () funciona. Abaixo das duas chamadas
para makePlanet (), adicione a seguinte linha para imprimir um número aleatório no
console do JavaScript:
console.log(Math.random());
function r(max) {
if (max) return max * Math.random();
return Math.random();
Para ver como isso funciona, vamos seguir essa função com mais quatro
instruções console.log ():
var randomNum = r();
console.log(randomNum);
randomNum = r(100);
console.log(randomNum);
console.log(r(100));
console.log(r(100));
Com isso, devemos ter um total de cinco valores registrados no console JavaScript.
Usando funções
Agora que entendemos o que r () faz, vamos usá-lo.
De volta à função makePlanet (), altere os valores de x, y e z, como mostrado.
function makePlanet () {
var size = 50;
» var x = r (1000) - 500;
» var y = r (1000) - 500;
» var z = r (1000) - 1000;
var surface = 'purple';
var shape = new THREE.SphereGeometry(size);
var cover = new THREE.MeshBasicMaterial({color: surface});
var planet = new THREE.Mesh(shape, cover);
planet.position.set (x, y, z);
scene.add(planet);
}
Chamar r (1000) nos dará um número aleatório entre 0 e 1000. Para x, subtraímos
500 de r (1000) para nos dar um número aleatório entre –500 (até a esquerda) e 500
(até a direita). Se você não tiver certeza sobre esse cálculo, envie r (1000) - 500
para console.log () e clique no botão ATUALIZAR algumas vezes para ver o que
acontece no console JavaScript.
Fazemos o mesmo com y, para colocar aleatoriamente os planetas entre -500
(abaixo) e 500 (acima). Subtraímos 1000 de r (1000) para z para que os planetas
fiquem bem longe da nossa posição inicial.
O resultado deve ser alguns planetas espalhados aleatoriamente!
Não se preocupe muito com a forma como os loops funcionam - nós os veremos
novamente no Capítulo 7: Uma análise mais aprofundada dos fundamentos do
JavaScript.
Enquanto fazemos alterações, também devemos alterar o tamanho do planeta
para ser um número aleatório entre 0 e 50. Isso é fácil graças à nossa função r ().
Altere o valor do tamanho no início de makePlanet () para r (50).
function makePlanet() {
» var size = r(50);
var x = r (1000) - 500;
var y = r (1000) - 500;
var z = r (1000) - 1000;
var surface = 'purple';
var shape = new THREE.SphereGeometry(size);
var cover = new THREE.MeshBasicMaterial({color: surface});
var planet = new THREE.Mesh(shape, cover);
planet.position.set(x, y, z);
scene.add(planet);
}
Com isso, o tamanho de cada planeta é um número aleatório entre 0 e 50. Temos
alguns planetas grandes e pequenos - muito mais realistas!
Mais importante, podemos começar a ver o poder das funções. Pense nos dois
primeiros planetas que criamos neste capítulo. Escrevemos dez linhas de código
para criar dois planetas. Se continuássemos fazendo isso por 100 planetas, teríamos
escrito 500 linhas de código. Graças às funções, escrevemos menos de 20 linhas de
código. Yay!
Quebrando funções
Você tem alguma ideia de quão poderosas são as funções. Mas cuidado, pois eles
são fáceis de quebrar de várias maneiras diferentes. Vimos isso no Capítulo 2,
Depuração: corrigindo código quando as coisas dão errado. Agora que entendemos
melhor as funções, vale a pena dar uma nova olhada em quebrá-las.
A coisa mais comum a fazer é esquecer uma chave:
É difícil perder esse erro! As mensagens de erro não dizem que esquecemos a
chave de abertura {, o que seria mais útil. Mas é bastante claro que algo era
esperado antes da declaração if. Quando soubermos disso, será fácil rastrear o erro.
O que acontece se colocarmos a chave de abertura de volta, mas remover a de
fechamento }, após a declaração de retorno?
function r(max) {
if (number) return number * Math.random(); - aqui, em vez de number, deveria ser max.
return Math.random();
}
function r(max) {
if (max) return max * Math.random();
return Math.random();
}
Portanto, abaixo das instruções console.log () que usamos para testar a função
r(), adicione uma nova função rColor ().
function rColor () {
return new THREE.Color(r (), r (), r ());
}
Graças a outra função simples, agora temos 100 planetas, de várias formas e
cores, espalhados pelo espaço!
Bônus # 2: controles de vôo
Qual é a graça de espalhar um monte de planetas, se você não pode voar através
deles, certo?
Os controles Fly são muito mais complicados do que parecem - muito mais
complexos do que os controles que estamos construindo para o nosso avatar. Então,
carregaremos novos controles em vez de escrevermos os nossos. No início do
código, adicione uma nova tag <script> para carregar no FlyControls.js.
<body></body>
<script src="/three.js"></script>
» <script src="controlsFlyControls.js"></script>
Falaremos mais sobre, como carregar códigos como esse, no Capítulo 9: O que é
todo este outro código?
Carregar o código é apenas metade da tarefa. A outra metade é usando-o. Para
fazer isso, adicione o seguinte, abaixo da função rColor (), que deve estar na parte
inferior do nosso código:
Para usar os controles, oculte seu código. As seguintes teclas do teclado permitem
voar ou girar em direções diferentes.
Dica: é bem divertido pressionar Q e W ao mesmo tempo. Mas você pode ficar um
pouco tonto!
Falando da função animate (), é uma função como as que acabamos de escrever.
Dê uma olhada:
① function animate () {
var delta = clock.getDelta ();
controls.update (delta);
② renderer.render (scene, camera);
③ requestAnimationFrame (animate);
}
④ animate ();
Capítulo 6
Projeto: Movendo mãos e pés
Quando vimos nosso avatar pela última vez no Capítulo 4, Projeto: Movendo
Avatares, ele estava se movendo muito bem. Mas foi um pouco ... rígido. Mesmo
quando o corpo se mexia, as mãos e os pés ficavam imóveis. Neste capítulo,
daremos ao nosso avatar um pouco mais de vida.
Primeiros passos
Estamos trabalhando novamente nos capítulos anteriores. Como fizemos muito
trabalho para mover o avatar no Capítulo 4, Projeto: Movendo Avatares, vamos fazer
uma cópia desse projeto para trabalhar neste capítulo.
Se ainda não estiver aberto no 3DE Code Editor, abra o projeto que chamamos de
My Avatar: Keyboard Controls. Para fazer uma cópia, clique no botão de menu e
escolha MAKE A COPY (fazer uma cópia) no menu.
Nomeie o projeto My Avatar: Moving Hands and Feet e clique no botão SAVE.
Com isso, estamos prontos para começar a adicionar vida ao nosso avatar!
Como você sabe, os três números que usamos para definir a posição da mão são a
posição X (esquerda/direita), a posição Y (para cima/baixo) e a posição Z
(entrada/saída). No caso da mão direita, colocamos -150 do centro do avatar.
Além de definir todos os três números para a posição, podemos alterar apenas uma
das posições atualizando position.x, position.y ou position.z. Para mover a mão
direita para frente (em direção ao visualizador), adicione a linha position.z conforme
mostrada abaixo:
Altere o valor de position.z de 100 para -100. O que acontece? O que acontece se
você continuar mudando entre 100 e -100?
Quando z é 100, a mão é movida para frente.
Quando z é -100, a mão se move para trás, de modo que quase não conseguimos
ver a mão atrás do corpo.
E quando você muda de posição.z para frente e para trás entre -100 e 100, é
quase como se a mão estivesse girando para frente e para trás. Parabéns! Você
acabou de aprender uma famosa técnica de animação!
Em alguns jogos, basta mover uma coisa de um lugar para outro para fazer parecer
que está se movendo. Mas podemos fazer melhor no nosso jogo.
Comece excluindo a linha que define a posição.z. Não queremos defini-lo uma
vez. Queremos animá-lo. Isso significa que estaremos trabalhando na função
animate () novamente. Após o Capítulo 4, Projeto: Movendo avatares, já estamos
animando piruetas e inverter com uso da função.
Isso funciona bem, mas antes de começarmos a animar mais coisas na função
animate (), vamos arrumar as coisas um pouco.
Vamos demorar um pouco para garantir que tudo ainda funcione. Oculte o código e
pressione C e F para garantir que o movimento e a rotação do carrinho ainda
funcionem.
Se algo der errado, verifique o console do JavaScript!
Se tudo estiver funcionando, mostre o código novamente. Agora, podemos
adicionar mais três coisas dentro e ao redor da função animar.
① var clock = new THREE.Clock();
var isCartwheeling = false;
var isFlipping = false;
function animate() {
requestAnimationFrame(animate);
② walk();
acrobatics();
renderer.render(scene, camera);
}
animate();
③ function walk() {
var speed = 10;
var size = 100;
var time = clock.getElapsedTime();
var position = Math.sin(speed time) size;
rightHand.position.z = position;
}
② Além de fazer acrobacias, agora também vamos caminhar. Depois de digitar isso,
seu código será interrompido temporariamente. Isso está ok! Vamos corrigi-lo na
próxima etapa.
③ Esta é a função que move as mãos e os pés. Vai depois da função animate () e
acima da função acrobatics (). Há algo especial nesta função!
Se você digitou tudo corretamente, deverá ver a mão direita do avatar balançando
para frente e para trás, conforme mostrado na figura.
E deve estar se movendo muito rapidamente.
Legal! Mas como isso funciona? Bem, é tudo graças ao poder de uma coisa linda
chamada ... um seno (sine). É muito útil na programação 3D.
Como você pode imaginar pelo nome, Math.sin tem algo a ver com matemática.
Um seno forma um número entre -1 e 1. Mais importante, uma função seno cria um
número que muda suavemente para frente e para trás entre -1 e 1 conforme outro
número fica maior. Isso pode não parecer grande coisa. Mas isto é.
Em nosso código, a função seno está usando o tempo. Como o tempo está sempre
aumentando, a função senoidal se move para frente e para trás entre -1 e 1. É
perfeito para mover mãos e pés para frente e para trás!
Nessa mesma linha, também fazemos alguma multiplicação. Em JavaScript, o
caractere asterisco (*) é usado para multiplicar números. Multiplicamos o tempo pela
quantidade de velocidade. Multiplicamos o resultado seno por tamanho.
Vamos jogar!
Experimente os números dentro de walk. Se você alterar a velocidade de 10 para
100, o que acontece? Se você alterar o tamanho de 100 para 1000, o que acontece?
Tente usar position.x ou position.y em vez de position.z. Tente alterar position.y
e position.z ao mesmo tempo.
Depois de sentir esses números, tente fazer com que a outra mão e os pés se
movam ao mesmo tempo. Jogar!
Balançando as mãos e os pés juntos
Como funcionou? Você foi capaz de balançar todas as mãos e pés? Caso
contrário, não se preocupe, é um pouco complicado.
Se você tentou mover as mãos e os pés da mesma maneira, deve ter notado que
nosso avatar está se movendo de maneira estranha. Os pés e as duas mãos
avançam ao mesmo tempo. E então os dois pés e as duas mãos balançam para trás
ao mesmo tempo. Ninguém anda assim na vida real.
Quando você anda, um pé está na frente e o outro está atrás. Em termos de avatar,
um pé está na direção Z positiva, enquanto o outro está na direção Z negativa:
function walk () {
var speed = 10;
var size = 100;
var time = clock.getElapsedTime ();
var position = Math.sin (speed time) size;
» rightHand.position.z = position;
» leftHand.position.z = -position;
rightFoot.position.z = -position;
leftFoot.position.z = position;
}
Com isso, as mãos e os pés do nosso avatar devem balançar para frente e para
trás em um bom movimento de caminhada.
Caminhando ao se mover
No momento, nosso avatar está andando constantemente - mesmo quando não o
estamos controlando com nossos controles do Capítulo 4, Projeto: Movendo
avatares. Vamos resolver esse problema.
Primeiro, vamos adicionar uma maneira de rastrear a direção em que o avatar
está se movendo. Adicione mais quatro linhas acima da função animate (),
começando com o valor de isMovingRight:
Esse código diz que nosso avatar não está se movendo a princípio. Até que algo
aconteça, nosso avatar não está se movendo para a direita, esquerda, frente ou trás
- é falso que o avatar esteja se movendo em qualquer uma dessas direções.
Em seguida, adicione uma nova função chamada isWalking (). Vai nos dizer se o
avatar está andando. Adicionaremos esse código após a função walk () (e logo
acima da função acrobatics ()).
function isWalking() {
if (isMovingRight) return true;
if (isMovingLeft) return true;
if (isMovingForward) return true;
if (isMovingBack) return true;
return false;
}
Para que isWalking () nos diga se o avatar está se movendo, ele precisa retornar
um valor. Se o avatar estiver se movendo para a direita, ele retorna verdadeiro. Se
estiver se movendo para a esquerda, para frente ou para trás, também retorna
verdadeiro. Se não estiver fazendo nada disso, então nosso avatar não está se
movendo e retorna falso. Agora, de volta em walk (), adicione uma linha no topo da
função:
function walk () {
» if (!isWalking ()) return;
var speed = 10;
var size = 100;
var time = clock.getElapsedTime ();
var position = Math.sin (speed time) size;
rightHand.position.z = position;
leftHand.position.z = -position;
rightFoot.position.z = -position;
leftFoot.position.z = position;
}
Esta linha de código significa que se o avatar não estiver andando, retorne
imediatamente da função. Ao contrário de isWalking (), não estamos retornando um
valor como true (verdadeiro) ou false (falso). Estamos saindo de walk () sem fazer
mais nada. Ou seja, se o avatar não estiver andando, saia da função walk sem
executar nenhum código que faz o avatar parecer que está andando.
Uma vez feito isso, as mãos e os pés do nosso avatar devem parar de se mover.
Os valores para isMovingRight e os outros são false (falsos) e não há nada em
nosso código para alterá-los. Portanto, isWalking () sempre retorna false. E como
isWalking () é sempre false, sempre que walk () é chamado, ele retorna
imediatamente, sem fazer nada.
Queremos que as mãos e os pés do avatar se movam quando pressionamos os
controles do teclado. Portanto, precisamos atualizar a função sendKeyDown.
Adicione as linhas mostradas. Certifique-se de incluir as chaves para cada uma das
quatro instruções if!
Vamos brincar!
Se você está pronto para um desafio, vamos buscar melhores controles de
acrobacia. Uma vez que temos o código para atender os eventos keydown e keyup,
tente fazer as rodadas e os giros começarem quando a tecla C ou F for pressionada
e parar quando a tecla C ou F for liberada. Você acha que os controles são melhores
assim? Se sim, deixe-os lá - é o seu jogo!
Capítulo 7
Mas, você pode pressionar a tecla de seta para cima no teclado para abrir a última
coisa que você digitou.
A palavra-chave var
A palavra-chave var é uma abreviação de variável. Uma variável é algo que pode
mudar.
A menos que você tenha visto datas de JavaScript antes, provavelmente não
saberia o que esse código faz (especialmente que 0 é o primeiro mês em
JavaScript). Mas, graças aos comentários, é muito fácil descobrir.
DICA - Você não precisa digitar os comentários (mas deve).
Os comentários que você vê neste livro têm como objetivo fornecer dicas úteis.
Você não precisa digitá-los. Mas você deveria. Quando você abrir seu código
posteriormente para fazer uma alteração, os comentários o ajudarão a lembrar por
que você fez as coisas.
Na verdade, em vez de copiar comentários, você deve adicionar seus próprios. Se
você tiver dificuldade em entender algo ao codificá-lo, provavelmente terá dificuldade
em entender quando voltar ao código para adicionar coisas novas ou alterar alguma
linha deste código (para melhorar o comportamento de alguns objetos, por exemplo).
Outra boa utilidade do uso de comentários é se valer do fato de que ele não é
processado na execução do programa (na verdade ele é excluído durante a
conversão do código fonte em código de máquina). Assim, quando, por exemplo,
você quer experimentar uma outra forma para um comando ou instrução ou, até
mesmo, uma longa função, que já esteja funcionando no programa, você não precisa
excluí-lo logo e depois incluir a nova forma que quer experimentar, pois se a forma
anterior se mostrar melhor, você vai ter que digitá-la novamente. Então, basta
transformar em comentário, tudo o que quer substituir, e, basta excluir as barras
duplas para voltar a ser parte do código, novamente, se assim o preferir.
Os comentários são pequenas dicas para o seu futuro!
A seguir, vamos dar uma olhada nos diferentes tipos de coisas do JavaScript,
começando com números.
Então funciona até com números negativos. Lembre-se disso, porque os números
negativos serão úteis enquanto brincamos com gráficos 3D.
OK, adicionar e subtrair são muito fáceis em JavaScript. E quanto à multiplicação
e divisão? As teclas de sinal de mais e menos estão na maioria dos teclados, mas
não as teclas × e ÷.
Para multiplicação, use o caractere asterisco (*):
3*7
// 21
2 * 2.5
// 5
-2 * 4
// -8
7*6
// 42
45 / 9
// 5
100 / 8
// 12.5
84 / 2
// 42
Uma outra coisa a saber sobre os números é que, ao fazer muita aritmética ao
mesmo tempo, você pode usar parênteses para agrupar coisas. A matemática entre
parênteses é sempre calculada primeiro:
// Same as 5 * 6
// O mesmo que 5*6 é
5 * (2 + 4)
// 30
// Same as 10 + 4
// O mesmo que 10+4 é
(5 * 2) + 4
// 14
j=i++
// 0
i
//1
j
//
0
Geometria
Math.sin(0)
// 0
Math.sin(2*Math.PI)
// 0
Math.cos(0)
// 1
Strings
Palavras e letras são strings em JavaScript. As strings começam e terminam
com aspas “”.
title = "3D Game Programming for Kids"
Muitos programadores preferem apóstrofos (‘’) às aspas (“”) porque não temos
que manter pressionada a tecla Shift. Sim, os programadores são realmente
preguiçosos.
Dito isso, alguns programadores preferem o uso de aspas (“”) pois são úteis
quando a string precisa conter, dentro dela, um ou mais apóstrofos. Algumas vezes,
apóstrofo simples (‘) e outras, duplos (‘’), como a seguir:
citação = " De Hamlet: ‘To be or not to be, that is the question’ ".
// Com apóstrofo duplo.
Não importa o que usamos, JavaScript sempre usa aspas. Portanto, não se
surpreenda se o console JavaScript relatar sua string de apóstrofo com aspas.
Strings são combinados (ligados, unidos) usando o operador + (mais).
str1 = 'Howdy'
str2 = 'Bob'
Muito estranho, certo? Já mencionamos que a maioria dos teclados não tem
teclas de multiplicação ou divisão. Portanto, não é muito surpreendente que não
existam teclas para unir duas ou mais strings. É um pouco surpreendente que o
JavaScript reutilize o sinal de + (mais). O que você acha que acontece se tentar
juntar uma string e um número? Bem, experimente:
Você tentou? A moral desta lição é que você deve usar parênteses se tentar fazer
matemática ao combinar números com strings. Mas a verdadeira lição é fazer
matemática antes de combinar o resultado com uma string - como fizemos quando
definimos a variável answer.
Booleanos
Um valor booleano é true (verdadeiro) ou false (falso).
no = false
// false
yes = true
// true
theOpposite = !yes
// false
theOppositeOfTheOpposite = !!yes
// true
isTenGreaterThanSix = 10 > 6
// true
isTwelveTheSameAsEleven = 12 == 11
// false
Nada (Nothing)
O JavaScript ainda tem uma maneira de descrever nothing (nada). Na verdade, o
JavaScript gosta tanto de nada que tem duas coisas para significar o nada!
meansNothing = null
alsoMeansNothing = undefined
Quando os programadores dizem que um valor está vazio, usamos null. O valor
undefined (indefinido) não é normalmente definido pelos programadores. Em vez
disso, o JavaScript usa isso para identificar variáveis que nunca foram definidas.
Listando coisas
Às vezes é muito útil ser capaz de descrever uma lista de coisas. Em JavaScript,
as listas são feitas com colchetes [ ]. Uma lista de filmes incríveis pode ser parecida
com esta:
amazingMovies = [
'Star Wars',
'The Empire Strikes Back',
'Indiana Jones and the Raiders of the Lost Ark'
]
amazingMovies.length
// 3
Para obter uma entrada na lista, use colchetes com um número, começando em 0.
amazingMovies[0]
// "Star Wars"
amazingMovies[1]
// "The Empire Strikes Back'
amazingMovies[2]
// "Indiana Jones and the Raiders of the Lost Ark"
amazingMovies[3]
// ???
Maps (mapas)
Mapas e listas, ambos coletam informações em JavaScript. A principal diferença
entre listas e mapas é a maneira como você obtém valores deles.
Em listas, obtemos valores com base em onde eles estão na lista - o valor em 0, o
valor em 1, o valor em 42. Listas são úteis, mas às vezes não são tão fáceis de usar
como gostaríamos - especialmente se quisermos armazenar diferentes tipos de
informações.
Para criar mapas em JavaScript, você usa chaves { }.
greatMovie = {
name: 'Toy Story',
year: 1995,
stars: ['Tom Hanks', 'Tim Allen']
}
Os mapas recebem esse nome porque mapeiam uma chave para um valor. No
greatMovie, a chave name (nome), mapeia para o valor "Toy Story". A chave year
(ano), mapeia para 1995. A chave stars (astros), mapeia para uma lista de dois
atores.
Para obter informações de um mapa, usamos colchetes [ ], assim como fazíamos
com as listas. Mas em vez de usar um número dentro dos colchetes, usamos uma
versão de string da chave.
greatMovie['name']
// "Toy Story"
greatMovie['year']
// 1995
Outra maneira de obter informações de um mapa é colocando a chave após um
período.
greatMovie.name
// "Toy Story"
greatMovie.stars
// ["Tom Hanks", "Tim Allen"]
greatMovie.stars[0]
// "Tom Hanks"
Estruturas de controle
Já falamos sobre as partes individuais que compõem o JavaScript: os números,
strings e booleanos que servem como substantivos em sentenças JavaScript.
Nesta seção, falamos sobre sentences (frases) e paragraphs (parágrafos)
JavaScript - sobre como combinar as partes em estruturas de código maiores.
gameOver = true
if (gameOver) console.log('Game Over!!!')
// "Game Over!!!"
Simples declarações de condição if (se) como essa podem ir em uma única linha.
Quando mais de uma coisa está sendo feita após um if, precisamos colocar essas
coisas entre chaves.
gameOver = true
if (gameOver) {
now = new Date()
console.log('Game ended on ' + now.toDateString())
}
//"Game ended on Sun Apr 01 2018"
Neste caso, definimos a variável now para a data de hoje na primeira linha da
instrução if. Na segunda linha, usamos essa variável now para relatar quando o
jogo terminou.
A expressão que segue a instrução if (se) não precisa ser uma variável. Ele só
precisa resultar em um valor booleano.
gameOver = true
score = 400
score = 10
Use o artifício de edição de seta para cima, no console, para tentar valores
diferentes para pontuação. Se a pontuação for superior a 100, a primeira mensagem
deve ser registrada no console. Caso contrário, se a pontuação for superior a 20, a
mensagem simples “Game Over” deve ser registrada. E se a pontuação não for
maior que 100 ou 20, a mensagem triste, de fim de jogo, deve ser registrada.
Não abuse do ifs
If, else if e else são muito poderosos, mas podem ser usados demais. Se você
tiver que usá-los, tente ficar com a versão de uma linha - eles são mais fáceis de ler
e menos propensos a adicionar bugs no código.
Loops
Vimos loops pela primeira vez no Capítulo 5, Funções: usar e usar novamente,
onde fizemos nosso código repetir 100 vezes para criar 100 planetas. Portanto,
sabemos que os loops são uma ótima maneira de fazer algo repetidamente. Vamos
olhar mais de perto agora.
Um loop for tem a seguinte estrutura:
for (var i=0; i<5; i++) {
console.log('Loop #' + i)
}
//"Loop #0"
// "Loop #1"
// "Loop #2"
// "Loop #3"
// "Loop #4"
Três expressões estão entre parênteses de um loop for. O primeiro declara uma
variável de loop. Usamos a palavra-chave var para declarar que i é a variável de
loop - e que começa com um valor de 0. A próxima expressão é um booleano que
descreve quando o loop continua a ser executado. No exemplo acima, dizemos que,
enquanto i for menor que 5, o loop continua. A última expressão é executada,
após cada loop que usamos, para aumentar o valor de i em 1 (um) com o
operador ++.
Cada vez que o loop é executado, o código dentro das chaves é executado.
Nesse caso, temos uma única linha que registra o número do loop no console.
A variável de loop pode começar com qualquer valor. A condição que mantém o
loop em execução pode ser qualquer expressão booleana. A expressão a seguir
pode fazer qualquer coisa, incluindo diminuir a variável de loop em 1.
amazingMovies = [
'Star Wars',
'The Empire Strikes Back',
'Indiana Jones and the Raiders of the Lost Ark'
]
Outra forma de loops for funciona apenas com mapas. Parece um pouco com o
formato regular, mas usa a palavra-chave in para obter todas as chaves em um
mapa.
greatMovie = {
name: 'Toy Story',
year: 1995,
stars: ['Tom Hanks', 'Tim Allen']
}
for (var key in greatMovie) {
console.log(key + ': ' + greatMovie[key])
}
//"name: Toy Story"
// "year: 1995"
// "stars: Tom Hanks,Tim Allen"
Dentro do loop, registramos cada chave junto com o valor para o qual a chave
mapeia. Os dois loops for diferentes são ótimos para obter informações de listas e
mapas.
Primeiros passos
Se ainda não estiver aberto no Editor de código 3DE, abra o projeto que
chamamos de My Avatar: Moving Hands and Feet (do Capítulo 6, Projeto: Moving
Hands and Feet). Para fazer uma cópia, clique no botão de menu e escolha FAZER
UMA CÓPIA no menu.
Nomeie o projeto como My Avatar: Turning e clique no botão SAVE.
var direction;
var lastDirection;
Precisamos de direction (direção, orientação) para que nosso código saiba em que
direção virar o avatar. Precisamos de lastDirection (última direção) para que o código
não possa fazer nada quando o avatar já estiver voltado - ou virado - para o lado
certo.
Agora vamos escrever uma nova função para transformar nosso avatar. Vamos
chamar essa função turn.
function turn () {
if (isMovingRight) direction = Math.PI/2;
if (isMovingLeft) direction = -Math.PI/2;
if (isMovingForward) direction = Math.PI;
if (isMovingBack) direction = 0;
if (!isWalking ()) direction = 0;
avatar.rotation.y = direction;
}
Por fim, chamamos essa função dentro de animate (). Adicione a chamada para
turn () logo após a primeira linha - após a linha requestAnimationFrame (animate).
function animate () {
requestAnimationFrame(animate);
» turn ();
walk ();
acrobatics ();
renderer.render (scene, camera);
}
animate();
Isso é incrível. Neste ponto, você fez um avatar de jogo bastante complicado.
Reserve um momento para pensar sobre tudo o que você conquistou:
- Dado ao avatar um corpo, mãos e pés.
- Fez o avatar se mover de forma que todas as peças se movessem com ele.
- Fez o avatar dar cambalhotas e rodar.
- Preso a câmera para o avatar.
- Fixe a câmera na posição do avatar para que saltos e rodadas não faça-nos
ficar tontos.
- Faça as mãos e os pés balançarem para frente e para trás quando o avatar
anda.
- Fez as mãos pararem de se mover quando o avatar não está se movendo.
- Fez o avatar virar para a direção em que está andando.
Essa é uma quantidade incrível de programação 3D em JavaScript. Você fez
muito bem em chegar até aqui. Mas ainda podemos fazer muito mais!
Primeiro, vamos dar uma olhada mais de perto nessa função turn para termos
certeza de que entendemos o que está acontecendo lá.
Quebrando
Na função turn, por que definimos a direção para valores como Math.PI e -
Math.PI/2?
Lembre-se da Geometry (geometria), que os ângulos, ou a quantidade de
rotação, usam radianos em vez de graus. No início, o avatar está voltado para a
câmera. Portanto, 0 ° de rotação é 0 radianos de rotação, o que significa estar
voltado para trás. E 180 ° é pi radianos, o que significa olhar para a frente na tela. A
tabela a seguir é a lista completa que estamos usando na função turn:
marker.add(camera);
Pense no marcador como uma caixa invisível que contém as partes do avatar. Ao
adicionar a câmera ao marcador, a estamos colando em um lado do marcador. Se
girarmos a caixa, a câmera terá que ir junto com ela:
É também por isso que adicionamos as mãos e os pés à cabeça do avatar em vez
de no marcador do avatar. Quando giramos o avatar dentro do marcador, suas mãos
e pés precisam se mover com ele - não ficar parados com o marcador
(OBSERVAÇÃO DO TRADUTOR: Aqui deve haver um engano do autor do livro,
pois o motivo de anexar as mãos e pés ao avatar – conforme explicado no capítulo
que trata sobre esse assunto – não foi por eles ficarem parados, foi que seriam
necessários criar várias funções rotation, uma para cada membro do avatar se estes
estivessem fixados ao marker, ao passo que, fixados ao avatar, basta a função girar
o avatar que gira tudo).
Qual direção quando não está andando
Uma última coisa a observar dentro de turn () é que, quando o avatar não está
andando, ficamos voltados para a frente.
Esta linha diz ao turn (), se o avatar não está se movendo, gire-o de volta para
frente. Como um programador de jogos, você pode preferir manter o avatar voltado
para a direção em que estava quando parou de andar. Ou seja, se o avatar está
caminhando para a direita e o jogador solta a tecla de seta para a direita, então você
pode querer que o avatar fique voltado para a direita. Se for essa a sua preferência,
remova essa linha. Você é o programador do jogo - depende de você!
Animando a rotação
Quando viramos nosso avatar, ele imediatamente se volta para uma nova direção.
Vamos torná-lo um pouco mais realista animando uma virada para a nova direção.
Para isso, precisaremos de uma nova coleção de código JavaScript. Falaremos mais
sobre coleções de código no próximo capítulo, Capítulo 9, O que é todo esse
outro código?. Por enquanto, pense neles como uma forma de usar código de outro
lugar.
A coleção de código que vamos usar nos ajudará a animar entre diferentes
posições e rotações. A coleção de código é chamada Tween (que traduzido
significa interpolação). Ele recebe o nome de sua capacidade de mudar entre os
valores ao longo do tempo.
Para isso, vá para o topo do seu código (o topo, não apenas para a linha INICIAR
A CODIFICAÇÃO NA PRÓXIMA LINHA). Adicione a tag <script> para tween.js,
conforme mostrado:
<script src="/three.js"></script>
» <script src="/tween.js"></script>
function turn() {
if (isMovingRight) direction = Math.PI/2;
if (isMovingLeft) direction = -Math.PI/2;
if (isMovingForward) direction = Math.PI;
if (isMovingBack) direction = 0;
if (!isWalking ()) direction = 0;
Vamos jogar!
Dissemos à coleção de códigos do Tween que ela será executada do início ao fim
em 500 milissegundos. Esse número estava no final da linha que começava com to.
Leva 1000 milissegundos para fazer um segundo, então 500 milissegundos é meio
segundo. O giro do avatar leva menos de um segundo. Experimente esse número
para obtê-lo do jeito que você gosta. 1000 é muito longo? 10 é muito curto? Você
decide!
Qual é o próximo
Uau! Nossa simulação de avatar simples está ficando bastante sofisticada, não é?
Já colocamos bastante trabalho em nosso avatar, mas você deve ter notado que ele
pode passar direto por nossas árvores. No próximo capítulo do projeto, vamos falar
sobre detecção de colisão e usá-la para fazer nosso avatar parar quando colidir com
uma árvore. Mas primeiro é hora de dar uma olhada em todo o código JavaScript
que foi adicionado para nós quando iniciamos este projeto.
Primeiros Passos
Crie um novo projeto a partir do modelo 3D inicial no Editor de código 3DE. Nomeie
o projeto como All that other code (todo aquele outro código).
<body> </body>
<body>
<h1>Hello!</h1>
<p>
You can make <b>bold</b> words,
<i>italic</i> words,
even <u>underlined</u> words.
</p>
<p>
You can link to
<a href="http://code3Dgames.com">other pages</a>.
You can also add images from web servers:
<img src="imagespurple_fruit_monster.png">
</p>
</body>
<script src="/three.js"></script>
Assim como as tags <body>, essas tags <script> são HTML. Essas tags
carregam o JavaScript de qualquer lugar da web para que possamos usá-lo. Neste
caso, estamos carregando o excelente código JavaScript 3D chamado Three.js.
O JavaScript não precisa vir de outros locais. Na maior parte deste livro, estamos
codificando dentro de uma página da web em HTML. Mas quando o código
JavaScript fica muito grande - como um monte de comandos e funções 3D - é muito
útil carregá-lo usando tags <script>. Até agora neste livro, escrevemos
aproximadamente 120 linhas de código e provavelmente já está ficando difícil
encontrar parte do código que escrevemos. Imagine se incluíssemos 1000 linhas do
código de outra pessoa no mesmo lugar que o nosso!
// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer ({antialias: true});
renderer.setSize (window.innerWidth, window.innerHeight);
document.body.appendChild (renderer.domElement);
Esta é uma estrada retangular, mas não parece retangular. Parece que está
ficando menor quanto mais longe fica. A câmera em perspectiva faz isso por nós:
Se usarmos uma câmera ortográfica, por outro lado, tudo parece plano:
Essa é a mesma estrada da imagem anterior. Acabamos de substituir as duas
linhas que criam a câmera em perspectiva com o seguinte:
var width = window.innerWidth;
var height = window.innerHeight;
var camera = new THREE.OrthographicCamera (
-width/2, width/2, height/2, -height/2, 1, 10000
);
Como você pode imaginar, a câmera em perspectiva, que dá a tudo uma sensação
tridimensional, é muito útil em jogos 3D. Por que você quer usar uma câmera
ortográfica?
As câmeras ortográficas são úteis em dois casos. A primeira é quando você quer
fazer um jogo 2D simples. Usar uma câmera 3D para um jogo simples parece
estranho - especialmente nas bordas da tela. A outra é quando fazemos jogos com
distâncias muito grandes, como jogos espaciais. Na verdade, podemos usar
câmeras ortográficas em algumas das simulações espaciais que faremos em breve.
Capítulo 10
Projeto: Colisões
Temos um avatar de jogo habilidoso. Ele se move, anda, até gira. Mas você deve
ter notado algo incomum em nosso avatar. Ele pode andar por entre as árvores.
O que é um pouco estranho.
Primeiros passos
Se ainda não estiver aberto no Editor de código 3DE, abra o projeto do Capítulo 8,
Projeto: Turning Our Avatar, que chamamos de My Avatar: Turning.
Faça uma cópia do nosso projeto de avatar. No menu do Editor de código 3DE,
selecione MAKE A COPY e insira My Avatar: Collisions como o novo nome do
projeto.
Raios e Interseções
Para evitar que nosso avatar caminhe por entre as árvores, vamos começar
imaginando uma seta apontando para baixo em nosso avatar.
Em geometria, chamamos de raio (ray) a uma flecha vinda de algo. Um raio é o que
você obtém quando começa em um lugar e aponta em uma direção. Na figura
acima, o lugar é onde nosso avatar está e a direção para baixo. Às vezes, dar
nomes a ideias tão simples parece bobo, mas é importante para os programadores
saberem esses nomes.
Agora que temos nosso raio apontando para baixo, imagine círculos no solo ao
redor de nossas árvores.
Esta é a maneira simples e louca de evitar que nosso avatar colida com uma
árvore: nós não! Em vez disso, evitamos que o raio do avatar aponte através do
círculo da árvore.
É um pouco estranho, mas assistir a certos filmes de ficção científica pode tornar
sua vida mais fácil como programador. Às vezes, os programadores dizem coisas
estranhas que acabam sendo citações de filmes. Não é obrigatório assistir ou
mesmo gostar desses filmes, mas pode ajudar.
Uma dessas citações é do clássico Star Trek II: The Wrath of Khan. A citação é:
“Ele é inteligente, mas não tem experiência. Seu padrão indica pensamento
bidimensional”. O bandido do filme não estava acostumado a pensar em três
dimensões, e os mocinhos usaram seu pensamento bidimensional contra ele.
Nesse caso, queremos fazer o oposto. Estamos construindo um jogo
tridimensional, mas vamos pensar apenas em colisões em duas dimensões. Não
vamos nos preocupar se a cabeça do avatar for alta o suficiente para tocar a parte
inferior das folhas. Não vamos verificar se uma mão toca o tronco. Esse tipo de
detecção de colisão é possível, mas é preciso muito trabalho - tanto para
programadores quanto para computadores. E essa difícil detecção de colisão
geralmente não acrescenta nada aos nossos jogos.
Neste ponto, uma imagem do que fazer a seguir deve estar se formando em sua
mente. Precisamos de uma lista desses limites do círculo da árvore que nosso
avatar não terá permissão para entrar. Precisamos construir esses limites de círculo
quando construímos as árvores. Então, vamos adicionar um raio para o nosso avatar
detectar quando ele está prestes a entrar no limite de um círculo. Por último,
precisamos impedir o avatar de entrar nessas áreas proibidas.
Vamos estabelecer a lista que conterá todos os limites proibidos. Faremos isso
após a codificação do avatar e a do marcador. Logo acima da função makeTreeAt
(), adicione o seguinte:
Não há nada muito chique nisso. Criamos nossa malha 3D usual - desta vez com
uma geometria de círculo simples. Nós o giramos de modo que fique plano e o
posicionamos abaixo da árvore. E, claro, terminamos adicionando-o à árvore.
Mas não terminamos com nossa malha de limite. No final, o colocamos na lista de
espaços não permitidos. Agora, toda vez que criarmos uma árvore com a função
makeTreeAt, estamos construindo essa lista. Vamos fazer algo com essa lista.
Vamos criar e adicionar uma função isColliding (). Adicionaremos essa função
após nossas outras funções, walk (), isWalking () e acrobatics (), e logo acima dos
atendentes de evento keydown e keyup.
function isColliding() {
var vector = new THREE.Vector3(0, -1, 0);
var raycaster = new THREE.Raycaster (marker.position, vector);
var intersects = raycaster.intersectObjects (notAllowed);
if (intersects.length > 0) return true;
return false;
}
if (code == 'ArrowLeft') {
marker.position.x = marker.position.x - 5;
isMovingLeft = true;
}
Uma mudança como essa pode significar que o avatar cruzou os limites. Nesse
caso, temos que desfazer a mudança imediatamente. Adicione o código a seguir na
parte inferior de sendKeyDown (). Coloque-o logo após a linha if (code == ’KeyF’)
e acima da linha com a chave de fechamento.
if (isColliding ()) {
if (isMovingLeft) marker.position.x = marker.position.x + 5;
if (isMovingRight) marker.position.x = marker.position.x - 5;
if (isMovingForward) marker.position.z = marker.position.z + 5;
if (isMovingBack) marker.position.z = marker.position.z - 5;
}
Leia estas linhas para ter certeza de entendê-las. Esse trecho de código diz: “se
detectarmos uma colisão, verifique a direção em que estamos nos movendo. Se
estivermos nos movendo para a esquerda, então inverta o movimento que o avatar
acabou de fazer - volte na direção oposta na mesma proporção”.
Com isso, nosso avatar pode caminhar até os limites da árvore, mas não vai além.
Uau! Isso pode parecer um código muito fácil, mas você acabou de resolver um
problema muito difícil na programação do jogo.
Qual o Próximo
A detecção de colisão em jogos é um problema realmente complicado de resolver,
então parabéns por chegar até aqui. Fica ainda mais difícil quando você precisa se
preocupar em mover-se para cima e para baixo, além de esquerda, direita, para trás
e para frente. Mas o conceito é o mesmo.
Normalmente, contamos com coleções de código, escritas por outras pessoas para
nos ajudar nesses casos. Usaremos essa biblioteca de código quando chegarmos
aos minijogos.
Mas primeiro vamos dar o toque final em nosso jogo de avatar. No próximo
capítulo, adicionaremos sons e pontuação. Vamos lá!
Quando terminar este capítulo, você será capaz de:
- Adicionar sons aos jogos.
- Ser capaz de adicionar uma pontuação simples a um jogo.
- Ter um jogo bobo para jogar.
Capítulo 11
Primeiros passos
Para fazer este jogo, precisamos do avatar, das árvores e das funções de detecção
de colisão nas quais trabalhamos ao longo deste livro. Após o Capítulo 10, Projeto:
Colisões, temos tudo de que precisamos para começar este projeto. Então, vamos
fazer uma cópia do projeto Collisions.
No menu do Editor de código 3DE, selecione MAKE A COPY e digite Fruit Hunt!
como o novo nome do projeto.
Para manter a pontuação neste jogo, precisamos de algo novo - um placar.
Para fazer sons em nosso jogo, precisamos de algo que possa reproduzir áudio.
Em vez de escrever nosso próprio placar e código de som, carregaremos coleções
de código para fazer isso por nós.
No Capítulo 9, O que é todo esse outro código?, vimos como carregar coleções
de código com a tag <script> bem no topo do código. Precisamos adicionar mais
dois.
Faremos essas mudanças iniciando uma nova linha após as duas tags <script> no
topo da página com atributos src. Adicione as seguintes tags <script> para obter o
placar e o código de som.
<script src="/scoreboard.js"></script>
<script src="/sounds.js"></script>
Uma vez que esta é apenas a seção de "primeiros passos" do nosso programa,
essas linhas não vão realmente mudar nada no jogo. Para ver o placar, precisamos
configurá-lo e ligá-lo. Vamos fazer isso a seguir.
Isso cria um novo placar. Diz ao placar para iniciar uma contagem regressiva,
mostrar a pontuação e adicionar uma mensagem de ajuda. Isso deve adicionar um
placar bacana à nossa tela, completo com o tempo restante do jogo (45 segundos),
a pontuação atual (0) e uma dica para os jogadores que podem pressionar a tecla de
ponto de interrogação (?) para obter alguma ajuda.
O placar deve ser parecido com o seguinte:
Antes de fazer o jogo se comportar da maneira que o texto de ajuda diz que
deveria, precisamos ensinar ao jogo o que fazer quando o tempo acabar. Para fazer
isso, adicione o seguinte na linha após todo o código do placar:
scoreboard.onTimeExpired (timeExpired);
function timeExpired() {
scoreboard.message ("Game Over!");
}
Aqui, informamos ao placar que, no momento em que o tempo expirar, ele deve
chamar a função timeExpired (). Essa função define a mensagem do placar para
Game Over!
É isso para o placar. Agora, vamos descobrir uma maneira de o jogador adicionar
pontos ao placar.
notAllowed.push(boundary);
treeTops.push(top);
Agora que temos uma lista das copas das árvores, podemos esconder o tesouro
em uma e sacudi-la. Após as quatro chamadas para a função makeTreeAt, adicione
a seguinte função para atualizar o número da árvore do tesouro:
var treasureTreeNumber;
function updateTreasureTreeNumber () {
var rand = Math.random() * treeTops.length;
treasureTreeNumber = Math.floor(rand);
}
Math.random () é um velho amigo agora. Ele retorna um número decimal entre 0,0
e 1,0. Multiplicamos isso pelo número total de copas das árvores, que atualmente é
4. Isso nos dá um número aleatório entre 0,0 e 4,0, o que nos deixa perto do que
precisamos para escolher uma copa da lista. Mas precisamos de uma ajudinha da
função Math.floor ().
Vimos no Capítulo 7, Uma Análise mais Detalhada dos Fundamentos do
JavaScript, que a primeira coisa em uma lista de JavaScript está em 0. Se houver
quatro coisas em nossa lista treeTops, a primeira está em treeTops [0] e as outras
em treeTops [1], treeTops [2], e treeTops [3]. Isso significa que precisamos definir
treasureTreeNumber como 0, 1, 2 ou 3. No momento, temos números aleatórios
como 0,1223, 1,448, 2,993 e 3,8822. O que queremos é apenas o número antes da
vírgula decimal. Isso é exatamente o que Math.floor () faz!
Agora que temos uma maneira de escolher aleatoriamente uma árvore do tesouro,
precisamos sacudir essa árvore. Adicione o seguinte abaixo da função
updateTreasureTreeNumber ():
function shakeTreasureTree() {
① updateTreasureTreeNumber();
② var tween = new TWEEN.Tween({shake: 0});
③ tween.to({shake: 20 * 2 Math.PI}, 81000);
④ tween.onUpdate(shakeTreeUpdate);
⑤ tween.onComplete(shakeTreeComplete);
⑥ tween.start();
}
Esta função tem muito código, mas faz apenas duas coisas: atualizar o número da
árvore do tesouro e sacudir a árvore. Cada uma das seis linhas faz algo importante:
③ Define o ponto final e o tempo que levará para chegar lá. O ponto final é
20 vezes 2 × pi. O tremor é visto por 8 segundos, ou 8.000
milissegundos.
⑥ Começa a tremer!
function shakeTreeUpdate(update) {
var top = treeTops[treasureTreeNumber];
top.position.x = 50 * Math.sin(update.shake);
}
function shakeTreeComplete () {
var top = treeTops[treasureTreeNumber];
top.position.x = 0;
setTimeout (shakeTreasureTree, 2*1000);
}
Novamente, esta função captura a copa da árvore atual. Em seguida, ele move a
copa das árvores de volta para o centro do tronco, definindo sua posição x para 0.
A última parte de shakeTreeComplete define um tempo limite de 2 segundos. A
função setTimeout () em JavaScript chama outra função após um determinado
período de tempo. Nesse caso, esperamos 2 segundos depois que a árvore do
tesouro parar de tremer e, em seguida, chamamos a função shakeTreasureTree
para escolher uma nova árvore do tesouro e começar a tremer.
Tudo o que resta agora é chamar shakeTreasureTree () pela primeira vez.
Adicione a seguinte chamada a shakeTreasureTree () abaixo de
shakeTreeComplete ():
shakeTreasureTree();
Depois de digitar tudo isso, uma árvore diferente deve estar balançando
incontrolavelmente, dizendo ao jogador que há um tesouro para coletar. Agora que
temos um tesouro nas árvores, vamos dar ao avatar uma maneira de pegar esse
tesouro.
Você pode adicionar esse código if logo acima das outras instruções if que
transformam o avatar. Com isso, se a tecla de espaço for pressionada, a função
jump() é chamada. Adicionamos a função jump() acima da função isColliding.
function jump() {
if (avatar.position.y > 0) return;
checkForTreasure();
animateJump();
}
Se o avatar já estiver pulando - se sua posição y para cima e para baixo for maior
que 0, o código retornará sem fazer nada. Caso contrário, verificamos se há um
tesouro próximo e animamos o salto em nossa tela. Para verificar se o avatar está
perto o suficiente para pegar o tesouro, adicione a função checkForTreasure ()
abaixo de jump ().
function checkForTreasure() {
var top = treeTops[treasureTreeNumber];
var tree = top.parent;
var p1 = tree.position;
var p2 = marker.position;
var xDiff = p1.x - p2.x;
var zDiff = p1.z - p2.z;
var distance = Math.sqrt(xDiff*xDiff + zDiff*zDiff);
if (distance < 500) scorePoints();
}
function scorePoints () {
if (scoreboard.getTimeRemaining () == 0) return;
scoreboard.addPoints (10);
}
function animateJump () {
var tween = new TWEEN.Tween({jump: 0});
tween.to({jump: Math.PI}, 400);
tween.onUpdate(animateJumpUpdate);
tween.onComplete(animateJumpComplete);
tween.start();
}
function animateJumpComplete() {
avatar.position.y = 0;
}
Isso deve resolver! Se você ocultar o código, agora você pode se mover, encontrar
a árvore ativa e pular para pegar o tesouro dela. Se você for muito rápido, pode até
pular várias vezes ao lado da árvore ativa para obter vários pontos.
Este já é um jogo divertido, mas podemos adicionar alguns ajustes para torná-lo
ainda melhor.
function scorePoints() {
if (scoreboard.getTimeRemaining() == 0) return;
scoreboard.addPoints(10);
» Sounds.bubble.play();
}
var fruit;
function animateFruit() {
if (fruit) return;
function animateFruitUpdate(update) {
fruit.position.y = update.height;
fruit.rotation.x = update.spin;
}
function animateFruitComplete() {
marker.remove(fruit);
fruit = undefined;
}
É outra interpolação com funções! A única mudança aqui é que estamos definindo
duas propriedades de número diferentes: o spin (giro) e o height (altura) da fruta. O
giro começa em 0 e gira cerca de quatro vezes ao longo de toda a animação. A fruta
também sobe da posição 200 para 350 na tela ao longo da animação.
Obviamente, a função animateFruit precisa ser chamada antes de fazer qualquer
coisa. Adicione uma chamada a ele na parte inferior da função scorePoints para
que tenha a seguinte aparência:
function scorePoints() {
if (scoreboard.getTimeRemaining() == 0) return;
scoreboard.addPoints(10);
Sounds.bubble.play();
» animateFruit();
}
Neste capítulo, vamos cobrir como construir formas e materiais interessantes que
pareçam com isso:
Primeiros passos
Inicie um novo projeto no 3DE. Escolha o modelo de projeto 3D inicial (com
animação) no menu. Em seguida, salve-o com o nome Luzes e materiais.
Voltaremos a trabalhar com o melhor formato do mundo, o donut. Portanto,
adicione o seguinte código abaixo de COMECE A CODIFICAÇÃO NA PRÓXIMA
LINHA.
Se você fez tudo corretamente, verá um donut vermelho muito opaco. Você pode
estar pensando: "É isso?" Bem, claro que não é isso!
A coisa mais importante a notar aqui é que estamos usando um novo material, o
MeshPhongMaterial. Este material possui uma série de características que o
tornam mais real. Os computadores têm que trabalhar mais para renderizar esse
material, portanto, use-o apenas quando achar que seus jogos serão beneficiados.
ATENÇÃO Importante
O MeshPhongMaterial recebeu o nome do programador de computador Bui
Tuong Phong, o que inventou as técnicas que tornam esses materiais tão bonitos.
Somos capazes de fazer coisas incríveis hoje por causa dos programadores que
vieram antes de nós. Devemos muito a eles por seu trabalho. Podemos retribuir
criando coisas incríveis, compartilhando-as e ajudando outras pessoas a construir
suas próprias coisas bonitas. Assim como Phong fez!
Antes de começarmos a brincar com as cores, vamos girar o donut como fizemos
no Capítulo 1, Projeto: Criando formas simples. Adicione o código de rotação
dentro da função animate ().
Isso não vai mudar muito. Um donut vermelho sem graça ainda deve estar girando
no meio da cena. Vamos iluminar um pouco as coisas.
Emitindo luz
A luz opaca na cena vem de uma luz no topo do código.
Comente essa luz.
Sem luz, tudo fica preto. Temos um donut vermelho girando em uma sala escura e
não podemos vê-lo.
O primeiro tipo de luz com que podemos brincar é a luz “emissiva”. É a luz com a
qual o próprio objeto brilha. É como uma lâmpada LED branca que pode brilhar em
cores diferentes - embora a própria lâmpada seja branca, ela pode brilhar em
vermelho, verde, amarelo ...
Então, vamos voltar ao nosso código de donut para fazer nosso, donut vermelho ...
brilhar em amarelo.
A luz emissiva tem seus usos, mas outras luzes podem ser mais divertidas.
Portanto, comente (ou apenas exclua) a luz emissiva.
// cover.emissive.set('yellow');
Estamos de volta a uma sala escura. Em vez de fazer nosso donut brilhar, vamos
adicionar algumas luzes para brilhar no donut.
Luz ambiente
Primeiro, vamos adicionar um pouco de luz "ambiente" à cena. Uma cena
raramente fica completamente preta. Normalmente há um pouco de iluminação.
A luz que comentamos no início do nosso código é esse tipo de luz. Então, vamos
excluir o comentário e mudar seu brilho de um bastante alto 0,8 (1,0 é o brilho
máximo) para um escuro 0,1 (0,0 é sem luz).
Se você ocultar o código, verá que essa quantidade de luz ambiente mal dá para
ver o donut. Provavelmente parece muito escuro, mas é perfeito ao trabalhar com
outras luzes. Isso é exatamente o que faremos na próxima seção!
Ponto de luz
Para aumentar o realismo, vamos adicionar um "ponto de luz", que é como uma
espécie de lâmpada. Adicione o código para nossa luz pontual abaixo do código do
donut.
Estamos posicionando a luz acima e atrás do donut. A luz pontual é brilhante, mas
não muito brilhante em 0,8. O resultado deve ser um donut de aparência bem legal:
Uma coisa estranha sobre a programação com luzes 3D é que a luz não vem de
um objeto na cena. Posicionamos a fonte de luz nas coordenadas XY-Z de (0, 300, -
100). A luz vem daqui, mas não há lâmpada à vista.
Muitas vezes ajuda ver uma lâmpada, então vamos adicionar uma lâmpada branca
brilhante à luz pontual. A luz já está lá, então este é apenas um marcador - uma
lâmpada falsa. Adicione a lâmpada falsa abaixo do código de luz pontual real.
Brilho
O donut já tem uma aparência bem legal. A seguir, vamos brincar com o brilho do
material que envolve o donut. Na programação 3D, a cor de brilho é chamada de cor
especular. A cor especular combina com a luz que brilha sobre ela. Se uma luz
brilhante incide sobre uma cor especular escura, ela produz muito pouco brilho. É
assim que o material se parece agora. Se tornarmos uma cor especular mais
brilhante, uma luz brilhante se combinará para produzir brilho.
Defina a cor especular do material do nosso donut logo após criarmos a capa.
Sombras
Desenhar sombras dá muito trabalho para os computadores, então não enlouqueça
com eles. Já que eles dão muito trabalho, eles são desativados no início.
Precisamos trabalhar cuidadosamente em cada objeto que precisa de uma sombra -
e ativar as sombras para a cena e a luz também.
A primeira coisa que precisamos é de algum terreno para ver uma sombra - não
veremos uma sombra, a menos que ela caia sobre algo. Adicione o seguinte código
para criação do solo, abaixo do código do donut e acima do código da luz.
Isso cria um plano, gira-o plano e adiciona-o à cena - sem sombras. Para ativar as
sombras, seguimos estas quatro etapas:
Em seguida, queremos que nosso donut projete uma sombra, então habilite a
propriedade castShadow no donut.
Com isso, devemos ter uma sombra para nosso donut giratório. Se você não vir
uma sombra, verifique o console JavaScript e certifique-se de ter seguido cada uma
das quatro etapas necessárias para as sombras.
Quatro etapas podem parecer muito, mas as sombras exigem muito trabalho do
computador. Se cada luz cria sombras e cada objeto projeta uma sombra e cada
objeto pode ter uma sombra caindo sobre ele ... bem, então o computador vai fazer
com que cada objeto possa ter uma sombra caindo sobre ele ... bem, então o
computador vai usar todos os seu poder desenha sombras e não sobrou nada para o
usuário realmente jogar.
Vamos Brincar!
Luzes e materiais têm muitas propriedades que interagem entre si. A melhor
maneira de entendê-los é jogando! Mude a cor da luz pontual para roxo. O que
acontece se a luz for azul, mas o donut for vermelho? Altere a quantidade de RGB
especular no donut - primeiro mantendo todos os três números iguais e depois
tornando os valores de verde e azul (o segundo e o terceiro valores) 0.
Isso move o donut para frente e para trás, mesmo enquanto ele continua a girar.
Também vamos diminuir um pouco a luz pontual, já que teremos mais de uma luz
brilhando em nosso donut.
» var point = new THREE.PointLight('white', 0.4);
point.position.set(0, 300, -100);
point.castShadow = true;
scene.add(point);
Isso cria um white spotlight (holofote branco) com brilho médio de 0,4. Ele
posiciona o light200 à direita e 300 acima do centro da cena.
Essa luz também projeta sombras. Antes de adicionar essa luz à cena, fazemos
algo um pouco diferente: trocamos a câmera da sombra.
Agora você deve esperar que o código 3D tente fazer o mínimo de trabalho
possível. Uma maneira de o código 3D fazer isso é reutilizando o código da câmera
para desenhar sombras. Em vez de ver objetos, as câmeras de sombra veem ...
sombras. Outra maneira de o código 3D limitar a quantidade de trabalho é fazer com
que as câmeras de sombra não “vejam” muito longe.
Vamos Brincar!
Tente diminuir o valor de spot.shadow.camera.far para algo como 500. A sombra
do holofote deve ser cortada. A câmera de sombra simplesmente não se preocupa
em ver - ou desenhar - uma sombra além disso, o que resulta em uma sombra
quebrada. Um valor de 750 acabou sendo apenas o suficiente para ver uma sombra
adequada nesta cena.
Se desejar, você pode adicionar um holofote falso (de araque, de imitação) após o
código do holofote, assim como fizemos com a luz pontual.
Observe que tanto com o ponto de luz quanto com o holofote, a sombra do nosso
donut fica mais longa à medida que se afasta da luz. É assim que as sombras da
vida real funcionam com lâmpadas e holofotes, mas não é assim que nossas
sombras agem à luz do sol. À luz do sol, nossas sombras ficam exatamente do
mesmo comprimento mesmo quando caminhamos.
Na programação 3D, a luz solar é feita com uma luz “direcional”. Para ver isso,
comente a linha que adiciona o destaque à cena.
Então, abaixo do código falso do holofote, adicione uma luz direcional, colocando-a
nas mesmas coordenadas que usamos para o holofote.
Tal como acontece com o holofote, temos que ajustar a câmera de sombra da luz
direcional. A câmera de sombra do holofote era preguiçosa em desenhar sombras
para longe da luz. A câmera de sombra da luz direcional é preguiçosa em todos os
outros lugares: para cima, para baixo, para a esquerda e para a direita! Mas, uma
vez que instruímos a câmera de sombra a não ser muito preguiçosa, devemos ter
uma sombra que permaneça exatamente do mesmo tamanho enquanto nosso donut
se move para frente e para trás.
Normalmente não é necessário adicionar uma luz falsa para a luz do sol. O
verdadeiro Sol está bem alto no céu. Podemos fingir que o sol em nossos jogos
também está alto.
Textura
Tudo isso parece incrível. Mas ainda podemos fazer mais uma coisa para torná-lo
ainda melhor. Acima do código para o solo, vamos carregar uma imagem de
“textura”. Então, logo após a capa ser criada, atribuímos essa textura à propriedade
map (mapa) da capa.
Exploração posterior
É fácil se perder brincando com luzes e materiais. É muito engraçado. Também
pode ser frustrante tentar fazer tudo certo. Esta é uma parte importante da
programação do jogo, mas não tão importante quanto a jogabilidade.
As próximas seções estão incluídas caso você realmente tenha gostado deste
capítulo e queira jogar um pouco mais. Divirta-se, mas não exagere!
<script src="/three.js"></script>
» <script src="controlsOrbitControls.js"></script>
É isso aí! Se você ocultar o código, poderá clicar e arrastar com o mouse para girar
a cena. Você também pode rolar com o mouse ou touchpad para aumentar e
diminuir o zoom.
Ajustes finais
Para tornar uma cena o mais realista possível sem muito poder de computação, a
primeira coisa que faremos é remover nossa luz pontual. Das três luzes que podem
projetar sombras, as luzes pontuais são as mais difíceis nos computadores. Uma vez
que eles brilham em todas as direções, o computador precisa olhar em todas as
direções em busca de objetos que projetem sombras. Isso é muito trabalho.
As luzes pontuais ainda podem ser úteis, especialmente se não precisarem projetar
uma sombra. Mas vamos removê-lo aqui comentando a linha que adiciona a luz
pontual ao cenário.
// scene.add(point);
Já vimos todas as opções que têm maior impacto nas luzes direcionais, então não
precisamos ajustá-las ainda mais aqui.
Adicione o holofote de volta à cena, aumente seu brilho para 0,7 e adicione as
configurações de angle (ângulo) e penumbra (penumbra):
» var spot = new THREE.SpotLight('white', 0.7);
spot.position.set(200, 300, 0);
spot.castShadow = true;
spot.shadow.camera.far = 750;
» spot.angle = Math.PI/4;
» spot.penumbra = 0.1;
» scene.add(spot);
O ângulo descreve o quão estreito o holofote deve ser. A penumbra descreve como
as bordas do holofote devem ser confusas.
Vamos Brincar!
Brinque com esses números. O ângulo não pode ser maior do que Math.PI / 2,
então tente números entre esse e Math.PI / 100. A penumbra pode ser um número
entre 0 (bordas claras não difusas) e 1 (muito difusas). Quais números podem ser os
melhores para uma luz de pesquisa? Qual número faria a cena mais assustadora?
O resultado final, depois de girar a cena, pode ser algo como:
Isso, meus colegas programadores 3D, é lindo!
Neste capítulo, cobriremos algo que todo programador 3D deve aprender em algum
momento: como visualizar a Lua e suas fases. Vai ficar parecido com isto:
Por que a fase da Lua é importante para os programadores 3D? Primeiro, é um
problema bastante simples - o Sol brilha na Lua, que gira em torno da Terra. Além
disso, nos permite usar nosso conhecimento sobre luzes e materiais. Também nos
permite fazer os truques de posicionamento relativo que praticamos anteriormente
com as mãos e os pés de um avatar.
Não é importante o suficiente? Vá assistir Toy Story. Foi o primeiro longa-metragem
animado por computador, e os programadores por trás do projeto garantiram que
eles acertassem. Uma lua crescente é visível atrás de Woody e Buzz enquanto eles
discutem por terem sido deixados por Andy no posto de gasolina. Se foi importante o
suficiente para esses cineastas, é importante o suficiente para nós!
Primeiros passos
Inicie um novo projeto no 3DE. Escolha o 3D starter project - with Animation
(modelo de projeto 3D inicial - com animação) no menu. Em seguida, salve-o com o
nome Moon Phases.
Antes de codificar, vamos diminuir o brilho da luz ambiente que é automaticamente
adicionada à nossa cena.
Existe alguma luz ambiente no espaço - especialmente aquela luz refletida perto de
planetas e luas. Mas quase toda a luz do sistema solar vem do sol. Adicionaremos o
Sol em breve.
Além de mudar a luz, vamos mudar também para uma câmera ortográfica. Quando
falamos sobre essas câmeras no Capítulo 9, O que é todo esse outro código?
mencionamos que os jogos espaciais eram realmente um bom uso para elas. E aqui
estamos em um jogo espacial! Bem, uma simulação espacial, mas perto o suficiente.
Comente a linha PerspectiveCamera (ou exclua-a) e adicione a câmera ortográfica
conforme mostrado:
Não se esqueça da última linha que atribui a câmera a uma segunda variável,
aboveCam. Fazemos isso para que possamos mudar a câmera entre esta câmera
sobre o sistema solar e uma outra. No momento, a camera e aboveCamera,
apontam para a mesma câmera, mas vamos mudar isso um pouco.
Isso é tudo para a configuração. Vamos começar adicionando o Sol ao centro do
sistema solar.
O Sol no Centro
Você está se tornando um especialista com objetos 3D, então você sabe o que
fazer. Abaixo da linha START CODING, vamos criar uma capa (cover) e uma forma
(shape), combiná-los em uma malha (mesh) e adicioná-los à cena (scene).
Agora que entendemos melhor essas coisas no Capítulo 12: Trabalhando com
Luzes e Materiais, estamos usando um material Phong para o Sol. Isso permite que
o Sol emita luz amarela - para que possa brilhar em amarelo.
O Sol tem que fazer mais do que brilhar. Tem que iluminar tudo o mais no sistema
solar. Como vimos no capítulo anterior, emitir uma cor não ilumina a programação
3D. Para isso, adicionamos uma luz pontual - uma luz de “lâmpada”.
var sunlight = new THREE.PointLight('white', 1.7);
sun.add(sunlight);
É um pouco difícil ver a Terra. Estamos olhando para o Pólo Norte da Terra com o
Oceano Pacífico voltado para o sol. Será mais fácil ver quando a Terra começar a
girar e orbitar. Então, vamos fazer isso a seguir.
function gameStep() {
setTimeout(gameStep, 1000/30);
if (pause) return;
days = days + speed * clock2.getDelta();
earth.rotation.y = days;
var years = days / 365.25;
earth.position.x = 300 * Math.cos(years);
earth.position.z = -300 * Math.sin(years);
}
gameStep();
Dentro de gameStep (), a primeira coisa que fazemos é definir um tempo limite
para chamar gameStep () novamente. Usamos setTimeout () no Capítulo 11,
Projeto: Caça às Frutas para esperar antes de sacudir uma nova árvore de tesouro.
Aqui, usamos para esperar um pouco antes de atualizar nossa simulação.
Leva um dia para a Terra fazer uma rotação. Demora 365,25 dias para fazer uma
órbita completa em torno do sol. Em outras palavras, depois de um dia, a Terra está
a 1/365,25 do caminho em sua órbita - ou 1/365,25 do caminho ao longo de um ano.
Assim, atribuímos a quantidade parcial de anos que se passaram para dias/365,25.
Em seguida, usamos nossos antigos companheiros de trigonometria seno e
cosseno para converter a quantidade parcial de anos nas posições X e Z. Se você
ainda não fez trigonometria na escola, não se preocupe muito com o funcionamento
do seno e do cosseno. Apenas saiba que eles podem trabalhar juntos dessa forma
para fazer círculos. E preste atenção na aula de matemática ao falar de senos e
cossenos - eles são incríveis!
Com tudo isso, devemos ter a Terra girando muito rápida e lentamente girando em
sua órbita.
Vamos fazer mais uma coisa com nossa lógica de jogo antes de adicionar a Lua à
simulação. Adicione um atendente de teclado (document.addEventListener) após
a chamada para gameStep ().
Coordenadas locais
Vamos tentar construir a Lua. Não vamos adicioná-lo à cena ainda - apenas
construí-lo no início. Adicione o seguinte abaixo do código que adiciona a Terra à
cena:
scene.add(moon);
Você provavelmente sabia que não ia funcionar muito bem. A Lua é adicionada à
cena, mas apenas fica lá um pouco afastada do centro, sem se mover.
Em vez disso, podemos mudar a última linha para que a Lua seja adicionada à
Terra.
earth.add(moon);
Isso também não está certo. A Lua está agora ao lado da Terra. Ele até permanece
com a Terra enquanto a Terra gira em torno do Sol. Mas a Lua está girando ao redor
da Terra tão rápido quanto a rotação da Terra. Uma vez que adicionamos a Lua à
Terra, sempre que a Terra gira, a Lua vai junto com ela. Como a Lua real leva um
pouco mais de 29 dias para dar a volta na Terra, isso não está certo.
Para fazer isso funcionar, precisamos pegar emprestadas as ideias que usamos no
Capítulo 8, Projeto: Girando nosso avatar para girar nosso avatar sem mover a
câmera. Nesse jogo, adicionamos a câmera e o jogador a um marcador. Faremos o
mesmo aqui, mas vamos chamá-lo de coordenadas locais em vez de marcador. Eles
significam a mesma coisa, mas as coordenadas locais são a maneira mais comum
de dizer isso na programação 3D.
Você pode perder brevemente a visão da Terra depois de fazer essa alteração.
Isso está ok.
Também comentamos (ou excluímos) a linha que define a posição X da Terra. Não
mudamos mais a posição da Terra. Em vez disso, adicionamos a Terra a um sistema
de coordenadas local, que já posicionamos 300 de distância do sol.
A seguir, vamos mudar a lógica de simulação em gameStep () para mover as
coordenadas locais da Terra em vez da Terra.
function gameStep () {
setTimeout (gameStep, 1000/30);
if (pause) return;
days = days + speed * clock2.getDelta();
earth.rotation.y = days;
var years = days / 365.25;
» earthLocal.position.x = 300 * Math.cos(years);
» earthLocal.position.z = -300 * Math.sin(years);
}
Com isso, a Lua não está mais girando descontroladamente em torno da Terra. Ela
não está se movendo ao redor da Terra - ela sempre permanece "abaixo" da Terra,
pois vemos o sistema solar de cima.
São três linhas de código. Vamos realmente fazer a Lua girar em torno da Terra
com uma linha? Sim!
Na função gameStep (), giramos a órbita da Lua da mesma forma que giramos a
Terra, apenas 29,5 vezes mais lento.
function gameStep() {
setTimeout(gameStep, 1000/30);
if (pause) return;
days = days + speed * clock2.getDelta();
earth.rotation.y = days;
var years = days / 365.25;
earthLocal.position.x = 300 * Math.cos(years);
earthLocal.position.z = -300 * Math.sin(years);
» moonOrbit.rotation.y = days / 29.5;
}
camera = moonCam;
Essa última linha atribui a câmera à nossa nova moonCam. A função animate ()
usa essa variável de câmera para renderizar a cena. Ao atribuir a câmera à
moonCam, nossa visão mudará para a Lua em vez de olhar para o sistema solar de
cima.
A câmera da lua (moon-cam) é uma câmera em perspectiva para obter uma
melhor visão da lua. Nós o posicionamos a 25 de distância do centro do sistema de
coordenadas local da Terra, o que o coloca um pouco acima da superfície da Terra,
que demos um raio de 20. Giramos a câmera para ficar de frente para a Lua e
adicionamos a câmera à sua órbita.
Adicionamos a câmera da lua (moon-cam) à órbita da lua para que ela sempre
fique de frente para a lua. Conforme a órbita da Lua gira, a câmera lunar irá girar
junto com ela. Você pode pensar na órbita da Lua - outro sistema de coordenadas
local - como uma placa na qual colamos a câmera e a Lua.
Quando giramos a placa, a Lua e a câmera giram junto com ela:
Agora que temos duas câmeras, vamos adicionar o código para alternar para frente
e para trás. Na função sendKeyDown () que escrevemos na parte inferior do nosso
código, adicione outra instrução if.
function sendKeyDown(event) {
var code = event.code;
if (code == 'Digit1') speed = 1;
if (code == 'Digit2') speed = 10;
if (code == 'Digit3') speed = 100;
if (code == 'Digit4') speed = 1000;
if (code == 'KeyP') pauseUnpause();
function switchCamera () {
if (camera == moonCam) camera = aboveCam;
else camera = moonCam;
}
É muito legal ver a Lua girar em torno da Terra enquanto a Terra gira em torno do
sol, e alternar entre a câmera acima e a câmera lunar. É especialmente útil
desacelerar a simulação para 1 ou pausar (pause) a simulação e alternar (switch)
entre as câmeras.
Bônus # 1: Estrelas
O que é uma simulação espacial sem estrelas? Estrelas em jogos 3D são muito
interessantes. Daria muito trabalho no computador simular estrelas gerando 500
malhas de esferas e movendo-as para muito longe. Em vez disso, os programadores
3D usam um material especial para criar uma forma com muitos pontos. Para facilitar
no computador, este material de pontos mostra apenas os pontos na forma, em vez
de cobrir suavemente a forma inteira como materiais regulares.
Abaixo do código da Lua, comece criando a capa e a forma de nossas pontas
estelares.
var cover = new THREE.PointsMaterial ({color: 'white', size: 15});
var shape = new THREE.Geometry();
shape.vertices.push(point);
}
Isso pode parecer uma matemática assustadora, mas não é tão ruim. Ele faz um
loop em mais de 500 números, criando um ponto para cada um a uma distância de
4.000. Ele faz isso aleatoriamente - escolhendo ângulos, convertendo os ângulos em
coordenadas X-Y-Z e adicionando essas coordenadas à nossa forma geométrica
básica.
Se você estiver curioso, as variáveis ra e dec são ascensão reta (r e a iniciais de
right ascension) e declinação (dec letras iniciais de declination). Os astrônomos
usam esses dois valores para descrever locais no céu. A ascensão reta (ra)
descreve o quão distante a leste ou oeste uma estrela ou planeta está - como a
longitude, mas para o céu noturno. A declinação (dec) descreve o quão distante ao
norte ou ao sul uma coisa está - como a latitude. Usando ascensão reta e
declinação, os astrônomos podem localizar qualquer coisa no céu. Escolhemos
valores aleatoriamente para ambos e, em seguida, usamos senos e cossenos para
converter esses ângulos em pontos muito distantes, fazendo-os parecer estrelas.
Os pontos em uma geometria são chamados de vértices. Após adicionar 500
pontos aleatórios aos vértices de nossa forma, criamos uma malha de pontos e a
adicionamos à cena.
Vamos precisar de mais uma câmera para isso. Logo abaixo do código da câmera
da lua, adicione uma câmera da nave:
Adicione o código dos controles de vôo logo abaixo disso. (Veja o Guia do
Mochileiro da Galáxia [Ada95]) para entender a importância da constante 42.
Na função animate (), comente (ou exclua) a linha que obtém o tempo decorrido.
Além disso, adicione linhas para obter a mudança no tempo e atualizar nossos
controles.
function sendKeyDown(event) {
var code = event.code;
if (code == 'Digit1') speed = 1;
if (code == 'Digit2') speed = 10;
if (code == 'Digit3') speed = 100;
if (code == 'Digit4') speed = 1000;
if (code == 'KeyP') pauseUnpause();
if (code == 'KeyC') switchCamera();
» if (code == 'KeyF') fly();
}
Coloque a função fly () bem no final do nosso código e faça com que ela mude a
câmera para shipCam.
function fly () {
camera = shipCam;
}
Temos uma boa recompensa aqui quando colocamos nossa lógica de simulação
dentro da função gameStep () em vez da função animate (). Como nossos controles
de vôo estão dentro de animate (), podemos pausar a simulação e ainda voar
explorando a simulação pausada. A animação ainda funciona, embora os planetas
estejam pausados!
Você ainda pode usar as teclas W, A, S, D, Q, E, R, F e setas conforme descrito no
Bônus # 2: Controles de vôo. Apenas não bata na Terra!
Compreendendo as fases
A Lua tem quatro fases principais: nova, quarto crescente, cheia e quarto
minguante. Lua nova é quando a Lua está entre a Terra e o Sol. Uma vez que o Sol
está brilhando no lado da Lua que não podemos ver, não vemos a Lua neste
momento (também, ela está na mesma parte do céu que o Sol).
O primeiro quarto ou quarto crescente, significa que a lua está a um quarto do
caminho em torno de sua órbita. Isso não significa que está um quarto aceso. Como
você pode ver, a lua crescente do primeiro quarto está meio cheia.
Quando a Lua está a dois quartos (ou lua cheia) do caminho ao redor da Terra, ela
é chamada de cheia. A parte da Lua que vemos está completamente iluminada.
Você sabe o que é o terceiro quarto ou quarto minguante. A lua está a três quartos
do caminho ao redor da Terra e, novamente, está meio iluminada, embora seja a
outra metade da lua do primeiro quarto.
Quando o lado iluminado está crescendo, diz-se que está crescendo. Quando está
ficando menor, diz-se que está diminuindo. E agora você sabe quase tudo que há
para saber sobre as fases da Lua. Melhor ainda, você tem sua própria simulação!
O código até agora Se você gostaria de verificar o código neste capítulo, ele está
incluído em Código: Fases da Lua.
Primeiros passos
Inicie um novo projeto no 3DE. Escolha 3D starter project - with Animation) (o
modelo de projeto 3D inicial - com animação) e nomeie esse projeto como Purple
Fruit Monster. Não use o modelo com a física para este projeto - vamos usar isso
em capítulos posteriores
<body></body>
<script src="/three.js"></script>
① <script src="/physi.js"></script>
② <script src="/scoreboard.js"></script>
① Vamos usar o código para simular movimentos da vida real, como cair, rolar e
colidir. Usamos a coleção de código Physijs (física + JavaScript), então não
temos que escrever todo o código de física nós mesmos.
// Physics settings
① Physijs.scripts.ammo = '/ammo.js';
② Physijs.scripts.worker = '/physijs_worker.js';
// The "scene" is where stuff in our game will happen:
③ var scene = new Physijs.Scene();
④ scene.setGravity(new THREE.Vector3( 0, -250, 0 ));
var flat = {flatShading: true};
var light = new THREE.AmbientLight('white', 0.8);
scene.add(light);
Resta apenas um último ajuste para que nossa cena simule ativamente a atividade
física. Vamos esperar até depois de adicionar alguns objetos à cena antes de
trabalhar nisso. Primeiro, vamos converter de uma cena 3D em uma cena
bidimensional.
Vamos fazer 2D
A mudança mais importante a fazer em um jogo 2D é usar uma câmera ortográfica.
De volta ao Capítulo 9: O que é todo esse outro código?, falamos sobre dois usos
para essas câmeras: visualizações de longa distância e jogos 2D. Usamos uma
câmera ortográfica para as longas distâncias do espaço no Capítulo 13, Projeto:
Fases da Lua. Agora usamos um para um jogo 2D.
Ainda trabalhando acima da linha START CODING, comente (ou exclua) o código
para a câmera em perspectiva normal. Em seguida, adicione uma
OrthographicCamera como mostrado.
Outra mudança que faremos é um céu azul. Para alterar a cor de toda a cena,
defina a cor "clear" - a cor que é desenhada quando a cena está livre de qualquer
outra coisa - para azul celeste.
Com isso, estamos prontos para começar a programar nosso jogo de salto.
Descreva o jogo
Vamos pensar em como podemos organizar nosso código. Para chegar até aqui no
livro, você escreveu muito código. Às vezes, deve ter sido difícil percorrer o código
para ver o que você fez. Você não é o primeiro programador a se deparar com esse
problema e não será o último. Felizmente, você pode aprender com os erros dos
programadores antes de você.
Uma das maneiras mais fáceis de organizar o código é tratá-lo um pouco como a
escrita. Quando você escreve uma dissertação, é útil começar com um esboço.
Depois de ter o esboço, você pode preencher os detalhes. Ao organizar o código, é
útil escrever primeiro o esboço e, em seguida, adicionar o código abaixo dele. Como
estamos programando, nossos esboços também são escritos em código. Digite o
seguinte, incluindo as barras duplas, abaixo de INICIAR CODIFICAÇÃO NA
PRÓXIMA LINHA.
Este esboço não inclui tudo no jogo, mas é muito. O terreno será a área de jogo. O
avatar é o jogador do jogo. O placar irá manter a pontuação e exibir informações
úteis.
As barras duplas no início de cada uma dessas linhas introduzem um comentário
JavaScript, que vimos pela primeira vez em Código é para Computadores e
Humanos, Comentários são Apenas para Humanos. Isso significa que o
JavaScript irá ignorar essas linhas. Isso é bom, pois ainda não definimos essas
funções.
Os programadores chamam isso de código de "comentário" para que ele não seja
executado. Os programadores fazem isso por vários motivos. Aqui, estamos fazendo
isso para delinear o código sem causar erros.
Definiremos essas funções na mesma ordem em que estão no esboço do código.
Isso torna mais fácil encontrar o código. Olhando para o esboço do código, sabemos
que a função addGround será definida antes da função addAvatar, que será
seguida por addScoreboard (). Quanto mais rápido podemos encontrar o código,
mais rápido podemos corrigi-lo ou adicionar coisas a ele. Quando você escreve
muito código, truques como este podem realmente ajudar a manter as coisas
corretas.
Depois de construir cada função, voltaremos a este esboço de código para remover
as barras duplas antes da chamada da função - vamos "descomentar" as chamadas
quando estiverem prontas.
Vamos começar a escrever o código que corresponde a este esboço.
Nosso terreno é uma caixa gigante. É como outras caixas que construímos - com
um toque especial. Em vez de uma malha simples e antiga, usamos um
Physijs.BoxMesh aqui. Malhas da coleção de código Physijs são como malhas
regulares, exceto que também podem se comportar como objetos físicos reais - elas
caem e ricocheteiam umas nas outras.
Ao criar uma malha Physijs, podemos passar um terceiro argumento além da
geometria e do material. Esse terceiro argumento é a massa do objeto, o que nos
permite tornar as coisas muito pesadas ou muito leves. Nesse caso, definimos a
massa com um número especial: 0. O 0 significa que a forma nunca se move. Se
não definíssemos a massa do solo para 0, o solo cairia como qualquer outra coisa!
Ao contrário das malhas regulares, as diferentes formas têm diferentes malhas
físicas. A lista inclui Physijs.BoxMesh, Physijs.CylinderMesh, Physijs.ConeMesh,
Physijs.PlaneMesh, Physijs.SphereMesh e para todas as outras formas,
Physijs.ConvexMesh.
Depois que essa função é definida, descomentamos a chamada para
addGrouund().
Se tudo estiver ok, vamos ver um fundo verde com um céu azul no fundo, conforme
mostrado na figura.
Sprites são gráficos que estão sempre voltados para a câmera, que é exatamente o
que queremos que nosso avatar 2D faça neste jogo. Sprites são supereficientes em
códigos gráficos. Sempre que podemos usá-los, tornamos muito mais fácil para o
computador fazer tudo o que é necessário para manter o jogo funcionando sem
problemas.
Sprites começam como pequenas coisas 1 a 1 em uma cena. Para ver este sprite,
nós o dimensionamos em 100 nas direções X e Y — nós o estendemos nas direções
esquerda / direita e para cima / baixo.
A malha de caixa (box mesh) no início de addAvatar () está fazendo todo o
trabalho de cair, colidir com frutas e colidir com o solo. Damos a ele uma pequena
massa de 1 para que seja fácil empurrar com os controles que adicionaremos um
pouco. É invisível porque definimos visível: falso em seu material, mas ainda está
lá. Adicionamos o sprite à malha da caixa para sabermos onde está o avatar.
A última coisa que fazemos em addAvatar () é definir os "fatores" angulares e
lineares. Esses fatores indicam o quanto um objeto pode girar ou se mover em
certas direções. Ao definir o fator angular para zero, estamos dizendo que nosso
avatar não pode girar em nenhuma direção. Mesmo que salte em uma fruta girando,
o avatar sempre ficará para cima e para baixo. Definindo o fator linear para dois
“Uns” e um “Zero” (1, 1, 0), estamos dizendo que o avatar pode se mover nas
direções X e Y, mas não na direção Z. Em outras palavras, estamos dizendo ao
nosso código 3D que, embora estejamos criando uma forma tridimensional, ela só se
moverá em duas dimensões.
Mova de volta para o esboço do código e descomente a chamada addAvatar.
Redefinindo a posição
Poderíamos ter posicionado o avatar em addAvatar (), mas apenas o adicionamos
à cena. Em vez disso, criaremos uma função separada para definir a posição. Por
que usar uma função separada? Assim, podemos reutilizá-la!
Quando falamos sobre funções no Capítulo 5, Funções: Use e Use Novamente,
dissemos que algumas funções contam parte de uma história. As funções em nosso
esboço de código fazem isso - elas contam a história da configuração do jogo.
Outro tipo de função é aquela que é chamada repetidamente. Vamos criar uma
daquelas funções que podem iniciar - ou reiniciar - o jogo movendo o avatar para
sua posição inicial. Adicione o seguinte após a função addAvatar ():
function reset() {
avatar.__dirtyPosition = true;
avatar.position.set(-0.6*w, 200, 0);
avatar.setLinearVelocity(new THREE.Vector3(0, 250, 0));
}
» reset ();
Isso deve deixar nosso avatar pairando acima do solo, à esquerda da tela.
Antes de adicionar controles para mover o avatar, temos que dizer ao nosso motor
de física para simular ativamente a gravidade e as colisões.
function gameStep () {
scene.simulate ();
setTimeout (gameStep, 1000/30);
}
gameStep ();
reset ();
Lançamento de frutas
Primeiro, para criar frutas, adicione uma função abaixo da função reset ().
function makeFruit () {
var shape = new THREE.SphereGeometry (40, 16, 24);
var cover = new THREE.MeshBasicMaterial ({visible: false});
var fruit = new Physijs.SphereMesh (shape, cover);
fruit.position.set (w, 40, 0);
scene.add (fruit);
return fruit;
}
Isso é uma boa quantidade de digitação, mas deve ser familiar. Estamos criando
uma esfera como um substituto habilitado para a física de frutas. Tal como acontece
com o avatar, tornamos esta esfera invisível. Para habilitar a física na esfera,
criamos um Physijs.SphereMesh. Antes de adicioná-lo à cena, o posicionamos à
direita da janela e logo acima do solo.
Para a imagem da fruta, usamos a segunda maneira de adicionar gráficos 2D
simples. Depois de carregar a imagem, mapeamos em um material básico e o
adicionamos a um plano simples - carregamos a imagem, mapeamos em um
material básico e o adicionamos a uma geometria plana simples. Não podemos usar
um sprite aqui como fizemos com o avatar, porque a imagem precisará girar
conforme a bola gira.
Definimos novamente os fatores angulares e lineares para que a fruta só possa se
mover e girar bidimensionalmente. O fator angular define apenas 1 para o eixo Z.
Isso significa que a fruta será capaz de girar como os ponteiros de um relógio
analógico.
Antes de retornar à fruta da função, definimos uma propriedade isFruit. Isso nos
ajudará mais tarde, quando precisarmos decidir se o avatar está colidindo com o
solo ou com uma dessas frutas.
Para ter certeza de que tudo isso foi digitado corretamente, adicione uma chamada
para makeFruit () após a definição da função. Você deve ver a fruta na extremidade
direita da tela e não deve haver erros no console JavaScript. Se tudo estiver OK,
remova a chamada makeFruit ().
Em seguida, precisamos lançar a fruta. Adicione a função launchFruit () acima da
definição da função makeFruit ().
function launchFruit() {
var speed = 500 + (10 Math.random() scoreboard.getScore ());
var fruit = makeFruit ();
fruit.setLinearVelocity (new THREE.Vector3 (-speed, 0, 0));
fruit.setAngularVelocity (new THREE.Vector3 (0, 0, 10));
}
launchFruit ();
setInterval (launchFruit, 3*1000);
A primeira chamada para launchFruit () lança uma única fruta imediatamente. Em
seguida, usamos setInterval () para continuar chamando launchFruit () a cada 3
segundos. A função setTimeout () que já vimos chama uma função uma vez após
um atraso. A função setInterval () faz a mesma coisa, mas continua chamando
continuamente.
Com isso, temos muitas frutas indo para o Purple Fruit Monster. Podemos até
usar os controles do teclado para rebater a fruta. Em seguida, precisamos registrar a
pontuação quando isso acontecer.
Isso se parece muito com o atendente de evento keydown que usamos para
responder às ações do teclado. Em vez de processar e enviar eventos de teclado
para o jogo, aqui enviamos eventos de colisão.
Se o avatar colidir com uma fruta, adicionamos 10 pontos à pontuação, damos um
pequeno salto ao avatar e removemos a fruta da tela (porque o Monstro Roxo da
Fruta comeu a fruta).
Esconda o código e experimente!
Fim do Jogo
Temos todos os elementos de que precisamos para este jogo, exceto um: uma
maneira de perder. Vamos adicionar o código para que o jogo termine quando o
Monstro de Fruta Roxo tocar o solo. Adicione o código para que o jogo termine
quando o Monstro de Fruta Roxo tocar o solo.
Quando o jogo termina, precisamos de uma maneira de dizer a várias partes do
código para parar de fazer o que estão fazendo - pelo menos até que o jogo seja
reiniciado. Usaremos uma variável gameOver para isso. Adicione-o acima do
esboço do código.
» var gameOver = false;
var ground = addGround ();
var avatar = addAvatar ();
var scoreboard = addScoreboard ();
Reset ();
O jogo não acabou quando é iniciado pela primeira vez, então definimos
gameOver como false aqui.
Uma das formas de o jogo terminar é quando o avatar atinge o solo ou quando
colide com o solo. Portanto, na função sendCollision () na parte inferior do nosso
código, adicione uma segunda verificação de colisão, conforme mostrado:
if (object.isFruit) {
scoreboard.addPoints (10);
avatar.setLinearVelocity (new THREE.Vector3 (0, 250, 0));
scene.remove (object);
}
» if (object == ground) {
» gameOver = true;
» scoreboard.message (
» "Purple Fruit Monster crashed! " +
» "Press R to try again."
» );
» }
}
Se o objeto com o qual o avatar está colidindo for o solo, o jogo acabou. Também
atualizamos o placar com uma mensagem útil. E observe que voltaremos
imediatamente da função se o jogo terminar. Não há razão para verificar se há
colisões quando o jogo terminar!
Neste ponto, temos uma maneira de dizer que o jogo acabou, mas nada para. A
animação ainda acontece e a fruta ainda rola. Precisamos ensinar nosso código o
que significa quando o jogo acaba.
Faça backup na função animate (), adicione uma checagem que retorna
imediatamente - antes que qualquer coisa seja animada - se o jogo acabou.
var clock = new THREE.Clock ();
function animate () {
» if (gameOver) return;
requestAnimationFrame (animate);
var t = clock.getElapsedTime ();
function launchFruit () {
» if (gameOver) return;
scoreboard.score (0);
scoreboard.message ('');
»
var last = scene.children.length - 1;
» for (var i=last; i>=0; i--) {
var obj = scene.children [i];
»
0: Fruit #1
1: Fruit #2
2: Ground
0: Fruit #2
1: Ground
2: Ground
1: Fruit #2
0: Fruit #1
Quando i vale 2, não fazemos nada porque o solo não é um pedaço de fruta.
Quando i é 1, removemos a Fruta # 2, deixando a lista assim:
1: Ground
0: Fruit #1
0: Ground
Terminar apenas com o solo e sem frutos é o que queremos. A moral dessa história
é que precisamos ter cuidado ao remover coisas das listas de JavaScript.
Felizmente, a outra coisa que fazemos dentro da função reset () é mais fácil.
Definimos gameOver de volta para false e reiniciamos a função animate (). Com
isso, nosso jogo está pronto!
Melhorias
Parabéns! Você acabou de escrever outro jogo do zero. E é divertido e desafiador.
Você ainda pode usar outras maneiras de melhorar as coisas. Algumas sugestões:
- Adicione coisas que o Monstro da Fruta Roxa não goste, para fazer a
pontuação diminuir quando ela as comer. Dica: uma imagem
imagesrotten_banana.png também está disponível!
- Pare o jogo se muitas frutas passarem pelo Monstro de Frutas Roxas.
Dica: crie uma função checkMissedFruit () que é chamada no início de
launchFruit (). A função ‘checkedFruit ()’ deve percorrer todas as frutas
na cena, contando o número cuja posição X é muito baixa. Se essa
contagem for muito alta, o jogo acaba!
Este é o seu código, portanto, torne o jogo melhor como só você pode!
Vamos deixar este jogo bonito, então usaremos as habilidades do Capítulo 12:
Trabalhando com Luzes e Materiais. Precisaremos da física para fazer a bola cair,
para fazê-la deslizar para frente e para trás no tabuleiro de jogo e para detectar
quando ela atinge o gol, então usaremos algumas das habilidades do Capítulo 14:
Projeto: The Purple Fruit Monster Game. E estaremos adicionando muitas formas
e movendo-as, então precisaremos das habilidades da primeira metade do livro
também. Uma palavra para o sábio: um monte de coisas, está acontecendo neste
jogo, o que significa que vamos digitar muito código. Para economizar tempo, não
falaremos muito sobre as ideias e abordagens que apresentamos nos capítulos
anteriores. Se você ainda não trabalhou nesses capítulos anteriores, programar este
jogo pode ser frustrante!
Primeiros passos
Inicie um novo projeto no 3DE. Escolha o 3D starter project (with Physics) e nomeie
esse projeto como Tilt-a-Board.
<body></body>
<script src="/three.js"></script>
①
<script src="/physi.js"></script>
<script>
// Physics settings
② Physijs.scripts.ammo = '/ammo.js';
③ Physijs.scripts.worker = '/physijs_worker.js';
// The "scene" is where stuff in our game will happen:
④ var scene = new Physijs.Scene();
⑤ scene.setGravity(new THREE.Vector3( 0, -100, 0 ));
var flat = {flatShading: true};
var light = new THREE.AmbientLight('white', 0.8);
scene.add(light);
Para obter a melhor visualização deste jogo, queremos a câmera um pouco acima
e atrás do tabuleiro do jogo, mas ainda olhando para o centro da cena. Também
precisamos habilitar sombras no renderizador, como fizemos no Capítulo 12:
Trabalhando com Luzes e Materiais. Adicione o seguinte código de configuração
acima da linha START CODING:
Isso é tudo para a configuração. Vamos começar com o código que vem depois de
COMEÇAR A CODIFICAÇÃO NA PRÓXIMA LINHA.
Descreva o Jogo
Além das luzes, este jogo terá uma bola, um tabuleiro e um objetivo (goal). Vamos
começar com o seguinte esboço de código, incluindo as barras duplas:
Adicionar Luzes
Antes de qualquer coisa, vamos adicionar algumas luzes à cena. Abaixo do esboço
do código comentado, adicione a seguinte definição de função de addLights:
function addLights () {
var lights = new THREE.Object3D ();
var light1 = new THREE.PointLight ('white', 0.4);
light1.position.set (50, 50, -100);
light1.castShadow = true;
lights.add (light1);
var light2 = new THREE.PointLight ('white', 0.5);
light2.position.set (-50, 50, 175);
light2.castShadow = true;
lights.add (light2);
scene.add (lights);
return lights;
}
function addBall () {
var shape = new THREE.SphereGeometry (10, 25, 21);
var cover = new THREE.MeshPhongMaterial ({color: 'red'});
cover.specular.setRGB (0.6, 0.6, 0.6);
var ball = new Physijs.SphereMesh (shape, cover);
ball.castShadow = true;
scene.add (ball);
return ball;
}
Esta função adiciona uma bola vermelha habilitada para física à cena. Damos a ele
um pouco de brilho especular e o habilitamos a projetar sombras.
Depois de terminar a função, descomente a chamada para addBall () no esboço do
código.
var lights = addLights ();
» var ball = addBall ();
//var board = addBoard ();
//var goal = addGoa l();
Como a bola é habilitada para a física, ela sai de cena para nunca mais ser vista
como mostrado na figura.
Vamos consertar isso, começando com um tabuleiro de jogo para pegar a bola.
Adicionar o Tabuleiro de Jogo
Adicione a função addBoard após a função addBall (). (Aviso: este requer muita
digitação.)
function addBoard() {
var cover = new THREE.MeshPhongMaterial({color: 'gold'});
cover.specular.setRGB(0.9, 0.9, 0.9);
var shape = new THREE.CubeGeometry(50, 2, 200);
var beam1 = new Physijs.BoxMesh(shape, cover, 0);
beam1.position.set(-37, 0, 0);
beam1.receiveShadow = true;
var beam2 = new Physijs.BoxMesh(shape, cover, 0);
beam2.position.set(75, 0, 0);
beam2.receiveShadow = true;
beam1.add(beam2);
shape = new THREE.CubeGeometry(200, 2, 50);
var beam3 = new Physijs.BoxMesh(shape, cover, 0);
beam3.position.set(40, 0, -40);
beam3.receiveShadow = true;
beam1.add(beam3);
var beam4 = new Physijs.BoxMesh(shape, cover, 0);
beam4.position.set(40, 0, 40);
beam4.receiveShadow = true;
beam1.add(beam4);
beam1.rotation.set(0.1, 0, 0);
scene.add(beam1);
return beam1;
}
Criamos quatro feixes e combinamos todos juntos para fazer o tabuleiro do jogo.
No final, inclinamos um pouco o tabuleiro (para fazer a bola rolar) e o adicionamos à
cena. Observe que marcamos cada um dos feixes para torná-los capazes de ter
sombras.
Uma coisa nova é o 0 em cada um dos BoxMeshes.
Isso não está certo, já que o objetivo do jogo é fazer a bola cair no meio do
tabuleiro. Portanto, antes de adicionarmos o gol, vamos primeiro redefinir a
localização da bola.
Reinicie o Jogo
Quando o jogo começa - ou reinicia - o tabuleiro deve ter uma ligeira inclinação e a
bola deve cair na borda do feixe mais à esquerda. Adicione a seguinte função reset
() abaixo da função addBoard ():
function reset() {
ball.__dirtyPosition = true;
ball.__dirtyRotation = true;
ball.position.set (-33, 200, -65);
ball.setLinearVelocity (new THREE.Vector3 (0, 0, 0));
ball.setAngularVelocity (new THREE.Vector3 (0, 0, 0));
board.__dirtyRotation = true;
board.rotation.set (0.1, 0, 0);
}
» reset();
Nosso esboço de código agora adiciona luzes, a bola do jogo e o tabuleiro do jogo.
Em seguida, ele redefine tudo para a posição inicial. Com isso, a bola deve começar
na parte de trás do tabuleiro em vez de cair pelo buraco do meio.
Adicione uma chamada para reset () na função gameStep ().
function gameStep () {
» if (ball.position.y < -500) reset ();
Essa instrução if verifica se a bola caiu muito longe. Se a posição Y da bola estiver
abaixo de -500, então restauramos a bola de volta à posição inicial para que o
jogador possa tentar novamente, conforme mostrado na figura.
Adicionar Controles do Jogo
Adicionaremos os controles do jogo na parte inferior do nosso código. Adicione o
seguinte listener “keydown” e a função sendKeyDown () abaixo da função
gameStep () e a chamada para gameStep ():
As funções esquerda, direita, para cima e para baixo são muito fáceis de entender.
Eles são tão curtos que podemos colocar toda a definição da função em uma linha!
O que estamos fazendo na função de inclinação chamada por cada um deles é um
pouco mais complicado.
Já conhecemos __dirtyRotation da função reset (). Temos que defini-lo aqui, caso
contrário, o tabuleiro não se move. Lembra quando adicionamos 0 ao BoxMesh em
addBoard ()? Esse 0 diz que o tabuleiro não se move ou gira ... a menos que
definamos uma propriedade incorreta.
Sem um truque como esse, provavelmente teríamos que usar quatro instruções if
diferentes. Então, nós, programadores preguiçosos, gostamos desse truque!
Experimente o tabuleiro de jogo! Você deve ser capaz de inclinar para a esquerda,
direita, para cima e para baixo usando as teclas de seta.
Você pode até conseguir passar a bola pelo gol central, mas como ainda não há
nada lá, nada acontece. Vamos adicionar uma meta a seguir.
Adicionar a Meta
Para manter nosso código organizado, continuamos a definir funções na mesma
ordem em que são chamadas. Portanto, coloque a função addGoal () abaixo de
addBoard (), mas acima de reset ().
function addGoal () {
shape = new THREE.CubeGeometry (100, 2, 100);
cover = new THREE.MeshNormalMaterial ({wireframe: true});
var goal = new Physijs.BoxMesh (shape, cover, 0);
goal.position.y = -50;
scene.add (goal);
return goal;
}
Esta é apenas uma pequena caixa plana que colocamos abaixo do tabuleiro do
jogo. Isso não fará nada até que adicionemos um atendente de evento de colisão.
Wireframing
Você deve ter notado que definimos wireframe como true quando criamos a meta.
Um wireframe nos permite ver a geometria sem um material para envolvê-la. É uma
ferramenta útil para explorar formas e desenhar planos como fizemos aqui.
Normalmente você deve remover a propriedade wireframe no código do jogo
finalizado (você também pode remover as chaves - [] - que o envolvem). Neste jogo,
provavelmente faz mais sentido mudar o wireframe: true para visible: false, de
modo que o objetivo seja invisível para o jogador.
reset ();
» addGoalLight ();
Com isso, você deve ver a luz do gol branca quase transparente.
Normalmente, esta primeira luz de objetivo é a que será vista. Quando o jogador
vencer, vamos alternar. Primeiro, o vermelho ficará visível e o branco ficará invisível.
Então, meio segundo depois, o branco estará visível e o vermelho invisível. Para que
isso aconteça, adicione a função win () abaixo de addGoalLight ():
function win(flashCount) {
if (!flashCount) flashCount = 0;
goalLight1.material.visible = !goalLight1.material.visible;
goalLight2.material.visible = !goalLight1.material.visible;
flashCount++;
if (flashCount > 10) {
reset ();
return;
}
Esta é uma pequena função divertida! A primeira linha verifica se win () foi
chamado com um argumento. Se esse flashCount não estiver definido, então o
definimos como 0.
A próxima linha altera a propriedade visible no material da primeira luz de objetivo.
Para fazer isso, usamos o operador booleano “not” de Booleanos. Se a propriedade
visible for verdadeira, então visible é atualizado para "not" true - ou false. Se a
propriedade visible for false, visible é atualizado para true.
A próxima linha pega qualquer novo valor existente para a propriedade visible da
luz da meta 1 e define a propriedade visible da luz da meta 2 para o oposto.
Portanto, se a luz da meta 1 estiver visível, a luz da meta 2 ficará invisível. Se a luz
da meta 1 não estiver visível, a luz da meta 2 estará visível. Legal certo? Mas
espere, a diversão não para por aí!
Em seguida, aumentamos a contagem de flashes - o número de vezes que a luz
piscou - em 1 (um). Se o número de flashes for maior do que 10, reinicializamos o
jogo e retornamos da função win ().
Se o número de flashes ainda for menor que 10, ignoramos esta instrução if e
passamos para setTimeout (). Dizemos a setTimeout () para chamar essa mesma
função win () após uma espera de meio segundo (500 milissegundos). Também
fazemos algo um pouco diferente. Passamos o valor de flashCount, que acabamos
de aumentar em 1 algumas linhas antes, para win () depois de esperar meio
segundo.
Em outras palavras, escrevemos win () para que ele continue chamando a si
mesmo. Cada vez que win () é chamado, ele simula uma luz piscando, alternando
qual luz de objetivo é visível. Em seguida, ele espera meio segundo para fazer tudo
de novo, com um flashCount de mais um do que no início. Isso é difícil de entender,
mas é mais uma demonstração do poder das funções.
Temos mais uma coisa a acrescentar antes de terminarmos com a meta. Após a
função win (), adicione um atendente de evento a goal.
É isso Aí!
Você deve ter um jogo de tilt-a-game totalmente funcional neste ponto. Use as
setas do teclado para inclinar o tabuleiro e pontuar.
Bônus # 1: Adicionar um Plano de Fundo
Podemos dar a este minijogo uma sensação de era espacial com o plano de fundo
estrelado do Capítulo 13 - Projeto: Fases da Lua. Abaixo da definição da função
win e do atendente de evento de colisão, adicione o seguinte código:
function addBackground () {
var cover = new THREE.PointsMaterial ({color: 'white', size: 2});
var shape = new THREE.Geometry ();
shape.vertices.push (point);
}
var stars = new THREE.Points (shape, cover);
scene.add (stars);
}
reset ();
addGoalLight ();
» addBackground ();
Construir estrelas a partir de uma malha de pontos como essa é muito legal. Este é
apenas o começo do que é possível com pontos. Vamos ver algo incrível quando
nós ...
Uma das muitas coisas em que as GPUs são boas são as “partículas de
sombreador” (“shader particles”) - pontos especiais que podem se misturar em
uma cena. Pontos regulares, como aqueles que usamos no campo estelar, não
funcionam para partículas de sombreamento. Precisamos de um novo tipo de ponto.
Mas primeiro, temos que carregar uma nova coleção de código.
<body></body>
<script src="/three.js"></script>
<script src="/physi.js"></script>
» <script src="/spe.js"></script>
scene.add (fire.mesh);
goalFire.velocity.value = new THREE.Vector3 (0, 75, 0);
goalFire.velocity.spread = new THREE.Vector3 (10, 7.5, 5);
goalFire.acceleration.value = new THREE.Vector3 (0, -15, 0);
goalFire.position.spread = new THREE.Vector3 (25, 0, 0);
goalFire.size.value = 25;
goalFire.size.spread = 10;
goalFire.color.value = [new THREE.Color ('white'), new THREE.Color ('red')];
goalFire.disable ();
}
A última propriedade diz para começar as partículas como brancas e terminar como
vermelhas.
As partículas de sombreamento (shader) precisam ser atualizadas regularmente. É
melhor fazer isso ao animar a cena, então adicione o código realçado abaixo à
função animate ().
Você também pode animar as luzes na cena conforme mostrado na última linha,
mas isso é com você!
Para se certificar de que tudo foi digitado corretamente, volte para a função
addGoalLight (). Altere a última linha dessa função para goalFire.enable (). Se tudo
estiver funcionando, você deverá ver um incêndio no meio do tabuleiro. Apenas
certifique-se de trocá-lo de volta - só queremos o tiro de meta ativado após uma
vitória.
Vamos Jogar!
Existem muitas propriedades das partículas. A melhor maneira de entendê-los é
dedicar algum tempo para brincar com eles. O que acontece se você alterar as
propriedades de propagação (spread) para 0 (zero)? O que acontece se você alterar
os valores de aceleração (acceleration)? O que acontece se você adicionar outra
cor à color? Você pode fazer um fogo melhor do que este?
» goalFire.enable ();
flashCount++;
if (flashCount > 10) {
reset ();
» goalFire.disable ();
return;
}
Isso remove o código que muda a visibilidade das duas luzes de gol falsas. Em vez
disso, ele ativa o emissor do goalFire quando é chamado pela primeira vez - quando
a bola colide pela primeira vez com o gol. Então, quando a função win () for
chamada o suficiente, desabilitamos novamente o fogo até a próxima vez que o
jogador vencer o jogo.
Isso é muito legal. E talvez o seu fogo seja ainda melhor. Lembre-se de que os
jogos de aparência bacana não substituem a diversão dos jogos. No momento, o
jogo é muito fácil de vencer. Você pode tornar isso mais difícil?
Desafio
Para tornar o jogo mais difícil, tente aumentar o comprimento da trave à esquerda.
Experimente adicionar outra viga nas costas que vai da esquerda para a direita.
Finalmente, faça a bola cair na borda dessa nova trave nas costas.
Você também pode tentar adicionar um placar como o do Capítulo 11 - Projeto:
Caça às Frutas, que reinicia o jogo se o jogador não conseguir terminar em um
determinado período de tempo.
Você consegue pensar em outras maneiras de melhorar este minijogo? Seja
criativo!
Capítulo 16
Introdução
Crie um novo projeto no Editor de código 3DE. Para isso, vamos usar o Empty
project (modelo de projeto vazio) - NÃO o modelo de animação usual! - e chamá-lo
de JavaScript Objects.
Não criaremos visualizações neste capítulo. Em vez disso, vamos criar objetos em
3DE e olhar para eles no console JavaScript. Portanto, certifique-se de ter o console
JavaScript aberto.
Objetos Simples
Qualquer coisa que possamos tocar ou falar no mundo real ou virtual pode ser
descrito na programação de computador: um avatar, um carro, uma árvore, um livro,
um filme. Qualquer coisa. Quando os programadores falam sobre coisas no mundo
dos computadores, chamamos essas coisas de objetos. Vamos pensar sobre filmes.
Acho que todos podemos concordar que Star Wars é o melhor filme de todos os
tempos, certo? Claro que nós podemos.
Bem, vamos descrever Star Wars como um objeto JavaScript. Adicione o seguinte
abaixo “Seu código vai aqui…”:
var bestMovie = {
title: 'Star Wars',
year: 1977,
};
Ei, espere. Não é apenas um mapa, que conhecemos no Capítulo 7: Uma Análise
mais Aprofundada dos Fundamentos do JavaScript? A resposta é sim! Mas pode
ser mais do que apenas um mapa.
Em JavaScript, outro nome para um mapa antigo simples é um objeto literal. Não
há diferença entre os dois, então na maioria das vezes apenas os chamamos de
mapas. Nós os chamamos de: literais de objeto quando nos sentimos
extravagantes. Ou quando estamos prestes a transformá-los em objetos "reais".
A diferença entre um objeto literal e um objeto real são os valores. No momento,
as chaves de título e ano apontam para valores simples - uma string e um
número. Mesmo se usarmos uma lista ou qualquer outra coisa sobre a qual falamos
no Capítulo 7: Uma Análise mais Detalhada dos Fundamentos do JavaScript,
ainda temos um objeto literal.
var bestMovie = {
title: 'Star Wars',
year: 1977,
stars: ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher'],
};
As coisas ficam reais quando apontamos as teclas para ... funções!
var bestMovie = {
title: 'Star Wars',
year: 1977,
stars: ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher'],
logMe: function () {
console.log (this.title + ', starring: ' + this.stars);
},
};
Isso é selvagem. A tecla logMe está apontando para uma função - sem um nome.
E dentro da função há duas referências a isso. O que está acontecendo aqui?
bestMovie.logMe();
No console JavaScript, você deve ver a mensagem: “Star Wars, estrelando: Mark
Hamill, Harrison Ford, Carrie Fisher.” Isso é legal, certo?
Para que isso funcione, o JavaScript faz duas coisas com a nossa função de
objeto:
1. Permite-nos chamar a tecla como se fosse uma função. A chave é logMe, mas
podemos chamá-la de logMe ().
2. Ele faz um trabalho muito especial com essa variável dentro da função, porque
não a criamos.
A variável this acabou sendo uma das coisas mais poderosas em JavaScript. Ele
tem vários usos, mas vamos usá-lo para nos referir ao objeto atual. Sempre que
criamos um objeto, JavaScript automaticamente define this para significar o objeto
atual, e é por isso que this.title e this.stars funcionam em logMe ().
NOTA - this só Funciona Dentro de Funções.
Essa variável é especial. JavaScript apenas nos permite usá-lo dentro de uma
função. Se você tentar usá-lo em outro lugar, receberá um erro.
Além de acessar valores simples com this, podemos até acessar outras funções.
Vamos mudar logMe () para que não esteja construindo uma string. Em vez disso,
ele obterá essa string de outra função, about ().
var bestMovie = {
title: 'Star Wars',
year: 1977,
stars: ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher'],
logMe: function () {
»
var me = this.about ();
console.log (me);
»
},
»
» about: function () {
»
return this.title + ', starring: ' + this.stars;
» },
};
bestMovie.logMe();
Com isso, a função about () está construindo uma string a partir das chaves em
bestMovie e a função logMe () está chamando about () para obter essa string. O
acesso a chaves e métodos funciona por meio de this.
Propriedades e Métodos
Uma das coisas irritantes de ser um programador é manter os nomes das coisas
em ordem: variáveis, strings, chaves, listas, funções. Pode ser opressor -
especialmente quando você aprende que os programadores têm dois nomes para a
mesma coisa, como mapas e literais de objeto.
Bem, vamos fazer de novo!
Ao falar sobre objetos, não nos referimos a chaves (keys) e funções. Em vez
disso, os chamamos de propriedades e métodos.
Pode parecer sem importância quais nomes usamos para isso, mas é importante
por três razões:
Copiando Objetos
Na vida real, você copia uma ideia legal ou um dispositivo interessante duplicando
tudo o que ele faz e alterando algumas coisas aqui e ali para torná-lo ainda melhor.
O que você está copiando se torna o protótipo da nova maneira de fazer isso.
JavaScript lida com a cópia de objetos de maneira semelhante.
Para descrever outro filme, podemos copiar o prototípico objeto bestMovie com
Object.create ().
var greatMovie = Object.create (bestMovie);
greatMovie.logMe ();
// => Star Wars, starring: Mark Hamill,Harrison Ford,Carrie Fisher
Isso altera as propriedades de title (título), year (ano) e stars (estrelas) do novo
objeto greatMovie. O que torna a programação com objetos tão legal é o que
acontece quando chamamos o método logMe () no greatMovie.
greatMovie.logMe ();
// => Toy Story, starring: Tom Hanks,Tim Allen
bestMovie.logMe ();
// => Star Wars, starring: Mark Hamill,Harrison Ford,Carrie Fisher
Graças a isso, o método about () sempre usa o título e a lista de estrelas do objeto
atual.
Observe que atualizar as propriedades no novo objeto greatMovie não afeta o
objeto bestMovie. bestMovie tem todas as suas propriedades inalteradas e seu
método logMe ainda exibe os resultados originais.
Vamos Jogar!
Brinque com seus próprios objetos. Crie seu próprio favoriteMovie (filme favorito).
Você pode criar um método logFullTitle () que exibe o title (título), seguido pelo
year (ano) entre parênteses? Deve ser semelhante a “Star Wars (1977)” no console
JavaScript. Certifique-se de que funciona para o objeto de protótipo e também para o
seu favoriteMovie (filme favorito).
Esta é apenas uma função normal que usa: a palavra-chave função, um nome
(Movie) e uma lista de argumentos (como o título do filme e a lista de estrelas no
filme).
No entanto, fazemos algo diferente dentro desta definição de função do que em
funções típicas. Em vez de realizar cálculos ou alterar valores, atribuímos as
propriedades do objeto atual. Nesse caso, atribuímos, ao título do objeto atual:
this.title, aos nomes dos atores e atrizes que estrelaram no filme: this.stars e, ao
ano em que o filme foi feito: this.year.
Além de atribuir os valores this, nada é realmente especial sobre esta função.
Então, como ele cria objetos? O que o torna um criador de objeto e não uma função
regular? A resposta é o que vimos no primeiro capítulo deste livro: a nova palavra-
chave. Não chamamos Movie da maneira que chamaríamos uma função normal. É
um construtor de objeto (sim, os programadores realmente amam nomes
extravagantes). Portanto, construímos novos objetos com ele, colocando new antes
do nome do construtor.
var kungFuMovie = new Movie ('Kung Fu Panda', ['Jack Black', 'Angelina Jolie']);
O Movie em new Movie é a função construtor que definimos. São necessários dois
argumentos: o título (Kung Fu Panda) e uma lista de estrelas (Jack Black e
Angelina Jolie). Então, graças às atribuições de propriedade que fizemos na função
construtora, podemos acessar essas propriedades da mesma forma como fizemos
com nossos objetos anteriores.
console.log (kungFuMovie.title);
// => Kung Fu Panda
console.log (kungFuMovie.stars);
// => ['Jack Black', 'Angelina Jolie']
console.log (kungFuMovie.year);
// => 2018
Você pode notar que o ano do filme Kung Fu Panda está errado - ele foi lançado
em 2008, não em 2018. Isso ocorre porque nosso construtor só sabe definir a
propriedade ano como o ano atual.
Vamos Jogar!
Se você estiver pronto para um desafio, mude o construtor para que ele receba um
terceiro argumento: year (ano). Se o ano for dado como o terceiro argumento, use-o
para this.year - caso contrário, atribua this.year ao ano atual como estamos
fazendo acima.
Movie.prototype.logMe = function () {
console.log (this.title + ', starring: ' + this.stars);
};
kungFuMovie.logMe ();
// => Kung Fu Panda, starring: Jack Black,Angelina Jolie
NOTA IMPORTANTE!
Os objetos JavaScript podem ter vários métodos, como logMe, mas é bom manter
o número deles pequeno. Se você estiver escrevendo com 12 ou mais métodos,
pode ser hora de um segundo objeto com um novo construtor.
O que acontecerá se tentarmos fazer isso com nosso método logMe ()?
Por mais louco que possa parecer, JavaScript esqueceu o que é this! Quando
setTimeout () chama logMe (), o JavaScript a trata como uma simples função, em
vez de um método que conhece kungFuMovie ou bestMovie. Quando ele tenta
chamar o método logMe () para kungFuMovie, JavaScript sabe que o método é
aquele que definimos no protótipo de Movie.
Movie.prototype.logMe = function () {
console.log (this.title + ', starring: ' + this.stars);
};
Desafio
Este foi um capítulo difícil. Se você gostaria de ter certeza de que entendeu como
adicionar métodos aos protótipos, experimente.
Você pode definir uma função about () no Movie.prototype que retorna as
mesmas informações que about () do bestMovie? Em seguida, faça com que
logMe () registre os resultados de this.about ().
Se você conseguir fazer isso funcionar, está no bom caminho para trabalhar com
objetos!
Capítulo 17
Neste capítulo, construímos um jogo que nos permite exercitar quase todas as
nossas novas habilidades. Usaremos colisões, física, iluminação, materiais e até
objetos JavaScript.
Vamos usá-los para criar um minijogo bacana. Os melhores jogos combinam sorte
e habilidade para desafiar os jogadores. “Ready, Steady, Launch” é exatamente
esse tipo de jogo. Quando terminarmos, deve ser parecido com isto:
1. O lançador
2. Cestas
3. Vento
E todos os três saberão como se desenhar na cena. Então vamos fazer isso!
Introdução
Começamos criando um novo projeto no Editor de código 3DE. Vamos usar o
modelo 3D starter project (with Phisics) (Projeto 3D Inicial - com Física), você
precisa alterar o modelo desta vez e chamá-lo de Ready, Steady, Launch.
Como você pode imaginar, este modelo inclui muito do trabalho do mecanismo de
física que adicionamos manualmente no Capítulo 14 - Projeto: O jogo do monstro
da fruta roxa.
Ainda precisamos fazer algumas alterações antes da linha START CODING.
Primeiro, queremos manter a pontuação neste minijogo, então precisamos incluir a
coleção de código scoreboard.js. Comece uma nova linha após a linha 3, antes da
tag <script> simples, e adicione a seguinte tag <script>:
<script src="/scoreboard.js"></script>
Em seguida, precisamos ajustar a câmera para que fique um pouco mais alta e
olhando para o centro da cena. Para fazer isso, adicione o código realçado abaixo,
antes de a câmera ser adicionada à cena.
camera.position.z = 500;
» camera.position.y = 200;
» camera.lookAt (new THREE.Vector3 (0,0,0));
scene.add (camera);
Como não adicionamos nada à cena ainda, isso não fará uma diferença
perceptível. Mas com certeza ajudará os jogadores a ver melhor o jogo.
Vamos começar adicionando o inicializador para o jogo.
O lançador
O lançador será uma seta que aponta na direção em que queremos lançar a bola.
Adicione o código de seta do iniciador logo abaixo da linha com START CODING
ON THE NEXT LINE.
Um Launcher terá duas propriedades: um ângulo que descreve para onde está
apontando e a quantidade de energia que usa para um lançamento. Quando
construímos o objeto lançador, ele atribui essas propriedades e se desenha na tela.
O método draw () deve adicionar a seta à tela. Portanto, vamos converter nosso
código de seta em um método draw () adicionando as linhas destacadas acima e
abaixo do código de seta.
» Launcher.prototype.draw = function () {
var direction = new THREE.Vector3 (0, 1, 0);
var position = new THREE.Vector3 (0, -100, 250);
var length = 100;
this.arrow = new THREE.ArrowHelper (
direction,
position,
length,
'yellow'
);
scene.add (this.arrow);
» };
É uma boa ideia recuar o código da seta conforme mostrado para facilitar a leitura.
A seta desaparece quando você adiciona isso. O código de seta no método draw ()
é chamado sempre que um novo objeto inicializador é criado, então vamos criar um.
Launcher.prototype.vector = function () {
return new THREE.Vector3 (
Math.sin (this.angle),
Math.cos (this.angle),
0
);
};
Launcher.prototype.moveLeft = function () {
this.angle = this.angle - Math.PI / 100;
this.arrow.setDirection (this.vector ());
};
Oculte o código e tente fazer isso. Você deve conseguir pressionar a tecla de seta
para a esquerda para ver a seta do iniciador girar para a esquerda. Se não, você
conhece o procedimento: verifique se a seta do lançador gira para a esquerda. Se
não, você sabe o que fazer: verifique o console JavaScript! Assim que a seta se
mover quando a seta para a esquerda for pressionada, mostre o código novamente.
Como já estamos olhando para o event listener (atendente de evento) keydown,
vamos prosseguir e adicionar uma instrução if para lidar com uma tecla de seta para
a direita.
Launcher.prototype.moveRight = function () {
this.angle = this.angle + Math.PI / 100;
this.arrow.setDirection (this.vector());
};
Launcher.prototype.powerUp = function () {
if (this.power >= 100) return;
this.power = this.power + 5;
this.arrow.setLength (this.power);
};
Launcher.prototype.launch = function () {
var shape = new THREE.SphereGeometry (10);
var material = new THREE.MeshPhongMaterial ({color: 'yellow'});
var ball = new Physijs.SphereMesh (shape, material, 1);
ball.name = 'Game Ball';
ball.position.set (0,0,300);
scene.add (ball);
this.power = 0;
this.arrow.setLength (100);
};
A primeira metade desse método cria uma bola de jogo com um peso de 1. Ela
adiciona a bola à cena perto da seta do lançador. Em seguida, ele calcula um vetor
de velocidade e o usa para definir a velocidade da bola - sua velocidade e direção.
Finalmente, ele redefine a potência e a seta para seus valores originais.
Para usar o launcher (iniciador), precisamos voltar ao código do atendente do
evento na parte inferior de tudo. Quando um jogador pressiona a tecla de seta para
baixo (e a mantém pressionada), queremos ligar
Em seguida, adicione um novo atendente de evento que ouvirá uma tecla subindo
(sendo liberada).
document.addEventListener ('keyup', sendKeyUp);
function sendKeyUp (event) {
var code = event.code;
if (code == 'ArrowDown') launcher.launch ();
}
Com isso, quando o jogador solta a seta para baixo, o lançador deve lançar uma
bola. Esconda o código e experimente!
Placar
Queremos manter o placar neste minijogo, então vamos adicionar um placar.
Abaixo da linha que cria o novo Launcher (), adicione o código usual do placar:
Você pode pular o texto de ajuda por enquanto se estiver ansioso para voltar à
codificação. Só não se esqueça de adicioná-lo mais tarde - não é divertido jogar um
jogo em que você não conhece as regras.
Cestas e metas
Podemos lançar bolas de jogo, mas ainda não há como marcar. Nesta seção,
construiremos um objeto Basket que será adicionado à pontuação quando atingido
por uma bola lançada. Começaremos com um pouco mais de código no Basket do
que no Launcher, mas tente manter as etapas menores.
Vamos adicionar o código do protótipo Basket abaixo do código do protótipo do
Launcher. Portanto, adicione alguns espaços abaixo da chave de fechamento de
Launcher.prototype.launch e, em seguida, adicione o construtor Basket.
var r = Math.random;
this.color = new THREE.Color (r (), r (), r ());
this.draw ();
}
Para múltiplos de 10, ele retorna o número de 0s (zeros) após o 1 (um). Para
outros números, o logaritmo de base 10 está em algum lugar entre 0 e 1.
É por isso que definimos a altura em 100 dividido pelo logaritmo da altura. Em vez
de alturas 10 e 1, obtemos:
Existem outros logaritmos além desta versão de base 10. Existem outros usos para
logaritmos além de diminuir mudanças. Portanto, preste atenção quando eles vierem
na aula de matemática! Depois que o construtor estiver pronto, adicione o método
draw () logo abaixo do construtor Basket.
Basket.prototype.draw = function () {
var cover = new THREE.MeshPhongMaterial ({
color: this.color,
shininess: 50,
specular: 'white'
});
Se tudo estiver digitado corretamente, você deverá ver uma plataforma quadrada
no centro da cena. É um quadrado de tamanho 200 que vale 10 pontos. Você pode
até tentar lançar algumas bolas nele - não adicionamos pontuação ainda, mas você
ainda deve ser capaz de acertar o alvo. E se não funcionar ... verifique o console
JavaScript.
Antes de terminar as cestas, adicione um segundo goal à cena.
Basket.prototype.draw = function () {
var cover = new THREE.MeshPhongMaterial ({
color: this.color,
shininess: 50,
specular: 'white'
});
» var halfSize =
» var halfHeight =
»
» shape = new THREE.CubeGeometry (this.size, this.height, 1);
» var side1 = new Physijs.BoxMesh (shape, cover, 0);
»
» side1.position.set (0, halfHeight, halfSize);
» scene.add (side1);
»
» var side2 = new Physijs.BoxMesh (shape, cover, 0);
» side2.position.set (0, halfHeight, -halfSize);
» scene.add (side2);
»
» shape = new THREE.CubeGeometry (1, this.height, this.size);
» var side3 = new Physijs.BoxMesh (shape, cover, 0);
» side3.position.set (halfSize, halfHeight, 0);
» scene.add (side3);
»
» var side4 = new Physijs.BoxMesh (shape, cover, 0);
» side4.position.set (-halfSize, halfHeight, 0);
» scene.add (side4);
»
» this.waitForScore (goal);
};
Não se esqueça da última linha! A última linha nova dentro do método draw ()
quebrará nosso código, mas o adicionará de qualquer maneira. Depois que todos os
lados são adicionados, chamamos o método waitForScore (). Vamos adicionar esse
método a seguir para que possamos finalmente marcar pontos!
Isso então chamará o método score () (que adicionaremos a seguir) quando uma
bola colidir com o gol. Se você for perspicaz, notará que tivemos que vincular isso ao
método score (). Conforme descrito no Capítulo 16: Aprendendo Sobre Objetos
JavaScript, este é um caso de JavaScript dificultando a vida de nós,
programadores. Sem bind(this), as colisões chamariam o método score (), mas
esqueceriam isso. E sem isso, o método score () não tem como saber como meus
pontos foram marcados!
Sim. JavaScript é realmente uma loucura. A última coisa a fazer aqui é definir esse
método score ().
Basket.prototype.score = function (ball) {
if (scoreboard.getTimeRemaining () == 0) return;
scoreboard.addPoints (this.points);
scene.remove (ball);
};
Se não sobrar tempo, então voltamos do método sem fazer mais nada. Caso
contrário, adicionamos todos os pontos que esse gol valha ao placar. Por último,
removemos a bola da cena para que um salto não conte duas vezes.
Ufa! É uma boa quantidade de código, mas se você acertou tudo, deve ser capaz
de jogar um jogo bastante desafiador.
Vento!
Agora que estamos nos familiarizando com esses objetos JavaScript, vamos tentar
digitar o objeto Wind de uma vez. Comece com o construtor, que pode ir abaixo de
todos os métodos de Basket - após o método score (). O construtor chamará
métodos para desenhar o vento na cena e começar a alterá-lo.
function Wind () {
this.draw ();
this.change ();
}
Como fizemos com os outros objetos, os métodos para Wind podem seguir o
construtor. Primeiro, adicione o método draw (), que usará o auxiliar de seta
novamente.
Wind.prototype.draw = function () {
var dir = new THREE.Vector3 (1, 0, 0);
var start = new THREE.Vector3 (0, 200, 250);
this.arrow = new THREE.ArrowHelper (dir, start, 1, 'lightblue');
scene.add (this.arrow);
};
Desta vez, ele posiciona a seta 200 acima e 250 na frente do centro. Também torna
a seta azul claro para ser mais parecida com o vento.
O método change () usará Math.random () para mudar a direção (-1 para a
esquerda, 1 para a direita) e a força do vento.
Wind.prototype.change = function () {
if (Math.random () < 0.5) this.direction = -1;
else this.direction = 1;
this.strength = 20*Math.random ();
this.arrow.setLength (5 * this.strength);
this.arrow.setDirection (this.vector());
setTimeout (this.change.bind (this), 10000);
};
Wind.prototype.vector = function () {
var x = this.direction * this.strength;
return new THREE.Vector3 (x, 0, 0);
};
Com isso, estamos prontos para adicionar vento ao nosso jogo. Crie o vento abaixo
do placar e os dois gols que adicionamos nas seções anteriores.
Estamos quase terminando. A seta do vento agora deve aparecer em nosso jogo e
deve mudar a cada 10 segundos. Ainda precisamos que o vento afete as bolas que
foram lançadas.
Para isso, primeiro precisamos de uma função que retorne uma lista de todas as
bolas do jogo. Adicione allBalls () abaixo do new wind que acabamos de adicionar.
function allBalls () {
var balls = [];
for (var i=0; i<scene.children.length; i++) {
if (scene.children[i].name.startsWith ('Game Ball')) {
balls.push (scene.children[i]);
}
}
return balls;
}
Isso percorre tudo na cena, procurando apenas as bolas do jogo. Chamamos as
bolas em Launcher.prototype.launch () de “Bola de jogo”. Portanto, o loop verifica
cada objeto para ver se seu nome começa com “Game Ball”. Nesse caso, ele
empurra o objeto para a lista de bolas. No final de allBalls (), essa lista é retornada.
Por fim, dentro da função gameStep (), percorremos todas as bolas do jogo e
aplicamos uma força no centro de cada bola.
function gameStep () {
scene.simulate ();
Também adicionamos um check para remover qualquer bola que tenha caído bem
abaixo das cestas. Não temos mais razão para rastreá-los, então podemos também
tornar mais fácil no computador removendo-os de cena.
E é isso! Ufa! Isso deu muito trabalho, mas se tudo estiver codificado corretamente,
você deve ter um joguinho legal em mãos. Se houver algum problema, não se
esqueça de verificar o console JavaScript.
Este é um jogo legal e desafiador. Experimente para ver se consegue quebrar 500
pontos!
Vamos Brincar!
A diferença entre um bom jogo e um ótimo jogo está nos detalhes. Portanto,
brinque com o código para tornar isso ainda melhor. Adicione mais algumas luzes.
Adicione sombras. Deixe o jogador pressionar a tecla R para reiniciar o jogo depois
de terminado.
Neste capítulo, vamos transformar o jogo Ready, Steady, Launch do último capítulo
em um jogo para dois jogadores, que deve se parecer com a imagem acima. Este
não será um jogo online para vários jogadores. Ambos os jogadores usarão o
mesmo computador e teclado para jogar. Como veremos, fazer isso já é difícil o
suficiente. Mas, vamos construir um jogo divertido (você pode até tentar tirar as
bolas do outro jogador do céu!) e começar a ver como podemos convertê-lo em um
jogo online algum dia.
As grandes mudanças que precisamos fazer no jogo original Ready, Steady,
Launch são:
1. Precisamos adicionar dois iniciadores em vez de um.
2. Cada lançador precisará de seu próprio placar.
3. As cestas terão de adicionar pontos ao placar correto.
Enquanto fazemos isso, preste atenção especial em como os objetos tornam
tudo isso possível.
Começando
Começamos criando uma cópia de Ready, Steady, Launch, nomeando-a Ready,
Steady, Launch 2.
Este é um lembrete gentil de uma dica do Capítulo 4 - Projeto: Movendo
Avatares. Se você tiver um código funcionando, sempre certifique-se de ter uma
cópia em algum lugar. Você pode pensar que suas próximas mudanças são
pequenas e não podem quebrar as coisas. No entanto, é muito fácil quebrar coisas
mal durante a programação. Quando isso acontece, um backup é como ouro. Você
pode consultar seu backup ou excluir seu novo código e começar novamente. É por
isso que fazemos de novo aqui.
Vamos renomear launcher para launcher1 e dizer a este iniciador que ele ficará no
lado esquerdo da tela. Além disso, vamos adicionar um segundo iniciador, que ficará
no lado direito.
var launcher1 = new Launcher ('left');
var launcher2 = new Launcher ('right');
Essa mudança não parecerá mudar nada. Porém, quebra alguma coisa. Se você
ocultar o código e tentar movê-lo ou iniciá-lo com as setas do teclado, nada
acontecerá. E o console JavaScript terá erros como: “launcher is not defined.”
Isso ocorre porque o código do teclado, que deve estar na parte inferior do nosso
código, está tentando mover o launcher (inicializador) antigo.
Depois de fazer essas alterações, o jogo não deve ficar diferente. Mas se você
ocultar o código, os controles do teclado devem ser diferentes. As setas do teclado
não funcionam mais. E se você mover um lançador com as teclas A / D e o outro
com as teclas J / L, você verá que existem, de fato, dois lançadores que duas
pessoas podem controlar!
Certifique-se de experimentar e certifique-se de que não haja erros no console
JavaScript. É muito mais fácil corrigir um erro logo após sua introdução do que
quando você está tentando adicionar um código completamente diferente.
Mas nosso protótipo de lançador não tem ideia do que isso significa. Vamos
ensinar.
Começamos no construtor do Launcher, que deve estar próximo ao topo do
código. No momento, o construtor não faz nada com a localização left (esquerda) ou
right (direita) que estamos enviando.
function Launcher () {
this.angle = 0;
this.power = 0;
this.draw ();
}
Altere o método draw () conforme mostrado para mover a seta e alterar a cor:
Launcher.prototype.draw = function () {
var direction = new THREE.Vector3 (0, 1, 0);
» var x = 0;
» if (this.location == 'left') x = -100;
» if (this.location == 'right') x = 100;
» var position = new THREE.Vector3 (x, -100, 250 );
var length = 100;
this.arrow = new THREE.ArrowHelper (
direction,
position,
length,
» this.color
);
scene.add (this.arrow);
};
Mude o método launch (lançamento) para iniciar as bolas do novo local e para
mudar a cor das bolas
Launcher.prototype.launch = function () {
var shape = new THREE.SphereGeometry (10);
» var material = new THREE.MeshPhongMaterial ( {color: this.color});
var ball = new Physijs.SphereMesh (shape, material, 1);
ball.name = 'Game Ball';
» var p = this.arrow.position;
» ball.position.set (p.x, p.y, p.z);
scene.add (ball);
»
»
var speedVector = new THREE.Vector3(
2.5 * this.power * this.vector ().x,
2.5 * this.power * this.vector ().y,
-80
);
ball.setLinearVelocity (speedVector);
this.power = 0;
this.arrow.setLength (100);
};
Se não houver erros no código, devemos ter dois lançadores, em dois lugares
diferentes, controlados por dois conjuntos diferentes de chaves, com duas cores
diferentes!
Isso é bem legal. Passar de um jogador para dois não é uma coisa pequena, mas
graças ao objeto Launcher - e dando pequenos passos - estamos no bom caminho.
Mesmo tendo dois jogadores, ainda temos um placar. A seguir, vamos dar a cada
jogador seu próprio placar.
Dois Placares
Felizmente, o código do placar que usamos é um objeto, o que torna nosso
trabalho de adicionar dois placares mais fácil - uau! O código que adiciona o placar
deve estar abaixo de onde criamos launcher1 e launcher2. Deve ser parecido com
o seguinte:
Este código cria um placar para todo o jogo. Mas queremos um placar para cada
launcher (lançador). Para fazer isso, iremos recortar e colar cuidadosamente todo
esse código. Realce este código em 3DE, começando com var scoreboard até a
chave de fechamento } após "Game Over" e, em seguida, corte (Ctrl+X ou ⌘+X) de
sua localização atual. Em seguida, queremos colar (Ctrl+V ou ⌘+V) o código logo
abaixo do último método de protótipo do Launcher, que deve ser launch ().
Em seguida, torne este um método para o Launcher adicionando um método
keepScore e uma chave de fechamento }. Reserve algum tempo para indentar tudo
de forma clara, caso contrário, este código parecerá estranho ou errado quando
você vê-lo mais tarde. Este novo método keepScore deve ser parecido com o
seguinte:
» Launcher.prototype.keepScore = function () {
var scoreboard = new Scoreboard ();
scoreboard.countdown (60);
scoreboard.score (0);
scoreboard.help (
'Use right and left arrow keys to point the launcher. ' +
'Press and hold the down arrow key to power up the launcher. ' +
'Let go of the down arrow key to launch. ' +
'Watch out for the wind!!!'
);
scoreboard.onTimeExpired (timeExpired);
function timeExpired () {
scoreboard.message ("Game Over!");
}
» };
Finalmente, após o código que cria launcher1 e launcher2, vamos adicionar uma
nova variável scoreboard (placar) que vem da propriedade scoreboard de um dos
lançadores.
Launcher.prototype.keepScore = function () {
» var scoreboard = new Scoreboard ('top' + this.location);
scoreboard.countdown (60);
scoreboard.score (0);
scoreboard.help (
'Use right and left arrow keys to point the launcher. ' +
'Press and hold the down arrow key to power up the launcher. ' +
'Let go of the down arrow key to launch. ' +
'Watch out for the wind!!!'
);
scoreboard.onTimeExpired (timeExpired);
function timeExpired () {
scoreboard.message ("Game Over!");
}
this.scoreboard = scoreboard;
};
Com isso, dois placares separados devem estar lá, um para cada lançador.
Parece bom, mas ainda não terminamos. Temos dois placares, mas a pontuação é
enviada para apenas um deles. A seguir, atualizaremos o código da cesta para que
ele possa atualizar o placar para o inicializador adequado.
O método score () conhece a bola que caiu nele e o marcador que mantém o
placar. A bola é um argumento para o método. O placar é aquele que costumava ser
definido após o launcher (que se tornou launcher1 e launcher2).
Não podemos mais usar aquele placar. A cesta precisa saber qual placar recebe
pontos adicionados pela bola que acabou de cair. Mas como a cesta pode saber
qual placar usar se tudo que ela sabe é que a bola caiu?
A resposta a essa pergunta é que podemos anexar o placar à bola quando ela for
lançada. Fazemos isso com uma mudança no método launch () do protótipo do
Launcher.
Launcher.prototype.launch = function () {
var shape = new THREE.SphereGeometry (10);
var material = new THREE.MeshPhongMaterial ({color: this.color});
var ball = new Physijs.SphereMesh (shape, material, 1);
ball.name = 'Game Ball';
» ball.scoreboard = this.scoreboard;
var p = this.arrow.position;
ball.position.set (p.x, p.y, p.z);
scene.add (ball);
this.power = 0;
this.arrow.setLength (100);
};
Agora, quando uma bola entra em uma cesta, a cesta diz ao placar da bola para
somar pontos! O jogo está realmente se moldando agora, mas ainda existem dois
pequenos bugs - dois problemas - que devemos consertar antes de jogar com outra
pessoa.
Compartilhando um Teclado
O primeiro bug tem a ver com ligar o inicializador. Se o jogador nº 1 mantém
pressionada a tecla S, o lançador liga normalmente. Mas, se o jogador nº 2
pressionar a tecla K, a ligação do jogador nº 1 será interrompida.
Esse bug ocorre porque há apenas um teclado e os teclados esperam que apenas
uma pessoa pressione e segure uma tecla. Se você pressionar e segurar a tecla S
em um processador de texto, obterá vários Ss em seu documento - o teclado repete
a letra S. Se você mantiver a tecla S pressionada e, em seguida, pressionar a tecla
K ao mesmo tempo, o computador calcula que agora você deseja adicionar um
monte de Ks ao documento e passa a repetir a tecla K.
Para fazer os power-ups funcionarem quando duas pessoas estiverem
compartilhando o teclado, precisamos substituir as repetições do teclado por
repetições JavaScript. Aprendemos repetições de JavaScript no Capítulo 14 -
Projeto: O Jogo do Monstro da Fruta Roxa. As repetições de JavaScript são
chamadas de intervalos. Precisamos de dois intervalos, um para cada jogador.
Iniciaremos cada intervalo quando a tecla de inicialização for pressionada. Para
fazer isso, faça as alterações destacadas no listener keydown.
» var powerUp1;
» var powerUp2;
» function powerUpLauncher1 () { launcher1.powerUp (); }
» function powerUpLauncher2 () { launcher2.powerUp (); }
document.addEventListener ('keydown', sendKeyDown);
function sendKeyDown (event) {
» if (event.repeat) return;
var code = event.code;
if (code == 'KeyA') launcher1.moveLeft ();
if (code == 'KeyD') launcher1.moveRight ();
» if (code == 'KeyS') {
» clearInterval (powerUp1);
» powerUp1 = setInterval (powerUpLauncher1, 20);
» }
if (code == 'KeyJ') launcher2.moveLef t();
Isso corrige os power-ups para dois jogadores, mas ainda precisamos iniciar
depois de ligar. Para fazer isso, faça as alterações destacadas no atendente keyup.
document.addEventListener ('keyup', sendKeyUp);
function sendKeyUp(event) {
var code = event.code;
» if (code == 'KeyS') {
» launcher1.launch ();
» clearInterval (powerUp1);
» }
» if (code == 'KeyK') {
» launcher2.launch ();
» clearInterval (powerUp2);
» }
» }
function reset () {
if (scoreboard.getTimeRemaining() > 0) return;
» scoreboard.score (0);
» scoreboard.countdown (60);
animate ();
gameStep ();
}
» Launcher.prototype.reset = function () {
» var scoreboard = this.scoreboard;
» if (scoreboard.getTimeRemaining () > 0) return;
scoreboard.score (0);
scoreboard.countdown (60);
» };
Isso ensina cada lançador que é criado - como os dois para o jogador nº 1 e
jogador nº 2 - como zerar a si mesmos e a seus placares.
Tudo o que resta é voltar para a função reset (). Lá, chamamos esse método
reset() em ambos os iniciadores.
function reset () {
Animate ();
gameStep ();
}
Vamos Brincar!
A mensagem de ajuda para os placares ainda diz aos jogadores para usar as setas
do teclado. Você pode alterar as mensagens para que mostrem as novas chaves?
Você pode fazer com que os dois painéis de pontuação diferentes mostrem a
mensagem correta para seu inicializador?
O Código até Agora
Caso queira verificar o código neste capítulo, você pode encontrá-lo em Código:
Dois Jogadores Pronto, Constante, Inicializar. O código para as mensagens de
ajuda atualizadas está incluído se você precisar de algumas dicas.
Qual é o Próximo
Ufa! Isso deu um pouco de trabalho. Mas se você olhar para trás, não foram
necessárias tantas mudanças para chegar lá. Tivemos que fazer alguns truques com
objetos - especialmente para fazer com que as cestas avisassem o placar correto
para somar pontos. Principalmente, descobrimos que ter o iniciador e os placares
como objetos tornava a troca de um jogador para dois bastante fácil.
Por mais fácil que fosse, ainda exigia esforço. E as coisas só ficam mais difíceis
quando os jogos estão online, e é por isso que não os cobriremos neste livro. Mas
agora você já viu o suficiente de jogos multijogador para ter uma ideia de como eles
funcionam. Continue trabalhando na programação e você será capaz de criar
incríveis jogos multijogador antes mesmo de perceber!
Quando terminar este capítulo, você:
- Entenderá como deformar formas em algo completamente diferente.
- Será capaz de projetar um mundo - com terrenos tão acidentados ou lisos
quanto você quiser.
- Completará outro jogo 3D divertido
Capítulo 19
Introdução
Começamos criando um novo projeto no Editor de código 3DE. Usamos o modelo
do Projeto 3D Starter (com Físics) e o chamamos de River Rafter.
No topo do nosso código, vamos trazer algumas coleções de código que vão nos
ajudar.
<body></body>
<script src="/three.js"></script>
<script src="/physi.js"></script>
» <script src="controlsOrbitControls.js"></script>
» <script src="/scoreboard.js"></script>
» <script src="/noise.js"></script>
Queremos sombras neste jogo, então diminua a intensidade da luz ambiente para
0,2.
Logo abaixo dessas linhas, adicione a luz direcional do Capítulo 12: Trabalhando
com Luzes e Materiais.
Quando criamos a câmera, mudamos os dois últimos números para 0,1 e 100.
Queremos ver um pouco mais de perto neste jogo - tão perto quanto 0,1 de distância
das coisas. E não precisaremos ver muito longe - 100 deve ser mais do que
suficiente. Também movemos a câmera, 8 para a esquerda, 8 para cima e 8 para
trás.
Este é um jogo diurno ao ar livre, então vamos fazer um céu azul. Como fizemos no
Capítulo 14 - Projeto: O Jogo do Monstro da Fruta Roxa, mude a cor de toda a
cena definindo a cor “clara” para azul celeste. Enquanto estamos fazendo uma
mudança no renderizador, vamos também habilitar sombras nesta cena.
Removeremos os controles de órbita em breve, mas eles são úteis para obter uma
visão melhor conforme construímos o rio. Lembre-se de que esses controles nos
permitem clicar e arrastar a cena. Rolar com o mouse ou touchpad aumentará e
diminuirá o zoom. E as setas do teclado se movem para cima, para baixo, para a
esquerda e para a direita.
Contanto que a cena agora esteja azul e não haja erros no console JavaScript, isso
é tudo que precisamos fazer acima da linha START CODING. Vamos descer para o
resto do código e começar a fazer coisas!
function addGround () {
var faces = 99;
var shape = new THREE.PlaneGeometry (10, 20, faces, faces);
scene.add (mesh);
return mesh;
}
Depois que você tiver esse código digitado e o console JavaScript não mostrar
nenhum erro, devemos ver um quadrado verde e plano no meio da cena.
Function addGround () {
Var faces = 99;
Var shape = new THREE.PlaneGeometry (10, 20, faces, faces);
Scene.add (mesh);
Return mesh;
}
Você pode manter tudo isso em uma linha - no livro, são cinco linhas mais curtas
em vez de uma longa apenas para caber! Certifique-se de adicionar a vírgula após o
0 para shininess (brilho) e configuração de wireframe.
A estrutura de arame (wireframe) oculta o material, mas ainda mostra as faces
usadas para construir a forma. Deve ser parecido com isto:
Se você aumentar o zoom usando o touchpad ou a roda de rolagem do mouse,
verá que cada face - cada um dos milhares de retângulos - é dividido em triângulos.
Para este jogo, não importa que os computadores usem triângulos. Mas todos os
programadores 3D sabem que os computadores preferem triângulos. E, como você
é um programador 3D, precisava saber disso. Então, agora você sabe! Faces e
triângulos são importantes, mas seus cantos são ainda mais importantes. Nosso
terreno tem milhares de faces retangulares, todas próximas umas das outras. Esses
retângulos compartilham bordas (edges). Eles compartilham cantos. E aumentamos
quatro desses cantos compartilhados em 5 (lembre-se de que dois cantos estão
próximos um do outro, enquanto os outros dois estão nas linhas 10 e 50).
Esses cantos compartilhados são chamados de vértices na programação 3D.
Quando puxamos os vertices [50] para cima, as duas faces que compartilham
aquele canto vieram com ele. As faces são distorcidas para que permaneçam
conectadas ao resto do plane (plane).
Você pode remover a configuração de wireframe neste ponto. Vejamos o resto
deste código. Observe que criamos um material de física (physics) em duas etapas.
Primeiro, criamos um material Phong regular. Isso é grama, então damos a ela 0
brilho (shininess). Atribuímos isso a uma variável _cover temporária - o sublinhado
no início é nossa maneira de dizer a nós mesmos que essa variável será usada
apenas brevemente. Na verdade, ele é usado apenas na próxima linha, onde
criamos um material habilitado para a física.
Nem sempre temos que fazer dessa maneira - apenas usamos materiais Phong
normais em todos os nossos outros projetos habilitados para a física. Fazemos isso
aqui para que possamos definir esses dois números, que são a fricção (friction) e
elasticidade (bounciness) do material. Ambos os valores são um número entre 0,0 e
1,0. Definimos um atrito alto de 0,8, o que significa que seria difícil algo deslizar
neste material no solo. Definimos um low bounciness de 0.1, o que significa que as
coisas não vão bater nele.
A outra coisa nova na função addGround () é um tipo diferente de malha (mesh)
habilitada para física (physics), chamada heigh field mesh (malha de campo de
altura) ou HeighfieldMesh ().
Não puxamos cada vértice na direção Z pela mesma quantidade. Em vez disso,
puxamos cada um pelo ruído, que é calculado pelo código da coleção de códigos
noise.js. O resultado deve ser algo assim.
As bordas têm saliências, mas o meio é bem chato. Para torná-lo mais
interessante, adicione as duas linhas a seguir após o loop de noise (ruído) e acima
das linhas _cover e cover:
shape.computeFaceNormals ();
shape.computeVertexNormals ();
Acontece que precisamos chamar esses dois métodos sempre que empurramos
(push) ou puxamos (pull) vértices. O código 3D mantém o controle de muitas
informações para que possa renderizar cenas de forma rápida e realista. Não
consegue rastrear mudanças fora do comum, mas nos dá uma maneira de deixar o
código 3D saber quando fizemos algo como empurrar ou puxar vértices.
As normais (normals) que estão sendo recomputadas são a direção para a qual as
faces e seus cantos estão apontando. Os normais (normals) ajudam com
iluminação (lighting), sombreamento (shading) e sombras (shadows). Não
precisamos nos preocupar muito com o funcionamento dos normais, mas
precisamos informar ao renderizador que os alteramos, informando a forma para
computeFaceNormals e computeVertexNormals. Portanto, após qualquer
alteração nos vértices, temos que dizer ao código 3D para recomputar onde os
vértices e faces estão.
Podemos deformar formas e torná-las muito boas. Este é um jogo de river rafting.
Se puxarmos os pontos um de cada vez, vai demorar muito para fazer um rio neste
plano. Vejamos a seguir como podemos tornar um rio um pouco mais rápido.
Escavando um Rio
Quando puxamos (pulled) os vértices [50], puxamos o vértice no meio do topo do
plano. Quando adicionamos 100, 10 * 100 e 50 * 100 a 50, extraímos vértices em
uma linha reta.
Existem 99 retângulos na parte superior do plano. Isso significa que existem 100
vértices - os 99 cantos superiores esquerdo mais o canto superior direito na
extremidade. Portanto, toda vez que movemos 100, passamos para a próxima linha.
Podemos fazer uso disso em um loop for. Em vez de iniciar o loop em 0, podemos
começar em 50. E, em vez de aumentar em 1 a cada vez através do loop, podemos
aumentar em 100.
Ainda na função addGround (), adicione o seguinte código abaixo do loop de ruído
(noise) e acima dos métodos de computação:
Vamos Brincar!
Brinque com os números. Altere 20 para 50. Altere 7 para 5. Qual é a aparência de
20 * Math.PI? 2 * Math.PI? Esses números controlam o tamanho das curvas e
quantas curvas existem. O que parece melhor para o seu jogo? Acho que o 7 *
Math.PI parece melhor, mas você decide!
OK, temos um rio ventoso, mas certamente não é grande o suficiente para uma
jangada descer. Vamos torná-lo mais amplo. Dentro do loop, para cada linha,
queremos puxar para baixo 10 vértices em cada lado do centro. Então, dentro do
loop, adicionamos outro loop que começa em -20 e termina em 20.
function addGround () {
var faces = 99;
var shape = new THREE.PlaneGeometry (10, 20, faces, faces);
scene.add (mesh);
return mesh;
}
Não há nada de novo aí. Criamos uma cobertura física para a água assim como
fizemos com o solo, dando-lhe atrito 0 (muito escorregadio) e elasticidade 0,6.
Com isso, temos água no nosso rio!
Isso foi muito trabalho. E foi muito diferente do que vimos. O resto do jogo deve ser
um pouco mais familiar. Em seguida, adicionamos uma maneira de manter a
pontuação
Placar de Pontuação
Queremos manter a pontuação neste jogo, então, no esboço do código no topo do
nosso código, adicione uma chamada à função addScoreboard ().
function addScoreboard () {
var scoreboard = new Scoreboard ();
scoreboard.score (0);
scoreboard.timer ();
scoreboard.help (
'left / right arrow keys to turn. ' +
'space bar to move forward. ' +
'R to restart.'
);
return scoreboard;
}
Isso cria o mesmo tipo de placar que usamos ao longo do livro. Ele marca pontos,
conta o tempo e dá uma ajudinha. Agora que temos um rio e um placar, vamos
adicionar a jangada ao jogo.
Novamente, isso quebra as coisas porque ainda não há função addRiver (). Vamos
adicioná-lo após a última chave na função addScoreboard ().
function addRaft() {
var shape = new THREE.TorusGeometry (0.1, 0.05, 8, 20);
var _cover = new THREE.MeshPhongMaterial ({visible: false});
var cover = Physijs.createMaterial (_cover, 0.4, 0.6);
var mesh = new Physijs.ConvexMesh (shape, cover, 0.25);
mesh.rotation.x = -Math.PI/2;
scene.add (mesh);
mesh.setAngularFactor (new THREE.Vector3(0, 0, 0));
return mesh;
}
Essa é uma grande função, mas é tudo o que vimos antes. Criamos a malha
habilitada para física para a jangada, tornando-a um pouco escorregadia (0,4) e um
pouco elástica (0,6). Deixamos a malha um pouco pesada (0,25). Finalmente,
giramos na horizontal.
A própria malha ficará invisível. Vamos usá-lo como um marcador, como fizemos
com o avatar no Capítulo 8 - Projeto: Virando Nosso Avatar. Para ver a jangada,
criamos um tubo laranja, que é adicionado à malha. Também adicionamos um
“leme” para saber para que direção a jangada está voltada. Depois de adicionar tudo
isso à cena, definimos o fator angular da malha para que ela não gire. Vamos girar o
tubo dentro da malha, mas a própria malha deve estar sempre voltada para a
mesma direção.
A jangada foi adicionada à cena neste ponto, mas não está no início do rio. Vamos
mudar isso na próxima parte do nosso código.
Reiniciando o Jogo
O solo, a água, o placar e a jangada são as peças mais importantes do nosso jogo.
Então, depois que cada um for adicionado, vamos reiniciar o jogo para um bom
ponto de partida.
var ground = addGround ();
var water = addWater ();
var scoreboard = addScoreboard ();
var raft = addRaft ();
» reset ();
Abaixo de addRaft, adicionamos a função reset ().
function reset () {
camera.position.set (0,-1,2);
camera.lookAt (new THREE.Vector3 (0, 0, 0));
raft.add (camera);
scoreboard.message ('');
scoreboard.resetTimer ();
scoreboard.score (0);
raft.__dirtyPosition = true;
raft.position.set (0.75, 2, -9.6);
raft.setLinearVelocity (new THREE.Vector3(0, 0, 0));
}
Controles de Teclado
Estamos prontos para adicionar controles de teclado à cena, mas primeiro
precisamos remover os controles de órbita. Se deixarmos os controles de órbita
ativados, o pressionamento das teclas de seta moveria a jangada e a câmera, o que
seria muito confuso. Portanto, exclua ou comente os controles de órbita acima da
linha START CODING.
// new THREE.OrbitControls (camera, renderer.domElement);
function pushRaft () {
var angle = raft.tube.rotation.z;
var force = new THREE.Vector3 (Math.cos (angle), 0, -Math.sin (angle));
raft.applyCentralForce (force);
}
A Linha de Chegada
Eventualmente, a jangada chega à linha de chegada. E então ele cai da beira do rio
e segue em frente. E indo. Em vez disso, vamos pausar o jogo para que os
jogadores possam ter um momento para admirar sua pontuação antes de tentar
novamente. Precisamos fazer alterações em quatro lugares: em nosso esboço de
código e nas funções reset, animate e gameStep.
Vamos começar com o esboço do código. Antes da chamada para a função reset,
precisamos adicionar uma linha para a variável gameOver.
» var gameOver;
var ground = addGround ();
var water = addWater ();
var scoreboard = addScoreboard ();
var raft = addRaft ();
reset ();
Outras funções usarão essa variável para decidir se precisam animar ou atualizar o
jogo. JavaScript é muito rígido sobre quando as variáveis são declaradas. A regra é
que as variáveis precisam ser declaradas antes de serem usadas. A variável
gameOver será usada em reset, animate e gameStep, portanto, a declaramos
antes de qualquer um deles ser chamado.
Definimos gameOver pela primeira vez no final da função de reset. Sempre que o
jogo é iniciado, que é o que o reset faz, o jogo não acaba. Portanto, definimos
gameOver como false.
function reset () {
camera.position.set (0,-1,2);
camera.lookAt (new THREE.Vector3 (0, 0, 0));
raft.add(camera);
scoreboard.message ('');
scoreboard.resetTimer ();
scoreboard.score (0);
raft.__dirtyPosition = true;
raft.position.set (0.75, 2, -9.6);
raft.setLinearVelocity (new THREE.Vector3 (0, 0, 0));
» gameOver = false;
» animate ();
» scene.onSimulationResume ();
» gameStep ();
}
E como começamos animate () dentro da função reset (), não precisamos mais
chamá-la aqui. Portanto, comente ou exclua. Fazemos algo semelhante na função
gameStep. Se o jogo terminar, saímos imediatamente da função sem concluir
nenhuma das etapas usuais. Se o jogo ainda não terminou, verificamos se deveria.
function gameStep () {
» if (gameOver) return;
» checkForGameOver ();
scene.simulate ();
// Update physics 60 times a second so that motion is smooth
setTimeout (gameStep, 1000/60);
}
» // gameStep ();
function checkForGameOver () {
if (raft.position.z > 9.8) {
gameOver = true;
scoreboard.stopTimer ();
scoreboard.message("You made it!");
}
function reset () {
» resetPowerUps ();
camera.position.set (0,-1,2);
camera.lookAt (new THREE.Vector3(0, 0, 0));
raft.add (camera);
scoreboard.message ('');
scoreboard.resetTimer ();
scoreboard.score (0);
raft.__dirtyPosition = true;
raft.position.set (0.75, 2, -9.6);
raft.setLinearVelocity (new THREE.Vector3(0, 0, 0));
gameOver = false;
animate ();
scene.onSimulationResume ();
gameStep ();
}
Isso vai quebrar a cena, já que não definimos resetPowerUps () ainda. Vamos
fazer isso a seguir. Adicione logo abaixo da chave de fechamento para a função
reset ().
function resetPowerUps () {
var random20 = 20 + Math.floor (10*Math.random ());
var p20 = ground.riverPoints [random20];
addPowerUp (p20);
return mesh;
}
function removeOldPowerUps () {
var last = scene.children.length - 1;
for (var i=last; i>=0; i--) {
var obj = scene.children [i];
if (obj.powerUp) scene.remove(obj);
}
}
É a mesma coisa que fizemos no Capítulo 14 - Projeto: O Jogo do Monstro da
Fruta Roxa, começando com o último objeto filho na cena e passando para o
primeiro para não pular objetos.
Com isso, temos duas frutas que podem te ajudar a marcar pontos como um louco
durante o jogo. Você pode até ser capaz de bater minha pontuação mais alta:
Capítulo 20
JavaScript é a linguagem da Internet. Cada site da web ou móvel que você visita
usa JavaScript de uma forma ou de outra. Então, em nosso último capítulo, vamos
dar uma breve olhada em como os sites da internet funcionam e como eles
dependem do JavaScript.
Não entraremos em muitos detalhes aqui - apenas o suficiente para começar a criar
suas próprias páginas com JavaScript. A maneira mais fácil de fazer isso é colocar
um de nossos próprios projetos em um site. Isso exigirá algumas alterações no
código que estamos escrevendo, mas em comparação com tudo o mais que
abordamos neste livro, isso será fácil.
Primeiro, uma rápida visão geral de como funcionam os navegadores da web e
móveis.
Para que algo divertido aconteça, a página da web precisa dizer ao navegador para
fazer muitas e muitas outras solicitações. Isso é feito com tags HTML e JavaScript.
Algumas das tags em HTML são para parágrafos ou palavras de formatação.
Outras tags, como <img>, carregam coisas como imagens ou estilos que fazem
coisas legais acontecerem. Depois, há as tags <script> que podem carregar
coleções de código JavaScript ou permitir que escrevamos JavaScript diretamente
na página da web.
E assim, assim que o navegador obtém a página da web que solicitou, ele tem que
pedir toneladas e mais coisas que estão descritas nessas tags.
Como todas essas coisas levam tempo, temos que ter cuidado ao executar nosso
JavaScript. Não queremos tentar iniciar nosso código JavaScript antes que as
coleções de código e as imagens tenham terminado de carregar.
A maneira mais fácil de esperar que tudo carregue é o que fizemos ao longo deste
livro: coloque nosso JavaScript abaixo de tudo o mais na página da web.
<body></body>
<script>
// This is where we have been coding - after the <body> tags
// É aqui que temos codificado - após o <body> tags
</script>
Sites Gratuitos
Anteriormente, observamos que apenas os servidores da web disponíveis
publicamente podem servir páginas da web, imagens, JavaScript e assim por diante.
Normalmente, isso vai custar-lhe algum dinheiro. Mas existem maneiras de tornar
suas páginas da web e jogos JavaScript publicamente disponíveis gratuitamente.
Um dos mais fáceis é o Blogger - http://blogger.com
Muitos sites gratuitos removerão o JavaScript ou dificultarão a inserção do
JavaScript em seus sites. Se você está procurando um site gratuito para usar,
certifique-se de que ele suporte à adição de seu próprio JavaScript. Os sites devem
oferecer suporte à inserção de JavaScript dentro de tags <script> - como temos
feito ao longo do livro. Os sites também devem nos permitir carregar coleções de
código usando tags <script> com atributos src. Felizmente, o Blogger permite que
façamos as duas coisas.
Se você está compartilhando jogos simples com amigos e familiares, um site
gratuito é a melhor opção. É fácil de configurar e usar. Você só precisa pagar por um
site para jogos mais complexos - os tipos que precisam de contas de usuário e
salvar pontuações ou outras informações do jogo.
Para ter uma ideia de como copiar alguns de nossos projetos em um site real,
vamos dar uma olhada em como colocar uma de nossas animações 3D no Blogger.
Colocando seu Código em Outro Site
Antes de trabalhar nesta seção, você precisará criar uma conta do Blogger (ou uma
conta em um serviço semelhante). As instruções abaixo devem funcionar para a
maioria dos sites, mas todos os serviços de postagem têm suas próprias
peculiaridades que você pode ter que depurar por conta própria. Observe também
que os controles do Blogger podem mudar com o tempo e podem não ter a
aparência ou funcionar exatamente como mostrado aqui.
Publicar nosso código no Blogger é muito fácil. Comece uma postagem
normalmente, mas certifique-se de clicar no botão HTML na barra de ferramentas
Postagem:
Na área de texto abaixo do botão HTML, adicione algum HTML. O seguinte cria um
parágrafo curto, seguido por um ponto para o seu jogo 3D, seguido por outro
parágrafo curto:
<script src="/three.js"></script>
Isso informa ao poderoso navegador da web para encontrar a coleção de códigos
no servidor da web atual. O servidor da web para todos os nossos códigos é:
www.code3Dgames.com
já que 3DE está localizado em:
https://www.code3Dgames.com/3de
Mas agora que estamos criando uma página no Blogger, temos que dizer às
páginas do Blogger para procurar as coleções de código em:
www.code3Dgames.com
em vez de no Blogger. Para fazer isso, basta adicionar:
https://www.code3Dgames.com
no início dos valores src das tags <script>.
<script src="https://code3Dgames.com/three.js"></script>
<script src="https://code3Dgames.com/controls/OrbitControls.js"></script>
Com isso, você poderá publicar sua postagem e ver seu trabalho:
https://talk.code3Dgames.com/
http://code3Dgames.blogspot.com/2018/02/amazing-3d-animation.html
Apêndice 1
Código do Projeto
Este apêndice contém versões completas de todos os projetos criados neste livro.
Seu código pode não ser exatamente igual ao código a seguir - tudo bem. O código
é incluído no caso de você ter problemas e desejar comparar seu código a uma
versão funcional.
<body></body>
<script src="/three.js"></script>
<script>
// The "scene" is where stuff in our game will happen:
var scene = new THREE.Scene ();
var flat = {flatShading: true};
var light = new THREE.AmbientLight ('white', 0.8);
scene.add (light);
// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer ();
renderer.setSize (window.innerWidth, window.innerHeight);
document.body.appendChild (renderer.domElement);
function animate () {
requestAnimationFrame (animate);
var t = clock.getElapsedTime ();
animate ();
<body></body>
<script src="/three.js"></script>
<script>
// The "scene" is where stuff in our game will happen:
var scene = new THREE.Scene ();
var flat = {flatShading: true};
var light = new THREE.AmbientLight('white', 0.8);
scene.add (light);
// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer ({antialias: true});
renderer.setSize (window.innerWidth, window.innerHeight);
document.body.appendChild (renderer.domElement);
<body></body>
<script src="/three.js"></script>
<script>
// The "scene" is where stuff in our game will happen:
// A "cena" é onde as coisas em nosso jogo acontecerão:
var scene = new THREE.Scene ();
var flat = {flatShading: true};
var light = new THREE.AmbientLight ('white', 0.8);
scene.add (light);
// The "renderer" draws what the camera sees onto the screen:
// O "renderizador" desenha o que a câmera vê na tela:
var renderer = new THREE.WebGLRenderer ({antialias: true});
renderer.setSize (window.innerWidth, window.innerHeight);
document.body.appendChild (renderer.domElement);
marker.add (camera);
//Trees
// Árvores
makeTreeAt (500, 0);
makeTreeAt (-500, 0);
makeTreeAt (750, -1000);
makeTreeAt (-750, -1000);
if (isCartwheeling) {
avatar.rotation.z = avatar.rotation.z + 0.05;
}
if (isFlipping) {
avatar.rotation.x = avatar.rotation.x + 0.05;
}
renderer.render (scene, camera);
}
Animate ();
<body></body>
<script src="/three.js"></script>
<script src="controlsFlyControls.js"></script>
<script>
// The "renderer" draws what the camera sees onto the screen:
// O "renderizador" desenha o que a câmera vê na tela:
var renderer = new THREE.WebGLRenderer ({antialias: true});
renderer.setSize (window.innerWidth, window.innerHeight);
document.body.appendChild (renderer.domElement);
function makePlanet () {
var size = r (50);
var x = r (1000) - 500;
var y = r (1000) - 500;
var z = r (1000) - 1000;
var surface = rColor ();
makePlanet ();
makePlanet ();
function r (max) {
if (max) return max * Math.random ();
return Math.random ();
}
randomNum = r (100);
console.log (randomNum);
console.log (r (100));
console.log (r (100));
function rColor () {
return new THREE.Color (r (), r (), r ());
}
function animate () {
var delta = clock.getDelta ();
controls.update (delta);
Código: Colisões
Este é o código do avatar após adicionarmos as colisões no Capítulo 10 - Projeto:
Colisões:
<body></body>
<script src="/three.js"></script>
<script src="/tween.js"></script>
<script>
// The "scene" is where stuff in our game will happen:
var scene = new THREE.Scene();
var flat = {flatShading: true};
var light = new THREE.AmbientLight('white', 0.8);
scene.add(light);
// The "camera" is what sees the stuff:
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
camera.position.z = 500;
// scene.add(camera);
// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// ******** START CODING ON THE NEXT LINE ********
var marker = new THREE.Object3D();
scene.add(marker);
//var cover = new THREE.MeshNormalMaterial(flat);
var body = new THREE.SphereGeometry(100);
var cover = new THREE.MeshNormalMaterial();
var avatar = new THREE.Mesh(body, cover);
marker.add(avatar);
var hand = new THREE.SphereGeometry(50);
var rightHand = new THREE.Mesh(hand, cover);
rightHand.position.set(-150, 0, 0);
avatar.add(rightHand);var leftHand = new THREE.Mesh(hand, cover);
leftHand.position.set(150, 0, 0);
avatar.add(leftHand);
var foot = new THREE.SphereGeometry(50);
var rightFoot = new THREE.Mesh(foot, cover);
rightFoot.position.set(-75, -125, 0);
avatar.add(rightFoot);
var leftFoot = new THREE.Mesh(foot, cover);
leftFoot.position.set(75, -125, 0);
avatar.add(leftFoot);
marker.add(camera);
var notAllowed = [];
function makeTreeAt(x, z) {
var trunk = new THREE.Mesh(
new THREE.CylinderGeometry(50, 50, 200),
new THREE.MeshBasicMaterial({color: 'sienna'})
);
var top = new THREE.Mesh(
new THREE.SphereGeometry(150),
new THREE.MeshBasicMaterial({color: 'forestgreen'})
);
top.position.y = 175;
trunk.add(top);
var boundary = new THREE.Mesh(
new THREE.CircleGeometry(300),
new THREE.MeshNormalMaterial()
);
boundary.position.y = -100;
boundary.rotation.x = -Math.PI/2;
trunk.add(boundary);
notAllowed.push(boundary);
trunk.position.set(x, -75, z);
scene.add(trunk);
}makeTreeAt( 500, 0);
makeTreeAt(-500, 0);
makeTreeAt( 750, -1000);
makeTreeAt(-750, -1000);
// Now, animate what the camera sees on the screen:
var clock = new THREE.Clock();
var isCartwheeling = false;
var isFlipping = false;
var isMovingRight = false;
var isMovingLeft = false;
var isMovingForward = false;
var isMovingBack = false;
var direction;
var lastDirection;
function animate() {
requestAnimationFrame(animate);
TWEEN.update();
turn();
walk();
acrobatics();
renderer.render(scene, camera);
}a
nimate();
function turn() {
if (isMovingRight) direction = Math.PI/2;
if (isMovingLeft) direction = -Math.PI/2;
if (isMovingForward) direction = Math.PI;
if (isMovingBack) direction = 0;
if (!isWalking()) direction = 0;
if (direction == lastDirection) return;
lastDirection = direction;
var tween = new TWEEN.Tween(avatar.rotation);
tween.to({y: direction}, 500);
tween.start();
}f
unction walk() {
if (!isWalking()) return;var speed = 10;
var size = 100;
var time = clock.getElapsedTime();
var position = Math.sin(speed time) size;
rightHand.position.z = position;
leftHand.position.z = -position;
rightFoot.position.z = -position;
leftFoot.position.z = position;
}f
unction isWalking() {
if (isMovingRight) return true;
if (isMovingLeft) return true;
if (isMovingForward) return true;
if (isMovingBack) return true;
return false;
}f
unction acrobatics() {
if (isCartwheeling) {
avatar.rotation.z = avatar.rotation.z + 0.05;
}i
f (isFlipping) {
avatar.rotation.x = avatar.rotation.x + 0.05;
}
}f
unction isColliding() {
var vector = new THREE.Vector3(0, -1, 0);
var raycaster = new THREE.Raycaster(marker.position, vector);
var intersects = raycaster.intersectObjects(notAllowed);
if (intersects.length > 0) return true;
return false;
}d
ocument.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
var code = event.code;
if (code == 'ArrowLeft') {
marker.position.x = marker.position.x - 5;
isMovingLeft = true;
}if (code == 'ArrowRight') {
marker.position.x = marker.position.x + 5;
isMovingRight = true;
}i
f (code == 'ArrowUp') {
marker.position.z = marker.position.z - 5;
isMovingForward = true;
}i
f (code == 'ArrowDown') {
marker.position.z = marker.position.z + 5;
isMovingBack = true;
}i
f (code == 'KeyC') isCartwheeling = !isCartwheeling;
if (code == 'KeyF') isFlipping = !isFlipping;
if (isColliding()) {
if (isMovingLeft) marker.position.x = marker.position.x + 5;
if (isMovingRight) marker.position.x = marker.position.x - 5;
if (isMovingForward) marker.position.z = marker.position.z + 5;
if (isMovingBack) marker.position.z = marker.position.z - 5;
}
}d
ocument.addEventListener('keyup', sendKeyUp);
function sendKeyUp(event) {
var code = event.code;
if (code == 'ArrowLeft') isMovingLeft = false;
if (code == 'ArrowRight') isMovingRight = false;
if (code == 'ArrowUp') isMovingForward = false;
if (code == 'ArrowDown') isMovingBack = false;
}
</script
<body></body>
<script src="/three.js"></script>
<script src="/tween.js"></script>
<script src="/scoreboard.js"></script>
<script src="/sounds.js"></script>
<script>
// The "scene" is where stuff in our game will happen:
var scene = new THREE.Scene();
var flat = {flatShading: true};
var light = new THREE.AmbientLight('white', 0.8);
scene.add(light);
// The "camera" is what sees the stuff:
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
camera.position.z = 500;
// scene.add(camera);
// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// ******** START CODING ON THE NEXT LINE ********
var marker = new THREE.Object3D();
scene.add(marker);
//var cover = new THREE.MeshNormalMaterial(flat);
var body = new THREE.SphereGeometry(100);
var cover = new THREE.MeshNormalMaterial();
var avatar = new THREE.Mesh(body, cover);
marker.add(avatar);
var hand = new THREE.SphereGeometry(50);var rightHand = new THREE.Mesh(hand, cover);
rightHand.position.set(-150, 0, 0);
avatar.add(rightHand);
var leftHand = new THREE.Mesh(hand, cover);
leftHand.position.set(150, 0, 0);
avatar.add(leftHand);
var foot = new THREE.SphereGeometry(50);
var rightFoot = new THREE.Mesh(foot, cover);
rightFoot.position.set(-75, -125, 0);
avatar.add(rightFoot);
var leftFoot = new THREE.Mesh(foot, cover);
leftFoot.position.set(75, -125, 0);
avatar.add(leftFoot);
marker.add(camera);
var scoreboard = new Scoreboard();
scoreboard.countdown(45);
scoreboard.score();
scoreboard.help(
'Arrow keys to move. ' +
'Space bar to jump for fruit. ' +
'Watch for shaking trees with fruit. ' +
'Get near the tree and jump before the fruit is gone!'
);
scoreboard.onTimeExpired(timeExpired);
function timeExpired() {
scoreboard.message("Game Over!");
}v
ar notAllowed = [];
var treeTops = [];
function makeTreeAt(x, z) {
var trunk = new THREE.Mesh(
new THREE.CylinderGeometry(50, 50, 200),
new THREE.MeshBasicMaterial({color: 'sienna'})
);
var top = new THREE.Mesh(
new THREE.SphereGeometry(150),new THREE.MeshBasicMaterial({color: 'forestgreen'})
);
top.position.y = 175;
trunk.add(top);
var boundary = new THREE.Mesh(
new THREE.CircleGeometry(300),
new THREE.MeshNormalMaterial()
);
boundary.position.y = -100;
boundary.rotation.x = -Math.PI/2;
trunk.add(boundary);
notAllowed.push(boundary);
treeTops.push(top);
trunk.position.set(x, -75, z);
scene.add(trunk);
}m
akeTreeAt( 500, 0);
makeTreeAt(-500, 0);
makeTreeAt( 750, -1000);
makeTreeAt(-750, -1000);
var treasureTreeNumber;
function updateTreasureTreeNumber() {
var rand = Math.random() * treeTops.length;
treasureTreeNumber = Math.floor(rand);
}f
unction shakeTreasureTree() {
updateTreasureTreeNumber();
var tween = new TWEEN.Tween({shake: 0});
tween.to({shake: 20 * 2 Math.PI}, 81000);
tween.onUpdate(shakeTreeUpdate);
tween.onComplete(shakeTreeComplete);
tween.start();
}f
unction shakeTreeUpdate(update) {
var top = treeTops[treasureTreeNumber];
top.position.x = 50 * Math.sin(update.shake);
}} f
unction shakeTreeComplete() {
var top = treeTops[treasureTreeNumber];
top.position.x = 0;
setTimeout(shakeTreasureTree, 2*1000);
}s
hakeTreasureTree();
// Now, animate what the camera sees on the screen:
var clock = new THREE.Clock();
var isCartwheeling = false;
var isFlipping = false;
var isMovingRight = false;
var isMovingLeft = false;
var isMovingForward = false;
var isMovingBack = false;
var direction;
var lastDirection;
function animate() {
requestAnimationFrame(animate);
TWEEN.update();
turn();
walk();
acrobatics();
renderer.render(scene, camera);
}a
nimate();
function turn() {
if (isMovingRight) direction = Math.PI/2;
if (isMovingLeft) direction = -Math.PI/2;
if (isMovingForward) direction = Math.PI;
if (isMovingBack) direction = 0;
if (!isWalking()) direction = 0;
if (direction == lastDirection) return;
lastDirection = direction;
var tween = new TWEEN.Tween(avatar.rotation);
tween.to({y: direction}, 500);
tween.start();tween.start();
}f
unction walk() {
if (!isWalking()) return;
var speed = 10;
var size = 100;
var time = clock.getElapsedTime();
var position = Math.sin(speed time) size;
rightHand.position.z = position;
leftHand.position.z = -position;
rightFoot.position.z = -position;
leftFoot.position.z = position;
}f
unction isWalking() {
if (isMovingRight) return true;
if (isMovingLeft) return true;
if (isMovingForward) return true;
if (isMovingBack) return true;
return false;
}f
unction acrobatics() {
if (isCartwheeling) {
avatar.rotation.z = avatar.rotation.z + 0.05;
}i
f (isFlipping) {
avatar.rotation.x = avatar.rotation.x + 0.05;
}
}f
unction jump() {
if (avatar.position.y > 0) return;
checkForTreasure();
animateJump();
}f
unction checkForTreasure() {
var top = treeTops[treasureTreeNumber];
var tree = top.parent;
var p1 = tree.position;
var p2 = marker.position;var xDiff = p1.x - p2.x;
var zDiff = p1.z - p2.z;
var distance = Math.sqrt(xDiff*xDiff + zDiff*zDiff);
if (distance < 500) scorePoints();
}f
unction scorePoints() {
if (scoreboard.getTimeRemaining() == 0) return;
scoreboard.addPoints(10);
Sounds.bubble.play();
animateFruit();
}f
unction animateJump() {
var tween = new TWEEN.Tween({jump: 0});
tween.to({jump: Math.PI}, 400);
tween.onUpdate(animateJumpUpdate);
tween.onComplete(animateJumpComplete);
tween.start();
}f
unction animateJumpUpdate(update) {
avatar.position.y = 100 * Math.sin(update.jump);
}f
unction animateJumpComplete() {
avatar.position.y = 0;
}v
ar fruit;
function animateFruit() {
if (fruit) return;
fruit = new THREE.Mesh(
new THREE.CylinderGeometry(25, 25, 5, 25),
new THREE.MeshBasicMaterial({color: 'gold'})
);
marker.add(fruit);
var tween = new TWEEN.Tween({height: 200, spin: 0});
tween.to({height: 350, spin: 2 * Math.PI}, 500);
tween.onUpdate(animateFruitUpdate);
tween.onComplete(animateFruitComplete);
tween.start();} f
unction animateFruitUpdate(update) {
fruit.position.y = update.height;
fruit.rotation.x = update.spin;
}f
unction animateFruitComplete() {
marker.remove(fruit);
fruit = undefined;
}f
unction isColliding() {
var vector = new THREE.Vector3(0, -1, 0);
var raycaster = new THREE.Raycaster(marker.position, vector);
var intersects = raycaster.intersectObjects(notAllowed);
if (intersects.length > 0) return true;
return false;
}d
ocument.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
var code = event.code;
if (code == 'ArrowLeft') {
marker.position.x = marker.position.x - 5;
isMovingLeft = true;
}i
f (code == 'ArrowRight') {
marker.position.x = marker.position.x + 5;
isMovingRight = true;
}i
f (code == 'ArrowUp') {
marker.position.z = marker.position.z - 5;
isMovingForward = true;
}i
f (code == 'ArrowDown') {
marker.position.z = marker.position.z + 5;
isMovingBack = true;
}i
f (code == 'KeyC') isCartwheeling = !isCartwheeling;
if (code == 'KeyF') isFlipping = !isFlipping;if (code == 'Space') jump();
if (isColliding()) {
if (isMovingLeft) marker.position.x = marker.position.x + 5;
if (isMovingRight) marker.position.x = marker.position.x - 5;
if (isMovingForward) marker.position.z = marker.position.z + 5;
if (isMovingBack) marker.position.z = marker.position.z - 5;
}
}d
ocument.addEventListener('keyup', sendKeyUp);
function sendKeyUp(event) {
var code = event.code;
if (code == 'ArrowLeft') isMovingLeft = false;
if (code == 'ArrowRight') isMovingRight = false;
if (code == 'ArrowUp') isMovingForward = false;
if (code == 'ArrowDown') isMovingBack = false;
}
</script>
Código: Trabalhando com Luzes e Materiais
Esta é a versão final do código que usamos para explorar luzes e materiais no
Capítulo 12 - Trabalhando com Luzes e Materiais:
<body></body>
<script src="/three.js"></script>
<script src="controlsOrbitControls.js"></script>
<script>
// The "scene" is where stuff in our game will happen:
var scene = new THREE.Scene();
var flat = {flatShading: true};
var light = new THREE.AmbientLight('white', 0.1);
scene.add(light);
// The "camera" is what sees the stuff:
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
camera.position.z = 500;
camera.position.y = 500;
camera.lookAt(new THREE.Vector3(0,0,0));
scene.add(camera);
// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// ******** START CODING ON THE NEXT LINE ********
var shape = new THREE.TorusGeometry(50, 20, 8, 20);
var cover = new THREE.MeshPhongMaterial({color: 'red'});
cover.specular.setRGB(0.9, 0.9, 0.9);
var donut = new THREE.Mesh(shape, cover);
donut.position.set(0, 150, 0);
donut.castShadow = true;
scene.add(donut);
var texture = new THREE.TextureLoader().load("textureshardwood.png");
var shape = new THREE.PlaneGeometry(1000, 1000, 10, 10);
var cover = new THREE.MeshPhongMaterial();
cover.map = texture;
cover.map = texture;
var ground = new THREE.Mesh(shape, cover);
ground.rotation.x = -Math.PI/2;
ground.receiveShadow = true;
scene.add(ground);
var point = new THREE.PointLight('white', 0.4);
point.position.set(0, 300, -100);
point.castShadow = true;
// scene.add(point);
var shape = new THREE.SphereGeometry(10);
var cover = new THREE.MeshPhongMaterial({emissive: 'white'});
var phonyLight = new THREE.Mesh(shape, cover);
point.add(phonyLight);
var spot = new THREE.SpotLight('white', 0.7);
spot.position.set(200, 300, 0);
spot.castShadow = true;
spot.shadow.camera.far = 750;
spot.angle = Math.PI/4;
spot.penumbra = 0.1;
scene.add(spot);
var shape = new THREE.CylinderGeometry(4, 10, 20);
var cover = new THREE.MeshPhongMaterial({emissive: 'white'});
var phonyLight = new THREE.Mesh(shape, cover);
phonyLight.position.y = 10;
phonyLight.rotation.z = -Math.PI/8;
spot.add(phonyLight);
var sunlight = new THREE.DirectionalLight('white', 0.4);
sunlight.position.set(200, 300, 0);
sunlight.castShadow = true;
// scene.add(sunlight);
var d = 500;
sunlight.shadow.camera.left = -d;
sunlight.shadow.camera.right = d;
sunlight.shadow.camera.top = d;
sunlight.shadow.camera.bottom = -d;
controls = new THREE.OrbitControls( camera, renderer.domElement );
// Start Animation
var clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
var t = clock.getElapsedTime();
// Animation code goes here...
donut.rotation.set(t, 2*t, 0);
donut.position.z = 200 * Math.sin(t);
renderer.render(scene, camera);
}a
nimate();
</script>
<body></body>
<script src="/three.js"></script>
<script src="controlsFlyControls.js"></script>
<script>
// The "scene" is where stuff in our game will happen:
var scene = new THREE.Scene();
var flat = {flatShading: true};
var light = new THREE.AmbientLight('white', 0.1);
scene.add(light);
// The "camera" is what sees the stuff:
var aspectRatio = window.innerWidth / window.innerHeight;
var w = window.innerWidth / 2;
var h = window.innerHeight / 2;
var camera = new THREE.OrthographicCamera(-w, w, h, -h, 1, 10000);
camera.position.y = 500;
camera.rotation.x = -Math.PI/2;
scene.add(camera);
var aboveCam = camera;
// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// ******** START CODING ON THE NEXT LINE ********
var cover = new THREE.MeshPhongMaterial({emissive: 'yellow'});
var shape = new THREE.SphereGeometry(50, 32, 16);
var sun = new THREE.Mesh(shape, cover);
scene.add(sun);
var sunlight = new THREE.PointLight('white', 1.7);
sun.add(sunlight);
var earthLocal = new THREE.Object3D();
earthLocal.position.x = 300;
scene.add(earthLocal);
scene.add(earthLocal);
var texture = new THREE.TextureLoader().load("texturesearth.png");
var cover = new THREE.MeshPhongMaterial({map: texture});
var shape = new THREE.SphereGeometry(20, 32, 16);
var earth = new THREE.Mesh(shape, cover);
earthLocal.add(earth);
var moonOrbit = new THREE.Object3D();
earthLocal.add(moonOrbit);
var texture = new THREE.TextureLoader().load("texturesmoon.png");
var cover = new THREE.MeshPhongMaterial({map: texture, specular: 'black'});
var shape = new THREE.SphereGeometry(15, 32, 16);
var moon = new THREE.Mesh(shape, cover);
moon.position.set(0, 0, 100);
moon.rotation.set(0, Math.PI/2, 0);
moonOrbit.add(moon);
var moonCam = new THREE.PerspectiveCamera(70, aspectRatio, 1, 10000);
moonCam.position.z = 25;
moonCam.rotation.y = Math.PI;
moonOrbit.add(moonCam);
camera = moonCam;
var shipCam = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
shipCam.position.set(0, 0, 500);
scene.add(shipCam);
var controls = new THREE.FlyControls(shipCam, renderer.domElement);
controls.movementSpeed = 42;
controls.rollSpeed = 0.15;
controls.dragToLook = true;
controls.autoForward = false;
var cover = new THREE.PointsMaterial({color: 'white', size: 15});
var shape = new THREE.Geometry();
var distance = 4000;
for (var i = 0; i < 500; i++) {
var ra = 2 Math.PI Math.random();
var dec = 2 Math.PI Math.random();
var point = new THREE.Vector3();
point.x = distance Math.cos(dec) Math.cos(ra);
point.y = distance * Math.sin(dec);
point.z = distance Math.cos(dec) Math.sin(ra);
shape.vertices.push(point);
}v
ar stars = new THREE.Points(shape, cover);
scene.add(stars);
// Start Animation
var clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
// Animation code goes here...
var delta = clock.getDelta();
controls.update(delta);
renderer.render(scene, camera);
}a
nimate();
var speed = 10;
var pause = false;
var days = 0;
var clock2 = new THREE.Clock();
function gameStep() {
setTimeout(gameStep, 1000/30);
if (pause) return;
days = days + speed * clock2.getDelta();
earth.rotation.y = days;
var years = days / 365.25;
earthLocal.position.x = 300 * Math.cos(years);
earthLocal.position.z = -300 * Math.sin(years);
moonOrbit.rotation.y = days / 29.5;
}g
ameStep();
gameStep();
document.addEventListener("keydown", sendKeyDown);
function sendKeyDown(event) {
var code = event.code;
if (code == 'Digit1') speed = 1;
if (code == 'Digit2') speed = 10;
if (code == 'Digit3') speed = 100;
if (code == 'Digit4') speed = 1000;
if (code == 'KeyP') pauseUnpause();
if (code == 'KeyC') switchCamera();
if (code == 'KeyF') fly();
}f
unction pauseUnpause() {
pause = !pause;
clock2.running = false;
}f
unction switchCamera() {
if (camera == moonCam) camera = aboveCam;
else camera = moonCam;
}f
unction fly() {
camera = shipCam;
}
</script>
<body></body>
<script src="/three.js"></script>
<script src="/physi.js"></script>
<script src="/scoreboard.js"></script>
<script>
// Physics settings
Physijs.scripts.ammo = '/ammo.js';
Physijs.scripts.worker = '/physijs_worker.js';
// The "scene" is where stuff in our game will happen:
var scene = new Physijs.Scene();
scene.setGravity(new THREE.Vector3( 0, -250, 0 ));
var flat = {flatShading: true};
var light = new THREE.AmbientLight('white', 0.8);
scene.add(light);
// The "camera" is what sees the stuff:
var w = window.innerWidth / 2;
var h = window.innerHeight / 2;
var camera = new THREE.OrthographicCamera(-w, w, h, -h, 1, 10000);
camera.position.z = 500;
scene.add(camera);
// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor('skyblue');
document.body.appendChild(renderer.domElement);
// ******** START CODING ON THE NEXT LINE ********
var gameOver = false;
var ground = addGround();
var avatar = addAvatar();
var scoreboard = addScoreboard();
reset();
reset();
function addGround() {
var shape = new THREE.BoxGeometry(2*w, h, 10);
var cover = new THREE.MeshBasicMaterial({color: 'lawngreen'});
var ground = new Physijs.BoxMesh(shape, cover, 0);
ground.position.y = -h/2;
scene.add(ground);
return ground;
}f
unction addAvatar() {
var shape = new THREE.CubeGeometry(100, 100, 1);
var cover = new THREE.MeshBasicMaterial({visible: false});
var avatar = new Physijs.BoxMesh(shape, cover, 1);
scene.add(avatar);
var image = new THREE.TextureLoader().load("imagesmonster.png");
var material = new THREE.SpriteMaterial({map: image});
var sprite = new THREE.Sprite(material);
sprite.scale.set(100, 100, 1);
avatar.add(sprite);
avatar.setLinearFactor(new THREE.Vector3(1, 1, 0));
avatar.setAngularFactor(new THREE.Vector3(0, 0, 0));
return avatar;
}f
unction addScoreboard() {
var scoreboard = new Scoreboard();
scoreboard.score();
scoreboard.help(
"Use arrow keys to move and the space bar to jump. " +
"Don't let the fruit get past you!!!"
);
return scoreboard;
}f
unction reset() {
avatar.__dirtyPosition = true;
avatar.position.set(-0.6*w, 200, 0);
avatar.setLinearVelocity(new THREE.Vector3(0, 250, 0));
scoreboard.score(0);
scoreboard.message('');
var last = scene.children.length - 1;
for (var i=last; i>=0; i--) {
var obj = scene.children[i];
if (obj.isFruit) scene.remove(obj);
}i
f (gameOver) {
gameOver = false;
animate();
}
}f
unction launchFruit() {
if (gameOver) return;
var speed = 500 + (10 Math.random() scoreboard.getScore());
var fruit = makeFruit();
fruit.setLinearVelocity(new THREE.Vector3(-speed, 0, 0));
fruit.setAngularVelocity(new THREE.Vector3(0, 0, 10));
}l
aunchFruit();
setInterval(launchFruit, 3*1000);
function makeFruit() {
var shape = new THREE.SphereGeometry(40, 16, 24);
var cover = new THREE.MeshBasicMaterial({visible: false});
var fruit = new Physijs.SphereMesh(shape, cover);
fruit.position.set(w, 40, 0);
scene.add(fruit);
var image = new THREE.TextureLoader().load("imagesfruit.png");
cover = new THREE.MeshBasicMaterial({map: image, transparent: true});
shape = new THREE.PlaneGeometry(80, 80);
var picturePlane = new THREE.Mesh(shape, cover);
fruit.add(picturePlane);
fruit.setAngularFactor(new THREE.Vector3(0, 0, 1));
fruit.setLinearFactor(new THREE.Vector3(1, 1, 0));
fruit.isFruit = true;
return fruit;
}
function checkMissedFruit() {
var count=0;
for (var i=0; i<scene.children.length; i++) {
var obj = scene.children[i];
if (obj.isFruit && obj.position.x < -w) count++;
}i
f (count > 10) {
gameOver = true;
scoreboard.message(
'Purple Fruit Monster missed too much fruit! ' +
'Press R to try again.'
);
}
}f
unction gameStep() {
scene.simulate();
setTimeout(gameStep, 1000/30);
}g
ameStep();
var clock = new THREE.Clock();
function animate() {
if (gameOver) return;
requestAnimationFrame(animate);
var t = clock.getElapsedTime();
// Animation code goes here...
renderer.render(scene, camera);
}a
nimate();
document.addEventListener("keydown", sendKeyDown);
function sendKeyDown(event) {
var code = event.code;
if (code == 'ArrowLeft') left();
if (code == 'ArrowRight') right();
if (code == 'ArrowUp') up();
if (code == 'ArrowDown') down();
if (code == 'Space') up();
if (code == 'KeyR') reset();
}
}f
unction left() { move(-100, 0); }
function right() { move(100, 0); }
function up() { move(0, 250); }
function down() { move(0, -50); }
function move(x, y) {
if (x > 0) avatar.scale.x = 1;
if (x < 0) avatar.scale.x = -1;
var dir = new THREE.Vector3(x, y, 0);
avatar.applyCentralImpulse(dir);
}a
vatar.addEventListener('collision', sendCollision);
function sendCollision(object) {
if (gameOver) return;
if (object.isFruit) {
scoreboard.addPoints(10);
avatar.setLinearVelocity(new THREE.Vector3(0, 250, 0));
scene.remove(object);
}i
f (object == ground) {
gameOver = true;
scoreboard.message(
"Purple Fruit Monster crashed! " +
"Press R to try again."
);
}
}
Código: Tilt-a-Board
Esta é a versão final do código do jogo do Capítulo 15 - Projeto: Tilt-a-Board,
incluindo os dois desafios de bônus:
<body></body>
<script src="/three.js"></script>
<script src="/physi.js"></script>
<script src="/spe.js"></script>
<script>
// Physics settings
Physijs.scripts.ammo = '/ammo.js';
Physijs.scripts.worker = '/physijs_worker.js';
// The "scene" is where stuff in our game will happen:
var scene = new Physijs.Scene();
scene.setGravity(new THREE.Vector3( 0, -100, 0 ));
var flat = {flatShading: true};
var light = new THREE.AmbientLight('white', 0.2);
scene.add(light);
// The "camera" is what sees the stuff:
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
camera.position.z = 500;
scene.add(camera);
// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.set(0, 100, 200);
camera.lookAt(new THREE.Vector3(0, 0, 0));
renderer.shadowMap.enabled = true;
// ******** START CODING ON THE NEXT LINE ********
var lights = addLights();
var ball = addBall();
var board = addBoard();
var goal = addGoal();
reset();
addGoalLight();
function addLights() {
var lights = new THREE.Object3D();
var light1 = new THREE.PointLight('white', 0.4);
light1.position.set(50, 50, -100);
light1.castShadow = true;
lights.add(light1);
var light2 = new THREE.PointLight('white', 0.5);
light2.position.set(-50, 50, 175);
light2.castShadow = true;
lights.add(light2);
scene.add(lights);
return lights;
}f
unction addBall() {
var shape = new THREE.SphereGeometry(10, 25, 21);
var cover = new THREE.MeshPhongMaterial({color: 'red'});
cover.specular.setRGB(0.6, 0.6, 0.6);
var ball = new Physijs.SphereMesh(shape, cover);
ball.castShadow = true;
scene.add(ball);
return ball;
}f
unction addBoard() {
var cover = new THREE.MeshPhongMaterial({color: 'gold'});
cover.specular.setRGB(0.9, 0.9, 0.9);
var shape = new THREE.CubeGeometry(50, 2, 200);
var beam1 = new Physijs.BoxMesh(shape, cover, 0);
beam1.position.set(-37, 0, 0);
beam1.receiveShadow = true;
var beam2 = new Physijs.BoxMesh(shape, cover, 0);
beam2.position.set(75, 0, 0);
beam2.receiveShadow = true;
beam1.add(beam2);
shape = new THREE.CubeGeometry(200, 2, 50);
var beam3 = new Physijs.BoxMesh(shape, cover, 0);
beam3.position.set(40, 0, -40);
beam3.receiveShadow = true;
beam1.add(beam3);
var beam4 = new Physijs.BoxMesh(shape, cover, 0);
beam4.position.set(40, 0, 40);
beam4.receiveShadow = true;
beam1.add(beam4);
beam1.rotation.set(0.1, 0, 0);
scene.add(beam1);
return beam1;
}f
unction addGoal() {
shape = new THREE.CubeGeometry(100, 2, 100);
cover = new THREE.MeshNormalMaterial({wireframe: true});
var goal = new Physijs.BoxMesh(shape, cover, 0);
goal.position.y = -50;
scene.add(goal);
return goal;
}f
unction reset() {
ball.__dirtyPosition = true;
ball.__dirtyRotation = true;
ball.position.set(-33, 200, -65);
ball.setLinearVelocity(new THREE.Vector3(0, 0, 0));
ball.setAngularVelocity(new THREE.Vector3(0, 0, 0));
board.__dirtyRotation = true;
board.rotation.set(0.1, 0, 0);
}v
ar fire, goalFire;
function addGoalLight(){
var material = new THREE.TextureLoader().load('texturesspe/star.png');
fire = new SPE.Group({texture: {value: material}});
goalFire = new SPE.Emitter({particleCount: 1000, maxAge: {value: 4}});
fire.addEmitter(goalFire);
fire.addEmitter(goalFire);
scene.add(fire.mesh);
goalFire.velocity.value = new THREE.Vector3(0, 75, 0);
goalFire.velocity.spread = new THREE.Vector3(10, 7.5, 5);
goalFire.acceleration.value = new THREE.Vector3(0, -15, 0);
goalFire.position.spread = new THREE.Vector3(25, 0, 0);
goalFire.size.value = 25;
goalFire.size.spread = 10;
goalFire.color.value = [new THREE.Color('white'), new THREE.Color('red')];
goalFire.disable();
}f
unction win(flashCount) {
if (!flashCount) flashCount = 0;
goalFire.enable();
flashCount++;
if (flashCount > 10) {
reset();
goalFire.disable();
return;
}s
etTimeout(win, 500, flashCount);
}g
oal.addEventListener('collision', win);
function addBackground() {
var cover = new THREE.PointsMaterial({color: 'white', size: 2});
var shape = new THREE.Geometry();
var distance = 500;
for (var i = 0; i < 2000; i++) {
var ra = 2 Math.PI Math.random();
var dec = 2 Math.PI Math.random();
var point = new THREE.Vector3();
point.x = distance Math.cos(dec) Math.cos(ra);
point.y = distance * Math.sin(dec);
point.z = distance Math.cos(dec) Math.sin(ra);
shape.vertices.push(point);
shape.vertices.push(point);
}v
ar stars = new THREE.Points(shape, cover);
scene.add(stars);
}/
/ Animate motion in the game
var clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
var dt = clock.getDelta();
fire.tick(dt);
lights.rotation.y = lights.rotation.y + dt/2;
}a
nimate();
// Run physics
function gameStep() {
if (ball.position.y < -500) reset(ball);
scene.simulate();
// Update physics 60 times a second so that motion is smooth
setTimeout(gameStep, 1000/60);
}g
ameStep();
document.addEventListener("keydown", sendKeyDown);
function sendKeyDown(event){
var code = event.code;
if (code == 'ArrowLeft') left();
if (code == 'ArrowRight') right();
if (code == 'ArrowUp') up();
if (code == 'ArrowDown') down();
}f
unction left() { tilt('z', 0.02); }
function right() { tilt('z', -0.02); }
function up() { tilt('x', -0.02); }
function down() { tilt('x', 0.02); }
function tilt(dir, amount) {
board.__dirtyRotation = true;
board.rotation[dir] = board.rotation[dir] + amount;
}
</script>
Código: Aprendendo Sobre Objetos JavaScript
O código do Capítulo 16 - Aprendendo Sobre Objetos JavaScript, deve ser
parecido com o seguinte:
<body></body>
<script src="/three.js"></script>
<script>
// Your code goes here...
var bestMovie = {
title: 'Star Wars',
year: 1977,
};
var bestMovie = {
title: 'Star Wars',
year: 1977,
stars: ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher'],
};
var bestMovie = {
title: 'Star Wars',
year: 1977,
stars: ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher'],
logMe: function() {
console.log(this.title + ', starring: ' + this.stars);
},
};
bestMovie.logMe();
var bestMovie = {
title: 'Star Wars',
year: 1977,
stars: ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher'],
logMe: function() {
var me = this.about();
console.log(me);
},
about: function() {
return this.title + ', starring: ' + this.stars;
},
},
};
bestMovie.logMe();
var greatMovie = Object.create(bestMovie);
greatMovie.logMe();
// => Star Wars, starring: Mark Hamill,Harrison Ford,Carrie Fisher
greatMovie.title = 'Toy Story';
greatMovie.year = 1995;
greatMovie.stars = ['Tom Hanks', 'Tim Allen'];
greatMovie.logMe();
// => Toy Story, starring: Tom Hanks,Tim Allen
bestMovie.logMe();
// => Star Wars, starring: Mark Hamill,Harrison Ford,Carrie Fisher
function Movie(title, stars) {
this.title = title;
this.stars = stars;
this.year = (new Date()).getFullYear();
}v
ar kungFuMovie = new Movie('Kung Fu Panda', ['Jack Black', 'Angelina Jolie']);
console.log(kungFuMovie.title);
// => Kung Fu Panda
console.log(kungFuMovie.stars);
// => ['Jack Black', 'Angelina Jolie']
console.log(kungFuMovie.year);
// => 2018
Movie.prototype.logMe = function() {
console.log(this.title + ', starring: ' + this.stars);
};
kungFuMovie.logMe();
// => Kung Fu Panda, starring: Jack Black,Angelina Jolie
setTimeout(kungFuMovie.logMe.bind(kungFuMovie), 500);
setTimeout(bestMovie.logMe.bind(bestMovie), 500);
// The Challenges :)
var bestMovie = {
title: 'Star Wars',
year: 1977,
stars: ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher'],
logMe: function() {
var me = this.about();
console.log(me);
},
about: function() {
return this.title + ', starring: ' + this.stars;
},
logFullTitle: function() {
var title = this.fullTitle();
console.log(title);
},
fullTitle: function() {
return this.title + ' (' + this.year + ')';
}
};
bestMovie.logMe();
bestMovie.logFullTitle();
function Movie(title, stars, date) {
this.title = title;
this.stars = stars;
if (date) this.year = date;
else this.year = (new Date()).getFullYear();
}M
ovie.prototype.about = function() {
return this.title + ', starring: ' + this.stars;
};
Movie.prototype.logMe = function() {
var me = this.about();
console.log(me);
};
var kungFuMovie = new Movie('Kung Fu Panda', ['Jack Black', 'Angelina Jolie'],
2008);
console.log(kungFuMovie.year);
kungFuMovie.logMe();
</script>
Código: Ready, Steady, Launch
Esta é a versão final do código do jogo do Capítulo 17 - Projeto: Ready, Steady,
Launch:
<body></body>
<script src="/three.js"></script>
<script src="/physi.js"></script>
<script src="/scoreboard.js"></script>
<script>
// Physics settings
Physijs.scripts.ammo = '/ammo.js';
Physijs.scripts.worker = '/physijs_worker.js';
// The "scene" is where stuff in our game will happen:
var scene = new Physijs.Scene({ fixedTimeStep: 2 / 60 });
scene.setGravity(new THREE.Vector3( 0, -100, 0 ));
var flat = {flatShading: true};
var light = new THREE.HemisphereLight('white', 'grey', 0.7);
scene.add(light);
// The "camera" is what sees the stuff:
var width = window.innerWidth,
height = window.innerHeight,
aspectRatio = width / height;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
// var camera = new THREE.OrthographicCamera(
// -width/2, width/2, height/2, -height/2, 1, 10000
// );
camera.position.z = 500;
camera.position.y = 200;
camera.lookAt(new THREE.Vector3(0,0,0));
scene.add(camera);
// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
document.body.style.backgroundColor = '#ffffff';
// ******** START CODING ON THE NEXT LINE ********function Launcher() {
this.angle = 0;
this.power = 0;
this.draw();
}L
auncher.prototype.draw = function() {
var direction = new THREE.Vector3(0, 1, 0);
var position = new THREE.Vector3(0, -100, 250);
var length = 100;
this.arrow = new THREE.ArrowHelper(
direction,
position,
length,
'yellow'
);
scene.add(this.arrow);
};
Launcher.prototype.vector = function() {
return new THREE.Vector3(
Math.sin(this.angle),
Math.cos(this.angle),
0
);
};
Launcher.prototype.moveLeft = function(){
this.angle = this.angle - Math.PI / 100;
this.arrow.setDirection(this.vector());
};
Launcher.prototype.moveRight = function(){
this.angle = this.angle + Math.PI / 100;
this.arrow.setDirection(this.vector());
};
Launcher.prototype.powerUp = function(){
if (this.power >= 100) return;
this.power = this.power + 5;
this.arrow.setLength(this.power);
};
Launcher.prototype.launch = function(){
var shape = new THREE.SphereGeometry(10);
var material = new THREE.MeshPhongMaterial({color: 'yellow'});
var ball = new Physijs.SphereMesh(shape, material, 1);
ball.name = 'Game Ball';
ball.position.set(0,0,300);
scene.add(ball);var speedVector = new THREE.Vector3(
2.5 * this.power * this.vector().x,
2.5 * this.power * this.vector().y,
-80
);
ball.setLinearVelocity(speedVector);
this.power = 0;
this.arrow.setLength(100);
};
function Basket(size, points) {
this.size = size;
this.points = points;
this.height = 100/Math.log10(size);
var r = Math.random;
this.color = new THREE.Color(r(), r(), r());
this.draw();
}B
asket.prototype.draw = function() {
var cover = new THREE.MeshPhongMaterial({
color: this.color,
shininess: 50,
specular: 'white'
});
var shape = new THREE.CubeGeometry(this.size, 1, this.size);
var goal = new Physijs.BoxMesh(shape, cover, 0);
goal.position.y = this.height / 100;
scene.add(goal);
var halfSize = this.size/2;
var halfHeight = this.height/2;
shape = new THREE.CubeGeometry(this.size, this.height, 1);
var side1 = new Physijs.BoxMesh(shape, cover, 0);
side1.position.set(0, halfHeight, halfSize);
scene.add(side1);
var side2 = new Physijs.BoxMesh(shape, cover, 0);
side2.position.set(0, halfHeight, -halfSize);
scene.add(side2);shape = new THREE.CubeGeometry(1, this.height, this.size);
var side3 = new Physijs.BoxMesh(shape, cover, 0);
side3.position.set(halfSize, halfHeight, 0);
scene.add(side3);
var side4 = new Physijs.BoxMesh(shape, cover, 0);
side4.position.set(-halfSize, halfHeight, 0);
scene.add(side4);
this.waitForScore(goal);
};
Basket.prototype.waitForScore = function(goal){
goal.addEventListener('collision', this.score.bind(this));
};
Basket.prototype.score = function(ball){
if (scoreboard.getTimeRemaining() == 0) return;
scoreboard.addPoints(this.points);
scene.remove(ball);
};
function Wind() {
this.draw();
this.change();
}W
ind.prototype.draw = function(){
var dir = new THREE.Vector3(1, 0, 0);
var start = new THREE.Vector3(0, 200, 250);
this.arrow = new THREE.ArrowHelper(dir, start, 1, 'lightblue');
scene.add(this.arrow);
};
Wind.prototype.change = function(){
if (Math.random() < 0.5) this.direction = -1;
else this.direction = 1;
this.strength = 20*Math.random();
this.arrow.setLength(5 * this.strength);
this.arrow.setDirection(this.vector());
setTimeout(this.change.bind(this), 10000);
};
Wind.prototype.vector = function(){
var x = this.direction * this.strength;
return new THREE.Vector3(x, 0, 0);
};};
var launcher = new Launcher();
var scoreboard = new Scoreboard();
scoreboard.countdown(60);
scoreboard.score(0);
scoreboard.help(
'Use right and left arrow keys to point the launcher. ' +
'Press and hold the down arrow key to power up the launcher. ' +
'Let go of the down arrow key to launch. ' +
'Watch out for the wind!!!'
);
scoreboard.onTimeExpired(timeExpired);
function timeExpired() {
scoreboard.message("Game Over!");
}v
ar goal1 = new Basket(200, 10);
var goal2 = new Basket(40, 100);
var wind = new Wind();
var light = new THREE.PointLight( 0xffffff, 1, 0 );
light.position.set(120, 150, -150);
scene.add(light);
function allBalls() {
var balls = [];
for (var i=0; i<scene.children.length; i++) {
if (scene.children[i].name.startsWith('Game Ball')) {
balls.push(scene.children[i]);
}
}r
eturn balls;
} //
Animate motion in the game
function animate() {
if (scoreboard.getTimeRemaining() == 0) return;
requestAnimationFrame(animate);
renderer.render(scene, camera);
}a
nimate();// Run physics
function gameStep() {
if (scoreboard.getTimeRemaining() == 0) return;
scene.simulate();
var balls = allBalls();
for (var i=0; i<balls.length; i++) {
balls[i].applyCentralForce(wind.vector());
if (balls[i].position.y < -100) scene.remove(balls[i]);
} //
Update physics 60 times a second so that motion is smooth
setTimeout(gameStep, 1000/60);
}g
ameStep();
function reset() {
if (scoreboard.getTimeRemaining() > 0) return;
scoreboard.score(0);
scoreboard.countdown(60);
var balls = allBalls();
for (var i=0; i<balls.length; i++) {
scene.remove(balls[i]);
}a
nimate();
gameStep();
}d
ocument.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
var code = event.code;
if (code == 'ArrowLeft') launcher.moveLeft();
if (code == 'ArrowRight') launcher.moveRight();
if (code == 'ArrowDown') launcher.powerUp();
if (code == 'KeyR') reset();
}d
ocument.addEventListener('keyup', sendKeyUp);
function sendKeyUp(event){
var code = event.code;if (code == 'ArrowDown') launcher.launch();
}
</script>
Código: Pronto para Dois Jogadores, Constante,
Inicializar
Esta é a versão final do código do jogo do Capítulo 18 - Projeto: Jogos para Dois
Jogadores:
<script src="/three.js"></script>
<script src="/physi.js"></script>
<script src="/scoreboard.js"></script>
<script>
// Physics settings
Physijs.scripts.ammo = '/ammo.js';
Physijs.scripts.worker = '/physijs_worker.js';
// The "scene" is where stuff in our game will happen:
var scene = new Physijs.Scene({ fixedTimeStep: 2 / 60 });
scene.setGravity(new THREE.Vector3( 0, -100, 0 ));
var flat = {flatShading: true};
var light = new THREE.HemisphereLight('white', 'grey', 0.7);
scene.add(light);
// The "camera" is what sees the stuff:
var width = window.innerWidth,
height = window.innerHeight,
aspectRatio = width / height;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
// var camera = new THREE.OrthographicCamera(
// -width/2, width/2, height/2, -height/2, 1, 10000
// );
camera.position.z = 500;
camera.position.y = 200;
camera.lookAt(new THREE.Vector3(0,0,0));
scene.add(camera);
// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
document.body.style.backgroundColor = '#ffffff';
// ******** START CODING ON THE NEXT LINE ********
function Launcher(location) {function Launcher(location) {
this.location = location;
this.color = 'yellow';
if (location == 'right') this.color = 'lightblue';
this.angle = 0;
this.power = 0;
this.draw();
this.keepScore();
}L
auncher.prototype.draw = function() {
var direction = new THREE.Vector3(0, 1, 0);
var x = 0;
if (this.location == 'left') x = -100;
if (this.location == 'right') x = 100;
var position = new THREE.Vector3( x, -100, 250 );
var length = 100;
this.arrow = new THREE.ArrowHelper(
direction,
position,
length,
this.color
);
scene.add(this.arrow);
};
Launcher.prototype.vector = function() {
return new THREE.Vector3(
Math.sin(this.angle),
Math.cos(this.angle),
0
);
};
Launcher.prototype.moveLeft = function(){
this.angle = this.angle - Math.PI / 100;
this.arrow.setDirection(this.vector());
};
Launcher.prototype.moveRight = function(){
this.angle = this.angle + Math.PI / 100;
this.arrow.setDirection(this.vector());
};
Launcher.prototype.powerUp = function(){
if (this.power >= 100) return;
this.power = this.power + 5;
this.arrow.setLength(this.power);
};
Launcher.prototype.launch = function(){var shape = new THREE.SphereGeometry(10);
var material = new THREE.MeshPhongMaterial({color: this.color});
var ball = new Physijs.SphereMesh(shape, material, 1);
ball.name = 'Game Ball';
ball.scoreboard = this.scoreboard;
var p = this.arrow.position;
ball.position.set(p.x, p.y, p.z);
scene.add(ball);
var speedVector = new THREE.Vector3(
2.5 this.power this.vector().x,
2.5 this.power this.vector().y,
-80
);
ball.setLinearVelocity(speedVector);
this.power = 0;
this.arrow.setLength(100);
};
Launcher.prototype.keepScore = function(){
var scoreboard = new Scoreboard('top' + this.location);
scoreboard.countdown(60);
scoreboard.score(0);
var moveKeys;
if (this.location == 'left') moveKeys = 'A and D';
if (this.location == 'right') moveKeys = 'J and L';
var launchKeys;
if (this.location == 'left') launchKey = 'S';
if (this.location == 'right') launchKey = 'K';
scoreboard.help(
'Use the ' + moveKeys + ' keys to point the launcher. ' +
'Press and hold the ' + launchKey + ' key to power up the launcher. ' +
'Let go of the ' + launchKey + ' key to launch. ' +
'Watch out for the wind!!!'
);
scoreboard.onTimeExpired(timeExpired);
function timeExpired() {
scoreboard.message("Game Over!");
}t
his.scoreboard = scoreboard;
};
Launcher.prototype.reset = function(){Launcher.prototype.reset = function(){
var scoreboard = this.scoreboard;
if (scoreboard.getTimeRemaining() > 0) return;
scoreboard.score(0);
scoreboard.countdown(60);
};
function Basket(size, points) {
this.size = size;
this.points = points;
this.height = 100/Math.log10(size);
var r = Math.random;
this.color = new THREE.Color(r(), r(), r());
this.draw();
}B
asket.prototype.draw = function() {
var cover = new THREE.MeshPhongMaterial({
color: this.color,
shininess: 50,
specular: 'white'
});
var shape = new THREE.CubeGeometry(this.size, 1, this.size);
var goal = new Physijs.BoxMesh(shape, cover, 0);
goal.position.y = this.height / 100;
scene.add(goal);
var halfSize = this.size/2;
var halfHeight = this.height/2;
shape = new THREE.CubeGeometry(this.size, this.height, 1);
var side1 = new Physijs.BoxMesh(shape, cover, 0);
side1.position.set(0, halfHeight, halfSize);
scene.add(side1);
var side2 = new Physijs.BoxMesh(shape, cover, 0);
side2.position.set(0, halfHeight, -halfSize);
scene.add(side2);
shape = new THREE.CubeGeometry(1, this.height, this.size);
var side3 = new Physijs.BoxMesh(shape, cover, 0);
side3.position.set(halfSize, halfHeight, 0);
scene.add(side3);scene.add(side3);
var side4 = new Physijs.BoxMesh(shape, cover, 0);
side4.position.set(-halfSize, halfHeight, 0);
scene.add(side4);
this.waitForScore(goal);
};
Basket.prototype.waitForScore = function(goal){
goal.addEventListener('collision', this.score.bind(this));
};
Basket.prototype.score = function(ball){
var scoreboard = ball.scoreboard;
if (scoreboard.getTimeRemaining() == 0) return;
scoreboard.addPoints(this.points);
scene.remove(ball);
};
function Wind() {
this.draw();
this.change();
}W
ind.prototype.draw = function(){
var dir = new THREE.Vector3(1, 0, 0);
var start = new THREE.Vector3(0, 200, 250);
this.arrow = new THREE.ArrowHelper(dir, start, 1, 'lightblue');
scene.add(this.arrow);
};
Wind.prototype.change = function(){
if (Math.random() < 0.5) this.direction = -1;
else this.direction = 1;
this.strength = 20*Math.random();
this.arrow.setLength(5 * this.strength);
this.arrow.setDirection(this.vector());
setTimeout(this.change.bind(this), 10000);
};
Wind.prototype.vector = function(){
var x = this.direction * this.strength;
return new THREE.Vector3(x, 0, 0);
};
var launcher1 = new Launcher('left');var launcher2 = new Launcher('right');
var scoreboard = launcher1.scoreboard;
var goal1 = new Basket(200, 10);
var goal2 = new Basket(40, 100);
var wind = new Wind();
var light = new THREE.PointLight( 0xffffff, 1, 0 );
light.position.set(120, 150, -150);
scene.add(light);
function allBalls() {
var balls = [];
for (var i=0; i<scene.children.length; i++) {
if (scene.children[i].name.startsWith('Game Ball')) {
balls.push(scene.children[i]);
}
}r
eturn balls;
}/
/ Animate motion in the game
function animate() {
if (scoreboard.getTimeRemaining() == 0) return;
requestAnimationFrame(animate);
renderer.render(scene, camera);
}a
nimate();
// Run physics
function gameStep() {
if (scoreboard.getTimeRemaining() == 0) return;
scene.simulate();
var balls = allBalls();
for (var i=0; i<balls.length; i++) {
balls[i].applyCentralForce(wind.vector());
if (balls[i].position.y < -100) scene.remove(balls[i]);
}/
/ Update physics 60 times a second so that motion is smooth
setTimeout(gameStep, 1000/60);} g
ameStep();
function reset() {
if (scoreboard.getTimeRemaining() > 0) return;
launcher1.reset();
launcher2.reset();
var balls = allBalls();
for (var i=0; i<balls.length; i++) {
scene.remove(balls[i]);
}a
nimate();
gameStep();
}v
ar powerUp1;
var powerUp2;
function powerUpLauncher1(){ launcher1.powerUp(); }
function powerUpLauncher2(){ launcher2.powerUp(); }
document.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
if (event.repeat) return;
var code = event.code;
if (code == 'KeyA') launcher1.moveLeft();
if (code == 'KeyD') launcher1.moveRight();
if (code == 'KeyS') {
clearInterval(powerUp1);
powerUp1 = setInterval(powerUpLauncher1, 20);
}i
f (code == 'KeyJ') launcher2.moveLeft();
if (code == 'KeyL') launcher2.moveRight();
if (code == 'KeyK') {
clearInterval(powerUp2);
powerUp2 = setInterval(powerUpLauncher2, 20);
}i
f (code == 'KeyR') reset();
}document.addEventListener('keyup', sendKeyUp);
function sendKeyUp(event){
var code = event.code;
if (code == 'KeyS') {
launcher1.launch();
clearInterval(powerUp1);
}i
f (code == 'KeyK') {
launcher2.launch();
clearInterval(powerUp2);
}
}
</script>
Código: River Rafter
Esta é a versão final do código do jogo do Capítulo 19 - Projeto: River Rafter. É
muito longo. Inclui alguns extras para brincar também.
<body></body>
<script src="/three.js"></script>
<script src="/physi.js"></script>
<script src="controlsOrbitControls.js"></script>
<script src="/scoreboard.js"></script>
<script src="/noise.js"></script>
<script>
// Physics settings
Physijs.scripts.ammo = '/ammo.js';
Physijs.scripts.worker = '/physijs_worker.js';
// The "scene" is where stuff in our game will happen:
var scene = new Physijs.Scene();
scene.setGravity(new THREE.Vector3( 0, -10, 0 ));
var flat = {flatShading: true};
var light = new THREE.AmbientLight('white', 0.2);
scene.add(light);
var sunlight = new THREE.DirectionalLight('white', 0.8);
sunlight.position.set(4, 6, 0);
sunlight.castShadow = true;
scene.add(sunlight);
var d = 10;
sunlight.shadow.camera.left = -d;
sunlight.shadow.camera.right = d;
sunlight.shadow.camera.top = d;
sunlight.shadow.camera.bottom = -d;
// The "camera" is what sees the stuff:
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 100);
camera.position.set(-8, 8, 8);
scene.add(camera);
// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setClearColor('skyblue');
renderer.shadowMap.enabled = true;renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// new THREE.OrbitControls(camera, renderer.domElement);
// ******** START CODING ON THE NEXT LINE ********
var gameOver;
var ground = addGround();
var water = addWater();
var scoreboard = addScoreboard();
var raft = addRaft();
reset();
function addGround() {
var faces = 99;
var shape = new THREE.PlaneGeometry(10, 20, faces, faces);
var riverPoints = [];
var numVertices = shape.vertices.length;
var noiseMaker = new SimplexNoise();
for (var i=0; i<numVertices; i++) {
var vertex = shape.vertices[i];
var noise = 0.25 * noiseMaker.noise(vertex.x, vertex.y);
vertex.z = noise;
}f
or (var j=50; j<numVertices; j+=100) {
var curve = 20 * Math.sin(7*Math.PI * j/numVertices);
var riverCenter = j + Math.floor(curve);
riverPoints.push(shape.vertices[riverCenter]);
for (var k=-20; k<20; k++) {
shape.vertices[riverCenter + k].z = -1;
}
}s
hape.computeFaceNormals();
shape.computeVertexNormals();
var _cover = new THREE.MeshPhongMaterial({color: 'green', shininess: 0});
var cover = Physijs.createMaterial(_cover, 0.8, 0.1);
var mesh = new Physijs.HeightfieldMesh(shape, cover, 0);mesh.rotation.set(-0.475 *
Math.PI, 0, 0);
mesh.receiveShadow = true;
mesh.castShadow = true;
mesh.riverPoints = riverPoints;
scene.add(mesh);
return mesh;
}f
unction addWater() {
var shape = new THREE.CubeGeometry(10, 20, 1);
var _cover = new THREE.MeshPhongMaterial({color: 'blue'});
var cover = Physijs.createMaterial(_cover, 0, 0.6);
var mesh = new Physijs.ConvexMesh(shape, cover, 0);
mesh.rotation.set(-0.475 * Math.PI, 0, 0);
mesh.position.y = -0.8;
mesh.receiveShadow = true;
scene.add(mesh);
return mesh;
}f
unction addScoreboard() {
var scoreboard = new Scoreboard();
scoreboard.score(0);
scoreboard.timer();
scoreboard.help(
'left / right arrow keys to turn. ' +
'space bar to move forward. ' +
'R to restart.'
);
return scoreboard;
}f
unction addRaft() {
var shape = new THREE.TorusGeometry(0.1, 0.05, 8, 20);
var _cover = new THREE.MeshPhongMaterial({visible: false});
var cover = Physijs.createMaterial(_cover, 0.4, 0.6);
var mesh = new Physijs.ConvexMesh(shape, cover, 0.25);
mesh.rotation.x = -Math.PI/2;
cover = new THREE.MeshPhongMaterial({color: 'orange'});
var tube = new THREE.Mesh(shape, cover);tube.position.z = -0.08;
tube.castShadow = true;
mesh.add(tube);
mesh.tube = tube;
shape = new THREE.SphereGeometry(0.02);
cover = new THREE.MeshBasicMaterial({color: 'white'});
var rudder = new THREE.Mesh(shape, cover);
rudder.position.set(0.15, 0, 0);
tube.add(rudder);
scene.add(mesh);
mesh.setAngularFactor(new THREE.Vector3(0, 0, 0));
return mesh;
}f
unction reset() {
resetPowerUps();
camera.position.set(0,-1,2);
camera.lookAt(new THREE.Vector3(0, 0, 0));
raft.add(camera);
scoreboard.message('');
scoreboard.resetTimer();
scoreboard.score(0);
raft.__dirtyPosition = true;
raft.position.set(0.75, 2, -9.6);
raft.setLinearVelocity(new THREE.Vector3(0, 0, 0));
gameOver = false;
animate();
scene.onSimulationResume();
gameStep();
}f
unction resetPowerUps() {
removeOldPowerUps();
var random20 = 20 + Math.floor(10*Math.random());
var p20 = ground.riverPoints[random20];
addPowerUp(p20);
var random70 = 70 + Math.floor(10*Math.random());var random70 = 70 +
Math.floor(10*Math.random());
var p70 = ground.riverPoints[random70];
addPowerUp(p70);
}f
unction addPowerUp(riverPoint) {
ground.updateMatrixWorld();
var x = riverPoint.x + 4 * (Math.random() - 0.5);
var y = riverPoint.y;
var z = -0.5;
var p = new THREE.Vector3(x, y, z);
ground.localToWorld(p);
var shape = new THREE.SphereGeometry(0.25, 25, 18);
var cover = new THREE.MeshNormalMaterial();
var mesh = new Physijs.SphereMesh(shape, cover, 0);
mesh.position.copy(p);
mesh.powerUp = true;
scene.add(mesh);
mesh.addEventListener('collision', function() {
for (var i=0; i<scene.children.length; i++) {
var obj = scene.children[i];
if (obj == mesh) scene.remove(obj);
}s
coreboard.addPoints(200);
scoreboard.message('Yum!');
setTimeout(function() {scoreboard.clearMessage();}, 5*1000);
});
return mesh;
}f
unction removeOldPowerUps() {
var last = scene.children.length - 1;
for (var i=last; i>=0; i--) {
var obj = scene.children[i];
if (obj.powerUp) scene.remove(obj);
}
}/
/ Animate motion in the game
function animate() {
if (gameOver) return;
requestAnimationFrame(animate);
renderer.render(scene, camera);renderer.render(scene, camera);
}/
/ animate();
// Run physics
function gameStep() {
if (gameOver) return;
checkForGameOver();
scene.simulate();
// Update physics 60 times a second so that motion is smooth
setTimeout(gameStep, 1000/60);
}/
/ gameStep();
function checkForGameOver() {
if (raft.position.z > 9.8) {
gameOver = true;
scoreboard.stopTimer();
scoreboard.message("You made it!");
}i
f (scoreboard.getTime() > 60) {
gameOver = true;
scoreboard.stopTimer();
scoreboard.message("Time's up. Too slow :(");
}
if (gameOver) {
var score = Math.floor(61-scoreboard.getTime());
scoreboard.addPoints(score);
if (scoreboard.getTime() < 40) scoreboard.addPoints(100);
if (scoreboard.getTime() < 30) scoreboard.addPoints(200);
if (scoreboard.getTime() < 20) scoreboard.addPoints(500);
}
}d
ocument.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
var code = event.code;
if (code == 'ArrowLeft') rotateRaft(1);
if (code == 'ArrowRight') rotateRaft(-1);
if (code == 'ArrowDown') pushRaft();
if (code == 'Space') pushRaft();
if (code == 'KeyR') reset();} f
unction rotateRaft(direction) {
raft.tube.rotation.z = raft.tube.rotation.z + direction * Math.PI/10;
}f
unction pushRaft() {
var angle = raft.tube.rotation.z;
var force = new THREE.Vector3(Math.cos(angle), 0, -Math.sin(angle));
raft.applyCentralForce(force);
}
</script>
Código: Obtendo Código na Web
Este é o código usado para criar a postagem do Blogger do Capítulo 20 - Obtendo
Código na Web. Você pode encontrar a postagem completa do Blogger em:
http://code3Dgames.blogspot.com/2018/02/amazing-3d-animation.html.
Este apêndice contém uma lista das coleções de código JavaScript usadas neste
livro e detalhes de como encontrar mais informações sobre cada uma. Todas as
bibliotecas de código usadas neste livro são gratuitas e de código aberto, o que
significa que as pessoas que escreveram o código querem que você o use como
quiser. A única regra é que você deve dar às pessoas crédito por seu trabalho -
como fazemos aqui!
Three.js
Three.js é a coleção de código principal usada ao longo deste livro. A página inicial
do projeto inclui muitas animações e exemplos interessantes, muitos dos quais você
pode experimentar no Editor de código 3DE. Estamos usando a versão 87 do
Three.js. A documentação detalhada para propriedades e métodos não discutidos
neste livro está disponível online.
http://threejs.org/
http://code3Dgames.com/docs/threejs/
Physijs
O mecanismo de física que usamos neste livro é Physijs. A página da Web do
Physijs inclui breves exemplos e alguns artigos introdutórios. O projeto Physijs não
tem tanta documentação quanto o projeto Three.js, mas existe alguma
documentação para o wiki do projeto. Estamos usando a versão do Physijs
compatível com Three.js r87. Uma vez que Physijs continua a crescer, o wiki pode
se referir a recursos mais novos do que aqueles suportados pela versão que
estamos usando.
http://chandlerprall.github.io/Physijs/
https://github.com/chandlerprall/Physijs/wiki
Controls
Os dois controles que usamos neste livro são os controles de voo e os controles de
órbita. Controles de voo Usamos FlyControls.js para voar pelo espaço no Capítulo
5 - Funções: Usar e Usar Novamente, e no Capítulo 13 - Projeto: Fases da Lua.
Podemos criar controles de voo após a câmera ser definida com isto:
controls.movementSpeed = 100;
controls.rollSpeed = 0.5;
controls.dragToLook = true;
controls.autoForward = false;
Orbit Controls
O outro tipo de controle é o OrbitControls.js, que nos permite girar a câmera em
torno do centro da cena. Usamos esses controles para ter uma visão melhor das
coisas conforme os construímos no Capítulo 12 - Trabalhando com Luzes e
Materiais e no Capítulo 19 - Projeto: River Rafter. Depois que a câmera e o
renderizador são definidos, podemos criar controles de órbita com o seguinte
Esses controles nos permitem clicar e arrastar a cena. Rolar com o mouse ou
touchpad aumentará e diminuirá o zoom. As setas do teclado se movem para cima,
para baixo, para a esquerda e para a direita.
Ruído (Noise)
Usamos o noise.js para criar terreno irregular no Capítulo 19 - Projeto: River
Rafter. A coleção de códigos de ruído é um código de bônus incluído em Three.js.
Você pode usá-lo em qualquer forma que desejar. Tudo o que temos que fazer é
contar o número de vértices na forma, criar um criador de ruído e então fazer um
loop sobre os vértices adicionando ruído a cada um. O exemplo da viga do rio é um
bom lugar para começar:
Você pode brincar com os números na linha que define o ruído. Tente aumentar e
diminuir o número 0,25 no início e os números 0,1 dentro do método noise ().
Scoreboard.js
A coleção de código Scoreboard.js é um código JavaScript simples que fornece os
fundamentos da pontuação em jogos. Suporta muito pouca configuração, mas
pretende ser fácil de usar para programadores.
A coleção de código Scoreboard.js suporta mensagens, texto de ajuda,
pontuação, um cronômetro decorrido e um cronômetro de contagem regressiva.
Cada um deles pode ser mostrado, oculto, redefinido e atualizado.
https://github.com/eee-c/scoreboard.js
https://eee-c.github.io/ShaderParticleEngine/
Bibliografia
Douglas Adams. O Guia do Mochileiro das Galáxias. Ballantine Books, New York,
NY, 1995.
Obrigado!
Como você gostou deste livro? Por favor nos informe. Reserve um momento para
nos enviar um e-mail para support@pragprog.com com seus comentários. Conte-
nos sua história e você pode ganhar e-books grátis. Use a linha de assunto
“Comentários sobre o livro”. Pronto para o seu próximo grande livro Pragmatic
Bookshelf? Venha para https://pragprog.com e use o código de cupom
BUYANOTHER2018 para economizar 30% em seu próximo e-book. Nulo onde for
proibido, restrito ou indesejado. Não use e-books perto da água. Se a erupção
persistir, consulte um médico. Não se aplica ao e-book The Pragmatic Programmer
porque é mais antigo do que a própria Pragmatic Bookshelf. Os efeitos colaterais
podem incluir maior conhecimento e habilidade, maior comercialização e profunda
satisfação. Aumente a dosagem regularmente.
E obrigado por seu apoio contínuo,
Andy Hunt, editor
TUDO DAQUI PRA FRENTE NÃO FAZ PARTE DO LIVRO
CONSTRUINDO O GAME
UM PASSO A PASSO DAS ETAPAS DE CRIAÇÃO DOS ELEMENTOS:
AVATAR E CENÁRIO COM FLORESTA
Código:
Isto cria o corpo, sem pés e sem mãos, do avatar e o inclui na cena para que
seja visto. Logo a seguir removemos o argumento flat que consta na
instrução da 2.ª linha, resultando em:
Código:
Código:
Código:
O erro é que a função animate faz só o avatar girar e, por enquanto, o avatar
só tem corpo, as mãos e os pés não foram adicionados a ele e sim à cena
(scene). Então vamos adicioná-los ao avatar. É só alterar as linhas onde tem
add nos códigos das mãos e pés e mudar scene por avatar.
Fica assim:
avatar.add(rightHand);
avatar.add(leftHand);
avatar.add (rightHand);
avatar.add (leftHand);
Agora tudo está girando pois tudo faz parte, agora, do avatar.
Mas ele vai ficar girando pra sempre?
Mas o que queremos não é saber qual a tecla pressionada e sim controlar o
movimento do avatar com o teclado. Para isso, substitua o comando:
alert(event.code) – que não é o que queremos – por:
if (isFlipping) {
avatar.rotation.x = avatar.rotation.x + 0.05;
}.
Vamos, a seguir, limitar o movimento do avatar de forma a evitar que ele saia
da tela (da cena) quando se movimentar com o uso das teclas de direção.
Mas, antes, vamos criar uma floresta para compor a cena com o avatar.
Agora, vamos ver como evitar que o avatar saia da cena quando se move
para os lados. O que acontece, na verdade, é que quando o deslocamento do
avatar é muito grande, ele sai do campo de visão da câmera que se encontra
adicionada à cena. Como se constata no código que cria a câmera, a mesma
foi anexada à cena - scene.add(camera) – e fica estática.
Para solucionar isso basta fazer com que a câmera se movimente junto com
o avatar, então, é adicionar a câmera ao avatar em vez da cena. Ficará
assim:
Esse marcador não é um ente geométrico com formas e cores, ele apenas
existe, mas não é visível. Isso se consegue por meio de um conceito em 3D
conhecido como: Object3D.
Agora o avatar pode andar, girar e fazer pirueta e a câmera vai acompanhar
tudo, ficando na posição vertical (não irá mais girar nem fazer as piruetas
junto com o avatar).
function acrobatics() {
if (isCartwheeling) {
avatar.rotation.z = avatar.rotation.z + 0.05;
}
if (isFlipping) {
avatar.rotation.x = avatar.rotation.x + 0.05;
}
}
Esta função contém parte da função animate (). Verifique que, no corpo das
duas funções, tem instruções iguais. Na verdade, estamos retirando e
transferindo as instruções if do corpo de animate () para o corpo de
acrobatics ().
Logo em seguida, incluímos a função acrobatics () dentro da função animate
()
function animate () {
requestAnimationFrame(animate);
acrobatics ();
renderer.render (scene, camera);
}
Animate ();
function acrobatics () {
if (isCartwheeling) {
avatar.rotation.z = avatar.rotation.z + 0.05;
}
if (isFlipping) {
avatar.rotation.x = avatar.rotation.x + 0.05;
}
}
Agora, vamos incluir a linha: var clock = new THREE.Clock (); logo após as
quatro linhas que criam o leftFoot - abaixo da linha: marker.add(camera); e
acima da linha: var isCartwheeling = false;
Fica assim:
marker.add(camera);
Incluir, também, a linha: walk (); logo acima da linha acrobatics (); que fica
dentro da function animate ();
Function walk () {
var speed = 10;
var size = 100;
var time = clock.getElapsedTime ();
var position = Math.sin (speed* time)* size;
rightHand.position.z = position;
leftHand.position.z = position;
rightFoot.position.z = position;
leftFoot.position.z = position;
Para o efeito ser real, basta colocar um sinal negativo antes do position de
uma das mãos e um no position do pé oposto.
Function isWalking () {
if (isMovingRight) return true;
if (isMovingLeft) return true;
if (isMovingForward) return true;
if (isMovingBack) return true;
return false;
}
Esta função é que vai dizer “está andando” para a direção definida pela tecla
que estiver sendo pressionada. Mas para mandar esse aviso a função
isWalking (); precisa retornar um valor = true (verdadeiro) que só
acontecerá se uma tecla de direção estiver sendo pressionada (é o que diz
as instruções dentro do corpo da função) caso contrário, retornará false (ou
seja, não está andando).
Adicionar a linha: if(!isWalking ()) return; logo após a linha: function walk()
{
function sendKeyDown(event) {
var code = event.code;
if (code == 'ArrowLeft') {
marker.position.x = marker.position.x-5;
isMovingLeft = true;
}
if (code == 'ArrowRight') {
marker.position.x = marker.position.x+5;
isMovingRight = true;
}
if (code == 'ArrowUp') {
marker.position.z = marker.position.z-5;
isMovingForward = true;
}
if (code == 'ArrowDown') {
marker.position.z = marker.position.z+5;
isMovingBack = true;
}
if (code == 'KeyC') isCartwheeling = !isCartwheeling;
if (code == 'KeyF') isFlipping = !isFlipping;
});
Falta, ainda, algo nos movimentos de andar, tanto para a direita como para a
esquerda. Notamos que o avatar fica de frente, quando o correto seria ele
virar o corpo para a direção da caminhada. Vamos tratar disso a seguir.
var direction;
var lastDirection;
Aqui, direction é para que o nosso código saiba pra onde virar e
lastDirection para que o código não possa fazer nada quando o avatar já
estiver virado para o lado certo.
Agora, vamos criar a função que vai virar o corpo do avatar: function turn ()
Código da função:
function turn () {
if (isMovingRight) direction = Math.PI/2;
if (isMovingLeft) direction = -Math.PI/2;
if (isMovingForward) direction = Math.PI;
if (isMovingBack) direction = 0;
if (!isWalking ()) direction = 0;
avatar.rotation.y = direction;
}
Inclua esta função (função turn ()) abaixo da função animate ().
Por fim, vamos chamar a função turn () dentro da função animate (). Basta
incluir turn (); dentro do corpo da função animate () logo abaixo da linha:
requestAnimationFrame (animate);
1.8 -