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 complete 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]