On this page
Introduction
Awesome! You have a world, so now let’s give it an inhabitant.
Task
To make something move in a video game, you have to think about how games actually work.
Much like a flip book, a game is just a series of still pictures drawn on your screen very quickly — often 60 times every single second. Each picture is called a frame.
To make our panda spin, we can’t just tell the computer “spin the panda.” We have to give the game a repeating rule: “Every time you draw a frame, turn the panda a tiny bit more.”
In Panda3D, this repeating rule is called a Task.
from direct.showbase.ShowBase import ShowBase
app = ShowBase()
# --- THE SCENERY ---scene = app.loader.loadModel("models/environment")scene.reparentTo(app.render)scene.setScale(0.25)scene.setPos(0, 60, 0)
# --- THE PANDA ---# 1. Load the panda model and put it on the stagepanda = app.loader.loadModel("models/panda-model")panda.reparentTo(app.render)
# 2. Shrink it down (the built-in panda is HUGE!)panda.setScale(0.005)panda.setPos(0, 20, 0)
# --- THE ANIMATION ---# 3. Create a rule for spinningdef spin_panda(task): # Calculate a new angle based on the game's built-in stopwatch angle = task.time * 50.0
# Set the panda's rotation panda.setHpr(angle, 0, 0)
# Tell the engine to keep repeating this rule return task.cont
# 4. Give the rule to the game's "Task Manager"app.taskMgr.add(spin_panda, "SpinPandaTask")
app.run()Breaking Down the Magic
Run that in your terminal and you should see a giant panda spinning happily in front of your scenery! Here is exactly what is happening in the new code:
-
def spin_panda(task): The worddefis Python’s way of saying “I am defining a new rule.” We named this rulespin_panda. -
angle = task.time * 50.0. EveryTaskhas a built-in stopwatch (task.time) that tracks how many seconds the game has been running. By multiplying the time by 50, we create a number that constantly grows larger as the seconds tick by. We store that growing number in a container we namedangle. -
panda.setHpr(angle, 0, 0). This is how things rotate in 3D space. HPR stands for Heading, Pitch, and Roll:- Heading: Turning left and right (like shaking your head “no”).
- Pitch: Tilting forward and backward (like nodding your head “yes”).
- Roll: Tilting side-to-side (like doing a cartwheel).
Because we put our constantly growing angle number into the Heading slot, the panda continuously turns left!
-
return task.cont. This translates to “Task Continue.” It tells the game engine, “Once you finish this rule, don’t stop. Do it again on the next frame.” -
app.taskMgr.add(spin_panda, "SpinPandaTask"). Writing the rule isn’t enough; we have to hire someone to enforce it. The “Task Manager” is your backstage crew chief. This line hands the rule over to the crew chief so it actually starts running.
The Python class Actor is designed to hold an animatable model and a set of animations.
Actor
Let’s bring our panda to life!
Right now, the panda we loaded is just a static 3D model. You can think of a standard model like a statue—it looks nice, but its joints are frozen solid. To make the legs bend and the arms swing, game developers use a technique called “skeletal animation” or “rigging,” which puts an invisible skeleton inside the model.
In Panda3D, to use a model that has a moving skeleton, we can’t use the standard loadModel command anymore. Instead, we need a special blueprint called an Actor.
Since the Actor class inherits from NodePath, everything that can be done to a NodePath, such as reparentTo() and setPos(), etc., may also be done to an Actor.
In addition to the basic NodePath functionality, Actors have several additional methods to control animation. In order for Actors to animate, their pointer (variable) must be retained in memory.
https://docs.panda3d.org/1.10/python/programming/models-and-actors/actor-animations
Here is the updated code.
from direct.showbase.ShowBase import ShowBasefrom direct.actor.Actor import Actor
app = ShowBase()
# --- THE SCENERY ---scene = app.loader.loadModel("models/environment")scene.reparentTo(app.render)scene.setScale(0.25, 0.25, 0.25)scene.setPos(0, 60, 0)
# --- THE PANDA ---# 1. Load the panda as an "Actor" and give it its animation filespanda = Actor("models/panda-model", {"walk": "models/panda-walk4"})
# 2. Put it on the stage and adjust size/positionpanda.reparentTo(app.render)panda.setScale(0.005)panda.setPos(0, 20, 0)
# 3. Tell the panda to constantly repeat its walk animationpanda.loop("walk")
# --- THE SPINNING RULE ---def spin_panda(task): angle = task.time * 50.0 panda.setHpr(angle, 0, 0) return task.cont
app.taskMgr.add(spin_panda, "SpinPandaTask")
app.run()What Changed?
If you run this, you will now see the panda continuously walking in a circle!
Here is the breakdown of the new pieces:
-
from direct.actor.Actor import Actor. Just like we did with theShowBaseblueprint, we are going back into the Panda3D library to grab a new blueprint calledActor. This tells Python how to handle moving skeletons. -
panda = Actor("models/panda-model", {"walk": "models/panda-walk4"}). This is the most important change. We are combining two different files to create our character:"models/panda-model"is the physical body of the panda (the fur, the shape)."models/panda-walk4"is a hidden file in Panda3D that contains just the math for how a panda’s skeleton should step and sway.- By pairing them up, we are saying: “Here is your body, and here is a movement called ‘walk’. Memorize this movement!”
-
panda.loop("walk"). This tells theActorto play the “walk” movement we just taught it, and when it reaches the end of the animation, loop back to the beginning and do it again forever.
Because we left our spin_panda task in the code, the game is spinning the entire panda at the exact same time the panda is moving its legs.
Events
Taking control of a character is where a program truly starts to feel like a game.
To make this happen, we have to teach our game two fundamental programming concepts: Events (listening for you to press a key) and Logic (deciding what to do based on those key presses).
Here is the updated code.
Notice that we completely removed the old spin_panda task and replaced it with a new keyboard setup and a move_panda task.
from direct.showbase.ShowBase import ShowBasefrom direct.actor.Actor import Actor
app = ShowBase()
# --- THE SCENERY ---scene = app.loader.loadModel("models/environment")scene.reparentTo(app.render)scene.setScale(0.25)scene.setPos(0, 60, 0)
# --- THE PANDA ---panda = Actor("models/panda-model", {"walk": "models/panda-walk4"})panda.reparentTo(app.render)panda.setScale(0.005)panda.setPos(0, 20, 0)panda.loop("walk")
# --- KEYBOARD CONTROLS ---# 1. Create a "cheat sheet" to track which keys are currently held downkeys = {"turn_left": False, "turn_right": False, "walk_forward": False}
# 2. A rule to update our cheat sheetdef update_key(key_name, is_pressed): keys[key_name] = is_pressed
# 3. Tell the game engine to listen for specific keyboard eventsapp.accept("arrow_left", update_key, ["turn_left", True])app.accept("arrow_left-up", update_key, ["turn_left", False])
app.accept("arrow_right", update_key, ["turn_right", True])app.accept("arrow_right-up", update_key, ["turn_right", False])
app.accept("arrow_up", update_key, ["walk_forward", True])app.accept("arrow_up-up", update_key, ["walk_forward", False])
# --- THE MOVEMENT RULE ---def move_panda(task): # Turn left or right if keys["turn_left"]: panda.setH(panda.getH() + 2) if keys["turn_right"]: panda.setH(panda.getH() - 2)
# Walk forward if keys["walk_forward"]: # Move the panda forward relative to the direction it is facing panda.setY(panda, -4)
return task.cont
app.taskMgr.add(move_panda, "MovePandaTask")
app.run()Breaking Down the Magic
You can now use your up, left, and right arrow keys to walk the panda around the environment!
Here is how the new pieces work:
-
The “Cheat Sheet” (
keys = {...}). Computers have terrible short-term memory unless you explicitly tell them to remember something. We created a checklist called a Dictionary. By default, we setturn_left,turn_right, andwalk_forwardtoFalse(meaning the keys are not being pressed). -
The Listeners (
app.accept). These lines tell the game to keep an ear out for specific events.- When you press the left arrow (
"arrow_left"), it updates the cheat sheet, turningturn_lefttoTrue. - When you let go of the left arrow (
"arrow_left-up"— the-upmeans the key popped back up), it changes it back toFalse.
- When you press the left arrow (
-
_The Logic (
ifstatements). Inside ourmove_pandatask, we useifstatements. This is exactly how humans make decisions. The computer asks: “If'turn_left'is True on the cheat sheet, what do I do?” It then reads the indented code underneath it and rotates the panda slightly. -
Relative Movement (
panda.setY(panda, -4)). Normally, setting a position in 3D space moves an object based on the “World” coordinates (North, South, East, West). But if the panda turns, we want it to walk in the direction it is facing, not the direction the world is facing.By putting
pandainside the parentheses before the number-4, we are telling the engine: “Move the panda along its own personal Y-axis.” (Note: The built-in Panda3D model actually faces backwards down its Y-axis, which is why we have to use a negative number to move it forward!)
Camera
The camera is just another object in your 3D world. You can move it, rotate it, and attach it to other things exactly like we did with the panda.
To create a true “third-person” game, we want the camera to constantly hover behind our hero and follow them wherever they go. We can achieve this by updating the camera’s position every single frame right inside our move_panda task!
Here is the final, completed code.
from direct.showbase.ShowBase import ShowBasefrom direct.actor.Actor import Actor
app = ShowBase()
# 1. Disable the engine's default mouse controls so we can control the camera manuallyapp.disableMouse()
# --- THE SCENERY ---scene = app.loader.loadModel("models/environment")scene.reparentTo(app.render)scene.setScale(0.25)scene.setPos(0, 60, 0)
# --- THE PANDA ---panda = Actor("models/panda-model", {"walk": "models/panda-walk4"})panda.reparentTo(app.render)panda.setScale(0.005)panda.setPos(0, 20, 0)panda.loop("walk")
# --- KEYBOARD CONTROLS ---keys = {"turn_left": False, "turn_right": False, "walk_forward": False}
def update_key(key_name, is_pressed): keys[key_name] = is_pressed
app.accept("arrow_left", update_key, ["turn_left", True])app.accept("arrow_left-up", update_key, ["turn_left", False])
app.accept("arrow_right", update_key, ["turn_right", True])app.accept("arrow_right-up", update_key, ["turn_right", False])
app.accept("arrow_up", update_key, ["walk_forward", True])app.accept("arrow_up-up", update_key, ["walk_forward", False])
# --- THE MOVEMENT & CAMERA RULE ---def move_panda(task): # Turn left or right if keys["turn_left"]: panda.setH(panda.getH() + 2) if keys["turn_right"]: panda.setH(panda.getH() - 2)
# Walk forward if keys["walk_forward"]: panda.setY(panda, -4)
# 2. Make the camera follow the panda # Set the camera's position behind and slightly above the panda app.camera.setPos(panda, 0, 3000, 1200)
# Force the camera to point directly at the panda's center app.camera.lookAt(panda)
return task.cont
app.taskMgr.add(move_panda, "MovePandaTask")
app.run()Breaking Down the Magic
Run this code, and you finally have a fully controllable, walking character with a trailing camera!
Here is how the camera magic works:
-
app.disableMouse(): By default, Panda3D lets you move the camera around by clicking and dragging your mouse (which is great for inspecting a static scene). But because we want to write our own code to move the camera, we have to tell the game to turn off those default mouse controls. -
app.camera.setPos(panda, 0, 3000, 1200): Just like we did with the panda’s movement, putting panda as the first argument means we are placing the camera in relative space. We are telling the game, “Put the camera 3000 units behind the panda’s personal Y-axis, and 1200 units up on its Z-axis.” Because it sits inside themove_pandatask, this placement is recalculated 60 times a second, perfectly mirroring the panda’s movements! (Note: The numbers are so large because they scale down to match our0.005panda scale). -
app.camera.lookAt(panda): The camera is basically a giant eyeball.lookAt()is a super helpful built-in function that automatically calculates the math required to rotate that eyeball so it is staring dead center at whatever object you pass into the parentheses.
Camera (TODO move to a dedicated page)
By default, Panda3D includes a built-in mouse camera control (where you click and drag), but it’s meant for debugging, not for actual gameplay.
We will build a classic “First-Person” style mouse look. We will hide the mouse cursor, track its movement, and use that movement to look around the 3D world.
The Core Concepts
Before we write the code, here is a quick rundown of how we tackle this in Panda3D:
-
Disable the Default Mouse: We have to tell Panda3D to turn off its default debugger camera so we can take over.
-
Hide and Lock the Cursor: We will hide the mouse cursor and force it back to the exact center of the screen every single frame.
-
Measure the Difference (Delta): Because we constantly force the mouse back to the center, any time you move your physical mouse, the cursor briefly leaves the center. We measure that distance, rotate the camera, and snap the cursor back to the middle.
-
HPR (Heading, Pitch, Roll): This is how Panda3D handles rotation.
- Heading: Looking left and right (Yaw).
- Pitch: Looking up and down.
- Roll: Tilting your head side to side.
from direct.showbase.ShowBase import ShowBasefrom panda3d.core import WindowProperties
class MouseCameraGame(ShowBase): def __init__(self): super().__init__()
# 1. Disable the default Panda3D mouse camera controls self.disableMouse()
# Load a default environment model so we have something to look at self.scene = self.loader.loadModel("models/environment") self.scene.reparentTo(self.render) self.scene.setScale(0.25) self.scene.setPos(-8, 42, 0)
# 2. Set up our custom camera variables self.heading = 0.0 # Left/Right rotation self.pitch = 0.0 # Up/Down rotation self.mouse_sensitivity = 0.1 # How fast the camera moves
# 3. Hide the mouse cursor properties = WindowProperties() properties.setCursorHidden(True) self.win.requestProperties(properties)
# 4. Add our custom function to Panda3D's Task Manager # The Task Manager runs this function automatically every single frame self.taskMgr.add(self.mouse_look_task, "MouseLookTask")
def mouse_look_task(self, task): # Check if the application actually has the mouse inside its window if self.mouseWatcherNode.hasMouse():
# Get the size of the window and find the center coordinates center_x = self.win.getXSize() // 2 center_y = self.win.getYSize() // 2
# Get the current position of the mouse pointer pointer = self.win.getPointer(0) mouse_x = pointer.getX() mouse_y = pointer.getY()
# If we successfully snap the mouse back to the center of the screen... if self.win.movePointer(0, center_x, center_y):
# Calculate how far the mouse moved from the center delta_x = mouse_x - center_x delta_y = mouse_y - center_y
# Update our heading and pitch based on that movement self.heading -= delta_x * self.mouse_sensitivity self.pitch -= delta_y * self.mouse_sensitivity
# Optional: Clamp the pitch so you can't break your neck looking too far up/down if self.pitch < -90: self.pitch = -90 if self.pitch > 90: self.pitch = 90
# Apply the calculated rotation to the actual camera # We leave Roll at 0 because people don't usually walk with tilted heads! self.camera.setHpr(self.heading, self.pitch, 0)
# Tell the Task Manager to keep running this task on the next frame return task.cont
# Start the gamegame = MouseCameraGame()game.run()Run your Python script. You should see a simple grassy environment.
Move your mouse to look around.
You won’t be able to move forward or backward yet (that requires keyboard inputs), but you should have a perfectly smooth, first-person camera look system!
To exit the game, you can press Alt + F4 (Windows/Linux) or Cmd + Q (Mac), or simply close your terminal.
How to adjust the “feel”. If the camera feels too fast or too slow, simply change the
self.mouse_sensitivity = 0.1 variable. A lower number makes it slower, and a higher number makes it faster.