Colisões :: GameDev receitas Unity3D

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.

Para esses exercícios no Unity3D, monte uma cena como descrita na imagem abaixo:
CenaCollisions
Obs.: Durante o tutorial, vou utilizar os nomes dos elementos como estão marcados na imagem acima.

No Unity3D, as deteção de colisões são realizadas pela engine de física, portanto, os elementos necessários para a deteção estão localizados no menu Componet >> Physics, são eles os Colliders e o Rigidbody.

Para começarmos o demo, vamos adicionar e configurar os elementos básicos necessários.
Observe no objeto SphereTrigger, se há o component SphereCollider, e se esse component está marcado como um Trigger. Caso não haja o componente collider, para adicioná-lo, selecione o objeto SphereTrigger, vá ao menu Component >> Physics >> Sphere Collider. E para marcá-lo como trigger, vá ao componente no painel Inspector, e selecione a opção “Is Trigger”.

O objeto Cube, irá desempenhar um papel importante nesse demo, ele andará pela tela com os comandos do usuário, e irá demonstrar o funcionamento das colisões. Para configurá-lo, verifique se o mesmo possui o componente Box Collider, porém esse não pode ser marcado como um trigger. Adicione também ao Cube, o componente Rigidbody. Para adicionar tal componente, selecione o Cube, vá ao menu Component >> Physics >> Rigidbody. E nesse caso é importante marcar a opção “Is Kinematic”, isso nos permitirá mover o Cube, sem a necessidade de simulações de física.
Para finalizar, basta adicionar dois scripts ao cubo; Primeiro o script BasicInputHandlerCube, que você encontra no tutorial sobre Inputs de Usuário; O segundo script a ser adicionado é o script abaixo:

BasicCollisionHandlerCube:
[code lang=”java”]
private var colisionInfo:String = “Collision info”;
function Update () {
}

function OnCollisionEnter(col : Collision){
colisionInfo = “Collision Enter (Rigidbody) : “;
}

function OnCollisionExit(col : Collision){
colisionInfo = “Collision Exit (Rigidbody) : “;
}

function OnTriggerEnter(col : Collider){
colisionInfo = “Collision Enter (Trigger) : “;
}

function OnTriggerExit(col : Collider){
colisionInfo = “Collision Exit (Trigger) : “;
}

function OnTriggerStay(col : Collider){
colisionInfo = “Collision Stay (Trigger) : “;
}

function OnGUI(){
GUI.Label(Rect(200,75,300,100),colisionInfo);
}
[/code]

Após esse trabalho, quando você executar o jogo, verá na parte central da tela, um texto exibindo algumas informações sobre a colisão, quando ela existir.
O que acontece é o seguinte. Como dito anteriormente, o Unity3D, realiza as colisões usando a engine de física, logo, os Colliders são os “sensores” que são usados para calcular quando um “toca” ou “permeia” a àrea de outro collider. Quando esse toque é detetado, o componente Rigidbody dos objetos é informado, e envia uma mensagem para o gameObject que pode ser: OnCollisionEnter, OnCollisionStay, OnCollisionExit, OnTriggerEnter, OnTriggerStay, ou OnTriggerExit.
No caso de uma colisão com um trigger, que é o caso do objeto SphereTrigger, serão enviadas as mensagens OnTriggerEnter, quando a colisão se iniciar; OnTriggerStay, enquanto a colisão estiver em curso; OnTriggerExit, quando os objetos param de colidir.
No caso de uma colisão com um rigidbody, serão enviadas as mensagens OnCollisionEnter, OnCollisionStay, e OnCollisionExit, seguindo os mesmos princípios das colisões com Triggers.

Portanto, o que script faz é o seguinte, quando uma mensagem de colisão é enviada, o script BasicCollisionHandlerCube acionará a função apropriada à colisão, e irá mudar o texto da mensagem que aparece na tela, informando se a colisão foi com um Trigger, ou com um Rigidbody.

Para fazer a colisão com um rigidbody, vamos cofigurar o objeto SphereObject. Verifique se se a SphereObject, possui um SphereCollider (não trigger), e um Rigidbody (não kinematic).
Então use o Cube, para colidir sobre esse objeto e veja o resultado. Você verá que a esfera, não irá se interpolar com o Cube, e ainda irá reagir como uma bolinha sobre o plano.

Veja o exemplo final aqui.

As outras esferas que temos nas cenas, podem ser configuradas como lhe interessar. Use-as para experimentar.

Mensagens :: Gamedev receitas Unity3D

Para enviar mensagens de eventos entre objetos de jogo no Unity3D, há duas possibilidades: 1 – Enviar a mensagem para os objetos com parentesco; 2 – Fazer uma busca pela cena para encontrar objetos de jogo, e executar um componente.

Mensagens
Inicialmente, vejamos o primeiro modo, com objetos agrupados usando dos método, SendMessage, BroadcastMessage, e SendMessageUpwards.
Para começar, criamos uma cena com elementos básicos como descrito na imagem abaixo:
imagemCenaMessages
Obs.: Monte sua cena com elementos semelhantes a esses, e dê os mesmos nomes. Os objetos fountain, e cannon, devem possuir um EllipsoidParticleEmitter, um ParticleRender e um ParticleAnimator, configure-os como quiser.
Obs 2.: No objeto Cube2, utilize o script BasicInputHandler, visto no tutorial sobre Inputs de usuário.

SendMessage
O método SendMessage irá executar uma determinada função em qualquer componente do gameObject que enviou a mensagem.
No objeto fountain, coloque o seu BoxCollider Component como um trigger. Crie esses dois scripts, e os arraste para o gameObject.

FountainScript:
[code lang=”java”]
function Update () {
}

function TurnFountainOn( v: boolean){
particleEmitter.emit = v;
}
[/code]

SendSimpleMessageScript:
[code lang=”java”]
function Update () {
}

function OnTriggerEnter(col: Collider){
this.SendMessage(“TurnFountainOn”, true);
}

function OnTriggerExit(col: Collider){
this.SendMessage(“TurnFountainOn”, false);
}
[/code]

Ao arrastar esses scripts para o gameObject fountain, eles se tornam Components. O SendSimpleMessageScript irá detectar colisões com o seu BoxCollider (setado como um trigger), e na entrada e saída das colisões irá executar o envio de mensagem “TurnFountainOn”, junto com um valor boleano true ou false.
O FountainScript, possui a função TurnFountainOn, que irá ligar, ou desligar a emição de partículas, de acordo com o recebimento do parámetro true ou false.
Veja mais detalhes nesse link.

BroadcastMessage
O método BroadcastMessage fará o mesmo que o método SendMessage, porém com a possibilidade de enviar as mensagens para outros gameObjects, desde que esses gameObjects sejam filhos do objeto que enviar as mensagens.

Para essa etapa, arraste o gameObject cannon sobre o objeto cannonPlat (faça isso no painel, não na cena de jogo), para que ele seja um “filho” do cannonPlat.
Arraste o script FountainScript para o objeto cannon.
No objeto cannonPlat, configure seu BoxCollider como um trigger.

Crie um novo script, BroadcastMessageScript:
[code lang=”java”]
function Update () {
}

function OnTriggerEnter(col: Collider){
this.BroadcastMessage(“TurnFountainOn”, true);
}

function OnTriggerExit(col: Collider){
this.BroadcastMessage(“TurnFountainOn”, false);
}
[/code]

Esse script funciona basicamente como o SendSimpleMessageScript, detecta colisões com seu trigger, e chama a função TurnFountainOn, em qualquer objeto que seja “filho” do objeto cannonPlat.

Find Component
Todo gameObject no Unity3D pode executar a função Find, que irá procurar por gameObjects pela cena. Dessa busca, os Components dos objetos são acessíveis, e podemos executar funções pertencentes à eles.

Para essa etapa, configure o BoxCollider do objeto plat, como um trigger. Adicione o FountainScript ao objeto cannon.

Crie o seguinte script, FindBehaviorScript:
[code lang=”java”]
function Update () {
}

function OnTriggerEnter(col: Collider){
GameObject.Find(“cannon”).GetComponent(“FountainScript”).TurnFountainOn(true);
GameObject.Find(“fountain”).GetComponent(“FountainScript”).TurnFountainOn(true);
}

function OnTriggerExit(col: Collider){
GameObject.Find(“cannon”).GetComponent(“FountainScript”).TurnFountainOn(false);
GameObject.Find(“fountain”).GetComponent(“FountainScript”).TurnFountainOn(false);
}
[/code]

Arraste o script para o objeto plat. Assim como os scripts anteriores, esse script irá atentar para colisões, e então, Pesquisar na cena pelos objetos cannon, e fountain. Com esses objetos localizados, o script busca pelo componente FountainScript, e então executa a função TurnFountainOn.
Veja esse link para mais informações.

Veja aqui o exemplo final.

Inputs de Usuário :: GameDev receitas Unity3D

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.

Input de Usuário ::
Crie um novo script Javascript no painel “Project”, chamado BasicInputHandler, e acoloque o seguinte código:
[code lang=”Javascript”]
function Update () {
//Move o objeto pelo eixo Z
if(Input.GetAxis(“Vertical”) > 0){
transform.Translate(Vector3.forward * Time.deltaTime);
}
else if(Input.GetAxis(“Vertical”) < 0){
transform.Translate(- Vector3.forward * Time.deltaTime);
}

//Move o objeto pelo eixo X
if(Input.GetKey(“d”)){
transform.Translate(Vector3.right * Time.deltaTime);
}
else if(Input.GetKey(“a”)){
transform.Translate(- Vector3.right * Time.deltaTime);
}
}
[/code]
Adicione um cubo à sua cena, e arraste esse script para esse cubo.

Esse script está checando o Input de usuário de duas formas, a primeira pelo eixo “Vertical” do objeto Input, e outra pelas teclas ‘d’ e ‘a’.

Veja o exemplo aqui.

Opção de Eixos.
No Unity3D, há esse objeto global chamado Input, que captura as entradas de usuário, e às torna acessíveis para outros objetos do jogo. Esse eixo “Vertical”, que o objeto Input está utilizando, é previamente configurado, para ser ativado pelas teclas W,S, seta-para-cima e seta-para-baixo: As teclas W e Seta-para-cima, fazem com que o eixo “Vertical” seja positivo; As teclas S e Seta-para-baixo, fazem que o eixo seja negativo.
ISSO É UM CONFIGURAÇÃO QUE PODE SER MODIFICADA pelo desenvolvedor. Se você preferir que outras teclas acionem o eixo Vertical, entre no menu EDIT >> Project Settings >> Input. O painel Inspector irá exibir os eixos previamente configurados no Input. Você pode modificar as configurações de cada um deles, criar e excluir os eixos (basta modificar a propriedade Size, se você aumentar terá mais eixos disponíveis, se reduzir, eixos serão deletados).

Opção de Teclas.
A outra opção de utilização do Input, é pela função GetKey. O objeto Input captura quando as teclas são pressionadas, e essa informação fica disponível para a utilização.

Ambas as formas de captura do Input são válidas. A diferença entre elas é um detalhe, que não é muito claro de início, mas que vou descrever abaixo:
No objeto Input, do Unity3D, quando uma tecla que está ligada à algum eixo, é pressionada (digamos a tecla W), o eixo em questão começa a “crescer”, seu tamanho inicial que é zero, começa a aumentar; E quando a tecla é solta, o eixo começa a diminuir, seu tamanho diminui até chegar a zero.
Imagine essa situação no nosso cubo:
Assim que o usuário pressiona, digamos a tecla W, o eixo “Vertical” começa a crescer em comprimento, e o Cubo então, começa a se deslocar, afinal nosso código desloca o objeto quando o tamanho do eixo for diferente de zero. Mas assim que o usuário solta a tecla que ativa o eixo, o eixo começa a diminuir de tamanho lentamente, o que faz com que o nosso objeto continue se movendo, e interrompa seu deslocamento, apenas quando o eixo chegar ao valor zero.

Com o código acima, você perceberá, que há uma diferença entre as animações do movimento do cubo pelo eixo X e Z. Qual é o certo de ser usado? Depende de você escolher!

 

Update :: GameDev receitas Unity3D

“Updates”, é uma nomenclatura usada no Unity3D, que se refere: “A necessidade de prover a certos objetos de jogo, loops de execução.”

Por exemplo, ao se criar um personagem em um jogo, a programação desse personagem pode ter uma série de funções que devem ser checadas, e executadas, a cada frame do jogo.

Diferentes game engines, possuem diferentes nomenclaturas, e formas de se implementar esses procedimentos. Aqui veremos como funciona no Unity3D. Na verdade o conceito é simples, basta usar a função Update, nos seus scripts.

Cria uma cena no Unity, e adicione dois cubos à cena, e posicione-os como preferir. Mas nomeios com os nomes “Cubo1” e “Cubo2″.

[code lang=”javascript”]
private var teste:int = 0;

function Update () {
Debug.Log(“Frame : “+teste);
teste++;
}
[/code]
Obs.: Crie esse script, nomeie-o como quiser. Arraste o script, do painel “Project”, para um dos GameObjects que estejam na sua cena (de preferência um dos cubos). Ao rodar o script, você vera esses resultados na janela do “Console”.

Esse script, está contando o número de frames do seu jogo. Logo na primeira linha, criamos a variável ‘teste’, com valor 0. A função Update, será executada a cada loop do programa, e a cada loop, ela irá escrever no Console a mensagem: “Frame : 0”, “Frame : 1”; “Frame : 2″… e assim sucessivamente

Agora para fazer com que um objeto de jogo se desloque na cena, façamos o seguinte script:

[code lang=”javascript”]
private var teste:int = 0;

function Update () {

transform.Translate(Vector3.right * Time.deltaTime);
Debug.Log(“Frame: “+teste);
teste++;
}
[/code]

Execute seu jogo, e você verá, o seu cubo se deslocando aos poucos sobre o eixo X, no sentido positivo do eixo.
Veja o exemplo aqui.

Panda3D gamedev tutorial 4

Neste tutorial, iremos trabalhar com simulações simples de física. O Panda3D possui uma pequena engine de física, que serve para simulações simples. Usaremos ela para melhorar o deslocamento do nosso personagem, e implementar um salto. Também veremos o uso de Intervals no Panda3D.

Esse tutorial tem por objetivo mostrar como usar, de maneira simplificada, uma engine de física. Caso você tenha interesse em utilizar o ODE no Panda, veja este pequeno tutorial, também lhe será útil: http://paulobarbeiro.com.br/blog/?p=77

Para começar com uma má notícia… boa parte do que foi feito até o momento nos tutoriais anteriores, principalmente o que foi feito para movimentar o avatar, não servirá para as simulações de física, logo vamos jogar, no lixo! 🙂 Isso lhe servirá como uma lição: Não se apegue a seu código! 😀 E pense bem, se irá usar simulações de física ou não, porque os códigos não serão reaproveitáveis.

O trabalho será meio longo. E antes de começarmos com códigos vamos explorar um pouco de teoria, e para ter uma “visão panorâmica” do que iremos fazer.

Engines de Física ——————-

Engines de física, são softwares dedicados a realizar cálculos de física, geralmente física Newtoniana, para diversos tipos de aplicações, sendo games uma das possibilidades.

Essas engines de física tem um único trabalho: calcular física. Sim, só isso! Engines de física não fazem renderizações, não capturam inputs de usuário, não fazem síntese sonora, elas apenas calculam valores, com base em fórmulas de física.
Dentro dessas fórmulas, encontra-se muitas opções, entre elas: as três leis de Newton, molas, juntas, colisões… etc. Cada engine de física, possui suas implementações de fórmulas. Cabe a você escolher qual engine usar, baseando-se no que cada uma oferece.

Bem, sendo que uma engine de física, apenas faz cálculos, talvez você esteja se perguntando, como elas são usadas em games?

Até o presente momento, nessa série de tutoriais, nós realizamos as movimentações de nosso personagem, modificando sua posição de coordenadas a cada frame do jogo. Ou seja, para que o avatar se desloca-se para a direita, nós incrementamos sua coordenada X a cada frame, e isso é o suficiente para dar uma ilusão de deslocamento. Essa é uma abordagem plenamente possível, e utilizada em muito jogos! Não há nada de errado nisso! Porém essa movimentação, feita dessa forma, ignora, por exemplo, uma reação natural a certas forças da natureza, como a gravidade. Qualquer um que olhar nosso jogo até o presente momento, irá reparar que o avatar cai a uma velocidade constante, e isso não é um comportamento natural em nosso universo.
E para muitos tipos de jogos, buscar essa naturalidade faz parte da diversão!

Usar simulações de física é um primeiro passo para buscar realismo nos jogos. Isso é uma busca que muda de jogo para jogo. Em alguns casos o realismo pode estragar completamente o jogo!

Então, quando utilizamos engines de física, nós deixamos de manipular as coordenadas os objetos! Quando se usa simulação de física, temos que pensar os movimentos, como reação a aplicação de forças sobre os objetos. Ou seja, para que um objeto se mova para a direita, ao invés de modificar suas coordenadas, temos que aplicar uma força nesse objeto, para fazer com que ele se mova para a direita. Todo o resto fica por conta da engine de física. O que irá determinar a forma do movimento, serão os resultados dos cálculos de simulação de física. Tudo que temos de fazer, é calibrar a aplicação das forças ao nosso gosto!

Pensar o movimento sobre a óptica da aplicação de forças físicas, não é complicado. Mas nos exige um pequeno conhecimento sobre forças e sua representação matemática, os vetores. Todos já estudaram isso nas aulas de física na escola, basta relembrar, e utilizar de uma forma muito mais divertida! Aqui tem um link que pode ajudar um pouco.

Há uma quantidade bem grande de explicações sobre vetores, cálculos vetorias, forças e forças vetoriais pela internet como um todo. Caso o link não lhe ajude, busque pela internet.

Agora, sabemos conceitualmente como as engines de física funcionam. Agora vamos pensar conceitualmente como isso se aplicaria ao nosso game.

Até o presente momento, temos movimentos em três direções, à direita, à esquerda, e para baixo. Então, para que nosso avatar se desloque temos que aplicar forças nele, ou seja:
– para baixo: teremos a força da gravidade;
– para direita: uma força que o empurre para direita;
– para esquerda: uma força que o empurre para a esquerda.

E caso queiramos um salto, teremos uma força que empurre nosso avatar para o alto! Veja o diagrama abaixo:

grafo05

Nesse diagrama também temos os valores vetoriais para cada uma dessas forças. Esses valores vetoriais, representam a direção e intensidade da força. Por exemplo, a gravidade: Vec3(0,0,-9.81).
Esses três valores que forma o vetor, são coordenadas x,y,z. No caso da gravidade, x=0, y=0, e z=-9.81, ou seja, ela é uma força que aponta para baixo, com intensidade 9.81. Neste tutorial todas as forças são bem simples: apontam para direções nos eixos x, y, ou z, veja as outras:

– pushRight: Vec3(10,0,0): aponta para a “direita” (x positivo), intensidade 10
– pushLeft: Vec3(-10,0,0): aponta para a “esquerda” (x negativo), intensidade 10
– pushUp: Vec3(0,0,20): aponta para cima (z positivo), intensidade 20

O que nós faremos no nosso código, é uma implementação a engine de física do Panda3D, que irá aplicar essas forças em nosso avatar, de acordo com os inputs do usuário. Durante o processo, veremos a implementação dos elementos necessários, e problemas de lógica que teremos de achar soluções.

Essa engine de física que usaremos, é “embutida” no Panda3D, ela é muito simples, e com poucos recursos se comparada a outras engines de física, mas possui o que precisamos para nosso jogo. Devido a essas limitação, os desenvolvedores do Panda3D, também implementaram uma outra engine de física, a ODE (Open Dynamics Engine) um software já consagrado. Mas nada impede que outras engines sejam usadas. Outra possibilidade, é a PhysX, também implementada por alguns usuários!

Vamos aos passos iniciais. No arquivo Panda-tut-00.py, não teremos grandes modificações. Nesse arquivo principal, temos que importar e iniciar o PhysicsCollisionHandler, que será o grande responsável pela detecção de colisões, nesse caso, utilizando a simulação de física do Panda3D.

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

import direct.directbase.DirectStart
from pandac.PandaModules import *

from direct.task.Task import Task
from direct.showbase.DirectObject import DirectObject

from avatar import Avatar

#game class
class Game(DirectObject):
def __init__(self):
#init collider system
traverser = CollisionTraverser()
base.cTrav = traverser
base.cTrav.setRespectPrevTransform(True)

base.pusher = CollisionHandlerPusher()
base.pusher.addInPattern(“%fn-into-%in”)
base.pusher.addOutPattern(“%fn-out-%in”)

base.physics = PhysicsCollisionHandler()
base.physics.addInPattern(“%fn-into-%in”)
base.physics.addOutPattern(“%fn-out-%in”)
base.enableParticles()

#load platform model
self.plataforma = loader.loadModel(“../assets/eggs/plataformaBase”)
self.plataforma.reparentTo(render)
self.plataforma.setPos(0,10,0)
#load background sky
self.wall = loader.loadModel(‘../assets/eggs/papelParede’)
self.wall.reparentTo(render)
self.wall.setScale(1)
self.wall.setPos(-5, 30, -2)
#avatar
avatar = Avatar()
avatar.setCollision(traverser)

game = Game()
run()
[/code]

Observe que nesse código, apenas adicionamos as linhas 24, 25, 26 e 27. O PhysicsCollisionHandler funciona de forma muito semelhante aos outros “collision handlers”, a maior diferença é a obrigatoriedade de se ativar o sistema de partículas (linha 27).
As linhas relativas ao CollisionHandlerPusher, podem ser apagadas, elas não possuem mais uso, só as mantenho lá, para que você possa comparar.

Agora na classe Avatar, teremos grandes modificações! Vou colocar todo o código abaixo, e depois seguir nas explicações de cada alteração.

código classe avatar
[code lang=”python”]
from direct.showbase.DirectObject import DirectObject
from direct.actor.Actor import Actor
from direct.task import Task
from pandac.PandaModules import *
from direct.interval.IntervalGlobal import *

#modulo da classe Avatar
class Avatar(DirectObject):
def __init__(self):
self.persona = render.attachNewNode(‘persona’)

self.personaActorNode = ActorNode(‘personaActorNode’)
self.personaActorNode.getPhysicsObject().setMass(35)
self.personaActorNP = self.persona.attachNewNode(self.personaActorNode)
base.physicsMgr.attachPhysicalNode(self.personaActorNode)

self.personaActor = Actor(‘../assets/eggs/personagem.egg’,
{‘idle’:’../assets/eggs/personagem-parado’,
‘run’ :’../assets/eggs/personagem-correr’,
‘jump’:’../assets/eggs/personagem-pular’}
)
self.personaActor.setScale(1)
self.personaActor.setPos(0,0,5)
self.personaActor.reparentTo(self.personaActorNP)

self.persona.setPos(0,10,10)
self.persona.setScale(.15)
self.persona.setH(90)

self.state = { ‘left’ :False,
‘right’ :False,
‘ground’:False,
‘canJump’ :False
}

taskMgr.add(self.movement, “Avatar Movement”)

#timer for jump
self.startCallforJump = 0
self.timer = LerpFunc(self.performJump,
fromData = 0,
toData =1,
duration = 0.25,
blendType=’easeIn’,
extraArgs=[],
name=None)

#capture keyboard events
self.accept(‘arrow_left’, self.changeState, [‘left’,True])
self.accept(‘arrow_left-up’, self.changeState, [‘left’,False])
self.accept(‘arrow_right’, self.changeState, [‘right’,True])
self.accept(‘arrow_right-up’, self.changeState, [‘right’,False])
self.accept(‘space’, self.changeState,[‘jump’,True])
self.accept(‘space-up’, self.changeState,[‘jump’,False])
#capture collision events
self.accept(‘bola0CN-into-plataforma’, self.changeState, [‘ground’,True ])
self.accept(‘bola0CN-out-plataforma’, self.changeState , [‘ground’,False])

#Persona physics forces
self.pushUp=LinearVectorForce(0,0,20)
self.pushUpFN = ForceNode(‘pushup-force’)
self.pushUpFNP = render.attachNewNode(self.pushUpFN)
self.pushUpFN.addForce(self.pushUp)

self.pushLeft=LinearVectorForce(-10,0,0)
self.pushLeftFN = ForceNode(‘pushleft-force’)
self.pushLeftFNP = render.attachNewNode(self.pushLeftFN)
self.pushLeftFN.addForce(self.pushLeft)

self.pushRight=LinearVectorForce(10,0,0)
self.pushRightFN = ForceNode(‘pushright-force’)
self.pushRightFNP = render.attachNewNode(self.pushRightFN)
self.pushRightFN.addForce(self.pushRight)

self.gravityFN=ForceNode(‘world-forces’)
self.gravityFNP=render.attachNewNode(self.gravityFN)
self.gravityForce=LinearVectorForce(0,0,-9.81) #gravity acceleration
self.gravityFN.addForce(self.gravityForce)

base.physicsMgr.addLinearForce(self.gravityForce)

def setCollision(self, trav):
#colision nodes and solids
self.ball0 = CollisionSphere(0,0,1.5,1.5)
self.ball0NP = self.personaActorNP.attachNewNode(CollisionNode(‘bola0CN’))
self.ball0NP.node().addSolid(self.ball0)
self.ball0NP.show()

base.physics.addCollider(self.ball0NP, self.personaActorNP)
base.cTrav.addCollider(self.ball0NP, base.physics)

def movement(self, task):
#print self.personaActorNode.getPhysical(0).getLinearForces()
if(self.state[‘left’] == True):
if(len(self.personaActorNode.getPhysical(0).getLinearForces()) == 0):
self.personaActorNode.getPhysical(0).addLinearForce(self.pushLeft)
else:
self.personaActorNode.getPhysical(0).removeLinearForce(self.pushLeft)

if(self.state[‘right’] == True):
if(len(self.personaActorNode.getPhysical(0).getLinearForces()) == 0):
self.personaActorNode.getPhysical(0).addLinearForce(self.pushRight)
else:
self.personaActorNode.getPhysical(0).removeLinearForce(self.pushRight)

return Task.cont

def changeState(self, key, value, col=None):
if(self.state[‘canJump’] == True and key == ‘jump’):
if(value==True):
self.startCallForJump = globalClock.getRealTime()
if(value == False):
preparationTime = globalClock.getRealTime() – self.startCallForJump
if (preparationTime > 0.4): preparationTime = 0.4
elif(preparationTime < 0.2): preparationTime = 0.2
upforce = 1000*preparationTime
self.pushUp.setVector(0,0,upforce)
self.timer.start()
self.startCallForJump = 0
else:
self.state[key] = value

if(self.state[‘ground’] == True):
self.state[‘canJump’]=True
elif(self.state[‘ground’] == False):
self.state[‘canJump’]=False

def performJump(self, data):
if(data == 0):
self.personaActorNode.getPhysical(0).addLinearForce(self.pushUp)
elif(data == 1):
self.personaActorNode.getPhysical(0).removeLinearForce(self.pushUp)
[/code]

Vamos começar pela função __init__(self):

A primeira alteração é adicionar aos nodes e nodepaths que compõe nosso avatar, um ActorNode. O ActorNode é uma classe para um nó que interage com o sistema de física. Não confunda com a classe Actor, destinada à utilização de modelos “riggados”. Primeiro nós criamos o ActorNode (linha 5), configuramos a massa do nosso objeto; Adicionamos o ActorNode ao persona node; e o nodepath do ActorNode, dentro do persona node, é adicionado ao handler physics.
Observe outro detalhe importante na linha 17: O node personaActor, com o modelo do personagem, passa a ser ligado ao ActorNode. É essa ligação que fará com que o modelo, se movimente pela tela com as simulações de física.

Nas linhas 32 e 32, temos uma pequena lógica para realizar um salto. Veremos esse processo com mais detalhes em breve.
Nas linhas 46 e 47, temos uma captura de input de usuário com a tecla de espaço.

Das linhas 52 à 71, temos a configurações das forças que atuarão sobre nosso personagem, que discutimos anteriormente. E na linha 71, em especial vemos como fazer a aplicação da força ao ActorNode, no caso, a única força que será onipresente, a gravidade!
A criação dessas forças, é feita coma criação de um vetor de força, no caso, LinearVectorForce, que contém a direção e intensidade da força em questão. Com o LinearVectorForce criado, temos que criar um ForceNode, que será anexado ao render. Nesse momento, as forças ficam disponíveis para uso, basta aplicá-las.

A função setCollision:
Neste função temos poucas alterações, a única diferença é a necessidade, óbvia, de ligar os collisionNodes ao PhysicsCollisionHanlder.

A função movement:
Na função movement, temos a lógica que realiza o movimento do personagem aplicando as forçar apropriadas. Basicamente quando o estado do personagem muda para a direita ou esquerda, a função aplica e remove as forças, para que a simulação represente o que desejamos. Os comando addLinearForce() e removeLinearForce(), são bem auto-explicativos, não?

A função changeState:
Na função changeState temos uma lógica especial para a realização do salto do avatar.
Primeiro, o objetivo dessa função é filtrar o input de usuário pela tecla de espaço, para a realização do salto. O “filtro” consiste em realizar um salto mais forte, ou mais fraco, dependendo do tempo que o jogador pressiona a tecla de espaço.
Quando a barra de espaço é pressionada, a função avalia se o avatar pode saltar(canJump), ou seja, se ele está tocando a plataforma. Então ele avalia se a tecla de espaço foi pressionada ou desapertada; Se foi apertada, a função guarda o tempo em que ela foi apertada; Quando a tecla for solta, a função calcula o tempo em que ela ficou pressionada, e divide esse valor de 1000, o resultado é aplicado ao vetor de forca linear self.pushUp, e um timer é ativado, que executa a aplicação e remoção da força.

A função performJump:
Essa função é uma LerpFunction, declarada na linha 40, na função __init__. Essa função ira realizar uma espécie de timer, que usaremos para aplicar a força do salto, e bem pouco tempo depois, remover essa força. Um salto de uma humano, ou outro animal terrestre, é basicamente um único impulso bem forte, e não uma aplicação constante de força. Logo, para simular um salto humano, temos que fazer isso, uma aplicação de força em um curtíssimo período de tempo.

Panda3D gamedev tutorial 3

Agora precisamos definir colisões para nosso personagem!
Colisões entre objetos em jogos, são um ponto absurdamente importante. Importante por se tratar de algo fundamental para a mecânica do jogo, e também por ser um tópico delicado em termos computacionais. E esse fator técnico nos obriga a algumas reflexões.

Calcular colisões entre objetos, com eficiência, envolve uma série de algoritmos complexos – os quais não vou entrar em detalhes – e que consomem um processamento considerável do computador, e para permitir que essas colisões sejam possíveis em tempo real, é necessário realizar algumas simplificações na descrição dos nossos elementos. Por exemplo: Se temos um modelo de uma cabeça, colisões não serão calculadas para cada depressão ou volume facial, mas simplificamos o formato da cabeça para uma esfera, ou um tubo, uma forma geométrica mais simples. Dessa forma as colisões conseguem ser calculadas de forma a não travar o bom desempenho da renderização.

Essas formas geométricas simples (esferas, cubos, tubos) são tratadas nas game engines como sólidos de colisão, pois serão esses objetos usados para cálculos de colisão.
Cada game engine possui seus próprios sólidos de colisão, e precisamos usá-los para criar uma descrição simplificada de nossos objetos.
No caso de nosso avatar, fiz a escolha de utilizar uma séria de esferas para suas colisões. Veja abaixo:

simplificacaoColisao

Porém, essa imagem acima é meramente ilustrativa, no nosso caso, não precisaremos de tanta definição, e vamos colocar esses sólidos de colisão na medida que forem necessários, começaremos apenas com uma esfera aos pés do avatar, para detectar colisões com a plataforma.

Criar um sólido de colisão, não é tudo. É necessário configurar os cálculos de colisões. No Panda3D, temos que adicionar outros dois objetos, um “Collision Traverser”, e um “collision handler”. Vamos adicionar esses objetos todos, e nos preocupar com os problemas depois 🙂 Sim teremos problemas, mas todos solucionáveis. Primeiro vamos criar o “Collision Traverser” e o “collision handler”, no arquivo Panda-tut-00.py:

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

import direct.directbase.DirectStart
from pandac.PandaModules import *

from direct.task.Task import Task
from direct.showbase.DirectObject import DirectObject

from avatar import Avatar

#game class
class Game(DirectObject):
def __init__(self):
#init collider system
traverser = CollisionTraverser()
base.cTrav = traverser

base.pusher = CollisionHandlerPusher()
base.pusher.addInPattern(“%fn-into-%in”)
base.pusher.addOutPattern(“%fn-out-%in”)

#load platform model
self.plataforma = loader.loadModel(“../assets/eggs/plataformaBase”)
self.plataforma.reparentTo(render)
self.plataforma.setPos(0,10,0)
#load background sky
self.wall = loader.loadModel(‘../assets/eggs/papelParede’)
self.wall.reparentTo(render)
self.wall.setScale(1)
self.wall.setPos(-5, 30, -2)
#avatar
avatar = Avatar()
avatar.setCollision(traverser)

game = Game()
run()
[/code]
O que temos de novo:
– logo no inicio da função __init__(self), temo a criação dos elementos traverser e do base.pusher, que são respectivamente, o “Collision traverser” e o “collision handler”
– logo abaixo da construção do Avatar, temos a linha: avatar.setCollision(traverser). Aqui é o ponto onde criamos no avatar as configurações necessárias para que o mesmo colida com outros objetos.

Vamos a classe Avatar:

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

#modulo da classe Avatar
class Avatar(DirectObject):
def __init__(self):
self.persona = render.attachNewNode(‘persona’)
self.personaActor = Actor(‘../assets/eggs/personagem.egg’,
{‘idle’:’../assets/eggs/personagem-parado’,
‘run’ :’../assets/eggs/personagem-correr’,
‘jump’:’../assets/eggs/personagem-pular’}
)
self.personaActor.setScale(1)
self.personaActor.setPos(0,0,5)
self.personaActor.reparentTo(self.persona)

self.persona.setPos(0,10,0)
self.persona.setScale(.15)
self.persona.setH(90)

self.state = { ‘flying’:True,
‘left’ :False,
‘right’ :False
}

taskMgr.add(self.movement, “Avatar Movement”)

#capture keyboard events
self.accept(‘arrow_left’, self.changeState, [‘left’,True])
self.accept(‘arrow_left-up’, self.changeState, [‘left’,False])
self.accept(‘arrow_right’, self.changeState, [‘right’,True])
self.accept(‘arrow_right-up’, self.changeState, [‘right’,False])

def setCollision(self, trav):
#colision nodes and solids
self.ball0 = CollisionSphere(0,0,1.5,1.5)
self.ball0NP = self.persona.attachNewNode(CollisionNode(‘ball0CN’))
self.ball0NP.node().addSolid(self.ball0)
self.ball0NP.show()

trav.addCollider(self.ball0NP, base.pusher)
base.pusher.addCollider(self.ball0NP, self.persona)

def movement(self, task):
if(self.state[‘flying’] == True):
self.persona.setZ( self.persona.getZ() – 0.1 )
if(self.persona.getZ() < -2.3):
self.state[‘flying’]=False
#move sideways
else:
if(self.state[‘left’]):
self.persona.setX( self.persona.getX() – 0.1 )
elif(self.state[‘right’]):
self.persona.setX( self.persona.getX() + 0.1 )
return Task.cont

def changeState(self, key, value):
self.state[key] = value
[/code]
O que há de novo nessa classe:
– logo nas primeiras linhas: from pandac.PandaModules import * . Nesta linha importamos alguns módulos necessários para realizar as configurações de colisão.
– abaixo da função __init__ e antes da função movement, inserimos a função setCollision.
– na função setCollision criamos dois sub nós ao no persona.
grafo04

Após essas alterações será fácil perceber que o o avatar irá de deslocar de maneira estranha pela plataforma, porém ficará claro que ele está “esbarrando” na plataforma. Esse é o comportamento normal do Collision Handler Pusher. Ele impede que os objetos atravessem uns aos outros.

Outro detalhe importantíssimo! ESPECIALMENTE QUANDO VOCÊ FOR UTILIZAR SEUS PRÓPRIOS MODELOS 3D!!!

Até aqui, foi configurado os objetos traverser e handler, e também a configuração de colisão para o avatar. NÃO FOI FEITA NENHUMA CONFIGURAÇÃO DE COLISÃO PARA A PLATAFORMA!!
Na verdade verdadeira, o arquivo da plataforma já está configurado para colisões, por isso não foi necessário fazê-lo. Vou explicar como foi feito, para que este tutorial não fique incompleto, e que quando você for utilizar seus próprios modelos 3D, possa fazer o mesmo.

Para a plataforma, poder-se-ia (rsrsrsrs) usar sólidos de colisão, mas optei por usar Collision Mesh. Collision Mesh, é utilizar o próprio modelo 3D como sólido de colisão. A limitação é: Seu modelo deve ser simples, e não côncavo!
Quando você converte seus modelos 3D para o formato do Panda3D( .egg), o arquivo final, é essencialmente um arquivo de texto, que você pode abrir em qualquer editor de texto simples, e modificá-lo. Para fazer com que um modelo, simples e não côncavo, seja sensível à colisão, precisamos adicionar uma linha a cada “group”: <Collide>{Polyset keep descend}

Os arquivos .egg são um texto estruturado tal como um XML. Inicialmente, no .egg, é configurado os materiais existentes na geometria. Então iniciam-se os grupos. Um “Group”, é iniciado com sua matriz (informação de posicionamento, e rotação); Logo após, inicia-se uma lista (VertexPool) com informações de todos os vértices que compõe aquela geometria; Finalmente, temos uma lista com informações dos polígonos que compõe a geometria.

Logo, a linha <Collide>{Polyset keep descend}, deve ser colocada no inicio de cada Group. Abra o arquivo plataforma.egg e veja como ele funciona.

[code lang=”xml”]
piso {
{Polyset keep descend}
{
{
1.000000 0.000000 0.000000 0.000000
0.000000 11.226257 0.000000 0.000000
0.000000 0.000000 0.182287 0.000000
0.000000 0.000000 0.000000 1.000000
}
}
piso {
0 {
1.000000 11.226256 -0.182287
}
1 {
1.000000 -11.226257 -0.182287
}
[…]
22 {
-1.000000 11.226261 -0.182287
}
23 {
-1.000000 11.226257 0.182287
}
}
{
{ Material }
{ 0.000000 0.000000 -1.000000 }
{ 0 1 2 3 { piso } }
}
[ … ]
{
{ Material }
{ 0.000003 1.000000 0.000012 }
{ 20 21 22 23 { piso } }
}
}
[/code]

Antes da implementação, vamos entender do que se trata esse tal de CollisionHandlerPusher, e como as colisões serão encaixadas no nosso jogo.
O CollisionHandlerPusher, é um “tratador” de colisões no Panda3D, que não permite que os objetos atravessem uns os outros. Ele desloca o objeto que está se movendo para trás, ou para o lado, desviando-o da intersecção dos objetos.

Outra possibilidade do Collision Handler Pusher, é gerar eventos! Ou seja, quando objetos colidem, ele pode gerar eventos notificando o sistema da colisão. Essa é uma funcionalidade do Collision Handler Event, donde o Collision Handler Pusher é derivado. Logo ele herda essa funcionalidade do Handler Event. A geração de eventos do Collision Handler Pusher, é habilitada nas linhas 20 e 21, do arquivo Panda-tut-00.py.

Implementação —————

Agora que temos nossa colisão básica configurada, falta trabalhar nos detalhes de implementação.
A primeira colisão que temos nessa cena, é quando o avatar cai na plataforma. Até o presente momento, nós fazemos esse cálculo baseado em uma posição no eixo Z. Mas para deixar nosso jogo mais correto, vamos fazer isso com colisão, entre a esfera e a plataforma.

Antes de realizar a implementação

Para começar, vamos apagar as linhas 49 e 50:
[code lang=”python”]
if(self.persona.getZ() < -2.3):
self.state[‘flying’]=False
[/code]
A partir de agora o que irá mudar o state[‘flying’] para False, será a colisão entre a esfera e a plataforma. Vamos criar um novo accept no nosso avatar, para capturar o evento gerado pela colisão (‘ball0CN-into-plataforma’):
[code lang=”python”]
self.accept(‘ball0CN-into-plataforma’, self.changeState, [‘flying’,False])
[/code]

Nesta linha acima, definimos que a colisão entre a esfera e a plataforma, será tratada pela mesma função self.changeState. Observe com detalhe o nome do evento: ‘ball0CN-into-plataforma’. Observe a linha 20 do Panda-tut-00.py, base.pusher.addInPattern(“%fn-into-%in”).
Quando configuramos a geração de eventos pelo pusher, e usamos essa combinação “%fn-into-%in”, o pusher irá gerar eventos com os nomes dos colliders, no nosso caso, a “bolla0CN”, e a “plataforma”.

Porém temos um problema. Toda função que trata uma colisão, obrigatoriamente, tem que receber as informações da colisão. Essa informação é transmitida à função como um parâmetro, logo a função tem que estar preparada para recebê-lo. O que não acontece com nossa função self.changeState. Bem, você pode pensar que: “ah… blz, vamos colocar uma variável ali nos argumentos da função, que fica tudo certo!”. Não… Isso não é tão simples… e como fica a chamada da função nos eventos de teclado, que não enviam um terceiro parâmetro? Esqueceu disso? Se uma função for chamada, com mais ou menos parâmetros os quais ela possui, acontece um erro, e o programa é encerrado.
Felizmente, tem uma manhã em python para lidar com isso. Inserimos um parâmetro ‘col’ (diminutivo de ‘colisão’ rsrsrs) e dizemos que ele é igual a ‘None’ (nulo em python), caso a função não receba esse argumento!! Engenhoso não? Veja:
[code lang=”python”]
def changeState(self, key, value, col=None):
self.state[key] = value
[/code]

Ao final desse processo, teremos nosso avatar, parando de cair ao tocar a plataforma, porém ainda teremos um problema, veja que ele parece patinar sobre a plataforma! Isso acontece pq o pusher quer repelir o avatar da plataforma! Podemos solucionar esse problema, forçando o personagem na posição X e Y que queremos! Veja a solução no código completo abaixo:

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

#modulo da classe Avatar
class Avatar(DirectObject):
def __init__(self):
self.persona = render.attachNewNode(‘persona’)
self.personaActor = Actor(‘../assets/eggs/personagem.egg’,
{‘idle’:’../assets/eggs/personagem-parado’,
‘run’ :’../assets/eggs/personagem-correr’,
‘jump’:’../assets/eggs/personagem-pular’}
)
self.personaActor.setScale(1)
self.personaActor.setPos(0,0,5)
self.personaActor.reparentTo(self.persona)

self.persona.setPos(0,10,0)
self.persona.setScale(.15)
self.persona.setH(90)

self.state = { ‘flying’:True,
‘left’ :False,
‘right’ :False
}
self.lastXpos = 0;

taskMgr.add(self.movement, “Avatar Movement”)

#capture keyboard events
self.accept(‘arrow_left’, self.changeState, [‘left’,True])
self.accept(‘arrow_left-up’, self.changeState, [‘left’,False])
self.accept(‘arrow_right’, self.changeState, [‘right’,True])
self.accept(‘arrow_right-up’, self.changeState, [‘right’,False])
self.accept(‘ball0CN-into-plataforma’, self.changeState, [‘flying’,False])
self.accept(‘ball0CN-out-plataforma’, self.changeState, [‘flying’,True])

def setCollision(self, trav):
#colision nodes and solids
self.ball0 = CollisionSphere(0,0,1.5,1.5)
self.ball0NP = self.persona.attachNewNode(CollisionNode(‘ball0CN’))
self.ball0NP.node().addSolid(self.ball0)
self.ball0NP.show()

trav.addCollider(self.ball0NP, base.pusher)
base.pusher.addCollider(self.ball0NP, self.persona)

def movement(self, task):
if(self.state[‘flying’] == True):
self.persona.setZ( self.persona.getZ() – 0.06 )
#move sideways
else:
if(self.state[‘left’]):
self.persona.setX( self.persona.getX() – 0.1 )
elif(self.state[‘right’]):
self.persona.setX( self.persona.getX() + 0.1 )
else:
self.persona.setX(self.lastXpos)
if(self.persona.getY() != 10):
self.persona.setY(10)
return Task.cont

def changeState(self, key, value, col=None):
self.state[key] = value
if(value == 0):
self.lastXpos = self.persona.getX()
[/code]

Basicamente, na função movement, checamos se a posição Y do avatar não for igual a 10, então ela é corrigida para 10. Isso resolve a “patinação” do avatar para frente e para tráz. Mas ele ainda patina para os lados. Para resolver isso, criamos uma variável chamada lastXpos. Essa variável irá fazer o trabalho de posicionar o avatar sempre no ponto X correto, e não permitir de ele patine para os lados. Observe na função movement, que quando o avatar estiver tocando na plataforma, e não estiver se movendo para a direita ou esquerda, ele será posicionado sempre na posição lastXpos. Essa variável tem seu valor atualizado, sempre que o avatar para de se mover: Veja na função self.changeState, sempre que o ‘value’ for igual a zero, atualizamos a variável lastXpos.

Panda3D gamedev tutorial 2

Neste segundo tutorial criaremos nosso avatar, e habilitaremos algumas funcionalidades de interação do Panda3D: tasks e inputs de usuário.
Em termos de código python, veremos como trabalhar com módulos, condicionais e dicionários.

Porque precisamos desses módulos python? Bem, nosso código começará a ganhar tamanho, e manter nossas classes em arquivos diferentes, é altamente recomendável para facilitar nosso desenvolvimento. Essa coisa de módulos é super simples: basta criar um novo arquivo de código na nossa pasta Panda-project/src e nameá-lo avatar.py, e teremos nele o código abaixo:

[code lang=”python”]
from direct.showbase.DirectObject import DirectObject
from direct.actor.Actor import Actor

#modulo da classe Avatar
class Avatar(DirectObject):
def __init__(self):
self.persona = render.attachNewNode(‘persona’)
self.personaActor = Actor(‘../assets/eggs/personagem.egg’,
{‘idle’:’../assets/eggs/personagem-parado’,
‘run’ :’../assets/eggs/personagem-correr’,
‘jump’:’../assets/eggs/personagem-pular’}
)

self.personaActor.setPos(0,0,4.6)
self.personaActor.reparentTo(self.persona)

self.persona.setPos(0,10,0)
self.persona.setH(90)
self.persona.setScale(.1)
[/code]

Vejamos as coisas novas:
1 – from direct.actor.Actor import Actor
::> Essa é a classe do Panda3D que lida com modelos “riggados”. Que é o caso do nosso modelo.

2 – self.persona = render.attachNewNode(‘persona’)
::> Aqui um novo ‘nó’ é criado, que irá abrigar o ‘nó’ para o modelo 3D. Precisaremos fazer isso, porque teremos outros nós a serem criados, e logo, devemos manter esses nós organizados.

3 – self.personaActor = Actor(‘../assets/eggs/personagem.egg’,
::> Nessa linha criamos o Actor, baseados no arquivo do personagem.egg.

4 – {‘idle’:’../assets/eggs/personagem-parado’,
‘run’ :’../assets/eggs/personagem-correr’,
‘jump’:’../assets/eggs/personagem-pular’}
)
::> Nessas linhas informamos ao Actor, quais animações queremos no nosso personagem. O que vemos aqui é um dicionário python. Podemos notar que ‘idle’ é a chave, para o valor ‘./assets/eggs/personagem-parado’, ou seja a animação ‘idle’, será a animação do arquivo “personagem-parado.egg”. Esse pequeno processo se repete para todas as animações que quisermos.

5 – self.persona.setH(90)
::> Este comando rotaciona o nó persona. Tente comentar essa linha e veja o que vai acontecer.

Para facilitar a compreenção do que foi feito, veja no gráfico abaixo, como que está organizado esse node relativo ao Avatar:
Panda3D-sceneGraph_01
No diagrama acima, temos um esquema ilustrando que o nó do personaActor, nó que guarda nossa geometria, está linkado ao nó persona. E está posicionado na posição (0,0,4.6) relativo ao nó persona, ou seja, levemente acima.

Neste próximo diagrama, temos a demonstração de como vai funcionar nosso grafo a partir de agora:
Panda3D-sceneGraph_02

Agora, temos a segunda parte… importar esse módulo no arquivo principal Panda-tut-00.py, onde precisamos importar a classe Avatar do módulo avatar:

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

import direct.directbase.DirectStart
from pandac.PandaModules import *

from direct.task.Task import Task
from direct.showbase.DirectObject import DirectObject

from avatar import Avatar

#classe para o jogo
class Game(DirectObject):
def __init__(self):
#carregar o modelo da plataforma
self.plataforma = loader.loadModel(“../assets/eggs/plataformaBase”)
self.plataforma.reparentTo(render)
self.plataforma.setPos(0,10,0)
#plano de fundo
self.parede = loader.loadModel(‘../assets/eggs/papelParede’)
self.parede.reparentTo(render)
self.parede.setScale(1)
self.parede.setPos(-5, 30, -2)

#avatar
self.avatar = Avatar()

game = Game()
run()
[/code]

Captura_de_tela-1Ao final teremos algo como essa imagem ao lado.
De agora em diante, todos os comandos relativos ao Avatar serão escritos nesse arquivo Avatar.py. Vamos iniciar com uma lógica bem simples, imperfeita para nossas finalidades, mas boa para introduzir quem esta começando. Vamos fazer com que nosso personagem caia até a plataforma, e então possa se deslocar lateralmente sobre a mesma.

Façamos nesta ordem:
– avatar cai, se não estiver tocando a plataforma;
– quando tocar a plataforma para de cair;
– quando estiver na plataforma, ele pode se deslocar para os lados;
– quando o usuário pressionar as setas para direita ou esquerda, o avatar se desloca;

Para essa pequena lógica, vamos criar uma função que analise os ‘estados’ do avatar, para então decidir se ele deve cair, ou não; se deve ir para os lados, ou não. E vamos criar os estados também!
E teremos algo novo também, uma tarefa! Tarefa é uma nomenclatura usada no Panda3D, que se refere: A necessidade de prover a certos objetos de jogo, loops de execução. Isso nos permite criar uma ação que se repetirá a cada frame do jogo! Veja maiores detalhes aqui.

O código do módulo avatar ficará assim:
[code lang=”python”]
from direct.showbase.DirectObject import DirectObject
from direct.actor.Actor import Actor
from direct.task import Task

#modulo da classe Avatar
class Avatar(DirectObject):
def __init__(self):
self.persona = render.attachNewNode(‘persona’)
self.personaActor = Actor(‘../assets/eggs/personagem.egg’,
{‘idle’:’../assets/eggs/personagem-parado’,
‘run’ :’../assets/eggs/personagem-correr’,
‘jump’:’../assets/eggs/personagem-pular’}
)
self.personaActor.setScale(.3)
self.personaActor.setPos(0,0,1.5)
self.personaActor.reparentTo(self.persona)

self.persona.setPos(0,30,0)
self.persona.setH(90)

self.state = { ‘flying’:True,
‘left’ :False,
‘right’ :False
}

taskMgr.add(self.movement, “Avatar Movement”)

def movement(self, task):
if(self.state[‘flying’] == True):
self.persona.setZ( self.persona.getZ() – 0.1 )
return Task.cont
[/code]

O que temos de novo:
1 – um dicionário de estados: self.state = {‘flying’:True, ‘left’:False, ‘right’:False}
::> Ele guardará as informações se o avatar esta voando (‘flying’), ou seja não está tocando a plataforma; E ainda se o avatar esta se deslocando para a esquerda(‘left’) ou direita(‘right’). O estado ‘flying’ é o único marcado como True, porque assim que o jogo começa, nosso personagem está no alto.
2 – Temos um gerenciador de tarefas: taskMgr.add(self.movement, “Avatar Movement”)
::> Nesse gerenciador, definimos a função self.movement, como a função que será executada a cada quadro, e nomeamos essa tarefa como “Avatar Movement”.
3 – Uma nova função: def movement(self, task):
::> Essa é a função que será executada pelo gerenciador de tarefas a cada frame do jogo.
Nessa função, pelo fato dela ser chamada pelo gerenciador de tarefas, obrigatoriamente ela deve receber o argumento ‘task’, e retornar uma constante de Task, no caso Task.cont, para continuar a tarefa no próximo frame.
Também temos uma pequena lógica na função. Ela avalia se o estado ‘flying’ é verdadeiro, e se for, o posicionamento do avatar no eixo Z será modificado, reduzindo o valor, e a ilusão com isso, será que o avatar está caindo.

Até o dado momento, o avatar será “jogado para baixo” infinitamente. Vamos fazer com que ele para quando sua altura for igual a altura da plataforma, altera a função movement dessa forma:

[code lang=”python”]
def movement(self, task):
if(self.state[‘flying’] == True):
self.persona.setZ( self.persona.getZ() – 0.1 )
if(self.persona.getZ() < -2.25):
self.state[‘flying’]=False
[/code]

Nessa alteração, modificamos a função de forma que assim que o nodepath persona, chegar na altura da plataforma, o estado ‘flying’ se torna Falso (‘False’), e então, o avatar para de cair.

Aqui temos um exemplo bem simples de como construir uma lógica com base em valores, inteiros e boleanos. Em breve modificaremos esse código para solucionar um problema que veremos em breve.

Inputs de Usuário ————
Vamos adicionar a possibilidade do usuário pressionar as tecla direcionais e o avatar se deslocar pela tela, para a direita e esquerda. Isso é o que chamamos de Inputs de usuário, para outros detalhes clique aqui.
Inputs de usuários são eventos dentro do Panda3D. Para capturar esses eventos, devemos:
– declarar quais objetos devem ser notificados de certos eventos: accept()
– definir funções para executar ações;

veja abaixo as alterações no módulo avatar, discutiremos as alterações posteriormente:

[code lang=”python”]
from direct.showbase.DirectObject import DirectObject
from direct.actor.Actor import Actor
from direct.task import Task

#modulo da classe Avatar
class Avatar(DirectObject):
def __init__(self):
self.persona = render.attachNewNode(‘persona’)
self.personaActor = Actor(‘../assets/eggs/personagem.egg’,
{‘idle’:’../assets/eggs/personagem-parado’,
‘run’ :’../assets/eggs/personagem-correr’,
‘jump’:’../assets/eggs/personagem-pular’}
)
self.personaActor.setScale(1)
self.personaActor.setPos(0,0,5)
self.personaActor.reparentTo(self.persona)

self.smiley = loader.loadModel(‘smiley’)
self.smiley.reparentTo(self.persona)
self.smiley.setPos(0,0,0)
self.smiley.setScale(.2)

self.persona.setPos(0,10,0)
self.persona.setScale(.15)
self.persona.setH(90)

self.state = { ‘flying’:True,
‘left’ :False,
‘right’ :False
}

taskMgr.add(self.movement, “Avatar Movement”)

#capture keyboard events
self.accept(‘arrow_left’, self.changeState, [‘left’,True])
self.accept(‘arrow_left-up’, self.changeState, [‘left’,False])
self.accept(‘arrow_right’, self.changeState, [‘right’,True])
self.accept(‘arrow_right-up’, self.changeState, [‘right’,False])

def movement(self, task):
if(self.state[‘flying’] == True):
self.persona.setZ( self.persona.getZ() – 0.1 )
if(self.persona.getZ() < -2.3):
self.state[‘flying’]=False
#move sideways
else:
if(self.state[‘left’]):
self.persona.setX( self.persona.getX() – 0.1 )
elif(self.state[‘right’]):
self.persona.setX( self.persona.getX() + 0.1 )
return Task.cont

def changeState(self, key, value):
self.state[key] = value
[/code]

As novidades ficam após a linha 34.
Nas linhas 35, 36, 37 e 38, notificamos ao Panda3D, que nosso avatar deve ser notificado quando as teclas direcionais direita e esquerda forem pressionadas e soltas. Os eventos ‘arrow_left’ e ‘arrow_right’ acontecem quando o usuário pressiona as teclas. Os eventos ‘arrow_left-up’, e ‘arrow_right-up’, acontecem quando o usuário solta as teclas.
Posteriormente, dizemos que a função self.changeState é a função que será executada, a cada vez que nosso objeto avatar for notificado dos eventos. Para finalizar esses “accepts”, passamos as listas [‘left’,True], [‘left’,False], [‘right’,True] e [‘right’,False]. Essas listas serão utilizadas na função self.changeState, como parâmetros.

A função changeState, possui uma tarefa muito simples: mudar o estado do nosso avatar.
Ela está preparada com dois argumentos, key e value, que se referem aos estados no dicionário self.state, veja, self.state[key], ou seja o argumento ‘key’, deverá obrigatoriamente ser uma string (palavra) ‘left’ ou ‘right’, e os valores que serão definidos para o estado será sempre True ou False. Repare que os argumentos ‘key’ e ‘value’, enviados para a função, são aquelas listas [‘left’, True] e [‘right’,False]!!

Para finalizar essa implementação, e de fato fazer o avatar se deslocar pela tela, colocamos na função movement, uma lógica que observa se os estados ‘left’ e ‘right’ são verdadeiros para então deslocar o personagem pelo eixo X.

Sim!! Temos uma interatividade! Interatividade muito tosca… mas já é alguma coisa. Agora quando nosso avatar chega na plataforma, ele pode se deslocar!

Colisões ———-
No próximo post vamos melhorar significamente nosso código!
Se você reparar temos um grande problema: nosso avatar passa direto pelos obstáculos! Temos que implementar colisões!
Precisamos implementar uma colisão com a plataforma e com os obstáculos. Para isso usaremos algumas técnicas.
AVISO: No próximo post nosso código ganhará um pouco de complexidade!

Panda3D gamedev tutorial 1

Este é o primeiro post de uma série falando sobre desenvolvimento de jogos com Panda3D (versão 1.6).
Minha idéia é uma introdução sobre desenvolvimento de jogos, e eu escolhi o Panda3D como game engine, porque é um ótimo software, livre e multi-plataforma.

Antes de começar a seguir esses tutoriais, você precisa baixar uns arquivos, e seguir umas instruções para organizar as pastas dos arquivos. Acesse aqui.

O objetivo desse tutorial é fazer um jogo super simples: Uma garota, em um cenário futurista, tem que passar por umas barreiras e armadilhas.

Bueno… espero que vc já tenha o Panda3D 1.6.2 instalado, caso contrário, acesse www.panda3d.org e instale-o.

… vamos começar.

Para começar a escrever código do Panda3D, você precisa de um editor de texto simples, Microsoft Word ou Open Office Writer não servem!
Caso você trabalhe com Linux, Gedit é muito bom!
Caso você trabalhe com Mac, Xcode até dá… mas não é o melhor…
Caso você trabalhe com Windows, tente o Notepad++

Mas para todos eu sugiro o Eclipse com plugin de python (você pode usar o Eclipse for PHP), ele é um pouco difícil de configurar, mas vale o esforço.

Com tudo pronto, vamos digitar um código. Comece um novo arquivo, nomeie-o como Panda-tut-00.py, e vamos colocar o seguinte código:

[code lang=”python”]
import direct.directbase.DirectStart

run()
[/code]

Esse é o código mais simples que podemos fazer e ter o Panda3D funcionando!
A primeira linha importa a classe DirectStart, que define algumas configurações e inicia o sistema do Panda3D.
A linha run(), basicamente inicia o ‘main loop’ do Panda3D. Todo jogo precisa de um ‘main loop’. O main loop, é a mágica que nos fornece o “Frame”! Graças aos “frame por segundo”, podemos mudar o posicionamento dos objetos do jogo, durante o tempo, e assim a mágica acontece.

Para executar esse código, você precisa:

Caso você trabalhe com Linux (você já sabe oq fazer, eu sei…), navegue pelo Terminal até a pasta Panda-project/src, e execute: python Panda-tut-00.py
Caso você trabalhe com Mac (eu acho que você sabe oq fazer…), navegue pelo Terminal até a pasta Panda-project/src, e execute: python Panda-tut-00.py
Caso você trabalhe com Windows (ohh… Deus, que o Senhor tenha piedade de sua alma!)… Você precisará lembrar como navegar pelas pastas do seu sistema pelo Prompt de Comando, usando o comando ‘cd’. Para abrir o prompt de commando, vá ao menu iniciar, e na pesquisa digite “command”, e então navegue até a pasta Panda-project/src, e execute: python Panda-tut-00.py

Esse é o processo de como rodar as todos os códigos feitos para o Panda3D, decore!

Ok… vamos começar de verdade!

Vamos a um código de verdade, o anterior foi só para preparar alguma base. Vamos carregar para o jogo uma arena, e um plano de fundo:

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

import direct.directbase.DirectStart
from pandac.PandaModules import *

from direct.task.Task import Task
from direct.showbase.DirectObject import DirectObject

#game class
class Game(DirectObject):
def __init__(self):
#load platform model
self.plataforma = loader.loadModel(“../assets/eggs/plataformaBase”)
self.plataforma.reparentTo(render)
self.plataforma.setPos(0,10,0)

#load background sky
self.wall = loader.loadModel(‘../assets/eggs/papelParede’)
self.wall.reparentTo(render)
self.wall.setScale(1)
self.wall.setPos(-5, 30, -2)

game = Game()
run()
[/code]

Ao rodar o programa você verá algo assim:
Tela inicial

LEMBRANDO: Isto é um código para computadores, nenhum erro de digitação é tolerado. Uma única letra errada, causará um erro. Outro erro bem comum é quando o Panda3D não encontra os arquivos que estamos carregando para o jogo, isso geralmente acontece se vc digitar o endereço dos arquivos errado, ou se não organizou os arquivos de forma correta… dá uma lida aqui.

O que nós fizemos:

1 – Tá vendo aquele número no canto superior direito da tela? São nossos frames por segundo! essas duas primeiras linhas são responsáveis por ele aparecer alí:
[code lang=”python”]
from pandac.PandaModules import loadPrcFileData
loadPrcFileData(”,’show-frame-rate-meter 1′)
[/code]

2 – Agora pedimos ao Panda3D, algumas das ferramentas que ele tem disponível para trabalhar. O Panda3D, é um conjunto de muitas ferramentas para desenvolvimento de games, e não precisamos de todas a todo momento, então pedimos essas ferramentas na medida que as usamos:
[code lang=”python”]
import direct.directbase.DirectStart
from pandac.PandaModules import *

from direct.task.Task import Task
from direct.showbase.DirectObject import DirectObject
[/code]

3 – Como somos pessoas do bem, criaremos nosso jogo com “programação orientada a objetos”, isso quer dizer que cada objeto de nosso jogo será criado com um conjunto de código separado dos outros. E para começar, nossa própria lógica do jogo será um objeto em sí, e escreveremos uma classe para ela:
[code lang=”python”]
class Game(DirectObject):
def __init__(self):

self.platforma = loader.loadModel(“../assets/eggs/plataformaBase”)
self.platforma.reparentTo(render)
self.platforma.setPos(0,10,0)

self.wall = loader.loadModel(‘../assets/eggs/papelParede’)
self.wall.reparentTo(render)
self.wall.setScale(1)
self.wall.setPos(-5, 30, -2)
[/code]

4 – Finalmente, instanciamos nossa classe do jogo, e executamos o main loop:
[code lang=”python”]
game = Game()

run()
[/code]

Ponto crucial

Nesse momento vamos falar sobre o que esta acontecendo dentro da classe Game. A classe Game é inicializada na função def __init__(self):
Esse função é executada uma única vez quando o objeto é instanciado(trocando em miúdos, quando ele é criado), e nessa função nós mandamos o Panda3D configurar alguns elementos do jogo: a arena e o plano de fundo. Vejamos o exemplo da plataforma.
Primeiramente pedimos ao Panda3D para importar o modelo “plataformaBase”, e armazenar o ‘caminho’ para esse modelo na variável self.plataforma.

Porque esse é um ponto crucial?? Primeiro porque esse é o passo mais básico para carregar qualquer modelo para sua cena, mas mais importante que isso, precisamos entender esse tal de “render”.

O “render” é o nó raiz do grafo de cena do Panda3D! Essa explicação foi suficiente pra você? Ótimo! Pule para o próximo capítulo. Se a definição não lhe serviu, preste atenção na explicação.

Primeiro, o que é um grafo de cena? Um grafo de cena é uma forma estruturada de se organizar elementos em um ambiente gráfico.

Quase todos os softwares gráficos que conheço usam um grafo de cena. Basicamente um grafo de cena, possui um ‘nó’ principal, que aponta para outros elementos que apareceram na cena. Essa forma estruturada serve para otimizar trabalho do sistema de renderização, comunicação entre objetos da cena, collisões, etc… Dê uma olhada nesse link, e nesse também!
Qualquer objeto que tenha que aparecer na cena, precisa estar lincado, direta ou indiretamente ao grafo de cena. Um objeto pode se linkar ao gafo diretamente, ou a outro objeto que já esteja linkado.

Cada elemento adicionado ao grafo de cena, temos um nó (node) e um caminho (nodepath). O nó é criado no grafo de cena, e o caminho para o nó (nodepath), é alocado na variável do código python. ‘Nós’ e ‘caminho de nós’ (nodepaths), não são a mesma coisa, mas no Panda3D, nodepaths são muito mais usados do que os nós propriamente ditos. Então, quando o código diz: self.plataforma = load.loadModel(“../assets/eggs/plataformaBase”), o Panda3D, carrega o modelo 3D, cria um nó para o grafo de cena, salva o nodepath na variável self.plataforma.
Posteriormente, para que o objeto seja visível, é necessário linkar seu nó, ao ‘render’: self.plataforma.reparentTo(render).

Bem, o render é o nó principal desse grafo de cena, logo é o centro, o ponto de origem do sistema de coordenadas do Panda3D. Então, após importar o modelo, e linkar ao grafo, podemos posicioná-lo na cena: self.plataforma.setPos(0,10,0)

Esse processo se repetirá por todos os elementos que quisermos colocá-lo na cena. Então ao fim desse código teremos, uma estrutura com essa aparência:

Panda3D-sceneGraph_00

SUGESTÃO: brinque com as posições dos objetos, e com seus links:
– altere os valores das cooredenadas tanto da plataforma, quanto do wall, para entender melhor os valores;
– link o wall, à plataforma: self.wall.reparentTo(self.plataforma);
– brinque com as coordenadas;
– linke a plataforma ao wall, e modifique as coordenadas.
Isso lhe ajudará a entender melhor essas coisas no Panda3D.

This is the first post about game development in Panda3D. (version 1.6)

My idea is to introduce people in game development, I have chosen Panda3D as game engine, because it is a great software, free and multi-platform.

In order to start following these tutorials, you will need to download some files, and follow some instructions about how organize the files. Here is the link.

The goal here is to build a very simple game: A girl, in a futuristic scenario, who needs to avoid some barriers and traps.

I hope you already have Panda3D 1.6.2, otherwise, go to www.panda3d.org, download and install it. (There is some issue with MacOSX version, check forum to find solutions)

… let’s start.

To start writing Panda3D code all you need is a simple text editor, Microsoft Word or Open Office Writer do not work for this!
In case you work on Linux, Gedit is great!
In case you work on Mac, Xcode is… is ok, but…
In case you work on Windows, you should try Notepad++

But for all, I suggest Eclipse with python plugin (you can download Eclipse for PHP), it is a bit hard to setup, but it worth the effort.

So… if you are ready, let’s go and write some code. Start a new file, we could name it as Panda-tut-00.py, and write the following code:

[code lang=”python”]
import direct.directbase.DirectStart

run()
[/code]

This is the most simple code we can write to test if Panda3D is working!
The first line imports the DirectStart class, that starts Panda3D basic systems, and creates the screen where our game is gonna happened.
The line run(), basically starts the Panda3D main loop. Every game needs a main loop. The main loop, is a magical stuff that will give our game the “Frames”! And thanks for the frames per second, we can change game objects position in time, and all the magic is possible.

In order to run this simple code you have to:

In case you work on Linux (you know what to do, but…), you go to the Terminal, navigate to Panda-project/src folder, and run: python Panda-tut-00.py
In case you work on Mac (I think you know what to do…), you go to the Terminal, navigate to Panda-project/src folder, and run: python Panda-tut-00.py
In case you work on Windows (ohh… dear Lord, that God have mercy of your soul!)… You need to remember how to navigate using the Command Prompt, and the command ‘cd’. To start Command Prompt, go to Start menu >> All Programs >> Accessories >> Command Prompt, and navigate till the Panda-project/src folder, and run the command: python Panda-tut-00.py

…and that’s how you are gonna run and test, all our codes! You must know this process by heart!

Ok… let’s start for real!

Let’s start a real code… the previous was just to lay down some basis. Let’s start loading our scenario, our arena:

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

import direct.directbase.DirectStart
from pandac.PandaModules import *

from direct.task.Task import Task
from direct.showbase.DirectObject import DirectObject

#game class
class Game(DirectObject):
def __init__(self):
#load platform model
self.platform = loader.loadModel(“../assets/eggs/plataformaBase”)
self.platform.reparentTo(render)
self.platform.setPos(0,10,0)

#load background sky
self.wall = loader.loadModel(‘../assets/eggs/papelParede’)
self.wall.reparentTo(render)
self.wall.setScale(1)
self.wall.setPos(-5, 30, -2)

game = Game()
run()
[/code]

After running this code, you should see some thing like this:Tela inicial

JUST TO REMEMBER: this is a computer code, so, none errors in typing are tolerated. One single wrong letter, might cause an error. Also, another common error occurs when Panda3D do not find the models, we are asking him to bring to the game! So… follow the instructions here.

Let’s see what we have done:

1 – Are you seeing that little number on top-right corner? Those are your frames per second! and the first two lines are responsible for it to appear:
[code lang=”python”]
from pandac.PandaModules import loadPrcFileData
loadPrcFileData(”,’show-frame-rate-meter 1′)
[/code]

2 – Now we need to inform Panda3D, which “parts” of it we need for the job. Panda3D is very big tool, and we don’t need all of it’s parts all the time… so we are asking it to give us some of it’s tools:
[code lang=”python”]
import direct.directbase.DirectStart
from pandac.PandaModules import *

from direct.task.Task import Task
from direct.showbase.DirectObject import DirectObject
[/code]

3 – As we are good people, we will create our code using object oriented programming, it means that all our game objects will be created in deferent parts of code. To start with our own game logic, will be and object itself, for that we define a class for it:
[code lang=”python”]
class Game(DirectObject):
def __init__(self):

self.platform = loader.loadModel(“../assets/eggs/plataformaBase”)
self.platform.reparentTo(render)
self.platform.setPos(0,10,0)

self.wall = loader.loadModel(‘../assets/eggs/papelParede’)
self.wall.reparentTo(render)
self.wall.setScale(1)
self.wall.setPos(-5, 30, -2)
[/code]

4 – We must instantiate our game class, into an object, and run it:
[code lang=”python”]
game = Game()

run()
[/code]

Crucial point

At this point we must talk about what is going on inside the class Game. The Game class has an initialize function def __init__(self):
This function is called once when the object is been instantiated, so inside this function, we write all the code we need to set up or game object. At the moment, we just brought to the game our arena and a background. Let’s watch the self.platform.
First we tell Panda3D to load the “plataformaBase” model, and put it’s path into self.platform variable. Than, we reparent the platform to the ‘render’, and finally we give platform a position.

Why this is a crucial point? First because this is the basic step, to load whatever you want into the game. But above that, we must understand what is this ‘render’?

The ‘render’ object is Panda3D scene graph’s top node! Was that explanation enough for you? Greate! Skip to the next chapter. If that definition wasn’t enough for you, pay attention to the explanation.

First of all, what is a scene graph? A scene graph is a structure to organize hierarchically the elements of a scene.

Almost all graphic softwares I know use a scene graph. Basically a scene graph starts with a main object, which points to other objects that are included into the scene. Such structure is used to optimize rendering processes, comunications among elements, collisions, etc. Take a look at this link, and this one!
Basically any object we want to appear in the game scene, must be some how linked to the scene graph. This object can be linked straight to the “render” node, or linked to an object which must be linked to “render”.

Each element we add to the scene graph, we create a Node and a NodePath. The node is created into the scene graph, and the path to this node (nodepath) is stored into a variable we name, in python code. Node and nodepaths, are not the same thing, but in Panda3D, nodepaths are much more used than the node itself. So, when the code says : self.platform = loader.loadModel(“../assets/eggs/plataformaBase”), Panda3D, loads a model, and creates a scene graph node, and save a path to this node in the self.platform variable.
Latter, in order to make this model visable, it’s necessary to link this new node to the render: self.platform.reparentTo(render).

Well, the “render” is the top node on Panda3D scene graph, and it is the center, the origin point of Panda3D’s coordinates system. So, after having the model imported, and linked to the scene graph, we can set a position to it: self.platform.setPos(0,10,0)

This process is repeated to all objects we want to insert into the scene. At the end of this process, we will have a scene graph estructure like this:

Panda3D-sceneGraph_00

ADVICE: mass around with object’s position, and it’s links:
– change the coordinates values in platform and wall objects;
– link the wall, to the platform: self.wallreparentTo(self.plataforma);
– change the coordinates again;
– link the platform to the wall, and change the coordinates again.
This will help you to understand better this subject in Panda3D.