Computing">
Nothing Special   »   [go: up one dir, main page]

Criar Jogos 3D em Javascript

Fazer download em docx, pdf ou txt
Fazer download em docx, pdf ou txt
Você está na página 1de 391

Como você pode aprender a programar

Todo mundo é diferente.


Todo mundo aprende de maneira diferente. Por esse motivo, há pelo menos três
maneiras diferentes de aprender com este livro:
1. Brinque com coisas legais e leia um capítulo ocasional sobre fundamentos.
2. Aprenda o básico e faça algumas coisas legais com o que você sabe.
3. Basta digitar o código (como eu fazia quando criança).
Você escolhe o que funciona melhor para você! Se você deseja jogar primeiro (a
primeira opção), comece com o Capítulo 1 e siga em ordem pelo resto do livro. Você
trabalhará principalmente em jogos e simulações em capítulos de projetos - capítulos
cujos títulos começam com a palavra "Projeto" - seguidos por capítulos ocasionais
sobre habilidades fundamentais. Se você não tiver certeza de qual opção é a melhor,
escolha esta primeira. É assim que eu gostaria de ter aprendido.
Se você é do tipo de pessoa que gosta de entender os fundamentos antes de criar
coisas maiores (a segunda opção), leia primeiro os capítulos "essenciais" - qualquer
capítulo sem "Projeto" no título. Ainda há bastante codificação neles e diversão em
3D em alguns deles. Comparado a outras linguagens de programação, o núcleo do
JavaScript é bastante pequeno. Você pode aprender de 80 a 90% do núcleo do
JavaScript apenas lendo esses capítulos. Ser capaz de usá-lo bem é outra coisa - é
aí que os capítulos do “Projeto” entram!
Se você deseja apenas codificar (a terceira opção), mude para o Apêndice 1,
Código do projeto. Todo o código de todos os jogos está lá. Se você se prender a
parte da codificação, passe para o capítulo de onde o código veio para obter uma
explicação mais profunda. Foi assim que aprendi e acabei bem!
Não importa qual opção é melhor para você, existe uma regra: sempre digite o
código manualmente. Isso é muito mais lento que copiar e colar. É muito mais
provável que você cometa erros ao digitar o código em si mesmo.

Ir devagar e cometer erros é o ponto!

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.

Não pule o capítulo 2!


A solução de problemas é muito importante, portanto, leia o Capítulo 2. Não
importa como você aprenda, as habilidades de depuração descritas no Capítulo 2
devem ser lidas. Pode parecer que você pode pular essas coisas, mas pular esse
capítulo é como praticar esportes sem almofadas ou dirigir sem cinto de segurança.
As coisas podem parecer boas sem a segurança das habilidades de depuração. Até
o ponto em que algo der realmente errado.
É 100% bom se você não conseguir descobrir um problema sozinho. Fico feliz em
ajudar. Este livro tem sua própria página da web, onde você pode encontrar todo o
código desse livro, bem como o fórum da comunidade do livro.
[1] Se você ficar preso, poste uma pergunta no fórum. Eu quase sempre respondo
em 24 horas. Apenas me diga como você já tentou resolver o problema. Caso
contrário, essa será minha primeira pergunta para você. Mas por favor, quero que
você faça bem. Pergunte. Use o fórum.
Vamos codificar!

O que você precisa para este livro


Você precisará do navegador Google Chrome e de um computador relativamente
novo. É isso aí!
Nem todos os navegadores da web podem gerar objetos interessantes para jogos
em 3D que criaremos neste livro. Para aproveitar ao máximo o livro, você deve
instalar o navegador Google Chrome no seu computador.
[2] Outros navegadores da web funcionarão, mas alguns dos exercícios deste livro
contam com recursos disponíveis apenas no Google Chrome. Qualquer computador
moderno (dos últimos cinco anos) com o Google Chrome instalado será suficiente.
Para ter certeza, você pode testar os recursos do seu computador visitando o site
Get WebGL.
[3] Se esse site não informar que seu navegador oferece suporte ao WebGL,
talvez você precise tentar outro computador.

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.

A quem este livro não é indicado.


Apenas para esclarecer: não nos tornaremos especialistas em JavaScript.
E ... não vamos nos tornar especialistas em programação de jogos em 3D.
Abordaremos uma tonelada deste livro, mas não estaremos prontos para criar o
próximo Facebook quando terminarmos.
E não poderemos recriar o MarioKart. Ambos exigem centenas de programadores
trabalhando por centenas de horas - geralmente com habilidades mais avançadas do
que abordaremos neste livro. Mas aprenderemos as partes mais importantes do
JavaScript. E aprenderemos muitas habilidades em 3D. Podemos fazer coisas
incríveis com essas habilidades. E estaremos prontos para começar com coisas
ainda maiores e ainda mais incríveis.

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/

Quando terminar este capítulo, você poderá:


Escrever código!
Saber como criar formas 3D!
Ser capaz de programar JavaScript simples!
Fazer sua primeira animação!
Capítulo 1
Projeto: Criando formas simples
Neste capítulo, vamos direto à escrita do código do computador.
Haverá tempo de sobra para descrever o JavaScript, a linguagem de programação
que estamos usando. Também há muito tempo para conversar sobre o que é uma
linguagem de programação. Por enquanto, programamos - escrevemos código - sem
nos preocuparmos muito com detalhes.
A ideia é lhe familiarizar com a digitação do código e lhe dar uma ideia do que é
programar. Acredite ou não, apenas fazendo isso, você aprenderá muita coisa sobre
programação. Você dará seus primeiros passos na construção de mundos 3D. E
você até começará sua primeira animação.
A coisa mais importante que você fará neste capítulo é jogar. Os melhores
programadores brincam com seu código. Eles experimentam. Eles alteram o código
para ver o que acontece. Eles ajustam as coisas para ultrapassar os limites. Eles
quebram coisas. Realmente!
Então, vamos direto à quebra - e à criação - juntos.

Programando com o 3DE Code Editor


Neste livro, usamos o 3DE Code Editor, ou 3DE, para abreviar, para fazer nossa
programação. Alguns dos muitos recursos importantes do 3DE são:
Ele roda diretamente no seu navegador.
Permite digitar nosso código de programação e ver os resultados imediatamente.
Seu trabalho é salvo automaticamente toda vez que você digita - você também
pode salvar no menu, se desejar.
Você pode baixar projetos para uso em outros sites (falaremos sobre isso no
Capítulo 20, como obter código na Web).
Você pode exportar todos os projetos para backups ou carregá-los em outro
computador.
Ele roda offline. Após a primeira visita, o 3DE é armazenado no seu navegador
para que você possa continuar trabalhando mesmo se não estiver conectado à
Internet!
Lembre-se de que você precisará usar o navegador Google Chrome para este
livro. Embora a maioria dos exercícios funcione em outros navegadores, é mais fácil
usar um único navegador para todos os nossos projetos, especialmente quando
você deseja fazer perguntas no fórum do livro.
[4] Para começar, abra o 3DE Code Editor [5] usando o Chrome. Deve ser algo
como isto:

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);

Quando você terminar de digitar isso, verá algo legal:

A bola que digitamos - a bola que programamos - apareceu no 3DE. Parabéns!


Você acabou de escrever seu primeiro programa JavaScript!

Se isso não funcionou, verifique novamente se você digitou tudo exatamente


como mostrado. Preste muita atenção à capitalização, pois essa é uma causa
comum de problemas. Se isso ainda não funcionar, vá para o Capítulo 2, Depuração:
corrigindo código quando ocorrerem problemas (Chapter 2, Debugging: Fixing Code
When
Things Go Wrong) para as etapas de solução de problemas. E se isso não
funcionar, pergunte no fórum de livros on-line.
Vamos dar uma olhada rápida na programação 3D que acabamos de fazer.
Coisas 3D são construídas a partir de duas partes: a shape (forma) e cover (algo
que cobre a forma). A combinação dessas duas coisas, a shape (forma) e cover (a
capa), recebe um nome especial na programação 3D: mesh (malha).
Mesh (Malha) é uma palavra chique para uma coisa 3D. As malhas precisam de
formas (às vezes chamadas de geometria) e algo para cobri-las (às vezes chamadas
de materiais). Neste capítulo, veremos diferentes formas. Não vamos lidar com
capas diferentes para as nossas formas até mais tarde.
Quando temos uma malha, nós a adicionamos à cena (scene). A scene (cena) é
onde a mágica acontece na programação 3D. É o mundo em que tudo acontece.
Nesse caso, é onde nossa bola está saindo, esperando por alguns amigos. Vamos
adicionar outras formas à cena para que a bola não fique sozinha.

Criando formas com JavaScript


Encontramos uma forma: a esfera. Muitas formas estão disponíveis para
programadores em 3D. As formas podem ser simples, como cubos, pirâmides, cones
e esferas. As formas também podem ser mais complexas, como rostos ou carros.
Neste livro, ficaremos com formas simples. Quando construímos coisas como
árvores, combinamos formas simples, como esferas e cilindros, para fazê-las.
Então, vamos explorar essas formas ...

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?

» var shape = new THREE.SphereGeometry(250);


var cover = new THREE.MeshNormalMaterial(fla
var ball = new THREE.Mesh(shape, cover);
scene.add(ball);
Isso deve torná-la muito maior:

O que acontece se você mudar de 250 para 10?


Como você provavelmente adivinhou, fica muito menor. Então, essa é uma
maneira de controlar uma esfera. Qual é o outro caminho?

Not Chunky (não robusto): SphereGeometry(100, 20, 15


Se você clicar no Hide Code button (botão ocultar) no 3DE, poderá observar que
nossa esfera não é realmente uma bola suave:
NOTA: Você pode ocultar ou mostrar facilmente o código
Se você clicar no botão Hide Code (Ocultar código) no canto superior direito da
janela do 3DE, verá apenas a área de jogo e os objetos no jogo. É assim que você
jogará nos próximos capítulos. Para recuperar seu código, clique no botão Show
Code (Mostrar código) no 3DE Code Editor.

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:

» var shape = new THREE.SphereGeometry(100, 20, 15);


var cover = new THREE.MeshNormalMaterial(flat);
var ball = new THREE.Mesh(shape, cover);
scene.add(ball);

O primeiro número é o tamanho, o segundo número é o número de pedaços ao


redor da esfera e o terceiro número é o número de pedaços acima e abaixo da
esfera.
Isso deve tornar uma esfera muito mais suave:
O número de pedaços que obtemos sem que o SphereGeometry use mais pode
não parecer ótimo, mas não o altere, a menos que você precise. Quanto mais
pedaços estiverem em forma, mais difícil será o trabalho do computador para
desenhá-lo. Geralmente, é mais fácil para um computador tornar as coisas mais
fáceis escolhendo uma capa diferente para a forma.

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:

var shape = new THREE.SphereGeometry(100);


var cover = new THREE.MeshNormalMaterial(flat);
var ball = new THREE.Mesh(shape, cover);
scene.add(ball);
» ball.position.set(-250,250,-250);

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):

var shape = new THREE.CubeGeometry(100, 100, 100);


var cover = new THREE.MeshNormalMaterial(flat);
var box = new THREE.Mesh(shape, cover);
scene.add(box);

Se você tiver feito tudo correto, deverá ver… um quadrado?

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);

Esses três números abaixam a caixa no sentido anti-horário e esquerda-direita.


Nesse caso, giramos 0,5 para baixo e 0,5 para a direita:

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:

» var shape = new THREE.CubeGeometry(300, 100, 20);


var cover = new THREE.MeshNormalMaterial(flat);
var box = new THREE.Mesh(shape, cover);
scene.add(box);
box.rotation.set(0.5, 0.5, 0);

Isso deve produzir algo como isto:


Vamos brincar!
Reserve um tempo para experimentar os números dentro da CubeGeometry e os
números da rotação. Ambos demoram um pouco para se acostumar. E brincar com
eles é a melhor maneira de chegar lá!
Acredite ou não, agora você sabe muito sobre JavaScript e programação 3D.
Ainda há muito a aprender, é claro, mas você já pode fazer bolas e caixas. Você já
pode movê-los e transformá-los. E você só tinha que escrever dez linhas de
JavaScript para fazer tudo - legal!
Vamos mudar nossa caixa para que possamos brincar com mais formas:

var shape = new THREE.CubeGeometry(300, 100, 20);


var cover = new THREE.MeshNormalMaterial(flat);
var box = new THREE.Mesh(shape, cover);
scene.add(box);
box.rotation.set(0.5, 0.5, 0);
» box.position.set(250, 250, -250);

Usando cilindros para todos os tipos de formas


Um cilindro, às vezes também chamado de tubo, é uma forma
surpreendentemente útil na programação 3D. Pense bem: os cilindros podem ser
usados como troncos de árvores, latas, rodas ... muitas coisas! Mas você sabia que
cilindros podem ser usados para criar cones, árvores sempre verdes e até
pirâmides? Vamos ver como!
Size: CylinderGeometry (20, 20, 100)
Abaixo do código da caixa, digite o seguinte para criar um cilindro (uma linha em
branco primeiro pode facilitar a leitura do código):
var shape = new THREE.CylinderGeometry(20, 20, 100);
var cover = new THREE.MeshNormalMaterial(flat);
var tube = new THREE.Mesh(shape, cover);
scene.add(tube);

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);

Ao fabricar um cilindro, os dois primeiros números descrevem o tamanho da parte


superior e inferior do cilindro. O último número é a altura do cilindro. Portanto, nosso
cilindro tem uma parte superior e inferior com 20 e 100 de altura.

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.

Pirâmides: CylinderGeometry (1, 100, 100, 4)


Você notou que os cilindros parecem grossos como as esferas? Não deve ser
surpresa, então, que você possa controlar a fragilidade dos cilindros. Se você definir
o número de pedaços para 20, por exemplo, com o disco, desta forma:

» var shape = new THREE.CylinderGeometry(100, 100, 20, 20);


var cover = new THREE.MeshNormalMaterial(flat);
var tube = new THREE.Mesh(shape, cover);
scene.add(tube);
tube.rotation.set(0.5, 0, 0);

Então você deve ver algo assim:


Assim como nas esferas, você deve usar muitos pedaços assim, somente quando
realmente precisar.
Você consegue pensar em como transformar isso em uma pirâmide? Você tem
todas as pistas que precisa.

Vamos brincar!
Brinque com números diferentes e veja o que você pode criar!

Você conseguiu descobrir?


Não se preocupe se você não conseguiu. A maneira como faremos isso é
realmente muito sorrateira. A resposta é que você precisa diminuir o número de
pedaços que você usa para fazer um cone. Se você definir a parte superior como 1,
a parte inferior como 100, a altura como 100 e o número de partes como 4, obterá o
seguinte:

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:

NOTA - Trapaceie sempre que possível


Você não deve trapacear na vida real, mas na programação - especialmente na
programação 3D - você deve sempre procurar maneiras mais fáceis de fazer as
coisas. Mesmo se houver uma maneira usual de fazer algo, pode haver uma
maneira melhor de fazê-lo.

Você está indo muito bem até agora. Mova o tubo para fora do centro, como
fizemos com o cubo e a esfera:

tube.position.set(250, -250, -250);


Agora vamos para as duas últimas formas deste capítulo.

Construindo superfícies planas com aviões


Um plane (plano) é uma superfície plana. Planes são especialmente úteis para o
solo, mas também podem ser úteis para marcar portas e arestas em nossos jogos.
PlaneGeometry (100, 100)
Como os planes são apenas quadrados, eles são muito mais simples que os
outros objetos que vimos até agora. Abaixo do código do seu cilindro (e uma linha
em branco), digite o seguinte:

var shape = new THREE.PlaneGeometry(100, 100);


var cover = new THREE.MeshNormalMaterial(flat);
var ground = new THREE.Mesh(shape, cover);
scene.add(ground);
ground.rotation.set(0.5, 0, 0);

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);

Agora vamos para a melhor forma do mundo.

Renderização de donuts (rosquinhas) com torus (toro)


Na linguagem de programação 3D, uma forma de donut (rosquinha) é chamada de
torus (toro). O torus mais simples que podemos criar precisa atribuir dois valores:
um para a distância do centro até a borda externa e outro para a espessura do tubo.
TorusGeometry (100, 25)
Digite o seguinte, abaixo do código do seu plane:
var shape = new THREE.TorusGeometry(100, 25);
var cover = new THREE.MeshNormalMaterial(flat);
var donut = new THREE.Mesh(shape, cover);
scene.add(donut);

Você deve ver uma rosquinha muito grossa:

Até agora você provavelmente já sabe como tornar a rosquinha menos grossa.

TorusGeometry (100, 25, 8, 25)


Como a esfera, a forma do donut é construída a partir de pedaços. Os pedaços
podem ser maiores ou menores ao redor do tubo interno, o que podemos definir
incluindo um terceiro número ao definir a TorusGeometry. Também podemos incluir
um quarto número para ajustar o tamanho dos pedaços ao redor da parte externa da
rosquinha. Tente experimentar números como o seguinte e veja o que acontece.

var shape = new THREE.TorusGeometry(100, 25, 8, 25);


var cover = new THREE.MeshNormalMaterial(flat);
var donut = new THREE.Mesh(shape, cover);
scene.add(donut);

TorusGeometry (100, 25, 8, 25, 3.14)


Podemos fazer outro truque com donuts (rosquinhas). Tente adicionar outro
número: 3,14 à forma TorusGeometry:

var shape = new THREE.TorusGeometry(100, 25, 8, 25, 3.14);


var cover = new THREE.MeshNormalMaterial(flat);
var donut = new THREE.Mesh(shape, cover);
scene.add(donut);

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).

O código até agora


Para facilitar um pouco as coisas, a versão completa deste projeto é incluída como
parte de Code: Creating Simple Shapes (código: Criando formas simples). Use esse
código para verificar seu trabalho com mais facilidade durante os exercícios, mas
tente não copiar e colar o código no 3DE. É impossível aprender e entender a
programação, a menos que você mesmo codifique.

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

Ao concluir este capítulo, você:


Reconhecerá erros do editor de código.
Saiba como procurar erros no console JavaScript do navegador.
Consiga corrigir projetos quando um programa congela.

Capítulo 2

Depuração: corrigindo código quando algo dá


errado
A programação é uma grande habilidade a ter. Ser capaz de corrigir programas
corrompidos é uma habilidade ainda melhor.
Quebras de código. O tempo todo. Às vezes, as pessoas com quem você trabalha
cometem erros - não importa quão boas sejam. Às vezes cometemos erros - não
importa quão cuidadosos sejamos. Erros acontecem e quebras de código. E não
importa quem o quebrou. O que realmente importa é quem vai consertar isso. Dica:
é você!
Portanto, neste capítulo, examinaremos algumas estratégias para nos ajudar a
consertar as coisas - para nos ajudar a depurar código quebrado. Às vezes, nosso
editor de código pode ajudar. Outras vezes, podemos usar o console JavaScript do
navegador. Às vezes até quebramos o navegador inteiro. Também vamos descobrir
como consertar isso.

A programação pode ser esmagadora


Às vezes, a programação pode fazer você querer jogar seu computador contra a
parede (não). Ao programar, lembre-se desses dois fatos:
Haverá coisas que você não sabe - tudo bem.
Seus programas serão interrompidos - tudo bem.
Lembre-se de que todos lutam com isso e você ficará bem.

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.

Deixe o modelo definido para o projeto inicial 3D.


Vamos começar quebrando coisas simples que nosso editor de código pode
encontrar para nós.

Depurando no 3DE: The Red X (o X vermelho )


Um X vermelho próximo ao seu código significa que o 3DE vê um problema que
interromperá a execução do código. Vamos escrever um JavaScript muito ruim para
ver essa ação. Digite a seguinte linha abaixo INICIAR O CODIFICAÇÃO NA
PRÓXIMA LINHA.
bad () javascript
Esse é um JavaScript ruim!
Você está se perguntando o porquê? É ruim porque você nunca deve ter uma
palavra que vem logo após parênteses em JavaScript. Se você escrever um código
como esse, o 3DE mostrará um X vermelho próximo à linha do problema, indicando
que a linha deve ser corrigida. Mova o ponteiro do mouse sobre o X vermelho para
exibir a mensagem de erro real, conforme mostrado na figura a seguir.
Nosso editor de código realmente não gostou do JavaScript! Veremos o que tudo
isso significa quando discutirmos o:
Capítulo 5, Funções: Use e use novamente e o
Capítulo 7, uma análise mais aprofundada dos fundamentos do JavaScript.
Apenas lembre-se de que é assim que o editor de código nos diz que existem
palavras após os parênteses quando não deveria haver.

Algumas coisas para verificar seu código quando você vê um X vermelho:


- Você esqueceu um ponto e vírgula? Está faltando outra pontuação no
JavaScript, como parênteses, colchetes ou aspas? Olhar em volta.
- Se você não encontrar um problema na linha X vermelha, observe também a
linha anterior. Se você não vê isso lá, veja a linha abaixo. O 3DE nem sempre pode
dizer onde o problema começa e pode estar desativado em uma ou duas linhas.

Depurando no 3DE: O triângulo amarelo


Ao contrário de um X vermelho, um triângulo amarelo que aparece à esquerda do
seu código não é um limitador de exibição. Seu código provavelmente será
executado mesmo se as linhas do seu código estiverem marcadas com triângulos
amarelos, mas pode não ser executado corretamente. É melhor se livrar desses
triângulos quando eles surgirem.
Vamos ver isso em ação escrevendo um pouco mais de JavaScript ruim (mas não
muito ruim). Primeiro, remova a linha do bad javascript da seção anterior. Em
seguida, adicione as seguintes linhas:

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.

» var favoriteFood = 'Cookie';


eat(favoriteFood);

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.

Abrindo e fechando o console JavaScript


Se o código não estiver funcionando conforme o esperado, um dos primeiros
locais a serem verificados é o console JavaScript do navegador. A maneira mais
rápida de fazer isso é com combinações especiais de teclado que somente
programadores JavaScript como nós conhecemos!
Em PCs e Chromebooks, Ctrl + Shift + J (mantendo pressionadas as teclas Ctrl,
Shift e J ao mesmo tempo) abrirá e fechará o console do JavaScript.

Se você estiver usando um computador Apple, poderá usar para


abrir e fechar o console.
Não se preocupe se você vir vários avisos e erros na primeira vez em que abrir o
console do JavaScript. Ele mantém um log de eventos que acontecem em uma
página da web ou no 3DE Code Editor. Se as mensagens forem demais, você pode
limpar o log com o botão que possui um círculo com uma linha e clicar no botão
UPDATE no 3DE para mostrar apenas as informações atuais do log. A mesma
combinação de teclas que abre o console do JavaScript irá fechá-lo (mas deixe-o
aberto neste capítulo).
Depurando no console
Depois de abrir o console, você verá uma mensagem de erro que eat (comer) não
está definida.

Não importa como o erro é formatado, o navegador vê um código incorreto e o


reporta no console JavaScript. Na linha 24 do nosso programa, pedimos ao
navegador para executar a eat function (função comer). O único problema é que
nunca dissemos ao navegador como fazer isso!
A formatação do erro pode mudar
Não se surpreenda se o erro do seu console não parecer exatamente como
mostrado. O formato do relatório pode mudar muito, mesmo para o mesmo código.
Às vezes, ele adiciona outro número à mensagem de erro. Como ‘24:3‘ em vez de
apenas ‘24‘.

Às vezes, adiciona linhas extras.

Às vezes, os erros são iniciados com "VM", seguido de números estranhos.

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‘).

Falaremos mais sobre as funções no Capítulo 5, Funções: Use e use novamente.


Por enquanto, basta saber que uma função é uma maneira de escrever código que
pode ser executado repetidas vezes. Para resolver esse problema, vamos dar ao
nosso programa JavaScript uma função eat ().

var favoriteFood = 'Cookie';


eat(favoriteFood);
» function eat(food) {
» console.log(food);
»}

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.

O utilitário console.log () é super útil


Os navegadores possuem algumas ferramentas de depuração bastante
sofisticadas. Às vezes, eles podem ser úteis - vale a pena aprender um dia, mas não
para este livro. Muitas vezes, a ferramenta de depuração mais útil é console.log ().
Quando o código se torna complexo, é fácil ficar confuso com os valores. Se você
nunca tiver certeza de um valor, envie-o para console.log () e verifique os
resultados 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.');
}
Graças a essa declaração console.log (), a mensagem “Cookie!!! Nom. Nom.
Nom.” deve estar no console.

Por que alguns erros ocorrem apenas na execução de código


Antes de um programa ser executado, os computadores leem o código e o
convertem em uma estrutura interna. Esse processo é chamado de compilação de
um programa.
Os editores de código podem compilar o código primeiro para ver se ele será
executado. Se não for compilado, eles poderão sinalizar esses erros de tempo de
compilação com triângulos vermelhos e amarelos. Os editores de código são bons
para encontrar esses erros de tempo de compilação.
Outros erros não ocorrerão até o código realmente ser executado. O código
compila OK, mas pode estar faltando algo (como uma função) que não pode ser
visto até que o código seja executado. Esses são erros de tempo de execução. O
console JavaScript nos ajuda a corrigir erros de tempo de execução.

Antes de encerrarmos este capítulo, vamos ver alguns erros de programação em


3D que você provavelmente se confrontará.

Erros comuns de programação em 3D


Mantenha o console JavaScript aberto para esta seção. Após a chave de
fechamento da função eat, adicione uma linha em branco e digite o código
destacado.

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.

Possível mensagem de erro - não há um construtor


Vamos dar uma olhada no que deu errado. No console do JavaScript, você verá
uma mensagem de erro.

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.

Os números de linha do console nem sempre são


exatos
O 3DE faz o possível para corrigir os números de linha no console e, algumas
vezes, ele consegue - pode estar correto, e outras - - pode até estar correto para
você agora. Outras vezes, porém, pode ser desativado em algumas linhas. Comece
observando o número exato da linha. Se isso não parece corresponder ao erro,
verifique as próximas linhas.

Portanto, se você encontrar esta mensagem no console do JavaScript, verifique


novamente a ortografia: “THREE”, shapes, and covers ("TRÊS" formas e capas).
Verifique também se as letras corretas estão em maiúsculas.

Possível mensagem de erro – Three (três) não está definido


Corrija a ortografia de SphereGeometry. Mesmo depois disso, a bola ainda não
aparece na tela. Outra coisa está errada com o nosso código. Olhando no console
JavaScript, você deve ver algo como o seguinte.

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.

Possível mensagem de erro - Não é uma função


Mesmo com esses dois problemas corrigidos, a esfera ainda não está visível e
temos outra mensagem de erro no console.

Nesse caso, informamos ao navegador que poderíamos usar o código chamado


ad. O navegador tentou, mas não conseguiu encontrar nada que funcionasse porque
pretendíamos usar add (), não ad (). Em outras palavras, não queremos colocar a
bola na tela; nós queremos add (adicioná-la).
Depois de fixar essa linha, você finalmente verá uma bola e a "Nom. Nom. Nom.”
mensagem aparece novamente no console do JavaScript.

Recuperando quando o 3DE está quebrado


Quebrar um navegador da Web é surpreendentemente fácil. É tão fácil e acontece
com tanta frequência que as pessoas inventaram muitas descrições para um
navegador quebrado: congelado, bloqueado, parado ou simplesmente quebrado. Se
você criar uma esfera com um milhão de pedaços, o navegador congelará
completamente. Se você criar um loop de código sem ponto de parada, o navegador
será bloqueado. Sem digitação. Sem rolagem. Nada.
Se o navegador estiver congelado, o 3DE Code Editor está quebrado, certo?
Bem, sim, mas há uma maneira fácil de corrigi-lo: adicione ?e ou ?edit-only ao
URL para ver http://code3Dgames.com/3de/?e. Este é edit-only (modo somente
de edição) para 3DE.
Corrija a última coisa que você digitou para quebrar o 3DE e remova o ponto de
interrogação somente para edição do URL para voltar a
http://code3Dgames.com/3de/. Agora você deve ver a visualização novamente.
Em alguns computadores, você pode achar que precisa fechar a guia ou a janela
do navegador antes de tentar isso. Em seguida, você pode abrir uma nova janela ou
guia, na qual é possível inserir o URL somente de edição do 3DE. Os Chromebooks
do Google, em particular, executam melhor o modo somente edição com este
procedimento.

O que vem a seguir


Você captou uma quantidade incrível de informações úteis neste capítulo. Os
melhores programadores são todos solucionadores de problemas criativos. O código
de depuração é uma grande parte da solução de problemas. Revise este capítulo de
tempos em tempos para se lembrar de como fazer isso.
Agora você já viu como usar os indicadores de aviso e erro do editor de código
para solucionar problemas de código. Você também aprendeu a usar o console
JavaScript para ver e corrigir erros que o editor não consegue encontrar. E não
subestime o poder do console.log () - pode ser uma grande ajuda. Agora você
conhece alguns dos erros mais comuns de programação em 3D que você pode
cometer. E se as coisas derem realmente errado, você pode se recuperar de um
navegador congelado. Agora que sabemos como fazer formas e onde verificar
quando as coisas dão errado, vamos começar nosso primeiro jogo criando nosso
próprio avatar.

Quando terminar este capítulo:


- Você poderá agrupar formas simples
- Criar seu próprio avatar no jogo
- Adicionar animações simples ao avatar

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.

A diferença entre um jogador e um avatar


Neste livro, usaremos a palavra player (jogador) para designar a pessoa que está
jogando o jogo. A palavra avatar será usada para descrever o que representa o
jogador dentro do jogo.

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).

var body = new THREE.SphereGeometry(100);


var cover = new THREE.MeshNormalMaterial(flat);
var avatar = new THREE.Mesh(body, cover);
scene.add(avatar);

Já sabemos o que acontece quando digitamos isso - temos uma bola no centro da
cena.

Antes de avançarmos, vamos remover flat da segunda linha.

var body = new THREE.SphereGeometry(100);


» var cover = new THREE.MeshNormalMaterial();
var avatar = new THREE.Mesh(body, cover);
scene.add(avatar);

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.

Fazendo um todo a partir de peças


Vamos adicionar uma mão ao lado do corpo. Adicione as seguintes linhas abaixo
do código que você já inseriu para criar o corpo.

var hand = new THREE.SphereGeometry(50);


var rightHand = new THREE.Mesh(hand, cover);
rightHand.position.set (-150, 0, 0);
scene.add(rightHand);
Observe que não criamos uma nova capa para a mão. Em vez disso, reutilizamos
a mesma capa, que chamamos de capa quando a usamos no corpo do avatar. Isso
economiza um pouco de digitação.
Menos digitação é uma coisa boa, já que somos todos programadores e
programadores que são preguiçosos no coração. Isso me lembra alguma sabedoria
de programação que vale a pena compartilhar:

Bons programadores são preguiçosos


Não quero dizer que programadores odeiam trabalhar. Na verdade, amamos
nosso trabalho e geralmente gastamos muito tempo trabalhando, porque o amamos
muito.
Não, o que quero dizer com preguiça é que odiamos fazer um trabalho em que os
computadores sejam melhores. Portanto, em vez de criar mãos e pés
individualmente, preferimos escrever uma única mão / pé e copiá-la quantas vezes
for necessário.

Ser preguiçoso nos beneficia de duas maneiras muito importantes:


- Digitamos menos. Acredite ou não, esta é uma grande vitória. Não apenas
precisamos digitar menos na primeira vez, mas também precisamos ler menos
quando queremos atualizar mais tarde.
- Se queremos mudar a maneira como um membro é criado, precisamos mudar
apenas uma coisa. Ou seja, se quisermos mudar a capa ou até a forma de uma mão
no futuro, teremos que fazer apenas uma alteração em um só lugar.

Então, vamos ver se podemos ficar ainda mais preguiçosos quando criamos a
mão esquerda para o nosso avatar:

var leftHand = new THREE.Mesh(hand, cover);


leftHand.position.set(150, 0, 0);
scene.add(leftHand);

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.

Leia esta seção! (em algum momento)


Se você estiver impaciente, vá para Adding Feet for Walking, onde você pode
continuar criando o avatar do jogo. Você ainda aprenderá bastante apenas
programando o restante do avatar. E não há problema em ficar impaciente para
continuar. Lembre-se de voltar aqui em algum momento para entender realmente os
números que estamos usando neste capítulo.

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:

var leftHand = new THREE.Mesh(hand, cover);


leftHand.position.set(150, 0, 0);
scene.add(leftHand)

Os números dentro de leftHand.position.set (150, 0, 0) são as posições X, Y e Z


da mão esquerda (Z seria para frente e para trás). Definimos X como 150, enquanto
Y e Z são ambos 0. Isso é realmente o mesmo que leftHand.position.x = 150.
Como veremos em breve, pode ser muito conveniente definir vários valores em
uma única linha.
Mas por que 150? A resposta é que o raio do corpo é 100 e o raio da mão é 50.
Precisamos mover a mão 100 + 50 ou 150 na direção X (esquerda / direita):

Se apenas movêssemos o centro da mão 100, acabaríamos com a mão


parcialmente dentro do corpo:
Vamos brincar!
Se você não está convencido, tente você mesmo. Mude se você não estiver
convencido, tente você mesmo. Altere o número da posição X, mexendo no primeiro
número em rightHand.position.set (-150, 0, 0). Experimente para as mãos
esquerda e direita. Porém, não as torne muito grandes ou elas nem estarão mais na
tela!

Adicionando pés para caminhar


Para os pés, usaremos novamente esferas de tamanho 50. Deixo para você
descobrir como adicionar as linhas de código relevantes.
Algumas dicas:
- Não mova os pés para a esquerda / direita, na medida em que fizemos as mãos.
Os pés devem estar embaixo do corpo.
- Você terá que movê-los para baixo. O posicionamento para cima / baixo é feito
com a direção Y em vez da direção X. Com leftHand.position.set (150, 0, 0),
definimos a posição X como 150. Também queremos alterar o segundo número.
Pode ser necessário usar números negativos para diminuir - por exemplo, –25.
- Lembre-se de que a mão foi adicionada antes de renderizarmos a cena - antes
da linha com renderer.render (scene, camera). Os pés devem ser adicionados
antes de renderizar a cena também.
Aqui está como fizemos a mão direita; isso pode ajudá-lo a descobrir os pés:
var hand = new THREE.SphereGeometry(50);
var rightHand = new THREE.Mesh(hand, cover);
rightHand.position.set(-150, 0, 0);
scene.add(rightHand);

Boa sorte! Vamos brincar!


Tente colocar os pés você mesmo. Para mover os pés para a esquerda e para a
direita, altere o primeiro número em rightFoot.position.set (0, 0, 0). Para movê-lo
para cima e para baixo, você altera o segundo número (o terceiro é para frente e
para trás). Pode demorar um pouco para acertar, mas acredite: é uma boa prática.
Tente um pouco e continue com o texto.

Você entendeu?

É assim que pode parecer:


não se preocupe se o seu não for exatamente o mesmo.
O seu pode até ser melhor! Se você estiver com dificuldades, consulte o código
que usamos para criar o avatar:
var body = new THREE.SphereGeometry(100);
var cover = new THREE.MeshNormalMaterial();
var avatar = new THREE.Mesh(body, cover);
scene.add(avatar);
var hand = new THREE.SphereGeometry(50);
var rightHand = new THREE.Mesh(hand, cover);
rightHand.position.set(-150, 0, 0);
scene.add(rightHand);
var leftHand = new THREE.Mesh(hand, cover);
leftHand.position.set(150, 0, 0);
scene.add(leftHand);
var foot = new THREE.SphereGeometry(50);
var rightFoot = new THREE.Mesh(foot, cover);
var rightFoot.position.set(-75, -125, 0);
scene.add(rightFoot);
var leftFoot = new THREE.Mesh(foot, cover);
leftFoot.position.set(75, -125, 0);
scene.add(leftFoot);

Isso é tudo após INICIAR O CODIFICAÇÃO NA PRÓXIMA LINHA.

Desafio: faça o seu próprio avatar


Se você está pronto para um desafio, veja se você pode criar um avatar que
pareça algo
COMO ISTO:
Para fazer isso, você precisa substituir o corpo por uma das formas do Capítulo 1,
Project: Creating Simple Shapes (Projeto: Criando formas simples), e adicionar uma
cabeça. Não se preocupe com braços e pernas para conectar as mãos e os pés ao
corpo - isso tornaria mais difícil nos próximos capítulos.
E, é claro, você pode criar qualquer tipo de avatar que desejar. Lembre-se de
fazer um com as mãos e os pés - precisaremos deles nos próximos capítulos.

Fazendo cartwheels (piruetas)


Nós adicionaremos controles ao nosso avatar mais tarde. Mas antes de prosseguir
para a próxima lição, vamos fazer o avatar dar alguns giros. Assim como fizemos no
final do Capítulo 1, Project: Creating Simple Shapes (Projeto: Criando formas
simples), começamos alterando a última linha do código (que está logo acima da tag
</script>) no final do editor. Em vez de dizer ao navegador para mostrar a cena uma
vez, animamos a cena da seguinte forma:

// Now, animate what the camera sees on the screen:


//gora, anime o que a câmera vê na tela:

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.

var rightHand = new THREE.Mesh(hand, cover);


rightHand.position.set(-150, 0, 0);
» scene.add(rightHand);

Em vez de adicionarmos a mão à cena, a adicionamos ao avatar:

var rightHand = new THREE.Mesh(hand, cover);


rightHand.position.set(-150, 0, 0);
» avatar.add(rightHand);

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).

O código até agora


A totalidade do código será semelhante ao código em Code: Making a Avatar. Não
se preocupe se o seu não for exatamente como esse código. Seu código pode ser
melhor ou apenas diferente.

O que vem a seguir


Temos um avatar bem legal. Pode ser bom ter rosto ou roupas. Mas você sabe o
que seria ainda melhor? Se pudéssemos mover nosso avatar com o teclado. E é
exatamente isso que faremos no Capítulo 4, Project: Moving Avatars (Projeto:
Movendo Avatares). Por enquanto, dedique algum tempo para brincar com o
tamanho, posicionamento e rotação das partes que compõem seu avatar.
Quando terminar este capítulo, você poderá:
- Controlar as coisas com o teclado.
- Entender como conectar câmeras para que elas se movam junto com o avatar.
- Tenha sua primeira experiência com eventos JavaScript, que são loucos,
importantes e muito estranhos!

Capítulo 4

Projeto: Movendo Avatares


No Capítulo 3, Projeto: Criando um Avatar, abordamos como construir um avatar de
jogo. Mas um avatar que não pode se mover é muito chato. Portanto, neste capítulo,
aprenderemos como fazer o avatar se mover pela cena. Também daremos a ele
uma pequena floresta para brincar. Ele ficará parecido com isto:

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.

Salve seu trabalho


O código de trabalho (o código final ou trechos de códigos a ser utilizados) é uma
coisa rara e maravilhosa. Venho programando há 20 anos. Eu ainda escrevo mais
código quebrado do que código de trabalho. Sempre que finalmente consigo
trabalhar (finalizar códigos), a primeira coisa a fazer é salvá-lo. Bem, a primeira coisa
que faço é uma pequena dança - apenas uma pequena, eu sou péssima -, mas vale
a pena comemorar. E qualquer coisa que valha a pena comemorar vale a pena
salvar.

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.

Criando sistemas interativos com eventos de


teclado
Quando as coisas acontecem nos navegadores da web, o JavaScript pensa
nessas coisas como eventos. Para que nosso código faça algo quando um evento
acontece, escrevemos um código que atende a diferentes tipos de eventos. Eventos
(event) e atendentes de eventos (EventListener) são estranhos no começo, mas
fazem sentido quando você vê algum código. Então vamos codificar! Adicione o
seguinte na parte inferior do nosso código, abaixo da linha animate (animada) que
adicionamos no Capítulo 3, Project: Making an Avatar (Projeto: Criando um avatar).

document.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
alert(event.code);
}

Esse código atende a um evento keydown (de pressionamento de tecla). Você


provavelmente adivinhou que esses eventos acontecem quando qualquer tecla é ...
pressionada. Quando nosso código perceber um pressionamento de tecla, ele
informará a função sendKeyDown () para usar essa tecla para enviar um comando
ao avatar.
Antes de dizer ao avatar para mover, precisamos descobrir qual tecla é
pressionada. Portanto, em nossa função sendKeyDown (), examinamos o valor
event.code.
O que é esse código? Para responder a isso, vamos tentar! Clique no botão
OCULTAR CÓDIGO na barra de ferramentas na parte superior da janela do 3DE e
pressione a tecla A no teclado. Você deve ver algo como este diálogo de alerta.

Legal! Pressionamos a tecla A, que o JavaScript chama de KeyA. E as teclas de


seta?
Clique no botão OK no alerta, se você ainda não o fez. Repita as teclas de seta
para a esquerda, para cima, para a direita e para baixo no teclado. Para a seta
esquerda, você deve descobrir que o computador pensa que é um ArrowLeft. Para
a seta para cima, o computador pensa que é um ArrowUp. Para a seta direita, o
computador detecta a chave como ArrowRight. Para a seta para baixo, o
computador pensa que é uma ArrowDown.
Vamos usar esses códigos-chave para mover nosso avatar!

Convertendo eventos do teclado em movimento de


avatar
Mostre seu código novamente e remova a linha alert(event.code) dentro de
document.addEventListener. Substitua-o pelo seguinte:

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.

var leftFoot = new THREE.Mesh(foot, cover);


leftFoot.position.set(75, -125, 0);
» scene.add(leftFoot);

Execute isso e o pé esquerdo desaparecerá, como mostrado na figura.

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!

Desafio: Iniciar / Parar animação


Lembre-se dos valores isCartwheeling e isFlipping de quando criamos o avatar
no Capítulo 3, Projeto: Criando um avatar? Vamos adicionar mais duas instruções
if ao atendente de eventos do teclado. Se KeyC for pressionado, o avatar deve
iniciar ou parar de girar. Se KeyF for pressionado, a rotina de inversão deve iniciar
ou parar.
Dica: você pode alternar entre true (verdadeiro) ou false (falso), colocando um
ponto de exclamação na frente dele. Assim, você pode definir os valores da chave
isCartwheeling com algo como isCartwheeling =! IsCartwheeling. É estranho,
mas falaremos mais sobre isso em booleanos. Por enquanto, tente obter
isCartwheeling e isFlipping para mudar quando a tecla correta for pressionada.
Você conseguiu fazê-lo funcionar sozinho? Não se preocupe se não o fez: esse tipo
de JavaScript pode ser um pouco estranho na primeira vez que você o experimenta.
Aqui está a função animate que manipula com o Cartwheeling (giro) e Flipping
(inversão):

var isCartwheeling = false;


var isFlipping = false;
function animate() {
requestAnimationFrame(animate);
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();

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.

Construindo uma floresta com funções


Precisamos de muitas árvores para nossa floresta. Poderíamos construí-los um de
cada vez, mas não vamos fazer isso. Em vez disso, adicione o seguinte JavaScript
depois de todas as partes do corpo do avatar:

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);
}

makeTreeAt (500, 0);


makeTreeAt (-500, 0);
makeTreeAt (750, -1000);
makeTreeAt (-750, -1000);

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);
}

Função makeTreeAt(x,z) { (função faça uma árvore)

① Faça um tronco (variável trunk) de um cilindro.


② Faça uma copa (variável top - de topo) de árvore, de uma esfera.
③ Mova a copa das árvores para cima (lembre-se de que Y está para cima e para
baixo) até o topo do tronco.
④ Adicione a copa das árvores ao tronco.
⑤ Defina a posição do tronco com os valores x e z com os quais a função foi
chamada: makeTreeAt (500,0), por exemplo. O valor Y de -75 move o tronco
para baixo o suficiente para parecer um tronco de árvore.
⑥ Adicione o tronco, com a copa das árvores, à cena.

É 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).

makeTreeAt( 500, 0);


makeTreeAt(-500, 0);
makeTreeAt( 750, -1000);
makeTreeAt(-750, -1000);

Agora que temos uma floresta, vamos ver o que podemos fazer sobre o avatar sair
da tela.

Movendo a câmera com o avatar


A maneira mais fácil de manter o avatar na tela é movendo a câmera para onde
quer que o avatar se mova. Se a câmera estiver sempre apontada para o avatar, ele
não poderá sair da tela!
Agora, para que as mãos e os pés se movam junto com o nosso avatar, os
adicionamos ao corpo do avatar em vez de adicioná-los à cena. Precisamos fazer o
mesmo com a câmera.
Primeiro, vamos encontrar a linha que diz scene.add (camera) e excluí-la. Em
seguida, abaixo da linha em que o avatar é adicionado à cena - e acima da função
makeTreeAt, vamos adicionar a câmera ao avatar:

var leftFoot = new THREE.Mesh(foot, cover);


leftFoot.position.set(75, -125, 0);
avatar.add(leftFoot);
» avatar.add(camera);

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.

Se travarmos a câmera e o avatar nesse marcador, movê-lo moverá o avatar e a


câmera.
Mas, mais importante, quando o avatar gira, a câmera não se move. O avatar está
girando, mas o marcador não gira. Como o marcador não está girando, a câmera
também não gira.

Na programação 3D, este marcador é apenas um marcador. Deveria ser invisível.


Portanto, não queremos usar malhas ou geometrias para isso. Em vez disso,
usamos o Object3D. Vamos adicionar o seguinte código antes do código gerado
pelo avatar, logo após INICIAR O CODIFICAÇÃO NA PRÓXIMA LINHA:
var marker = new THREE.Object3D();
scene.add(marker);

Agora, alteramos o avatar para que seja adicionado ao marcador em vez da cena:

var avatar = new THREE.Mesh(body, cover);


» marker.add(avatar);

Também precisamos mudar a forma como a câmera é adicionada. Em vez de


adicionar a câmera ao avatar, nós a adicionamos ao marcador.

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) {

var code = event.code;

» if (code == 'ArrowLeft') marker.position.x = marker.position.x - 5;

» if (code == 'ArrowRight') marker.position.x = marker.position.x + 5;

» if (code == 'ArrowUp') marker.position.z = marker.position.z - 5;

» if (code == 'ArrowDown') marker.position.z = marker.position.z + 5;

if (code == 'KeyC') isCartwheeling = !isCartwheeling;

if (code == 'KeyF') isFlipping = !isFlipping;

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.

O código até o momento


Se você deseja verificar seu código até o momento, compare-o com o código em
Code: Moving Avatars.

O que vem a seguir


Abordamos algumas habilidades muito importantes neste capítulo. Eventos como
esses eventos do teclado são extremamente importantes em JavaScript. Vamos ver
eventos novamente. E agruparemos objetos como esse repetidamente à medida que
nossas habilidades de jogo melhorarem. O agrupamento simplifica a movimentação
das coisas, além de torcer, girar, crescer e encolher as coisas.
Se você deseja continuar trabalhando em nosso avatar, pule para o Capítulo 6,
Projeto: Movendo mãos e pés. Se você pular adiante, não se esqueça de voltar ao
próximo capítulo, que explora mais as funções do JavaScript. Já estamos usando
funções para criar uma floresta, animar e ouvir eventos. Há muito mais para
aprender sobre funções. E se isso não faz você querer ler o capítulo sobre funções,
talvez seja isso: criamos uma centena de planetas e adicionamos controles de voo
para voar em torno deles!
Realmente, é bem legal.

Notas de rodapé
https://en.wikipedia.org/wiki/Web_colors

Ao concluir este capítulo:


Você entenderá uma ferramenta (funções) superpoderosa para programadores.
Será capaz de contar histórias e calcular coisas com as funções.
Crie 100 planetas e voe entre eles.

Capítulo 5
Funções: usar e usar novamente

Os programas de computador são um pouco como histórias.


Era uma vez, havia 100 planetas especiais. Cada planeta tinha uma
cor diferente e um tamanho diferente. Eles estavam espalhados por
todo o espaço conhecido. E havia apenas um navio rápido o
suficiente - e um piloto louco o suficiente - para alcançá-los todos.
Até agora, temos um pouco do conhecimento necessário para escrever essa
história no código JavaScript. Mas por onde começamos? Como poderíamos
organizar tudo isso? Nós realmente vamos ter que digitar todo o código para 100
planetas?
A resposta de programação para essas perguntas é uma ferramenta bacana
chamada função. Discutimos brevemente sobre as funções no Capítulo 2,
Depuração: corrigindo código quando as coisas dão errado e no Capítulo 4, Projeto:
Movendo avatares. Agora vamos explorar como eles funcionam.
Programar com funções - especialmente em JavaScript - pode ficar complicado.
Seriamente complicado. Muito poder de programação está envolvido com funções,
e é em parte por isso que elas se tornam tão complicadas. Mas o básico é bem fácil.
Neste livro, vamos nos ater ao básico, usando funções para fazer três coisas:

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)

À medida que exploramos as funções deste capítulo, falaremos principalmente


sobre os itens 2 e 3.
Usaremos funções para contar partes das histórias e organizá-las mais tarde.
Para começar a explorar, vamos tentar criar 100 planetas.

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:

var shape = new THREE.SphereGeometry(50);


var cover = new THREE.MeshBasicMaterial({color: 'blue'});
var planet = new THREE.Mesh(shape, cover);
planet.position.set(-300, 0, 0);
scene.add(planet);

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:

var shape = new THREE.SphereGeometry(50);


var cover = new THREE.MeshBasicMaterial({color: 'yellow'});
var planet = new THREE.Mesh(shape, cover);
planet.position.set(200, 0, 250);
scene.add(planet);

Isso adiciona um segundo planeta (amarelo) à direita e um pouco à frente.


São dois planetas.

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());

Agora abra o console JavaScript, conforme descrito em Abrindo e fechando o


console JavaScript. No console, você verá um número decimal aleatório entre 0 e 1.

Se você clicar no botão UPDATE novamente no editor de código, poderá assistir à


alteração do número no console JavaScript. Nunca é o mesmo número duas vezes.
Às vezes é perto de 0. Às vezes é perto de 1. Às vezes é no meio. Isso pode ser
realmente útil. Exceto que não queremos espalhar planetas entre 0 e 1.
Isso colocaria todos os nossos planetas muito perto do centro da cena. Queremos
que eles sejam espalhados aleatoriamente entre 0 e 1000. Para fazer isso, podemos
multiplicar Math.random () por 1000 sempre que usarmos Math.random (). Mas isso
dá muito trabalho. Somos programadores preguiçosos, certo? Vamos usar as
funções para nos manter preguiçosos.

Funções que retornam valores


Vamos escrever uma função r () que retorna um número entre 0 e algum número
de nossa escolha. Adicione a seguinte função abaixo da instrução console.log () da
seção anterior:

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.

O primeiro é o valor de Math.random () da seção anterior - deve ser um número


decimal entre 0 e 1.
O próximo número é retornado de r () - também deve estar entre 0 e 1.
Os próximos três são o resultado da chamada de r (100) - todos os três números
devem estar entre 0 e 100.
Essa função r () será perfeita para nossos planetas espalhados pelo espaço. Mas
como isso funciona?
Podemos diagrama uma função como:
As várias partes de uma função são as seguintes:

Função Start (início da função)


Uma definição de função sempre começa com a palavra function - é assim
que o JavaScript sabe que está vendo uma função em vez de outros tipos de código.
Name (nome da função)
Usamos uma função chamando seu nome - r () ou makePlanet ().
Arguments (argumentos)
Estes são os valores que enviamos para a função. Podemos passar zero (0),
um (1) ou mais valores para uma função.
- Uma função de zero argumento, como makePlanet (), não tem nada entre
parênteses.
- Uma função de um argumento, como r (número), tem um único argumento
entre parênteses.
- Uma função de dois argumentos ou mais, lista os argumentos separados
com vírgulas: playSong (title, howLong).
Body (corpo da função)
Tudo que fica entre as chaves (chaves de abertura e fechamento).
Função Return / Leave
A instrução return faz duas coisas.
Primeiro: ele deixa imediatamente a função - qualquer código abaixo da
instrução de retorno é ignorado.
Segundo: ele envia o valor após a instrução de retorno de volta ao
código que está chamando a função.
OK, agora voltemos à nossa função r ().
A primeira linha do corpo da função diz que, if (se) lhe dermos um valor max
(máximo), então return (retornamos) o produto obtido da multiplicação de max com
Math.random (). Atribuímos a uma função um valor de argumento, chamando-a com
um valor como: r (2), r (100) ou r (1000).
Se não atribuirmos a r () um valor máximo - se chamarmos r () sem nada entre
parênteses -, ignoraremos o retorno na primeira linha. Sem valor máximo, a função
passa para a segunda linha, onde retorna Math.random () sem multiplicação.
Podemos pegar o valor de retorno de funções como r () e atribuí-los a valores -
como fizemos com randomNum. Também podemos registrá-los diretamente no
console, como fizemos com console.log (r ()).

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!

Bem, dois planetas espalhados aleatoriamente mais os azuis e amarelos que


colocamos da maneira mais difícil. Graças às funções, é fácil chamar makePlanet ()
quantas vezes quisermos. Definitivamente, não digitaremos makePlanet () 100
vezes - somos preguiçosos, lembra? Em vez disso, vamos criar um "loop" de
programação para fazer isso. O código a seguir fará um loop sobre makePlanet ()
100 vezes para nós. Adicione-o abaixo das outras duas chamadas para makePlanet
().

for (var i=0; i<100; i++) {


makePlanet();
}

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:

function r(max) - aqui, falta a chave de abertura do corpo da função.


if (max) return max * Math.random();
return Math.random();
} - aqui é a chave de fechamento.

É 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) { - aqui é a chave de abertura do corpo da função.


if (max) return max * Math.random();
return Math.random();
- Aqui falta a chave de fechamento do corpo da função.

O editor de código é definitivamente capaz de descobrir esse problema!

Portanto, preste atenção aos avisos do editor de código quando estiver


programando com funções.
Desafio
Tente descobrir onde encontrar as mensagens de erro para os seguintes
exemplos. Dica: Como na Depuração no console, alguns deles podem aparecer
apenas no console JavaScript.
Esquecimento de parênteses em torno do argumento:

function r max { - max deve ficar entre parênteses.


if (max) return max * Math.random();
return Math.random();
}

Esquecimento do argumento da função:

function r () { - faltou o argumento max dentro dos parênteses.


if (max) return max * Math.random();
return Math.random();
}

Nome de variável incorreto dentro da função:

function r(max) {
if (number) return number * Math.random(); - aqui, em vez de number, deveria ser max.
return Math.random();
}

Função chamada com o nome errado:

function r(max) {
if (max) return max * Math.random();
return Math.random();
}

var randomNum = randomNumber(); - aqui, em vez de randomNumber(); deveria ser Math.random():


console.log(randomNum);
Uau! Existem muitas maneiras de interromper funções. Acredite em mim quando
digo que você interromperá as funções dessa e de muitas outras maneiras. Mas tudo
bem. Cada vez que você corrige um código quebrado, aprende algo novo. E
aprender coisas novas é a coisa mais importante que você pode fazer para se tornar
um ótimo programador.

Grandes programadores quebram coisas o tempo


todo
Como eles quebram tanto as coisas, eles são realmente bons em consertar as
coisas. Essa é outra habilidade que torna ótimos programadores excelentes.

Certifique-se de corrigir a função r () se quiser experimentar alguns dos códigos


de bônus a seguir.

Bônus # 1: Cores Aleatórias


O roxo é uma ótima cor. Mas se você preferir mais variedade, seria bom escolher
aleatoriamente a cor. O problema é que as cores que vimos até agora foram
nomeadas de cores, como azul.
var shape = new THREE.SphereGeometry(50);
» var cover = new THREE.MeshBasicMaterial({color: 'blue'});
var planet = new THREE.Mesh(shape, cover);
planet.position.set(-500,0,0);
scene.add(planet);

Cores nomeadas como azul, amarelo e roxo são difíceis de aleatorizar.


Felizmente, você pode criar cores de outras formas na programação. Uma das
maneiras mais comuns usa um dos algarismos, 0 ou 1, para especificar quanto
vermelho, verde e azul entram em uma cor.
Aqui estão algumas cores comuns no formato vermelho, verde e azul (também
conhecido como RGB). Você não precisa digitá-las, elas são apenas interessantes
de analisar.

var red = var THREE.Color(1, 0, 0);


var blue = var THREE.Color(0, 1, 0);
var green = var THREE.Color(0, 0, 1);
var cyan = var THREE.Color(1, 1, 0);
var white = var THREE.Color(1, 1, 1);
var black = var THREE.Color(0, 0, 0);
var grey = var THREE.Color(0.5, 0.5, 0.5);

Números entre 0 ou 1? Nossa função r () pode fazer isso! Se não passarmos


argumentos para r (), ele retornará Math.random (), que gera um número entre 0 e
1.

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 ());
}

Em seguida, de volta ao corpo do makePlanet (), altere a surface de 'purple'


para rColor ().
function makePlanet() {
var size = r(50);
var x = r(1000) - 500;
var y = r(1000) - 500;
var z = r(1000) - 1000;
» var surface = rColor();
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);
}

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:

var controls = new THREE.FlyControls(camera);


controls.movementSpeed = 100;
controls.rollSpeed = 0.5;
controls.dragToLook = true;
controls.autoForward = false;
var clock = new THREE.Clock();
function animate() {
var delta = clock.getDelta();
controls.update(delta);
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate

Para usar os controles, oculte seu código. As seguintes teclas do teclado permitem
voar ou girar em direções diferentes.

Motion Direction Keys


Move Forward / Backward W/S
Move Left / Right A/D
Move Up / Down R/F
Spin Clockwise / Counterclockwise Q/E
Spin Left / Right Left Arrow / Right Arrow
Spin Up / Down Up Arrow / Down Arrow

Dica: é bem divertido pressionar Q e W ao mesmo tempo. Mas você pode ficar um
pouco tonto!

Experimente os valores de movementSpeed e rollSpeed como desejar. Os


valores usados devem funcionar razoavelmente bem com nossos planetas. O valor
autoForward avança, a menos que você pressione uma tecla. Se dragToLook
estiver definido como false, a cena será movida sempre que você mover o mouse.

Desde que giramos nossas formas no Capítulo 1, Projeto: Criando formas


simples, usamos a função animate (). Aqui, dizemos aos controles fly para atualizar
sua posição no corpo da função animate. Um delta de relógio é a quantidade de
tempo que passou desde a última vez que os controles atualizaram a cena. Os
controles fly usam esse delta para tornar as atualizações agradáveis e suaves. Sem
isso, flying (voando) pode parecer nervoso ou gaguejar às vezes.

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 ();

①. Define animate () como qualquer outra função.


②. Diz ao JavaScript para atualizar a cena 3D.
③. Isso é algo que ainda não vimos: está enviando a função animate () como
argumento para a função requestAnimationFrame () incorporada aos
navegadores!
④. É chamada como qualquer outra função.

A chamada para a função requestAnimationFrame () é bastante interessante e


sugere, como funções complexas podem ser obtidas. Ele é incorporado aos
navegadores para que os navegadores possam chamar uma função sempre que o
navegador não estiver ocupado fazendo outra coisa (como atualizando algo na
página, carregando novas informações de um servidor da Web ou executando outra
função). Estamos dizendo ao navegador para chamar nossa função animate ()
novamente assim que não estiver ocupado. E quando isso acontece, a função
animate () obtém um delta do relógio, atualiza os controles de voo (o fly controls),
reproduz novamente a cena e ... solicita ao navegador que faça tudo de novo assim
que o navegador estiver pronto!
Sem algo como requestAnimationFrame () - e sua capacidade de passar uma
função como argumento, as animações e a programação 3D na Web seriam muito
mais difíceis. Quase tudo gaguejaria ou parava completamente às vezes. Então, sim,
para requestAnimationFrame () e funções!

O código até o momento


Caso você queira verificar o código neste capítulo, ele está incluído no Código:
Functions: Use and Use Again.

Quais são as próximas


Funções são ferramentas muito poderosas para programadores de JavaScript.
Vimos dois bons usos para funções. Funções repetidas como makePlanet ()
economizam muita codificação. Calcular valores como a função r () aleatória
também ajuda a facilitar nossa vida de codificação. Vimos até que a função animate
() que usamos em todos os lugares é um tipo de função de contar uma história.
Conta a história da animação em nossos jogos.
Mais importante, porém, reunimos tudo isso para construir um pequeno universo
legal. E ainda estamos arranhando a superfície!
Como você verá em breve, usaremos muitas funções nos próximos capítulos.
Vamos iniciar no próximo capítulo em como ensinamos nosso avatar a mover as
mãos e os pés!

Quando terminar este capítulo, você:


- Entenderá algumas matemáticas importantes para jogos em 3D.
- Saberá como mover objetos para frente e para trás.
- Terá um avatar que parecerá estar andando.

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!

Movendo uma Mão


Vamos começar com uma mão. Lembre-se dos capítulos anteriores que mãos e
pés são apenas bolas que se destacam da cabeça. Criamos a mão direita em
JavaScript com isso:

var rightHand = new THREE.Mesh(hand, cover);


rightHand.position.set (-150, 0, 0);
avatar.add (rightHand);

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:

var rightHand = new THREE.Mesh(hand, cover);


rightHand.position.set (-150, 0, 0);
avatar.add(rightHand);
rightHand.position.z = 100;

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.

var isCartwheeling = false;


var isFlipping = false;
function animate() {
requestAnimationFrame(animate);
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();

Isso funciona bem, mas antes de começarmos a animar mais coisas na função
animate (), vamos arrumar as coisas um pouco.

Limpeza rápida de código


Muita coisa está acontecendo em nossa função animate () - tanto que é um pouco
confuso. Essa mensagem pode dificultar a leitura do nosso código. Estaremos
adicionando ainda mais coisas para animar. A menos que façamos algo, essa
função de animação ficará feia.
Então, vamos fazer uma pequena limpeza de código. Logo abaixo da função
animate(), crie uma função acrobatics. Essa função fará a inversão e rotação,
então você pode cortar e colar o if (isCartwheeling) e if (isFlipping) de animate ()
nesta nova função acrobatics(). Então, podemos chamar acrobatics de dentro de
dentro de animate (), tornando-as mais fáceis de ler.
var isCartwheeling = false;
var isFlipping = false;
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;
}
}

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;
}

① Usaremos esse relógio (clock 3D) como um temporizador para a nossa


animação.

② 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:

var speed = 10;


var size = 100;
var time = clock.getElapsedTime ();
var position = Math.sin (speed time) size;
» rightFoot.position.z = -position;
» leftFoot.position.z = position;

As pessoas também costumam mover a mão direita para a frente quando o pé


esquerdo está à frente. E se a mão direita está para frente, a esquerda deve estar
para trás. Podemos fazer nosso avatar fazer isso com o seguinte código:

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:

var clock = new THREE.Clock();


var isCartwheeling = false;
var isFlipping = false;
var isMovingRight = false;
var isMovingLeft = false;
var isMovingForward = false
var isMovingBack = false;

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!

document.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;
}
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;
});

① A left arrow means the avatar is moving left.


② A right arrow means the avatar is moving right.
③ An up arrow means the avatar is moving forward.
④ A down arrow means the avatar is moving backward.

Enquanto o jogador mantiver uma dessas teclas pressionada, o avatar continuará


se movendo na direção correta. Os manipuladores de teclado JavaScript funcionam
assim. Ativamos os controles de movimento, mas ainda precisamos ser capazes de
desligá-los. Já que usamos a tecla keydown para decidir quando uma tecla está
sendo pressionada, você provavelmente pode adivinhar como decidiremos quando
uma tecla for solta. Após a última linha do código do atendente de evento
(EventListener(keydown)) – após a linha que contém os fechamentos }); - adicione
o seguinte código de atendente de evento: keyup.

document.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;
}
Com isso, podemos ocultar nosso código e testar nossos controles. Devemos ser
capazes de mover nosso avatar com as teclas de seta e ver as mãos e os pés do
avatar balançando para frente e para trás. Quando soltamos essas teclas, o avatar
deve parar de andar.
Legal!

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!

O código até agora


Se você quiser verificar o código neste capítulo, vá para Code: Moving Hands
and Feets
.

O que vem a seguir


Agora temos uma nova maneira de dar vida aos nossos avatares. De volta ao
Capítulo 4, Projeto: Movendo Avatares, fomos capazes de mover o avatar pela
cena e realizar viradas e piruetas. Neste capítulo, fizemos com que partes do avatar
se movessem - fazendo o avatar parecer muito mais vivo.
O novo conceito neste capítulo não era algo em JavaScript ou mesmo em 3D. Era
uma coisa matemática: seno. Mesmo que você tenha aprendido sobre isso na aula
de matemática, aposto que não aprendeu a usá-lo como fizemos aqui!
Uma coisa que ainda falta em nosso avatar é a habilidade de girar. Mesmo
quando o avatar se move para a esquerda ou direita, ele continua voltado para a
frente. Isso é um pouco estranho, certo? No Capítulo 8, Projeto: Virando Nosso
Avatar, vamos cobrir como girar o avatar inteiro.
Mas primeiro é hora de uma pausa rápida para olhar um pouco mais de perto o
JavaScript.
Quando terminar este capítulo, você saberá:

- O que muitas dessas coisas, são JavaScript (como var).


- Ser capaz de manter grandes listas de coisas (duas maneiras diferentes!)
- Entenda 80% do JavaScript Vale a pena saber

Capítulo 7

Uma análise mais detalhada dos fundamentos do


JavaScript
Antes de adicionarmos mais ao nosso avatar, este é um bom momento para
examinar o JavaScript mais de perto. Há muito neste capítulo. Não se preocupe se
ficar opressor. Está tudo bem se você apenas olhar este capítulo rapidamente na
primeira vez que ler o livro. Apenas lembre-se de voltar em algum momento - este
capítulo tem muitas coisas que é importante saber se você deseja programar bem.
Como qualquer outra linguagem de programação, o JavaScript foi desenvolvido
para que tanto os computadores quanto as pessoas pudessem entendê-lo. Como
qualquer outra linguagem de programação, é igualmente provável que computadores
e pessoas fiquem confusos com ela. Essa é uma boa piada de programação.
Infelizmente, a maioria dos programadores tem um senso de humor terrível. Eu sou
considerado hilário. Se você tiver piadas melhores do que essa, será uma adição
bem-vinda ao mundo da programação! Enfim ...
A programação JavaScript descreve coisas e o que essas coisas fazem, assim
como o inglês e outras linguagens. Quando construímos nosso avatar, usamos
JavaScript para descrever sua cabeça, mãos e pés. Também descrevemos como o
renderizador 3D deve desenhar a cena em nosso navegador. Para colocar tudo
junto, JavaScript tem palavras-chave e estruturas que tanto os computadores quanto
os humanos podem entender.
Nós vimos muito disso até agora, mas não olhamos muito de perto. Vamos fazer
isso agora.
Primeiros passos
Em vez de desenhar e mover formas neste capítulo, vamos explorar a linguagem
de programação JavaScript.
Podemos fazer isso no console JavaScript, então vamos começar abrindo-o.
Consulte: Abrindo e fechando o console JavaScript, se você não se lembra como.
O console JavaScript é o melhor amigo de um programador da web. Os
navegadores modernos incluem o console JavaScript com todos os tipos de
recursos que ajudam os programadores a melhorar o desempenho da rede, a
segurança, o uso de memória e muito mais. Neste livro, nós o usamos para depurar
nosso código e para fazer experiências com JavaScript. Falamos sobre depuração
no Capítulo 2, Depuração: consertando código quando as coisas dão errado. Neste
capítulo, fazemos experiências. E quando os programadores experimentam, nós
brincamos!
Primeiro, uma dica rápida ...
Você pode alterar o código no console (mais ou menos)
Se você cometer um erro em seu código e pressionar Enter, não poderá
simplesmente clicar no erro para alterá-lo.

Mas, você pode pressionar a tecla de seta para cima no teclado para abrir a última
coisa que você digitou.

Em seguida, você pode corrigir os problemas e pressionar Enter para tentar


novamente.
DICA - Não digite a mesma coisa repetidamente no Console JavaScript.
Console JavaScript.
Nós, programadores, temos que digitar o código.
Mas nós, programadores, gostamos de ferramentas que tornam a digitação mais
fácil. Use as teclas de seta para cima e para baixo para acessar e navegar pelo
histórico do código que você digita no console JavaScript.
Fazer pequenas alterações no código que você já digitou economiza muito tempo.
Tudo bem, vamos nos divertir com JavaScript no console!

Descrevendo coisas em JavaScript


Você percebeu como introduzimos coisas novas em JavaScript?
var speed = 10;
var title = '3D Game Programming for Kids';
var isCool = true;

A palavra-chave var declara coisas novas em JavaScript. Ele diz ao computador e


aos humanos que estão lendo o código: “Prepare-se - algo novo está chegando!”

A palavra-chave var
A palavra-chave var é uma abreviação de variável. Uma variável é algo que pode
mudar.

Primeiro, a variável i é definida com o valor 0. Em seguida, ela é definida com o


valor 1. Por último, a variável i, é definida com o valor 2. O valor para o qual a
variável aponta muda - ele varia - conforme o código faz as coisas (ou conforme
digitamos as mudanças no console).
Sempre usamos var quando introduzimos uma variável pela primeira vez em nosso
código. Sem ele, o JavaScript pode ficar confuso sobre qual valor é definido para
uma variável e quando. Em outras palavras, não usar var pode tornar o código com
erros difícil de corrigir. Portanto, sempre use var ao introduzir uma variável ... ...
Exceto no console JavaScript. O código no console JavaScript é para experimentos
rápidos, então há poucas chances de criar bugs. Ao escrever esses experimentos
rápidos, os programadores economizam tempo, não digitando var ou terminando as
linhas com ; (ponto-e-vírgula).
O outro motivo para ignorar var no console JavaScript é que o valor que está sendo
definido não é relatado. Quando você define uma variável no console JavaScript
sem var, o valor é relatado após pressionar Enter no teclado.

Estranhamente, se você definir uma variável com var no console JavaScript, o


console relata o resultado como indefinido.

No entanto, não é indefinido: Digitar apenas o nome da variável e pressionar


Enter irá relatar seu valor.

JavaScript é uma ótima linguagem. Ele pode, entretanto, se comportar


estranhamente, às vezes.
Sempre introduza variáveis com var (mas não no console JavaScript).
O código é melhor se você sempre usar var ao introduzir pela primeira vez uma
variável em um projeto ou dentro de uma função. É mais fácil de ler e menos
provável que leve a bugs estranhos. Mas não use var no console JavaScript, porque
JavaScript é esquisito, misterioso.
Diferentes tipos de coisas em JavaScript
JavaScript pode definir variáveis para muitas coisas diferentes. Ele pode definir
números, palavras, datas e valores verdadeiros e falsos. JavaScript tem duas
maneiras diferentes de definir “nada” (JavaScript é estranho). Existem algumas
maneiras de listar as coisas. Existem até maneiras de definir uma variável para
formas 3D!
Vamos dar uma olhada em cada uma delas - e maneiras de codificar com elas.

Código é para Computadores e Humanos.


Comentários são apenas para Humanos.
Escrevemos código para computadores e pessoas. O código é escrito para
computadores para que os computadores possam fazer coisas. O código é escrito
para as pessoas, para que possamos entender o código e adicionar recursos a ele.
Às vezes, o código pode ser um pouco difícil para as pessoas entenderem. Quando
isso acontece, os programadores adicionam comentários ao código.
Os computadores sabem que devem ignorar os comentários. Os programadores
sabem que devemos lê-los para entender melhor o código a seguir.

Em JavaScript, barras duplas // indicam comentários. Digite o seguinte no console


JavaScript.

// Set today's date


// Defina a data de hoje.
date = new Date ()

// Set January 1, 2050


// Defina 1.º de Janeiro de 2050
date = new Date (2050, 0, 1)

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.

Números, Palavras e Outras Coisas


em JavaScript
Sabemos que podemos mudar coisas, mas como cada tipo de variável pode mudar
em JavaScript? Vamos pegar um de cada vez.
Números
Você pode usar símbolos matemáticos padrão para adicionar e subtrair números
em JavaScript. Tente o seguinte no console JavaScript, pressionando Enter após
cada problema de matemática.
5+2
10 - 9.5
23 - 46
84 + -42
Você deve obter as seguintes respostas (a resposta é mostrada nos comentários
abaixo do problema de matemática).
5+2
// 7
10 - 9.5
// 0.5
23 - 46
// -23
84 + -42
// 42

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

A divisão é feita com o caractere de barra (/):

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

O que acontece sem os parênteses? E você consegue adivinhar por quê?


Sem parênteses, em uma operação que contenha multiplicação, somas e
subtrações, a multiplicação é feita primeiro, depois é que são efetuadas as adições e
subtrações. Lembre-se da “ordem das operações” de sua aula de matemática!
Um último operador numérico que vale a pena mencionar é o operador de
incremento ++. Ele aumenta uma variável em 1 (valor um), mas retorna o valor
anterior.
i=0
// 0

j=i++
// 0

i
//1

j
//
0

Quando chamamos i ++ na segunda linha, ele aumenta o valor de i de 0 para 1.


Mas retorna o valor antes do aumento, que é definido como j. Portanto, j é
definido como 0 ao mesmo tempo que i é aumentado em 1 (um). Certifique-se
de experimentar no console JavaScript. O comportamento pode parecer um pouco
estranho, mas é muito útil, como veremos mais adiante neste capítulo e em todo o
livro.

Geometria

Estamos trabalhando em conceitos de jogos 3D neste livro, o que significa


geometria. Discutiremos a geometria em mais detalhes como parte dos vários
capítulos do projeto que precisam dela. Por enquanto, vamos considerar duas
funções geométricas: seno e cosseno. Se você não os conhece, não se preocupe -
você as conhecerá nos jogos.
Só, lembre-se de que em JavaScript, não usamos graus. Em vez disso, usamos
radianos. O que são radianos? Em vez de dizer que giramos 180 °. Quando
giramos metade de um círculo, devemos dizer que "giramos pi radianos".

Pi é um número especial em matemática. Seu valor é de cerca de 3,14159. Você


frequentemente verá o símbolo π usado para pi. Vamos chamá-lo de pi, pois
JavaScript o chama de Math.PI.
Se você se lembra dos donuts de Rendering Donuts (não do tipo que você
come) with Torus, foi assim que fizemos o donut meio comido. Dissemos a ele para
ter 3,14 radianos - ou cerca de pi radianos. Percorrer um círculo completo é duas
vezes mais do que um giro de 180 °, mais comumente chamada de giro de 360 °,
que tem dois radianos pi ou 2 × pi.

360 ° é 2 * Math.PI em JavaScript. Segue uma tabela de conversão útil:

Degrees Radians JavaScript


0° 0 0
45° pi ÷ 4 Math.PI/4
90° pi ÷ 2 Math.PI/2
180° pi Math.PI
360° 2 × pi 2*Math.PI
720° 4 × pi 4*Math.PI
Funções geométricas também estão disponíveis na coleção de códigos
matemáticos do JavaScript. Um exemplo é o sine (seno) que vimos no Capítulo 6,
Projeto: Movendo as mãos e os pés. JavaScript encurta ambos, sine (seno) e seu
companheiro, cosine (cosseno), para sin e cos:

Math.sin(0)
// 0

Math.sin(2*Math.PI)
// 0

Math.cos(0)
// 1

IMPORTANTE - Realmente, Realmente Perto de Zero


Dependendo do seu computador, quando você tentou Math.sin (2 * Math.PI) no
console JavaScript, você pode não ter obtido a resposta certa. O seno de 2 × pi é 0,
mas você pode ter visto algo como -2.4492127076447545e-16. Isso mostra que os
computadores não são perfeitos. Às vezes, sua matemática pode estar um pouco
errada.
Quando o JavaScript tem e-16 no final de um número, significa que é um
número decimal com 16 casas à esquerda. Em outras palavras, -2.45e-16 é a
mesma coisa que escrever -0,000000000000000245. Esse é um número muito,
muito pequeno - você teria que somar mais de dois milhões de vezes para chegar a
1.

Estas funções Math. e os operadores aritméticos simples irão aparecer com


bastante frequência conforme progredimos. A matemática é muito divertida na
programação de jogos 3D!

Strings
Palavras e letras são strings em JavaScript. As strings começam e terminam
com aspas “”.
title = "3D Game Programming for Kids"

As strings também podem começar e terminar com um apóstrofo ‘’.

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:

motto = "Don't be lazy"


// Com apóstrofo simples dentro da string.

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'

str1 + ' ' + str2;


// "Howdy 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:

str = 'The answer to 7 + 4 is: '


answer = 7 + 4
str + answer

Experimente você mesmo


Você está acompanhando tudo isso no console JavaScript, certo? Definitivamente
experimente este!
O resultado é que, ao combinar uma string e um número, o JavaScript trata o
número como uma string (quando o JavaScript não consegue realizar uma operação
matemática com um dado tipo número, com outros dados apresentados por não
serem numéricos, ele converte o dado numérico para o formato dos outros dados, no
caso abaixo com strings):

str = 'The answer to 7 + 4 is: '


answer = 7 + 4
str + answer
// "The answer to 7 + 4 is: 11"

Mas cuidado! É fácil confundir o JavaScript quando se utiliza o operador + (mais).


Tente cada um dos seguintes no console JavaScript.

'The answer to 7 + 4 is ' + 7 + 4


'The answer to 7 + 4 is ' + (7 + 4)

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

É possível inverter Booleanos com o operador not (!). Em JavaScript, o ponto de


exclamação (!) é o operador not.

theOpposite = !yes
// false
theOppositeOfTheOpposite = !!yes
// true

Não usaremos booleanos diretamente assim com muita frequência. Normalmente


veremos operadores de comparação que fazem booleanos.

isTenGreaterThanSix = 10 > 6
// true
isTwelveTheSameAsEleven = 12 == 11
// false

A lista de operadores de comparação é a seguinte:

Operator Name Description


== Equal Are the two values equal?
Igual Os dois valores são iguais?
< Less than Is the first value less than the second?
Menor do que O primeiro valor é menor que o segundo?
> Greater than Is the first value greater than the second?
Maior do que O primeiro valor é maior que o segundo?
<= Less than or equal Is the first value less than or equal to the second?
Menor que ou igual O primeiro valor é menor ou igual ao segundo?
>= Greater than or equal Is the first value greater than or equal to the
s second?
Maior que ou igual O primeiro valor é maior ou igual aosegundo?
ATENÇÃO - Sinal de igual duplo vs. sinal de igual único
Um sinal de igual duplo (==) é uma expressão JavaScript que verifica se algo é
igual a outro. Ele não faz alterações em nada - ele apenas verifica os valores e
produz um valor booleano.
Como vimos ao longo do livro, um único sinal de igual (=) torna uma variável igual
a outra coisa. É chamado de operador de atribuição porque atribui um valor a uma
variável - ele muda essa variável.
Você pode estar se perguntando se é aconselhável ter dois operadores muito
diferentes que sejam tão semelhantes. Não é. Esta é uma fonte muito comum de
erros, mesmo para pessoas que já programam há anos. Mas como já existe há tanto
tempo, provavelmente não mudará tão cedo. Portanto, esteja atento a esses tipos de
erros.

Dois outros operadores trabalham com valores booleanos: o operador e (and)


(&&) e o operador ou (or) (||). Eles nos permitem perguntar se duas coisas são
verdadeiras, se uma das duas coisas é verdadeira e outras questões semelhantes. A
seguir, usamos esses operadores para descobrir se 1º de abril de 2025 é um dia de
semana ou fim de semana. Como mencionado anteriormente, o JavaScript maluco
pensa que janeiro é o mês 0 (zero), tornando abril o mês 3.

date = new Date(2025, 3, 1)


// Tue Apr 01 2025 14:00:00 GMT-0400 (EDT)

isWeekDay = date.getDay() != 0 && date.getDay() != 6


// true

isWeekend = date.getDay() == 0 || date.getDay() == 6


// false

O valor de getDay() é o dia da semana, começando com 0 para domingo e


terminando com 6 para sábado. Portanto, uma data é um dia da semana se
getDay() não for 0 e não for 6. Da mesma forma, uma data é um fim de semana se
getDay() for 0 ou 6. Mas não use || ou && muito — pode tornar o código difícil de ler.

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'
]

O número de coisas em uma lista é definido por “length” - “comprimento” da lista.

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]
// ???

Certifique-se de tentar isso no console JavaScript. A entrada 0 é “Star Wars”, a


primeira entrada da lista. A entrada 2 é “Indiana Jones e os Caçadores da Arca
Perdida”, a última entrada da lista. O comprimento da lista é 3 - 0, 1 e 2 entradas.
Então o que acontece quando você acessa AmazingMovies [3]? Experimente!
Para alterar um valor em uma lista, podemos usar o operador de atribuição.

amazingMovies[2] = 'Indiana Jones and the Last Crusade'


// "Indiana Jones and the Last Crusade"
Para adicionar um valor a uma lista, use push (), que retornará o novo
comprimento da lista.
amazingMovies.push('Wonder Woman')
// 4
amazingMovies[3]
// "Wonder Woman"

List (listas) são uma ótima maneira de armazenar um monte de informações.


Vejamos outra maneira, chamada de “maps” (mapas).

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"

Para alterar um valor ou adicionar um valor, podemos usar atribuição.

greatMovie['name'] = 'Toy Story 2'


// "Toy Story 2"

greatMovie['description'] = 'Woody is stolen by Al'


// "Woody is stolen by Al"

Veremos mapas novamente quando falarmos sobre sombreamento plano no


Capítulo 9, O que é todo esse outro código?. Os mapas também terão um papel
importante mais tarde.

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.

Executando o código somente se algo for verdadeiro


Às vezes, queremos pular o código. Por exemplo, provavelmente não queremos
animar a cena se o jogo acabou. Nesses casos, usamos a palavra-chave if (se).

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

if (gameOver && score > 100) console.log('Great Game!!!')


// "Great Game!!!"

Também podemos estender uma instrução if com else if e else.

score = 10

if (score > 100) console.log ('Great Game!!!')


else if (score > 20) console.log ('Game Over.')
else console.log ('Game Over :(')
// "Game Over :("

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.

for (var i=5; i>=0; i--) {


console.log('Loop #' + i)
}
//"Loop #5"
// "Loop #4"
// "Loop #3"
// "Loop #2"
// "Loop #1"
// "Loop #0"

Os loops são especialmente excelentes para trabalhar com listas.

amazingMovies = [
'Star Wars',
'The Empire Strikes Back',
'Indiana Jones and the Raiders of the Lost Ark'
]

for (var i=0; i<amazingMovies.length; i++) {


console.log('GREAT: ' + amazingMovies[i])
}
//"GREAT: Star Wars"
// "GREAT: The Empire Strikes Back"
// "GREAT: Indiana Jones and the Raiders of the Lost Ark"

No código acima, estamos usando a mesma variável de loop i e a mesma


expressão ++ para aumentar essa variável de loop. A condição que mantém o loop
em andamento é que i é menor que o comprimento de amazingMovies - enquanto i
for menor que 3, o loop continua.
Relembrando como procuramos as coisas em listas, na primeira vez no loop, i é
0, então registramos AmazingMovies [0] - Star Wars - no console. Na última vez no
loop quando i é menor que 3, registramos AmazingMovies [2] - Os Caçadores da
Arca Perdida - no console. Apenas certifique-se de não usar <= para decidir se um
loop de lista pode continuar. Você consegue adivinhar por quê? Experimente!

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.

O que vem a seguir


Este capítulo apresentou muitas informações. Não se preocupe se nem tudo fizer
sentido ainda. Ao trabalhar nos capítulos posteriores, volte aqui se tiver dúvidas.
Mais e mais disso começará a fazer sentido conforme você avança.
O material neste capítulo e no Capítulo 5, Funções: usar e usar novamente,
cobre uma grande parte da linguagem JavaScript. Você realmente sabe muito de
JavaScript agora! Mas não confunda saber muito com ser capaz de usá-lo bem. Nós
sabemos como fazer “palavras” e “sentenças” em JavaScript, mas ainda estamos
muito longe de escrever grandes histórias, e é por isso que tantos capítulos ainda
faltam neste livro!
Com isso em mente, vamos voltar a adicionar coisas legais ao nosso avatar!
Quando terminar com este capítulo, você:
- Saberá matemática ainda mais divertida para a programação 3D.
- Saberá como girar algo para ficar em uma direção específica.
- Será capaz de fazer animações suaves.
Capítulo 8

Projeto: Transformando Nosso Avatar


Estamos quase terminando de animar nosso avatar. No Capítulo 4, Projeto:
Movendo Avatares, aprendemos como fazer nosso avatar se mover. No Capítulo
6, Projeto: Movendo as mãos e os pés, fizemos o avatar parecer que estava
andando. Agora precisamos fazer com que pareça que pode virar quando mudamos
de direção.
Girar ou fazer piruetas não é novidade para nós - já podemos fazer o avatar
girar e se mover. Mas, desta vez, queremos fazer nosso avatar ficar de frente para
uma direção específica quando se move ou quando caminha.

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.

Voltando para a direção correta


Fazer com que o avatar fique voltado para a direção correta é bastante fácil -
especialmente com tudo o que já sabemos. Assim como fizemos quando
adicionamos o movimento de andar das mãos e dos pés, vamos adicionar outra
função para animar o giro de nosso avatar.
Vamos começar indo para a lista de coisas que controlam nosso avatar.

var clock = new THREE.Clock();


var isCartwheeling = false;
var isFlipping = false;
var isMovingRight = false;
var isMovingLeft = false;
var isMovingForward = false;
var isMovingBack = false;
No final da lista, adicione direction e lastDirection.

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.

Adicione-a abaixo da função animate ().

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();

Esconda o código e experimente!


Se tudo estiver funcionando, quando andamos para a esquerda ou direita, o
avatar fica voltado para a direção em que está se movendo:

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:

Direction Degrees Radians JavaScript


Forward 180° pi Math.PI
Right 90° pi ÷ 2 Math.PI/2
Left -90° -pi ÷ 2 -Math.PI/2
Backward 0° 0 0

Por que rotação.y?


Isso explica o número que usamos para a variável direction (direção) na função
turn, mas por que definimos rotação.y? Por que não rotation.z ou rotation.x?
Bem, para começar, nós já mudamos a rotation.x quando fazemos cartwheels
(piruetas) e a rotation.z quando nós viramos (flip). Portanto, faz sentido usarmos
outra coisa para girar (spin).
Definimos a rotation.y porque queremos girar o avatar em torno do eixo y.
Lembre-se de que, em 3D, o eixo y está apontando para cima e para baixo. Se
você imaginar uma haste espetada bem no meio do avatar, esse é o eixo y do
avatar:
Girar o avatar em torno deste pólo é o que significa girar o avatar em torno do eixo
y.
Não se esqueça do avatar.rotation!
Se você tentou virar marker.rotation em vez de avatar.rotation, deve ter notado
que não apenas o avatar girou, mas todo o resto parecia girar também. Isso ocorre
porque anexamos a câmera ao marker (marcador) do avatar:

var marker = new THREE.Object3D();


scene.add(marker);

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.

if (!isWalking ()) direction = 0;

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>

A primeira etapa no uso do Tween é adicionar sua função de atualização à nossa


função animate, logo acima da chamada para turn ().
function animate() {
requestAnimationFrame(animate);
» TWEEN.update();
turn();
walk();
acrobatics();
renderer.render(scene, camera);
}
animate();

Em seguida, precisamos mudar a função turn que acabamos de escrever. Em vez


de definir a direção imediatamente, iniciaremos uma interpolação que girará o avatar
na nova direção. Substitua a última linha da função turn () - aquela que define
avatar.rotation.y - conforme mostrado.

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 ();
}

Certifique-se de incluir as linhas que verificam e definem lastDirection. Essas


duas linhas primeiro verificam se a direção que o avatar está enfrentando agora é a
mesma que a direção que ele estava enfrentando da última vez que turn () foi
chamado. Não queremos animar uma virada para a esquerda se já estivermos
virando para a esquerda!
As três linhas tween (interpolação) incluídas na parte inferior fazem a rotação real.
A primeira linha diz que nossa interpolação deve mudar a rotação do avatar. A
segunda diz que queremos interpolar a rotação Y para parar em direction (direção) -
e que queremos 500 milissegundos (½ segundo). Por último, começamos a
interpolação!

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!

O código até agora


Se você quiser verificar novamente o código neste capítulo, vá para Código:
transformando nosso avatar.

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.

Quando terminar este capítulo, você saberá:


- Um pouco sobre como fazer páginas da web.
- Entenda o código inicial.
- Sinta-se à vontade para alterar o código inicial.
Capítulo 9

O que é todo esse outro código?


Pulamos muito para que pudéssemos começar a programar imediatamente.
Também valeu totalmente a pena - criamos e animamos formas, construímos e
controlamos um avatar e aprendemos algumas habilidades de depuração
impressionantes.
Embora tenhamos sido capazes de fazer tudo isso, o material acima de "COMECE
A CODIFICAR NA PRÓXIMA LINHA" é importante entender. E graças a todas as
habilidades que já adquirimos, será muito mais fácil entender o que todo aquele
outro código faz.

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).

Uma introdução rápida ao HTML


Bem no topo de nosso código está o seguinte HTML:

<body> </body>

HTML é a linguagem de marcação de hipertexto. HTML não é uma linguagem de


programação. Então, o que está atrapalhando nosso lindo código JavaScript?
É usado para construir páginas da web, mas não faz com que as páginas da web
façam coisas interessantes. Fazer coisas interessantes em páginas da web é o
motivo pelo qual o JavaScript foi inventado.
Mesmo não sendo uma linguagem de programação, ainda precisamos de HTML.
Uma vez que JavaScript é uma linguagem de programação da web, precisamos de
uma página da web onde possamos programar - mesmo que seja apenas uma
página simples.
A primeira linha contém tags <body>. Em vez de etiquetas de preços ou tags de
nomes, as tags HTML descrevem coisas que são úteis na web. A maioria das tags
HTML, como <body>, vêm em pares. A primeira parte do par é o nome da tag
cercada por um símbolo de menor que (<) e um símbolo de maior que (>) - como
<body>. A segunda parte é a mesma coisa, mas com uma barra (/) antes do nome
da tag - como </body>.
Os autores de HTML normalmente colocam textos, imagens, links para outras
páginas e muito mais entre as tags <body> e </body>. Como estamos programando,
não colocamos nada entre as tags <body>. Nossas páginas da web começam sem
“corpo”. Em vez disso, temos preenchido esse corpo vazio com nossas cenas 3D.
Para ter uma ideia rápida do que o HTML faz, adicione o seguinte HTML entre as
duas tags <body>, conforme mostrado:

<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>

ATENÇÃO - Ignorar avisos do 3DE para HTML


Seu código HTML pode receber avisos de X vermelho. Eles podem ser ignorados
com segurança. 3DE destina-se a editar JavaScript, não HTML, por isso ele pode
ficar confuso.
Se você ocultar o código no Editor de código 3DE, verá algo assim:
Este é um livro de JavaScript, não um livro de HTML, mas você já viu algumas das
possibilidades do HTML. Após as tags <body> está outra linha de HTML com as tags
<script> de abertura e fechamento.

<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!

Configurando a Cena (Scene)


Para fazer qualquer coisa na programação 3D, precisamos de uma cena. Pense
na cena como o universo em que toda a ação acontecerá.
As cenas são realmente simples de trabalhar. Adicionamos objetos a eles ao
longo do livro. Depois que as coisas foram adicionadas a uma cena, é função da
cena manter o controle de tudo. Na verdade, isso é tudo que precisamos saber
sobre as cenas - depois de criar uma, adicionamos muitas coisas a ela e a cena
cuida do resto.

O código a seguir em 3DE faz exatamente isso:


// 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);

O valor flat é um mapa, que descrevemos em Maps. Na época em que


introduzimos as malhas pela primeira vez no capítulo 1, usamos o valor flat para
tornar os pedaços de nosso material de capa planos e grossos. É daí que vem esse
valor fixo.
Finalmente, adicionamos uma luz à cena. Essa luz será útil quando começarmos a
explorar o Capítulo 12: Trabalhando com luzes e materiais.

Usar Câmeras para Capturar as Cenas


Scenes faz um ótimo trabalho em manter o controle de tudo, mas elas não nos
mostram o que está acontecendo. Para ver qualquer coisa na cena, precisamos de
uma câmera. Observe o seguinte código em 3DE:

// 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);

Isso pega o que está na cena e mostra em nossas telas de computador.

O objetivo do aspectRatio é determinar a forma da janela do navegador. É a


mesma coisa que as relações de aspecto para telas de cinema e aparelhos de TV.
Uma TV grande com proporção de 4:3 pode ter quatro metros de largura e três
metros de altura (OK, isso é uma TV muito grande). Uma tela 4:3 ainda maior pode
ter doze metros de largura e nove metros de altura (multiplique o 4 e o 3 em 4:3 por
3 para obter 12: 9). A maioria dos filmes hoje é feita em uma proporção de 16:9, o
que significa que uma tela de nove metros de altura teria dezesseis metros de
largura - quatro metros extras quando comparada com a proporção de 4:3 de
mesma altura.
Por que isso é importante para nós? Se você tentar projetar um filme feito em 16:9
em uma tela 4:3, muito esforço terá que ser feito. Da mesma forma, um filme 4:3
precisaria ser esticado para ser mostrado em uma tela 16:9. Em vez de alongar ou
espremer, a maioria dos filmes é cortada para que você perca aqueles quatro metros
de ação. Nossa biblioteca Three.js não corta - ela estica ou esmaga. Em outras
palavras, é muito importante obter a proporção certa.

Depois de construirmos uma nova câmera, precisamos adicioná-la à cena. Como


qualquer coisa na programação 3D, a câmera é colocada no centro da cena à qual a
adicionamos. Nós o movemos 500 unidades para longe do centro na direção Z
("fora" da tela) para que tenhamos uma boa visão do que está acontecendo no
centro da cena.

Usando um Renderizador para Projetar o que a


Câmera Vê
A scene (cena) e a camera são suficientes para descrever como a cena se parece
e de onde a vemos, mas é necessário mais uma coisa para mostrá-la na página da
web. Este é o trabalho do renderizador. Ele mostra, ou renderiza, a cena como a
câmera a vê:

// 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);

Quando criamos o valor do renderer (renderizador), usamos um mapa que define


“antialias” como true (verdadeiro). Antialiasing (suavização) é um termo de
computador que significa bordas suaves. Os gráficos 3D ficam mais bonitos quando
seus lados são lisos, é por isso que ativamos o anti-serrilhamento aqui.
Temos que dizer ao renderizador o tamanho da tela que ele irá desenhar.
Definimos o tamanho da visualização para ocupar toda a janela do navegador
(window.innerWidth e window.innerHeight).
Para incluir o renderizador na página da web, usamos sua propriedade
domElement. Um domElement é outro nome para uma tag HTML como as que
adicionamos anteriormente neste capítulo. Em vez de conter um título ou parágrafo,
este domElement contém nossos incríveis mundos 3D.
Adicionamos esse domElement ao document.body - que é a mesma tag <body>
anterior que continha o HTML. A função appendChild cuida de adicionar o
domElement ao corpo do documento. Se você está se perguntando por que temos
nomes como appendChild e domElement, tudo o que posso dizer é que estou feliz
por você ser um programador de jogos 3D, não um programador da web. Os
programadores da Web precisam usar nomes bobos (e difíceis de lembrar) como
esse o tempo todo.
Neste ponto, o renderizador pode desenhar na tela, mas ainda precisamos dizer a
ele para renderizar antes que qualquer coisa apareça. É aqui que renderer.render
() entra em jogo no final do seu código atual.

// Now, show what the camera sees on the screen:


renderer.render (scene, camera);

Pode parecer que o renderizador é um irmão ou irmã mais novo desagradável,


fazendo a coisa certa apenas quando somos extremamente específicos em nossas
instruções. Em certo sentido, isso é verdade, mas, em outro sentido, toda a
programação é assim. Até que digamos ao computador exatamente a maneira certa
de fazer algo, ele geralmente faz algo completamente inesperado.
No caso do renderizador, já podemos ver porque é bom ter esse tipo de controle.
Em alguns de nossos experimentos, renderizamos apenas uma vez. Mas em muitos
de nossos projetos, renderizamos repetidamente dentro de uma função animar. Sem
esse tipo de controle, seria muito mais difícil escolher o estilo de renderização
correto.

Explorando Câmeras Diferentes


Você deve ter notado que chamamos nossa câmera de PerspectiveCamera. Se
esse nome parece estranhamente específico, é porque existem outros tipos de
câmeras. Na maior parte, queremos ficar com câmeras em perspectiva na
codificação 3D. Vamos dar uma olhada no que é uma câmera em perspectiva e
compará-la com outro tipo de câmera que às vezes também é usada.

Uma Olhada Rápida em uma Câmera com um Nome


Estranho
O outro tipo de câmera é chamado de orthographic (ortográfica). Para entender o
que uma câmera ortográfica faz, vamos adicionar uma estrada vermelha pela qual o
monstro da fruta roxa pode viajar. Adicione o seguinte após: COMEÇAR A
CODIFICAÇÃO NA PRÓXIMA LINHA:
var shape = new THREE.CubeGeometry(200, 1000, 10);
var cover = new THREE.MeshBasicMaterial({color:'darkred'});
var road = new THREE.Mesh(shape, cover);
scene.add(road);
road.position.set(0, 400, 0);
road.rotation.set(-Math.PI/4, 0, 0);

Nossa câmera em perspectiva torna a estrada mais ou menos assim:

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:

var aspectRatio = window.innerWidth / window.innerHeight;


var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);

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.

O que vem a seguir


Agora que entendemos tudo sobre câmeras, cenas e carregamento de JavaScript
de outro lugar, vamos mudá-los cada vez mais. Mas primeiro, vamos ensinar nosso
avatar do game a não (not) andar por através as árvores, ou seja, quando topar com
uma árvore, ou qualquer outro elemento sólido pela frente, ele perceber e colidir
(não atravessar) com o elemento.

Quando terminar este capítulo, você será capaz de:


- Impedir que os elementos do jogo se movam entre si.
- Entender as colisões, que são importantes no jogo.
- Ter cercas invisíveis em torno de nossas árvores.

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.

O Código Quase Nunca Faz o que Queremos


O Código faz apenas o que mandamos fazer. Com muita frequência, ele faz o
oposto do que queremos. Isso pode ser uma das coisas mais frustrantes da
programação. Mas, finalmente, fazer com que o código faça o que você quer é uma
das coisas mais divertidas da programação!

Neste capítulo, vamos resolver nosso problema de avatar-em-uma-árvore usando


detecção de colisão. Vamos atualizar nosso código para detectar quando nosso
jogador está colidindo com árvores e impedir que o avatar avance.
A detecção de colisão é importante em quase qualquer jogo ou simulação. Existem
diferentes tipos de detecção de colisão. Neste capítulo, descreveremos e falaremos
sobre uma abordagem de ray casting (lançamento de raio) para detectar colisões.
A melhor coisa sobre ray casting (o lançamento de raios) é que é tão simples. No
entanto, não confunda simples com básico. Mesmo que nossa detecção de colisão
seja simples, é poderosa. É tão poderoso que os programadores costumam usá-lo
em jogos avançados e sofisticados, especialmente quando a velocidade é
necessária.
Então, vamos mergulhar!

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.

Os Programadores Gostam de dar Nomes Extravagantes a Ideias Simples


Saber os nomes de conceitos simples torna mais fácil falar com outras pessoas que
fazem o mesmo trabalho. Os programadores chamam esses nomes de padrões.

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.

Se, a qualquer momento, descobrirmos que o próximo movimento colocaria o raio


do avatar onde ele apontaria através do círculo, paramos o avatar de se mover.
Pense no raio como um feixe de laser. Começa a partir do avatar e aponta para
baixo.
Se esse laser lança sua luz sobre o círculo da árvore, detectamos uma colisão. Isso
é tudo que há para fazer!

É 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.

Então, nós trapaceamos. Pensamos em colisões em apenas duas dimensões (X e


Z), ignorando completamente a dimensão Y para cima e para baixo.

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:

var notAllowed = [];

Lembre-se de Listando Coisas, que os colchetes [ ], são a maneira do JavaScript


de fazer listas. Aqui, nossos colchetes vazios criam uma lista vazia. A variável
notAllowed é uma lista de espaços em que o avatar não é permitido e que, esta
lista, ainda está vazia, ou seja, não contém nenhum elemento dentro dela.

Faremos a próxima alteração dentro da função makeTreeAt. Quando fizermos


nossa árvore, faremos os limites também. Adicione o seguinte código após a linha
que adiciona a copa da árvore ao tronco. Deve estar logo acima da linha que define
a posição do tronco com trunk.position.set (x, -75, z).

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);

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;
}

Esta função retorna um booleano - uma resposta verdadeira ou falsa - dependendo


se o avatar está colidindo com um limite. Para obter essa resposta, criamos um raio
e verificamos se ele aponta através de alguma coisa. Conforme descrito
anteriormente, um raio é a combinação de uma direção - ou vetor (para baixo, em
nosso caso) e um ponto (neste caso, o marcador de posição - maker.position - do
avatar). Em seguida, perguntamos a esse raio se ele atravessa (secciona) qualquer
um dos objetos não permitidos - notAllowed. Se o raio cruzar um desses objetos,
então a variável intersects (de intersecção) terá um comprimento maior que 0
(zero). Nesse caso, detectamos uma colisão e retornamos true (verdadeiro). Caso
contrário, não há colisão e retornamos false (falso).

As colisões são um problema difícil de resolver em muitas situações, então você


está indo muito bem acompanhando isso. Mas ainda não terminamos. Agora
podemos detectar quando um avatar está colidindo com um limite, mas ainda não
impedimos o avatar. Vamos fazer isso no código que lida com as teclas do teclado
sendo pressionadas. Quando uma tecla de seta é pressionada, nossa função
sendKeyDown () muda a posição do avatar.

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.

O código até agora


Se você gostaria de verificar novamente o código neste capítulo, está em Código:
Colisões.

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

Projeto: Caça às Frutas


Temos um avatar. Temos árvores. Certamente há diversão para o avatar ter
nessas árvores. Então, vamos fazer disso um jogo!
Passamos 10 capítulos falando sobre a construção de um avatar e uma área de
jogo. Agora você tem uma boa ideia do que é necessário para codificar essas
coisas. Agora, este não vai ser o melhor jogo de todos. Nosso jogo será um tanto
simples, mas o suficiente para lhe dar ideias do que você pode querer adicionar a
seguir.
Nosso jogo vai desafiar o avatar a tirar coisas daquelas árvores. As árvores estão
escondendo frutas gostosas que o avatar deseja. E se o avatar conseguir chegar à
fruta a tempo, marcará pontos. Vai ficar parecido com isto:

Muito obrigado à colega programadora de jogos Sophie H. por criar o conceito de


jogo usado neste capítulo!

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.

Iniciando um Placar do Zero


O resto do código neste capítulo irá para o lugar usual, abaixo da linha INICIAR
CODIFICAÇÃO NA PRÓXIMA LINHA. Mais especificamente, adicione o seguinte
código do placar após os códigos do avatar e do marker (marcador), mas acima da
função makeTreeAt ().
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!'
);

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.

Dando um pequeno balanço nas árvores


O objetivo deste jogo será encontrar frutas nas árvores. A fruta será o tesouro do
nosso jogo. A qualquer momento, apenas uma árvore terá um tesouro. Para mostrar
de que árvore é, vamos dar uma sacudidela. Mas primeiro precisamos de uma lista
de árvores.
No Capítulo 10, Projeto: Colisões, adicionamos uma lista de limites
notAllowed=[], acima da função makeTreeAt (). Agora, adicionamos uma lista de
treeTops logo abaixo, após notAllowed.

var notAllowed = []; (Esta linha existe acima da função makeTreeAt)


var treeTops = [];

Em seguida, dentro do corpo da função makeTreeAt (), abaixo da linha:


notAllowed.push(boundary); vamos adicionar a linha: treeTops.push(top); (isto
coloca as copas das árvores dentro da lista de topos da árvore - treeTops [top].

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:

① Atualiza o número da árvore - chamando a função


updateTreasureTreeNumber () que acabamos de escrever.

② Define o ponto de partida, que é 0 shakes.

③ 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.

④ Define a função que continua sendo chamada conforme o valor muda.


Essa função é chamada centenas de vezes conforme a interpolação se
move entre o ponto inicial e o ponto final.

⑤ Define a função que é chamada uma vez no final.

⑥ Começa a tremer!

Não é nenhuma surpresa que usamos updateTreasureTreeNumber () para


definir o número da árvore - é por isso que escrevemos essa função. O resto da
função é outra interpolação (tween) - embora um pouco diferente da interpolação
(tween) que vimos no Capítulo 8, Projeto: Transformando nosso avatar.
Essa interpolação (tween) vai se mover para frente e para trás. No Capítulo 6,
Projeto: Movendo as mãos e os pés, vimos que Math.sin () é ótimo para ir e vir.
Portanto, essa interpolação (tween) usará Math.sin () para sacudir a árvore.
A interpolação (tween) começa a agitação em 0 e se move para 20 vezes 2 × pi -
(20 2 Math.PI). No Capítulo 7, Uma análise mais aprofundada dos fundamentos
do JavaScript, vimos que ir de 0 a 2 × pi em Math.sin () começa em 0, vai e volta e
para em 0. Então, 20 vezes isso é 20 balançadas para trás e adiante.
Quando usamos uma interpolação (tween) para virar nosso avatar, deixamos a
interpolação alterar a rotação do avatar diretamente. Nesse caso, queremos
balançar o topo da árvore para frente e para trás. Precisamos chamar Math.sin ()
para isso, e é por isso que temos que dizer ao interpolador (tween) para chamar
shakeTreeUpdate () com atualizações.
Na verdade, ainda não criamos shakeTreeUpdate (), então adicione o seguinte
shakeTreasureTree ():

function shakeTreeUpdate(update) {
var top = treeTops[treasureTreeNumber];
top.position.x = 50 * Math.sin(update.shake);
}

Sempre que o interpolador chama essa função com uma atualização -


provavelmente cerca de 50 vezes por segundo, essa função encontra a copa da
árvore correta e move a posição com Math.sin ().
Quando a interpolação estiver concluída, dizemos a ela para chamar a função
shakeTreeComplete ().
Adicione essa função conforme mostrado:

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.

Saltando por pontos


Para marcar pontos, o avatar precisa pular ao lado da árvore atual cheia de
tesouros. Faremos duas coisas quando isso acontecer: marcar alguns pontos e fazer
uma pequena animação do tesouro.
Mas primeiro precisamos de uma chave que iniciará um salto. Para fazer isso,
adicionamos a seguinte instrução if à função sendKeyDown ():

if (code == 'Space') jump ();

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();
}

A função checkForTreasure pode parecer um pouco grande, mas na verdade faz


apenas duas coisas:
1. Calcule a distância entre a árvore do tesouro atual e o avatar
2. Se essa distância for inferior a 500, adicione alguns pontos ao placar

A maior parte de checkForTreasure () calcula a distância entre o avatar e a


árvore. Ele encontra a copa da árvore repleta de tesouros. Em seguida, precisamos
obter o "pai" da copa da árvore - o tronco da árvore ao qual a copa foi adicionada.
Definimos dois valores de posição: a posição da árvore e a do avatar. Usando essas
posições, obtemos as diferenças na posição x e na posição z. Uma vez que temos
as diferenças, a distância é fácil: a raiz quadrada de xDiff ao quadrado mais zDiff ao
quadrado.

Alerta do teorema de Pitágoras


Se você já aprendeu um pouco de trigonometria, pode ter reconhecido o teorema
de Pitágoras na função checkForTreasure. Usamos para encontrar a distância
entre dois pontos: o avatar e a árvore ativa.
Se você ainda não viu o teorema de Pitágoras na escola, agora você tem ainda
mais motivos para prestar atenção quando o vê!

Se a distância for inferior a 500, chamamos a função scorePoints (). Por


enquanto, mantemos essa função muito simples - se o tempo restante for 0, não
faremos nada; caso contrário, adicionamos 10 pontos ao placar. Adicione
scorePoints () após a função checkForTreasure.

function scorePoints () {
if (scoreboard.getTimeRemaining () == 0) return;
scoreboard.addPoints (10);
}

Certifique-se de adicionar a primeira linha a essa função; caso contrário, os


jogadores podem obter pontos após o tempo expirar!
A última coisa que precisamos fazer é animar o salto para que possamos vê-lo na
tela. O avatar deve começar em y de 0 e terminar em y de 0, subindo e descendo
suavemente. Se você adivinhou que é hora de outro tween, você está correto!
Adicione a função animateJump () abaixo de scorePoints ().

function animateJump () {
var tween = new TWEEN.Tween({jump: 0});
tween.to({jump: Math.PI}, 400);
tween.onUpdate(animateJumpUpdate);
tween.onComplete(animateJumpComplete);
tween.start();
}

Isso é muito semelhante à interpolação (tween) que usamos para sacudir as


árvores. Ele começa o salto (jump) em 0. Ele termina o salto (jump) em Math.PI
após 400 milissegundos. Lembre-se de que Math.sin () de 0 é 0. Math.sin () então
aumenta para 1 e volta para 0 quando chegamos a Math.PI, tornando esse o local
perfeito para encerrar o salto (jump).
Assim como a interpolação (tween) de trepidação da árvore, temos funções a
serem chamadas quando a interpolação estiver sendo atualizada e quando for
concluída. Adicione essas funções após animateJump ():
function animateJumpUpdate(update) {
avatar.position.y = 100 * Math.sin(update.jump);
}

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.

Tornando nossos jogos ainda melhor


Passamos muito tempo neste livro adicionando animações ao nosso avatar.
Fazemos isso em parte para entender conceitos importantes como agrupar objetos,
mas também porque isso é muito do que os programadores de jogos 3D fazem.
O que torna um jogo atraente e divertido o suficiente para fazer os jogadores
voltarem é uma combinação de jogabilidade interessante e os vislumbres ocasionais
de realismo. Nosso avatar não precisa realmente de mãos e pés que se movam
como na vida real, mas esta animação ajuda a tornar o jogo mais real. Neste jogo, a
jogabilidade é bastante simples: pressione a barra de espaço perto do tesouro para
ganhar pontos.

Adicionando Animação e Som


Quantos ajustes você adiciona depende de você, o programador do jogo. Mas, para
este capítulo, vamos adicionar duas coisas quando o avatar receber a fruta do
tesouro: um pequeno som e uma animação da fruta que o avatar acabou de obter.
Adicionar som ao jogo é o mais fácil dos dois, então vamos lidar com isso primeiro.
Na função scorePoints, adicione uma chamada para Sounds.bubble.play.

function scorePoints() {
if (scoreboard.getTimeRemaining() == 0) return;
scoreboard.addPoints(10);
» Sounds.bubble.play();
}

Você pode encontrar mais informações na biblioteca Sounds.js em Sounds.js. A


biblioteca tem um número relativamente pequeno de sons para escolher, mas o
suficiente para começar a escrever jogos.
Com essa linha adicionada, podemos marcar pontos e ouvir o som quando o
avatar pula para pegar uma fruta-tesouro. Mas na verdade não estamos vendo
nenhuma dessas frutas douradas.
Para ver e animar a fruta, precisamos adicionar a fruta ao marcador (marker) do
avatar e, em seguida, interpolar (tween). A interpolação será um pouco diferente das
que fizemos até agora, pois animará duas coisas. Ele vai subir acima do avatar e vai
girar. O código a seguir, que podemos adicionar após a função scorePoints, fará
tudo isso:

var 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();
}

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();
}

O resultado é uma bela animação que é reproduzida enquanto o avatar coleta


frutas.
Uau! Ponto!

O que Mais Podemos Adicionar?


É para o nosso avatar que construímos do zero, começando no Capítulo 3,
Projeto: Fazendo um Avatar. Isso não significa que você não possa tornar este
jogo ainda melhor!
É muito fácil pegar o fruto de uma árvore neste jogo. Talvez você possa adicionar
um ajuste em que o avatar tem permissão para apenas um pedaço de fruta de uma
árvore? Também pode ser bom penalizar um jogador - pense em subtractPoints -
se o avatar pular quando a árvore não estiver ativa e se mexendo. Se você acha que
o player está se movendo muito rápido ou muito devagar, procure na função
sendKeyDown () maneiras de melhorar isso. Você pode construir o jogo para ter
todos os tipos de cantos e recantos e prêmios.
Este é o trabalho do designer do jogo, que por acaso é você. Faça uma cópia do
código até agora e veja o que você pode adicionar para fazer o jogo funcionar da
maneira que você deseja.
Como você vai tornar este jogo excelente?

O código Até Agora


Se você quiser verificar novamente o código neste capítulo, vá para Código: caça
às frutas.

O que vem a seguir


Isso pode ser para nossos projetos de avatar, mas ainda há muito o que fazer. A
seguir, vamos explorar mais os pequenos toques que entram na programação 3D,
começando com luzes, materiais e sombras.

Quando terminar este capítulo, você será capaz de:


- Fazer formas tão brilhantes quanto quiser
- Saiber como fazer sombras em jogos 3D
- Ser capaz de adicionar texturas para tornar as formas 3D ainda mais realistas
Capítulo 12

Trabalhando com Luzes e Materiais

Neste capítulo, vamos cobrir como construir formas e materiais interessantes que
pareçam com isso:

Voltando ao Capítulo 1, Projeto: Criando formas simples, apresentamos a malha


como a combinação de formas e coberturas. Desde então, falamos muito sobre
formas - os diferentes tipos, como podemos combiná-los, como podemos movê-los e
muito mais. Mas, além de mudar as cores, realmente não falamos sobre o que é
possível com capas de malha.
É incrível o que podemos realizar. Então vamos fazer 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.

var shape = new THREE.TorusGeometry(50, 20, 8, 20);


var cover = new THREE.MeshPhongMaterial({color: 'red'});
var donut = new THREE.Mesh(shape, cover);
donut.position.set (0, 150, 0);
scene.add(donut);

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 ().

var clock = new THREE.Clock();


function animate () {
requestAnimationFrame(animate);
var t = clock.getElapsedTime();

// Animation code goes here...


» donut.rotation.set (t, 2*t, 0);
renderer.render (scene, camera);
}
animate ();
Uma última coisa a ser configurada é a localização da câmera. Será mais fácil ver
nossa cena se a câmera estiver mais alta, olhando para baixo. Portanto, acima da
linha START CODING, adicione uma posição y para a câmera e aponte a câmera
para o centro da cena.

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);

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.

var light = new THREE.AmbientLight('white', 0.8);


» //scene.add(light);

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.

var shape = new THREE.TorusGeometry(50, 20, 8, 20);


var cover = new THREE.MeshPhongMaterial({color: 'red'});
» cover.emissive.set('yellow');
var donut = new THREE.Mesh(shape, cover);
donut.position.set(0, 150, 0);
scene.add(donut);
Com isso, nosso donut vermelho deve estar brilhando 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).

» var light = new THREE.AmbientLight('white', 0.1);


» scene.add(light);

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.

var point = new THREE.PointLight('white', 0.8);


point.position.set (0, 300, -100);
scene.add(point);

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.

var shape = new THREE.SphereGeometry(10);


var cover = new THREE.MeshPhongMaterial({emissive: 'white'});
var phonyLight = new THREE.Mesh(shape, cover);
point.add(phonyLight);
Só porque é falso, não significa que temos que fazer com que pareça falso. Damos
a ele uma cor emissiva de branco brilhante para que emita branco - como uma
lâmpada 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.

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);
scene.add(donut);

Estamos usando cores vermelho-verde-azul como fizemos para construir cores


aleatórias no Capítulo 5, Funções: Use e Use novamente. Mas aqui, não estamos
criando cores, apenas variações de cinza. Quando todos os valores RGB são iguais,
você fica cinza. Quando estão perto de 0, um cinza quase preto é produzido.
Quando todos eles estão próximos de 1, você obtém um cinza muito claro, quase
branco.
E quando essa cor especular é clara, vemos mais do brilho como mostrado na
figura.
Agora conhecemos todos os diferentes tipos de cores que trabalham juntas para
adicionar realismo a objetos 3D. Vamos dar uma olhada em algo ainda mais legal:
sombras.

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.

var shape = new THREE.PlaneGeometry (1000, 1000, 10, 10);


var cover = new THREE.MeshPhongMaterial();
var ground = new THREE.Mesh(shape, cover);
ground.rotation.x = -Math.PI/2;
scene.add(ground);

Isso cria um plano, gira-o plano e adiciona-o à cena - sem sombras. Para ativar as
sombras, seguimos estas quatro etapas:

1. Habilite sombras no renderizador.


2. Ative sombras em uma luz.
3. Ative sombras no objeto que projeta uma sombra.
4. Ative sombras no objeto sobre o qual a sombra incide.

O código 3D nem se preocupa com sombras, a menos que estejam habilitadas no


renderizador. Para fazer isso, defina a propriedade shadowMap.enabled no
renderizador, que está acima da linha START CODING.

var renderer = new THREE.WebGLRenderer({antialias: true});


» renderer.shadowMap.enabled = true;
renderer.setSize (window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
A menos que digamos o contrário, as luzes não projetam sombras. Então, vamos
habilitar sombras de nosso ponto de luz.

var point = new THREE.PointLight('white', 0.8);


point.position.set (0, 300, -100);
» point.castShadow = true;
scene.add(point);

Em seguida, queremos que nosso donut projete uma sombra, então habilite a
propriedade castShadow no donut.

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);

Por último, dizemos ao solo que ele recebe uma sombra.

var shape = new THREE.PlaneGeometry(1000, 1000, 10, 10);


var cover = new THREE.MeshPhongMaterial();
var ground = new THREE.Mesh(shape, cover);
ground.rotation.x = -Math.PI/2;
» ground.receiveShadow = true;
scene.add(ground);

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.

Holofotes e luz solar


Até agora, vimos dois tipos de luzes:

- Point, que atua como uma lâmpada


- Ambient, que fornece uma pequena quantidade de luz em todos os lugares

Duas outras luzes valem uma olhada rápida:

- Spotlight, que é ótimo para focar a luz em uma única posição


- Direcional, o que cria sombras como a luz do sol
A diferença entre as luzes é mais fácil de ver com objetos em movimento. Portanto,
na função animate (), adicione o código a seguir para alterar a posição do donut.

donut.rotation.set(t, 2*t, 0);


» donut.position.z = 200 * Math.sin(t);

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);

Agora é hora de adicionar um spotlight (destaque). Adicione o seguinte código


após a luz pontual falsa (de araque, de imitação):

var spot = new THREE.SpotLight('white', 0.4);


spot.position.set(200, 300, 0);
spot.castShadow = true;
spot.shadow.camera.far = 750;
scene.add(spot);

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.

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);

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.

var spot = new THREE.SpotLight('white', 0.4);


spot.position.set(200, 300, 0);
spot.castShadow = true;
spot.shadow.camera.far = 750;
» //scene.add(spot);

Então, abaixo do código falso do holofote, adicione uma luz direcional, colocando-a
nas mesmas coordenadas que usamos para o holofote.

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;

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.

» 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;
var ground = new THREE.Mesh(shape, cover);
ground.rotation.x = -Math.PI/2;
ground.receiveShadow = true;
scene.add(ground);

Isso faz um piso de madeira muito bonito para nossa cena.


Chamar a propriedade de “map” pode parecer um pouco estranho. Ele recebe o
nome da maneira como o código 3D aplica imagens quadradas - como
hardwood.png - a formas que não são quadrados. A imagem deve ser “mapeada”
da melhor maneira possível em esferas e cilindros. Veremos um exemplo disso no
Capítulo 13, Projeto: Fases da Lua.
Nossa coleção de código 3D inclui várias imagens de textura que podem substituir
hardwood.png:
brick.png, floor.png, grass.png, ground.png, metal.png, rock.png e wood.png.
Experimente-os!

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!

Obtendo uma Visão Melhor


Esta é uma criação impressionante. Para obter uma melhor visualização, podemos
adicionar controles de “órbita” do mouse à cena. Esses controles nos permitem clicar
e arrastar com o mouse para girar a cena em torno do centro.
Comece carregando na coleção de códigos de controles de órbita, no topo de
nosso código.

<script src="/three.js"></script>
» <script src="controlsOrbitControls.js"></script>

Em seguida, logo acima da função animate (), adicione os controles da seguinte


maneira:

controls = new THREE.OrbitControls (camera, renderer.domElement );

É 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!

O código até agora


Se você gostaria de verificar o código neste capítulo, vá para Código:
Trabalhando com luzes e materiais.

Qual o Próximo Assunto


Luzes e materiais são tópicos avançados e nós apenas arranhamos a superfície do
que é possível. Eles podem ter um grande impacto nos jogos. Só não fique muito
louco com eles. Eles podem fazer o computador trabalhar muito, tornando o jogo
mais lento. Além disso, a jogabilidade é mais importante do que qualquer outra
coisa. Esta é uma lição importante em qualquer tipo de programação, não apenas
em jogos de JavaScript: só porque você pode, não significa que deve. Os melhores
programadores do mundo conhecem bem esta regra. E agora você também! Vamos
colocar nossas novas habilidades de iluminação em bom uso no próximo capítulo,
enquanto simulamos as fases da Lua.

Quando terminar com este capítulo, você:

- Terá um truque importante para a caixa de ferramentas virtual do seu


programador 3D.
- Será capaz de alternar entre várias câmeras.
- Saberá como e por que manter o código do jogo e da animação separados
- Entenda as fases da Lua melhor do que a maioria das pessoas (mas não os
animadores de Toy Story).
Capítulo 13

Projeto: Fases da Lua

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.

var scene = new THREE.Scene();


var flat = {flatShading: true};
» var light = new THREE.AmbientLight('white', 0.1);
scene.add(light);

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:

var aspectRatio = window.innerWidth / window.innerHeight;


» //var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
» 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;

Usamos a largura e a altura da janela do navegador para criar a câmera


ortográfica, assim como fizemos em Uma Espiada em uma Câmera com um
Nome Estranho. Também movemos a câmera 500 acima do sistema solar - 500 na
direção Y - em vez de 500 na direção Z. Depois disso, giramos a câmera para
apontar para o centro da cena.

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).

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);

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);

Em seguida, adicionamos a Terra à cena. Novamente fazemos a combinação usual


de capa e forma para fazer uma malha, mas com um toque divertido.

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);
earth.position.x = 300;
scene.add(earth);

Como vimos no Capítulo 12: Trabalhando com luzes e materiais, carregamos


uma imagem da Terra. Em seguida, mapeamos essa imagem na esfera usando a
propriedade map do material Phong.

É 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.

Lógica de Jogo e Simulação


Faremos uma nova função para mover e girar a Terra. Poderíamos colocar isso
dentro da função animate (), mas separar o código do jogo ou da simulação do
código da animação tornará nosso código mais flexível. Os programadores de jogos
geralmente se referem a isso como separar a lógica do jogo do código de animação.
As propriedades que controlaremos nesta simulação são a velocidade com que ela
é executada, se a simulação está ou não pausada, e o número de dias que se
passaram dentro da simulação. Também precisaremos de outro relógio interno para
ajudar a rastrear o tempo dentro da simulação.
Adicione o seguinte no final do nosso código, abaixo da chamada para animate ():

var speed = 10;


var pause = false;
var days = 0;
var clock2 = new THREE.Clock();

Abaixo disso, adicionamos uma função gameStep ().

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.

Se esperássemos 1000 milissegundos, a simulação seria atualizada uma vez por


segundo. Aqui, esperamos 1000 dividido por 30. Em outras palavras, atualizamos
nossa simulação 30 vezes por segundo. Isso pode parecer muito, mas a animação é
atualizada cerca de 60 vezes por segundo graças a requestAnimationFrame (),
que é uma função especial que vimos no final do Capítulo 5, Funções: Use e Use
novamente. Mover a lógica do jogo para uma função separada que é chamada com
menos frequência ajudará a execução da animação de maneira mais suave.

No restante de gameStep (), retornamos imediatamente - deixamos a função sem


fazer nada - se a simulação for pausada. Em seguida, atualizamos o número de dias
que se passaram. Perguntamos ao clock2 quanto tempo se passou desde a última
vez que perguntamos. A resposta de getDelta () será muito pequena - perto de 1/30
de segundo. Pegamos esse pequeno número, multiplicamos pela velocidade e
adicionamos ao número de dias que já se passaram.

Podemos usar o número de dias atribuindo-o à rotação da Terra. Ou seja, a rotação


sobre o eixo Y - o eixo para cima e para baixo - será igual ao número de dias que se
passaram.

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 ().

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();
}
function pauseUnpause () {
pause = !pause;
clock2.running = false;
}

A função sendKeyDown altera a velocidade da simulação de lenta para rápida


quando as teclas 1, 2, 3 e 4 são pressionadas. Isso funcionará apenas com as teclas
numéricas na parte principal do teclado. Para trabalhar com um teclado numérico,
teríamos que usar Numpad1, Numpad2, Numpad3 e Numpad4 em vez das
strings que começam com “Dígit”. Quando a tecla P é pressionada, ele chama a
função pauseUnpause ().

Essa função pauseUnpause () usa o ponto de exclamação que vimos no Capítulo


7, Uma análise mais detalhada dos fundamentos do JavaScript para alternar a
pausa (pause) para frente e para trás. Também dizemos ao clock2 que ele não está
mais funcionando. Não nos preocupamos em dizer ao clock2 quando ele está
funcionando novamente - da próxima vez que pedirmos getDelta (), o clock2 será
reiniciado automaticamente.
Com isso, devemos ser capazes de ocultar o código e pausar e retomar a
simulação da rotação da Terra e da translação em torno do sol. Podemos diminuir a
velocidade pressionando 1 e aumentar a velocidade com 2, 3 e 4. Isso é muito legal,
mas as coisas realmente legais começam a seguir!

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:

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);

É semelhante ao que fizemos com a Terra, mas com algumas mudanças.


Obviamente, usamos uma imagem da Lua em vez da Terra para a textura mapeada.
Também tornamos a cor especular preta porque a Lua quase não tem brilho. É
menor do que a geometria da Terra, e giramos para que o lado correto da Lua esteja
voltado para nós.
Vamos tentar adicionar a Lua à 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.

Portanto, acima do código da Terra, precisamos adicionar earthLocal.

var earthLocal = new THREE.Object3D ();


earthLocal.position.x = 300;
scene.add(earthLocal);

Assim que tivermos as coordenadas locais para a Terra, adicionamos a malha da


Terra a ela. Altere o código conforme mostrado para adicionar o Earth ao novo
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);
» //earth.position.x = 300;
» earthLocal.add(earth);

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);
}

Neste ponto, a Lua está novamente girando descontroladamente ao redor da


Terra uma vez por dia. Pode parecer que não fizemos nenhum progresso, mas foi
uma grande melhoria. Podemos realmente fazer a Lua orbitar a Terra com apenas
quatro linhas de código.
Acima do código para a Lua, adicione outro sistema de coordenadas local - este é
para a órbita da Lua.

var moonOrbit = new THREE.Object3D();


earthLocal.add(moonOrbit);
Isso também adiciona a órbita da Lua ao sistema de coordenadas local da Terra. À
medida que o sistema de coordenadas locais da Terra muda de posição, a órbita da
Lua segue com ele - assim como nosso avatar fez quando seu marcador foi movido.
Essas são duas das quatro linhas prometidas. Em seguida, adicionamos a Lua à
órbita da Lua em vez de à Terra.

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);

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;
}

Com isso, a Lua deve estar viajando com, E ao redor, da Terra!

Nota - Não Subestime o Poder das Coordenadas Locais


Esta é a segunda vez que usamos as coordenadas locais em nossa programação
3D. Não será a última. Mover um monte de coisas juntas e ainda ser capaz de mover
e girar apenas algumas delas é poderoso. Tão poderoso que quase parece uma
trapaça. Se parece trapacear na programação, provavelmente você está fazendo
algo certo!

Ação com várias câmeras!


Temos uma bela simulação do Sol, da Terra e da Lua sobre do sistema solar.
Vamos adicionar uma segunda câmera à cena para que possamos mudar a visão de
cima do sistema solar para uma visão da Terra. Isso nos ajudará a entender melhor
as fases da lua.
Lembre-se da parte de Introdução deste capítulo que chamamos nossa câmera
atual aboveCam. Vamos adicionar moonCam, que mostrará a Lua vista da Terra.
Adicione o seguinte código abaixo do código para a Lua:

var moonCam = new THREE.PerspectiveCamera(70, aspectRatio, 1, 10000);


moonCam.position.z = 25;
moonCam.rotation.y = Math.PI;
moonOrbit.add(moonCam);

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();

» if (code == 'KeyC') switchCamera();


}

Quando a tecla C é pressionada, a função switchCamera () é chamada. Temos


que adicionar essa função a seguir, o que fazemos abaixo da função
pauseUnpause () na parte inferior de nosso código.

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();

O PointsMaterial é semelhante a outros materiais que vimos - podemos até definir


a cor para branco. O tamanho que especificamos é o tamanho dos pontos quando
adicionarmos a malha à cena. Os pontos estarão longe, então os fazemos 15 para
serem grandes o suficiente para serem vistos.
A forma que estamos usando é uma geometria básica. Não é um cubo. Não é um
cilindro. Não é uma esfera. Ainda nem é realmente uma forma. Temos que adicionar
pontos a ele antes que tenha qualquer estrutura.
Adicionamos essa estrutura com muita ajuda de nossos amigos matemáticos.

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);
}

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.

var stars = new THREE.Points (shape, cover);


scene.add(stars);

Com isso, temos as estrelas!

Bônus # 2: Controles de Vôo


Se fizermos uma simulação espacial, vamos querer voar através dela, certo?
Podemos usar os mesmos controles que usamos para voar através dos planetas no
Capítulo 5, Funções: Use e Use novamente. Comece carregando a coleção de
códigos dos controles de vôo no topo de nosso código:
<body></body>
<script src="/three.js"></script>
» <script src="controlsFlyControls.js"></script>

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:

var shipCam = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);


shipCam.position.set (0, 0, 500);
scene.add(shipCam);

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.

var controls = new THREE.FlyControls(shipCam, renderer.domElement);


controls.movementSpeed = 42;
controls.rollSpeed = 0.15;
controls.dragToLook = true;
controls.autoForward = false;

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.

var clock = new THREE.Clock();


function animate() {
requestAnimationFrame(animate);
» // var t = clock.getElapsedTime ();

// Animation code goes here...


» var delta = clock.getDelta ();
» controls.update (delta);
renderer.render (scene, camera);
}
Animate ();
Finalmente, adicione uma instrução if dentro de sendKeyDown que chama fly ()
se a tecla F for pressionada.

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.

Entre a lua nova e os quartos, a lua é crescente (uma meia-lua).


Entre os quartos e a Lua cheia, a Lua é chamada de gibosa.

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!

Não é perfeito, mas ainda é uma ótima simulação


Você deve ter notado que a Lua cobre completamente o Sol depois que ele se
move através de um crescente minguante. Ou seja, nossa simulação mostra um
eclipse solar total a cada mês. Esta é uma boa pista de que nossa simulação não é
perfeita, pois eclipses solares são raros. O que precisamos para torná-lo melhor?
Primeiro, os tamanhos e distâncias da Terra e da Lua em nossa simulação estão
muito distantes. O Sol em nossa simulação tem tamanho 100. Uma Terra com o
tamanho correto teria um tamanho menor que 1 - o nosso é 40! Mesmo que a Terra
tivesse o tamanho correto, ainda está muito perto do Sol. Nossa Terra simulada está
a 300 metros do Sol. Para ser preciso, para um Sol de tamanho 100, a Terra deveria
estar a 11.000 de distância! A Lua também é muito grande. Na simulação, é 75 por
cento do tamanho da nossa Terra, mas deveria ter 25 por cento.
Não tornamos nossa simulação mais precisa por três motivos. Primeiro, tudo teria
que ser minúsculo para caber na tela. A Terra e a Lua seriam pequenos pontos e até
o Sol seria pequeno se afastássemos o zoom o suficiente para ver tudo. Em
segundo lugar, o software 3D não seria capaz de acompanhar luzes e formas com
distâncias como 11.000 e tamanhos como 1. O código foi projetado para funcionar
com coisas fáceis de ver. Terceiro, você provavelmente não tem um monitor que
possa mostrar 11.000 pixels - telas gigantes de 4K têm apenas 3840!
Alguns outros problemas com a simulação não são tão grandes. A órbita da Terra
não é um círculo - é uma elipse / oval. Isso significa que às vezes a Terra pode estar
um pouco mais perto ou mais longe do sol. Além disso, a órbita da Lua é inclinada
em comparação com a da Terra, então às vezes ela está acima ou abaixo do Sol em
vez de causar um eclipse.
E apesar de tudo isso, esta, ainda é uma grande simulação. Isso nos ajuda a
entender como as fases da Lua funcionam e fica lindo ao fazer isso.

Nota - Às vezes, Bom o Suficiente é Ótimo


Para um programador, é tentador tornar tudo perfeito. À medida que você ganha
experiência, fica surpreso com o quão bom, "bom o suficiente", pode ser!

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.

O que vem a seguir


Isso encerra as simulações espaciais no livro. Parabéns - você conquistou uma
grande tradição em programação 3D. Esperançosamente, você também aprendeu
uma ou duas coisas sobre o espaço. Mais importante para suas habilidades de
computador, você foi apresentado ao conceito de coordenadas locais, que
definitivamente usaremos em nossos jogos.
Falando em jogos, vamos começar alguns no próximo capítulo!

Quando terminar com este capítulo, você:


- Será capaz de construir jogos com movimentos reais, incluindo: pular, cair e
colidir
- Entenderá como construir jogos 2D
- Terá um mini-jogo desafiador!
Capítulo 14

Projeto: O jogo do monstro da fruta roxa

Neste capítulo, faremos um jogo de salto bidimensional. O jogador usará os


controles do teclado para fazer o Monstro de Fruta Roxo pular e se mover, para
capturar o máximo de frutas que rolam, sem tocar o solo. Vai ficar parecido com isto:
Pode parecer um jogo simples de escrever, mas vamos usar muitas das
habilidades e conhecimentos que desenvolvemos no livro. E para começar a pular,
rolar e capturar, vamos apresentar um novo nível de sofisticação ao nosso código.
Esta vai ser divertida!

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

Vamos Fazer Física!


Este jogo precisará de duas coleções de código JavaScript e algumas
configurações para acompanhá-los. No início do arquivo, adicione duas novas tags
<script>:

<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.

② Para manter a pontuação, usamos novamente a coleção de códigos do placar.

No topo do código do modelo de projeto 3D starter project, logo abaixo da tag


<script> sem um atributo src, faça as alterações observadas abaixo.

// 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);

① Uma configuração que permite ao Physijs decidir quando as coisas se chocam.


② Código de “trabalhador” (worker) que é executado em segundo plano, realizando
todos os cálculos físicos.
③ Em vez de um THREEscene, precisamos usar uma Physijsscene.
④ Mesmo com a física, não teremos gravidade a menos que a adicionemos à cena.
Nesse caso, adicionamos a gravidade na direção Y negativa, que é para baixo.

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.

Vetores são Direção e Magnitude


Estamos usando THREE.Vector3 para definir a gravidade. Vamos usar muito isso
neste capítulo. Se você assistiu ao primeiro filme: Meu Malvado Favorito, já sabe o
que é!
O bandido nesse filme é Vector. Ele escolheu seu nome de supervilão porque um
vetor é uma flecha com direção e magnitude (Oh, sim!). Isso significa que um vetor
inclui duas informações: a direção para a qual ele aponta e com que intensidade ele
aponta nessa direção.
O vetor que descreve a gravidade neste jogo aponta na direção Y negativa (para
baixo). Ele tem uma magnitude alta (250), o que significa que as coisas vão cair
rapidamente.

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.

» // var aspectRatio = window.innerWidth / window.innerHeight;


» // var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
» 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);

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.

var renderer = new THREE.WebGLRenderer({antialias: true});


renderer.setSize (window.innerWidth, window.innerHeight);
» renderer.setClearColor('skyblue');
document.body.appendChild(renderer.domElement);

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ê.

Mantenha seu Código Organizado


A programação é bastante difícil por si só. Não torne isso mais difícil escrevendo
um código confuso. Organizar o código não importa muito com programas curtos.
Mas o código cresce à medida que novas coisas são adicionadas. O código
organizado – indentado e com funções definidas na ordem em que são chamadas -
é um código que pode crescer.

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.

//var ground = addGround ();


//var avatar = addAvatar ();
//var scoreboard = addScoreboard ();

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.

Adicionando Terreno para o Jogo


A primeira chamada de função em nosso esboço de código é para a função
addGround. Logo abaixo do esboço do código (após a linha // addScoreboard ()
comentada), defina essa função da seguinte maneira:
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;
}

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().

» var ground = addGround ();


//var avatar = addAvata r ();
//var scoreboard = addScoreboard ();

Se tudo estiver ok, vamos ver um fundo verde com um céu azul no fundo, conforme
mostrado na figura.

Construir um Avatar Simples


Na programação 3D, você pode fazer gráficos simples de duas maneiras.
Usaremos ambos neste jogo - um tipo para o Monstro de Fruta Roxo e o outro tipo
para a fruta. A técnica gráfica simples que usamos para o Purple Fruit Monster é
chamada de sprite.
Na função addAvatar (), criamos uma malha de caixa invisível habilitada para a
física e, em seguida, adicionamos o sprite à malha de caixa.
Adicione esta função abaixo da função addGround ().
function 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;
}

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.

var ground = addGround ();


» var avatar = addAvatar ();
//var scoreboard = addScoreboard ();
Com isso, temos um avatar de Monstro de Fruta Roxo ... que está preso no chão.

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));
}

O nome da propriedade __dirtyPosition é uma espécie de piada de programador.


Estamos fazendo uma bagunça aqui, então dizemos que é "sujo". Qual é a
bagunça? Como o avatar é habilitado para física, normalmente não poderíamos
mudar sua posição. Poderíamos empurrá-lo para um novo local, mas não temos
permissão para movê-lo instantaneamente de um lugar para outro. Mas para
reiniciar o jogo, precisamos fazer exatamente isso - mudar imediatamente a posição
do avatar de volta ao início. Definir __dirtyPosition como true nos permite fazer
isso.

ATENÇÃO - dirty Começa com Dois Sublinhados


Certifique-se de adicionar dois sublinhados antes de dirtyPosition. Não é
_dirtyPosition. A configuração é __dirtyPosition. Se você usar apenas um
sublinhado, não haverá erros, mas os controles de movimento não funcionarão.

Movemos o avatar 60 por cento do caminho para a esquerda: -0,6 vezes a


distância do centro para a borda esquerda da janela. Também o movemos 200
acima do solo. Por fim, definimos speed - a velocidade - do avatar. Usamos um
vetor para iniciá-lo com uma velocidade de 250 para cima na direção Y.
Adicione uma chamada para reset () logo abaixo do esboço do código.

var ground = addGround ();


var avatar = addAvatar ();
//var scoreboard = addScoreboard ();

» 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.

Simule Física Ativamente


Como fizemos no Capítulo 13, Projeto: Fases da Lua, colocamos nosso código
de jogo - nossa simulação de física - dentro de uma função chamada gameStep ().
Adicione logo abaixo da função reset ().

function gameStep () {
scene.simulate ();
setTimeout (gameStep, 1000/30);
}
gameStep ();

Não se esqueça de chamar gameStep () imediatamente após a definição da


função. Assim que estiver codificado, nosso avatar deve começar um pouco acima
do solo, pular um pouco para cima e, em seguida, cair de volta no chão. Agora
estamos simulando a física do mundo real em nosso jogo!
O setTimeout () dentro de gameStep () chama gameStep () depois de esperar
1000/30 milissegundos - aproximadamente 30 milissegundos. Isso pedirá ao código
Physijs para atualizar as posições de tudo na cena a cada 30 milissegundos.
Parece muito, mas é um bom equilíbrio. Não é tão comum que os computadores
comecem a ficar lentos. Muitas vezes é o suficiente para que as atualizações
pareçam suaves quando animadas.
A seguir, vamos adicionar alguns controles para mover o Monstro de Fruta Roxo.
Controles de movimento
Para controlar o avatar, usamos o atendente de evento keydown que vimos nos
capítulos anteriores. Adicione o seguinte código abaixo da função animate:

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();
}

function 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);
}

Não há nada sofisticado com o atendente de evento sendKeyDown (). As


funções left (), right (), up () e down () que são chamadas também são muito
simples. Eles chamam uma função move () com diferentes quantidades de
movimento nas direções X e Y.
A função move () é um pouco interessante. As duas primeiras linhas definem a
escala X do avatar se estivermos nos movendo na direção X. Isso vira a imagem do
Monstro da Fruta Roxa para a esquerda ou direita, dependendo da direção em que
estamos nos movendo.
As duas últimas linhas de move () empurram o avatar na direção correta. Primeiro,
calculamos a direção. Por exemplo, quando a tecla de seta para a esquerda é
pressionada, a função left () é chamada. A função left () chama move (-100, 0), que
diz a move para definir x para -100 e y para 0. O valor dir é então definido como um
vetor apontando (-100, 0, 0), que é 100 para a esquerda. Aplicar um impulso central
nessa direção significa um empurrão rápido no centro do avatar. Empurrar o centro
de um objeto é mais fácil do que empurrar uma borda. Uma vez que somos
programadores e gostamos do fácil, vamos no centro.
Com isso, devemos ser capazes de ocultar o código e mover o avatar para cima,
para baixo, para a esquerda e para a direita.

Adicionar Pontuação do Jogo


Para completar o esboço do código, adicionamos a seguir o placar. Coloque isso
abaixo da função addAvatar () e acima da função reset ().
function 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;
}

É semelhante ao placar que usamos no Capítulo 11, Projeto: Caça às frutas,


portanto, o código deve parecer familiar. Remova o comentário da função
addScoreboard no esboço do código.
var ground = addGround ();
var avatar = addAvatar ();
» var scoreboard = addScoreboard ();

reset ();

Agora você deve ver um placar mostrando 0 pontos.


Jogabilidade
Neste ponto, terminamos o esboço do código e temos o básico para um jogo 2D
sólido. Temos a área de jogo, o avatar (incluindo os controles) e uma forma de
marcar pontos. Para tornar o jogo interessante, ainda precisamos trabalhar um
pouco mais. A seguir, vamos adicionar jogabilidade. Vamos lançar algumas frutas e
desafiar o jogador a fazer o avatar comer o máximo possível sem tocar o solo.

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);

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;
}

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));
}

Começamos fazendo frutas com a função makeFruit () que acabamos de escrever.


Calculamos a velocidade como 500 mais um pequeno extra (acréscimo). O pequeno
extra é um número aleatório que aumenta à medida que a pontuação aumenta. O
jogo deve ficar mais difícil quanto mais tempo o jogador joga. Tornamos mais difícil
rolando a fruta cada vez mais rápido!
Aplicamos a velocidade com setLinearVelocity (). O movimento precisa ser da
direita para a esquerda, então definimos a direção X para o negativo da
configuração de velocidade. Por último, damos uma pequena rotação à fruta.
Ainda precisamos chamar essa função. Portanto, abaixo da função launchFruit (),
adicione duas chamadas.

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.

Comendo Frutas e Mantendo a Pontuação


No final do nosso código - abaixo da função move (), adicione o código para enviar
colisões para o resto do nosso código.
avatar.addEventListener ('collision', sendCollision);
function sendCollision (object) {
if (object.isFruit) {
scoreboard.addPoints (10);
avatar.setLinearVelocity (new THREE.Vector3(0, 250, 0));
scene.remove (object);
}
}

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:

avatar.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);
}
» 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 ();

// Animation code goes here...


renderer.render (scene, camera);
}
animate ();

Faça a mesma coisa acima animate (), na função launchFruit ().

function 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));
}

Então, temos que ensinar a função reset () como reiniciar as coisas.


function 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);


» }
»
» if (gameOver) {
» gameOver = false;
» animate ();
» }
}
Estamos fazendo duas coisas aqui: removendo frutas velhas e reiniciando o jogo.
Para remover a fruta, fazemos um loop sobre todos os "filhos" na cena - todos os
objetos que adicionamos à cena. Em qualquer etapa do loop, se descobrirmos que o
objeto é um pedaço de fruta, pedimos à cena que o remova. Assim, ao reiniciarmos
o jogo, nenhuma fruta velha sobrará.
Observe que temos que retroceder ao remover coisas de uma lista em JavaScript -
caso contrário, corremos o risco de pular coisas que não pretendíamos pular. A lista
de coisas na cena pode ser:

0: Fruit #1
1: Fruit #2
2: Ground

Se começarmos o loop for de 0, pela primeira vez na lista, i seria 0 e


removeríamos a Fruta # 1 da cena. Então, a lista ficaria assim:

0: Fruit #2
1: Ground

Na próxima vez através do loop, i aumentaria para 1. Verificando o objeto número


1, encontraríamos o chão. Já que isso não é fruta, nosso código não o removeria de
cena. E então nosso loop parava, pois não há mais itens na lista.
Mas espere! Pulamos direto para a Fruta # 2. Ao remover algo no início da lista,
deslocamos tudo o mais na lista para baixo em um (1). No momento em que o loop
começa novamente, pulamos uma fruta velha.
Não temos esse problema de fazer o contrário. Em vez de começar com o primeiro
item da lista e aumentar a variável i a cada vez, começamos com o último item e
diminuímos i após cada loop. O exemplo inverso começaria assim:

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

Então, no próximo loop, i seria 0 e removeríamos a Fruta # 1, deixando-nos com:

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!

O código até Agora


Se você gostaria de verificar novamente o código neste capítulo, vá para Código:
O jogo do monstro da fruta roxa. Esse código inclui a melhoria
checkMissedFruit(), mas tente primeiro por conta própria.
O que Vem a Seguir
Este foi um jogo impressionante de fazer. Nos próximos capítulos, praticaremos as
habilidades físicas que desenvolvemos aqui. Também vamos desenvolver o conceito
de uma função gameStep, que era bastante simples neste jogo. Mas antes disso,
que pontuação máxima você consegue com o Purple Fruit Monster?

Quando terminar com este capítulo, você terá:


- Construído um minijogo 3D completo.
- Entendido como construir peças de jogo 3D complexas.
- Sido capaz de criar efeitos de partículas legais como fogo Ser capaz de combinar
formas, materiais, luzes e física em um jogo
Capítulo 15

Projeto: Incline um Tabuleiro

Vamos construir um jogo 3D em que uma bola cai em um pequeno labirinto no


espaço. O objetivo do jogo é usar as setas do teclado para inclinar o labirinto de
forma que a bola caia por um pequeno orifício no centro do tabuleiro - sem cair da
borda. Vai ficar parecido com isto:

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.

Gravidade e Outras Configurações


Para fazer a “física” funcionar no jogo Purple Fruit Monster, precisamos trabalhar
um pouco antes de INICIAR A CODIFICAÇÃO NA PRÓXIMA LINHA. Sem habilitar
a física, as formas não cairão, rolarão, saltarão, deslizarão ou farão qualquer coisa
como as coisas do mundo real.
Neste projeto, escolhemos um modelo que já inclui a configuração necessária para
o mecanismo de física funcionar. Verifique novamente se você vê o código de física
no projeto “Tilt-a-Board” que você acabou de criar.

<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);

① Carregue a biblioteca de física.


② Diga à biblioteca de física onde pode encontrar ajuda adicional
para detectar colisões.
③ Configure um worker para realizar todos os cálculos físicos.
④ Crie um Physijs.scene habilitado para física.
⑤ Ative a gravidade.

Luzes, Câmera, Sombras!


A maior parte da luz neste jogo virá de luzes pontuais que podem projetar sombras.
Então, vamos diminuir o brilho da luz ambiente no topo do código de 0,8 para 0,2.

var light = new THREE.AmbientLight('white', 0.2);

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:

camera.position.set (0, 100, 200);


camera.lookAt (new THREE.Vector3 (0, 0, 0));
renderer.shadowMap.enabled = true;

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:

//var lights = addLights ();


//var ball = addBall ();
//var board = addBoard ();
//var goal = addGoal ();

Assim como fizemos no Capítulo 14 - Projeto: O jogo do Monstro da Fruta


Roxa, vamos descomentar essas chamadas de função conforme definimos as
funções.

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;
}

Estamos adicionando duas luzes pontuais aqui. Vimos no Capítulo 1:


Trabalhando com Luzes e Materiais, que são semelhantes às lâmpadas.
Agrupamos ambos em um objeto 3D, luzes, que são adicionadas à cena e
retornadas da função. Ambas as luzes lançarão sombras.
Agora que adicionamos a definição da função, descomente a chamada para
addLights no esboço do código.

» var lights = addLights ();


//var ball = addBall ();
//var board = addBoard ();
//var goal = addGoal ();

Adicionar o Game Ball


Adicione a função addBall abaixo da definição de função para addLights.

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.

var beam1 = new Physijs.BoxMesh(shape, cover, 0);


Como vimos no Capítulo 14 - Projeto: O Jogo do Monstro da Fruta Roxa, o 0 diz
à biblioteca de física que o tabuleiro não se move. Sem o 0, nosso tabuleiro de jogo
cairia da tela exatamente como a bola.
Descomente a chamada para addBoard no esboço do código.

var lights = addLights ();


var ball = addBall ();
» var board = addBoard ();
//var goal = addGoal ();

Com isso, a bola deve cair bem no meio do tabuleiro.

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);
}

Não se esqueça dos dois sublinhados antes de dirtyPosition e dirtyRotation!


Primeiro, usamos as dirtypositions em Resetting the Position (Reinicializar a
posição). Usamos __dirtyPosition e __dirtyRotation aqui, para que possamos
alterar a posição e o giro da bola.
Adicione uma chamada à função reset () abaixo do esboço do código.
var lights = addLights ();
var ball = addBall ();
var board = addBoard ();
//var goal = addGoal ();

» 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 ();

// Update physics 60 times a second so that motion is smooth


setTimeout (gameStep, 1000/60);
}

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 ():

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 ();
}

Agora estamos familiarizados com o uso de eventos de teclado JavaScript para


controlar a jogabilidade como este. Aqui, estamos chamando funções para inclinar o
tabuleiro de jogo para a esquerda, direita, para cima e para baixo. Adicionaremos
essas definições de função a seguir, após a função sendKeyDown ().

function 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;
}

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.

O que é realmente sorrateiro no tilt é board.rotation [dir]. Quando a função


esquerda é chamada, ela chama tilt, definindo o argumento dir como 'z'. Como dir é
'z', definir board.rotation [dir] é o mesmo que definir board.rotation ['z']. Isso é
algo novo! Já vimos coisas como board.rotation.z, mas nunca vimos colchetes e
uma string como essa.
Bem, acontece que board.rotation [’z’] é o mesmo que board.rotation.z.
JavaScript vê ambos como uma mudança na propriedade z da rotação. Usando esse
truque, escrevemos apenas uma linha que pode atualizar todas as direções em tilt
(diferentes na inclinação).

board.rotation [dir] = board.rotation [dir] + amount;

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.

Antes de chegarmos à detecção de colisão para vencer o jogo, vamos adicionar


outra função para adicionar uma "luz de gol". Essa luz fará duas coisas: destacar o
objetivo do jogador e piscar quando o jogador vencer o jogo.

Adicione a função addGoalLight () abaixo da função reset ().

var goalLight1, goalLight2;


function addGoalLight () {
var shape = new THREE.CylinderGeometry (20, 20, 1000);

var cover = new THREE.MeshPhongMaterial ({


emissive: 'white',
opacity: 0.15,
transparent: true,
color: 'black'
});
goalLight1 = new THREE.Mesh (shape, cover);
scene.add (goalLight1);

var cover2 = new THREE.MeshPhongMaterial ({


visible: false,
emissive: 'red',
opacity: 0.4,
transparent: true,
color: 'black'
});
goalLight2 = new THREE.Mesh (shape, cover2);
scene.add (goalLight2);
}
Esta função é um pouco estranha. Ele não adiciona uma luz real que ilumina nossa
cena. Além disso, adiciona duas luzes falsas, não apenas uma "luz de objetivo".
Lembre-se de que estamos usando a luz do gol para destacar o gol para o jogador.
Ele foi feito para parecer um pouco com um holofote. Para dar a aparência de um
holofote brilhando sobre algo importante, marcamos como transparente e atribuímos
uma opacidade baixa. Ser transparente significa que podemos ver através dele. A
opacidade determina como é fácil ver através - um número próximo a 0, como 0,15,
é quase completamente transparente.
Para a luz do segundo gol, reutilizamos a mesma forma, mas usamos um material
ligeiramente diferente. Ainda é transparente, mas com uma opacidade de 0,4, não é
tão transparente quanto a primeira luz. Esta luz emitirá uma cor vermelha em vez do
branco da primeira. Mais importante, tornamos esse material invisível com
visible:false.
Apenas uma dessas luzes de meta ficará visível por vez. Para ver isso, vamos
adicionar esta função ao nosso esboço de código:

var lights = addLights ();


var ball = addBall ();
var board = addBoard ();
var goal = addGoal ();

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;
}

setTimeout (win, 500, flashCount);


}

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.

goal.addEventListener ('collision', win);

Isso chama win () quando a bola colide com o gol.


Com isso, concluímos o objetivo. Você deve conseguir esconder o código e usar as
setas do teclado para inclinar o tabuleiro até que a bola caia pelo buraco e caia no
gol.
Se a meta não piscar, verifique o console JavaScript. Você também pode tentar
adicionar uma chamada para win () logo abaixo do esboço do código. Isso vai
chamar win () sem a necessidade de inclinar a mesa. Você pode então ver o que há
de errado no console JavaScript e tentar consertá-lo nas funções addGoalLight ()
ou win (). Lembre-se de remover a chamada para win () do esboço do código
quando terminar a depuração. Não queremos dar aos jogadores uma vitória a menos
que eles a ganhem!

É 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 ();

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.co s(dec) Math.sin (ra);

shape.vertices.push (point);
}
var stars = new THREE.Points (shape, cover);
scene.add (stars);
}

Depois de fazer isso, você pode adicionar uma chamada à função


addBackground no esboço do código.

var lights = addLights ();


var ball = addBall ();
var board = addBoard ();
var goal = addGoal ();

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 ...

Bônus 2: Faça fogo!


Todos os computadores modernos são sistemas extremamente complexos. Quase
todos eles têm uma parte dedicada ao processamento de gráficos: uma unidade de
processamento gráfico ou GPU. Sempre que nós, programadores, conseguirmos
fazer a GPU funcionar é uma vitória porque isso libera a Unidade Central de
Processamento, ou CPU, para executar mais lógica do jogo.

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>

Em seguida, substituímos completamente a função addGoalLight (). Em vez de


duas luzes falsas entre as quais alternamos, adicionaremos um "emissor de
partículas de sombreador" (“shader particle emitter”).

var 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);

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 ();
}

Esta partícula de sombreador começa desativada. Iremos ativá-la em breve, mas


primeiro aqui está uma descrição rápida do que fazem, as várias propriedades, nesta
função.
Esta função carrega uma imagem “faísca” que constituirá nosso fogo. Nós
agrupamos essas partículas no valor do fogo. Em seguida, construímos o objetivo do
“emissor” de fogo, que emitirá - ou jogará fora - 1000 partículas de fogo, cada uma
das quais existirá por quatro segundos antes de morrer. Adicionamos o emissor ao
grupo de partículas de fogo e adicionamos o grupo à cena. Em seguida, definimos
um monte de propriedades para as partículas emissoras, incluindo:

- Sua velocidade na direção Y.


- Quanto a velocidade de cada partícula pode variar.
- Quão rápido as partículas são puxadas para baixo.
- Quão espalhado no fundo do fogo é.
- Quão grande são as partículas.
- Quão o tamanho das partículas pode variar.

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 ().

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;
}
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?

Após desabilitar o fogo no final de addGoalLight (), precisamos habilitá-lo quando


o jogador vencer o jogo. Altere a função win () conforme mostrado:

function win (flashCount) {


if (!flashCount) flashCount = 0;

» goalFire.enable ();

flashCount++;
if (flashCount > 10) {
reset ();
» goalFire.disable ();
return;
}

setTimeout (win, 500, flashCount);


}
goal.addEventListener ('collision', win);

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!

O Código até Agora


Se você quiser verificar novamente o código neste capítulo, vá para Código:
Inclinar a Placa (Tilt-a-Board).

O que vem a seguir


Esse foi o nosso jogo mais bonito até agora. Combinamos nossas habilidades de
escrever jogos 3D com nossas novas habilidades de fazer sombras e materiais. O
jogo inclinar um tabuleiro é divertido de jogar e tem uma aparência atraente.
Demorou muito para codificar, mas valeu a pena.
Nos próximos capítulos, examinaremos um pouco mais o JavaScript.
Especificamente, vamos cobrir objetos, que temos usado o tempo todo, mas não
falamos sobre como fazer. Assim que tivermos essa habilidade, vamos construir
mais alguns jogos bacanas.

Quando terminar este capítulo, você:

- Saberá o que significa aquela palavra-chave new que continuamos usando.


- Será capaz de definir seus próprios objetos.
- Viu a pior coisa que o JavaScript faz.

Capítulo 16

Aprendendo Sobre Objetos JavaScript


Fizemos alguns progressos incríveis até agora. Temos um avatar que pode andar
pela tela e esbarrar em obstáculos. Construímos um modelo animado dos
movimentos da lua. Também testamos nossas novas habilidades para criar alguns
jogos muito legais.
Fizemos tanto progresso, na verdade, que atingimos o limite do que podemos fazer
com JavaScript - pelo menos sem introduzir algo novo. Para entender por que
precisamos aprender sobre esse novo conceito, considere nosso avatar. Podemos
fazer muitos jogos em que nosso avatar possa jogar sozinho, mas e se o jogador
quiser jogar com outros?
Se dois avatares estivessem na tela ao mesmo tempo, como adicionaríamos todas
essas mãos, pés e corpos à tela e não os misturaríamos? Como faríamos cada um
se mover de forma independente? Como atribuiríamos cores e formas diferentes a
cada avatar?
As coisas ficam rapidamente fora de controle se tentarmos realizar todas essas
coisas com o que sabemos até agora. Então é hora de aprender sobre os objetos e
ver o que podemos fazer com eles

Este é um Capítulo Desafiador


Muitos novos conceitos estão neste capítulo. Você pode achar melhor folhear este
capítulo pela primeira vez e depois voltar para explorá-lo com mais profundidade
posteriormente.

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.

NOTA - Coloque uma vírgula após a última entrada em mapas e coisas


semelhantes a mapas.
Em mapas, é útil colocar uma vírgula após a última entrada. Isso não é obrigatório
e parece um pouco estranho, mas fazemos isso para tornar mais fácil adicionar outra
entrada. Sem uma vírgula, é fácil quebrar o mapa ao adicionar outro item. É fácil
simplesmente começar a digitar a próxima entrada sem se lembrar de primeiro
colocar uma vírgula após a entrada anterior. Se já temos essa vírgula, não
precisamos nos preocupar em esquecê-la!

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?

A melhor maneira de responder é chamar a função logMe. Acredite ou não, já


sabemos como chamar essa função. Temos chamado funções semelhantes desde o
Capítulo 1, quando adicionamos coisas a uma cena com scene.add (ball) ou
definimos as posições com position.set (-250,250, -250).
Para a função logMe () em bestMovie, chamamos bestMovie.logMe (). Adicione
essa chamada abaixo da chave de fechamento de bestMovie.

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:

1. Esses nomes descrevem melhor como os usamos com objetos.


2. Outras linguagens de programação usam esses nomes para seus objetos,
por isso é bom estar familiarizado com eles.
3. É assim que vamos chamá-los de agora em diante!

Se acessarmos title (título), year (ano) ou stars (estrelas) no bestMovie, é como


perguntar sobre as diferentes propriedades ou características do bestMovie.year
(melhor filme do ano). Portanto, bestMovie.year é uma propriedade do melhor filme.
E bestMovie.stars é outra propriedade da mesma coisa.
O nome método descreve como um objeto faz algo. É a maneira - ou método - em
que executa algum tipo de ação. Portanto, bestMovie.logMe () é o método pelo qual
bestMovie envia suas informações para o console JavaScript. E bestMovie.about
() é como bestMovie combina suas informações mais importantes para se
descrever.
Esperançosamente, a razão para os nomes fazerem sentido, porque vamos usá-los
na próxima seção ...

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

Object.create () criará um novo objeto com todas as mesmas propriedades e


métodos do prototípico objeto que criamos anteriormente. Portanto, o novo objeto,
greatMovie, tem o mesmo título e atores que o bestMovie original. Também possui
os mesmos métodos logMe e about.
Vamos fazer com que este novo objeto se refira a um filme favorito de todos os
programadores 3D como nós, Toy Story.

greatMovie.title = 'Toy Story';


greatMovie.year = 1995;
greatMovie.stars = ['Tom Hanks', 'Tim Allen'];

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

No console JavaScript, vemos informações sendo registradas sobre Toy Story. O


que acontece quando registramos as informações do bestMovie?

bestMovie.logMe ();
// => Star Wars, starring: Mark Hamill,Harrison Ford,Carrie Fisher

No console JavaScript, vemos informações sobre Star Wars.


O novo greatMovie e o prototípico bestMovie do qual foi copiado são objetos
diferentes. Eles agora têm propriedades completamente diferentes. Mas eles ainda
usam os mesmos métodos logMe () e about () - não os alteramos no greatMovie. E
mesmo que usem os mesmos métodos, eles produzem resultados diferentes.
Esses métodos produzem resultados diferentes porque usamos this.title e
this.stars no método about ().
about: function () {
return this.title + ', starring: ' + this.stars;
},

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).

Toda essa conversa de protótipos e prototípicos objetos não é apenas uma


desculpa para lançar palavras bonitas. Na verdade, o conceito de protótipo é muito
importante em JavaScript e ajuda a responder a uma pergunta que você pode ter
feito desde o primeiro capítulo deste livro: que é essa palavra-chave new que
continuamos digitando?

Construindo Novos Objetos


Agora temos uma boa ideia do que é um objeto em JavaScript. Também agora
vemos como um objeto pode ser um prototípico objeto e atuar como um modelo para
a criação de objetos semelhantes. Criar novos objetos como esse pode ser
entediante e sujeito a erros. Considere o seguinte: se esquecermos de atribuir a
propriedade year (ano) em greatMovie, o objeto pensará que Toy Story foi feito em
1977. A menos que digamos ao objeto de forma diferente, ele copia todas as
propriedades do objeto original (bestMovie), incluindo o ano, 1977!
Outra maneira de criar objetos em JavaScript é usar funções simples. Sim, as
mesmas funções simples que vimos pela primeira vez no Capítulo 5 - Funções:
Usar e Usar Novamente. Surpreendentemente, não precisamos fazer nada de
especial em uma função para criar novos objetos.
Como achamos que tem uma aparência melhor, os programadores geralmente
colocam o nome de uma função em maiúscula se ela criar novos objetos. Por
exemplo, uma função que cria objetos de filme pode ser chamada de Movie (Filme).

function Movie (title, stars) {


this.title = title;
this.stars = stars;
this.year = (new Date ()).getFullYear ();
}

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.

Agora sabemos como os criadores de nossa coleção de código JavaScript 3D


escrevem todo o seu código, para que possamos escrever coisas como:

var shape = new THREE.SphereGeometry(100);


var cover = new THREE.MeshNormalMaterial();
var ball = new THREE.Mesh(shape, cover);

SphereGeometry, MeshNormalMaterial e Mesh são funções construtoras da


coleção de código de Three.js.
Um mistério foi resolvido, mas um permanece: se estamos usando funções
construtoras de para construir objetos, como podemos fazer métodos para esses
objetos? Como poderíamos definir um método logMe () para objetos Movie?
A resposta é porque enfatizamos a palavra “protótipo” na seção anterior. Para criar
um método logMe para os objetos criados com nosso construtor Movie, definimos o
método no protótipo do construtor. Ou seja, para um filme prototípico, queremos que
o método logMe se pareça com o seguinte:

Movie.prototype.logMe = function () {
console.log (this.title + ', starring: ' + this.stars);
};

Com esse método em vigor, podemos pedir ao kungFuMovie para se registrar.

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.

A Pior Coisa em JavaScript: Perder this


JavaScript é uma ótima linguagem de programação. Exceto quando não é. O que
acontece com this, em certos casos, é um bom exemplo de quando o JavaScript
não é tão bom.
Considere usar setTimeout () para chamar uma função após um delay (atraso). Já
fizemos isso várias vezes no livro. No Capítulo 11 - Projeto: Caça às Frutas,
usamos setTimeout () para sacudir a árvore do tesouro após um delay (atraso) de 2
(dois) segundos.
setTimeout (shakeTreasureTree, 2*1000);

Após 2 segundos (2.000 milissegundos), setTimeout() chamou nossa função


shakeTreasureTree(). Também fizemos isso várias vezes para a função
gameStep().

setTimeout (gameStep, 1000/30);

O que acontecerá se tentarmos fazer isso com nosso método logMe ()?

setTimeout (kungFuMovie.logMe, 500);

Experimente e veja o que acontece.


O que esperamos é, após um atraso de meio segundo, podermos olhar no console
JavaScript para ver a mensagem “sobre”: “Kung Fu Panda, estrelado por: Jack
Black, Angelina Jolie.”
O que realmente vemos, no entanto, é a mensagem: “undefined, starring:
undefined.” Humm?
O que acontece se tentarmos isso com bestMovie?
setTimeout (bestMovie.logMe, 500);

Depois disso, vemos um erro no console JavaScript semelhante a: “this.about não


é uma função”. O que está acontecendo aqui?

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);
};

Mas this não é mais atribuído ao kungFuMovie. Portanto, quando setTimeout () o


executa, this.title e this.stars são ambos indefinidos. É por isso que recebemos a
mensagem “indefinido, starring: indefinido”.
Quando setTimeout () tenta chamar logMe () em bestMovie, JavaScript também
esqueceu o que é this. JavaScript nem mesmo lembra que adicionamos um método
about () a bestMovie (), então recebemos uma mensagem de erro de que
“this.about não é uma função”.
O problema de JavaScript esquecer this é uma das partes mais difíceis da
programação JavaScript. Pessoas que programaram JavaScript por anos ainda se
confundem com isso. Parte do problema é que JavaScript é simplesmente estranho -
esse comportamento não parece fazer muito sentido. Este também é um problema
difícil porque as mensagens de erro não são úteis - algo pode estar indefinido por
vários motivos e nem sempre é óbvio que o JavaScript está apenas sendo estranho.
Felizmente, depois de sabermos qual é o problema, é uma solução fácil.
Lembramos ao JavaScript que this deveria estar com o método bind ().

setTimeout (kungFuMovie.logMe.bind (kungFuMovie), 500);


setTimeout (bestMovie.logMe.bind (bestMovie), 500);

Sim, é muito estranho escrever kungFuMovie ou bestMovie duas vezes para


chamar um método. Mas isso é realmente a pior coisa sobre a programação com
JavaScript, e não é tão ruim assim. Além disso, só temos que fazer isso ao trabalhar
com algo como setTimeout () ou, como veremos no próximo capítulo, com métodos
de colisão.
Vamos Brincar!
O que acontece se você bind() bestMovie para kungFuMovie.logMe? O que
acontece se você bind(kungFuMovie) para bestMovie.logMe?

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!

O Código até Agora


Para verificar novamente o código neste capítulo, vá para Código: Aprendendo
sobre objetos JavaScript.

O que Vem a Seguir


Programar com objetos é uma coisa difícil de entender. Se você entendeu tudo
neste capítulo, então está se saindo muito melhor do que eu quando tentei aprender
pela primeira vez. Se nem tudo fazia sentido, não se preocupe. Os exemplos que
vamos usar nos próximos jogos devem ajudar a tornar as coisas um pouco mais
claras.
Depois de escrever um ou dois jogos com objetos, pode ser útil reler este capítulo.
Conforme os jogos que você inventa por conta própria ficam cada vez mais
complexos, você vai querer contar com objetos para ajudar a organizar seu código.

Quando terminar este capítulo, você:

- Terá um divertido jogo de habilidade e azar.


- Compreenderá melhor como usar objetos JavaScript para construir jogos.
- Terá ainda mais experiência com, gráficos e física interessantes.

Capítulo 17

Projeto: Pronto, Firme, Lançamento

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:

Objetos JavaScript São ... Esquisitos


Eu disse isso no Capítulo 16: Aprendendo Sobre Objetos JavaScript, e direi
novamente aqui. Objetos JavaScript podem ficar estranhos. Eles até atrapalham
programadores de JavaScript experientes, então não desanime se você não
"entender" partes disso. Trabalhe no capítulo e você entenderá o suficiente. Em
seguida, pode voltar e reler o Capítulo 16, Aprendendo Sobre Objetos JavaScript.

A seguir estão as três partes importantes deste jogo:

1. O lançador
2. Cestas
3. Vento

O lançador lançará as bolas para o ar na tentativa de colocá-las nas cestas para


marcar pontos. O vento vai mudar continuamente durante o jogo, tornando mais
difícil para os jogadores usarem o lançador.

Faremos cada uma dessas partes importantes em um objeto JavaScript:

1. O objeto lançador saberá como se mover para a esquerda ou direita e como


lançar uma bola.
2. O objeto cesta esperará que as bolas colidam e ganhe pontos.
3. O objeto de vento mudará de direção e velocidade com o tempo.

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.

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);

Usamos um “auxiliar de flecha” para desenhar nosso lançador. Podemos substituí-


lo por um estilingue animado ou algo semelhante mais tarde, mas uma flecha servirá
para o nosso minijogo. A seta precisa de uma direção para apontar (direto para
cima), uma posição (um pouco para baixo e um pouco para trás do centro), um
comprimento e uma cor. Com esses valores, criamos uma nova seta e a
adicionamos à cena.
O lançador vai fazer muito: desenhar essa flecha, mover para a esquerda e para a
direita, puxar para trás para ligar e lançar bolas. Muitos recursos como esse
geralmente levam a um código confuso.
Portanto, vamos construir um objeto JavaScript chamado Launcher para controlar
tudo isso. Adicione o construtor do Launcher acima do código de seta que
acabamos de adicionar.
function Launcher () {
this.angle = 0;
this.power = 0;
this.draw ();
}

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.

var launcher = new Launcher ();


Com isso, a flecha deve voltar. Caso contrário, verifique o console JavaScript
conforme descrito no Capítulo 2 - Depurando: Corrigindo o Código Quando as
Coisas dão Errado.

Estamos fazendo pequenas alterações como fizemos no Capítulo 13 - Projeto:


Fases da Lua - pelos mesmos motivos que fizemos lá : para tornar mais fácil
perceber onde cometemos erros.
Um lançador precisa fazer mais do que apenas desenhar a si mesmo, então
continuaremos adicionando ao protótipo do iniciador. Para fazer isso, adicione
algumas linhas em branco acima da linha new Launcher.
O Launcher precisa de um método que converta seu ângulo em um vetor. Tanto a
flecha quanto as bolas lançadas precisam disso. Adicione o método vector () abaixo
do método draw ().

Launcher.prototype.vector = function () {
return new THREE.Vector3 (
Math.sin (this.angle),
Math.cos (this.angle),
0
);
};

Em seguida, adicione um método moveLeft () abaixo de vector ().

Launcher.prototype.moveLeft = function () {
this.angle = this.angle - Math.PI / 100;
this.arrow.setDirection (this.vector ());
};

Isso muda a direção do iniciador ligeiramente para a esquerda e ajusta a seta na


tela. Antes de adicionarmos o método moveRight (), vamos testar moveLeft () para
ter certeza de que não perdemos nada.
No final do nosso código - abaixo da função gameStep () e acima da tag de
fechamento </script>, adicione um atendente de evento para pressionamentos de
tecla.

document.addEventListener ('keydown', sendKeyDown);


function sendKeyDown (event) {
var code = event.code;
if (code == 'ArrowLeft') launcher.moveLeft ();
}

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.

document.addEventListener ('keydown', sendKeyDown);


function sendKeyDown (event) {
var code = event.code;
if (code == 'ArrowLeft') launcher.moveLeft ();
» if (code == 'ArrowRight') launcher.moveRight ();
}

Agora volte para o código do protótipo do Launcher. Abaixo do método moveLeft


(), adicione moveRight ()

Launcher.prototype.moveRight = function () {
this.angle = this.angle + Math.PI / 100;
this.arrow.setDirection (this.vector());
};

Certifique-se de que ambos movem para a direita e para a esquerda funcionam


depois de fazer essa alteração. Agora é a hora da diversão: ligar o lançador e lançar
as bolas.
O método powerUp () adiciona 5 à potência atual (até um máximo de 100). Em
seguida, ele ajusta o comprimento da seta para mostrar quanta energia será usada.

Launcher.prototype.powerUp = function () {
if (this.power >= 100) return;
this.power = this.power + 5;
this.arrow.setLength (this.power);
};

Então nós lançamos!

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);
};

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

document.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 ();
}

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:

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!");
}

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.

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 ();
}

Este construtor leva dois argumentos: o tamanho da cesta e o número de pontos


que ela vale. Dentro do construtor, lembramos (como já frisamos, o Javascript
esquece se não incluirmos this para lembrá-lo) esses dois valores como this.size e
this.points. Também calculamos a altura. Em seguida, atribuímos uma cor aleatória
à nossa cesta. Por último, o construtor Basket chama seu próprio método draw ().
Estamos nos divertindo um pouco com matemática com a altura da cesta.
Queremos que os cestos pequenos superem os cestos mais largos. As cestas
estreitas devem ser altas. As cestas largas devem ser mais baixas. A divisão quase
funciona para isso. Se disséssemos que a altura de uma cesta é 100 dividido pelo
tamanho, obteríamos as seguintes alturas:

- Para o tamanho 10 (pequeno), a altura seria 100 ÷ 10 = 10


- Para o tamanho 100 (largo), a altura seria 100 ÷ 100 = 1
Não queremos uma diferença tão grande entre as alturas. Os cestos estreitos
devem subir – mas não como uma torre - acima dos cestos mais largos. Além disso,
uma altura de 1 é muito pequena.
Os logaritmos são bons para alterações menores. Especificamente, usamos um
logaritmo de base 10 - Math.log10 (). Um logaritmo de base 10 retorna números
como este:
- Math.log10(10) é 1
- Math.log10(40) é 1.602
- Math.log10(100) -e 2
- Math.log10(10000) é 4

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:

- Para o tamanho 10 (pequeno), a altura é 100 ÷ log (10) = 100 ÷ 1 = 100


- Para o tamanho 100 (largo), a altura é 100 ÷ log (100) = 100 ÷ 2 = 50

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'
});

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);
}
Iniciamos o método draw () construindo um material brilhante com nossa cor
aleatória. Em seguida, construímos um cubo plano com uma altura y de 1 e x e z
(largura e profundidade) this.size. A malha física precisa de uma altura pequena - o
suficiente para mantê-la acima de outras malhas mais curtas.
Finalmente, nós o adicionamos à cena.
Vamos construir um objeto Basket para garantir que tudo esteja correto até agora.
Adicione o seguinte abaixo do código do placar:

var goal1 = new Basket (200, 10);

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.

var goal1 = new Basket (200, 10);


» var goal2 = new Basket (40, 100);

Este menor valerá incríveis 100 pontos se conseguirmos acertá-lo!


Faça backup no método Basket.prototype.draw (), adicione lados às cestas para
que se pareçam mais com cestas reais. Isso é muito código, mas deve ser familiar à
medida que adicionamos os lados posterior, frontal, direito e esquerdo.

Basket.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 =
» 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!

Basket.prototype.waitForScore = function (goal) {


goal.addEventListener ('collision', this.score.bind (this));
};

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);
};

Depois de atualizar a seta na tela com um novo comprimento e direção, usamos


setTimeout () para mudar o vento após 10 segundos. Vimos setTimeout () pela
primeira vez no Capítulo 11 - Projeto: Caça às Frutas.
E, sim. Tivemos que vincular isso ao método change (). Caso contrário, estaríamos
pedindo ao JavaScript para mudar um vento fantasma. JavaScript bobo.
Precisamos de um último método para o objeto Wind (Vento): um vetor () que
descreve o vento no formato (x, y, z).

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.

var wind = new Wind ();

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 ();

» 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);
}

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.

O Código até Agora


Se você gostaria de verificar o código neste capítulo, ele está incluído em Código:
Pronto, Estável, Iniciar. O código para reiniciar o jogo está incluído. Você terá que
criar o resto por conta própria!

O Que Vem a Seguir


Este não foi um capítulo fácil, então você se saiu muito bem ao chegar até aqui.
Parabéns!
Neste ponto, você está começando a juntar tudo. O JavaScript deve ser um pouco
mais familiar e confortável para você. Provavelmente não é tão familiar que você
possa escrever um jogo complexo do zero sem muita ajuda da web. Mas você está
se aproximando.
E, com sorte, coisas como física, luzes, materiais e objetos estão começando a
fazer sentido. Vamos manter esse progresso em movimento no próximo capítulo.

Quando terminar este capítulo, você terá:

- Um jogo para dois jogadores.


- Melhor entendido por que os objetos são tão importantes na programação.
- Começado a entender o que está envolvido em jogos multijogador online.
Capítulo 18

Projeto: Jogos para Dois Jogadores


Jogos para vários jogadores são difíceis de construir. Mas já fizemos muitas coisas
difíceis neste livro, então não vamos deixar isso nos impedir!

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.

Dois Launchers (Lançadores)


Graças ao objeto Launcher que construímos no capítulo anterior, podemos
realmente criar dois launchers. Eles não funcionam muito bem, mas às vezes você
tem que quebrar as coisas antes de poder montá-las de uma nova maneira.
Se você seguiu as sugestões de colocação de código no capítulo anterior, seu
código começa com o construtor Launcher, seguido por métodos no protótipo
Launcher. Em seguida, temos o construtor Basket, seguido por seus métodos. Por
último está o construtor Wind e seus métodos.
Depois dos métodos Wind, temos uma linha que cria um único launcher
(lançador):

var launcher = new Launcher ();

Pesquise o seu código se não conseguir localizá-lo


Ctrl+F ou ⌘+F no 3DE abrirá o recurso de localização. Digite alguns dos
caracteres que espera encontrar e 3DE o levará para a linha que os contém. Para o
novo lançador, experimente “new Laun”.

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.

document.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 ();
}

Precisamos mudar o launcher para launcher1. Também temos que adicionar


controles para mover o launcher2. Em vez de permitir que um jogador use as teclas
de seta e o outro jogador use outra coisa, vamos mover os dois controles para as
teclas na linha do meio do teclado. Nos EUA, a maioria dos teclados começa com as
letras “A”, “S” e “D” na linha do meio. No lado direito, esses teclados terminam com
"J", "K" e "L."
Usaremos A e D para mover o launcher1 e S, que está entre os dois, para lançar.
Para launcher2, J e L irão se mover, e K irá lançar. Para fazer isso funcionar, altere
o atendente “keydown” conforme mostrado a seguir.

document.addEventListener ('keydown', sendKeyDown);


function sendKeyDown(event) {
var code = event.code;
» if (code == 'KeyA') launcher1.moveLeft ();
» if (code == 'KeyD') launcher1.moveRight ();
» if (code == 'KeyS') launcher1.powerUp ();
»
» if (code == 'KeyJ') launcher2.moveLeft ();
» if (code == 'KeyL') launcher2.moveRight ();
» if (code == 'KeyK') launcher2.powerUp ();
if (code == 'KeyR') reset ();
}

E também precisamos mudar o atendente “keyup”.

document.addEventListener ('keyup', sendKeyUp);


function sendKeyUp (event) {
var code = event.code;
if (code == 'KeyS') launcher1.launch ();
if (code == 'KeyK') launcher2.launch ();
}

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.

DICA - Vencem as Pequenas Alterações


Ao adicionar recursos, execute pequenos passos e verifique constantemente se
nenhum erro inesperado ocorreu.

Em seguida, vamos mover os dois lançadores. Já estamos dizendo aos nossos


iniciadores que eles precisam estar à esquerda ou à direita:

var launcher1 = new Launcher ('left');


var launcher2 = new Launcher ('right');

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 ();
}

Para que o Launcher saiba o que fazer, fazemos as seguintes alterações.

» function Launcher (location) {


» this.location = location;
» this.color = 'yellow';
» if (location == 'right') this.color = 'lightblue';
this.angle = 0;
this.power = 0;
this.draw ();
}

O argumento de localização na primeira linha diz ao nosso construtor para esperar


um argumento, que chamamos de location (localização) para tornar mais fácil
lembrar o que deve fazer: definir a localização do iniciador. Em seguida, definimos
color (cor) para amarelo (que era o que o lançador e as bolas eram na versão do
jogo para um jogador). Por último, definimos color (cor) como azul claro se o
iniciador estiver no lado direito da tela.
Depois de fazer essas alterações no código, o jogo deve ter a mesma aparência,
sem erros. Para realmente ver a diferença, precisamos desenhar as setas do
lançador em lugares diferentes e usar cores diferentes para as setas e bolas.

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:

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!");
}

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!");
}
» };

Depois de fazer essa alteração, o placar desaparece. E tudo o mais também.


Verificando o console JavaScript, devemos ver um erro de que “placar não está
definido”. Isso ocorre porque as funções animate () e gameStep () usam o placar
para parar o jogo quando o tempo acabar. Essas duas funções ainda precisam saber
quando o tempo acabou, então temos que avisá-los de alguma forma.
A maneira mais fácil de fazer isso é fazer com que o método keepScore () salve
seu placar na propriedade scoreboard do iniciador (launcher).
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!");
}
» this.scoreboard = scoreboard;
};

Em seguida, no construtor do Launcher acima, chame este método keepScore ():

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 ();
}

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.

var launcher1 = new Launcher ('left');


var launcher2 = new Launcher ('right');
» var scoreboard = launcher1.scoreboard;

Depois de fazer essa mudança, devemos voltar ao ponto de partida. As cestas


devem estar novamente no meio da tela. Devemos ter dois lançadores. E devemos
ver apenas um placar.
Vemos apenas um painel de avaliação, mas na verdade existem dois: um em cima
do outro. Em seguida, precisamos dizer ao placar de cada lançador onde ele deve
ficar. Até agora, todos os nossos placares viveram no topo da cena. Mas o
construtor do Scoreboard pode viver em quatro lugares diferentes: 'topleft',
'topright', 'bottomleft' e 'bottomright'.
Uma vez que a propriedade location do Launcher já é 'left' ou 'right', podemos
combiná-la com 'top' para obter o valor correto para enviar ao Scoreboard (placar).
A seguinte mudança no método keepScore () resolve o problema:

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.

Ensinando Cestas para Atualizar o Placar Correto


No último capítulo, definimos o que uma cesta faz quando uma pontuação
acontece, da seguinte maneira:

Basket.prototype.score = function (ball) {


if (scoreboard.getTimeRemaining () == 0) return;
scoreboard.addPoints (this.points);
scene.remove (ball);
};

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);

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);
};

Esta é uma pequena mudança importante. Neste método - neste método do


Launcher, lançamos a bola que acabará por atingir uma cesta. Adicionamos uma
nova propriedade à ball, uma propriedade scoreboard. Atribuímos this.scoreboard
a essa propriedade ball.scoreboard. Como estamos no Launcher,
this.scoreboard é o mesmo placar que criamos em keepScore (). E assim, a bola
agora sabe qual placar deve somar pontos ao cair.
Portanto, no método score () de Basket, atribuímos a variável scoreboard a
ball.scoreboard.

Basket.prototype.score = function (ball) {


» var scoreboard = ball.scoreboard;
if (scoreboard.getTimeRemaining () == 0) return;
scoreboard.addPoints (this.points);
scene.remove (ball);
};

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();

if (code == 'KeyL') launcher2.moveRight ();


» if (code == 'KeyK') {
» clearInterval (powerUp2);
» powerUp2 = setInterval (powerUpLauncher2, 20);
» }
if (code == 'KeyR') reset ();
}

① Existem power-ups idênticos para os jogadores # 1 e # 2. Olhando de perto os


power-ups do jogador nº 1, vemos:
② Uma variável para manter um intervalo de inicialização ativo.
③ Uma função que diz ao lançador para ligar. Isso apenas adiciona um pouquinho
de potência ao iniciador - teremos que chamá-lo muito para ligá-lo totalmente.
④ Não são permitidas repetições simples do teclado antigo, graças à propriedade
especial repeat (repetição). Se esse evento keydown for uma repetição do
teclado, retornamos da função keyboard listener (de atendente do teclado)
sem fazer nada.
⑤ Se o intervalo de inicialização já estiver ativo, pare-o.

Inicie o intervalo powerUp1, executando a função powerUpLauncher1 () uma vez


a cada 20 milissegundos. Isso é 50 vezes por segundo, o que deve ligar o iniciador
de forma agradável e rápida.

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);
» }
» }

Ainda lançamos quando a power-up key (tecla de inicialização) é liberada.


Também precisamos limpar o intervalo de inicialização para que o lançador esteja
pronto para começar tudo de novo. Agora, o jogador nº 1 e o jogador nº 2 podem
usar o mesmo teclado para ligar e iniciar ao mesmo tempo.

Uma Reinicialização Completa


O último bug está em nossa função reset (). Estamos apenas redefinindo o placar
do jogador nº 1.

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]);
}

animate ();
gameStep ();
}

Queremos zerar a pontuação para 0 e a contagem regressiva para 60 segundos


para ambos os jogadores, não um. Então, vamos cortar essas duas linhas com
Ctrl+X ou ⌘+X.
Cole-os abaixo do último método do Launcher, que deve ser keepScore (), e
acima do construtor da função Basket (). Em seguida, faça um método reset () para
o Launcher adicionando o código destacado:

» 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 () {

if (scoreboard.getTimeRemaining () > 0) return;


» launcher1.reset ();
» launcher2.reset ();

var balls = allBalls ();


for (var i=0; i<balls.length; i++) {
scene.remove (balls[i]);
}

Animate ();
gameStep ();
}

Agora nosso jogo está realmente pronto para jogar!

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

Projeto: River Rafter


Para nosso projeto final, vamos construir um jogo de caibro no qual o jogador
precisa navegar uma jangada ao longo de um rio caudaloso, desviando de
obstáculos e pegando pontos de bônus sempre que possível. O jogo será parecido
com este esboço:
Este será um capítulo difícil. Estamos usando todas as habilidades que
aprendemos neste livro e, em seguida, aprendendo um monte de coisas novas.
Portanto, é realmente uma boa ideia deixar este capítulo para o final.
Dito isso, isso vai ser incrível, então vamos começar!

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>

Os controles de órbita, que usamos no Capítulo 12: Trabalhando com Luzes e


Materiais, nos permitirão olhar ao redor do mundo que iremos construir. Já
conhecemos bem o código do placar. O código de ruído (noise) é novo. Isso nos
permitirá fazer algumas coisas bem selvagens com as formas na cena. Primeiro,
vamos diminuir a gravidade neste jogo de -100 para -10. Isso fará com que a balsa
se mova um pouco mais devagar rio abaixo.

var scene = new Physijs.Scene ();


» scene.setGravity (new THREE.Vector3 (0, -10, 0));

Queremos sombras neste jogo, então diminua a intensidade da luz ambiente para
0,2.

» var light = new THREE.AmbientLight ('white', 0.2);


scene.add (light);

Logo abaixo dessas linhas, adicione a luz direcional do Capítulo 12: Trabalhando
com Luzes e Materiais.

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;

Para ver melhor o que estamos construindo, vamos alterar um pouco as


configurações da câmera.

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);

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.

var renderer = new THREE.WebGLRenderer ({antialias: true});


» renderer.setClearColor ('skyblue');
» renderer.shadowMap.enabled = true;
renderer.setSize (window.innerWidth, window.innerHeight);
document.body.appendChild (renderer.domElement);

A última parte da configuração é adicionar os controles de órbita da coleção de


códigos. Adicione isso logo acima da linha START CODING.

new THREE.OrbitControls (camera, renderer.domElement);

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!

Empurrando e Puxando Formas


Na programação 3D, as formas são muito mais fáceis de alterar do que você pode
imaginar. Já no Capítulo 1 - Projeto: Criando Formas Simples, nós sabemos
como alterar o tamanho, volume e rotação de uma forma. Acontece que podemos
fazer ainda mais. Muito mais. Para ver isso, adicione uma chamada para
addGround () abaixo da linha START CODING. Em seguida, defina a função
addGround () conforme mostrado:
var ground = addGround ();

function addGround () {
var faces = 99;
var shape = new THREE.PlaneGeometry (10, 20, faces, faces);

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;

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.

Var ground = addGround ();

Function addGround () {
Var faces = 99;
Var shape = new THREE.PlaneGeometry (10, 20, faces, faces);

Var __cover = new THREE.MeshPongMaterial ({color: ‘green’, shinoness: 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;

Scene.add (mesh);
Return mesh;
}

Há algumas coisas novas lá - e falaremos sobre isso um pouco - mas


principalmente este código deve ser familiar. Criamos uma forma (shape) de
retângulo/plano e uma capa verde (cover), combinamos-os em uma malha (mesh)
habilitada para a física (physics) e, em seguida, giramos (rotate) e colocamos na
cena (scene). Agora tente isso ... Dentro da função addGround (), logo abaixo da
linha que cria a shape (forma), adicione a seguinte linha:

var shape = new THREE.PlaneGeometry (10, 20, faces, faces);


» shape.vertices [50].z = 5;

Depois de inserir esse código, vemos nossa mudança do flat plano.

Uau! Isso é muito legal, certo? Agora experimente.

var shape = new THREE.PlaneGeometry (10, 20, faces, faces);


shape.vertices [50].z = 5;

» shape.vertices [50 + 100].z = 5;


» shape.vertices [50 + 10*100].z = 5;
» shape.vertices [50 + 50*100].z = 5;

Deve ficar parecido com o seguinte:


Vamos Brincar!
O que acontece quando você define uma das propriedades z para um número
negativo? O que acontece se você adicionar uma linha que também muda
shape.vertices [50].x? O que acontece quando você tenta outros números dentro
dos colchetes [ ]? Enlouqueça brincando com essas linhas - vamos excluí-las para
algo melhor daqui a pouco, então não se preocupe em quebrar nada.

A alteração de malhas como essa é a maneira como formas mais complexas


geralmente são criadas para jogos de computador. Frequentemente, os designers
criam malhas complexas em aplicativos separados que permitem que eles façam
isso com alguns cliques do mouse. Essas formas complexas são carregadas nos
jogos. Vamos nos limitar à codificação aqui mesmo no 3DE - há muito que podemos
fazer. Mas, primeiro, vamos dar uma olhada neste código.

O Que Está Acontecendo Aqui?


Se você deseja apenas codificar, vá para Digging a River. Mas volte aqui - esta
seção contém algumas coisas importantes!
Este código se parece muito com o código que escrevemos para planos (planes) e
malhas (mesh) habilitadas para física (physics). Mas existem algumas diferenças
importantes.
De volta ao Capítulo 1 - Projeto: Criando Formas Simples, criamos um plano
com tamanhos X e Y de 100 usando o new THREE.PlaneGeometry (100, 100).
Aqui, fazemos um plano (plane) com tamanho (size) X de 10 e tamanho (size) Y de
20.

var faces = 99;


var shape = new THREE.PlaneGeometry (10, 20, faces, faces);

Também definimos o número de “faces” X e Y para 99 cada. O que estamos


fazendo aqui é dizer ao código 3D para construir este plano retangular simples de 99
quadrados menores nas direções X e Y. Ou seja, em vez de um único retângulo
gigante, este plano é feito de 99 retângulos em ambas as direções. Se você fizer as
contas, 99 por 99 significa que temos um total de 9.801 quadrados. Isso seria muito
bobo para um flat plano, mas faremos bom uso de todos esses retângulos - ou pelo
menos de seus cantos.
Essas faces são importantes na programação 3D, então vale a pena dar uma
olhada rápida. Já vimos faces antes - eles eram os "pedaços" do Capítulo 1 -
Projeto: Criando Formas Simples. Quando programamos com eles, eles são
retângulos, mas os computadores não gostam de retângulos. Os computadores
gostam de triângulos. Então, eles pegam cada face - cada pedaço - e dividem em
dois triângulos.
Para ver isso, podemos habilitar “wireframing” no material.
var _cover = new THREE.MeshPhongMaterial ({
color: 'green',
shininess: 0,
» wireframe: true
});
var cover = Physijs.createMaterial (_cover, 0.8, 0.1);

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.

var _cover = new THREE.MeshPhongMaterial ({color: 'green', shininess: 0});


var cover = Physijs.createMaterial (_cover, 0.8, 0.1);

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 ().

var mesh = new Physijs.HeightfieldMesh (shape, cover, 0);


mesh.rotation.set (-0.475 * Math.PI, 0, 0);
mesh.receiveShadow = true;
mesh.castShadow = true;
Usamos esse tipo de malha ao trabalhar com formas empenadas ou distorcidas.
Uma vez que iremos deformar o solo na próxima seção, esta é a malha certa para
nós.
Observe também que não giramos totalmente o terreno plano. Se definirmos a
rotação em torno do eixo X como -0,5 * Math.PI, o solo seria perfeitamente plano.
Definindo-o como -0,475 * Math.PI, deixamos o solo em um pequeno ângulo.
Quando adicionamos uma balsa escorregadia à água escorregadia, a balsa desliza -
como se o rio a estivesse empurrando!

Terreno acidentado raramente é perfeitamente plano. Nos gráficos, chamamos o


terreno acidentado de ruidoso, e é por isso que adicionamos essa coleção de
códigos de ruído na configuração.
Ainda dentro da função addGround (), exclua as linhas que puxaram os vértices.
Em seguida, substitua-os por um loop que atravessa cada vértice do plano.

var faces = 99;


var shape = new THREE.PlaneGeometry (10, 20, faces, faces);

» 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;
» }

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 ();

Isso deve ser parecido com:

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.

var faces = 99;


var shape = new THREE.PlaneGeometry (10, 20, faces, faces);
shape.vertices [50].z = 5;
shape.vertices [50 + 100].z = 5;
shape.vertices [50 + 10*100].z = 5;
shape.vertices [50 + 50*100].z = 5;

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:

for (var j=50; j<numVertices; j+=100) {


shape.vertices [j].z = -1;
}

Os programadores são um grupo estranho. O primeiro loop em uma função


geralmente usa variável i. O segundo usa variável j (e o terceiro usa k). Como
usamos i para adicionar ruído ao solo, usamos j para encontrar os vértices no meio
do plano de solo. Isso deve reduzir cada um desses - a cada 100.º vértice - por -1.
Se você clicar e arrastar a cena, o solo agora deve se parecer com:
Precisamos fazer a curva do rio para a frente e para trás no solo. Somos
programadores 3D, então "para frente e para trás" significa senos e cossenos para
nós! Vamos usar uma função seno para adicionar uma curva. Em vez de puxar o
centro do rio para baixo em j, vamos puxar para baixo o centro do rio em j mais o
resultado da função seno.
Para fazer isso, altere o loop for da seguinte maneira:

for (var j=50; j<numVertices; j+=100) {


» var curve = 20 Math.sin (7Math.PI * j/numVertices);
» var riverCenter = j + Math.floor (curve);
»
» shape.vertices [riverCenter].z = -1;
}

Isso deve mudar o rio para ficar assim:


O 7 * Math.PI dentro da função seno diz que o rio vai e vem um pouco mais de três
vezes. Multiplicamos o resultado de Math.sin () por 20 para dizer a que distância do
centro o rio vai girar. O número resultante é a curva do rio neste ponto do loop.
A lista (list) de vertices só funciona com números inteiros como 50 ou 5050. O
valor de curve (curva) sempre terá um decimal como 50,1443. Portanto, precisamos
de uma função que remova tudo após o ponto decimal - que é exatamente o que
Math.floor () faz. Se j for 1050, então curve pode ser 14,893. Isso faria com que
riverCenter fosse 1050+Math.floor (14.893), ou 1064. Então, puxamos (pull) o
vértice número 1064. Trabalhando em todo o loop, assim, obtemos nosso belo rio
sinusoidal e curvilíneo.

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.

for (var j=50; j<numVertices; j+=100) {


var curve = 20 Math.sin (7Math.PI * j/numVertices);
var riverCenter = j + Math.floor (curve);
» for (var k=-20; k<20; k++) {
» shape.vertices [riverCenter + k].z = -1;
» }
}

Com isso, devemos ter um rio largo e cheio de curvas.

Isso é muito legal, certo?


Temos mais duas coisas a fazer com o rio. Primeiro, queremos uma maneira de
lembrar onde estão os pontos centrais do rio.

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;
}

for (var j=50; j<numVertices; j+=100) {


var curve = 20 Math.sin (7Math.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;
}
}
shape.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;
}

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 ().

var ground = addGround ();


var water = addWater ();
» var scoreboard = addScoreboard ();
Mas não há função addScoreboard () ainda, então isso temporariamente quebra
as coisas. Para obter tudo de volta rapidamente, adicione a função após a última
chave na função addRiver ().

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.

Construir uma Jangada para Corridas


Um formato de donut funcionará muito bem como uma jangada. Adicione a
chamada addRaft ao esboço do código na parte superior do nosso código.

var ground = addGround ();


var water = addWater ();
var scoreboard = addScoreboard ();
» var raft = addRaft ();

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;

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;
}

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));
}

Não se esqueça de que __dirtyPosition começa com dois caracteres de


sublinhado!

Com isso, o jogo deve ficar assim quando começar:

Movemos a câmera bem perto da cena e a adicionamos à jangada. Zeramos a


pontuação e o cronômetro no placar. Por fim, movemos a jangada para a linha de
partida: um pouco para a esquerda por causa das curvas do rio, um pouco para cima
por causa da inclinação que adicionamos ao rio e para trás porque é onde fica a
beira do rio.
O código nesta função deve funcionar tanto para iniciar o jogo quanto para reiniciá-
lo. A chamada setLinearVelocity define a velocidade da jangada para 0 toda vez
que ela é chamada. Sem isso, um jogador que reiniciou o jogo no meio de uma
corrida iria reiniciar na linha de partida já a toda velocidade.
Nesse ponto, você deve ter uma jangada descendo o rio, com a câmera
observando todo o caminho. Claro, isso é bastante inútil sem controles, então vamos
adicionar alguns.

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);

Agora, vamos adicionar nossos controles de jogo. Adicione-os no final do seu


código, logo acima da tag final </script>.

document.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 ();
}

function rotateRaft (direction) {


raft.tube.rotation.z = raft.tube.rotation.z + direction * Math.PI/10;
}

function pushRaft () {
var angle = raft.tube.rotation.z;
var force = new THREE.Vector3 (Math.cos (angle), 0, -Math.sin (angle));

raft.applyCentralForce (force);
}

Já vimos muitos atendentes de teclado a essa altura, então


document.addEventListener já deve ser familiar.
A função rotateRaft () não precisa se preocupar em quebrar a física porque
giramos o tubo não habilitado para física. Lembre-se de que ativamos a física da
malha invisível e colocamos o tubo não físico dentro da malha.
Por fim, a função pushRaft usa applyCentralForce para empurrar a jangada,
assim como empurramos as bolas do jogo no Capítulo 17 - Projeto: Pronto, Firme,
Lançar.
Com isso, temos as peças básicas de um jogo bem legal! As teclas de seta
esquerda e direita irão girar a jangada e a barra de espaço empurrará a jangada
para a frente na direção para a qual está voltada.
Podemos fazer muito mais com este jogo. Vamos começar com uma linha de
chegada e, em seguida, passar para algumas formas opcionais de adicionar
pontuação ao jogo.

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 ();
}

Após reiniciar o jogo, iniciamos a animação e iniciamos a lógica do jogo chamando


animate () e gameStep (). Também chamamos o método onSimulationResume ()
na cena, que redefine o código físico sempre que ele for reiniciado após ter sido
pausado.
Em seguida, dizemos à função animate que ela não precisa renderizar a cena
quando o jogo é gameOver. Ou seja, se gameOver estiver definido como true,
saímos da função animate antes de atualizar a câmera ou renderizar a cena.
function animate () {
» if (gameOver) return;
requestAnimationFrame (animate);
renderer.render (scene, camera);
}
» // animate ();

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 ();

Novamente, como iniciamos gameStep () dentro da função reset (), comentamos


ou excluímos a chamada para gameStep () após a definição da função. A função
checkForGameOver é nova. Ele pode ir logo após a função gameStep.

function checkForGameOver () {
if (raft.position.z > 9.8) {
gameOver = true;
scoreboard.stopTimer ();
scoreboard.message("You made it!");
}

if (scoreboard.getTime () > 60) {


gameOver = true;
scoreboard.stopTimer();
scoreboard.message("Time's up. Too slow :(");
}
}
Verificamos se a posição Z da jangada é maior que 9,8, que está muito perto da
borda do rio em 10,0. Se a balsa atingiu 9,8, definimos gameOver como true e
atualizamos o placar. Também adicionamos uma verificação de slowpokes aqui.
Com isso, o jogo deve fazer uma pausa no final do rio e exibir o tempo que o
jogador levou para completar a corrida e uma mensagem, “Você conseguiu!” Você
pode até ser capaz de torná-lo bem rápido:

Bônus: Manter a Pontuação


Este já é um jogo bastante impressionante. Parece legal e é desafiador. Portanto,
este é um bom ponto de parada. Mas se você gostaria de adicionar um pouco mais,
aqui estão alguns recursos bônus a serem adicionados.

Pontuação Baseada no Tempo


Vamos calcular a pontuação assim: você ganha mais pontos quanto mais rápido
terminar e ganha pontos de bônus se conseguir terminar bem rápido. Podemos fazer
isso adicionando outra verificação em checkForGameOver ().
function checkForGameOver () {
if (raft.position.z > 9.8) {
gameOver = true;
scoreboard.stopTimer ();
scoreboard.message ("You made it!");
}
if (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);
» }
}
Verificamos se o jogo acabou. Se qualquer uma das balsas chegou a 9.8 ou o
tempo expirou, gameOver é definido como verdadeiro. Quando isso acontecer, é
hora de registrar a pontuação.
Primeiro, subtraímos o tempo de 61. Nós “limitamos” isso porque queremos apenas
números inteiros. Por exemplo, se eu terminar em 29,9 segundos, a pontuação deve
ser Math.floor (61 - 29,9), que é o mesmo que Math.floor (31,1), que é 31 pontos.
Em seguida, adicionamos pontos de bônus:

- Se o jogador terminar em menos de 40 segundos, 100 pontos adicionais


serão atribuídos.
- Se o jogador terminar em menos de 30 segundos, os 100 pontos e mais 200
pontos serão atribuídos.
- Se o jogador terminar em menos de 20 segundos, os 100 e os 200 pontos
serão atribuídos, juntamente com 500 pontos adicionais, para um possível
total de 800 pontos extras a serem ganhos.

Consegues fazê-lo? Veja a figura.


Pontos PowerUp
Marcar pontos de bônus é muito mais difícil de adicionar a este jogo do que você
pode imaginar. Adicionar frutas para energizar o rio ou o solo não funciona. Em vez
disso, temos que adicionar frutas energéticas à cena, mas isso não é fácil porque o
rio está inclinado.

Se adicionarmos a fruta de power-up ao rio, a detecção de colisão para de


funcionar para a malha de frutas. No que diz respeito ao código de física, quando
adicionamos a fruta ao rio, a fruta não é mais uma malha separada. Agora é parte do
rio - e isso é um problema. Atender eventos na fruta não funcionará, pois não haverá
eventos. Poderíamos atender eventos no rio, mas seria realmente difícil dizer a
diferença entre colidir com o rio e a parte das frutas do rio.
Precisamos adicionar a fruta diretamente à cena. Dessa forma, ele permanece
como uma malha separada com seus próprios eventos de colisão. Mas quais
coordenadas usamos? Lembre-se de que o rio está inclinado. Olhando de lado,
podemos ver que o solo pensa que a coordenada do meio superior está em (0, 0, -
10), mas a cena pensa que essa coordenada está em (0, 4,5, -9,7).
Sabemos onde fica o centro do rio ao longo do plano graças à propriedade
riverPoints que adicionamos ao solo. Mas precisamos convertê-los de coordenadas
de solo em coordenadas de cena.
A matemática para fazer isso não é muito difícil - são apenas mais senos e
cossenos, mas não temos que fazer essa matemática nós mesmos. Em vez disso,
teremos nosso código 3D traduzido de coordenadas "locais" de rio para
coordenadas de cena "mundiais".
Queremos adicionar itens ao jogo sempre que ele for redefinido, muito do nosso
trabalho precisa acontecer durante e após a função de redefinição. Dentro da
definição de reset, adicione uma chamada para resetPowerUps:

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);

var random70 = 70 + Math.floor (10*Math.random ());


var p70 = ground.riverPoints [random70];
addPowerUp (p70);
}

Isso também será interrompido porque resetPowerUps () está chamando outra


nova função, addPowerUp (), duas vezes. Adicionaremos isso em um momento,
mas primeiro, vamos ter certeza de que entendemos o que está acontecendo dentro
de resetPowerUps ().
Lá, criamos dois números aleatórios e os usamos para obter dois pontos ao longo
do rio. Existem 100 pontos no rio no total. Queremos colocar alguns frutos em torno
dos 20.º e 70.º pontos do rio. O local deve ser diferente em cada jogo para mantê-lo
desafiador.
A propriedade riverPoints é uma lista, então encontramos um ponto com um
número inteiro - assim como fizemos com a propriedade vertices ao cavar o rio.
Novamente usamos Math.floor () para obter um número inteiro aleatório. Desta
forma, p20 e p70 serão pontos de rio aleatórios perto do ponto 20 do rio (perto do
início) e ponto 70 (perto do fim).
Passamos esses pontos de rio aleatórios para a nova função addPowerUp ().
Adicione logo abaixo de resetPowerUps ().

function 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);
}
scoreboard.addPoints (200);
scoreboard.message ('Yum!');
setTimeout (function () {scoreboard.clearMessage ();}, 5*1000);
});

return mesh;
}

Já vimos a maior parte disso antes - exceto no início da função. Isso é


definitivamente novo, mas faz exatamente o que falamos anteriormente. Ele
converte uma localização X-Y-Z de um ponto ao longo do solo em uma coordenada
de cena. Em termos de programação 3D, convertemos de coordenadas locais em
globais.
Antes de fazer isso, é uma boa ideia chamar updateMatrixWorld. Isso garante que
os vários valores de coordenadas estejam todos atualizados e precisos. Depois
disso, copiamos as coordenadas X e Y do ponto do rio.
Adicionamos um número aleatório à coordenada X para mover a fruta para a
margem do rio, tornando as coisas mais desafiadoras. Usamos -0,5 para a
coordenada Z para colocar a fruta na água.
Uma vez que temos p, pedimos ao solo para convertê-lo de coordenadas de solo
locais em coordenadas de cena mundial com o método localToWorld (). Depois
disso, estamos de volta a um território familiar.
Criamos uma forma de esfera e uma cobertura e as usamos para construir uma
malha habilitada para a física. Esta malha não se move, então ela obtém uma massa
de 0. Em seguida, definimos a posição da malha para o mesmo que as coordenadas
p usando o método copy (), copiando as coordenadas de p para a posição da
malha. Como essas coordenadas são coordenadas da cena, podemos adicionar a
malha diretamente à cena.
Duas outras coisas a serem observadas são a propriedade powerUp e a detecção
de colisão. A propriedade powerUp será usada para remover itens de power-up da
cena quando o jogo for reiniciado. Também adicionamos um manipulador de eventos
de colisão. Quando a balsa colide com uma fruta de reforço, ela removerá a fruta da
cena e adicionará pontos ao placar.
Usamos funções sem nomes aqui. Usamos funções sem nome para criar métodos
no Capítulo 16: Aprendendo sobre objetos JavaScript. Usá-los diretamente
dentro de manipuladores de eventos como este pode ficar confuso, mas funciona.
Quando uma colisão é detectada, o código dentro da função sem nome () é
executado - removendo o objeto da cena e adicionando pontos à pontuação.
Também definimos um "Yum!" mensagem no placar, apagando-a cinco segundos
depois - com outra função sem nome.
Uma última coisa que precisamos fazer é limpar a fruta do power-up quando o jogo
reiniciar. Por esse motivo, adicionamos a propriedade powerUp à fruta de power-
up. Sempre que a função reset () chama a função resetPowerUps () que acabamos
de adicionar, vamos olhar os "filhos" da cena em busca de objetos com esta
propriedade.
Adicione uma chamada para removeOldPowerUps () no início de
resetPowerUps().
function 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 p70 = ground.riverPoints [random70];
addPowerUp (p70);
}

Em seguida, adicione a função removeOldPowerUps () abaixo da última chave de


addPowerUp ().

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:

O Código até Agora


Se você quiser verificar o código neste capítulo, vá para Código: River Rafter.

O Que Vem a Seguir


Isso foi muito código. Mas valeu a pena. Colocamos muitas de nossas habilidades
em uso com física, luzes e materiais. Também vimos vislumbres do que mais é
possível na programação 3D, puxando vértices de formas e convertendo
coordenadas locais em coordenadas mundiais.
Como nossos outros jogos, não considere este um final. Ainda há muito que você
pode adicionar. Talvez você possa incorporar obstáculos que tirem pontos?
Adicionar alguns saltos? Tornar este um jogo para dois jogadores como fizemos no
Capítulo 18 - Projeto: Jogos Para Dois Jogadores? Tornar o curso mais longo?
Você pode tentar adicionar controles de câmera para que possa ver do ponto de
vista de um passageiro da balsa, em vez de ver tudo de cima. Talvez o jogo ficasse
melhor com imagens de textura? Você consegue fazer sons quando a jangada liga
ou termina? Ou talvez você já tenha ideias de como melhorar este jogo que eu nem
consigo começar a pensar.
Em nosso capítulo final, mudaremos de marcha para colocar nossos projetos na
web.

Ao concluir este capítulo, você:


- Terá uma ideia melhor das partes que compõem um site.
- Entenderá o que precisamos para construir nossos próprios sites.
- Saberá como colocar um projeto em um site para compartilhar com outras
pessoas

Capítulo 20

Obtendo Código na Web

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.

O Poderoso, Poderoso Navegador


Eis o poderoso navegador:
Quando informamos a um navegador que queremos um site ou uma página de um
site, ele envia uma solicitação pela Internet para uma máquina que chamamos de
servidor da web, conforme mostrado na figura.

Para acessar o servidor correto, nosso navegador deve procurar o endereço de


rede do servidor da web na Internet. O navegador usa o Domain Name Service
(DNS) para realizar essa pesquisa. Quando visitamos
http://www.code3Dgames.com, o DNS responde com um endereço de rede de
151.101.1.195. Esse endereço de rede - às vezes chamado de protocolo da Internet
ou endereço IP - é tudo de que um navegador precisa para enviar uma solicitação da
Web pela Internet.

Os Servidores da Web devem estar Disponíveis Publicamente na Internet


Lembre-se de que a máquina que contém um site da Web deve estar disponível
publicamente na Internet. As máquinas que você usa em casa quase nunca estão
disponíveis publicamente. Mesmo se outra pessoa na Internet souber o endereço de
rede de sua máquina, ela ainda não será capaz de acessá-lo porque não está
disponível publicamente - está escondido atrás de sua rede.
Infelizmente, isso significa que você geralmente precisa pagar um pouco para ter
seus jogos legais da web disponíveis na Internet. Você precisa pagar uma empresa
de hospedagem para hospedar seus jogos. E você também terá que pagar pelo DNS
que mapeia o nome de um site (como www.code3Dgames.com) para um endereço
de rede (como 151.101.1.195). No entanto, existem opções gratuitas, como veremos
em breve.

Uma solicitação da web pede a um determinado servidor da web uma determinada


informação que ele possui. Essas informações podem ser uma página da web, pode
ser uma imagem, pode ser um filme e pode ser JavaScript. Mas é apenas para uma
coisa.
Quando a solicitação do navegador chega ao servidor da web, o servidor verifica se
há o item solicitado. Os servidores da Web podem ter todos os tipos de informações
armazenadas neles, conforme mostrado na figura.

A primeira solicitação que um navegador envia a um servidor geralmente é para


uma página da web - um arquivo escrito em HTML, sobre o qual falamos no
Capítulo 9: O que é Todo Esse Outro Código?
Se o servidor tiver a página da web que o usuário está procurando, ele a enviará de
volta ao navegador.

Este é o tipo de parte estranha. A página da web geralmente é muito pequena e


desinteressante. Quando pensamos em páginas da web, geralmente pensamos em
algo que tem uma aparência bonita e faz muitas coisas incríveis. Mas, por si
mesmas, as páginas da web não parecem bonitas e não fazem nada. As páginas da
web se parecem muito com o HTML que vimos no Capítulo 9: O Que é Todo Esse
Outro Código?
<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>

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>

A tag <script> está na parte inferior do documento, o que significa que o


navegador exibirá as coisas da página da web (texto, imagens, informações de
estilo) antes de executar nosso código. Todo o resto deve estar pronto no momento
em que o navegador executa esse código.
Conforme você trabalha em mais e mais sites JavaScript, encontrará outras
soluções para isso. Qual solução você usa não importa muito. Apenas tenha em
mente que muito mais precisa ser carregado em uma página da web do que apenas
a página da web - e seu código precisa lidar com isso. Caso contrário, bugs
realmente estranhos podem se infiltrar em suas páginas e aplicativos.

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:

<p>I made this!</p>


<div id="3d-code-2018-12-31">
</div>
<p>
It's in the first chapter of
<a href="http://code3Dgames.com/">3D Game Programming for Kids</a>,
second edition.
</p>
Você pode alterar as palavras dentro das tags <p> para o que quiser. A tag <div>
que usamos aqui é um divisor vazio. Vamos usar isso para manter nosso jogo. É
importante que o atributo id do <div> seja único - que não haja outras marcas com o
mesmo id em qualquer lugar da página. Uma maneira de fazer isso é combinar a
finalidade (3d-code) e a data de postagem do código (2018-12-31, por exemplo).
Em seguida, copie seu código do 3DE e cole-o na postagem do Blogger. Para sua
primeira postagem, é melhor mantê-la simples, então usaremos o código de todo o
caminho de volta no Capítulo 1 - Projeto: Criando Formas Simples.
Ao copiar o código de 3DE, certifique-se de pular a primeira linha que contém
<body> <body>. Copie apenas da primeira tag <script> até o final.
Cole isso na postagem do Blogger abaixo do HTML que adicionamos
anteriormente, conforme mostrado na figura.

Antes de clicarmos no botão Publish, precisamos fazer algumas alterações.


Primeiro, temos que dizer ao Blogger onde encontrar as coleções de código que
estamos usando. Ao longo do livro, nossas tags de script apontaram para src
coleções de código que começam com uma barra.

<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>

Observe que também adicionamos os controles de órbita aqui - principalmente


porque eles são divertidos!
Em seguida, precisamos definir a proporção da cena. No Capítulo 9: O que é todo
esse outro código? vimos que a proporção da imagem descreve a largura e a
altura de uma cena. Em uma página da web, 4/3 é uma boa proporção (aspect
ratio), então mudamos isso da seguinte maneira:

» var aspectRatio = 4/3;


var camera = new THREE.PerspectiveCamera (75, aspectRatio, 1, 10000);
camera.position.z = 500;
scene.add (camera);

Do Capítulo 9: O que é todo esse outro código ? sabemos que geralmente


adicionamos o renderizador à página inteira. Para nossa postagem, queremos
anexá-lo ao <div> que adicionamos anteriormente. Isso requer duas alterações no
código do renderizador.
// 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);
» var container = document.getElementById ('3d-code-2018-12-31');
» container.appendChild (renderer.domElement);
Primeiro, não queremos mais definir o tamanho do renderizador para ser igual ao
da janela inteira. Vamos definir isso em breve, mas por agora, exclua a linha
renderer.setSize () ou comente.

A outra mudança do renderizador é adicioná-lo à tag <div>. Queremos que o


elemento <div> “contenha” a animação, então nomeamos a variável container. Nós
“obtemos” o <div> em JavaScript usando o ID que definimos no HTML. Se você
usou 3d-code2018-12-31 para o id da tag <div> anteriormente, obtenha-o usando
document.getElementById(’3d-code-2018-12-31’). Em seguida, adicionamos o
renderizador ao contêiner usando o mesmo método appendChild () de que falamos
no Capítulo 9: O que é todo esse outro código?
Queremos que o renderizador caiba dentro do contêiner. Usaremos uma função
para fazer isso. Adicione esta função logo abaixo da linha que anexa o renderizador
ao container.

function resizeRenderer (){


var width = container.clientWidth * 0.96;
var height = width/aspectRatio;
renderer.setSize (width, height);
}
resizeRenderer ();
window.addEventListener ('resize', resizeRenderer, false);

A função resizeRenderer () obtém a largura do contêiner de sua propriedade


clientWidth. Reduzimos a largura um pouquinho porque isso ajuda em alguns
navegadores. Obtemos a altura correta dividindo a largura pela proporção de tela.
Por último, definimos o tamanho do renderizador para esses valores de largura e
altura.
Chamamos a função resizeRenderer () imediatamente para obter o tamanho
correto quando a página é carregada pela primeira vez. Em seguida, fazemos algo
um pouco interessante: adicionamos um atendente de evento à janela. Isso escuta
o evento “resize” e chama a função resizeRenderer (). Dessa forma, cada vez que
um usuário,
olhando nossa página, redimensiona seu navegador, o renderizador se atualiza
com o tamanho correto.
A última configuração que faremos é adicionar controles de órbita. Queremos que
nossos amigos sejam capazes de mover a câmera ao redor da cena para ter uma
visão melhor de nossa incrível criação. Já adicionamos uma tag <script> para puxar
a coleção de código para os controles de órbita, para que possamos criar os
controles agora, logo acima da linha START CODING:
new THREE.OrbitControls (camera, renderer.domElement);

Com isso, você poderá publicar sua postagem e ver seu trabalho:

Certifique-se de compartilhar seu trabalho no fórum do livro!

https://talk.code3Dgames.com/

O Código até Agora


Para ver o código completo da postagem criada para este capítulo, vá para
Código: Obtendo código na Web.

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.

Código: Criando Formas Simples


Esta é a versão final do código de formas do Capítulo 1 - Projeto: Criando
Formas Simples:

<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 "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 ();
renderer.setSize (window.innerWidth, window.innerHeight);
document.body.appendChild (renderer.domElement);

// ******** START CODING ON THE NEXT LINE ********

var shape = new THREE.SphereGeometry (100, 20, 15);


var cover = new THREE.MeshNormalMaterial (flat);
var ball = new THREE.Mesh (shape, cover);
scene.add (ball);
ball.position.set (-250,250,-250);
var shape = new THREE.CubeGeometry (300, 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);
box.position.set (250, 250, -250);

var shape = new THREE.CylinderGeometry (1, 100, 100, 4);


var cover = new THREE.MeshNormalMaterial (flat);
var tube = new THREE.Mesh (shape, cover);
scene.add (tube);
tube.rotation.set (0.5, 0, 0);
tube.position.set (250, -250, -250);

var shape = new THREE.PlaneGeometry (100, 100);


var cover = new THREE.MeshNormalMaterial (flat);
var ground = new THREE.Mesh (shape, cover);
scene.add (ground);
ground.rotation.set (0.5, 0, 0);
ground.position.set (-250, -250, -250);

var shape = new THREE.TorusGeometry (100, 25, 8, 25);


var cover = new THREE.MeshNormalMaterial (flat);
var donut = new THREE.Mesh (shape, cover);
scene.add (donut);

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 ();

// Now, show what the camera sees on the screen:


renderer.render (scene, camera);
</script>

Código: Brincando com o Console e Descobrindo o que Está


Quebrado
Não havia nenhum código funcional do Capítulo 2 - Depuração: Corrigindo o
Código Quando as Coisas dão Errado. Escrevemos alguns códigos corrompidos
em 3DE e exploramos o console JavaScript.

Código: Fazendo um Avatar


Esta é a versão final do código do avatar do Capítulo 3 - Projeto: Fazendo um
Avatar:

<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 "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 body = new THREE.SphereGeometry (100);


var cover = new THREE.MeshNormalMaterial ();
var avatar = new THREE.Mesh (body, cover);
scene.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);

// Now, animate what the camera sees on the screen:


var isCartwheeling = false;
var isFlipping = false;
function animate () {
requestAnimationFrame (animate);
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 ();
</script>

Código: Avatares em Movimento


Este é o código de avatar em movimento do Capítulo 4 - Projeto: Avatares em
movimento:

<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 "camera" is what sees the stuff:


// A "câmera" é o que vê as coisas:
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:
// 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);

// ******** START CODING ON THE NEXT LINE ********

var marker = new THREE.Object3D ();


scene.add (marker);

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);

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);
}

//Trees
// Árvores
makeTreeAt (500, 0);
makeTreeAt (-500, 0);
makeTreeAt (750, -1000);
makeTreeAt (-750, -1000);

// Now, animate what the camera sees on the screen:


// Agora, anime o que a câmera vê na tela:
var isCartwheeling = false;
var isFlipping = false;
function animate () {
requestAnimationFrame (animate);

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 ();

document.addEventListener ('keydown', sendKeyDown);


function sendKeyDown (event) {
var code = event.code;
if (code == 'ArrowLeft') marker.position.x = marker.position.x - 5;
if (code == 'ArrowRight') marker.position.x = marker.position.x + 5;
if (code == 'ArrowUp') marker.position.z = marker.position.z - 5;
if (code == 'ArrowDown') marker.position.z = marker.position.z + 5;

if (code == 'KeyC') isCartwheeling = !isCartwheeling;


if (code == 'KeyF') isFlipping = !isFlipping;
}
</script>

Código: Funções: Use e Use Novamente


Interrompemos intencionalmente muitas coisas ao explorar as funções no Capítulo
5 - Funções: Use e Use novamente. Uma cópia do código que funciona segue:

<body></body>
<script src="/three.js"></script>
<script src="controlsFlyControls.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 "camera" is what sees the stuff:


// A "câmera" é o que vê as coisas:
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:
// 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);

// ******** START CODING ON THE NEXT LINE ********

var shape = new THREE.SphereGeometry (50);


var cover = new THREE.MeshBasicMaterial ({color: 'blue'});
var planet = new THREE.Mesh (shape, cover);
planet.position.set (-300, 0, 0);
scene.add (planet);

var shape = new THREE.SphereGeometry (50);


var cover = new THREE.MeshBasicMaterial ({color: 'yellow'});
var planet = new THREE.Mesh (shape, cover);
planet.position.set (200, 0, 250);
scene.add (planet);

function makePlanet () {
var size = r (50);
var x = r (1000) - 500;
var y = r (1000) - 500;
var z = r (1000) - 1000;
var surface = rColor ();

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);
}

makePlanet ();
makePlanet ();

for (var i=0; i<100; i++) {


makePlanet ();
}

console.log (Math.random ());

function r (max) {
if (max) return max * Math.random ();
return Math.random ();
}

var randomNum = r ();


console.log (randomNum);

randomNum = r (100);
console.log (randomNum);

console.log (r (100));
console.log (r (100));

function rColor () {
return new THREE.Color (r (), r (), r ());
}

var controls = new THREE.FlyControls (camera);


controls.movementSpeed = 100;
controls.rollSpeed = 0.5;
controls.dragToLook = true;
controls.autoForward = false;
var clock = new THREE.Clock ();

function animate () {
var delta = clock.getDelta ();
controls.update (delta);

renderer.render (scene, camera);


requestAnimationFrame (animate);
}
animate ();
</script>

Código: Movendo as Mãos e os Pés


Este é o código do Capítulo 6 - Projeto: Movendo as Mãos e os Pés:
<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 "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({flatShading: true});
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);
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);
}m
akeTreeAt( 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 = falsevar isMovingBack = false;
function animate() {
requestAnimationFrame(animate);
walk();
acrobatics();
renderer.render(scene, camera);
}a
nimate();
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;
}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;
}
}d
ocument.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
var code = event.code;
if (code == 'ArrowLeft') {
marker.position.x = marker.position.x - 5;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;
}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: Uma análise mais detalhada dos fundamentos do


JavaScript
Não havia código de projeto no Capítulo 7 - Uma Análise Mais Detalhada dos
Fundamentos do JavaScript.

Código: Transformando Nosso Avatar


Este é o código do Capítulo 8 - Projeto: Transformando Nosso Avatar:
<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);
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);
}m
akeTreeAt( 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 (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;
}
}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;
}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: O Que é Todo Esse Outro Código?


Não havia nenhum código novo no Capítulo 9 - O Que é Todo Esse outro
Código? Nós apenas exploramos o código que é criado automaticamente quando
iniciamos novos projetos.

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

Código: Caçar Frutas


Este é o código do avatar depois de adicioná-lo ao jogo fruit-hunt no Capítulo 11 -
Projeto: Fruit Hunt. Este código usa WebGLRenderer para deixar as árvores um
pouco mais bonitas, mas o CanvasRenderer deve funcionar quase tão bem.

<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>

Código: Fases da Lua


Esta é a versão final do código das fases da lua do Capítulo 13 - Projeto: Fases
da Lua:

<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>

Código: The Purple Fruit Monster Game


Esta é a versão final do código do jogo do Capítulo 14 - Projeto: The Purple Fruit
Monster Game:

<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.

<p>I made this!</p>


<div id="3d-code-2018-12-31">
</div>
<p>It's in the first chapter of
<a href="http://code3Dgames.com/">
3D Game Programming for Kids, second edition</a>.
</p>
<script src="https://code3Dgames.com/three.js"></script>
<script src="https://code3Dgames.com/controls/OrbitControls.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 = 4/3;
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});
var container = document.getElementById('3d-code-2018-12-31');
container.appendChild(renderer.domElement);
function resizeRenderer(){
var width = container.clientWidth * 0.96;
var height = width/aspectRatio;
renderer.setSize(width, height);
}r
esizeRenderer();
window.addEventListener('resize', resizeRenderer, false);
new THREE.OrbitControls(camera, renderer.domElement);
// ******** START CODING ON THE NEXT LINE ********
var shape = new THREE.SphereGeometry(100, 20, 15);
var cover = new THREE.MeshNormalMaterial(flat);
var ball = new THREE.Mesh(shape, cover);
scene.add(ball);
ball.position.set(-250,250,-250);
var shape = new THREE.CubeGeometry(300, 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);
box.position.set(250, 250, -250);
var shape = new THREE.CylinderGeometry(1, 100, 100, 4);
var cover = new THREE.MeshNormalMaterial(flat);
var tube = new THREE.Mesh(shape, cover);
scene.add(tube);
tube.rotation.set(0.5, 0, 0);
tube.position.set(250, -250, -250);
var shape = new THREE.PlaneGeometry(100, 100);
var cover = new THREE.MeshNormalMaterial(flat);
var ground = new THREE.Mesh(shape, cover);
scene.add(ground);
ground.rotation.set(0.5, 0, 0);
ground.position.set(-250, -250, -250);
var shape = new THREE.TorusGeometry(100, 25, 8, 25);
var cover = new THREE.MeshNormalMaterial(flat);
var donut = new THREE.Mesh(shape, cover);
scene.add(donut);
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);
}a
nimate();
// Now, show what the camera sees on the screen:
renderer.render(scene, camera);
</script>
Apêndice 2

Coleções de Código JavaScript Usadas Neste Livro

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:

var controls = new THREE.FlyControls(camera);

Em seguida, as seguintes teclas permitem que você voe pela cena:

Motion Direction Keys


Move Forward / Backward W/S
Move Left / Right A/D
Move Up / Down R/F
Spin Clockwise / Counterclockwise Q/E
Spin Left / Right Left Arrow / Right Arrow
Spin Up / Down Up Arrow / Down Arrow

As seguintes opções estão disponíveis para os controles de vôo:

controls.movementSpeed = 100;
controls.rollSpeed = 0.5;
controls.dragToLook = true;
controls.autoForward = false;

A rapidez com que você se move é controlada por movementSpeed. A velocidade


de rotação é controlada por rollSpeed. Clicar e arrastar o mouse na cena moverá a
câmera se dragToLook estiver definido como verdadeiro. Se autoForward for true,
a câmera voará automaticamente para frente sem pressionar nenhuma tecla.

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

var controls = new THREE.OrbitControls( camera, renderer.domElement );

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:

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(0.1 vertex.x, 0.1 * vertex.y);
vertex.z = noise;
}

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

Shader Particle Engine


A coleção de código spe.js é um motor de partículas, que cria efeitos interessantes
como campos de estrelas, fogo, nuvens e muito mais. [16] Usamos spe.js para criar
fogo no Capítulo 15 - Projeto: Tilt-a-Board, quando alguém marcou um gol.
Existem muitas opções para spe.js. Se você estiver interessado em jogar mais com
spe.js, verifique alguns dos exemplos da página inicial e veja o código-fonte HTML
com Ctrl+U ou Command+U. Você deve ser capaz de copiar esse código em 3DE
para brincar com ele.

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

É UM CONDENSADO FEITO POR MIM COMO RESUMO

CONSTRUINDO O GAME
UM PASSO A PASSO DAS ETAPAS DE CRIAÇÃO DOS ELEMENTOS:
AVATAR E CENÁRIO COM FLORESTA

1 – Iniciamos com a criação do Avatar


1.1 – Criação do Corpo do avatar:

Depois de criado o arquivo: Meu Avatar, no Editor 3D, iniciamos criando o


corpo do avatar (body).

Código:

var body = new THREE.SphereGeometry(100);


var cover = new THREE.MeshNormalMaterial(flat);
var avatar = new THREE.Mesh(body, cover);
scene.add(avatar);

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:

var body = new THREE.SphereGeometry(100);


var cover = new THREE.MeshNormalMaterial();
var avatar = new THREE.Mesh(body, cover);
scene.add(avatar);

1.2 – Adicionando a Mão Direita ao avatar:

Código:

var hand = new THREE.SphereGeometry(50); - Criação de mão.


var rightHand = new THREE.Mesh(hand, cover); - Criação da mão direita.
rightHand.position.set (-150, 0, 0); - Posicion. da mão no avatar.
scene.add(rightHand); - Adicion. a mão dir. à cena.

Aqui, aproveitamos a variável cover criada para o corpo reduzindo trabalho.

1.3 – Adicionando a Mão Esquerda ao avatar:

Código:

var leftHand = new THREE.Mesh(hand, cover);


leftHand.position.set (150, 0, 0);
scene.add (leftHand);

1.4 – Adicionando os Pés Direito e Esquerdo do avatar:

Código:

var foot = new THREE.SphereGeometry(50);


var rightFoot = new THREE.Mesh(foot, cover);
var rightFoot.position.set (-75, -125, 0);
scene.add(rightFoot);
var leftFoot = new THREE.Mesh(foot, cover);
leftFoot.position.set (75, -125, 0);
scene.add(leftFoot);

1.5 – Fazendo o avatar dar uns giros (piruetas)

Criaremos uma função chamada animate.


function animate () {
requestAnimationFrame (animate);
avatar.rotation.z = avatar.rotation.z + 0.05;
renderer.render (scene, camera);
}
Animate ();

Algo deu errado. Só o corpo do avatar gira. Por quê?

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?

Para controlar o movimento podemos fazer o seguinte: criar uma condição


que faça o avatar parar ou girar dependendo dessa condição, como a seguir:

var isCartwheeling = false;


function animate () {
requestAnimationFrame (animate);
if (isCartwheeling) {
avatar.rotation.z = avatar.rotation.z + 0.05;
}
renderer.render(scene, camera);
}
animate();

Agora o avatar parou de girar. Se mudarmos a condição de false (falso) para


true (verdadeiro), o avatar volta a girar.
Mas não é ainda o ideal. Melhor seria se pudéssemos controlar o movimento
do avatar, pelo teclado do computador, como é feito nos jogos. Para isso
precisamos incluir no código os comandos que operam o teclado – são
chamados de eventos (event) e atendente do evento (eventListener).

Isto seria assim:

document.addEventListener ('keydown', sendKeyDown);


function sendKeyDown(event) {
alert(event.code);
}

Aqui, o atendente de evento (EventListener) recebe dois argumentos: o


argumento keydown e o argumento sendKeyDown.
O keydown se refere ao pressionamento de uma tecla do teclado e que é
percebido pelo atendente de evento que informará ao sendKeyDown (que
é uma função) que envie um comando ao avatar e que no código acima, esse
comando é uma alerta – alert (event.code). Esse comando mostra, como
alerta, uma janela onde diz qual foi a tecla pressionada.

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:

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;

o código fica assim:

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;
}
Inclua esse novo código logo abaixo da função animate () que fica no final do
código já digitado até então. Observe que temos uso de == e =. O igual duplo
é usado como operador de igualdade (por exemplo: 2+2 é igual a 4) e o
simples é usado como operador atribuição (exemplo: var número = 4 ou seja,
dizemos que: está sendo atribuído o valor 4 à variável número e não: a
variável número é igual a 4).

Agora, o EventListener (atendente de evento) vai perceber o keydown


(evento de pressionamento de tecla) e chamará a função sendKeyDown
para executar o comando referente à uma tecla pressionada, conforme uma
das previstas no corpo desta função: seta direita, seta esquerda, seta acima
e seta abaixo, movimentando o avatar em relação aos respectivos eixos
indicados por essas setas na função.

Vamos experimentar mudar um pouco o código atual só pra ver o que


acontece. Mude a linha referente a avatar.add(leftFoot) para
scene.add(leftFoot) – depois de constatado o que acontece, mude
novamente para a forma anterior.
O que acontece? Vemos que o avatar se movimenta, mas o pé esquerdo
(leftFoot) fica estático. Isso é porque a função animate() se refere ao avatar
e, como leftFoot, agora, não está adicionado a ele, então leftFoot não faz
parte do avatar e sim da cena (scene).

Mas e o movimento de pirueta (Cartwheeling)? Como controlar pelo teclado?


Vamos incluir mais duas instruções if ao nosso código atual, uma para
controlar o giro (Cartwheeling) e outra para controlar a inversão (Flipping).

Inclua, logo após a linha: var isCartwheeling = false, o seguinte:

var isFlipping = false;

e, um pouco mais abaixo e após as linhas: avatar.rotation.z =


avatar.rotation.z + 0.05;
}, inclua:

if (isFlipping) {
avatar.rotation.x = avatar.rotation.x + 0.05;
}.

Aqui, incluímos as condições de false ou true para Flipping (inversão), igual


às do Cartwheeling (para ativar ou desativar o flipping).
E, finalmente, para controlar o cartwheeling e o flipping pelo teclado, inclua
abaixo da última instrução if extras:

if (code == 'KeyC') isCartwheeling = !isCartwheeling;


if (code == 'KeyF') isFlipping = !isFlipping;

Agora, a tecla C ativa ou desativa o movimento de giro (cartwheeling) em


relação ao eixo z e a tecla F, ativa ou desativa a inversão (flipping) do
movimento de giro para o eixo x.

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.

1.6 – Criando árvores.

Abaixo do código atual inclua a seguinte função:

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);
}

makeTreeAt (500, 0);


makeTreeAt (-500, 0);
makeTreeAt (750, -1000);
makeTreeAt (-750, -1000);
Agora nosso avatar está em uma floresta de quatro árvores (adicionadas à
cena – scene.add).
A função makeTreeAt(x, z) cria árvores usando o cilindro para formar o
tronco (var trunk) e a esfera para formar a copa (var top) das árvores. O uso
da função makeTreeAt não é indispensável pois sabemos como criar uma
árvore seguindo o mesmo raciocínio quando criamos o avatar, mas como são
quatro árvores teríamos que repetir quatro vezes o trabalho, enquanto que,
usando uma função fica menos trabalhoso.

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:

Exclua a linha: scene.add(camera); - que existe no código de criação da


câmera, logo no início da codificação e inclua a linha: avatar.add (camera);
logo após alinha: avatar.add (leftFoot);

Agora a câmera acompanha o avatar quando este se movimenta.

Só que surge um problema: quando o avatar gira ou faz piruetas, a câmera


também faz o mesmo. E não é isso que queremos. Então, fixar a câmera ao
avatar não foi o correto. O que precisamos é travar a câmera na posição do
avatar.
O artifício usado para travar a câmera a uma posição chama-se marcador
de posição (marker). Devemos adicionar um marcador (marker) ao game
(à cena). Depois adicionamos o avatar e a câmera ao marcador. No final é
como se tivéssemos uma caixa (o marcador) e dentro da caixa estão a
câmera e o avatar, então se o avatar andar e girar a câmera acompanha o
avatar, mas não gira.

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.

Inclua o seguinte código logo antes do início da criação do corpo do avatar


(body) – após: START CODING ON THE NEXT LINE:
var marker = new THREE.Object3D();
scene.add(marker);

Aqui, criamos o marcador (marker – um objeto 3D) e o adicionamos à cena.

Agora precisamos mudar, para que o avatar fique anexado ao marcador e


não à cena. Então substitua a linha: scene.add(avatar) por
marker.add(avatar).

Fazer o mesmo com a câmera, onde está scene.add(camera) mude para


marker.add(camera).

E, finalmente, na função sendKeyDown(event); mudar em cada instrução


if, substituindo, onde consta: avatar.position por marker.position.

O código ficará desta forma:

document.addEventListener ('keydown', sendKeyDown);


function sendKeyDown(event) {
var code = event.code;
if (code == 'ArrowLeft') marker.position.x = marker.position.x - 5;
if (code == 'ArrowRight') marker.position.x = marker.position.x + 5;
if (code == 'ArrowUp') marker.position.z = marker.position.z - 5;
if (code == 'ArrowDown') marker.position.z = marker.position.z + 5;
if (code == 'KeyC') isCartwheeling = !isCartwheeling;
if (code == 'KeyF') isFlipping = !isFlipping;
}

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).

Continuando o desenvolvimento do game, observamos que o avatar anda


para os lados, vai e volta, mas o corpo, as mãos e os pés, ficam rígidos. Na
vida real ninguém se movimenta desta forma. Então vamos dar mais realismo
aos movimentos do avatar. Quando ele andar, vai virar o corpo para a
direção do movimento e o movimento dos pés e das mãos será sincronizado,
esquerdo e direitos alternados e na direção do movimento de andar.

1.7 – Movendo uma mão


Para simular o movimento alternado das mãos e dos pés, na direção do
andar, precisamos alterar a posição destes na direção do eixo z. Então,
vamos incluir a linha: rightHand.position.z = 100; abaixo da linha:
avatar.add(rightHand); que se encontra na parte que cria a mão direita do
avatar. Vemos, agora que a mão direita se afastou do corpo, dando a
sensação que veio para a frente.
Se mudarmos o valor de z=100 para z=-100 notamos que a mão direita fica
por trás do corpo, como se fosse movida para trás. Alternando z entre 100 e -
100 continuamente, vai criar o movimento de vai e vem junto com o caminhar
do avatar. Mas o movimento da mão (no caso braço) será horizontal e na
direção do andar. Na realidade, os braços se movimentam em forma de
rotação em relação ao ombro e não horizontalmente como se fosse solto do
corpo. Então devemos fazer uso de giros (cartwheerings) e inversões
(flippings) para a animação alternada dos braços e pernas do avatar. Mas
antes vamos excluir a última linha que criamos para o exemplo de mover os
braços na direção z. Exclua a linha: rightHand.position.z = 100; criada
abaixo da linha: avatar.add(rightHand);

Agora, vamos criar a função acrobatics () que fará a inversão (flipping) e a


rotação (cartwheering) para os braços quando o avatar estiver se movendo.
Código da função acrobatics ():

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
()

O código resultante fica assim:

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;
}
}

Substitua, agora este novo código da função animate () pelo anterior.

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);

var clock = new THREE.Clock(); (linha incluída)


var isCartwheeling = false;

Incluir, também, a linha: walk (); logo acima da linha acrobatics (); que fica
dentro da function animate ();

E, por fim, definir a função walk (); assim:

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;

Agora, as mãos e os pés estão se movendo, continuamente, pra frente e pra


trás, mas não de forma alternada, como seria se o avatar estivesse
caminhando. O correto é um pé à frente e o outro atrás, assim como as
mãos.

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.

Agora temos o movimento real de pés e mãos de quem anda. Mas os


movimentos são sem controle, e quando anda para os lados o avatar não vira
o corpo para a direção da caminhada.

Vamos primeiro resolver o problema de virar o corpo ao andar.


Incluir as quatro linhas seguintes logo acima da linha: function animate ();

var isMovingRight = false;


var isMovingLeft = false;
var isMovingForward = false
var isMovingBack = false;
Essas quatro instruções dizem que o avatar não está se movendo para
nenhuma direção (todas são = a falso).
A seguir criamos a função isWalking () – está andando – 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;
}

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()
{

Aqui, o ponto de exclamação é um operador lógico e significa não (negação),


ou seja, se (não está andando ()) retorne (esse return é o mesmo que: “saia
da função sem fazer nada”). Isso acontece quando nenhuma tecla de direção
está sendo pressionada e o avatar não vai mais ficar se movendo sozinho,
vai ficar parado. Se uma tecla for pressionada, o avatar vai andar e a
instrução: if (!isWalking ()); é descartada e a função walk(); é executada,
movendo o avatar de acordo com a tecla pressionada.

Agora, para as mãos e os pés se movimentarem quando pressionarmos uma


tecla, precisamos incluir as instruções necessárias na função responsável
pelo movimento do avatar. Essa função é a sendKeyDown (event).

Inclua a linha: isMoving.Left = true; logo abaixo da linha:


marker.position.x=marker.position.x-5; que fica dentro da função
sendKeyDown(event) e repita esse procedimento para isMovingRight /
Forward e Back.

Ficando assim a função sendKeyDown (event):

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.

1.7 – Virando o corpo do avatar quando ele caminha.

Vamos para alista de comandos que controlam os movimentos do avatar.


Esta lista fica acima da função animate (). Abaixo da última linha da lista:
var isMovingBack = false; inclua as linhas:

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);

Agora sim, o avatar está caminhando de forma mais “realista”.


Mas observe que quando deixamos de pressionar a tecla de direção - e
consequentemente o avatar para de caminhar (na direção referente à tecla
pressionada) – o avatar se vira, ficando de frente à câmera. Isso é devido ao
comando: if (!isWalking ()) direction = 0; (este comando diz: se não está
andando, então vire para a direção zero – que é de frente para a câmera).

Se desejarmos que o avatar continue virado para a direção do movimento,


mesmo após soltarmos a tecla de direção, basta excluir esse comando.

Vamos, agora, corrigir um erro que está acontecendo: quando o avatar


encontra uma árvore no caminho, ele atravessa a mesma como se fosse um
raio-X, para isso, no próximo item vamos tratar de COLISÕES.

1.8 -

Você também pode gostar