Vetores & Física :: Gamedev receitas Panda3D

Baseado nos textos de Daniel Shiffman (http://www.shiffman.net/teaching/nature/)

Update – (26/10/2011):
Outros bons links com textos sobre vetores e jogos:
– Uso de vetores em jogos, por Vinícius Godoy de Mendonça; PontoV
– Uma breve explicação sobre vetores, por gamedevbrasil

Para realizar simulações de movimento em jogos, basicamente, basta fazer alterações das coordenadas de um dado objeto durante os ciclos de execução do programa, dessa forma então, cria-se a ilusão de animação.
Existem algumas formas clássicas para essa alteração de coordenadas:
– Apenas mudar os valores de coordenadas;
– Usar vetores;
– Simular cinemática;
– Simular Física;

Essas formas foram elencadas acima, com o intuito de criar uma espécia de linha evolutiva de complexidade. Isso não que dizer que a mais complexa é a mais útil que as demais, isso varia de projeto para projeto. Porém, é certo que as bases de um dos itens, influem sobre o seguinte.

Exemplo 1:

[code lang=”python”]
from direct.showbase.ShowBase import ShowBase
from direct.task import Task

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)
self.taskMgr.add(self.funcaoDeTarefa, “Tarefa de teste”)
self.cubo = self.loader.loadModel(“box”);
self.cubo.reparentTo(self.render)
self.cubo.setScale(1)
self.cubo.setPos(-3,10,-2)

def funcaoDeTarefa(self, task):
print “A posicao do cubo e: “, self.cubo.getPos()
self.cubo.setX(self.cubo.getX()+0.01)
return Task.cont

app = MyApp()
app.run()
[/code]

Nesse exemplo, temos uma simples variação de valor de coordenadas do cubo. Observe que na função de tarefa (funcaoDeTarefa) o valor da coordenada ‘x’ do cubo é incrementada em 0.01 a cada execução da tarefa.

Exemplo 2:

[code lang=”python”]

from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from pandac.PandaModules import Vec3

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

self.vector_left = Vec3(-0.01,0, 0)
self.vector_right= Vec3( 0.01,0, 0)
self.vector_up = Vec3( 0 ,0, 0.01)
self.vector_down = Vec3( 0 ,0,-0.01)
self.main_vec = Vec3(0,0,0)

self.taskMgr.add(self.funcaoDeTarefa, “Tarefa de teste”)
self.cubo = self.loader.loadModel(“box”);
self.cubo.reparentTo(self.render)
self.cubo.setScale(1)
self.cubo.setPos(-3,10,-2)

self.accept(‘d’, self.changeDirection, [self.vector_right])
self.accept(‘a’, self.changeDirection, [self.vector_left])
self.accept(‘w’, self.changeDirection, [self.vector_up])
self.accept(‘s’, self.changeDirection, [self.vector_down])

def changeDirection(self, direction):
self.main_vec = direction

def funcaoDeTarefa(self, task):
self.cubo.setPos(self.cubo.getPos()+self.main_vec)
return Task.cont

app = MyApp()
app.run()

[/code]

Neste segundo exemplo temos o uso de vetores para o deslocamento de um objeto.
Vejamos ver o uso dos vetores mais detalhadamente.

Vetores: ========================================
referências:

Para animar um objeto em um espaço virtual, precisamos de uma maneira de armazenar a informação sobre a localização do objeto, movimento, etc. Num espaço de duas dimensões (como a nossa tela de computador), a localização é expressa por coordenadas X e Y – em um espaço de três dimensões, a localização é expressa por coordenadas X, Y e Z – Este par (ou trio) de informação é o elemento essencial para os sistemas que criaremos. Teremos “pontos” e “vetores”. Um ponto representa um local e um vetor representa o deslocamento entre dois pontos.

Considere um retângulo que se desloca através da tela. Em um dado instante tem a seguinte descrição:
[quote]
Posição – a localização do objeto, expressa em duas dimensões como um ponto.
Velocidade – a taxa na qual objeto muda de posição por unidade de tempo, expressa em duas dimensões do vetor.
Aceleração – a taxa na qual objeto muda a velocidade por unidade de tempo, expressa como um vetor de duas dimensões.
[/quote]
Obs.: Caso a descrição do espaço seja em três dimensões, os pontos e vetores envolvidos, devem ter também três dimensões.

Tecnicamente, a posição é um ponto; velocidade / aceleração são vetores. Para nossos propósitos, entretanto, vamos considerar todos os três como vetores para simplificar a nosso código. Examine o seguinte pseudo código. Suponha três vetores: aceleração, velocidade e localização. Este algoritmo simples será a base para os nossos exemplos.

[quote]
velocidade = velocidade + aceleração
Localização = localização + velocidade
Desenha a coisa na localização
[/quote]

Uma vez que vetores representam pares de valores, não podemos simplesmente usar a adição ou multiplicação tradicional. Em vez disso, precisaremos fazer operações com vetores.

Felizmente as game engines nos fornecem comandos para simplificar essas operações.

Então na prática, podemos dizer que um vetor armazena informações de:
– posicionamento (coordenadas)
– direção
– intensidade (comprimento)

Todas essas informações, e operações que os vetores nos fornecem, fazem os vetores serem tão populares no desenvolvimento de jogos; Pois são muito úteis e práticos para as simulações, sejam simulações de deslocamento de objetos, ou simulações de física, ou ainda, em simulações de comportamentos, por que vetores são formas de expressar matematicamente movimentos. Como dito anteriormente, eles armazenam informações de direção, intensidade (comprimento do vetor) e posicionamento; Como se não o bastasse, existem as chamadas “operações vetoriais” que são operações matemáticas feitas com vetores, e as mesmas são muito úteis.

Talvês o exemplo mais comum seja a subtração de vetores. Podemos usá-la para fazer com que um objeto siga outro (Para maiores detalhes matemáticos sobre subtração de vetores, veja os links selecionados nas referências.), a mágica acontesse porque, quando subtraímos um vetor A, por um vetor B, o resultado é um vetor C, que parte do vetor A, até o vetor B, assim temos no vetor C, a direção, e a distância entre A e B.

Observe o exemplo abaixo. Temos uma esfera que percorre o caminho entre os cubos. Todo esse trabalho acontesse na função update da esfera.

[code lang=”python”]
from pandac.PandaModules import loadPrcFileData
loadPrcFileData(”,’show-frame-rate-meter 1′)

from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
from direct.task import Task
from pandac.PandaModules import *

class Cubo(DirectObject):
loc = Vec3(0,0,0)
vel = Vec3(0,0,0)
acc = Vec3(0,0,0)
mass = 1;
maxVel = 1;

def __init__(self, pos):
self.loc = pos
self.carregaModelos()

def carregaModelos(self):
self.cubo = loader.loadModel(‘box’)
self.cubo.setPos(self.loc)
self.cubo.reparentTo(render)

# Class smiley ————————————
class Smiley(DirectObject):
loc = Vec3(0,0,1)
vel = Vec3(0,0,0)
acc = Vec3(0,0,0)
mass = 1
maxForca = 0.1

def __init__(self, pathToFollow):
self.carregaModelos()
self.way = pathToFollow
self.nextIt = 1;
self.next = self.way[1].loc
print “next pos: “, self.next

def carregaModelos(self):
self.smiley = loader.loadModel(‘smiley’)
self.smiley.setPos(self.loc)
self.smiley.reparentTo(render)

def update(self):
target = self.next
direction = target – self.loc
distancia = direction.length()
direction.normalize()
direction *= self.maxForca

self.loc += direction
if(distancia = len(self.way) ):
self.nextIt=0
self.next = self.way[self.nextIt].loc
self.smiley.setPos(self.loc)

#Classe do jogo —————————————
class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

self.box0 = Cubo(Vec3(0,0,1))
self.box1 = Cubo(Vec3(10,0,1))
self.box2 = Cubo(Vec3(5,10,1))
self.box3 = Cubo(Vec3(-10,10,1))
self.box4 = Cubo(Vec3(-10,-5,1))
self.box5 = Cubo(Vec3(0,-10,1))

self.wayPoints = (self.box0, self.box1, self.box2, self.box3, self.box4, self.box5)

self.smileyForca = 0.5
self.smiley = Smiley(self.wayPoints)

#Codigo para o piso
self.cm = CardMaker(“ground”)
self.cm.setFrame(-20, 20, -20, 20)
self.ground = render.attachNewNode(self.cm.generate())
self.ground.setPos(0, 0, 0)
self.ground.lookAt(0, 0, -1)

self.taskMgr.doMethodLater(4, self.update, “update task”)

# Set the camera position
base.disableMouse()
base.camera.setPos(15, -30, 20)
base.camera.lookAt(0, 0, 0)

self.fiatLux()

def update(self, task):
self.smiley.update()
return Task.cont

def fiatLux(self):

# Directional light 01
self.directionalLight = DirectionalLight(‘directionalLight’)
self.directionalLight.setColor(Vec4(0.95, 0.95, 0.8, 1))
self.directionalLightNP = render.attachNewNode(self.directionalLight)
# This light is facing backwards, towards the camera.
self.directionalLightNP.setHpr(45, -15, 0)
render.setLight(self.directionalLightNP)

game = MyApp()

run()
[/code]

CINEMÁTICA ===========================================
Para começar vejamos uma simulação de cinemática. Cinemática é a um ramo da física clássica que se ocupa da descrição dos movimentos dos corpos, sem se preocupar com a análise de suas causas.

Exemplo 3:
[code lang=”python”]
from pandac.PandaModules import loadPrcFileData
loadPrcFileData(”,’show-frame-rate-meter 1′)

from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
from direct.task import Task
from pandac.PandaModules import Vec3

class Cubo(DirectObject):
loc = Vec3(0,30,0)
vel = Vec3(0,0,0)
acc = Vec3(0,0,0)

def __init__(self):
self.carregaModelos()

def carregaModelos(self):
self.cubo = loader.loadModel(‘box’)
self.cubo.setPos(self.loc)
self.cubo.reparentTo(render)

def update(self):
self.vel += self.acc
self.loc += self.vel
self.cubo.setPos(self.loc)
self.acc *= 0

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

self.vector_left = Vec3(-0.01,0, 0)
self.vector_right= Vec3( 0.01,0, 0)
self.vector_up = Vec3( 0 ,0, 0.01)
self.vector_down = Vec3( 0 ,0,-0.01)

self.taskMgr.add(self.funcaoDeTarefa, “Tarefa de teste”)
self.box = Cubo()

self.accept(‘d’, self.changeDirection, [self.vector_right])
self.accept(‘a’, self.changeDirection, [self.vector_left])
self.accept(‘w’, self.changeDirection, [self.vector_up])
self.accept(‘s’, self.changeDirection, [self.vector_down])

def changeDirection(self, direction):
self.box.acc += direction

def funcaoDeTarefa(self, task):
self.box.update()
return Task.cont

app = MyApp()
app.run()
[/code]

Neste exemplo, temos duas classes, a classe do Cubo, e outra da aplicação. A classe Cubo, possui as informações para o comportamento do cubo, os vetores de localização, velocidade e aceleração, e a rotina de atualização (descrita anteriormente) :

[quote]
velocidade = velocidade + aceleração
Localização = localização + velocidade
Desenha a coisa na localização
aceleração = aceleração * 0
[/quote]

Essa pequena rotina, é descrita pelas regras de cinemática, e usada na aplicação da segunda lei de Newton. Basicamente, a aceleração é o vetor que direciona o objeto. Quando a aceleração muda de direção, com o tempo, o objeto também muda de direção.

Depois de observada as bases da cinemática, temos duas formas de aplicações que derivam das mesmas bases: Física e comportamentos de movimentos(Inteligência Artificial para Games.)

Física ========================================

É plenamente possível realizar os cálculos necessários para simulação de física, porém esse é um tópico de alta complexidade. E para simplificar o trabalho, existem softwares especializados nesses cálculos, são as chamadas “engines de física”. No Panda3D, a mais usada é a ODE.
Essas engines de física, realizão os cálculos de simulação de forças, colisões, “juntas”, e outras fórmulas de física mecânica, e já atualizam a exibição dos objetos (só para Unity3D. No Panda3D é necessário colocar a atualização dos dados em uma tarefa ).

Exemplo 4:
[code lang=”python”]

from pandac.PandaModules import loadPrcFileData
loadPrcFileData(”,’show-frame-rate-meter 1′)

from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
from direct.task import Task
from pandac.PandaModules import *

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

self.fiatLux()

#Cria o mundo de fisica
self.world = OdeWorld()
self.world.setGravity(0,0,-9.81)

#estabelece configuracoes de reacao
self.world.initSurfaceTable(1)
self.world.setSurfaceEntry(0, 0, 150, 0.0, 9.1, 0.9, 0.00001, 0.0, 0.002)

#cria um ‘espaco’ para colisoes automaticas
self.space = OdeSimpleSpace()
self.space.setAutoCollideWorld(self.world)
self.contactgroup = OdeJointGroup()
self.space.setAutoCollideJointGroup(self.contactgroup)

#Codigo para o piso
self.cm = CardMaker(“ground”)
self.cm.setFrame(-20, 20, -20, 20)
self.ground = render.attachNewNode(self.cm.generate())
self.ground.setPos(0, 0, 0); self.ground.lookAt(0, 0, -1)
self.groundGeom = OdePlaneGeom(self.space, Vec4(0, 0, 1, 0))

#cria renderizacao de um cubo
self.box = loader.loadModel(‘../assets/eggs/box_c.egg’)
self.box.reparentTo(render)
self.box.setPos(0,9.5,7)
self.box.setHpr(5,35,57)
#self.box.setScale(1)
self.box.setColor(0.5, 0.5, 0.5, 1)

#cria um ‘body’ para representar o cubo no universo de fisica
self.boxBody = OdeBody(self.world)
self.boxMass = OdeMass()
self.boxMass.setBox(100,1,1,1)
self.boxBody.setMass(self.boxMass)
self.boxBody.setPosition(self.box.getPos(render))
print self.box.getQuat(render)
self.boxBody.setQuaternion(self.box.getQuat(render))
print self.boxBody.getQuaternion()
#cria um solido de colisao para realizar as colisoes no universo de fisica
self.boxGeom = OdeBoxGeom(self.space, 2,2,2)
self.boxGeom.setBody(self.boxBody)

# Set the camera position
base.disableMouse()
base.camera.setPos(40, 40, 20)
base.camera.lookAt(0, 0, 0)

self.taskMgr.doMethodLater(4, self.update, “update task”)

def update(self, task):
#obrigatorio
self.space.autoCollide()
self.world.quickStep(globalClock.getDt())

#atualiza a renderizacao do cubo, com base nas atualizacoes da fisica
self.box.setPosQuat(render, self.boxBody.getPosition(), Quat(self.boxBody.getQuaternion()))

#obrigatorio qdo houver colisoes
self.contactgroup.empty()
return Task.cont

def fiatLux(self):

# Directional light 01
self.directionalLight = DirectionalLight(‘directionalLight’)
self.directionalLight.setColor(Vec4(0.95, 0.95, 0.8, 1))
self.directionalLightNP = render.attachNewNode(self.directionalLight)
# This light is facing backwards, towards the camera.
self.directionalLightNP.setHpr(45, -15, 0)
render.setLight(self.directionalLightNP)

game = MyApp()

run()

[/code]

No exemplo acima, temos uma cena bem simples onde um cubo, cai sobre um plano. O código exibe a forma básica de configuração de elementos de jogo, com a integração de simulações realizadas pela engine de física. No Panda3D, a engine ODE é a padrão, mas nada impede de utilizar outras; No Unity3D, a engine padrão é a Physx.

Além de simular a força da gravidade agindo sobre objetos, podemos também fazer com que “forças” atuem sobre nosso objeto, como acontece em situações como: um chute em uma bola; um arremesso de uma pedra; o impacto de uma explosão, etc. Veja a aplicação de uma força simples:

[code lang=”python”]

from pandac.PandaModules import loadPrcFileData
loadPrcFileData(”,’show-frame-rate-meter 1′)

from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
from direct.task import Task
from pandac.PandaModules import *

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

self.fiatLux()

#Cria o mundo de fisica
self.world = OdeWorld()
self.world.setGravity(0,0,-9.81)

#estabelece configuracoes de reacao
self.world.initSurfaceTable(1)
self.world.setSurfaceEntry(0, 0, 150, 0.0, 9.1, 0.9, 0.00001, 0.0, 0.002)

#cria um ‘espaco’ para colisoes automaticas
self.space = OdeSimpleSpace()
self.space.setAutoCollideWorld(self.world)
self.contactgroup = OdeJointGroup()
self.space.setAutoCollideJointGroup(self.contactgroup)

#Codigo para o piso
self.cm = CardMaker(“ground”)
self.cm.setFrame(-20, 20, -20, 20)
self.ground = render.attachNewNode(self.cm.generate())
self.ground.setPos(0, 0, 0); self.ground.lookAt(0, 0, -1)
self.groundGeom = OdePlaneGeom(self.space, Vec4(0, 0, 1, 0))

#cria renderizacao de um cubo
self.box = loader.loadModel(‘../assets/eggs/box_c.egg’)
self.box.reparentTo(render)
self.box.setPos(0,9.5,7)
self.box.setHpr(5,35,57)
#self.box.setScale(1)
self.box.setColor(0.5, 0.5, 0.5, 1)

#cria um ‘body’ para representar o cubo no universo de fisica
self.boxBody = OdeBody(self.world)
self.boxMass = OdeMass()
self.boxMass.setBox(100,1,1,1)
self.boxBody.setMass(self.boxMass)
self.boxBody.setPosition(self.box.getPos(render))
self.boxBody.setQuaternion(self.box.getQuat(render))
#cria um solido de colisao para realizar as colisoes no universo de fisica
self.boxGeom = OdeBoxGeom(self.space, 2,2,2)
self.boxGeom.setBody(self.boxBody)

# Set the camera position
base.disableMouse()
base.camera.setPos(40, 40, 20)
base.camera.lookAt(0, 0, 0)

#captura entrada de teclado
self.accept(‘f’, self.aplicaForca)

self.taskMgr.doMethodLater(4, self.update, “update task”)

def update(self, task):
#obrigatorio
self.space.autoCollide()
self.world.quickStep(globalClock.getDt())

#atualiza a renderizacao do cubo, com base nas atualizacoes da fisica
self.box.setPosQuat(render, self.boxBody.getPosition(), Quat(self.boxBody.getQuaternion()))

#obrigatorio qdo houver colisoes
self.contactgroup.empty()
return Task.cont

def fiatLux(self):

# Directional light 01
self.directionalLight = DirectionalLight(‘directionalLight’)
self.directionalLight.setColor(Vec4(0.95, 0.95, 0.8, 1))
self.directionalLightNP = render.attachNewNode(self.directionalLight)
# This light is facing backwards, towards the camera.
self.directionalLightNP.setHpr(45, -15, 0)
render.setLight(self.directionalLightNP)

def aplicaForca(self):
print “aplica forca!”
print self.boxBody.getMass()
self.boxBody.setForce(100000,0,0)

game = MyApp()

run()

[/code]

Arquivos para tutorial Panda3D

Abaixo você vai achar os arquivos para os tutoriais de Panda3D

Panda-project (15Mb formato .zip)

Organização dos arquivos.

Para que os arquivos de código e os arquivos de assets (modelos, animações e texturas) funcionem como o esperado, é necessário que eles sejam organizados de forma estruturada, seguindo o seguinte esquema:

Panda3D_projectFolder
Neste esquema, a pasta principal do projeto deve ser a pasta Panda-project.

Como “filhas” da pasta Panda-project, temos as pastas assets e src. A pasta assets, irá abrigar os arquivos de arte para o projeto, ou seja, os modelos, texturas, animações, etc. Para tanto a pasta assets possui outras três subpastas eggs, som e texturas.

A pasta src (src refere-se a source) é onde devemos salvar os arquivo e código, que vc encontrará durante os tutoriais, sempre salvar com extensão .py.

Colisões :: Gamedev receitas Panda3D

Detetar colisões entre objetos é uma prática fundamental para (quase) todo jogo eletrônico.
Além de detetar as colisões, é importante saber o que fazer, depois que elas ocorrem.
As game engines, sempre possuem recursos para a deteção de colisões, e esse é um tópico muito importante de ser observado, antes de escolher qual game engine utilizar para seu projeto.

Em linhas gerais, os pontos básicos a serem observados são:
– como se configura a deteção de colisão;
– quais geometrias de colisão (esferas, cubos, planos …) são disponibilizados pela engine;
– Se a game engine, permite deteção de colisão com geometria 3D (mesh).

Quando as colisões são detetadas, em geral, eventos são disparados pelo sistema. E esses eventos, podem ser capturados por objetos de jogo; que iniciam funções, para gerar a dinâmica do jogo.

exemplo 1:

[code lang=”python”]
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from pandac.PandaModules import *

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

# Cria o Traverser e handlers: primeiro passo
base.cTrav = CollisionTraverser()
base.pusher = CollisionHandlerPusher()
base.pusher.addInPattern(‘%fn-into-%in’)

self.cubo = self.loader.loadModel(“box”);
self.cubo.reparentTo(self.render)
self.cubo.setScale(1)
self.cubo.setPos(-3,10,-2)

# Cria colliders: segundo passo
self.cuboCEsfera = CollisionNode(“cuboCEsfera”)
self.cuboCEsfera.addSolid( CollisionSphere(0,0,0, 1.05) )
self.cuboEsfNP = self.cubo.attachNewNode(self.cuboCEsfera)
self.cuboEsfNP.show()

# Apontar ao Traverser e ao Handler, quais objetos devem ser observados ao colidir, e como tratar as colisões entre eles : terceiro passo
base.cTrav.addCollider( self.cuboEsfNP, base.pusher )
base.pusher.addCollider( self.cuboEsfNP, self.cubo )

self.smiley = loader.loadModel(‘smiley’)
self.smiley.reparentTo(self.render)
self.smiley.setScale(0.5);
self.smiley.setPos(3, 10, -1.5)

self.smileyCEsfera = CollisionNode(“smileyCEsfera”)
self.smileyCEsfera.addSolid( CollisionSphere(0,0,0, 1.05) )
self.smileyEsfNP = self.smiley.attachNewNode(self.smileyCEsfera)
self.smileyEsfNP.show()

self.taskMgr.add(self.funcaoDeTarefa, “Tarefa de teste”)
self.accept(‘cuboCEsfera-into-smileyCEsfera’, self.eventoColisao)

def eventoColisao(self, entrada):
print “ocorreu choque entre objetos!!”
print entrada

def funcaoDeTarefa(self, task):
self.cubo.setX(self.cubo.getX()+0.05)
return Task.cont

app = MyApp()
app.run()
[/code]

Neste exemplo, realizamos uma deteção de colisão simples, com utilização de um “handler pusher” e geração de um evento no momento da colisão.
No Panda3D é necessário observar três pontos importantes:
1- A criação de um CollisionTraverser e um CollisionHandler;
2- A criação de: CollisionNode (nós de colisão) e CollisionSolid (sólidos de colisão);
3- A anexação dos CollisionNodes e CollisionSolid, para CollisionTraverser e CollisionHandler.

Etapas do código acima:

Passo 1:

# Cria o Traverser e handlers: primeiro passo
base.cTrav = CollisionTraverser()
base.pusher = CollisionHandlerPusher()
base.pusher.addInPattern(‘%fn-into-%in’)

Primeiramente, o CollisionTraveser foi criado. Posteriormente, o CollisionHandler foi criado como um Pusher (o Pusher não permite que objetos transpassem uns aos outros). Na terceira linha, adicionamos ao “Pusher”, a ordem de geração de eventos no momento das colisões.

Passo 2:

# Cria colliders: segundo passo
self.cuboCEsfera = CollisionNode(“cuboCEsfera”)
self.cuboCEsfera.addSolid( CollisionSphere(0,0,0, 1.05) )
self.cuboEsfNP = self.cubo.attachNewNode(self.cuboCEsfera)
self.cuboEsfNP.show()

Este passo é um pouco mais complicado.
Literalmente o código diz:
– crie um nó para colisões chamado “cuboCEsfera”, e guarde o caminho para ele na variável self.cuboCEsfera;
– adicione uma esfera de colisão, em “cuboCEsfera”.
– adicione ao nó self.cubo, o nó guardado na variável self.cuboCEsfera, e guarde o caminho na variável self.cuboEsfNP.
– mostre na tela a esfera de colisão.

Esses comandos são obrigatórios. Exceto o self.cuboEsfNP.show(), que serve apenas para uma pré-visualização.

Observe que esses passos se repetem a cada objeto onde que tem que interagir por colisão. Por exemplo, no objeto smiley, também criamos uma esfera de colisão.

Passo 3:

# Apontar ao Traverser e ao Handler, quais objetos devem ser observados ao colidir, e como tratar as colisões entre eles : terceiro passo
base.cTrav.addCollider( self.cuboEsfNP, base.pusher )
base.pusher.addCollider( self.cuboEsfNP, self.cubo )

Após definir cada nó de colisão (CollisionNode, passos 1 e 2), é necessário informar, como o sistema deve lidar com as colisões.
O comando : base.cTrav.addCollider( self.cuboEsfNP, base.pusher ), diz ao Traverser que as colisões envolvendo o nó self.cuboEsfNP, devem ser tratadas pelo handler “Pusher”.
O comando: base.pusher.addCollider( self.cuboEsfNP, self.cubo ), diz ao “Pusher”, ao se detectar colisões com o nó self.cuboEsfNP, o comportamento do pusher, deve ser aplicado ao nó self.cubo.

Este exemplo é bom para se usar em situações onde um objeto não pode atravessar outros objetos, como um personagem não atravessar uma parede.

Os três passos vistos até aqui, mostram a configuração básica para essa colisão, que constituem os passos principais para se configurar colisões no Panda3D. Agora, vejamos um detalhe interessante, a possibilidade de geração de eventos para cada colisão.

Quando criamos o Handler Pusher (passo 1), observe o comando: base.pusher.addInPattern(‘%fn-into-%in’). Este comando diz ao Pusher observar quando um objeto colidir com outro, e quando as colisões acontecerem, ele dispara eventos pelo sistema.

Observe a linha:
self.accept(‘cuboCEsfera-into-smileyCEsfera’, self.eventoColisao)
Esta linha é um comando de captura de eventos, no caso queremos capturar o evento que será gerado pelo Pusher, quando o cubo colidir com o smiley (‘cuboCEsfera-into-smileyCEsfera’). Observe que os nomes utilizados, são os nomes dos respectivos nós de colisão.

Quando esse evento é capturado, ele aciona a função self.eventoColisao. Observe, que essa função recebe, obrigatoriamente, um argumento, neste caso chamado de ‘entrada’. Essa variável, armazena informações relevantes à colisão, como coordenada da colisão, normal, penetração, etc.

exemplo 2:

[code lang=”python”]

from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from pandac.PandaModules import *

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

# Cria o Traverser e handlers: primeiro passo
base.cTrav = CollisionTraverser()
base.event = CollisionHandlerEvent()
base.event.addInPattern(‘%fn-into-%in’)
base.event.addAgainPattern(‘%fn-again-%in’)
base.event.addOutPattern(‘%fn-outof-%in’)

self.cubo = self.loader.loadModel(“box”);
self.cubo.reparentTo(self.render)
self.cubo.setScale(1)
self.cubo.setPos(-3,10,-2)
# Cria colliders: segundo passo
self.cuboCEsfera = CollisionNode(“cuboCEsfera”)
self.cuboCEsfera.addSolid( CollisionSphere(0,0,0, 1.05) )
self.cuboEsfNP = self.cubo.attachNewNode(self.cuboCEsfera)
self.cuboEsfNP.show()
# Apontar ao Traverser e ao Handler, quais objetos devem ser observados ao colidir, e como tratar as colisoes entre eles : terceiro passo
base.cTrav.addCollider( self.cuboEsfNP, base.event )

self.smiley = loader.loadModel(‘smiley’)
self.smiley.reparentTo(self.render)
self.smiley.setScale(0.5);
self.smiley.setPos(0, 10, -1.5)

self.smileyCEsfera = CollisionNode(“smileyCEsfera”)
self.smileyCEsfera.addSolid( CollisionSphere(0,0,0, 1.05) )
self.smileyEsfNP = self.smiley.attachNewNode(self.smileyCEsfera)
self.smileyEsfNP.show()

self.taskMgr.add(self.funcaoDeTarefa, “Tarefa de teste”)

#captura dos eventos de colisao
self.accept(‘cuboCEsfera-into-smileyCEsfera’, self.eventoColisaoIN)
self.accept(‘cuboCEsfera-again-smileyCEsfera’, self.eventoColisaoAGAIN)
self.accept(‘cuboCEsfera-outof-smileyCEsfera’, self.eventoColisaoOUT)

def eventoColisaoIN(self, entrada):
print “ocorreu toque entre objetos!!”
print entrada

def eventoColisaoOUT(self, entrada):
print “ocorreu afastamento entre objetos!!”
print entrada

def eventoColisaoAGAIN(self, entrada):
print “objetos ainda se tocando!!”

def funcaoDeTarefa(self, task):
self.cubo.setX(self.cubo.getX()+0.05)
return Task.cont

app = MyApp()
app.run()

[/code]

Neste segundo exemplo, temos uma demonstração do uso do CollisionHandlerEvent.
Esse handler, digamos é o pai do ‘pusher’. A diferença é que ele não impede os objetos atravessem uns aos outros. Nesse código, vemos que o cubo vai atravessar o smiley sem nenhum problema.

O Handler Event, é uma ótima forma de se criar censores pela arena de jogo. Por exemplo, um local onde um personagem passa e abre uma porta, ou dispara uma armadilha.

Observe as alterações nos passos 1 e 3, e na captura dos eventos.
As diferenças são poucas. A diferença mais significativa, é na criação, e captura, dos eventos. Neste segundo exemplo, além do evento ‘into’, definimos os eventos “outof” e “again”.

Os eventos “into”, acontessem no momento em que os objetos se tocam.
Os eventos “outof”, acontessem no momento em que os objetos param de se tocar.
Os eventos “again”, acontessem enquanto os objetos estiverem se tocando.


Inputs e eventos :: GameDev receitas Panda3D

Inputs e eventos, são elementos comuns em desenvolvimento de software como um todo. Afinal todo, ou quase todo, software recebe uma ordem de execução, através de um clique, ou de um comando de teclado, e essa ordem vinda de um usuário, chamamos, popularmente, de Input de usuário.
Eventos são, em geral, comandos trocados entre elementos de um software. No caso de um jogo, podemos imaginar a seguinte situação: Um personagem está andando por um castelo, onde há um “gatilho” de uma armadilha. Quando o personagem, passa pelo gatilho, o gatilho avisa a armadilha para disparar as flechas que matarão o personagem.
Nessa pequena descrição, podemos dizer que o gatilho, enviou uma mensagem para a armadilha disparar. Essa mesagem é o que chamamos de eventos.

Ambos, Inputs de usuário, e eventos entre objetos, possuem um papel primordial para a construção de um jogo.

Inputs ———————

Vejamos como capturar um comando de usuário, no caso a tecla ‘W’ sendo pressionada:

exemplo 1:

Inputs, no Panda3D, são capturados como eventos. Sempre que uma tecla é pressionada, uma mensagem de evento é enviado pelo sistema, e basta que se diga ao sistema, que aquele evento em especial, te interessa.

Esse processo é feito em duas etapas:
1- Configurar o evento que te interessa: self.accept( ‘nome do evento’, nome da função)
2- Definir uma função para tratar o evento. Ou seja, o que esse evento deve fazer.

No exemplo acima, o evento gerado pela tecla ‘w’, deve ser capturado, e esse evento executa a função capturaTecla.

exemplo 2:

Neste segundo exemplo, observa-se duas novas possibilidades: Uso de parâmentros, e evento “up”.
O envio de parâmetros junto ao evento de tecla é útil em várias situações. Neste caso enviamos uma string (‘caminhar’), e um valor boleano.
Outra novidade, o evento “-up”, utiliza-se quando se deseja detetar quando uma tecla foi solta.
No exemplo, temos um esboço para quando o usuário pressionar a tecla ‘w’, o personagem começa andar, e quando a tecla ‘w’ é solta, o personagem deve parar o “andar”.

EVENTOS ————————-

Eventos como dito antes, são uma possibilidade de fazer com que objetos do software se comuniquem entre si, trocando mensagens.
Em geral, há dois passos para fazer com que objetos de um software possam se comunicar, é necessário: 1- Definir qual evento o objeto deve “ouvir”, e; 2- Programar o envio das mensagens. Esse processo e mais simples do que parece.

3.b.1 – Panda3D ——————-

No Panda3D, como visto anteriormente, Inputs de usuário geram eventos, que podemos detectar. Essa deteção, acontesse porque definimos os eventos aos quais o objeto deve “ouvir”.
Agora vejamos como enviar um evento personalizado (mensagem) através do sistema, e como detectá-lo.

exemplo 1:

Neste simples exemplo, ao pressionar a tecla ‘w’, inicia-se a execução da tarefa que fará o cubo se deslocar pela tela (movendo-se da esquerda para a direita). Quando o cubo ultrapassar o ponto 3, no eixo X, um evento é enviado pelo sistema (messenger.send( ” stopCubo “) ). Esse evento é capturado pelo programa, que executa a função stopCubo, onde a tarefa de deslocamento do cubo é interrompida.