Panda3d moving the camera

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 templates

Avatar My youtube channel

Twitter: @pythonprogrammi - python_pygame

Claude's Games

Arkanoid
Platform 2d

1. Memory game

Videos

Speech recognition game

Pygame's Platform Game

Other Pygame's posts

Advertisement