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.

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]

Animações :: Gamedev receitas Panda3D

Quase qualquer jogo eletrônico utiliza animações para tornar o jogo mais imersivo.

Em jogos 3D as animações, e os modelos, são feitos em softwares especializados, como o Maya, Blender, 3DStudio, e posteriormente importadas pela game engine.

Outro detalhe comum, é que as game engines, separam as informações dos modelos 3D, e das animações, e geram arquivos distintos. Assim, temos um arquivo para o modelo 3D de um dado personagem, e suas animações, cada uma em um arquivo distinto.

No Panda3D, depois de você ter o seu modelo, e animações prontas, é necessário convertê-los para o formato .egg. O próprio Panda3D, possui softwares para essas conversões, dependendo do seu modelador 3D.

No Panda3D, quando se utiliza um modelo, que possui uma animação de esqueleto, é necessário se utilizar a classe Actor.

Para seguir esse tutorial, você precisará baixar alguns arquivos e seguir algumas instruções. Visite esse link antes de prosseguir!!

exemplo 1:

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

class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.carregaModelos()

def carregaModelos(self):
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.reparentTo(render)
self.personaActor.setPos(0,20,0)
self.personaActor.loop(‘run’)

game = MyApp()

run()
[/code]

No exemplo acima, apenas importamos um modelos com animação. A construção da classe Actor, exige:

– apontar o modelo do ‘personagem’.
– um dicionário com os nomes das animações, e seus respectivos arquivos.

Neste caso temos:
– O modelo. Localizado na pasta: ‘../assets/eggs/personagem.egg’
– Animação ‘idle’. Localizada na pasta: ‘../assets/eggs/personagem-parado’
– Animação ‘run’. Localizada na pasta: ‘../assets/eggs/personagem-correr’
– Animação ‘jump’. Localizada na pasta: ‘../assets/eggs/personagem-pular’

Posteriormente, a única diferença entre um modelo normal, e um Actor, está no comando loop(). Esse comando defini qual animação irá ser executada.

exemplo 2:

[code lang=”python”]

from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor

class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.carregaModelos()

self.accept(‘w’, self.mudaAnimacao, [‘run’])
self.accept(‘space’, self.mudaAnimacao, [‘jump’])
self.accept(‘s’, self.mudaAnimacao, [‘idle’])

def carregaModelos(self):
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.reparentTo(render)
self.personaActor.setPos(0,20,0)
self.personaActor.loop(‘run’)

def mudaAnimacao(self, name):
self.personaActor.loop(name)

game = MyApp()

run()
[/code]

Neste segundo exemplo, temos uma simples demonstração de como se fazer uma troca de animações. Ao se pressionar a tecla ‘w’, o personagem corre; Ao se pressionar a tecla ‘space’, o personagem ‘pula’; Ao se pressionar a tecla ‘s’ o personagem fica parado.

Observe que a transição entre animações é brusca. No exemplo abaixo, há uma sugestão de uso de “blend” de animações, o que permite transições mais suaves.

[code lang=”python”]

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

from direct.showbase.ShowBase import ShowBase
from pandac.PandaModules import *
from direct.actor.Actor import Actor
from direct.interval.IntervalGlobal import *

class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.carregaModelos()

self.accept(‘w’, self.mudaAnimacao, [‘run’])
self.accept(‘space’, self.mudaAnimacao, [‘jump’])
self.accept(‘s’, self.mudaAnimacao, [‘idle’])

#self.state: auxilia na mudanca entre animacoes
self.state = { ‘idle’:True,
‘run’:False,
‘jump’:False}

#self.blend: Uso de ‘Interval’ com ‘LerpFunction’ para realizar as mudancas de animacoes
self.blend = LerpFunc(self.blendAnim, fromData = 0, toData =1,
duration = .2, blendType=’noBlend’,
extraArgs=[], name=None)

def carregaModelos(self):
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.reparentTo(render)
self.personaActor.setPos(0,20,0)

#abilita o blend de animacoes
self.personaActor.enableBlend()

#opcao por iniciar todas as animacoes
self.personaActor.loop(‘run’)
self.personaActor.loop(‘idle’)
self.personaActor.loop(‘jump’)

#setControlEffect: definie o ‘peso’ de cada animacao no personagem(1 = 100% ; 0.1 = 10%)
self.personaActor.setControlEffect(‘jump’, 0.0)
self.personaActor.setControlEffect(‘idle’, 1.0)
self.personaActor.setControlEffect(‘run’, 0.0)

def mudaAnimacao(self, name):
# Essa funcao recebe o input do usuario, com um parametro de
# qual animacao ira iniciar (name).
# A LerpFunction self.blend recebe como argumentos extras,
# o nome do estado (self.state – ver código da funcao getCurrentState),
# e o nome da animacao que deve iniciar
self.blend.extraArgs = [self.getCurrentState(), name]
self.blend.start()

def getCurrentState(self):
if self.state[‘idle’]: return ‘idle’
elif self.state[‘run’]: return ‘run’
elif self.state[‘jump’]: return ‘jump’
else: return None

def changeStateTo(self, name):
self.state[‘idle’] = False
self.state[‘run’] = False
self.state[‘jump’] = False
self.state[name] = True

def blendAnim(self, data, atual, destino):
# Nessa funcao, ativada pela LerpFunction,
# os ‘pesos’ das animacoes sao trocados, e a transicao
# fica mais suave.
self.personaActor.setControlEffect(atual, 1-data)
self.personaActor.setControlEffect(destino, data)

if data == 1:
#ao final da transicao, mudamos o estado.
self.changeStateTo(destino)

game = MyApp()

run()

[/code]Every game uses animations to provide more immersion to the players.

In 3D games, animations and models, are made in specific softwares like Maya, Blender, 3DStudio, etc, and later on they are imported by the game engine.

Other common detail is that game engines usually uses separate files to the 3D models, and other files for the animations. This approach allows developers to manipulate those files more independently.

In Panda3D, you 3D model files (the model itself as well the animations) must be converted to the .egg format. This is a specific Panda3D’s file type, gracefully Panda3D, has some software to make such conversions from 3D modelers. Check Panda3D’s web site for more details.

When you want to use some rigged animated model on Panda3D, you must use the class Actor.

In order to keep following this tutorial, you need to download some files, and follow some instructions to set then up correctly.

sample 1:

[code lang=”python”]

from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor

class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.loadModels()

def loadModels(self):
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.reparentTo(render)
self.personaActor.setPos(0,20,0)
self.personaActor.loop(‘run’)

game = MyApp()

run()

[/code]

In the sample above, a model with three animations was imported, and set up to run one animation. In this case, a rigged model is been used, so we need to instantiate the Actor class. The Actor class demands:

– import a model file;

– a python dictionary with the animation names as keys, and it’s files as values.

In this case:

– the model. Located at folder: ‘../assets/eggs/personagem.egg’

– Animation ‘idle’. Located at folder: ‘../assets/eggs/personagem-parado’

– Animation ‘run’. Located at folder: ‘../assets/eggs/personagem-correr’

– Animation ‘jump’. Located at folder: ‘../assets/eggs/personagem-pular’

At the end, a loop() command sets the animation ‘run’ to be executed as a infinity loop. In case you want to test, try change the line: self.personaActor.loop(‘run’), and change the ‘run’, inside the loop() function call to ‘idle’ and ‘jump’. You will see diferent animations.

sample 2:

[code lang=”python”]

from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor

class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.loadModels()

self.accept(‘w’, self.changeAnimation, [‘run’])
self.accept(‘space’, self.changeAnimation, [‘jump’])
self.accept(‘s’, self.changeAnimation, [‘idle’])

def loadModels(self):
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.reparentTo(render)
self.personaActor.setPos(0,20,0)
self.personaActor.loop(‘run’)

def changeAnimation(self, name):
self.personaActor.loop(name)

game = MyApp()

run()

[/code]

In sample 2, there is a example showing how to change animations with user input. By pressing key ‘w’ the ‘run’ animation is played; by pressing key ‘s’, the ‘idle’ animation is played; And by pressing key ‘space’, the ‘jump’ animation is played.

You will see that the animation transition is quite bad. That because there is no blending during animation transitions. In next sample, we will fix that.

sample 3:

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

from direct.showbase.ShowBase import ShowBase
from pandac.PandaModules import *
from direct.actor.Actor import Actor
from direct.interval.IntervalGlobal import *

class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.loadModels()

self.accept(‘w’, self.changeAnimation, [‘run’])
self.accept(‘space’, self.changeAnimation, [‘jump’])
self.accept(‘s’, self.changeAnimation, [‘idle’])

#self.state: helps in changing animation logic
self.state = { ‘idle’:True,
‘run’:False,
‘jump’:False}

#self.blend: The ‘Interval’ with ‘LerpFunction’ is used to change the control effect smoothly
self.blend = LerpFunc(self.blendAnim, fromData = 0, toData =1,
duration = .2, blendType=’noBlend’,
extraArgs=[], name=None)

def loadModels(self):
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.reparentTo(render)
self.personaActor.setPos(0,20,0)

#enable Blend in animations (mandatory)
self.personaActor.enableBlend()

#let’s run all animations in loop at once
self.personaActor.loop(‘run’)
self.personaActor.loop(‘idle’)
self.personaActor.loop(‘jump’)

#setControlEffect: sets the “weight” that each animation will impolse to the model(1 = 100% ; 0.1 = 10%)
self.personaActor.setControlEffect(‘jump’, 0.0)
self.personaActor.setControlEffect(‘idle’, 1.0)
self.personaActor.setControlEffect(‘run’, 0.0)

def changeAnimation(self, name):
# This function will be called by an user input, with the argument ‘name’,
# this ‘name’, is the name for the next animation to play.
# The LerpFunction self.blend, shall receive as extra arguments,
# the name for current state (self.state – check getCurrentState function code),
# the name for the next animation
self.blend.extraArgs = [self.getCurrentState(), name]
self.blend.start()

def getCurrentState(self):
if self.state[‘idle’]: return ‘idle’
elif self.state[‘run’]: return ‘run’
elif self.state[‘jump’]: return ‘jump’
else: return None

def changeStateTo(self, name):
self.state[‘idle’] = False
self.state[‘run’] = False
self.state[‘jump’] = False
self.state[name] = True

def blendAnim(self, data, current, next):
# This function is called every frame during LerpFunction life (0.2 seconds),
# the animation’s ‘weight’ is changed slowly, and transitions blends.
# Smooth transitions!!
self.personaActor.setControlEffect(current, 1-data)
self.personaActor.setControlEffect(next, data)

if data == 1:
# at the end, we change the current state.
self.changeStateTo(next)

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.


Tarefas :: Gamedev receitas Panda3D

Bem, “Tarefas” (Tasks), é uma nomenclatura usada no Panda3D, 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 Panda3D.

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

def funcaoDeTarefa(self, task):
print “Essa funcao e executada uma vez a cada frame do jogo. “, task.time
return Task.cont

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

No Panda3D a construção de uma Task, é muito simples.
Na linha: self.taskMgr.add(self.funcaoDeTarefa, “Tarefa de teste”), é feito o registro de uma função que será executada a cada frame, e essa task é batizada com o nome “Tarefa de teste”.
Podemos definir as funções que queremos registrar como tarefa, e dar os nomes que acharmos mais adequados.
A função para tarefa, do exemplo, possui um parâmetro obrigatório, “task”, e um valor de retorno, igualmente obrigatório, que é uma das constantes do objeto Task, no caso, Task.cont.

exemplo 2:

[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 “Essa funcao e executada uma vez a cada frame do jogo. “, task.time
self.cubo.setX(self.cubo.getX()+0.01)
return Task.cont

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

Neste segundo exemplo, é possível observar que com uma tarefa, podemos, por exemplo, modificar o posicionamento de um objeto de jogo. Neste caso, na inicialização do jogo, posicionamos um cubo na coordenada x: -3, y:10, e z:-2. Na tarefa, o que fazemos é modificar a posição x do cubo, em 0.01 a cada frame, de forma que temos a ilusão do cubo andar para a direita.

No Panda3D é possível manipular as tarefas de forma a criar alguns comportamentos “customizados” de tarefas, como por exemplo, parar a tarefa, fazê-la executar depois de um certo tempo, etc.

exemplo 3:

[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 “Essa funcao e executada uma vez a cada frame do jogo. “, task.time
self.cubo.setX(self.cubo.getX()+0.01)
if self.cubo.getX() < 3:
return Task.cont
else:
return Task.done

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

Neste exemplo, o valor de retorno será Task.cont (“Tarefa.continuar”) enquanto a posição x do cubo seja menor que 3. Quando o cudo passar a posição 3, o valor de retorno será Task.done (“tarefa.concluída”), e a tarefa não mais será realizada.

exemplo 4:
[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.doMethodLater(2, 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 “Delay: “, task.delayTime
print “Frame: “, task.frame
print “Essa funcao e executada uma vez a cada frame do jogo. “, task.time
task.delayTime += 1
self.cubo.setX(self.cubo.getX()+0.5)
if self.cubo.getX() < 3:
return Task.again
else:
return Task.done

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

Neste último exemplo, pode-se ver que o cubo se desloca com intervalos de dois em dois segundos. Isso por que nesse exemplo, está sendo usado o método doMethodLater, onde é definido um tempo de 2 segundos para inicializar a tarefa. E quando a tarefa é executada, define-se um tempo de atraso (task.delayTime) para a próxima execução da tarefa.

No Panda3D, as tarefas são extremamente úteis em vários momentos, e para várias finalidade dentro de um jogo.

Conclusão —————————–

Comparando-se as duas engines, vemos que ambas tratam o mesmo tópico de maneiras distintas, e com particularidades, onde para se chegar aos mesmo objetivos, tem-se que contruir lógicas de maneiras diversas.

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.

Publicado: Livro Panda3D GE

Acabei de receber a minha cópia do livro Panda3D 1.6 Game Engine, escrito por David Brian Mathews (publicado pela Packt Publishing), o qual eu foi revisor.

O livro é muito bom para quem está começando a aprender o Panda3D.

O Panda3D é uma engine muito simples de ser usada, maleável na sua programação. Eu comecei a estudar o Panda3D na minha pós-graduação, e de tão simples, a usamos em nosso projeto de conclusão.

O livro escrito pelo David, está muito bem escrito e simples de se entender, embora exija um mínimo de compreensão de programação, e sua lógica. Mas leitores iniciantes em desenvolvimento de jogos entenderão com facilidade as ferramentas do Panda3D descritas neste livro, que se não me engano, é a única publicação sobre essa ferramenta, claro excluindo-se alguns posta e o próprio manual no site do Panda.

Em um breve começarei a escrever alguns comentários mais detalhados sobre tópicos do livro. Algumas especificidades para Mac e Linux, algumas formas de se resolver os problemas propostos no livro, e uma iniciação em programação usando o Panda3D.

mini-curso Inteligência Artificial :: Processing

Olá,

Esse post contém o conteúdo de aulas que ministrei no SESC Carmo/SP sobre código criativo e inteligência artificial, baseado nos texto de Daniel Shiffman.

Os vídeos estão em listas de reprodução do Youtube, que vc pode assistir aqui mesmo no blog.

Aula 1:

Aula 2:

Aula 3:

Aula 4:

Aula 5:

ótimo!!! Deu pau no servidor!

Olá,

Bem, essa semana estou resolvendo um grande pepino: Achar um novo servidor para meu site!

Depois de anos de bom relacionamento com a 000webhost, eles simplesmente sumiram com todo meu conteúdo! Uma falta de respeito! E com isso todos os posts do meu blog foram para o saco!

Todos meus tutoriais de Panda3D e Processing, se foram! Agora terei de reescrevê-los… mas bola pra frente. Vou começar por alguns que possuem conteúdo em outros sites, como umas aulas de Processing e Inteligência Artificial, e Processing com Computer Vision, que ambos possuem alguns vídeos no Youtube…. e por aí vai…

abs,