jogos 3d

64
7. JOGOS 3D Chegamos agora à parte que provavelmente você estava aguardando. Não é de se estranhar afinal, com as engines 3D, nós temos total liberdade de movimento e infinitas possibilidades em termos de jogabilidade. Eu me lembro até hoje dos primeiros jogos 3D que joguei: Wolfenstein 3D, Marathon e Doom. A sensação de sair dos limites tradicionais de um jogo 2D foi incrível. Como possuía um computador Apple, tive a exclusividade de conferir os incríveis gráficos de Marathon que deixavam qualquer concorrente no chinelo. A partir desse momento você vai poder entrar nesse seletíssimo submundo: o dos desenvolvedores de incríveis jogos 3D. Graças ao poder e a simplicidade do Blitz3D isso vai se dar de forma simples e sem traumas. Vamos lá! Introdução a 3D Enquanto no modo gráfico 2d trabalhávamos com apenas duas coordenadas “x e y”, uma vertical e outra horizontal, no modo 3d temos a adição de mais um eixo o eixo “z”, de profundidade. Gráficos 2d Gráficos 3d Dessa forma, se nos jogos 2d tínhamos que controlar só 2 variáveis de posicionamento, em um jogo 3d temos 3 variáveis de posicionamento. Mas não se preocupe, pois isso não significa que necessariamente você terá 50% a mais de trabalho. A criação de um jogo 3d é totalmente diferente da de um jogo 2d. Enquanto em jogos 2d usamos bitmaps para criar praticamente todo o jogo, em uma engine 3d usamos malhas 3d, os objetos 3d. Um objeto 3d normalmente é feito de um ou mais triângulos. As placas aceleradoras gráficas 3d usam basicamente triângulos para comporem a imagem, pois 3 vértices são a menor quantidade de dados matemáticos necessários para se compor um plano. Malha com 3 vértices e 1 triângulo Malha com 4 vértices e 2 triângulos Veja que de fato a composição de malhas 3d com triângulos é a mais simples, e a que pode render maior capacidade de processamento, pois a cada novo vértice criado, uma nova face (triângulo) também pode ser criada.

Upload: katia-e-david-lindgren

Post on 03-Jan-2016

21 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: jogos 3D

7. JOGOS 3D Chegamos agora à parte que provavelmente você estava aguardando. Não é de se estranhar afinal, com as engines 3D, nós temos total liberdade de movimento e infinitas possibilidades em termos de jogabilidade.Eu me lembro até hoje dos primeiros jogos 3D que joguei: Wolfenstein 3D, Marathon e Doom. A sensação de sair dos limites tradicionais de um jogo 2D foi incrível. Como possuía um computador Apple, tive a exclusividade de conferir os incríveis gráficos de Marathon que deixavam qualquer concorrente no chinelo.A partir desse momento você vai poder entrar nesse seletíssimo submundo: o dos desenvolvedores de incríveis jogos 3D. Graças ao poder e a simplicidade do Blitz3D isso vai se dar de forma simples e sem traumas.Vamos lá!

Introdução a 3DEnquanto no modo gráfico 2d trabalhávamos com apenas duas coordenadas “x e y”, uma vertical e outra horizontal, no modo 3d temos a adição de mais um eixo o eixo “z”, de profundidade.

Gráficos 2d Gráficos 3d

Dessa forma, se nos jogos 2d tínhamos que controlar só 2 variáveis de posicionamento, em um jogo 3d temos 3 variáveis de posicionamento. Mas não se preocupe, pois isso não significa que necessariamente você terá 50% a mais de trabalho. A criação de um jogo 3d é totalmente diferente da de um jogo 2d. Enquanto em jogos 2d usamos bitmaps para criar praticamente todo o jogo, em uma engine 3d usamos malhas 3d, os objetos 3d.Um objeto 3d normalmente é feito de um ou mais triângulos. As placas aceleradoras gráficas 3d usam basicamente triângulos para comporem a imagem, pois 3 vértices são a menor quantidade de dados

matemáticos necessários para se compor um plano.Malha com 3 vértices e 1 triângulo Malha com 4 vértices e 2 triângulos

Veja que de fato a composição de malhas 3d com triângulos é a mais simples, e a que pode render maior capacidade de processamento, pois a cada novo vértice criado, uma nova face (triângulo) também pode ser criada.Cada vértice possui uma coordenada x, y e z e pode ser manipulado individualmente. O fato de os vértices possuírem a coordenada z é que possibilita a criação de objetos 3d, pois do contrário só poderiam ser criados objetos planos.Abaixo você pode ver como o computador cria os cenários 3d por meio de triângulos e vértices. As imagens foram tiradas do projeto Tucano 3d, de minha autoria.

Page 2: jogos 3D

modo normal modo wire (mostra a estrutura)Como você pode ver, tudo no jogo é feito por triângulos, que são preenchidos por mapeamento de textura. Mas você não precisa se preocupar com isso se não quiser.O Blitz3D dá a possibilidade de trabalhar com objetos 3d tanto em modo bem primário, manipulando vértice por vértice, como de modo bem mais genérico, com modelos criados em programas externos, onde você só precisa de uma linha de código para carregá-los e mais uma linha para fazer com que acione as suas animações.

Modo gráficoA engine 3d do Blitz roda em modo gráfico próprio, assim, antes de qualquer outra coisa, devemos configurar o modo gráfico para começar a trabalhar com aplicações 3d.O exemplo abaixo mostra como fazer isso.Exemplo 1Graphics3D 640,480 SetBuffer BackBuffer() End

Veja que o comando para iniciar o modo gráfico 3D é Graphics3D. Esse comando é semelhante ao comando de modo gráfico 2d, ele também aceita as configurações de paleta de cores e de modo da janela. Veja um novo exemplo.

Exemplo 2Graphics3D 800,600, 32, 2 SetBuffer BackBuffer() WaitKeyEnd

Agora o programa roda em modo janela com 32 bits de profundidade de cores.

O mínimo necessárioPara começarmos a trabalhar com visualizações de objetos 3d existem configurações mínimas necessárias do programa. Não podemos trabalhar em modo 3d sem iniciar o modo gráfico 3d. Não podemos exibir um objeto 3d na tela se não existir uma câmera na aplicação e nada iremos enxergar se o ambiente estiver em profunda escuridão. Assim abaixo temos os requisitos para iniciar uma aplicação 3d:1. configura o modo gráfico2. criar uma câmera3. criar iluminaçãoVamos agora a um exemplo que contenha esses requisitos mínimos.

Exemplo 3

Graphics3D 640,480 SetBuffer BackBuffer() camera=CreateCamera() light=CreateLight() WaitKeyEnd

Ai está o nosso programa mínimo. A primeira coisa que fizemos foi definir o modo gráfico:Graphics3D 640,480

Logo em seguida configuramos o modo de double buffer, criando um buffer para desenho. Assim os gráficos vão ficar com melhor aparência e mais suaves.SetBuffer BackBuffer()

Jamais poderemos esquecer de criar uma câmera. As câmeras são nossos olhos em uma aplicação 3d. Tudo o que vemos em um ambiente desse tipo é capitado pela câmera, sem ela só veremos uma tela preta.camera=CreateCamera()

Criamos a câmera com o comando CreateCamera() e colocamos sua referência (handle) em uma variável, no caso camera.light=CreateLight()

Nessa linha geramos a iluminação por meio da criação de uma lâmpada. Veja que também declaramos uma variável para ela. O Blitz3D já inicializa com uma iluminação ambiente default, mas em tom cinza, por isso devemos criar uma lâmpada. WaitKeyEnd

Aqui aguardamos até que uma tecla seja pressionada para encerrar a aplicação.Nós já temos uma aplicação completa, mas ainda não visualizamos nada. No próximo exemplo carregamos e exibimos um modelo 3d para que você veja o efeito desses comandos em ação. O modelo 3d vem junto com o Blitz3D, mas o coloquei na pasta dos exemplos para facilitar nossa vida. Os comando usados serão explicados mais à frente.

Page 3: jogos 3D

Exemplo 4Graphics3D 640,480 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() carro=LoadMesh("midia/caro.3ds")RotateEntity carro, 0, 30, 0PositionEntity carro,0,-200,700 While Not KeyHit(1)y = y + 1RotateEntity carro, 0, y, 0RenderWorld Flip WendEnd

Para que os objetos 3d apareçam na tela é necessário ordenar ao blitz que renderize os objetos. Isso é feito nessa linha:RenderWorld

Com esse comando, renderizamos os objetos 3d e colocamos essa imagem no buffer de imagem. Para que eles apareçam no monitor devemos transferir a imagem para o buffer do monitor.Flip

Isso foi incrível, não foi?

PRIMITIVASO blitz possui comandos para criar 5 tipos de primitivas gráficas 3d: planos, cubos, esferas, cilindros e cones. Essas primitivas podem ser muito úteis para criação de cenários caso você não possua ou não saiba usar um modelador 3d. Também podem ser usadas para diversas finalidades, como ajudar no controle de um objeto 3d, restrição de movimentos, ou auxiliar na detecção de colisões.

PlanoO plano é a primitiva mais básica e é composto apenas de 2 triângulos e 4

vértices. É a primitiva ideal para quem deseja construir algo extremamente leve. Só devemos ter o cuidado de deixar a sua face voltada para o lado correto. Também é a primitiva ideal para criar suporte a restrição de movimentos ou colisões. Para isso o deixamos invisível e verificamos se um objeto colide com ele.Vamos a um exemplo de criação de um plano.

Exemplo 5Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera,0,1,0light=CreateLight() plano= CreatePlane()RenderWorld Flip WaitKeyEnd

CuboUm cubo é formado por 6 faces, assim ele possui 12 triângulos na sua composição. É a primitiva mais básica para compor objetos com volume, como paredes, muros, caixas, escadas, etc.

Exemplo 6Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() cubo= CreateCube()PositionEntity cubo,2,0,6RenderWorld Flip WaitKeyEnd

O comando CreateCube() cria um cubo de dimensões 2,2,2 e na posição 0,0,0. Também podemos criar uma hierarquia passando um objeto pai como referência com a sintaxe CreateCube(objetopai). Dessa forma o cubo criado ficará ligado ao objeto pai.Usamos o comando PositionEntity para colocar o cubo em um local que seja visível à câmera, senão não iríamos visualizar o objeto.

EsferaCom o comando CreateSphere() podemos criar esferas no Blitz3D. O comando padrão, sem parâmetros, cria uma esfera de raio igual a 1 e na posição 0,0,0. A esfera criada terá 8 segmentos e 224 polígonos de composição.

Page 4: jogos 3D

Também podemos passar como parâmetro a quantidade de segmentos que a esfera deverá possuir e/ou um objeto para criar uma hierarquia.

Exemplo 7Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() esfera1 = CreateSphere()PositionEntity esfera1,3,0,6esfera2 = CreateSphere(3)PositionEntity esfera2,-3,0,6esfera3 = CreateSphere(16, esfera1)PositionEntity esfera3,-3,0,0RenderWorld Flip WaitKeyEnd

Na primeira esfera que criamos usamos o comando padrão, sem passar parâmetros, assim será construída uma esfera de 8 segmentos.esfera1 = CreateSphere()

Para nossa segunda esfera, estamos passando o parâmetro 3, assim iremos construir uma esfera de 3 segmentos.esfera2 = CreateSphere(3)

Por fim, passamos para a terceira esfera os parâmetros “16, esfera1”, assim, a terceira esfera a ser criada, terá alta resolução, pois é formada por 16 segmentos, e será filha da primeira esfera.esfera3 = CreateSphere(16, esfera1)

A filiação dessa esfera para com a esfera 1, dá a ela a posição geográfica relativa de seu objeto pai, assim, para colocá-la ao centro, tivemos que fazer compensações no comando de posicionamento.PositionEntity esfera3, -3, 0, 0

CilindrosPara criar cilindros usamos o comando CreateCylinder(). Por padrão, o comando cria um cilindro de 8 segmentos sólido. Mas esse comando também permite passarmos parâmetros de segmentos, sólido e hierarquia. Assim, a assinatura do comando é “CreateCylinder(sementos, sólido, hierarquia)”. O parâmetro deve ser configurado em 1 para sólidos e 0 para criar um tubo.

Exemplo 8Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight()

ci = CreateCylinder()PositionEntity ci,2,0,5ci2 = CreateCylinder(32)PositionEntity ci2,-2,0,5RotateEntity ci2, 90,0,0RenderWorld Flip WaitKeyEnd

ConesPara criar cones, usamos o comando CreateCone(). Esse comando possui a mesma assinatura do CreateCylinder, assim aceita os parâmetros: segmentos, sólido e hierarquia.

Exemplo 9Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() cone = CreateCone(32)PositionEntity cone,1,0,3RenderWorld Flip WaitKeyEnd

Pintar Objetos 3dPodemos criar um pincel no Blitz3D e com ele podemos pintar objetos 3d. O primeiro passo é criar um pincel (brush) como o comando CreateBrush() e em seguida pintamos o objeto com esse pincel com o comando PaintMesh().

Exemplo 10Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() ob = CreateSphere(32)PositionEntity ob,1,0,3pincel = CreateBrush()BrushColor pincel, 255,0,0PaintMesh ob, pincelRenderWorld Flip WaitKeyEnd

A primeira coisa que fizemos foi criar o pincel, o brush.pincel = CreateBrush()

Em segundo momento, definimos uma cor para o pincel. Escolhemos a cor vermelha (255,0,0).BrushColor pincel, 255,0,0

O ultimo passo é pintar um objeto com o pincel.PaintMesh ob, pincel

Page 5: jogos 3D

TEXTURASTexturas são imagens que são aplicadas nas superfícies dos objetos 3d. Uma textura é mapeada na face de um objeto, criando um nível muito alto de realismo. Mas à frente iremos estudar técnicas avançadas de texturização. Nesse momento vamos aprender a carregar uma textura e aplicar a mesma em um objeto 3d.Para carregar uma textura usamos o comando LoadTexture() e para aplicar a mesma em um objeto usamos o comando EntityTexture. Veja o exemplo abaixo.

Exemplo 11Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() textura = LoadTexture("midia\parede.jpg") cubo = CreateCube()PositionEntity cubo,0,0,4EntityTexture cubo, texturaWhile Not KeyHit(1) TurnEntity cubo, 0, 1, 0RenderWorld Flip WendEnd

O primeiro passo é carregar uma textura de um arquivo externo para uma variáveltextura = LoadTexture("midia\parede.jpg")

Depois disso criamos o cubo, o objeto que receberá a textura.cubo = CreateCube()PositionEntity cubo,0,0,4

Por fim vamos aplicar a textura no cuboEntityTexture cubo, textura

Dentro do loop estamos rotacionando o cubo no eixo y com o comando TurnEntity.TurnEntity cubo, 0, 1, 0

Texturas com canal alphaAssim como fazemos máscaras com imagens em jogos 2d para remover o fundo de uma imagem, também podemos fazer algo semelhante com texturas. Imagine que você tem um ponto de seu jogo que a lateral de um ambiente é protegida com uma cerca ou uma grade. Se você fosse criar isso manualmente num modelador 3d você gastaria muito tempo e o resultado seria um modelo de alta complexidade que com certeza sobrecarregaria o seu jogo.Uma outra solução para isso é criar uma textura com o desenho da cerca ou grade, e

colocar um fundo preto. Daí basta colocarmos uma frag de carregamento da textura para 2, isso é, com canal alfa. Quando texturizarmos um objeto esse ficará transparente na parte escura da textura.Veja o exemplo a seguir

Exemplo 12Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,2,0light=CreateLight() cerca = LoadTexture("midia\alfa.png",2) ScaleTexture cerca, .2, 1Bloco = CreateCube()ScaleEntity Bloco, 3,1.5, 0.2PositionEntity bloco, 0, 1.1, 7b = CreateBrush()BrushColor b, 255, 0, 0PaintMesh bloco, bEntityTexture bloco, cercaRenderWorld Flip WaitKeyEnd

Veja que ao carregarmos a textura com o comando LoadTexture colocamos mais um parâmetro, a flag 2.cerca = LoadTexture("midia\alfa.png",2) Para que a cerca não ficasse totalmente branca, colorimos o modelo com um brush.b = CreateBrush()BrushColor b, 255, 0, 0PaintMesh bloco, b

Para aplicar a textura com canal alfa no objeto não há nenhuma novidade, segue-se o padrão.EntityTexture bloco, cerca

Ajustar texturasÉ muito difícil uma textura se ajustar perfeitamente a um modelo na qual será aplicada. Normalmente deveremos colocar várias cópias da textura alinhadas para dar um efeito maior de realismo. Um exemplo disso é quando vamos criar objetos grades como paredes e pisos. Se colocarmos apenas uma textura, ela será esticada e ficará disforme e desproporcional. Isso pode ser corrigindo com o comando ScaleTexture.Esse comando ajusta o quantidade de instâncias da textura que deverão ser aplicadas em um objeto. Como o objeto é mapeado por completo, quanto menor o tamanho da textura, mais ocorrências dela aparecerão em uma face do objeto.

Page 6: jogos 3D

Uma ocorrência por face => ScaleTexture textura, 1, 1

Uma ocorrência horizontal e 2 verticais => ScaleTexture textura, 1, 0.5

Dez ocorrências em todas as faces => ScaleTexture textura, 0.1, 0.1

Veja o exemplo abaixo onde criamos vários cubos do mesmo tamanho, mas configuramos as texturas com escalas diferentes.

Exemplo 13Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() Bloco1 = CreateCube()ScaleEntity Bloco1, 2, 2, 1PositionEntity Bloco1, -3, -2.2, 7Bloco2 = CreateCube()ScaleEntity Bloco2, 2, 2, 1PositionEntity Bloco2, 3, -2.2, 7Bloco3 = CreateCube()ScaleEntity Bloco3, 2, 2, 1PositionEntity Bloco3, -3, 2.2, 7Bloco4 = CreateCube()ScaleEntity Bloco4, 2, 2, 1PositionEntity Bloco4, 3, 2.2, 7tex1= LoadTexture("midia\parede.jpg") tex2= LoadTexture("midia\parede.jpg")tex3= LoadTexture("midia\parede.jpg")tex4= LoadTexture("midia\parede.jpg")ScaleTexture tex1, 1, 1EntityTexture bloco1, tex1ScaleTexture tex2, .5, 1EntityTexture bloco2, tex2ScaleTexture tex3, .5, .5EntityTexture bloco3, tex3ScaleTexture tex4, .2, .2EntityTexture bloco4, tex4RenderWorld Flip WaitKey

End

CoordenadasTambém podemos ajustar o posicionamento da textura no objeto por meio do comando PositionTexture. Por meio dele podemos ajustar a posição x e y da textura no objeto.

Exemplo 14Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() Bloco1 = CreateCube()ScaleEntity Bloco1, 5, 4, 1PositionEntity Bloco1, 0, 0, 7tex1= LoadTexture("midia\agua.png")ScaleTexture tex1, .2, .2EntityTexture bloco1, tex1While Not KeyHit(1)x# = x# + 0.005 PositionTexture tex1, x#, y RenderWorld FlipWendEnd

Veja que a assinatura desse comando é: PositionTexture textura, coordenada x, coordenada y

Texturas animadasAssim como podemos carregar imagens animadas, também podemos carregar texturas animadas. Para isso usamos o comando LoadAnimTexture(). Sua sintaxe é quase igual a do comando LoadAnimImage(), mas com a diferença que também devemos colocar o flag de textura. Confira:LoadAnimTexture(arquivo, flag, largura, altura, primeiro frame, frames)Veja o exemplo abaixo

Exemplo 15Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() Bloco1 = CreateCube()ScaleEntity Bloco1, 5, 4, 1PositionEntity Bloco1, 0, 0, 7tex1= LoadAnimTexture("midia\aguaani.png",1,150,150,0,2)ScaleTexture tex1, .2, .2While Not KeyHit(1)x = x + 1If x = 10x = 0frame = frame + 1If frame = 2 Then frame = 0 EndIfEntityTexture bloco1, tex1, frame

Page 7: jogos 3D

RenderWorld FlipWendEnd

Camadas de texturasUm dos maiores problemas no mapeamento de texturas é que quando temos que repetir várias vezes a mesma textura para preencher corretamente um objeto, causamos a sensação de uniformidade e fica visível que o que vemos é a repetição de várias imagens.No mundo real as coisas não são uniformes, elas têm disformidades aleatórias e o mapeamento de textura causa o efeito contrário, o de uniformidade.Para resolver isso podemos sobrepor camadas de texturas, assim devemos colocar uma textura para fazer um mapeamento de detalhe sobre a textura base, tirando o efeito de uniformidade causado pela repetição dessa. O ideal é que a textura filtro tenha uma escala de 1x1 para não causar repetições.O exemplo abaixo demonstra um mapeamento de textura com uma só camada e o mapeamento com o uso de uma textura para gerar detalhes.

Exemplo 16Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() Bloco1 = CreateCube()ScaleEntity Bloco1, 5, 4, 1PositionEntity Bloco1, 0, 0, 7tex1= LoadTexture("midia\pedra.jpg")ScaleTexture tex1, .2, .2EntityTexture bloco1, tex1, 0, 0filtro= LoadTexture("midia\filtro.jpg")While Not KeyHit(1)If KeyDown(57) Then EntityTexture bloco1, filtro, 0, 1RenderWorld Text 70, 10, "PRESSIONE SPACE PARA APLICAR FILTRO"FlipWendEnd

Veja que para aplicar camadas, devemos configurar a aplicação de cada textura com uma camada de acordo com a assinatura completa do comando EntityTexture, conforme abaixo:EntityTexture objeto, variável, frame, camadaPor isso que, como mostrado na linha abaixo, aplicamos essas camadas na textura.

EntityTexture bloco1, tex1, 0, 0A textura básica é aplicada como camada 0.If KeyDown(57) Then EntityTexture

bloco1, filtro, 0, 1Se apertarmos a barra de espaço, então aplicamos a camada seguinte, a de detalhamento.O Blitz3D suporta até 8 camadas de texturas, que vão da camada 0 até a camada 7. Aprender a usar corretamente esse recurso gera objetos de alta qualidade gráfica.

Cenários com primitivasCaso você não possua um modelador 3d, ou não saiba usar um, você pode criar cenários para seus jogos com primitivas gráficas texturizadas do blitz, veja o exemplo abaixo.

Exemplo 17Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,2,0light=CreateLight() tchao = LoadTexture("midia\chao.jpg") ScaleTexture tchao, .05, .05tparede = LoadTexture("midia\pedra.jpg") ScaleTexture tparede, .05, .5tbloco = LoadTexture("midia\parede.jpg") ScaleTexture tparede, .05, .5chao = CreateCube()ScaleEntity chao, 20,0.2, 20PositionEntity chao, -10, 0, -10parede1 = CreateCube()ScaleEntity parede1, 20,2, 0.5PositionEntity parede1, -10, 2, 10Bloco = CreateCube()PositionEntity bloco, 2, 1.1, 7EntityTexture chao, tchaoEntityTexture parede1, tparedeEntityTexture bloco, tblocoWhile Not KeyHit(1) RenderWorld Flip WendEnd

Veja o resultado:

Page 8: jogos 3D

TRANSFORMAÇÕESOs objetos 3d são passíveis de várias transformações como escalonamento (alterar o tamanho), posicionamento, rotação, movimentação, etc. Vamos estudar as principais transformações.

Escalonar Escalonar um objeto 3d é alterar suas escalas nos eixos x, y e z. Podemos aumentar ou diminuir um objeto em qualquer um desses três eixos. Podemos escalonar um objeto com o comando ScaleEntity ou escalonar os vértices dele por meio do comando ScaleMesh. Vamos a um exemplo.

Exemplo 18Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() ob = CreateSphere(32)PositionEntity ob,0,0,5x# = 1While Not KeyHit(1)Text 10, 10, "Aperte Space para escalonar"If KeyHit(57) x# = x# + 0.2ScaleEntity ob, x#, x#, x#EndIfRenderWorld Flip WendEnd

Veja que podemos escalonar um objeto nos seus três eixos:ScaleEntity ob, x#, x#, x#

Caso você deseje escalonar em um só eixo, basta deixar os demais com o valor 1, que corresponde ao tamanho real, sem escala. Substitua o trecho de código acima como na linha abaixo e faça um teste:ScaleEntity ob, x#, 1, 1

O próximo exemplo usa o comando de escalonamento dos vértices: ScaleMesh

Exemplo 19Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() ob = CreateSphere(32)PositionEntity ob,0,0,5x# = 1While Not KeyHit(1)Text 10, 10, "Aperte Space para escalonar"If KeyHit(57) x# = x# + 0.2ScaleMesh ob, x#, x#, x#EndIfRenderWorld Flip WendEnd

Rotação Existem três comando que possibilitam fazer rotação em um objeto 3d: RotateMesh, TurnEntity e RotateEntity. Embora cada um deles se dispõe à mesma finalidade, o fazem de maneiras diferentes, causando efeitos secundários diferentes. O primeiro que veremos em ação é o comando RotateMesh. A peculiaridade desse comando é o fato de rotacionar apenas os vértices do objeto. Embora isso cause a aparência visual de que o objeto todo foi rotacionado, seus dados lógicos não sofrem modificações, isso é, a orientação absoluta, suas faces continuam voltadas para o mesmo lugar.Ele também funciona por orientação relativa, isso é, o valor que passarmos a ele será calculado a partir do angulo atual do objeto e não de seu posicionamento relativo às coordenadas da engine. Isso significa que não precisamos criar um contador para criar um efeito de rotação contínua. Basta passar um pequeno valor e dar loops.

Exemplo 20Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() ob = CreateCube()PositionEntity ob,0,0,3pincel = CreateBrush()BrushColor pincel, 255,0,0PaintMesh ob, pincelWhile Not KeyHit(1)RotateMesh ob, 0, 1, 0

Page 9: jogos 3D

RenderWorld Text 10, 10, "ANGULO Y: " + EntityYaw#(ob) Flip WendEnd

Veja que não precisamos usar contador para criar esse efeito.RotateMesh ob, 0, 1, 0

A segunda coisa a considerar é o fato que a rotação dos vértices não alterou a orientação do objeto que continua com a face voltada para o mesmo lugar. A linha abaixo demonstra isso, pois o comando EntityYaw#() retorna o ângulo y atual de um objeto qualquer.Text 10, 10, "ANGULO Y: " + EntityYaw#(ob)

Veja que essa linha de informação foi colocada após o comando de Render e antes do Comando Flip. Isso significa que só colocamos imagens 2d no buffer depois de todo o cenário 3d ser renderizado, senão esses gráficos vão ser subscritos pela composição gráfica do cenário.RenderWorld Text 10, 10, "ANGULO Y: " + EntityYaw#(ob) Flip

O comando TurnEntity rotaciona o objeto inteiro, portanto tudo que está nele. Assim também sua orientação será alterada. Ele também possui sistema de rotação relativa, isso é, o numero que especificarmos que será o ângulo de rotação será somado ao ângulo atual do objeto, também desprezando o uso de contador para criar rotação continua.Nosso próximo exemplo é semelhante ao anterior, mas agora perceba que o ângulo Yaw (y) sofrerá alterações.

Exemplo 21Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() ob = CreateCube()PositionEntity ob,0,0,3pincel = CreateBrush()BrushColor pincel, 255,0,0PaintMesh ob, pincelWhile Not KeyHit(1)TurnEntity ob, 0, 1, 0RenderWorld Text 10, 10, "ANGULO Y: " + EntityYaw#(ob) Flip WendEnd

O comando RotateEntity gera efeitos de rotação absolutos, relativos ao cenário, isso é, relativos à orientação da engine. Ele é muito usado para colocar um objeto em um ângulo específico em relação à orientação do cenárioExemplo 22Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() ob = CreateCube()PositionEntity ob,0,0,3While Not KeyHit(1)RotateEntity ob, 0, 45, 0RenderWorld Text 10, 10, "ANGULO Y: " + EntityYaw#(ob) Flip WendEnd

Posicionamento Para posicionar um objeto usamos o comando PositionEntity. Com ele colocamos o objeto em uma posição absoluta do cenário. É o comando perfeito para fazer ajuste de personagens e objetos ao carregar um jogo ou uma fase.

Exemplo 23Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() ob = CreateCube()PositionEntity ob,0,0,3While Not KeyHit(1)x = Rnd(-2,2)y = Rnd(-2,2)z = Rnd(2, 20)If KeyHit(57) Then PositionEntity ob, x, y, zRenderWorldText 10,10, "Pressione SPACE para posicionar em local aleatório" Flip WendEnd

Mover Para movermos objetos 3d podemos fazer uso de dois comandos distintos: TranslateEntity e MoveEntity. Esses comandos têm efeitos diferentes, pois enquanto um deles move um objeto de acordo com a orientação universal, o outro o move de acordo com sua própria orientação. Assim, o primeiro é ideal para mover objetos e o segundo é melhor para controlar personagens.

Page 10: jogos 3D

O comando TranslateEntity move um objeto em um dos seus três eixos relativos ao ambiente 3d. Isso quer dizer que mesmo que ele seja rotacionado, sempre vai se mover para o mesmo lugar enquanto os parâmetros do comando não forem alterados. Esse é o comando padrão para mover objetos inanimados que tem sua trajetória definida por vetores.Veja um exemplo

Exemplo 24Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() ob = CreateCube()PositionEntity ob,0,0,20While Not KeyHit(1) If KeyDown(200) Then TranslateEntity ob, 0, 0, .1If KeyDown(208) Then TranslateEntity ob, 0, 0, -.1If KeyDown(203) Then TranslateEntity ob, -.1, y, 0If KeyDown(205) Then TranslateEntity ob, .1, y, 0If KeyDown(57) Then TurnEntity ob, 0, 1, 0RenderWorldText 10,10, "SPACE -> Rotaciona; SETAS -> movem" Text 10,25, "PERMANEÇA COM O SPACE PRESSIONADO E VERIFIQUE" Text 10,37, "QUE A ROTAÇÃO NÂO MUDA A DIREÇÃO" Flip WendEnd

Na linha abaixo, quando pressionamos a seta para cima, movemos o objeto em um décimo de unidades para o fundo da tela.If KeyDown(200) Then TranslateEntity ob, 0, 0, .1

Já a linha seguinte faz o efeito inverso: se pressionarmos a seta para baixo, o objeto se aproxima da tela.If KeyDown(208) Then TranslateEntity ob, 0, 0, -.1

As próximas linhas controlam os movimentos laterais, dai são implementadas com o uso das setas para as laterais.If KeyDown(203) Then TranslateEntity ob, -.1, y, 0If KeyDown(205) Then TranslateEntity ob, .1, y, 0

Por fim, para vermos como difere do outro comando, usamos a barra de espaços para rotacionar o objeto.If KeyDown(57) Then TurnEntity ob, 0, 1, 0

Com o comando MoveEntity podemos controlar melhor objetos animados, principalmente os personagens principais. Quando um carro faz uma ligeira curva ele deixa de ir para uma direção e passa a se mover para outra, para onde a sua frente aponta. Com o comando do exemplo anterior ficaria muito complicado fazer isso, afinal, quando ele rotaciona o objeto vai para a mesma direção. Mas com o uso de MoveEntity o movimento do objeto depende de sua orientação. Se definirmos que ele se moverá para frente, quando ele rotaciona, sua frente altera de posição e seu movimento obedece essa mudança de sentido.Veja o exemplo anterior agora usando o MoveEntity.

Exemplo 25Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()light=CreateLight() ob = CreateCube()PositionEntity ob,0,0,20While Not KeyHit(1) If KeyDown(200) Then MoveEntity ob, 0, 0, .1If KeyDown(208) Then MoveEntity ob, 0, 0, -.1If KeyDown(203) Then MoveEntity ob, -.1, y, 0If KeyDown(205) Then MoveEntity ob, .1, y, 0If KeyDown(57) Then TurnEntity ob, 0, 1, 0RenderWorldText 10,10, "SPACE -> Rotaciona; SETAS -> movem" Text 10,25, "PERMANEÇA COM O SPACE PRESSIONADO E VERIFIQUE" Text 10,37, "COMO A ROTAÇÃO ALTERA AGORA A DIREÇÃO" Flip WendEnd

MODELOS 3DO Blitz3D carrega três tipos de modelos 3d: modelos “.3ds” nativos do Autodesk 3ds Max, modelo “.x” que são nativos do Directx e modelos “.b3d” os modelos padrões do próprio Blitz3D.Controlamos todos esses modelos usando os mesmos comandos. Para carregar um modelo 3d basta usar o comando LoadMesh. Veja um exemplo simples.

Exemplo 26Graphics3D 800,600

Page 11: jogos 3D

SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,0,-30light=CreateLight() caixa = LoadMesh("midia\supplies.x")RenderWorldFlipWaitKeyEnd

Ao carregarmos um modelo 3d ele é automaticamente posicionado nas coordenadas 0,0,0Como você pode perceber, a textura é adicionada automaticamente ao modelo 3d, mas caso ocorra algum problema você também pode carregar e aplicar uma textura ao se modelo 3d. No exemplo abaixo, aplicamos uma segunda textura no modelo 3d.

Exemplo 27Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,0,-30light=CreateLight() caixa = LoadMesh("midia\supplies.x")tex = LoadTexture("midia\parede.jpg",2)EntityTexture caixa, tex, 0, 1RenderWorldFlipWaitKeyEnd

Veja que carregamos a segundo textura com a flag 2 de canal alpha para dar uma melhor composição de imagem:tex = LoadTexture("midia\parede.jpg",2)O comando LoadMesh aceita um segundo parâmetro que é para criar uma hierarquia. O modelo passado como parâmetro será o modelo pai. Se o modelo pai se move o filho se move também. Se o modelo filho se move o pai ficar parado. Somente os comandos sobre o objeto pai irão refletir na hierarquia. Veja isso no próximo exemplo.

Exemplo 28Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,0,-80light=CreateLight() caixa = LoadMesh("midia\supplies.x")caixa2 = LoadMesh("midia\supplies.x", caixa)MoveEntity caixa2, 30,0,0While Not KeyHit(1)If KeyHit(57) Then ob = Not(ob)If obIf KeyDown(200) Then TranslateEntity caixa, 0, .1, 0

If KeyDown(208) Then TranslateEntity caixa, 0, -.1, 0If KeyDown(203) Then TranslateEntity caixa, -.1, 0, 0If KeyDown(205) Then TranslateEntity caixa, .1, 0, 0ElseIf KeyDown(200) Then TranslateEntity caixa2, 0, .1, 0If KeyDown(208) Then TranslateEntity caixa2, 0, -.1, 0If KeyDown(203) Then TranslateEntity caixa2, -.1, 0, 0If KeyDown(205) Then TranslateEntity caixa2, .1, 0, 0EndIfRenderWorldColor 255,255,255Text 10,10, "SPACE -> MUDA OBJETO SELECIONADO; SETAS-> MOVE"Color 255,0,0If ob Text 10, 25, "Objeto Atual: * PAI *"ElseText 10, 25, "Objeto Atual: * FILHO *"EndIfFlipWendWaitKeyEnd

Clonar modelosSabemos que a velocidade de acesso a dados no disco rígido do computador é bem lenta se comparada à capacidade geral de processamento da máquina. Se tivermos uma situação onde temos que usar várias instâncias de um mesmo objeto podemos clonar um objeto em vez de carregar várias cópias dele com o LoadMesh, pois isso seria um pouco demorado. Para clonar um objeto usamos o comando CopyEntity.A tendência normal de um iniciante em Blitz3D é tentar usar um simples comando de atribuição para fazer uma cópia, como na linha abaixo:Objeto1 = LoadMesh(“midia\caixa.x”)Objeto2 = Objeto1

Esse exemplo está incorreto, pois quanto o Blitz3D carrega um objeto, ou uma imagem, ele cria um ponteiro para o mesmo. Assim, a variável “Objeto1” não possui um modelo 3d, mas só a referência ao endereço de memória do modelo. Dessa forma, quando fazemos a atribuição para a segunda variável estamos na verdade passando o endereço do primeiro objeto para ele e todos os comandos que aplicarmos a esse segundo objeto vão valer para o objeto original, que continuará a ser o único a existir. Assim o que fizemos na

Page 12: jogos 3D

verdade é criar uma nova variável para um mesmo objeto.Veja abaixo como fazer uma clonagem de maneira correta com o uso de CopyEntity.

Exemplo 29Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,0,-80light=CreateLight() caixa = LoadMesh("midia\supplies.x")caixa2 = CopyMesh(caixa)MoveEntity caixa2, 30,0,0RenderWorldFlipWaitKeyEnd

Esse comando também suporta a criação de hierarquia

Ocultando ObjetosExistem jogos com cenários muito grandes e ordenar que o Blitz3D faça o processamento gráfico de todos eles exigiria um demanda muito grande de hardware. Existem duas soluções possíveis para isso, a primeira delas é ir carregando objetos em tempo de execução, o que poderia causar retardamentos, os famosos “Lags”, o outra solução é ocultar os objetos que não estão sendo usados, objetos que estão distantes do foco da câmera. Para usar essa técnica usamos os comandos HideEntity e ShowEntity. O primeiro comando é responsável por esconder um objeto, isso é, dizer para a engine que esse objeto não deve ter sua imagem processada e nem verificar colisões para ele. Já o comando ShowEntity reativa o processamento de um objeto que foi omitido pelo comando HideEntity. O exemplo abaixo faz uso dos dois comandos

Exemplo 30Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,0,-30light=CreateLight() caixa = LoadMesh("midia\supplies.x")SHW = 1While Not KeyHit(1)If KeyHit(57) Then SHW = Not(SHW)RenderWorld If SHW ShowEntity caixaText 30, 25, "OBJETO -> SHOW"Else

HideEntity caixaText 30, 25, "OBJETO -> HIDE"EndIfText 300, 0, "SPACE => HIDE/SHOW"Text 30, 40, "Triangulos Renderizados: " + TrisRendered() FlipWendEnd

Veja que usamos os comando para alternar os modos Hide e Show. Também colocamos o comando TrisRendered(), que retorna o número de triângulos que estão sendo renderizados, para mostrar que em modo Hide não existe esforço de processamento.

Objetos em AlphaO Blitz3D possui o poder de torna um objeto parcial ou totalmente transparente. Esse ajuste na camada alfa de um objeto é muito útil para vários efeitos especiais. Podemos simular materiais translúcidos como líquidos, vidros, plásticos, água, etc.Outra aplicação importante dessa funcionalidade é criar objetos auxiliares para a física e para verificar colisões de forma mais precisa. Criamos os objetos auxiliares e deixarmos eles totalmente transparentes, assim podemos verificar colisões com eles e ninguém os verá. Um exemplo para esse tipo de aplicação é criar limitadores de movimento, no caso de o personagem não poder passar de um ponto do cenário, colocamos um plane totalmente transparente. Outro exemplo de uso é para a física de carros onde colocamos uma esfera em cada ponto onde estão as rodas para verificarmos a altura exata do terreno naquele local e podermos ajustar a inclinação do carro de forma correta.Vamos a um exemplo do uso de canal alfa em objetos 3d.

Page 13: jogos 3D

Exemplo 31Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,0,-20light=CreateLight() caixa = LoadMesh("midia\supplies.x")caixa2 = CopyMesh(caixa)PositionEntity caixa2, 10, 0, 5While Not KeyHit(1)x# = x# + 0.002 If x# > 1 Then x# = 0EntityAlpha caixa, x#RenderWorld Text 10,10, "VALOR ALPHA: " + X# FlipWendEnd

O comando EntityAlpha recebe dois parâmetros: o objeto a ser aplicado o efeito e a intensidade do canal alfa. A intensidade é um valor de 0 até 1. O valor 0 significa totalmente transparente e o valor 1 é totalmente opaco. Os valores intermediários correspondem a transparências intermediárias.

Adicionar CorO comando EntityColor é capaz de colorir um modelo 3d. Esse comando é diferente do comando PaintMesh, que é uma coloração exclusiva. Quando aplicamos o comando EntityColor sobre um objeto que possui uma textura, essa cor é adicionada à textura do objeto, fazendo um “Blend”, enquanto que o comando PaintMesh remove a textura dele.

Exemplo 32Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,0,-30

light=CreateLight() caixa = LoadMesh("midia\supplies.x")caixa2 = CopyMesh(caixa)PositionEntity caixa, 15, 0, 0pincel = CreateBrush()While Not KeyHit(1)If KeyHit(57) r = Rnd(255)g = Rnd(255)b = Rnd(255)BrushColor pincel, r, g, bPaintMesh caixa, pincel EntityColor caixa2, r, g, bEndIfRenderWorld FlipWendEnd

O comando EntityColor recebe 4 parâmetros: o objeto a ser colorizado, a saturação red, a saturação green e a saturação blue (vermelho, verde e azul).EntityColor caixa2, r, g, b

MODELOS ANIMADOSO Blitz3D possui suporte a 4 tipos de modelos animados: modelos .3ds que são nativos do 3ds Max, modelos .X que são nativos Microsoft Directx, modelos .b3d que são proprietários do Blitz3D e modelos .md2 que são nativos da engine Quake2.Os modelos .md2 vão ser tratados em um capítulo a parte, pois como eles possuem uma estrutura diferente, possuem comandos diferentes também. Os demais modelos possuem a mesma estrutura lógica e são controlados pelos mesmos comandos.Em nosso primeiro exemplo de modelos 3d animados, vamos carregar e animar um modelo do formato Directx criado pelo próprio Mark Sibly que vem como exemplo no Blitz3d.

Exemplo 33Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,20,-50light=CreateLight() robo = LoadAnimMesh("midia\robo1.x")Animate roboWhile Not KeyHit(1)UpdateWorldRenderWorld FlipWendEnd

A primeira novidade que temos nesse programa é o comando para carregar o modelo animado.robo = LoadAnimMesh("midia\robo1.x")

Page 14: jogos 3D

Veja que agora o comando é LoadAnimMesh, isso é “carregar um modelo animado”. Além da localização do arquivo, esse comando também pode aceitar uma entidade pai para compor uma hierarquia, como seu segundo argumento.O segundo comando que estamos vendo pela primeira vez aqui é o comando de animação de modelos 3d.Animate robo

Veja que passamos apenas um argumento para o comando Animate: a entidade a ser animada. Mas esse comando é muito mais complexo. Veja abaixo sua assinatura completa:Animate objeto, modo, velocidade, seqüência, transiçãoComo já sabemos, o objeto é um modelo animado que já carregamos.O modo é o tipo de animação que iremos aplicar ao modelo. Esse parâmetro é opcional. Os tipos são:0 – parado1 – animação em loop (o padrão, animação continua sem parar)2 – animação ping-pong (animação continua mas em forma de vai e volta)3 – única (anima só uma vez, do frame 0 até o final e para) O nosso terceiro parâmetro é velocidade. Com esse parâmetro nos definimos com qual velocidade a animação irá rodar. Esse parâmetro também é opcional.O parâmetro seguinte é o seqüência. Esse parâmetro depende de termos previamente carregado uma animação pelo método ExtractAnimSeq. Quando não usamos esse comando toda a animação é carregada em apenas uma seqüência, a seqüência 0, pelo método LoadAnimMesh. Por fim, o parâmetro transição define como se dará a transição do ultimo frame da animação para o primeiro.O ultimo comando que aparece pela primeira vez nesse programa é o de atualização do cenário 3d.UpdateWorld

Esse comando é muito importante, sem ele a animação não acontece e as colisões não podem ser verificadas, pois é ele que atualiza essas funcionalidades.Como dissemos anteriormente, os mesmos comando são validos para os modelos .3ds, .x e .b3d. Veja a seguir o mesmo programa agora carregando um modelo do formato .3ds.

Exemplo 34Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,20,-70light=CreateLight() robo = LoadAnimMesh("midia\robo2.3ds")Animate roboWhile Not KeyHit(1)UpdateWorldRenderWorld FlipWendEnd

Veja agora um exemplo com modelo .b3d. Esse modelo vem junto com o programa CharacterFX, um software para animação de personagens de baixo custo que exporta para os modelos .b3d, .x, .3ds, .md2, .n, .obj e milkshape3d.txt.

Exemplo 35Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,20,-100light=CreateLight() yago = LoadAnimMesh("midia\yago.b3d")RotateEntity yago, 0, 150, 0Animate yagoWhile Not KeyHit(1)UpdateWorldRenderWorld FlipWendEnd

Tipos de AnimaçãoVocê pode configurar o comando Animate para 4 tipos de estados de animação: parado, loop, ping-pong e única. O programa de exemplo abaixo mostra como funciona e como configuramos isso. Use a barra de

Page 15: jogos 3D

espaço para mudar os tipos de estado de animação.

Exemplo 36Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,20,-70light=CreateLight() robo = LoadAnimMesh("midia\robo2.3ds")tipo = 0While Not KeyHit(1)If KeyHit(57) tipo = tipo + 1If tipo = 4 Then tipo = 0Animate robo, tipoEndIfUpdateWorldRenderWorldText 250,10, "TECLE SPACE PARA MUDAR O TIPO DE ANIMAÇÃO"If tipo = 0 Then Text 10,25, "TIPO ATUAL: PARADO"If tipo = 1 Then Text 10,25, "TIPO ATUAL: LOOP"If tipo = 2 Then Text 10,25, "TIPO ATUAL: PING-PONG"If tipo = 3 Then Text 10,25, "TIPO ATUAL: UNICA"FlipWendEnd

Mudando velocidadeComo sabemos, o terceiro parâmetro do comando Animate é a velocidade da animação. Nosso próximo exemplo mostra como aplicar essa configuração.

Exemplo 37Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,20,-70light=CreateLight() robo = LoadAnimMesh("midia\robo2.3ds")velox# = 0.1Animate robo,1, velox#While Not KeyHit(1) If KeyDown(200) velox# = velox# + 0.02 Animate robo,1, velox#EndIfIf KeyDown(208) velox# = velox# - 0.02 Animate robo,1, velox#EndIfUpdateWorldRenderWorldText 150,10, "SETA ACIMA -> aumenta velocidade; SETA ABAIXO -> diminui”Text 10,25, “Velocidade atual: “ + velox#FlipWend

End

Carregar seqüências de animaçõesAlgo que facilita muito na hora de criar um jogo é dividir as animações em unidades lógicas separadas. Assim, podemos até criar uma variável para cada animação, ficando mais obvio o que cada coisa faz, pois afinal “Animate robo, 1, 1, corre” é bem mais fácil de entender e de fazer que que ter que verificar qual é o frame atual e se o frame for o frame final, reiniciar o frame da animação, etc... Como você já percebeu, sem a extração de animação a coisa fica bem mais complicada.Então vamos a um exemplo prático. A primeira coisa a saber é: quais são as animações disponíveis no modelo. O nosso amigo ninja que vamos usar, que é um modelo que vem junto com o Character FX, tem a seguinte especificação:1-14 Walk 15-30 Stealth Walk32-44 Punch and swipe sword45-59 Swipe and spin sword60-68 Overhead twohanded downswipe69-72 Up to block position 73-83 Forward kick84-93 Pick up from floor 94-102 Jump103-111 Jump without height 112-125 High jump to Sword Kill 126-133 Side Kick134-145 Spinning Sword attack 146-158 Backflip159-165 Climb wall166-173 Death 1 - Fall back onto ground174-182 Death 2 - Fall forward onto ground184-205 Idle 1 - Breathe heavily206-250 Idle 2251-300 Idle 3O nosso primeiro programa vai fazer uma demonstração de todos os elementos da animação.

Exemplo 38Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,5,-10light=CreateLight() ninja = LoadAnimMesh("midia\ninja.b3d")RotateEntity ninja, 0, 150, 0Animate ninja,1, .1While Not KeyHit(1)UpdateWorldRenderWorld Text 10,10, "FRAME: " + AnimTime(ninja)

Page 16: jogos 3D

FlipWendEnd

Veja que usamos o comando AnimTime() que retorna a animação atual de um modelo animado para verificar em qual parte da animação estamos.Para nosso exemplo prático não vamos usar todas as seqüências apresentadas, mas só àquelas que nos interessam:1-14 Walk (andar)60-68 Overhead twohanded downswipe (golpear)126-133 Side Kick (chutar)146-158 Backflip (saltar)Vamos ao código!

Exemplo 39Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,5,-15light=CreateLight() ninja = LoadAnimMesh("midia\ninja.b3d")andar = ExtractAnimSeq( ninja,1,14 ) golpear = ExtractAnimSeq( ninja,60,68 ) chutar = ExtractAnimSeq( ninja,126,133 ) saltar = ExtractAnimSeq( ninja,146,158 ) RotateEntity ninja, 0, 200, 0Animate ninja, 1, .2, andarWhile Not KeyHit(1)If KeyHit(57) Then Animate ninja, 1, .2, saltarIf KeyHit(44) Then Animate ninja, 1, .2, golpearIf KeyHit(45) Then Animate ninja, 1, .2, chutarUpdateWorldRenderWorld Text 10,10, "SPACE-> Saltar; Z -> Golpear; X -> Chutar"FlipWendEnd

A primeira coisa que fizemos foi extrair as animações e colocar em variáveis com nomes que indiquem o seu conteúdo:andar = ExtractAnimSeq( ninja,1,14 ) golpear = ExtractAnimSeq( ninja,60,68 ) chutar = ExtractAnimSeq( ninja,126,133 ) saltar = ExtractAnimSeq( ninja,146,158 )

Como o nosso modelo tem um documento de especificação isso foi fácil. Caso o modelo que você use não o possua, você terá que rodar a animação toda em uma velocidade bem baixa para verificar quais frames compõe qual animação.Vamos iniciar a aplicação com o modelo andando mesmo:Animate ninja, 1, .2, andar

O que fizemos a seguir foi indexar cada animação ao apertar de uma tecla. Assim configuramos o comportamento do modelo animado com nossas teclas configuradas para o controle.If KeyHit(57) Then Animate ninja, 1, .2, saltarIf KeyHit(44) Then Animate ninja, 1, .2, golpearIf KeyHit(45) Then Animate ninja, 1, .2, chutar

Como você pode perceber, está faltando algo ai, afinal, quando apertamos uma tecla, como a chutar, por exemplo, ele começa a chutar sem parar. O ideal é que ele dê apenas um chute e volte para seu estado inicial. Assim, cada vez que apertarmos uma tecla de comportamento ele deve executar a ação apenas enquanto a tecla estiver pressionada.Não fizemos isso por enquanto para não complicar muito. Vamos implementar passo o passo. O nosso próximo passo é fazer isso, fazer com que o personagem volte a seu estado de animação primitivo.Para que o personagem volte a sua animação primitiva teremos que fazer a verificação se a animação chegou ao seu final. Se ela chegou ao fim, então ordenamos que rode a animação principal.Para fazer isso vamos usar o comando AnimTime(), que nos informa qual é o frame atual de uma animação.Veja o exemplo prático

Exemplo 40Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,5,-15light=CreateLight() ninja = LoadAnimMesh("midia\ninja.b3d")RotateEntity ninja, 0, 200, 0parado = ExtractAnimSeq( ninja,5,5) golpear = ExtractAnimSeq( ninja,60,68 ) chutar = ExtractAnimSeq( ninja,126,133 ) saltar = ExtractAnimSeq( ninja,146,158 ) Animate ninja, 1, .2, paradoanima = paradoWhile Not KeyHit(1)If KeyHit(57) Animate ninja, 1, .2, saltaranima = saltarEndIfIf KeyHit(44) Animate ninja, 1, .2, golpearanima = golpearEndIfIf KeyHit(45) Animate ninja, 1, .2, chutaranima = chutarEndIf

Page 17: jogos 3D

frame = AnimTime(ninja)If anima = golpear And (frame > 7) Animate ninja, 1, .2, paradoanima = paradoEndIfIf anima = chutar And (frame > 6) Animate ninja, 1, .2, paradoanima = paradoEndIfIf anima = saltar And (frame > 11) Animate ninja, 1, .2, paradoanima = paradoEndIfUpdateWorldRenderWorld Text 10,10, "SPACE-> Saltar; Z -> Golpear; X -> Chutar"FlipWendEnd

Dessa vez iniciamos com o personagem parado, para isso criamos uma animação de parado com apenas um frame.parado = ExtractAnimSeq( ninja,5,5)

Também criamos uma variável para informa qual é a animação em curso. Isso vai facilitar na hora de verificar o frame atual.anima = parado

Agora, quando pressionamos uma tecla de animação, além de acionar a animação correspondente, também configuramos a variável de informação do tipo de animação.If KeyHit(57) Animate ninja, 1, .2, saltaranima = saltarEndIf

Por fim a verificação dos frames.frame = AnimTime(ninja)If anima = golpear And (frame > 7) Animate ninja, 1, .2, paradoanima = paradoEndIf

Veja que colocamos o frame atual em uma variável Frame o passo seguinte é verificar qual é a animação atual “If anima = golpear” e como a animação golpear tem 8 frames, verificamos se ela chegou a seu último frame, o frame 8 isso é, “frame > 7”.Se essas duas condições forem verdadeiras, então colocamos a animação em seu estado inicial “Animate ninja, 1, .2, parado” e configuramos a variável de informação “anima=parado”.Veja que fizemos um algoritmo desse para cada tipo de animação.Se você ficou bem atento já deve ter percebido que ainda falta uma coisa. Quando apertamos uma tecla de animação mais de uma vez o modelo para a animação em curso e reinicia a partir do frame inicial. Isso é péssimo, pois, por exemplo, no salto, ele

estava no alto e de ponta cabeço e de repente aparece de pé saltando de novo.Para resolver isso é muito simples. Como já temos uma variável informando qual é a animação atual, só vamos permitir uma nova animação quando a animação atual for a inicial, isso é, estiver parado.

Exemplo 41Graphics3D 800,600 SetBuffer BackBuffer() camera=CreateCamera()PositionEntity camera, 0,5,-15light=CreateLight() ninja = LoadAnimMesh("midia\ninja.b3d")RotateEntity ninja, 0, 200, 0parado = ExtractAnimSeq( ninja,5,5) golpear = ExtractAnimSeq( ninja,60,68 ) chutar = ExtractAnimSeq( ninja,126,133 ) saltar = ExtractAnimSeq( ninja,146,158 ) Animate ninja, 1, .2, paradoanima = paradoWhile Not KeyHit(1)If KeyHit(57) And Anima = paradoAnimate ninja, 1, .2, saltaranima = saltarEndIfIf KeyHit(44) And Anima = paradoAnimate ninja, 1, .2, golpearanima = golpearEndIfIf KeyHit(45) And Anima = paradoAnimate ninja, 1, .2, chutaranima = chutarEndIfframe = AnimTime(ninja)If anima = golpear And (frame > 7) Animate ninja, 1, .2, paradoanima = paradoEndIfIf anima = chutar And (frame > 6) Animate ninja, 1, .2, paradoanima = paradoEndIfIf anima = saltar And (frame > 11) Animate ninja, 1, .2, paradoanima = paradoEndIfUpdateWorldRenderWorld Text 10,10, "SPACE-> Saltar; Z -> Golpear; X -> Chutar"FlipWendEnd

Veja onde resolvemos o problema:If KeyHit(57) And Anima = parado

Adicionamos mais uma condição “And Anima = parado” para acionar uma animação. Agora a animação atual deverá ser o estado “parado” para que seja disparada uma nova animação. Em um jogo você deverá verificar em que estados seu personagem poderá iniciar uma nova animação. Os mais comuns

Page 18: jogos 3D

são a partir dos estados: parado, andando e correndo. Estados como atacando, atirando, saltando jamais devem ser estados iniciais a parir dos quais se inicia uma nova animação.Por hora só podemos avançar até aqui em modelos animados. Depois que fizermos um estudo sobre câmeras e cenários passaremos a exemplos práticos com modelos animados, onde iremos entrar em animações, algoritmos e eventos. Daí nós veremos como colocar isso em prática em um jogo.

MODELOS MD2Os modelos .md2 tem uma estrutura diferente dos demais modelos 3d, por isso foram criados comandos próprios para ele. O Blitz3D oferece 5 comandos para controlar os modelos .md2. Veja-os abaixo:LoadMD2 (carrega um modelo 3d do tipo .md2)AnimateMD2 (anima um modelo .md2)MD2AnimTime (retorna o frame atual da animação)MD2AnimLength (retorna o tamanho da animação)MD2Animating (retorna 1 se o modelo está animado e 0 se não)

Os comandos MD2AnimTime, MD2AnimLength e MD2Animating tem a assinatura muito simples, pois só aceitam um parâmetro: o modelo md2 carregado.O comando LoadMd2 além de aceitar o arquivo que possui o modelo 3d do tipo md2 também aceita como parâmetro um modelo para compor uma hierarquia.O comando AnimateMD2 é mais complexo. Ele aceita 6 parâmetros. Veja sua assinatura:AnimateMD2 modelo, modo, velocidade, primeiro frame, ultimo frame, transição.

Apenas o primeiro parâmetro, o modelo a ser animado, é obrigatório, sendo os demais opcionais. Veja que ele possui uma diferença em relação ao comando para animar os outros tipos de modelos. Ele não aceita uma seqüência de animação, mas sim frame inicial e frame final.Uma peculiariedade de um modelo .md2 é que sua texturização deve ser feita manualmente no Blitz3D.Vamos dar um exemplo de manipulação de modelos md2 que contenha todos os comando. Vamos usar o modelo Sydney que

é livre e pode ser encontrado junto com a Irrlicht Engine.

Exemplo 42 Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera, 0,10,-50light=CreateLight()RotateEntity light,90,0,0sid=LoadMD2( "midia/sydney.md2" )tex=LoadTexture( "midia/sydney.bmp" )EntityTexture sid, texRotateEntity sid,0,150,0AnimateMD2 sid,1,0.1,0,198While Not KeyDown( 1 )UpdateWorldRenderWorldText 10,10, "TAMANHO ANIMAÇÃO: " + MD2AnimLength(sid)Text 10,25, "FRAME ATUAL: " + MD2AnimTime (sid)Text 10,40, "ESTADO DA ANIM: " + MD2Animating(sid)FlipWendEnd

8. JOGOS 3D – Parte IIVamos iniciar a segunda parte de programação de jogos 3d. O mundo 3d é realmente repleto de entidades e propriedades e vamos dar um passo a mais adentro desse universo maravilhoso. Nesta seção vamos estudar os demais elementos que compõe um jogo 3d: cenário, câmeras, iluminação, sprites, física e também vamos dar exemplos práticos de como implementar funcionalidades essenciais de um jogo.

ColisõesA detecção de colisões é a alma de um jogo. Sem essa funcionalidade física tudo se daria em um mundo neutro. É por meio dela que pegamos itens, atiramos, interagimos com o cenário, etc.Vamos primeiro estudar o sistema de colisões automáticas do Blitz3D. Esse sistema de física disponível na engine disponibiliza 3 tipos de verificações de colisões:1. Esfera com esfera2. Esfera com polígono3. Esfera com caixaAlém de verificar colisões, o Blitz3D também produz resultados físicos automáticos dessas colisões. Podemos escolher qual será o resultado desse evento.1. O objeto para

Page 19: jogos 3D

2. O objeto desliza3. O objeto desliza em relação ao ângulo.Para criar um perfeito sistema de colisões é necessário um pouco de cuidado para não criar comandos desnecessários e nem sobrecarregar o sistema com excesso de verificações. O ideal é verificar colisões por meio de grupos de objetos, isso é, criamos um grupo de objetos que possuem às mesmas características física e verificamos as colisões em grupo.Veja as etapas necessárias para isso:1. passo: criar grupos de objetos (parede, árvore, etc)2. passo: dizer a qual grupo cada objeto pertence.3. passo: habilitar as colisões.4. passo: verificar as colisões.5. passo: ativar o updateworld.

1º PASSO: Criar Grupos de ObjetosImagine que no seu jogo você tenha umas 50 entidades do tipo paredes e muros. Paredes e muros têm o mesmo comportamento físico, quando um objeto os atinge, ele não atravessa, mas tende a deslizar.Como você possui 50 objetos distintos e tem que fazer verificação de colisões para os 50, então teríamos 50 linhas para configurar as colisões? A resposta é: só se você quiser!Você pode criar um grupo de objetos e dizer que os objetos pertencem aquele grupo específico. Ai em vez de ter 50 linhas para configurar as colisões e mais cinqüenta para verificar, você só vai ter 2 linhas.Isso que estamos falando não é um comando de colisões, mas uma técnica para organizar o código. Vamos fazer isso por meio de uma constante. Vamos criar uma constante que defina um grupo de objetosConst OB_PAREDE = 1Veja que o que fizemos foi criar uma constante e atribuir a ela um número. É que os tipos de objetos para colisões no Blitz3D não numerados de 0 a 999.

2º PASSO: Determinar a qual tipo um objeto pertenceTodo o objeto que precisa ter verificação de colisões precisa pertencer a um tipo, pois a verificação de colisões é feita por tipos (números de 0-999). No nosso caso, estamos criando grupos de tipos e definimos anteriormente que o grupo OB_PAREDE é do tipo 1. O que temos que fazer agora é

dizer que nossa parede pertence a esse grupo de objetos, isso é, ela é do tipo 1.EntityType parede1, OB_PAREDEEntityType parede2, OB_PAREDE...EntityType HEROY, 999

3º PASSO: Habilitar colisõesDepois de configuras todos dos tipos de objetos, o próximo passo é dizer quais são as colisões que o Blitz3D deverá verificar. Como nós criamos grupos isso ficará mais fácil, pois devemos dizer apenas que o personagem principal vai colidir com aquele grupo. Assim usamos esse comando apenas uma vez para cada grupo de objetos.Collisions 999, OB_PAREDE, 3, 1Esse comando já é um tanto quanto mais complexo e merece uma explicação mais detalhada. O primeiro parâmetro é o tipo do objeto principal, o que deverá colidir com os outros objetos. Normalmente é o personagem principal do jogo ou outro objeto que possua comportamento ativo.Collisions TIPO_DO_OBJETO_PRINCIPAL, OB_PAREDE, 3, 1O segundo parâmetro se refere ao objeto (ou aos objetos secundários) que recebem a ação. No nosso caso o cenário, as paredes.Collisions 999, OB_PAREDE, 3, 1O terceiro parâmetro se refere ao tipo de verificação de colisão. Lembre-se da tabelinha:1 – Esfera com esfera2 – Esfera com polígono3 – Esfera com caixaassim o comando:Collisions 999, OB_PAREDE, 3, 1Está criando uma verificação de colisão do tipo “Esfera com Caixa”, pois o tipo é “3”. As caixas serão os muros e paredes e a esfera será o Heroy. Veja que sempre o primeiro objeto, o principal é uma esfera nesse sistema.

4º PASSO: Verificar ColisõesEsse quarto passo nem sempre é necessário, pois a maior parte das colisões são apenas para criar efeitos de física, como impedir que um personagem atravesse uma parede, ou que quando saltemos, ao cair não atravessemos o chão, etc.

Page 20: jogos 3D

Mas em casos como verificação de colisões com tiros, com itens, etc, devemos criar um comando especial quando houver uma colisão. Se nos chocamos com uma bala perdida, perdemos vida, se tocarmos um item, ganhamos novos status, etc.Para isso, o Blitz3D disponibiliza o comandoEntityCollided, que verifica se um objeto colidiu com outro.If EntityCollided(Heroy, OB_PAREDE) Then Print “PARE”Perceba que esse comando não é “tipo, tipo”. Ele recebe os parâmetros “objeto, tipo”, isso é verifica se uma Entidade (Heroy) está colidindo com um grupo de objetos. Portanto, não seria possível fazer uma verificação do igual a EntityCollided(999,1), mas poderia ser EntityCollided(Heroy,1) pois sua assinatura é:EntityCollided(Objeto3d, tipo)Se precisarmos descobrir qual é o objeto do grupo que colidiu, esse comando retorna a referência desse objeto, portanto só precisamos fazer uma atribuição a uma variável.Objeto = EntityCollided(Objeto3d, tipo)Vamos a parte prática agora!

Exemplo 1Graphics3D 800,600SetBuffer BackBuffer()Const OB_CONE=1Const OB_BOLA=2camera=CreateCamera()PositionEntity camera,0,0,-5light = CreateLight()cone=CreateCone()PositionEntity cone,-5,0,5EntityColor cone,0,200,0EntityType cone,OB_CONEbola=CreateSphere(12)PositionEntity bola,5,0,5EntityColor bola,200,0,0EntityType bola, OB_BOLACollisions OB_BOLA,OB_CONE,3,1While Not KeyHit(1)MoveEntity bola,-0.02,0,0UpdateWorldRenderWorldIf EntityCollided(bola,OB_CONE) Then Text 380,50,"COLIDE"FlipWendEnd

A primeira coisa que fizemos foi criar os grupos de objetos. Embora nesse exemplo tenhamos apenas um objeto para cada tipo, fizemos por efeito didático.Const OB_CONE=1

Const OB_BOLA=2

Veja que criamos um grupo para o objeto do tipo cone e um para a bola. Eles serão de tipos diferentes pelo fato de possuirem comportamentos diferentes.O passo seguinte é dizer a qual grupo cada objeto pertence.EntityType cone,OB_CONE

Logo após o objeto ser criado, definimos a que grupo de entidades físicas de colisões ele pertence. Veja que o objeto cone pertence ao grupo OB_CONE.EntityType bola, OB_BOLA

O objeto bola pertence ao grupo OB_BOLA.Agora que já criamos os objetos e configuramos os seus grupos, isso é, declaramos a que grupo de objetos físicos eles pertencem, o passo seguinte é Ligar as colisões.Collisions OB_BOLA,OB_CONE,3,1

Veja que estamos ativando colisões entre os tipos OB_BOLA e OB_CONE, isso é, estamos dizendo ao Blitz3D “quero que você crie um sistema de colisões entre os objetos do tipo OB_BOLAe OB_CONE”. Veja que estamos dizendo ao Blitz para criar esse sistema de colisões como “Esfera-Caixa”, afinal ativamos a flag “3” para tipo de colisões e informamos também que desejamos que os objetos fiquem parados quando colidirem, pois acionamos a flag “1” para o comportamento físico.Veja que esse comando fica fora do loop, pois ele só precisa ser informado uma única vez.Por fim, quando os objetos colidirem iremos exibir uma mensagem de texto:If EntityCollided(bola,OB_CONE) Then Text 380,50,"COLIDE"

Repare que o primeiro parâmetro é um modelo 3d e o segundo é um tipo de objeto. Isso é, verificamos se o objeto bola está colidindo com algum objeto do tipo cone.Como você pode ter percebido, a colisão não é muito precisa, afinal temos um cone como objeto e verificamos colisões com cubo. Isso gera uma diferença e acaba detectando a colisão mesmo antes de ela ocorre. Para resolver esse problema, vamos usar uma colisão do tipo 2, isso é, Esfera-Polígono.

Exemplo 2Graphics3D 800,600SetBuffer BackBuffer()Const OB_CONE=1Const OB_BOLA=2

Page 21: jogos 3D

camera=CreateCamera()PositionEntity camera,0,0,-5light = CreateLight()cone=CreateCone()PositionEntity cone,-5,0,5EntityColor cone,0,200,0EntityType cone,OB_CONEbola=CreateSphere(12)PositionEntity bola,5,0,5EntityColor bola,200,0,0EntityType bola, OB_BOLACollisions OB_BOLA,OB_CONE,2,1While Not KeyHit(1)MoveEntity bola,-0.02,0,0UpdateWorldRenderWorldIf EntityCollided(bola,OB_CONE) Then Text 380,50,"COLIDE"FlipWendEnd

Veja que precisamos mudar apenas um dado em uma linha:Collisions OB_BOLA,OB_CONE,2,1Trocamos o parâmetro 3 (esfera-caixa) para a flag 2(esfera-polígono), e agora temos um sistema de colisões mais preciso.Embora a colisão esteja precisa, a nossa bola simplesmente para ao se chocar com o cone. Isso não ocorre num mundo real. Vamos alterar o parâmetro de resultado físico da colisão para “deslizar”, a flag 2.

Exemplo 3Graphics3D 800,600SetBuffer BackBuffer()Const OB_CONE=1Const OB_BOLA=2 camera=CreateCamera()PositionEntity camera,0,0,-5light = CreateLight()cone=CreateCone()PositionEntity cone,-5,0,5EntityColor cone,0,200,0EntityType cone,OB_CONEbola=CreateSphere(12)PositionEntity bola,5,0,5EntityColor bola,200,0,0EntityType bola, OB_BOLACollisions OB_BOLA,OB_CONE,2,2While Not KeyHit(1)MoveEntity bola,-0.02,0,0UpdateWorldRenderWorldIf EntityCollided(bola,OB_CONE) Then Text 380,50,"COLIDE"FlipWendEnd

Novamente alteramos apenas uma flag em uma linha do códigoCollisions OB_BOLA,OB_CONE,2,2

Manipulando Entidade de ColisãoQuando ligamos as colisões para um tipo de objeto, o Blitz3D verifica qual é o tipo de colisão que aquele objeto possui e se for um objeto do tipo Esfera ou Caixa o Blitz cria uma esfera ou uma caixa de colisão para ele.Mas infelizmente nem sempre ele faz isso com o tamanho correto, pois por padrão ele cria uma caixa de 2x2x2 e uma esfera de raio 1. E, as vezes, isso é pequeno demais ou grande demais para o objeto que estamos trabalhando.Podemos configurar o tamanho da entidade de colisão por meio dos comandos EntityRadius (para colisões por esferas) e EntityBox (para colisões por caixas).Para manipular o comando EntityBox devemos passar os parâmetros do objeto a ter sua caixa de colisões alterada, as posições x, y e z novas para a caixa e as 3 novas dimensões, largura, altura e profundidade. O comando EntityRadius necessita apenas do parâmetro co objeto a ser configurado e o novo raio da esfera.No exemplo abaixo diminuímos o raio original da esfera. Confira o resultado.

Exemplo 4Graphics3D 800,600SetBuffer BackBuffer()Const OB_CONE=1Const OB_BOLA=2 camera=CreateCamera()PositionEntity camera,0,0,-5light = CreateLight()cone=CreateCone()PositionEntity cone,-5,0,5EntityColor cone,0,200,0EntityType cone,OB_CONEbola=CreateSphere(12)PositionEntity bola,5,0,5EntityColor bola,200,0,0EntityType bola, OB_BOLAEntityRadius bola, .1Collisions OB_BOLA,OB_CONE,3,1While Not KeyHit(1)MoveEntity bola,-0.02,0,0UpdateWorldRenderWorldIf EntityCollided(bola,OB_CONE) Then Text 380,50,"COLIDE"FlipWendEnd

Cenários Existem duas técnicas predominantes na criação de cenários: o uso de modelos 3d e o uso de terrenos. O mais comum é usar

Page 22: jogos 3D

apenas modelos 3d ou então a combinação das duas técnicas.Os modelos 3d são modelos comuns, só que nesse caso são maiores que simples objetos e normalmente não possuem animações. Podem ser criados em programas próprios de modelagem como 3ds Max, Maya, Anim8tor ou Blender, ou então em programas próprios para a criação de cenários como o Cshop.Os terrenos são na verdade uma imagem constituída por tons de cinza onde cada saturação de cor representa uma altitude do relevo. A menor saturação de cor (0,0,0), que é o preto absoluto, representa o ponto mais baixo do terreno e a saturação máxima (255,255,255), branco absoluto, representa o ponto mais alto do cenário. As saturações intermediárias representam alturas intermediárias. Dessa forma, a imagem vai criar um mapa físico de relevo que poderá ser texturizado para criar um alto nível de realismo.Os terrenos podem ser criados em qualquer programa de edição de imagem que tenha suporte a ferramentas de pincel com controle de saturação (canal alpha). Existem vários programas 3d que também criam terrenos a partir de modelos 3d. Esses programas são a melhor maneira de lidar com essa técnica, pois temos uma visualização de como ficará o resultado.

Cenários com modelos 3dSe formos criar todo o cenário a partir de modelos 3d não seria uma boa prática criar tudo em um único modelo. O Blitz3D possui um sistema avançado de renderização que só processa os modelos 3D que estão na frente da câmera e dentro da área de visão estabelecida para essa. Se criarmos todo o cenário em um único objeto 3d ele inteiro vai ser processado o tempo todo, pois sempre haverá uma parte dele na frente da câmera. Do contrário, se o dividirmos em partes, só será processada a parte do cenário que estiver em nosso ângulo de visão.Você pode definir a melhor estratégia para trabalhar com essa característica da engine. As melhores opções são: carregar cada objeto individualmente ou dividir o cenário em setores.No primeiro exemplo que vamos apresentar abaixo estamos usando essa técnica, por isso vamos separar o cenário em parte: base,

objetos, casas, etc e carrear tudo individualmente, embora isso seja um pouco trabalhoso, garante uma melhor performance em aplicações grandes.Veja como ficou nosso cenário base:

Exemplo 5Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,0,20,-120light=CreateLight()RotateEntity light,90,0,0cenario=LoadMesh( "midia/cenariobase.3ds" )While Not KeyDown( 1 ) RenderWorldFlipWendEnd

No exemplo a seguir, usamos o mesmo modelo 3d, mas como sabemos que ele possui exatamente 200x150, usamos essa medida para criar um cenário maior por meio de cópias do primeiro. É só copiar o modelo e colocar numa posição de deslocamento igual ao comprimento e/ou largura dele. Perceba que ele se encaixa com tal precisão que não sabemos onde está a emenda.Também estamos usando as teclas de setas para move no cenário. Confira o resultado.

Exemplo 6Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,0,20,-120light=CreateLight()RotateEntity light,90,0,0cenario=LoadMesh( "midia/cenariobase.3ds" )cenario2 = CopyMesh(cenario)cenario3 = CopyMesh(cenario)cenario4 = CopyMesh(cenario)cenario5 = CopyMesh(cenario)cenario6 = CopyMesh(cenario)PositionEntity cenario2, -150,0,0PositionEntity cenario3, 150,0,0PositionEntity cenario4, -150,0,200PositionEntity cenario5, 0,0,200PositionEntity cenario6, 150,0,200While Not KeyDown( 1 )

Page 23: jogos 3D

If KeyDown( 205 ) Then TurnEntity camera,0,-1, 0If KeyDown( 203 ) Then TurnEntity camera,0, 1, 0 If KeyDown( 208 ) Then MoveEntity camera,0, 0,-1If KeyDown( 200 ) Then MoveEntity camera,0, 0, 1RenderWorldText 10,10, "USE AS SETAS PARA ANDAR"FlipWendEnd

Veja que colocamos os novos elementos do cenário em posições múltiplas de x=150 ou z=200, pois assim eles se encaixam perfeitamente, pois essas são as dimensões do modelo 3d.PositionEntity cenario2, -150,0,0PositionEntity cenario3, 150,0,0PositionEntity cenario4, -150,0,200PositionEntity cenario5, 0,0,200PositionEntity cenario6, 150,0,200

Usamos as teclas seta para a direita e seta para a esquerda para rotacionar a câmera no eixo y. Como você se lembra, o eixo y aponta para cima, isso cria um efeito de rotação como se estivéssemos virando nosso corpo para a direita ou para a esquerda.If KeyDown( 205 ) Then TurnEntity camera,0,-1, 0If KeyDown( 203 ) Then TurnEntity camera,0, 1, 0

Usamos as setas para cima e para baixo para andarmos para frente e para traz. Para isso usamos o comando MoveEntity que, como você recorda, ele faz um objeto se deslocar em eixos relativos a sua face. Frente e atrás em 3d é o eixo Z. Portanto aumentar a posição no eixo z é andar para frente e diminuir é andar para traz. Como o comando MoveEntity é baseado na face do objeto, quando rotacionamos a câmera, o movimento também passará a acompanhar a frente da câmera. Assim, sempre andará para frente da câmera ou para traz da mesma.If KeyDown( 208 ) Then MoveEntity camera,0, 0,-1If KeyDown( 200 ) Then MoveEntity camera,0, 0, 1

Adicionando algumas arvoresVamos agora adicionar alguns elementos paisagísticos ao cenário para dar um efeito maior de realismo. Existem vários programas que tornam a tarefa de cria modelos de árvores muito mais simples. O próprio 3ds Max tem uma funcionalidade incorporada, mas aconselho a usar os elementos

produzidos por ele como sprites, pois elas são muito complexas, de altíssima qualidade gráfica e apenas alguns modelos 3d dessas arvores já vão comprometer o desempenho.Uma solução mais barata e leve pode ser programas como o TreeMagik que é bem barato, criar infinidades de árvores e arbustos diferentes e produz LowPoly, isso é, com poucos polígonos.As árvores que estamos usando aqui foram criadas no TreeMagik. Veja o resultado.

Exemplo 7Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,0,20,-120light=CreateLight()RotateEntity light,90,0,0cenario=LoadMesh( "midia/cenariobase.3ds" )cenario2 = CopyMesh(cenario)cenario3 = CopyMesh(cenario)cenario4 = CopyMesh(cenario)cenario5 = CopyMesh(cenario)cenario6 = CopyMesh(cenario)PositionEntity cenario2, -150,0,0PositionEntity cenario3, 150,0,0PositionEntity cenario4, -150,0,200PositionEntity cenario5, 0,0,200PositionEntity cenario6, 150,0,200arvore1 = LoadMesh("midia/arv1.b3d")arvore2 = LoadMesh("midia/arv2.b3d")ScaleMesh arvore1, .05, .05, .05ScaleMesh arvore2, .05, .05, .05Dim arvoreA(10)Dim arvoreB(10)arvoreA(1) = arvore1arvoreB(1) = arvore2For x = 2 To 8 arvoreA(x) = CopyMesh(arvore1)arvoreB(x) = CopyMesh(arvore2)NextPositionEntity arvoreA(1), -13, 0, -50PositionEntity arvoreA(2), 17, 0, -50PositionEntity arvoreA(3), -13, 0, 50PositionEntity arvoreA(4), 17, 0, 50PositionEntity arvoreA(5), -13, 0, 150PositionEntity arvoreA(6), 17, 0, 150PositionEntity arvoreA(7), -13, 0, 250PositionEntity arvoreA(8), 17, 0, 250PositionEntity arvoreB(1), -50, 0, 15PositionEntity arvoreB(2), -50, 0, 185

Page 24: jogos 3D

PositionEntity arvoreB(3),-100, 0, 15PositionEntity arvoreB(4),-100, 0, 185PositionEntity arvoreB(5), 50, 0, 15PositionEntity arvoreB(6), 50, 0, 185PositionEntity arvoreB(7), 100, 0, 15PositionEntity arvoreB(8), 100, 0, 185While Not KeyDown( 1 ) If KeyDown( 205 ) Then TurnEntity camera,0,-1,0If KeyDown( 203 ) Then TurnEntity camera,0,1,0 If KeyDown( 208 ) Then MoveEntity camera,0,0,-1If KeyDown( 200 ) Then MoveEntity camera,0,0,1RenderWorldText 10,10, "USE AS SETAS PARA ANDAR"Flip 0WendEnd

Agora vamos adicionar elementos mais humanos, um carro e duas casas. Veja abaixo como ficou nosso cenário. Nada mal não é mesmo?

Exemplo 8Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,0,20,-120light=CreateLight()RotateEntity light,90,0,0cenario=LoadMesh( "midia/cenariobase.3ds" )cenario2 = CopyMesh(cenario)cenario3 = CopyMesh(cenario)cenario4 = CopyMesh(cenario)cenario5 = CopyMesh(cenario)cenario6 = CopyMesh(cenario)PositionEntity cenario2, -150,0,0PositionEntity cenario3, 150,0,0PositionEntity cenario4, -150,0,200PositionEntity cenario5, 0,0,200PositionEntity cenario6, 150,0,200arvore1 = LoadMesh("midia/arv1.b3d")arvore2 = LoadMesh("midia/arv2.b3d")ScaleMesh arvore1, .05, .05, .05ScaleMesh arvore2, .05, .05, .05Dim arvoreA(10)Dim arvoreB(10)arvoreA(1) = arvore1arvoreB(1) = arvore2For x = 2 To 8

arvoreA(x) = CopyMesh(arvore1)arvoreB(x) = CopyMesh(arvore2)NextPositionEntity arvoreA(1), -13, 0, -50PositionEntity arvoreA(2), 17, 0, -50PositionEntity arvoreA(3), -13, 0, 50PositionEntity arvoreA(4), 17, 0, 50PositionEntity arvoreA(5), -13, 0, 150PositionEntity arvoreA(6), 17, 0, 150PositionEntity arvoreA(7), -13, 0, 250PositionEntity arvoreA(8), 17, 0, 250PositionEntity arvoreB(1), -50, 0, 15PositionEntity arvoreB(2), -50, 0, 185PositionEntity arvoreB(3),-100, 0, 15PositionEntity arvoreB(4),-100, 0, 185PositionEntity arvoreB(5), 50, 0, 15PositionEntity arvoreB(6), 50, 0, 185PositionEntity arvoreB(7), 100, 0, 15PositionEntity arvoreB(8), 100, 0, 185carro = LoadMesh("midia\jeep.3ds")ScaleEntity carro, .7, .7, .7PositionEntity carro, 8, 0, -70casa1 = LoadMesh("midia\casa.3ds")ScaleEntity casa1, 2.5, 2.5, 2.5RotateEntity casa1, 0, 90, 0PositionEntity casa1, -47, 1, -50casa2 = LoadMesh("midia\casa2.3ds")ScaleEntity casa2, 2, 2, 2RotateEntity casa2, 0, -90, 0PositionEntity casa2, 51, 1, -50While Not KeyDown( 1 ) If KeyDown( 205 ) Then TurnEntity camera,0,-1,0If KeyDown( 203 ) Then TurnEntity camera,0,1,0 If KeyDown( 208 ) Then MoveEntity camera,0,0,-1If KeyDown( 200 ) Then MoveEntity camera,0,0,1RenderWorldText 10,10, "USE AS SETAS PARA ANDAR"FlipWendEnd

Cuidado com o uso de arvores. Por mais que elas seja LowPoly, a tendência é de usarmos diversas, assim então, um cenário mais realista deverá ter várias dezenas, sobrecarregando a engine. Se você precisa ter muitas árvore em seu jogo ou aplicação de realidade virtual, faça a opção de usar árvores como sprites, vai ficar muito leve e a qualidade é praticamente a mesma. Dessa forma você poderá usar centenas delas sem risco de sabotar a performance da máquina.Mais à frente nós explicaremos como usar sprites.

Terrenos

Page 25: jogos 3D

O Bitz3D possui uma ótima interface para trabalhar com terrenos. Diversas funcionalidades de controle e comandos para customizar qualidade e performance. Assim, é uma boa escolha trabalhar com esse tipo de cenário, principalmente se você vai ter cenários bem grandes.Vamos estudar algumas das principais técnicas para trabalhar com terrenos e obter o máximo proveito deles.Mas, o que é um terreno? Os terrenos do Blitz3d são modelos especiais de objetos 3d com processamento dinâmico. Terrenos são capazes de criar cenários com qualidade de milhões de polígonos com apenas alguns milhares deles. Isso se dá porque a engine do Blitz3d possui um sistema de composição inteligente desses objetos, aplicando o esforço de composição do mesmo nos locais à frente e mais próximos da câmera.Abaixo temos o bitmap do terreno que corresponde ao seu mapa de relevo. As partes mais claras correspondem aos pontos mais altos e as partes mais escuras são os lugares mais baixos. É a partir de uma imagem como essa que o Blitz3D vai construir o terreno.

Carregando um terreno de uma imagem.Vamos a primeira coisa que devemos fazer: carregar um terreno. Para isso vamos usar o comando LoadTerrain.

Exemplo 9Graphics3D 640,480SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,100,50,-20RotateEntity camera, 30,0,0light=CreateLight()RotateEntity light,90,0,0terrain=LoadTerrain( "midia/heightmap.bmp" )ScaleEntity terrain,1,30,1

tex=LoadTexture( "midia/floor.jpg" )ScaleTexture tex, 10,10EntityTexture terrain,tex,0,1While Not KeyDown( 1 )RenderWorldFlipWendEnd

Como você pode ver, o primeiro passo foi carregar um heightmap para transformar o mesmo em um terreno.terrain=LoadTerrain( "midia/heightmap.bmp" )

Quando carregamos um mapa desses, ele vem com a altura dos relevos muito tênues, por isso devemos efetuar uma operação de escala para ajustar essa propriedade.ScaleEntity terrain,1,30,1

Para o modelo não ficar todo branco, aplicamos uma textura para podermos visualizar a estrutura dele.tex=LoadTexture( "midia/floor.jpg" )ScaleTexture tex, 10,10EntityTexture terrain,tex,0,1

Como você pode ver, a aplicação da textura não gerou uma boa impressão. Isso se dá pelo fato de todo o objeto estar coberto com a mesma imagem de mesma cor e saturação. Assim não temos as disformidades normais de um cenário e nem variações de cores. Mas todo programa criador de terrenos também da suporte a criação de super textura com blend de várias texturas, colormaps e mapas de iluminação.Nesse próximo exemplo estamos criando um terreno e texturizando com uma super textura criada a partir do blend de várias outras. Veja como a sensação de realismo é muito superior.

Exemplo 10Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,100,50,-20RotateEntity camera, 30,0,0light=CreateLight()RotateEntity light,90,0,0terrain=LoadTerrain( "midia/heightmap.bmp" )ScaleEntity terrain,1,30,1tex=LoadTexture( "midia/Maptex.bmp" )ScaleTexture tex, TerrainSize(terrain),TerrainSize(terrain)EntityTexture terrain,tex,0,1While Not KeyDown( 1 )RenderWorldFlipWendEnd

Page 26: jogos 3D

A super textura vai conter a coloração correspondente a cada localidade gráfica do terreno, assim ela deve ser encaixada corretamente no mesmo. Para isso usamos uma técnica especial.ScaleTexture tex, TerrainSize(terrain),TerrainSize(terrain)

Por meio dessa técnica deixamos a textura do mesmo tamanho que o terreno, pois estamos dizendo de devemos escalonar a textura com o mesmo tamanho do terreno.No próximo exemplo adicionamos controles de mobilidade, assim você pode passear pelo cenário.

Exemplo 11Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,120,50,30light=CreateLight()RotateEntity light,90,0,0terrain=LoadTerrain( "midia/heightmap.bmp" )ScaleEntity terrain,1,35,1tex=LoadTexture( "midia/Maptex.bmp" )ScaleTexture tex, TerrainSize(terrain),TerrainSize(terrain)EntityTexture terrain,tex,0,1While Not KeyDown( 1 )If KeyDown( 205 )=True Then TurnEntity camera,0,-2,0If KeyDown( 203 )=True Then TurnEntity camera,0,2,0If KeyDown( 208 )=True Then MoveEntity camera,0,0,-0.2If KeyDown( 200 )=True Then MoveEntity camera,0,0,0.2x#=EntityX(camera)y#=EntityY(camera)z#=EntityZ(camera)terra_y#=TerrainY(terrain,x#,y#,z#)+5PositionEntity camera,x#,terra_y#,z#RenderWorldText 0,0,"Use as teclas de cursor para andar"FlipWendEnd

Estamos usando as setas direcionais para controlar o movimento.If KeyDown( 205 )=True Then TurnEntity camera,0,-2,0If KeyDown( 203 )=True Then TurnEntity camera,0,2,0If KeyDown( 208 )=True Then MoveEntity camera,0,0,-0.2If KeyDown( 200 )=True Then MoveEntity camera,0,0,0.2

O passo seguinte foi pegar as coordenadas atuais da câmera e colocar em variáveis.

x#=EntityX(camera)y#=EntityY(camera)z#=EntityZ(camera)

Depois pegamos a altura do terreno na posição em que a câmera está e adicionamos mais 5 unidades na altura, pois queremos deixa a câmera 5 unidades acima do terreno.terra_y#=TerrainY(terrain,x#,y#,z#)+5

Por fim, foi só deixar a câmera nas mesmas posições x e z e passar ela pra altura que encontramos do acima, isso é, a altura do terreno onde ela está + 5.PositionEntity camera, x#, terra_y#, z#

Isso nos garante que vamos sempre estar andando “em cima” do terreno.

Nível de qualidadeUm comando muito importante do sistema de terrenos do Blitz3d é o TerrainDetail. Com ele podemos configurar a resolução do terreno, isso é, com quantos triângulos esse objeto será criado e podemos ativar e desativar a opção vertex morph. A assinatura desse comando é representada abaixo.TerrainDetail terreno, resolução, vertexmorphA resolução padrão de um terreno é 2000 triângulos, mas seria interessante você deixar para o usuário decidir sobre isso. Assim você pode criar 3 níveis de resolução em uma aplicação. Uma baixa (1000) uma média (2000) e uma com alta resolução (4000), dessa forma o usuário escolhe o que melhor se adaptar ao seu Hardware.A opção vertex morph é boleana, portanto aceita os valores 0 para desativado e 1 para ativado. Esse dispositivo cria uma atenuação das arresta, dando efeito de suavização no terreno. Imprescindível para terrenos de baixa resolução.O exemplo a seguir faz testes e mostra como usar o comando TerrainDetail.

Exemplo 12Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,120,50,30light=CreateLight()RotateEntity light,90,0,0terrain=LoadTerrain( "midia/heightmap.bmp" )ScaleEntity terrain,1,35,1tex=LoadTexture( "midia/Maptex.bmp" )ScaleTexture tex, TerrainSize(terrain),TerrainSize(terrain)EntityTexture terrain,tex,0,1

Page 27: jogos 3D

Resolucao = 2000Vertex = 0While Not KeyDown( 1 )TerrainDetail terrain, Resolucao, VertexIf KeyHit(57) Then Vertex = Not VertexIf KeyDown(44) Then Resolucao = Resolucao - 10If KeyDown(45) Then Resolucao = Resolucao + 10If KeyDown( 205 )=True Then TurnEntity camera,0,-2,0If KeyDown( 203 )=True Then TurnEntity camera,0,2,0If KeyDown( 208 )=True Then MoveEntity camera,0,0,-0.2If KeyDown( 200 )=True Then MoveEntity camera,0,0,0.2x#=EntityX(camera)y#=EntityY(camera)z#=EntityZ(camera)terra_y#=TerrainY(terrain,x#,y#,z#)+5PositionEntity camera,x#,terra_y#,z#RenderWorldText 0,0, "Use as teclas de cursor para andar"Text 0,12, "Use Space para Ligar e Desligar Vertex Morph"Text 0,24, "Use Z e X para aumentar e diminuir a resolução" Text 0,50, "RESOLUÇÃO: " + Resolucao + " Vertex Morph: " + VertexFlipWendEnd

Talvez você ache que só a super textura não conseguiu gerar uma imagem muito detalhada do terreno, você pode então criar camadas de textura, ou para melhorar a resolução da imagem ou para dar efeitos especiais a ela.No exemplo a seguir, usamos uma imagem do exemplo do avião de Mark Sibly para criar um efeito mais de realismo no terreno, pois cria o efeito de rachaduras.

Exemplo 13Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,120,50,30light=CreateLight()RotateEntity light,90,0,0terrain=LoadTerrain( "midia/heightmap.bmp" )ScaleEntity terrain,1,35,1tex=LoadTexture( "midia/Maptex.bmp" )ScaleTexture tex, TerrainSize(terrain),TerrainSize(terrain)EntityTexture terrain,tex,0,2tex2 = LoadTexture( "midia/filtro.jpg" )ScaleTexture tex2,20,20EntityTexture terrain,tex2,0,1While Not KeyDown( 1 ) If KeyDown( 205 )=True Then TurnEntity camera,0,-2,0If KeyDown( 203 )=True Then TurnEntity camera,0,2,0If KeyDown( 208 )=True Then MoveEntity camera,0,0,-0.2If KeyDown( 200 )=True Then MoveEntity camera,0,0,0.2x#=EntityX(camera)y#=EntityY(camera)z#=EntityZ(camera)terra_y#=TerrainY(terrain,x#,y#,z#)+5PositionEntity camera,x#,terra_y#,z#RenderWorldText 0,0, "Use as teclas de cursor para andar" FlipWendEnd

Agora, e sua vez! Adicione objetos, árvores, casas e carros ao terreno e veja o resultado.

CâmerasComo sabemos, um cenário 3d não é nada sem uma câmera. Ela é nossos olhos, que nos mostra os objetos 3d que existem. As câmeras possuem uma série de propriedades e podemos implementar vários efeitos especiais como zoom e fog. Vamos estudar algumas dessas propriedades.Abaixo um exemplo simples de criação de câmera. Como você já sabe, uma câmera é criada pelo método CreateCamera.

Exemplo 14Graphics3D 640,480SetBuffer BackBuffer()light=CreateLight()camera=CreateCamera()cubo=CreateCube()PositionEntity cubo,0,0,5While Not KeyDown( 1 )TurnEntity cubo,0,1,0RenderWorldFlip

Page 28: jogos 3D

WendEnd

FogO efeito de fog tem duas finalidades: criar efeito de neblina e economizar processamento. Para fazer com que o Fog seja ativado, primeiro você deve dizer qual será a área de influência do Fog, o “Range”, por meio do comando CameraFogRang. Veja abaixo a assinatura desse comando.CameraFogRang câmera, inicio, fimO valor inicial é a distância próxima à câmera onde o efeito começa. O segundo valor, o final, é onde o efeito termina, isso é, onde a neblina fica tão intensa que nada mais pode ser visualizado.Esse comando apenas especifica os atributos de Fog, mas não o aciona. Para ligarmos o efeito de fog devemos usar o comando CameraFogMode. Veja a assinatura desse comando.CameraFogMode câmera, modoVamos a um exemplo prático...

Exemplo 15Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,0,1,0CameraFogRange camera,1,10light=CreateLight()RotateEntity light,90,0,0plano=CreatePlane()tex=LoadTexture( "midia/floor.jpg" )EntityTexture plano,texmodo = 0While Not KeyDown( 1 )CameraFogMode camera, modoIf KeyHit(57) Then modo = Not modoIf KeyDown( 205 )=True Then TurnEntity camera,0,-1,0If KeyDown( 203 )=True Then TurnEntity camera,0,1,0If KeyDown( 208 )=True Then MoveEntity camera,0,0,-0.05If KeyDown( 200 )=True Then MoveEntity camera,0,0,0.05RenderWorldText 10,10, "SPACE-> Liga desliga Fog SETAS-> caminha pelo cenário"FlipWendEnd

O primeiro passo foi configura o inicio da neblina para uma unidade de medida a frente da câmera e o fim do efeito para dez à frente.CameraFogRange camera,1,10

A linha a seguir liga e desliga o efeitoCameraFogMode camera, modo

Neblinas normalmente não possuem coloração preta; são de cor cinza. Mas talvez você queira uma neblina com uma cor mais viva, como verde ou vermelho para simular um efeito de gás químico. Para isso temos o comando CameraFogColor.CameraFogColor câmera, vermelho, verde, azul

Exemplo 16Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,0,1,0CameraFogRange camera,1,10CameraFogColor camera,100,100,100CameraFogMode camera, 1light=CreateLight()RotateEntity light,90,0,0plano=CreatePlane()tex=LoadTexture( "midia/floor.jpg" )EntityTexture plano,texWhile Not KeyDown( 1 )If KeyDown( 205 )=True Then TurnEntity camera,0,-1,0If KeyDown( 203 )=True Then TurnEntity camera,0,1,0If KeyDown( 208 )=True Then MoveEntity camera,0,0,-0.05If KeyDown( 200 )=True Then MoveEntity camera,0,0,0.05RenderWorldText 10,10, "SETAS-> caminha pelo cenário"FlipWendEnd

Nesse exemplo colocamos a saturação de cada cor em 100 daí criamos uma coloração cinza para o efeito de fog.CameraFogColor camera,100,100,100

Como você viu o efeito de fog só atuou sobre as áreas onde haviam entidades sendo exibidas. Caso você possua um cenário Indoor, isso é, interno, isso não será problema, pois haverá paredes para todos os lados.Para jogos em cenários abertos você poderá ter situações onde utilize um skybox ou um skydome e assim também haverá objetos 3d por todos os lados. Mas se quiser economizar o processamento mesmo, poderá eliminar esses recursos e configura a cor de fundo da câmera para ficar com a mesma cor do fog. Podemos fazer isso por meio do comando CameraClsColor. Esse comando funciona assim:CameraClsColor câmera, vermelho, verde, azul

Page 29: jogos 3D

Exemplo 17Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,0,1,0CameraClsColor camera,100,100,100CameraFogRange camera,1,10CameraFogColor camera,100,100,100CameraFogMode camera, 1light=CreateLight()RotateEntity light,90,0,0plano=CreatePlane()tex=LoadTexture( "midia/floor.jpg" )EntityTexture plano,texWhile Not KeyDown( 1 )If KeyDown( 205 )=True Then TurnEntity camera,0,-1,0If KeyDown( 203 )=True Then TurnEntity camera,0,1,0If KeyDown( 208 )=True Then MoveEntity camera,0,0,-0.05If KeyDown( 200 )=True Then MoveEntity camera,0,0,0.05RenderWorldText 10,10, "SETAS-> caminha pelo cenário"FlipWendEnd

Aqui está nosso comando que muda a cor de fundo da câmera.CameraClsColor camera,100,100,100

Áreas de visãoPor meio do comando CameraViewPort podemos configurar a área de visão de uma ou mais câmeras. Dessa forma, além de configurar a área de visão, podemos usar esse comando para configura duas ou mais câmeras em uma aplicação.CameraViewport camera, posiçãox, posiçãoy, largura, alturaPodemos com ele configurar onde a área de exibição se inicia (posiçãox e posiçãoy) e quais serão as dimensões dessa área de visão da câmera.

Exemplo 18Graphics3D 800,600SetBuffer BackBuffer()cam1=CreateCamera()CameraViewport cam1,0,0,399,600cam2=CreateCamera()CameraViewport cam2,401,0,399,600light=CreateLight()RotateEntity light,90,0,0plane=CreatePlane()grass_tex=LoadTexture( "midia/floor.jpg" )EntityTexture plane,grass_texPositionEntity plane,0,-1,0While Not KeyDown( 1 )

If KeyDown( 205 )=True Then TurnEntity cam1,0,-1,0If KeyDown( 203 )=True Then TurnEntity cam1,0,1,0If KeyDown( 208 )=True Then MoveEntity cam1,0,0,-0.05If KeyDown( 200 )=True Then MoveEntity cam1,0,0,0.05RenderWorldText 0,0,"SETAS-> Move Camera1"FlipWendEnd

Veja que criamos duas câmeras e colocamos uma ao lado da outra:cam1=CreateCamera()CameraViewport cam1,0,0,399,600cam2=CreateCamera()CameraViewport cam2,401,0,399,600

Mas só a primeira câmera será animada, isso é, obedecerá aos comandos do teclado.If KeyDown( 205 )=True Then TurnEntity cam1,0,-1,0If KeyDown( 203 )=True Then TurnEntity cam1,0,1,0If KeyDown( 208 )=True Then MoveEntity cam1,0,0,-0.05If KeyDown( 200 )=True Then MoveEntity cam1,0,0,0.05

Limite de visãoQuando criamos uma câmera, ela possui um limite de visão, um “Ranger”. Esse limite padrão é do ponto 1 até o 1.000. Se necessitarmos mudar esses valores podemos usar o comando CameraRange.CameraRange câmera, início, fimCom esse comando podemos declarar de onde se iniciará a exibição e até onde ela vai alcançar. Esse comando pode ser muito importante para aumentar o desempenho de uma aplicação, pois se nós diminuirmos a amplitude de visão da câmera, teremos menos objetos sendo renderizados.

Exemplo 19Graphics3D 800,600SetBuffer BackBuffer()cam=CreateCamera()light=CreateLight()plane=CreatePlane()grass_tex=LoadTexture( "midia/floor.jpg" )EntityTexture plane,grass_texPositionEntity plane,0,-5,0ini = 1fim = 1000While Not KeyDown( 1 )If KeyHit(57)ini = Rnd(0,25)fim = Rnd(100,300)CameraRange cam, ini, fimEndIf

Page 30: jogos 3D

If KeyDown( 205 )=True Then TurnEntity cam,0,-1,0If KeyDown( 203 )=True Then TurnEntity cam,0,1,0If KeyDown( 208 )=True Then MoveEntity cam,0,0,-0.05If KeyDown( 200 )=True Then MoveEntity cam,0,0,0.05RenderWorldText 0, 0,"SPACE -> Sorteia nova Aplitude"Text 0, 20, "Alcance da camera => INICIO: " + ini + " FIM: " + fimFlipWendEnd

Nesse exemplo criamos duas variáveis para controlar os valores iniciais e finais da área de visão da câmera.ini = 1fim = 1000

Quando pressionamos a tecla space (57) os valores das variáveis são sorteados e ocorre a execução do comando de alteração da amplitude da câmara com os novos valores.If KeyHit(57)ini = Rnd(0,25)fim = Rnd(100,300)CameraRange cam, ini, fimEndIf

Efeito de ZoomPor meio do comando CameraZoom conseguimos gerar um efeito de zoom na câmera. Isso pode ser bastante interessante para jogos de tiro onde o armamento possui luneta, ou então para simular o uso de binóculos.Esse comando é muito simples, pois só recebe dois parâmetros: a câmera na qual desejamos implementar o zoom e a intensidade do efeito.

Exemplo 20Graphics3D 800,600SetBuffer BackBuffer()cam=CreateCamera()light=CreateLight()plane=CreatePlane()grass_tex=LoadTexture( "midia/floor.jpg" )EntityTexture plane,grass_texPositionEntity plane,0,-5,0zoom# = 1While Not KeyDown( 1 )If KeyDown(44)zoom# =zoom# + 0.01CameraZoom cam, zoom#EndIfIf KeyDown(45)zoom# =zoom# - 0.01CameraZoom cam, zoom#EndIf

RenderWorldText 0, 0,"Z -> Zoom + X -> Zoom -"Text 0, 20, "ZOOM => " + zoom#FlipWendEnd

IluminaçãoA iluminação é algo muito importante em um jogo. Se o seu correto controle fica difícil criar realismo nas cenas e ela também é fundamental para criar efeitos especiais. Além da iluminação ambiente, o Blitz3d possui 3 tipos de emissores de luz. Vamos estudar esses 4 fatores e algumas formas de trabalhar com eles.

AmbienteLightQuando iniciamos uma cena 3d o Blitz3d já configura a iluminação ambiente da cena automaticamente para uma saturação 127, 127, 127. Veja nesse exemplo que mesmo sem criar uma fonte de emissão de luz, existe uma certar iluminação acinzentada no cenário.

Exemplo 21Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()sphere=CreateSphere( 32 )PositionEntity sphere,0,0,5While Not KeyDown( 1 ) RenderWorldFlipWendEnd

Essa saturação é a padrão do Blitz3d. Mas se quisermos, por exemplo, criar um efeito de escuridão, deveremos deixar a iluminação ambiente mais escura. Para mudarmos a iluminação ambiente usamos o comando AmbienteLight. Esse comando aceita três parâmetros que são as saturações red, green, blue do formato de cor RGB.No Exemplo abaixo usamos um gerador de valores randômicos para sortear a intensidade de cada por e aplicar ao ambiente.

Exemplo 22Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()sphere=CreateSphere( 32 )PositionEntity sphere,0,0,5r=127g=127b=127

Page 31: jogos 3D

While Not KeyDown( 1 )If KeyHit(57)r = Rnd(0,255)g = Rnd(0,255)b = Rnd(0,255)AmbientLight r,g,bEndIfRenderWorldText 0,0,"SPACE -> Sorteia Nova Iluminação Ambiente"Text 0,20,"Red: " + r + " Green: " + g + " Blue: " + bFlipWendEnd

Criando LuzesPor meio do comando CreateLight do Blitz3D podemos criar 3 tipos de fontes emissoras de luz.CreateLight( ) – comando padrão, cria uma luz direcional.CreateLight(1) – cria luz direcional (directional).CreateLight(2) – cria um ponto de luz (point).CreateLight(3) – cria uma luz em cone (spot).A luz direcional é como a luz do sol, iluminando tudo. Elas possuem uma posição infinita e alcance infinito, portanto atingem todos os objetos da mesma forma.O ponto de luz é como uma lâmpada, também chamada de Omni em modeladores 3d. Ela opera como uma lâmpada comum de residência, dessas redondas. Ilumina tudo ao redor a partir de um ponto, mas sua intensidade vai diminuindo com a distância.A iluminação do tipo cone, ou spot é uma iluminação em forma de cone. Possui um ângulo inicial de iluminação e um ângulo final.Vamos criar um ambiente de testes para essas lâmpadas.

Exemplo 23Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()AmbientLight 0,0,0bola=CreateSphere( 32 )PositionEntity bola,0,0,5parede1=CreateCube()PositionEntity parede1,-3,0,5ScaleMesh parede1, .5, 2, 5parede2 =CopyMesh(parede1)PositionEntity parede2, 3,0,5parede3 =CopyMesh(parede1)PositionEntity parede3, 0,0,10RotateEntity parede3, 0, 90,0

tipo = 2LUZ = CreateLight(2,bola)LightRange luz, 3PositionEntity luz, 2, 0, 0LightColor luz, 0,255,0While Not KeyDown( 1 )TurnEntity bola, 0, 1, 0If KeyHit(57)r = Rnd(0,255)g = Rnd(0,255)b = Rnd(0,255)ntipo = Rnd(1,3)If ntipo <> tipoFreeEntity luzLUZ = CreateLight(ntipo,bola)LightRange luz, 3PositionEntity luz, 2, 0, 0 tipo = ntipoEndIfLightColor luz, r,g,bEndIfRenderWorldText 0,0,"SPACE -> Sorteia Nova Cor e Tipo"Text 0,20,"Red: " + r + " Green: " + g + " Blue: " + bIf tipo = 1 Then Text 0, 40, "TIPO: Direcional"If tipo = 2 Then Text 0, 40, "TIPO: Omni"If tipo = 3 Then Text 0, 40, "TIPO: Spot"FlipWendEnd

Veja que, para intensificar o efeito da luz, deixamos a luz ambiente apagada, isso é, configuramos seu valor para preto 0,0,0.AmbientLight 0,0,0

Estamos criando uma luz inicial do tipo Omni, sendo ela filha do objeto bola. Fizemos isso para rotacioná-la de forma mais simples.LUZ = CreateLight(2,bola)

Estamos usando o comando LightRange para configurar o raio de alcance dela para 3, assim criaremos efeito de sombra e intensidade de iluminação pois a luz vai atingir um mesmo ambiente em intensidade diferente de acordo com a distância. O valor padrão para range é 1000, se nós deixássemos esse valor, todo o cenário seria ilumidado com mesma intensidade de luz.LightRange luz, 3

Como criamos a luz como filha do objeto bola, ele está na mesma posição dela, por isso devemos deslocar a mesma um pouco.PositionEntity luz, 2, 0, 0

Estamos configurando a cor inicial da luz para verde.LightColor luz, 0, 255, 0

Dentro do loop, estamos rotacionando a luz por meio da esfera. Como a luz é uma

Page 32: jogos 3D

entidade filha dessa, ela receberá os mesmos comandos automaticamente. Quando deslocamos a luz, não deslocamos o pivô dela, portanto, o efeito de rotação dela se dará no mesmo eixo da bola, causando um efeito de translação, assim como os planetas fazem em redor do sol.TurnEntity bola, 0, 1, 0

Quando apertamos a barra de espaço vamos sortear uma nova cor e um novo tipo de luz. Para mudar a luz, tivemos de destruir a entidade anterior e criar uma nova com os dados sorteados, mas só fazemos isso se a nova é de um tipo diferente da anterior.If KeyHit(57)r = Rnd(0,255)g = Rnd(0,255)b = Rnd(0,255)ntipo = Rnd(1,3)If ntipo <> tipoFreeEntity luzLUZ = CreateLight(ntipo,bola)LightRange luz, 3PositionEntity luz, 2, 0, 0 tipo = ntipoEndIfLightColor luz, r,g,bEndIf

SpritesSprites são entidades 2d presentes em um ambiente 3d. Eles nada mais são que imagens inseridas em um ambiente tridimencional. Eles possuem a característica especial de sempre estarem com sua face voltada para a câmera, assim ninguém irá perceber que ele é plano.Os sprites são comumente usados para 3 finalidades básicas:1. criar efeitos de partículas.2. gerar cenários mais leves para economia de processamento.3. criação de jogos 2d com aceleração gráfica.Como elemento básico para partículas sua usabilidade é muito obvia, afinal um sprite possui apenas 2 polígonos e pode conter uma imagem muito complexa, que gastaria dezenas ou até centenas de polígonos para ser criada em 3d. Como partículas não muitíssimas em um cenário (imagine neve ou chuva), só por meio de sprites é possível criar esse tipo de efeito.Também podemos usar sprites em objetos do cenário para deixar a aplicação mais leve. O mais comum é o uso para criar árvores e outras vegetações.

Talvez você deseje criar um jogo estilo 2d, mas necessita de alguns efeitos especiais, ou uma melhor perspectiva gráfica. Usando sprites você terá um alto poder de desempenho e poderá implementar alguns efeitos extras em seus jogos.No exemplo abaixo estamos criando um efeito de neve por meio de diversos sprites de esferas. Veja que embora temos milhares de objetos na tela, a quantidade de polígonos renderizados é bem baixa.

Exemplo 24Graphics3D 640,480Type TneveField spriteField px#Field py#Field pz#End Typecam = CreateCamera()MoveEntity cam,0,3,-10Global floco = LoadSprite("midia/floco.png")ScaleSprite floco, 0.1, 0.1HideEntity flocoWhile Not KeyDown(1)Criar()t = Mover() UpdateWorldRenderWorldColor 255,0,0Text 0,0, "FLOCOS: " + tText 0,20, "TOTAL DE POLIGONOS: " + TrisRendered()FlipWendEndFunction Criar()For k = 1 To 5y# = 10x# = Rnd (-10,10)z# = Rnd (-10,10)flocos.Tneve = New Tneveflocos\sprite = CopyEntity(floco)flocos\px# = x#flocos\py# = y#flocos\pz# = z#Next End FunctionFunction mover()For this.Tneve =Each Tnevethis\py# = this\py# - 0.02 PositionEntity this\sprite, this\px#, this\py#, this\pz#If this\py# < 0 FreeEntity this\sprite Delete this EndIfx = x + 1NextReturn xEnd Function

Page 33: jogos 3D

Veja que criamos um type para gerenciar as entidades. Isso é muito importante num efeito de partículas, pois os types não possuem limite preestabelecido de entidades, assim podemos criar a quantidade que quisermos.Type TneveField spriteField px#Field py#Field pz#End Type

Carregamos a primeira entidade base de sprite que servirá de cópia para todas as outras e demos o comando Hide para escondê-la da tela.Global floco = LoadSprite("midia/floco.png")ScaleSprite floco, 0.1, 0.1HideEntity floco

O nosso loop se inicia com a invocação da função Criar().Criar()

Vamos analisar essa função:Function Criar()For k = 1 To 5y# = 10x# = Rnd (-10,10)z# = Rnd (-10,10)flocos.Tneve = New Tneveflocos\sprite = CopyEntity(floco)flocos\px# = x#flocos\py# = y#flocos\pz# = z#Next End Function

Ela se inicia com um laço de 5 iterações, portanto estamos criando 5 entidades em cara iteração do loop. Isso dá mais ou menos 300 entidades por segundo, já que a velocidade da engine está limitada à velocidade de retraço do monitor (60).Colocamos a posição inicial y como 10 “y# = 10”. As posições x e z são aleatórias, são dadas por meio do comando de randomização “Rnd (-10,10)”. A partir disso, criamos a instância de floco e atribuímos a eles os valores para cada campo.Nesse momento, o controle do programa volta para o loop principal e a segunda função é chamada.t = Mover()

Perceba que essa função retorna um valor, a quantidade de flocos que existe atualmente. Vamos a função em si.Function mover()For this.Tneve =Each Tnevethis\py# = this\py# - 0.02 PositionEntity this\sprite, this\px#, this\py#, this\pz#

If this\py# < 0 FreeEntity this\sprite Delete this EndIfx = x + 1NextReturn xEnd Function

Essa função usa o loop For/Each para percorrer todas as instâncias de um Type, assim podemos controlar todos os flocos de neve de uma vez só. A primeira coisa que fazemos é mover o floco um pouco para baixo.this\py# = this\py# - 0.02 PositionEntity this\sprite, this\px#, this\py#, this\pz#

Depois disso, verificamos se o floco passou do ponto zero, isso é, chegou ao chão, em um jogo a melhor opção seria verificar a colisão com o terreno, e se chegou ao final, deletamos a sprite e destruímos a instância do type.If this\py# < 0 FreeEntity this\sprite Delete this EndIf

Para descobrir a quantidade de flocos de neve atuais colocamos um contador dentro do laço For/Each.x = x + 1

Por fim retornamos o valor encontrado.Return x

Por fim, restou renderizar a cena e exibir as informações.UpdateWorldRenderWorldColor 255,0,0Text 0,0, "FLOCOS: " + tText 0,20, "TOTAL DE POLIGONOS: " + TrisRendered()Flip

Vamos dar um exemplo agora de uso de sprites para criar vegetação.

Exemplo 25Graphics3D 800,600SetBuffer BackBuffer()cam=CreateCamera()light=CreateLight()plane=CreatePlane()tex=LoadTexture( "midia/floor.jpg" )EntityTexture plane,texPositionEntity plane,0,-2,0Dim moita(2000)moita(0) = LoadSprite("midia\moita.png",2)ScaleSprite moita(0), .5, .5For k = 1 To 2000x = Rnd(-10, 10)y = -1z = Rnd(-10,10)moita(k) = CopyEntity(moita(0))

Page 34: jogos 3D

PositionEntity moita(k), x, y, zNextWhile Not KeyDown( 1 ) If KeyDown( 205 )=True Then TurnEntity cam,0,-1, 0If KeyDown( 203 )=True Then TurnEntity cam,0, 1, 0If KeyDown( 208 )=True Then MoveEntity cam,0, 0,-.2If KeyDown( 200 )=True Then MoveEntity cam,0, 0, .2RenderWorldText 0, 0, "SETAS -> andam pelo cenário"Text 0, 20,"POLIGONOS RENDERIZADOS: " + TrisRendered()FlipWendEnd

Dessa vez carregamos o sprite com a flag 2, afinal não seria nada bom que eles fossem transparentes.moita(0) = LoadSprite("midia\moita.png",2)

Uma das vantagens de poder usar sprites é poder fazer operações com eles que não poderíamos fazer com objetos 2d, por exemplo, rotação em tempo real. Com o comando RotateSprite podemos rotacionar um sprite no sentido horário ou anti-horário, isso é, no eixo z.

Exemplo 26Graphics3D 800,600SetBuffer BackBuffer()cam=CreateCamera()light=CreateLight()ob = LoadSprite("midia\conc02.jpg")PositionEntity ob, 0,0,2While Not KeyDown( 1 ) If KeyDown( 203 ) Then rot = rot + 1If KeyDown( 205 ) Then rot = rot - 1RotateSprite ob, rotRenderWorldText 0, 0, "SETAS Esquerda e Direita rotaciona o Sprite"FlipWendEnd

PivotUm pivô é uma entidade invisível no cenário 3d sua funcionalidade é prover um pai para outro objeto em uma hierarquia, podendo ser usado para controlar as entidades filhas, sendo deu centro de rotação. Caso você decida criar uma entidade através de vértices e malhas, você poderá manipular todas essas entidades apenas por meio do pivô.No exemplo abaixo criamos um pivot e passamos este como entidade pai das esferas criadas posteriormente. Assim, basta

rotacionar o pivô que as três esferas são rotacionadas juntas.

Exemplo 27Graphics3D 800, 600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,0,0,-10light=CreateLight()pivo=CreatePivot()bola=CreateSphere(16,pivo)PositionEntity bola,1,0,0EntityColor bola, 255,0,0bola2=CreateSphere(16,pivo)PositionEntity bola2,4,2,0EntityColor bola2, 0,255,0bola3=CreateSphere(16,pivo)PositionEntity bola3,7,-1,0EntityColor bola3, 0,0,255While Not KeyDown(1)TurnEntity pivo,0,1,0RenderWorldFlipWendEnd

Som 3dUm som 3d é um som com posição geográfica dentro e um cenário. Quanto mais próximo estamos da fonte emissora do som, mais alto será o volume com o qual o escutamos.Para controlar sons 3d o Blitz3D possui 3 comandos:Load3Dsound -> carrega um som como entidade 3d.CreateListener -> configura uma entidade como ouvinte do som.EmitSound -> emite um som 3d.O primeiro comando não possui novidades, só devemos passar a ele o som que deverá ser carregado. Som3d = Load3DSound(“umsom.wav”)Para que possamos criar a sensação de distancia, devemos criar um ouvinte, a entidade que escuta o som, para isso normalmente usamos a câmera pai do ouvinte, pois ela representa a nossa posição no cenário 3d e assim o ouvinte a seguirá. No segundo parâmetro temos a perda por distância, onde o valor padrão é 1. Quanto menor esse valor, mais distante o som poderá ser ouvido.Ouvinte = CreateListener(camera,perda)Um som 3d é emitido por uma entidade 3d, por isso o comando EmitSound recebe o som a ser emitido e o objeto emissor, para que

Page 35: jogos 3D

esse som seja emitido no local onde está esse objeto.EmitSound(som3d, ObjetoEmissor)

Exemplo 28Graphics3D 800,600SetBuffer BackBuffer()camera=CreateCamera()PositionEntity camera,0,5,-10light=CreateLight()RotateEntity light,90,0,0cenario=LoadMesh("midia/cenariobase.3ds")radio =LoadMesh("midia/cdplay.3ds")PositionEntity radio, 15, 1.1, 20ScaleEntity radio, 5,5,5fone=CreateListener(camera,0.2) som=Load3DSound("midia/beep1.wav") While Not KeyDown(1)If KeyDown(205)=True Then TurnEntity camera,0,-1, 0If KeyDown(203)=True Then TurnEntity camera,0, 1, 0If KeyDown(208)=True Then MoveEntity camera,0, 0,-0.05If KeyDown(200)=True Then MoveEntity camera,0, 0, 0.05x = x + 1If x = 60x = 0EmitSound(som,radio)EndIfRenderWorldText 0,0,"Use Setas Para Mover"Text 0,20,"Va até o Radio, o Som vai aumentando o volume"FlipWendEnd

Criamos um ouvinte filho da câmera e com perda de 0.2.fone=CreateListener(camera,0.2)

Configuramos o modelo 3d de rádio para ser o emissor do somEmitSound(som,radio)

PráticasVamos agora explicar como implementar algumas coisas úteis em jogos 3d. Isso é, como implementar algumas funcionalidades básicas.

Fazer a camera seguir o personagemPara fazer com que uma câmera siga um personagem basta declarar a câmera como filha do personagem a ser seguido.Obj = Loadmesh(“obj.b3d”)camera = CreateCamera(Obj)Depois é só posicionar a câmera na distancia certa do personagem e dar uma ligeira inclinação para ela apontar para o lugar certo.

MoveEntity camera, 0, 5, 10RotateEntity camera, 30,0,0A partir desse momento é só controlarmos o personagem que a câmera o segue.

Exemplo 29Graphics3D 800,600SetBuffer BackBuffer()heroy = LoadAnimMesh("midia/ninja.b3d") ScaleEntity heroy, .1, .1, .1PositionEntity heroy, 20,0,30parado = ExtractAnimSeq(heroy,4,4) andando = ExtractAnimSeq(heroy,1,14)anima = 0camera=CreateCamera(heroy)MoveEntity camera, 0, 10,-10RotateEntity camera, 20,0,0light=CreateLight()terrain=LoadTerrain( "midia/heightmap.bmp" )ScaleEntity terrain,1,35,1tex=LoadTexture( "midia/Maptex.bmp" )ScaleTexture tex, TerrainSize(terrain),TerrainSize(terrain)EntityTexture terrain,tex,0,1While Not KeyDown( 1 )If KeyDown( 205 ) Then TurnEntity heroy,0,-1,0If KeyDown( 203 ) Then TurnEntity heroy,0, 1,0If KeyDown( 200 )MoveEntity heroy,0,0, 0.1If Animate = 0 Then Animate heroy, 1, 0.2, andandoAnimate = 1ElseAnimate heroy, 1, 0.2, paradoAnimate = 0EndIfx#=EntityX(heroy)y#=EntityY(heroy)z#=EntityZ(heroy)terra_y#=TerrainY(terrain,x#,y#,z#)PositionEntity heroy,x#,terra_y#,z#UpdateWorldRenderWorldText 0,0,"Use as teclas de cursor para andar"FlipWendEnd

Primeiro criamos o nosso heróiheroy = LoadAnimMesh("midia/ninja.b3d") ScaleEntity heroy, .1, .1, .1PositionEntity heroy, 20,0,30

Extraímos as animações que vamos usar e criamos uma variável para o controle das animações.parado = ExtractAnimSeq(heroy,4,4) andando = ExtractAnimSeq(heroy,1,14)anima = 0

A seguir, criamos a carmera como filha do Herói, e colocamos a mesma a uma certa distância, olhando para o herói.

Page 36: jogos 3D

camera=CreateCamera(heroy)MoveEntity camera, 0, 10,-10RotateEntity camera, 20,0,0

Se pressionarmos as setas para esquerda e para direita, o herói será rotacionado e como a câmera é sua filha, vai acompanhar o herói.If KeyDown( 205 ) Then TurnEntity heroy,0,-1,0If KeyDown( 203 ) Then TurnEntity heroy,0, 1,0

O passo mais complexo e animar o personagem se ele estiver indo pra frente.If KeyDown( 200 )MoveEntity heroy,0,0, 0.1If Animate = 0 Then Animate heroy, 1, 0.2, andandoAnimate = 1ElseAnimate heroy, 1, 0.2, paradoAnimate = 0EndIf

Se a seta para cima for pressionada o herói será movido para frente:MoveEntity heroy,0,0, 0.1

Se a animação de andar não estiver ativa, então rodamos essa animação.If Animate = 0 Then Animate heroy, 1, 0.2, andando

Se a tecla para cima não estiver sendo apertada, então o heroy deverá estar parado e a animação exibir esse estado.Animate heroy, 1, 0.2, parado

Colisões com o cenárioQuando formos trabalhar com sistema de colisões podem surgir alguns inconvenientes.O sistema automático de colisões do Blitz3D sempre cria colisões por esfera para o objeto principal, mas não podemos reposicionar essa esfera, podemos apenas aumentar o tamanho de seu raio x e y.O problema aparece quando queremos inserir um personagem animado. Ao criar a esfera de colisões, isso vai ser feito onde estiver o pivot do personagem, e normalmente o pivot dele é em seus pés, daí o centro da esfera de colisões será o pé do personagem. Resultado, ele não consegue subir uma mínima elevação, às vezes até é empurrando para baixo do cenário.O correto é ajustar o pivot do modelo 3d em um software de modelagem 3d, mas nem sempre isso é possível. Para resolver esse problema uso a técnica de criar um objeto auxiliar para a colisão, daí eu crio o herói como filho desse objeto, herdando o comportamento do objeto pai. O objeto auxiliar deve ter as mesmas dimensões do

modelo animado para não gerar efeitos desagradáveis. Para visualizar melhor como deveremos ajustar os raios da entidade de colisões, eu crio uma objeto auxiliar de uma esfera, daí é só passar os mesmos dados para a entidade de colisão. Veja abaixo uma imagem da esfera criada para auxiliar na colisão do personagem.

Os passos seguintes são: criar a entidade de colisão baseada nessa esfera e configurar o canal alfa para que a esfera fique invisível.

Exemplo 30Graphics3D 800,600SetBuffer BackBuffer()Const COLIDE_HEROI = 1Const COLIDE_CENARIO = 2CX = CreateSphere()ScaleMesh CX, 1.5,5.5,1.5PositionEntity CX, 0,5,10EntityType CX, COLIDE_HEROIEntityRadius CX, 1.5, 5.5EntityAlpha CX, 0heroy = LoadAnimMesh("midia/ninja.b3d",CX) MoveEntity heroy, 0,-5,0parado = ExtractAnimSeq(heroy,4,4) andando = ExtractAnimSeq(heroy,1,14)anima = 0camera=CreateCamera(heroy)MoveEntity camera, 0, 20,-20RotateEntity camera, 20,0,0light=CreateLight()RotateEntity light, 90,0,0cenario=LoadMesh( "midia/cenariobase.3ds" )EntityType cenario, COLIDE_CENARIOCollisions COLIDE_HEROI, COLIDE_CENARIO, 2, 2While Not KeyDown( 1 )If KeyDown( 205 ) Then TurnEntity CX,0,-1,0If KeyDown( 203 ) Then TurnEntity CX,0, 1,0If KeyDown( 200 )MoveEntity CX,0,0, 0.1If Animate = 0 Then Animate heroy, 1, 0.2, andando

Page 37: jogos 3D

Animate = 1ElseAnimate heroy, 1, 0.2, paradoAnimate = 0EndIfUpdateWorldRenderWorldText 0,0,"Use as teclas de cursor para andar"FlipWendEnd

A primeira coisa que fizemos foi criar os tipos de entidades de colisõesConst COLIDE_HEROI = 1Const COLIDE_CENARIO = 2

Criamos uma esfera para auxiliar nas colisões e ajustamos o tamanho dela para que fiquem do mesmo tamanho que o nosso modelo animado.CX = CreateSphere()ScaleMesh CX, 1.5, 5.5, 1.5

Ajustamos a posição da esfera no cenárioPositionEntity CX, 0,5,10

Configuramos o tipo de objeto de colisão da esfera. Ela será responsável pela colisão do herói.EntityType CX, COLIDE_HEROI

Ajustamos os raios da entidade de colisão da esfera que fique com o mesmo tamanho da esfera. Como você se lembra, ao ser criada essa entidade possui raios 1,1.EntityRadius CX, 1.5, 5.5

Deixamos a esfera auxiliar invisívelEntityAlpha CX, 0

Ao criar o modelo animado do herói, dizemos que ele será filho da esfera auxiliar de colisões.heroy = LoadAnimMesh("midia/ninja.b3d",CX) Ajustamos a posição do modelo animado para que se ajuste à posição da esfera, afinal o centro dele é no pé e a esfera tem seu centro em seu meio. Assim, ao ser criado como filho da esfera, ele terá seu pé no meio da esfera. MoveEntity heroy, 0,-5,0

A câmera será criada como filha do herói, assim seguirá o mesmo pelo cenário. Veja que também fizemos ajustes em sua posição e ângulo para pegar um melhor foco do cenário.camera=CreateCamera(heroy)MoveEntity camera, 0, 20,-20RotateEntity camera, 20,0,0

Carregamos o cenário e configuramos seu tipo de colisão.cenario=LoadMesh( "midia/cenariobase.3ds" )EntityType cenario, COLIDE_CENARIO

Agora falta acionar o sistema de colisões. Ligamos as colisões do tipo esfera para polígono, pois um cenário tem que ser sempre polígono (2), senão seria impossível interagir com ele. Também escolhermos o resultado da colisão como deslizar (2).Collisions COLIDE_HEROI, COLIDE_CENARIO, 2, 2

Os comando de movimentação e rotação são aplicados na esfera e não no modelo 3d, afinal é por meio dela que estamos controlando as colisões e física. O modelo 3d recebe os comportamentos por herança.If KeyDown( 205 ) Then TurnEntity CX,0,-1,0If KeyDown( 203 ) Then TurnEntity CX,0, 1,0

Escadas e rampasAgora que criamos o nosso sistema auxiliar de colisões, subir escadas e rampas é muito fácil. Nesse novo exemplo apenas adicionamos um objeto novo no cenário anterior.

Exemplo 31Graphics3D 800,600SetBuffer BackBuffer()Const COLIDE_HEROI = 1Const COLIDE_CENARIO = 2CX = CreateSphere()ScaleMesh CX, 1.5,5.5,1.5PositionEntity CX, 0,5,-10EntityType CX, COLIDE_HEROIEntityRadius CX, 1.5, 5.5EntityAlpha CX, 0heroy = LoadAnimMesh("midia/ninja.b3d",CX) MoveEntity heroy, 0,-5,0parado = ExtractAnimSeq(heroy,4,4) andando = ExtractAnimSeq(heroy,1,14)anima = 0camera=CreateCamera(heroy)MoveEntity camera, 0, 20,-20RotateEntity camera, 20,0,0

Page 38: jogos 3D

light=CreateLight()RotateEntity light, 90,0,0cenario=LoadMesh( "midia/cenariobase.3ds" )EntityType cenario, COLIDE_CENARIOrampa = LoadMesh( "midia/escada.3ds" )EntityType rampa, COLIDE_CENARIOPositionEntity rampa, 9, 0, 18Collisions COLIDE_HEROI, COLIDE_CENARIO, 2, 3While Not KeyDown( 1 )If KeyDown( 205 ) Then TurnEntity CX,0,-1,0If KeyDown( 203 ) Then TurnEntity CX,0, 1,0If KeyDown( 200 )MoveEntity CX,0,0, 0.1If Animate = 0 Then Animate heroy, 1, 0.2, andandoAnimate = 1ElseAnimate heroy, 1, 0.2, paradoAnimate = 0EndIfUpdateWorldRenderWorldText 0,0,"Use as teclas de cursor para andar"FlipWendEnd

Quando temos o nosso sistema de colisões definido tudo fica mais simples. Para colocar a rampa no cenário e ativar a sua interação precisamos apenas de 3 linhas de código. Uma para carregar o modelo 3d, outra para posicionar o modelo no local correto e uma para dizer qual é o seu grupo de colisão. Como você se lembra, colisões são feitas por grupos de objetos, isso é pelos tipos.rampa = LoadMesh( "midia/escada.3ds" )PositionEntity rampa, 9, 0, 18EntityType rampa, COLIDE_CENARIO

A essas alturas está tudo muito lindo, o problema é descer a escada. Como não criamos um sistema de gravidade, o objeto só sobe e nunca desce.

GravidadePara criarmos o efeito de gravidade devemos ter uma constante de gravidade e uma variável para controlar a velocidade atual. A cada iteração do loop, fazemos a velocidade atual ser modificada pela constante de gravidade, pois a gravidade é uma aceleração. Quando o objeto se chocar com o cenário a velocidade é zerada. Veja abaixo o exemplo.Exemplo 32Graphics3D 800,600SetBuffer BackBuffer()Const COLIDE_HEROI = 1

Const COLIDE_CENARIO = 2Const G# = 0.05Global VY# = 0CX = CreateSphere()ScaleMesh CX, 1.5,5.5,1.5PositionEntity CX, 0,6,-10EntityType CX, COLIDE_HEROIEntityRadius CX, 1.5, 5.5EntityAlpha CX, 0heroy = LoadAnimMesh("midia/ninja.b3d",CX) MoveEntity heroy, 0,-5,0parado = ExtractAnimSeq(heroy,4,4) andando = ExtractAnimSeq(heroy,1,14)anima = 0camera=CreateCamera(heroy)MoveEntity camera, 0, 20,-20RotateEntity camera, 20,0,0light=CreateLight()RotateEntity light, 90,0,0cenario=LoadMesh( "midia/cenariobase.3ds" )EntityType cenario, COLIDE_CENARIOrampa = LoadMesh( "midia/escada.3ds" )EntityType rampa, COLIDE_CENARIOPositionEntity rampa, 9, 0, 18Collisions COLIDE_HEROI, COLIDE_CENARIO, 2, 3While Not KeyDown( 1 )If KeyDown( 205 ) Then TurnEntity CX,0,-1,0If KeyDown( 203 ) Then TurnEntity CX,0, 1,0If KeyDown( 200 )MoveEntity CX,0,0, 0.1If Animate = 0 Then Animate heroy, 1, 0.2, andandoAnimate = 1ElseAnimate heroy, 1, 0.2, paradoAnimate = 0EndIfVY# = VY# - G#If EntityCollided(CX,COLIDE_CENARIO) Then VY# = 0 MoveEntity CX,0,VY#,0UpdateWorldRenderWorldText 0,0,"Use as teclas de cursor para andar"FlipWendEnd

Criamos uma constante para gravidade e uma variável para controlar a velocidade.Const G# = 0.05Global VY# = 0

O restante do código deverá estar dentro do loop, ou então em uma função de física invocada pelo loop.VY# = VY# - G#If EntityCollided(CX,COLIDE_CENARIO) Then VY# = 0 MoveEntity CX,0,VY#,0

Veja que a velocidade é atualizada constantemente pela gravidade. Se a

Page 39: jogos 3D

entidade se chocar com o cenário, a velocidade é zerada. Devemos sempre colocar um comando MoveEntity para atualizar a posição do personagem.É só isso! Simples não?

SaltandoCriar um salto em um ambiente 3d é muito simples se já existe a funcionalidade de grávida. Só precisamos configurar a velocidade y para um valor que corresponda ao esforço inicial do salto.

Exemplo 33Graphics3D 800,600SetBuffer BackBuffer()Const COLIDE_HEROI = 1Const COLIDE_CENARIO = 2Const G# = 0.05Global VY# = 0CX = CreateSphere()ScaleMesh CX, 1.5,5.5,1.5PositionEntity CX, 0,6,-10EntityType CX, COLIDE_HEROIEntityRadius CX, 1.5, 5.5EntityAlpha CX, 0heroy = LoadAnimMesh("midia/ninja.b3d",CX) MoveEntity heroy, 0,-5,0parado = ExtractAnimSeq(heroy,4,4) andando = ExtractAnimSeq(heroy,1,14)anima = 0camera=CreateCamera(heroy)MoveEntity camera, 0, 20,-20RotateEntity camera, 20,0,0light=CreateLight()RotateEntity light, 90,0,0cenario=LoadMesh( "midia/cenariobase.3ds" )EntityType cenario, COLIDE_CENARIOrampa = LoadMesh( "midia/escada.3ds" )EntityType rampa, COLIDE_CENARIOPositionEntity rampa, 9, 0, 18Collisions COLIDE_HEROI, COLIDE_CENARIO, 2, 3While Not KeyDown( 1 )If KeyDown( 205 ) Then TurnEntity CX,0,-1,0If KeyDown( 203 ) Then TurnEntity CX,0, 1,0If KeyDown( 200 )MoveEntity CX,0,0, 0.1If Animate = 0 Then Animate heroy, 1, 0.2, andandoAnimate = 1ElseAnimate heroy, 1, 0.2, paradoAnimate = 0EndIfIf KeyDown(57)If VY# = 0 Then VY# = 1EndIfVY# = VY# - G#

If EntityCollided(CX,COLIDE_CENARIO) Then VY# = 0 MoveEntity CX,0,VY#,0UpdateWorldRenderWorldText 0,0,"Use as teclas de cursor para andar"FlipWendEnd

Apenas adicionamos as linhas abaixo para criar saltos.If KeyDown(57)If VY# = 0 Then VY# = 1EndIf

O comando de salto é ativado por meio da barra de espaços (57), mas o personagem só saltará se estiver com velocidade vertical igual a 0, isso é, se estiver “pisando” no cenário.AtirarBoa parte dos jogos 3d fazem uso de algum tipo de arma de fogo. Dessa forma, para se tornar um desenvolvedor de jogos, obrigatoriamente devemos dominar essa técnica. Existem várias técnicas usadas para criar e controlar tiros. A maioria das que já vi usa matrizes, mas isso é um erro, pois dificulta a implementação, limita a quantidade de tiros e causa processamento desnecessário. Usando types você poderá fazer alocação dinâmica de recursos e controlar apenas os tiros ativos.A seguir apresentamos a lógica da criação e controle de tiros em um ambiente 3d.

1º PASSO: devemos criar um type modelo para os objetos tiro. Se tivermos apenas um tipo de tiro, teremos apenas um campo para o objeto tiro. Caso tenhamos mais de um tipo, deveremos ter pelo menos mais um campo para sinalizar qual é o tipo daquele tiro.

2º PASSO: devemos carregar uma textura para ser aplicada ao objeto de tiro quando esse for disparado. Se tivermos mais de um tipo de tiro, teremos mais texturas.

3º PASSO: devemos escolher uma tecla para disparar um tiro; quando for pressionada invocará a função criadora de tiros. Também devemos colocar dentro do loop a chamada a uma função de controle de tiros.

4º PASSO: vamos criar a função construtora de tiros. Devemos pegar a posição atual do personagem que vai disparar o tiro, para que

Page 40: jogos 3D

ele apareça no local certo. A segunda tarefa é criar uma instância do Type de tiros. Depois criamos um sprite e o texturizamos. Devemos também dar ao objeto a mesma orientação, isso é, o mesmo ângulo do atirador, para que o tiro vá sempre para frente. Por fim definimos o tipo de colisão do tiro.

5º PASSO: nossa função de controle. Ela deve ter duas finalidades básicas: mover o tiro e detectar colisões. A partir disso, poderemos gerar eventos a partir das colisões, como tirar vida, destruir os tiros, contar pontos, etc.O exemplo a seguir mostra na prática como criar a funcionalidade de tiros.

Exemplo 34Graphics3D 800,600SetBuffer BackBuffer()Global COLIDE_HEROI = 1Global COLIDE_CENARIO = 2Global COLIDE_TIRO = 3Type TtiroField ob End TypeConst G# = 0.05Global VY# = 0Global CX = CreateSphere()ScaleMesh CX, 1.5,5.5,1.5PositionEntity CX, 0,6,-10EntityType CX, COLIDE_HEROIEntityRadius CX, 1.5, 5.5EntityAlpha CX, 0Global heroy = LoadAnimMesh("midia/ninja.b3d",CX) MoveEntity heroy, 0,-5,0parado = ExtractAnimSeq(heroy,4,4) andando = ExtractAnimSeq(heroy,1,14)anima = 0camera=CreateCamera(heroy)MoveEntity camera, 0, 20,-20RotateEntity camera, 20,0,0light=CreateLight()RotateEntity light, 90,0,0Global cenario=LoadMesh( "midia/cenariobase.3ds" )EntityType cenario, COLIDE_CENARIOGlobal rampa = LoadMesh( "midia/escada.3ds" )EntityType rampa, COLIDE_CENARIOPositionEntity rampa, 9, 0, 18Global textiro = LoadTexture("midia\tiro.png",2)Collisions COLIDE_HEROI, COLIDE_CENARIO, 2, 3Collisions COLIDE_TIRO, COLIDE_CENARIO, 2, 1While Not KeyDown(1)If KeyDown(205) Then TurnEntity CX,0,-1,0

If KeyDown(203) Then TurnEntity CX,0, 1,0If KeyDown(200)MoveEntity CX,0,0, 0.1If Animate = 0 Then Animate heroy, 1, 0.2, andandoAnimate = 1ElseAnimate heroy, 1, 0.2, paradoAnimate = 0EndIfIf KeyHit(57) Then Atirar() Fisica()VY# = VY# - G#If EntityCollided(CX,COLIDE_CENARIO) Then VY# = 0 MoveEntity CX,0,VY#,0UpdateWorldRenderWorldText 0,0,"Use as teclas de cursor para andar"FlipWendEndFunction Atirar()x# = EntityX(CX)y# = EntityY(CX)z# = EntityZ(CX)bala.Ttiro = New Ttirobala\ob = CreateSprite()PositionEntity bala\ob, x#, y#, z#ScaleSprite bala\ob, .2, .2EntityTexture bala\ob, textiroEntityType bala\ob, COLIDE_TIROEntityRadius bala\ob, .2, .2ay# = EntityYaw(CX)RotateEntity bala\ob, 0, ay#, 0End FunctionFunction Fisica()For this.Ttiro = Each TtiroMoveEntity this\ob, 0,0,1If EntityCollided(This\ob,COLIDE_CENARIO) FreeEntity This\obDelete thisEndIfNextEnd Function

O primeiro passo foi criar um tipo de objetos de colisão para os tirosGlobal COLIDE_TIRO = 3

Criamos uma estrutura de Types para os tirosType TtiroField ob End Type

Carregamos uma textura para os tiros.Global textiro = LoadTexture("midia\tiro.png",2)

Acionamos o sistema de verificação de colisões entre os tiros e o cenário.Collisions COLIDE_TIRO, COLIDE_CENARIO, 2, 1

Page 41: jogos 3D

Agora, dentro do loop, quando apertamos a barra de espaço, invocamos a função de disparar tiros.If KeyHit(57) Then Atirar()

De detro do loop também chamamos a função de física, que também controla os tiros.Fisica()

A nossa função criadora de tiros ficou assim:Function Atirar()x# = EntityX(CX)y# = EntityY(CX)z# = EntityZ(CX)bala.Ttiro = New Ttirobala\ob = CreateSprite()PositionEntity bala\ob, x#, y#, z#ScaleSprite bala\ob, .2, .2EntityTexture bala\ob, textiroEntityType bala\ob, COLIDE_TIROEntityRadius bala\ob, .2, .2ay# = EntityYaw(CX)RotateEntity bala\ob, 0, ay#, 0End Function

De inicio, pegamos a posição atual da esfera auxiliar de colisão, afinal é ela que controla o jogo.x# = EntityX(CX)y# = EntityY(CX)z# = EntityZ(CX)

A seguir, criamos o tiro. Veja que o novo tiro é colocado na posição extraída do personagem.bala.Ttiro = New Ttirobala\ob = CreateSprite()PositionEntity bala\ob, x#, y#, z#

Logo após texturizamos com a imagem do tiro.EntityTexture bala\ob, textiro

Depois declaramos a que tipo de objetos de colisão o tiro pertence.EntityType bala\ob, COLIDE_TIRO

Configuramos o raio da esfera de colisões do tiro.EntityRadius bala\ob, .2, .2

O tiro sempre deverá ser dado para à frente do atirador, por isso devemos pegar o ângulo y atual do personagem, no nosso caso da esfera criada para auxiliar na colisão do personagem, e passar para o tiro.ay# = EntityYaw(CX)RotateEntity bala\ob, 0, ay#, 0

Agora nosso tiro está pronto.Toda vez que houver uma iteração do loop de controle do jogo a função de física será chamada. Vamos conferir o seu código.Function Fisica()For this.Ttiro = Each TtiroMoveEntity this\ob, 0,0,1If EntityCollided(This\ob,COLIDE_CENARIO) FreeEntity This\ob

Delete thisEndIfNextEnd Function

Usamos o laço For/Each para passar por todos os objetos ativos do tipo Ttiro.For this.Ttiro = Each Ttiro

Para cada objeto encontrado, movemos o mesmo 1 unidade de distância para sua frente.MoveEntity this\ob, 0,0,1

Se ocorrer uma colisão com o cenário, liberamos o sprite e destruímos o objeto.If EntityCollided(This\ob,COLIDE_CENARIO) FreeEntity This\obDelete thisEndIf

Bem simples não é mesmo? Se tivéssemos inimigos no cenário, também deveríamos ter criado um tipo de entidade inimigo, ativar colisões para esse tipo e verificar se elas ocorreram, para matá-los e destruir o tiro.

Atirando com o modo pickO pickmode é um componente extremamente poderoso do Blitz3d, pois pode nos dar uma série de informações e controle sobre entidades 3d e suas estruturas. Por enquanto, o que nos interessa é sua capacidade de “pegar coisas”, isso é, de secionarmos um objeto 3d dentro de um cenário 3d. Podemos usar essa capacidade de pegar entidades para criar um sistema de tiros super rápido, que se comportará de maneira mais realista que o de emissão de projétil.

O sistema de tiros que criamos no módulo anterior pode ser muito interessante para diversos tipos de jogos, onde poder ver o projétil seja algo importante. Mas em jogos de tiro em primeira pessoa isso é uma limitação. Quando alguém dispara um tiro com uma arma de fogo de verdade, ninguém consegue ver o seu projétil se deslocando pelo espaço.

Por meio do pickmode podemos verificar se uma entidade está na frente da mira quando disparamos um tiro. Se estiver, só nos resta tirar a vida do personagem. Como não necessitamos criar uma entidade e fazer com que ela se desloque pelo cenário, temos um tiro em tempo real, assim como em jogos do tipo Counter Strike e outros.

Page 42: jogos 3D

Nossa primeira aplicação demonstra como funciona o modo de selecionar coisas com o pickmode. São criados vários polígonos 3d, e ao clicar com o botão direito do mouse sobre algum deles, é exibido o número de referência da entidade 3d.

Exemplo 35

Graphics3D 640,480,0,2SetBuffer BackBuffer()

camera=CreateCamera()PositionEntity camera,0,2,-10

luz = CreateLight()

Bola=CreateSphere()EntityPickMode Bola,2 PositionEntity Bola,0,7,0EntityColor Bola, 255,0,0

Cubo=CreateCube()EntityPickMode Cubo,2 PositionEntity Cubo,0,-1,0EntityColor Cubo, 0,255,0

Cone=CreateCone()EntityPickMode Cone,2 PositionEntity Cone,5,3,0EntityColor Cone, 0,0,255

Cil=CreateCylinder()EntityPickMode Cil,2 PositionEntity Cil,-5,3,0EntityColor Cil, 255,0,255

While Not KeyDown( 1 )

If MouseHit(1)=True Then CameraPick(camera,MouseX(),MouseY())

RenderWorldText 10,10,"ENTIDADE: " + PickedEntity() FlipWend

End

A primeira coisa a ser feita é dizer que a entidade é uma entidade que está sobre o controle do pickmode. Assim devemos aplicar o comando EntityPickMode na entidade e dizer como ela deve se comportar.

EntityPickMode Bola,2

Veja que usamos o tipo “2”, que é para se comportar como polígono; abaixo temos a tabela dos tipos:

0: Não é pegável1: Esfera (usa EntityRadius)

2: Polígono (o formato real da entidade) 3: Caixa (usa o EntityBox como referência)

Como usamos o tipo polígono, cada objeto será selecionado de acordo com o seu formato real.

Vejamos abaixo o comando para pegar um objeto:

If MouseHit(1)=True Then CameraPick(camera,MouseX(),MouseY())

O comando para pegar um objeto é CameraPick( ). Perceba que esse comando é disparado quando apertamos o botão esquerdo do mouse:

If MouseHit(1)=True.

Para que esse comando opere, devemos fornecer 3 parâmetros para o mesmo: a câmera na qual desejamos usar o comando, a posição x na tela da câmera e a posição y na tela da câmera. Veja que estamos usando a câmera ativa da aplicação “câmera” e que como coordenadas estamos usando as posições x e y do mouse “MouseX(),MouseY()”.

Ao usarmos a posição atual do mouse, estamos dizendo que vamos selecionar sempre o objeto que estiver sendo apontado pelo mouse, assim o ponteiro do mouse vira um seletor de objetos. Basta clicar com o botão esquerdo dele para selecionar o objeto que estiver sendo apontando.

Por fim, vamos descobrir qual objeto está selecionado. A função PickedEntity() retorna a referência do objeto que foi selecionado. Assim, para descobrir qual objeto foi clicado, basta invocar a mesma.

Text 10,10,"ENTIDADE: " + PickedEntity()

Nosso próximo exemplo é mais prático. Agora que você já sabe como usar o pickmode, vamos deixar o programa um pouco mais complexo e vamos criar uma situação real de jogo. Nesse exemplo criamos 2 inimigos por meio de types dinâmicos e usamos uma mira para servir como referência de tiro de uma arma. Nesse exemplo você vai aprender:

Page 43: jogos 3D

1. como criar um sistema de tiros realista.2. como identificar uma entidade em uma coleção por meio de sua referência.3. como automatizar controle de entidades 3d.

Exemplo 36

Graphics3D 800,600,16,1SetBuffer BackBuffer()

Global camera=CreateCamera()PositionEntity camera,0,10,-50

luz = CreateLight()

Cenario = LoadMesh("midia/cenariobase.3ds")

Type TinimigoField MODELOField NOME$Field VIDA End Type

M3D = LoadAnimMesh("midia/zombie.b3d")

Inimigo.Tinimigo = New TinimigoInimigo\MODELO = M3DEntityPickMode Inimigo\MODELO, 2Inimigo\NOME$ = "Lucy"Inimigo\VIDA = 5TranslateEntity Inimigo\MODELO, 10, 0, 0

Inimigo.Tinimigo = New TinimigoInimigo\MODELO = CopyMesh(M3D)EntityPickMode Inimigo\MODELO, 2Inimigo\NOME$ = "Demon"Inimigo\VIDA = 5TranslateEntity Inimigo\MODELO, -10, 0, 0

Global mira = LoadImage("midia/mira.bmp")Global tiro = LoadSound("midia/tiro.wav")

While Not KeyDown( 1 )

Verificar()

UpdateWorldRenderWorldExibir() FlipWend

End

Function Verificar()PEGOU=0

If MouseHit(1)=True PlaySound tiroCameraPick(camera,MouseX(),MouseY())PEGOU = PickedEntity()EndIf

For This.Tinimigo = Each Tinimigo If This\MODELO = PEGOU Then This\VIDA = This\VIDA - 1

Next End Function

Function Exibir()DrawImage mira, MouseX()-14, MouseY()-14

x = 0 For This.Tinimigo = Each Tinimigo Text 10,10 + x, "NOME: " + This\NOME$Text 10,25 + x, "VIDA: " + This\VIDA x = x + 40 Next End Function

Vamos destrinchar esse aplicativo?

Abaixo estamos criando um type para ser a estrutura dos nossos inimigos. Estamos reservando um campo para o modelo 3d, um campo para o nome do inimigo e um campo para a vida dele. Perceba que uma entidade inimiga é muito mais que um modelo 3d. Um modelo 3d é apenas o corpo dela, sendo que um personagem possui vários dados a serem controlados, por isso a melhor maneira de gerenciá-los é por meio de types.

Type TinimigoField MODELOField NOME$Field VIDA End Type

Logo a seguir carregamos um modelo para ser o nosso personagem.

M3D = LoadAnimMesh("midia/zombie.b3d")

A princípio estamos carregando o mesmo em uma variável para podermos usar a mesma para realizar cópias do modelo.

Inimigo.Tinimigo = New TinimigoInimigo\MODELO = M3DEntityPickMode Inimigo\MODELO, 2Inimigo\NOME$ = "Lucy"Inimigo\VIDA = 5TranslateEntity Inimigo\MODELO, 10, 0, 0

Ai está o modo padrão de criação de um inimigo nesse contexto. Primeiro criamos uma instância do type:

Inimigo.Tinimigo = New Tinimigo

Depois configuramos o modelo 3d desse personagem. Veja que para o primeiro modelo basta fazer uma simples atribuição. Isso evita que o modelo carregado anteriormente fique perdido no cenário.

Inimigo\MODELO = M3D

Page 44: jogos 3D

Depois configuramos o modo pick do mesmo para polígono:

EntityPickMode Inimigo\MODELO, 2

Nosso próximo passo é dar um nome para ele:

Inimigo\NOME$ = "Lucy"

Vamos configura a vida:

Inimigo\VIDA = 5

Por fim, vamos posicionar o inimigo no local correto:

TranslateEntity Inimigo\MODELO, 10, 0, 0

Para criar o segundo modelo usamos praticamente a mesma lógica, mas com uma diferença: não podemos fazer referência para o mesmo modelo. Essa é uma das principais diferenças de um jogo 3d para um jogo 2d do ponto de vista de gerência de entidades. Enquanto em um jogo 2d precisamos ter apenas uma imagem de cada tipo de entidade, num jogo 3d para cada personagem temos que ter um objeto 3d diferente.

A melhor solução para lidar com isso é usar o comando CopyEntity( ), assim não precisaremos carregar várias vezes o mesmo modelo do disco rígido, pois isso poderia ser bem demorado em jogos com vários personagens. Veja como foi feito isso para o segundo zumbi:

Inimigo.Tinimigo = New TinimigoInimigo\MODELO = CopyMesh(M3D)

Dentro do nosso loop de jogo deixamos apenas chamadas genéricas. Não devemos colocar comandos dentro dessa área, a não ser aqueles essenciais como UpdateWorld, RenderWorld e Flip. Recomendo que todas as outras funcionalidades sejam invocadas a partir de funções. Nesse caso, criamos uma função de lógica Verificar( ) e uma função para exibição de dados Exibir( ).

While Not KeyDown( 1 )

Verificar()

UpdateWorldRenderWorld

Exibir() FlipWend

Vamos fazer uma análise detalhada da função lógica. Veja a mesma abaixo:

Function Verificar()PEGOU=0

If MouseHit(1)=True PlaySound tiroCameraPick(camera,MouseX(),MouseY())PEGOU = PickedEntity()EndIf

For This.Tinimigo = Each Tinimigo If This\MODELO = PEGOU Then This\VIDA = This\VIDA - 1 Next End Function

Como a função PickedEntity retorna de forma contínua o ultimo objeto selecionado, ela não pode ser usada diretamente para detecção de tiros, caso contrário ela criaria uma seqüência ininterrupta de eventos de tirar vida. Para gerenciar isso vamos criar uma variável de controle. Veja que a mesma sempre será iniciada na função com o valor 0.

PEGOU=0

Agora vamos aos eventos disparados quando apertamos o botão esquerdo do mouse;

If MouseHit(1)=True

O primeiro deles é emitir um som de tiro:

PlaySound tiro

A seguir disparamos o evento pick, que agora é o mesmo que disparar um tiro:

CameraPick(camera,MouseX(),MouseY())

E por fim, colocamos o objeto selecionado (o zumbi) na variável de controle:

PEGOU = PickedEntity()

Como só colocamos um objeto na variável PEGOU quando o botão do mouse é clicado, essa variável só vai ter conteúdo no momento que esse botão é apertado, pois a cada vez que a função é chamada, essa variável será zerada. Assim só será disparado um único tiro.

Page 45: jogos 3D

Se ao contrário disso, usássemos a função PickedEntity diretamente, sempre indicaria que tem um objeto pego, e isso seria o mesmo que dizer que acertou um tiro o tempo todo. Dessa forma tiraria a vida até que ocorresse outro clique do mouse em outro objeto ou então um clique no vazio.

Vamos agora fazer a verificação de qual inimigo foi atingido e tirar 1 ponto de vida.

For This.Tinimigo = Each Tinimigo If This\MODELO = PEGOU Then This\VIDA = This\VIDA - 1 Next

Veja que para isso usamos um loop do tipo For-Each. Assim verificamos todas as entidades do tipo Tinimigo. Quando o modelo de uma entidade for igual ao modelo 3d presente na variável PEGOU, ai nós tiramos a vida dessa entidade.

Vamos agora a alguns detalhes da nossa função de exibição.

DrawImage mira, MouseX()-14, MouseY()-14

Aqui estamos exibindo a mira da arma. Temos duas considerações importantes a fazer:

1. Exibição de imagens 2d em jogos 3d.2. Ajuste da imagem.

Para exibir uma imagem 2d em um jogo 3d (o que é muito usado para criar interfaces gráficas) deve-se colocar o comando de exibição depois do comando RenderWorld e Antes do Comando Flip, exatamente no meio deles.

Como uma imagem é exibida a partir do ponto ordenado pelo comando DrawImage, para que a posição do mouse fique exatamente no meio da imagem devemos fazer uns ajustes. Nesse caso, a imagem usada tem 28 x 28, assim basta dividir a imagem pela metade:

28 / 2 = 14

E fazer com que o ponto de exibição seja a posição do mouse menos esse metade:

Mousex() – 14

Dessa forma a nossa mira será precisa, pois está centralizada com o mouse.

Controle de ArmasImagine um jogo onde existem dezenas de armas diferentes a disposição. Como poderíamos controlar cada uma delas para que seus movimentos sigam a lógica de animação dos personagens?Muito simples. Basta que as declaremos como objetos filhos da mão do personagem. Como a arma é filha desse membro ela seguirá esse membro e sua animação. Se a mão se mover, a arma se move junto; se rotacionar, rotaciona junto. Assim, podemos carregar vários objetos declarando-os como filho da mão do personagem. Escondemos todas as armas por meio do comando HideEntity e só mostramos a arma que está sendo usada, por meio do comando ShowEntity. Quando selecionamos uma nova arma, essa receberá o comando Show e todas as outras o comando Hide. Como você se lembra, uma entidade que recebe o comando Hide não é renderizada e nem recebe cálculos de colisões, por isso elas não vão comprometer o processamento, vão apenas ocupa espaço de memória.No exemplo abaixo estamos usando um modelo .b3d como bones, então vamos vincular as armas ao osso da mão. Caso você use um modelo sem bones, com animação por keyframes, terá que vincular as armas à mão ou ao braço. Normalmente modelos animados com bones não possuem partes no corpo, pois são feitos a partir de um único objeto que sofre extrusão. Os modelos com animação por keyframe normalmente são constituídos de diversas partes, pois só podem ser animados por meio de rotação e translação.Vamos ao Exemplo.

Page 46: jogos 3D

Exemplo 37 Graphics3D 1280, 1024SetBuffer BackBuffer()cam = CreateCamera()PositionEntity cam, 0,20,-20RotateEntity cam, 30,0,0 zumbi = LoadAnimMesh("midia\zombie.b3d")PositionEntity zumbi, 0,-2,0RotateEntity zumbi, 0, 90, 0anima = ExtractAnimSeq(zumbi,2,20)Animate zumbi,1,.2,anima;PEGANDO O BONE DA MÃOMao = FindChild(zumbi,"Joint13");ARMA1bastao = LoadMesh("midia\bastao.3ds", mao)tbastao = LoadTexture("midia\w_bfg.pcx")EntityTexture bastao, tbastaoScaleEntity bastao, .3, .3, .3MoveEntity bastao, -.5, -1, 1RotateEntity bastao, 0, -90, 0HideEntity bastao;ARMA2espada = LoadMD2("midia\w_blaster.md2", mao)tespada =LoadTexture("midia\w_blaster.pcx")EntityTexture espada, tespadaScaleEntity espada, .3, .3, .3MoveEntity espada, -2.5, -2, .5RotateEntity espada, 0, 180, 0HideEntity espada;ARMA3pistola = LoadMesh("midia\pistol.x", mao)MoveEntity pistola, -.5, -1.5, .5RotateEntity pistola, -80, 0, 0ARMA = 3While Not KeyHit(1)TurnEntity zumbi, 0, 0.1, 0If KeyHit(57)ARMA = ARMA + 1If ARMA = 1 ShowEntity bastaoHideEntity espadaHideEntity pistola

EndIfIf ARMA = 2 ShowEntity espadaHideEntity bastao HideEntity pistolaEndIfIf ARMA = 3 ShowEntity pistolaHideEntity bastaoHideEntity espada EndIfIf ARMA = 4 ARMA = 0HideEntity bastaoHideEntity espadaHideEntity pistolaEndIfEndIf UpdateWorldRenderWorldText 0,0, "PRESSIONE SPACE PARA MUDAR A ARMA"Text 0,20, "ARMA ATUAL: " + ARMAFlipWend

Estamos usando como personagem principal o modelo zumbi, disponível no pacote do MilkShape3D.zumbi = LoadAnimMesh("midia\zombie.b3d")

A primeira coisa que devemos fazer é pegar a identidade do bone da mão e guardar em uma variável. Já sabíamos previamente qual era o nome desse bone, pois abrimos o modelo no modelador MilkShape3d para verificar isso. Quando você faz um modelo, você pode dar nomes a cada malha e a cada bone.Mao = FindChild(zumbi,"Joint13")

Agora que já temos a identidade do bone da mão, é só carregar as armas como filhas desse junta. Veja que colocamos a variável do bone como segundo parâmetro da função de carregar as armas, para que elas sejam filhas dessa entidade. Como elas são filhas, vão herdar o mesmo comportamento da mão, isso é, vão acompanhar a sua animação.bastao = LoadMesh("midia\

bastao.3ds",mao)Precisamos fazer alguns ajustes na posição da arma para que ela fique perfeitamente encaixada na mão do personagem.ScaleEntity bastao, .3, .3, .3MoveEntity bastao, -.5, -1, 1RotateEntity bastao, 0, -90, 0

Escondemos a arma com o comando hide.HideEntity bastao

Abaixo temo o sistema de controle das armas dentro do loop do jogo:If KeyHit(57)ARMA = ARMA + 1If ARMA = 1 ShowEntity bastao

Page 47: jogos 3D

HideEntity espadaHideEntity pistolaEndIf

Se a tecla space é pressionada, incrementamos em uma unidade a variável de controle de arma atual. Depois verificamos qual é a arma ativa. Exibimos a arma ativa e escondemos as outras.

Controle do ProcessoA construção de um jogo 3d deve ser cuidadosamente elaborando, principalmente no quesito performance. Do que vale um jogo que quase ninguém pode jogar? Por isso devemos estabelecer meios para gerenciar a demanda de processamento de um jogo para que ele não fuja das especificações.Duas coisas importantes a gerenciar são a quantidade de polígonos renderizados e o frame rate. Por meio da quantidade de polígonos renderizados, nós podemos controlar a complexidade de um jogo, seus mapas e cenários, podendo customizar o mesmo de acordo com as necessidades especificadas. O frame é a quantidade de quadros que a enigne consegue renderizar por segundo. Assim, um frame rate de 40 significa que o computador consegue exibir quarenta quadros por segundo. Para que um jogo tenha um efeito de animação suave tem que sustentar um frame rate de no mínimo 25 quadros por segundo. O ideal é que essa taxa alcance 50 para que o efeito de animação seja perfeito.O Blitz3D possui um comando interno para informa o número de triângulos renderizados, é o comando TrisRendered(), mas não possui um comando para frame rate.No exemplo abaixo criamos um algoritmo para informar o frame rate. Pressione a barra de espaços para ir adicionando novos objetos aleatoriamente.

Exemplo 38Graphics3D 800, 600SetBuffer BackBuffer()Type TbolaField obEnd Typecam = CreateCamera()luz = CreateLight()x = 0While Not KeyHit(1)If KeyHit(57)px = Rnd(-10,10)py = Rnd(-10,10)pz = Rnd(10, 30)

bola.Tbola = New Tbolabola\ob = CreateSphere(32)PositionEntity bola\ob, px, py, pzEntityColor bola\ob, Rnd(0,255), Rnd(0,255), Rnd(0,255)EndIf UpdateWorldRenderWorldText 0,0, "PRESSIONE SPACE PARA ADICIONAR OBJETOS"Text 0,20,"TRIANGULOS RENDERIZADOS: " + TrisRendered()Text 0,40,"FRAME RATE -> " + FPSRealFlip 0If Timer + 1000 <= MilliSecs() Timer = MilliSecs()FPSReal = FPSTempFPSTemp = 0EndIfFPSTemp = FPSTemp + 1Wend

Fóra a primeira verificação, cada verificação será realizada a cada 1 segundo, graças à linha abaixo.If Timer + 1000 <= MilliSecs()

O comando Millisecs retorna o tempo do sistema em milisegundos. Mil milisegundos é igual a um segundo. A variável Timer contem uma verificação de milisegundos gravada. Enquanto a nova verificação não for maior que Timer+1000, isso é, enquanto não se passar 1 segundo, os dados não serão atualizados.Timer = MilliSecs()

Caso se passe um segundo, pegamos o tempo atual e colocamos na variável para atualizar a contagem.FPSReal = FPSTemp

Pegamos o frame rate da variável temporária e colocamos na variável de informação.FPSTemp = 0

Reiniciamos a variável contadora de frame rate.FPSTemp = FPSTemp + 1

O frame rate é obtido por simples contador. A cada iteração do loop, uma unidade é incrementada. Quando transcorrer um segundo, esse resultado será transferido para a variável FPSReal, que agora conterá a quantidade de iterações do loop no ciclo, isso é o Frame Rate.Para que o programa possa ter frame rate livre devemos colocar a linha abaixo.Flip 0

O comando flip serve para atualizar a tela, e quando não damos nenhum valor a ele, sua velocidade será igual a velocidade de sincronismo da tela do monitor, que e entre 60 e 70. Para deixarmos com velocidade livre, colocamos o parâmetro 0.

Page 48: jogos 3D

Gostou do programa? Quantos polígonos seu computador suportou dentro da faixa de frame rate 25? O meu chegou a 1.341.184. Mas é lógico que meu computador não é um padrão de mercado de jogos...O ideal é que você teste seus jogos em 3 computadores. Um para baixa performance, um de hardware mediano e um para máquinas de alta performance. Seu jogo também deverá ter configurações que permitam ao usuário rodar o jogo pelo menos nessas três configurações.SkyDomeEm jogos de ambiente fechado existem paredes por todos os lados. Mas em jogos com cenário em ambientes externos devemos dar um jeitinho para colocar objetos de fundo para representar o céu e nuvens.Normalmente são utilizadas duas técnicas: skybox e skydome. O skybox é uma caixa que é texturizada para representar o céu. O problema do skybox é que por ser quadrado não dá uma impressão de perfeição. Ele era usado antigamente devido ao baixo poder de processamento das máquinas, mas hoje em dia isso não mais é problema.O skydome, ou skyesphere é uma esfera que é mapeada com uma textura de céu e sofre uma inversão de suas faces.O exemplo abaixo demonstra como criar um skydome.

Exemplo 39Graphics3D 640,480SetBuffer BackBuffer()camera=CreateCamera()luz=CreateLight()sky=CreateSphere()ScaleEntity sky,100,100,100tex=LoadTexture("midia/sky.bmp")EntityTexture sky, texFlipMesh skyWhile Not KeyDown( 1 )

TurnEntity sky, 0, 0.2, 0RenderWorldFlipWendEnd

O primeiro passo foi criar uma esfera e aumentar o tamanho dela.sky=CreateSphere()ScaleEntity sky,100,100,100

Depois disso, carregamos uma textura que possua uma imagem de céu e aplicamos sobre a esfera.tex=LoadTexture("midia/sky.bmp")EntityTexture sky, tex

O passo mágico é inverter o modelo 3d, fazemos isso com o comando FlipMesh.FlipMesh sky

Para dar um efeito de maior realismo, nós estamos rotacionando o skydome dentro do loop.TurnEntity sky, 0, 0.2, 0

Muito fácil!Empacotando o JogoPara distribuir um jogo comercialmente você precisa proteger suas mídias para que ninguém os utilize indevidamente. Você pode fazer isso via código ou por meio de ferramentas próprias para isso.Vamos indicar duas boas ferramentas free para isso. Ambas podem ser encontradas no site http://www.blitztools.de.vu.Blitz Media-LinkerEssa ferramenta cria um Stand-Alone-Executable, isso é, deixa o executável e as mídias todas em um só arquivo. Também dá a opção de colocar todos os recursos de mídia em apenas um único arquivo de dados, assim seu projeto tem apenas um executável e um arquivo de dados. É muito fácil de usar e dispensa explicações.BBCruncherEsse programa é bem mais versátil, possuindo várias funcionalidade e possível de ser usando em qualquer programa da família Blitz ou até mesmo com o Pure Basic.