8 min read

Panda3D 1.6 Game Engine Beginner’s Guide

Panda3D 1.6 Game Engine Beginner's Guide

Create your own computer game with this 3D rendering and game development framework

  • The first and only guide to building a finished game using Panda3D
  • Learn about tasks that can be used to handle changes over time
  • Respond to events like keyboard key presses, mouse clicks, and more
  • Take advantage of Panda3D’s built-in shaders and filters to decorate objects with gloss, glow, and bump effects
  • Follow a step-by-step, tutorial-focused process that matches the development process of the game with plenty of screenshots and thoroughly explained code for easy pick up       

Actors and Animations

An Actor is a kind of object in Panda3D that adds more functionality to a static model. Actors can include joints within them. These joints have parts of the model tied to them and are rotated and repositioned by animations to make the model move and change. Actors are stored in .egg and .bam files, just like models.

Animation files include information on the position and rotation of joints at specific frames in the animation. They tell the Actor how to posture itself over the course of the animation. These files are also stored in .egg and .bam files.

Time for action – loading Actors and Animations

Let’s load up an Actor with an animation and start it playing to get a feel for how this works:

  1. Open a blank document in NotePad++ and save it as Anim_01.py in the Chapter09 folder.
  2. We need a few imports to start with. Put these lines at the top of the file:
    import direct.directbase.DirectStart
    from pandac.PandaModules import *
    from direct.actor.Actor import Actor
  3. We won’t need a lot of code for our class’ __init__ method so let’s just plow through it here :

    class World:
    def __init__(self):
    base.disableMouse()
    base.camera.setPos(0, -5, 1)
    self.setupLight()
    self.kid = Actor(“../Models/Kid.egg”,
    {“Walk” : “../Animations/Walk.egg”})
    self.kid.reparentTo(render)
    self.kid.loop(“Walk”)
    self.kid.setH(180)

    
    
  4. The next thing we want to do is steal our setupLight() method from the Track class and paste it into this class:

    def setupLight(self):
    primeL = DirectionalLight(“prime”)
    primeL.setColor(VBase4(.6,.6,.6,1))
    self.dirLight = render.attachNewNode(primeL)
    self.dirLight.setHpr(45,-60,0)
    render.setLight(self.dirLight)
    ambL = AmbientLight(“amb”)
    ambL.setColor(VBase4(.2,.2,.2,1))
    self.ambLight = render.attachNewNode(ambL)
    render.setLight(self.ambLight)
    return

    
    
  5. Lastly, we need to instantiate the World class and call the run() method.
    w = World()
    run()
  6. Save the file and run it from the command prompt to see our loaded model with an animation playing on it, as depicted in the following screenshot:

    Animating in Panda3D

What just happened?

Now, we have an animated Actor in our scene, slowly looping through a walk animation. We made that happen with only three lines of code:

self.kid = Actor("../Models/Kid.egg",
    {"Walk" : "../Animations/Walk.egg"})
  self.kid.reparentTo(render)
  self.kid.loop("Walk")

The first line creates an instance of the Actor class. Unlike with models, we don’t need to use a method of loader. The Actor class constructor takes two arguments: the first is the filename for the model that will be loaded. This file may or may not contain animations in it. The second argument is for loading additional animations from separate files. It’s a dictionary of animation names and the files that they are contained in. The names in the dictionary don’t need to correspond to anything; they can be any string.

myActor = Actor( modelPath,
{NameForAnim1 : Anim1Path, NameForAnim2 : Anim2Path, etc})

The names we give animations when the Actor is created are important because we use those names to control the animations. For instance, the last line calls the method loop() with the name of the walking animation as its argument.

If the reference to the Actor is removed, the animations will be lost. Make sure not to remove the reference to the Actor until both the Actor and its animations are no longer needed.

Controlling animations

Since we’re talking about the loop() method, let’s start discussing some of the different controls for playing and stopping animations. There are four basic methods we can use:

  • play(“AnimName”): This method plays the animation once from beginning to end.
  • loop(“AnimName”): This method is similar to play, but the animation doesn’t stop when it’s over; it replays again from the beginning.
  • stop() or stop(“AnimName”): This method, if called without an argument, stops all the animations currently playing on the Actor. If called with an argument, it only stops the named animation.

    Note that the Actor will remain in whatever posture they are in when this method is called.

  • pose(“AnimName”, FrameNumber): Places the Actor in the pose dictated by the supplied frame without playing the animation.

We have some more advanced options as well. Firstly, we can provide option fromFrame and toFrame arguments to play or loop to restrict the animation to specific frames.

myActor.play("AnimName", fromFrame = FromFrame, toFrame = toFrame)

We can provide both the arguments, or just one of them. For the loop() method, there is also the optional argument restart, which can be set to 0 or 1. It defaults to 1, which means to restart the animation from the beginning. If given a 0, it will start looping from the current frame.

We can also use the getNumFrames(“AnimName”) and getCurrentFrame(“AnimName”) methods to get more information about a given animation. The getCurrentAnim() method will return a string that tells us which animation is currently playing on the Actor.

The final method we have in our list of basic animation controls sets the speed of the animation.

myActor.setPlayRate(1.5, "AnimName")

The setPlayRate() method takes two arguments. The first is the new play rate, and it should be expressed as a multiplier of the original frame rate. If we feed in .5, the animation will play half as fast. If we feed in 2, the animation will play twice as fast. If we feed in -1, the animation will play at its normal speed, but it will play in reverse.

Have a go hero – basic animation controls

Experiment with the various animation control methods we’ve discussed to get a feel for how they work. Load the Stand and Thoughtful animations from the animations folder as well, and use player input or delayed tasks to switch between animations and change frame rates. Once we’re comfortable with what we’ve gone over so far, we’ll move on.

Animation blending

Actors aren’t limited to playing a single animation at a time. Panda3D is advanced enough to offer us a very handy functionality, called blending. To explain blending, it’s important to understand that an animation is really a series of offsets to the basic pose of the model. They aren’t absolutes; they are changes from the original. With blending turned on, Panda3D can combine these offsets.

Time for action – blending two animations

We’ll blend two animations together to see how this works.

  1. Open Anim_01.py in the Chapter09 folder.
  2. We need to load a second animation to be able to blend. Change the line where we create our Actor to look like the following code:

    self.kid = Actor(“../Models/Kid.egg”,
    {“Walk” : “../Animations/Walk.egg”,
    “Thoughtful” : “../Animations/Thoughtful.egg”})

    
    
  3. Now, we just need to add this code above the line where we start looping the Walk animation:
    self.kid.enableBlend()
    self.kid.setControlEffect("Walk", 1)
    self.kid.setControlEffect("Thoughtful", 1)
  4. Resave the file as Anim_02.py and run it from the command prompt.

    Animating in Panda3D

What just happened?

Our Actor is now performing both animations to their full extent at the same time. This is possible because we made the call to the self.kid.enableBlend() method and then set the amount of effect each animation would have on the model with the self.kid.setControlEffect() method. We can turn off blending later on by using the self.kid.disableBlend() method, which will return the Actor to the state where playing or looping a new animation will stop any previous animations. Using the setControlEffect method, we can alter how much each animation controls the model. The numeric argument we pass to setControlEffect() represents a percentage of the animation’s offset that will be applied, with 1 being 100%, 0.5 being 50%, and so on. When blending animations together, the look of the final result depends a great deal on the model and animations being used. Much of the work needed to achieve a good result depends on the artist.

Blending works well for transitioning between animations. In this case, it can be handy to use Tasks to dynamically alter the effect animations have on the model over time.

Honestly, though, the result we got with blending is pretty unpleasant. Our model is hardly walking at all, and he looks like he has a nervous twitch or something. This is because both animations are affecting the entire model at full strength, so the Walk and Thoughtful animations are fighting for control over the arms, legs, and everything else, and what we end up with is a combination of both animation’s offsets.

Furthermore, it’s important to understand that when blending is enabled, every animation with a control effect higher than 0 will always be affecting the model, even if the animation isn’t currently playing. The only way to remove an animation’s influence is to set the control effect to 0.

This obviously can cause problems when we want to play an animation that moves the character’s legs and another animation that moves his arms at the same time, without having them screw with each other. For that, we have to use subparts.

 


Subscribe to the weekly Packt Hub newsletter

* indicates required

LEAVE A REPLY

Please enter your comment!
Please enter your name here