In this second part (part I) we can see how to change the camera wiev with panda3d. Panda3d is a moudle for python to make 3d games.
While module like pygame are “devoted” to 2d games, panda3d is done for 3d games. Another module for 3d games is Ursina.

from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor
from panda3d.core import Vec3, CollisionTraverser, CollisionNode, CollisionSphere, CollisionHandlerEvent, TextNode, Quat
from direct.gui.OnscreenText import OnscreenText
import random
class PandaBallGame(ShowBase):
def __init__(self):
super().__init__()
self.disableMouse()
# Load environment
self.scene = self.loader.loadModel("models/environment")
self.scene.reparentTo(self.render)
self.scene.setScale(0.25)
self.scene.setPos(-8, 42, 0)
# Load panda
self.panda = Actor("models/panda-model", {"walk": "models/panda-walk4"})
self.panda.reparentTo(self.render)
self.panda.setScale(0.005)
self.panda.setPos(0, 0, 0)
# Collision sphere for panda
panda_cnode = CollisionNode("panda")
panda_cnode.addSolid(CollisionSphere(0, 0, 0, 5))
self.panda_cnodepath = self.panda.attachNewNode(panda_cnode)
# Collision system
self.cTrav = CollisionTraverser()
self.cHandler = CollisionHandlerEvent()
self.cHandler.addInPattern("%fn-into-%in")
self.cTrav.addCollider(self.panda_cnodepath, self.cHandler)
self.projectile_cTrav = CollisionTraverser()
self.projectile_cHandler = CollisionHandlerEvent()
self.projectile_cHandler.addInPattern("projectile-into-%in")
self.cTrav.addCollider(self.panda_cnodepath, self.projectile_cHandler)
# Lists for balls and enemies
self.balls = []
self.enemies = []
self.powerups = []
self.projectiles = []
for i in range(5):
self.spawn_ball()
for i in range(3):
self.spawn_enemy()
self.spawn_fast_enemy()
self.spawn_invincibility_powerup()
self.spawn_shooter_powerup()
self.spawn_size_up_powerup()
# Score
self.score = 0
self.score_text = OnscreenText(text="Score: 0", pos=(-1.3, 0.9), scale=0.07, fg=(1, 1, 1, 1), align=TextNode.ALeft)
# Load beep sound
# Please replace "path/to/beep.wav" with a valid path to a sound file
self.beep_sound = self.loader.loadSfx("path/to/beep.wav")
# Key state map
self.keyMap = {"forward": False, "left": False, "right": False, "cam_left": False, "cam_right": False, "cam_up": False, "cam_down": False, "cam_in": False, "cam_out": False}
# Camera offset variables
self.camera_distance = 40
self.camera_height = 10
# Key bindings
self.accept("arrow_up", self.set_key, ["forward", True])
self.accept("arrow_up-up", self.set_key, ["forward", False])
self.accept("arrow_left", self.set_key, ["left", True])
self.accept("arrow_left-up", self.set_key, ["left", False])
self.accept("arrow_right", self.set_key, ["right", True])
self.accept("arrow_right-up", self.set_key, ["right", False])
self.accept("a", self.set_key, ["cam_left", True])
self.accept("a-up", self.set_key, ["cam_left", False])
self.accept("d", self.set_key, ["cam_right", True])
self.accept("d-up", self.set_key, ["cam_right", False])
self.accept("v", self.set_key, ["cam_down", True])
self.accept("v-up", self.set_key, ["cam_down", False])
self.accept("s", self.set_key, ["cam_up", True])
self.accept("s-up", self.set_key, ["cam_up", False])
self.accept("w", self.set_key, ["cam_down", True])
self.accept("w-up", self.set_key, ["cam_down", False])
self.accept("r", self.set_key, ["cam_in", True])
self.accept("r-up", self.set_key, ["cam_in", False])
self.accept("f", self.set_key, ["cam_out", True])
self.accept("f-up", self.set_key, ["cam_out", False])
self.accept("space", self.shoot)
# Collision events
self.accept("panda-into-ball", self.collect_ball)
self.accept("panda-into-enemy", self.game_over)
self.accept("panda-into-fast_enemy", self.game_over)
self.accept("panda-into-invincibility_powerup", self.activate_invincibility)
self.accept("panda-into-shooter_powerup", self.activate_shooter)
self.accept("panda-into-size_up_powerup", self.activate_size_up)
# Camera angle offset around panda
self.camera_angle = 0
# Panda invincibility state
self.is_invincible = False
# Panda shooter state
self.can_shoot = False
# Movement task
self.taskMgr.add(self.update, "updateTask")
def spawn_ball(self):
ball = self.loader.loadModel("models/smiley")
ball.setScale(0.5)
ball.setPos(random.uniform(-20, 20), random.uniform(-20, 20), 0)
ball.reparentTo(self.render)
ball_cnode = CollisionNode("ball")
ball_cnode.addSolid(CollisionSphere(0, 0, 0, 0.5))
ball.attachNewNode(ball_cnode)
self.balls.append(ball)
def spawn_enemy(self):
enemy = self.loader.loadModel("models/smiley")
enemy.setColor(1, 0, 0, 1)
enemy.setScale(1.5)
enemy.setPos(random.uniform(-20, 20), random.uniform(-20, 20), 0)
enemy.reparentTo(self.render)
enemy_cnode = CollisionNode("enemy")
enemy_cnode.addSolid(CollisionSphere(0, 0, 0, 1.5))
enemy.attachNewNode(enemy_cnode)
self.enemies.append(enemy)
def spawn_fast_enemy(self):
enemy = self.loader.loadModel("models/smiley")
enemy.setColor(1, 0.5, 0, 1)
enemy.setScale(1)
enemy.setPos(random.uniform(-20, 20), random.uniform(-20, 20), 0)
enemy.reparentTo(self.render)
enemy_cnode = CollisionNode("fast_enemy")
enemy_cnode.addSolid(CollisionSphere(0, 0, 0, 1))
enemy.attachNewNode(enemy_cnode)
self.enemies.append(enemy)
def spawn_invincibility_powerup(self):
powerup = self.loader.loadModel("models/smiley")
powerup.setColor(0, 1, 0, 1)
powerup.setScale(1)
powerup.setPos(random.uniform(-20, 20), random.uniform(-20, 20), 0)
powerup.reparentTo(self.render)
powerup_cnode = CollisionNode("invincibility_powerup")
powerup_cnode.addSolid(CollisionSphere(0, 0, 0, 1))
powerup.attachNewNode(powerup_cnode)
self.powerups.append(powerup)
def spawn_shooter_powerup(self):
powerup = self.loader.loadModel("models/smiley")
powerup.setColor(1, 1, 0, 1)
powerup.setScale(1)
powerup.setPos(random.uniform(-20, 20), random.uniform(-20, 20), 0)
powerup.reparentTo(self.render)
powerup_cnode = CollisionNode("shooter_powerup")
powerup_cnode.addSolid(CollisionSphere(0, 0, 0, 1))
powerup.attachNewNode(powerup_cnode)
self.powerups.append(powerup)
def spawn_size_up_powerup(self):
powerup = self.loader.loadModel("models/smiley")
powerup.setColor(1, 0, 1, 1)
powerup.setScale(1)
powerup.setPos(random.uniform(-20, 20), random.uniform(-20, 20), 0)
powerup.reparentTo(self.render)
powerup_cnode = CollisionNode("size_up_powerup")
powerup_cnode.addSolid(CollisionSphere(0, 0, 0, 1))
powerup.attachNewNode(powerup_cnode)
self.powerups.append(powerup)
def spawn_speed_boost_powerup(self):
powerup = self.loader.loadModel("models/smiley")
powerup.setColor(0, 0, 1, 1)
powerup.setScale(1)
powerup.setPos(random.uniform(-20, 20), random.uniform(-20, 20), 0)
powerup.reparentTo(self.render)
powerup_cnode = CollisionNode("speed_boost_powerup")
powerup_cnode.addSolid(CollisionSphere(0, 0, 0, 1))
powerup.attachNewNode(powerup_cnode)
self.powerups.append(powerup)
def collect_ball(self, entry):
ball_np = entry.getIntoNodePath().getParent()
ball_np.removeNode()
self.score += 100
self.score_text.setText(f"Score: {self.score}")
self.beep_sound.play()
def game_over(self, entry):
if not self.is_invincible:
print("Game Over!")
self.userExit()
def activate_invincibility(self, entry):
powerup_np = entry.getIntoNodePath().getParent()
powerup_np.removeNode()
self.is_invincible = True
self.panda.setAlphaScale(0.5)
self.taskMgr.doMethodLater(5, self.deactivate_invincibility, "deactivate_invincibility")
def deactivate_invincibility(self, task):
self.is_invincible = False
self.panda.setAlphaScale(1)
return task.done
def activate_shooter(self, entry):
powerup_np = entry.getIntoNodePath().getParent()
powerup_np.removeNode()
self.can_shoot = True
self.taskMgr.doMethodLater(10, self.deactivate_shooter, "deactivate_shooter")
def deactivate_shooter(self, task):
self.can_shoot = False
return task.done
def activate_size_up(self, entry):
powerup_np = entry.getIntoNodePath().getParent()
powerup_np.removeNode()
self.panda.setScale(0.01)
self.taskMgr.doMethodLater(10, self.deactivate_size_up, "deactivate_size_up")
def deactivate_size_up(self, task):
self.panda.setScale(0.005)
return task.done
def shoot(self):
if self.can_shoot:
projectile = self.loader.loadModel("models/smiley")
projectile.setColor(0, 0, 0, 1)
projectile.setScale(0.2)
projectile.setPos(self.panda.getPos())
projectile.reparentTo(self.render)
projectile_cnode = CollisionNode("projectile")
projectile_cnode.addSolid(CollisionSphere(0, 0, 0, 0.2))
projectile_cnodepath = projectile.attachNewNode(projectile_cnode)
self.projectile_cTrav.addCollider(projectile_cnodepath, self.projectile_cHandler)
# Move projectile forward
direction = self.panda.getQuat().getForward()
projectile.setFluidPos(self.panda.getPos() + direction * 2)
move_interval = projectile.posInterval(1, self.panda.getPos() + direction * 50)
move_interval.start()
# Collision with enemy
self.accept("projectile-into-enemy", self.destroy_enemy)
self.accept("projectile-into-fast_enemy", self.destroy_enemy)
def destroy_enemy(self, entry):
enemy_np = entry.getIntoNodePath().getParent()
projectile_np = entry.getFromNodePath().find("**/projectile").getParent()
enemy_np.removeNode()
projectile_np.removeNode()
def set_key(self, key, value):
self.keyMap[key] = value
def update(self, task):
dt = globalClock.getDt()
# Panda movement
if self.keyMap["forward"]:
self.panda.setY(self.panda, -1000 * dt)
if self.panda.getCurrentAnim() != "walk":
self.panda.loop("walk")
else:
if self.panda.getCurrentAnim() == "walk":
self.panda.stop()
if self.keyMap["left"]:
self.panda.setH(self.panda.getH() + 100 * dt)
if self.keyMap["right"]:
self.panda.setH(self.panda.getH() - 100 * dt)
# Camera rotation control
if self.keyMap["cam_left"]:
self.camera_angle += 60 * dt
if self.keyMap["cam_right"]:
self.camera_angle -= 60 * dt
if self.keyMap["cam_up"]:
self.camera_height -= 10 * dt
if self.keyMap["cam_down"]:
self.camera_height += 10 * dt
if self.keyMap["cam_in"]:
self.camera_distance -= 10 * dt
if self.keyMap["cam_out"]:
self.camera_distance += 10 * dt
# Move enemies randomly
for enemy in self.enemies:
if enemy.find("**/fast_enemy").isEmpty():
enemy.setX(enemy, random.uniform(-1, 1) * dt * 5)
enemy.setY(enemy, random.uniform(-1, 1) * dt * 5)
else:
enemy.setX(enemy, random.uniform(-1, 1) * dt * 10)
enemy.setY(enemy, random.uniform(-1, 1) * dt * 10)
# Camera follow with rotation
offset = Vec3(0, self.camera_distance, self.camera_height)
# Create a quaternion for rotation
q = Quat()
q.setHpr(Vec3(self.camera_angle, 0, 0))
# Rotate the offset vector
offset_rotated = q.xform(offset)
self.camera.setPos(self.panda.getPos() + offset_rotated)
self.camera.lookAt(self.panda)
return task.cont
app = PandaBallGame()
app.run()
Subscribe to the newsletter for updates
Tkinter templatesTwitter: @pythonprogrammi - python_pygame
Claude's Games
1. Memory gameVideos
Speech recognition gamePygame's Platform Game