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!

2 comentários sobre “Panda3D gamedev tutorial 2”

    1. Olá Apoena!
      FSM é um conceito básico de inteligência artificial usado em várias aplicações. Seria, sim, possível usá-lo no avatar.
      Eu estou pensando em usar FSM no próximo tutorial, falando dos menus e interfaces… vou pensar uma forma de utilizá-lo de forma que possa lhe ser útil. Aguarde!
      abs!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *