Trouble with making camera follow character

Hi, I am very new to Panda and I have been trying to make player movement. I have made a very very simple block of code that moves my character on the world axis, but though I have my camera parented to my character model, when it rotates on the heading the camera does not fully circle and change heading along with my character, of course, I needed a script for that.

So me at 10 pm slapped the tutorial camera rotate with this math which I am not familiar with and changed some variables to connect it with my player’s heading and It is not working.

The camera does rotate around the player perfectly fine in position, but though I am setting the heading the same as the player, the player is still turning on its own speed.

How can I fix the mess I have made?

from direct.showbase.ShowBase import ShowBase
from panda3d.core import loadPrcFileData
from panda3d.core import NodePath
from direct.actor.Actor import Actor
from direct.task import Task
from panda3d.core import LPoint3f  # Note: LPoint3f is like a vector 3 value.
from math import pi, sin, cos

# My Config Variables
winTitle = "Max's Panda 3D Program"
iconPath = "pandaIcon.ico"
fov = 50
frameMeter = True
winWidth = 720
winLength = 1280
fullscreen = False
syncVideo = True
sceneGraphAnalyzer = True
red = 135
green = 168  # RGB Values
blue = 222

red = red / 255
green = green / 255  # Convert to background-color doubles
blue = blue / 255

gameConfiguration = f"""
win-size {winLength} {winWidth}
window-title {winTitle}
show-frame-rate-meter {frameMeter}
background-color {red} {green} {blue} 0
default-fov {fov}
fullscreen {fullscreen}
icon-filename {iconPath}
show-scene-graph-analyzer-meter {sceneGraphAnalyzer}
sync-video {syncVideo}
"""

loadPrcFileData("", gameConfiguration)

# Key bind dictionary
keyMap = {
    "forward": False,
    "backward": False,
    "left": False,
    "right": False
}

# Update key state.
def updateKeyMap(key, state):
    keyMap[key] = state


class Main(ShowBase): # Class created, inherits ShowBase.
    def __init__(self):
        ShowBase.__init__(self) # Initialize ShowBase.
        self.disable_mouse()  # Disables default Panda camera controls.

        environmentNode = NodePath("Environment") # Creates Panda Node in scene graph.

        scene = self.loader.loadModel("phase_8/models/neighborhoods/the_burrrgh.bam")
        self.skybox = self.loader.loadModel("phase_3.5/models/props/TT_sky.bam")

        scene.reparentTo(environmentNode)  # Parents scene inside 'Environment' node.
        self.skybox.reparentTo(environmentNode)
        environmentNode.reparentTo(self.render) # Reparents node and children to 'render' (Scene Graph root).

        self.plrCharacter = Actor("phase_6/models/char/dale_1000.bam",
                            {"idleAnim":"phase_6/models/char/dale_idle.bam",
                             "walkAnim":"phase_6/models/char/dale_walk.bam"})
        self.plrCharacter.setScale(3.5)
        self.plrCharacter.reparentTo(self.render)
        self.plrCharacter.loop("idleAnim")
        self.plrCharacter.setPos(20, 0, 4)

        self.camera.reparentTo(self.plrCharacter)
        self.camera.setPos(self.plrCharacter.get_pos())

        self.accept("a", updateKeyMap, ["left", True])
        self.accept("a-up", updateKeyMap, ["left", False])
        self.accept("d", updateKeyMap, ["right", True])
        self.accept("d-up", updateKeyMap, ["right", False])
        self.accept("w", updateKeyMap, ["forward", True])
        self.accept("w-up", updateKeyMap, ["forward", False])
        self.accept("s", updateKeyMap, ["backward", True])
        self.accept("s-up", updateKeyMap, ["backward", False])

        self.task_mgr.add(self.skyboxRotate, "SkyboxRotate")
        self.task_mgr.add(self.playerMovement, "PlayerMovement")


    def skyboxRotate(self, task):
        deltaTime = globalClock.getDt()
        self.skybox.setHpr((self.skybox.get_hpr() + LPoint3f((2 * deltaTime), 0, 0)))
        return Task.cont

    def playerMovement(self, task):
        deltaTime = globalClock.getDt()

        if keyMap["left"]:
            heading = self.plrCharacter.getH()
            heading += (50 * deltaTime)
            self.plrCharacter.setH(heading)
            self.camera.setHpr(self.plrCharacter.get_h(), -7, 0)
            angleDegrees = self.plrCharacter.get_h()
            angleRadians = angleDegrees * (pi / 180.0)
            self.camera.setPos(5 * sin(angleRadians), -5 * cos(angleRadians), 1.7)

        if keyMap["right"]:
            heading = self.plrCharacter.getH()
            heading -= (50 * deltaTime)
            self.plrCharacter.setH(heading)
            self.camera.setHpr(self.plrCharacter.get_h(), -7, 0)
            angleDegrees = self.plrCharacter.get_h()
            angleRadians = angleDegrees * (pi / 180.0)
            self.camera.setPos(5 * sin(angleRadians), -5 * cos(angleRadians), 1.7)

        if keyMap["forward"]:
            position = self.plrCharacter.getPos()
            position.y += (15 * deltaTime)
            self.plrCharacter.setPos(position)

        if keyMap["backward"]:
            position = self.plrCharacter.getPos()
            position.y -= (15 * deltaTime)
            self.plrCharacter.setPos(position)

        return Task.cont

game = Main()
game.run()

If you parent the camera to the player, you no longer need to set the heading or position to the character position, because it automatically inherits them. So if the player is at (1, 2, 3), and you parent the camera to it, and then set the camera to (1, 2, 3), then the camera position will actually be (2, 4, 6).

Instead, just leave the heading of the camera to 0, and set the camera position to some fixed position behind the player (eg. (0, -5, 5)) and probably lookAt((0, 0, 0)) to make it look at the player.

We can’t run your code as-is because we don’t have the assets; if you want more specific help then you’ll have to post it with assets (that you own).

1 Like

Ill try that, thanks!

So the camera is following the player at a certain offset, and it is looking at the player, but It is not turning anymore.

In my movement script (which was working fine), I have the character’s heading turn when ‘a’ or ‘d’ is pressed. Now its not doing that, and Im not sure what is affecting that.

This is my current code:

from direct.showbase.ShowBase import ShowBase
from panda3d.core import loadPrcFileData
from panda3d.core import NodePath
from direct.actor.Actor import Actor
from direct.task import Task
from panda3d.core import LPoint3f  # Note: LPoint3f is like a vector 3 value.
from math import pi, sin, cos

# My Config Variables
winTitle = "Max's Panda 3D Program"
iconPath = "pandaIcon.ico"
fov = 50
frameMeter = True
winWidth = 720
winLength = 1280
fullscreen = False
syncVideo = True
sceneGraphAnalyzer = True
red = 135
green = 168  # RGB Values
blue = 222

red = red / 255
green = green / 255  # Convert to background-color doubles
blue = blue / 255

gameConfiguration = f"""
win-size {winLength} {winWidth}
window-title {winTitle}
show-frame-rate-meter {frameMeter}
background-color {red} {green} {blue} 0
default-fov {fov}
fullscreen {fullscreen}
icon-filename {iconPath}
show-scene-graph-analyzer-meter {sceneGraphAnalyzer}
sync-video {syncVideo}
"""

loadPrcFileData("", gameConfiguration)

# Key bind dictionary
keyMap = {
    "forward": False,
    "backward": False,
    "left": False,
    "right": False
}

# Update key state.
def updateKeyMap(key, state):
    keyMap[key] = state


class Main(ShowBase): # Class created, inherits ShowBase.
    def __init__(self):
        ShowBase.__init__(self) # Initialize ShowBase.
        self.disable_mouse()  # Disables default Panda camera controls.

        environmentNode = NodePath("Environment") # Creates Panda Node in scene graph.

        scene = self.loader.loadModel("phase_8/models/neighborhoods/the_burrrgh.bam")
        self.skybox = self.loader.loadModel("phase_3.5/models/props/TT_sky.bam")

        scene.reparentTo(environmentNode)  # Parents scene inside 'Environment' node.
        self.skybox.reparentTo(environmentNode)
        environmentNode.reparentTo(self.render) # Reparents node and children to 'render' (Scene Graph root).

        self.plrCharacter = Actor("phase_6/models/char/dale_1000.bam",
                            {"idleAnim":"phase_6/models/char/dale_idle.bam",
                             "walkAnim":"phase_6/models/char/dale_walk.bam"})
        self.plrCharacter.setScale(3.5)
        self.plrCharacter.reparentTo(self.render)
        self.plrCharacter.loop("idleAnim")
        self.plrCharacter.setPos(20, 0, 4)

        self.camera.reparentTo(self.plrCharacter)
        self.camera.setPos(0, -4, 1.6)
        self.camera.lookAt(0, 0, 1)

        self.accept("a", updateKeyMap, ["left", True])
        self.accept("a-up", updateKeyMap, ["left", False])
        self.accept("d", updateKeyMap, ["right", True])
        self.accept("d-up", updateKeyMap, ["right", False])
        self.accept("w", updateKeyMap, ["forward", True])
        self.accept("w-up", updateKeyMap, ["forward", False])
        self.accept("s", updateKeyMap, ["backward", True])
        self.accept("s-up", updateKeyMap, ["backward", False])

        self.task_mgr.add(self.skyboxRotate, "SkyboxRotate")
        self.task_mgr.add(self.playerMovement, "PlayerMovement")


    def skyboxRotate(self, task):
        deltaTime = globalClock.getDt()
        self.skybox.setH(self.skybox.getH() + (2 * deltaTime))
        return Task.cont

    def playerMovement(self, task):
        deltaTime = globalClock.getDt()
        heading = self.plrCharacter.getH()
        position = self.plrCharacter.getPos()

        if keyMap["left"]:
            heading += (50 * deltaTime)

        if keyMap["right"]:
            heading -= (50 * deltaTime)

        if keyMap["forward"]:
            position.y += (15 * deltaTime)

        if keyMap["backward"]:
            position.y -= (15 * deltaTime)

        self.plrCharacter.setPos(position)

        return Task.cont

game = Main()
game.run()

It looks like your removed the code that rotated your character (which was “self.plrCharacter.setH(heading)” in the code that you posted previously), as well as the code that rotated your camera; it was the latter that caused your initial problem, not the former, if I’m not much mistaken.

1 Like

So now it seems to be working but it is turning back to looking at the player, and for some reason the player character is still not turning in heading.

Im not sure whats interfering with the turning.

This is my code after applying what you said:

from direct.showbase.ShowBase import ShowBase
from panda3d.core import loadPrcFileData
from panda3d.core import NodePath
from direct.actor.Actor import Actor
from direct.task import Task
from panda3d.core import LPoint3f  # Note: LPoint3f is like a vector 3 value.
from math import pi, sin, cos

# My Config Variables
winTitle = "Max's Panda 3D Program"
iconPath = "pandaIcon.ico"
fov = 50
frameMeter = True
winWidth = 720
winLength = 1280
fullscreen = False
syncVideo = True
sceneGraphAnalyzer = True
red = 135
green = 168  # RGB Values
blue = 222

red = red / 255
green = green / 255  # Convert to background-color doubles
blue = blue / 255

gameConfiguration = f"""
win-size {winLength} {winWidth}
window-title {winTitle}
show-frame-rate-meter {frameMeter}
background-color {red} {green} {blue} 0
default-fov {fov}
fullscreen {fullscreen}
icon-filename {iconPath}
show-scene-graph-analyzer-meter {sceneGraphAnalyzer}
sync-video {syncVideo}
"""

loadPrcFileData("", gameConfiguration)

# Key bind dictionary
keyMap = {
    "forward": False,
    "backward": False,
    "left": False,
    "right": False
}

# Update key state.
def updateKeyMap(key, state):
    keyMap[key] = state


class Main(ShowBase): # Class created, inherits ShowBase.
    def __init__(self):
        ShowBase.__init__(self) # Initialize ShowBase.
        self.disable_mouse()  # Disables default Panda camera controls.

        environmentNode = NodePath("Environment") # Creates Panda Node in scene graph.

        scene = self.loader.loadModel("phase_8/models/neighborhoods/the_burrrgh.bam")
        self.skybox = self.loader.loadModel("phase_3.5/models/props/TT_sky.bam")

        scene.reparentTo(environmentNode)  # Parents scene inside 'Environment' node.
        self.skybox.reparentTo(environmentNode)
        environmentNode.reparentTo(self.render) # Reparents node and children to 'render' (Scene Graph root).

        self.plrCharacter = Actor("phase_6/models/char/dale_1000.bam",
                            {"idleAnim":"phase_6/models/char/dale_idle.bam",
                             "walkAnim":"phase_6/models/char/dale_walk.bam"})
        self.plrCharacter.setScale(3.5)
        self.plrCharacter.reparentTo(self.render)
        self.plrCharacter.loop("idleAnim")
        self.plrCharacter.setPos(20, 0, 4)

        self.camera.reparentTo(self.plrCharacter)
        self.camera.setPos(0, -4, 1.6)
        self.camera.lookAt(0, 0, 1)

        self.accept("a", updateKeyMap, ["left", True])
        self.accept("a-up", updateKeyMap, ["left", False])
        self.accept("d", updateKeyMap, ["right", True])
        self.accept("d-up", updateKeyMap, ["right", False])
        self.accept("w", updateKeyMap, ["forward", True])
        self.accept("w-up", updateKeyMap, ["forward", False])
        self.accept("s", updateKeyMap, ["backward", True])
        self.accept("s-up", updateKeyMap, ["backward", False])

        self.task_mgr.add(self.skyboxRotate, "SkyboxRotate")
        self.task_mgr.add(self.playerMovement, "PlayerMovement")


    def skyboxRotate(self, task):
        deltaTime = globalClock.getDt()
        self.skybox.setH(self.skybox.getH() + (2 * deltaTime))
        return Task.cont

    def playerMovement(self, task):
        deltaTime = globalClock.getDt()
        heading = self.plrCharacter.getH()
        position = self.plrCharacter.getPos()

        if keyMap["left"]:
            heading += (50 * deltaTime)

        if keyMap["right"]:
            heading -= (50 * deltaTime)

        if keyMap["forward"]:
            position.y += (15 * deltaTime)

        if keyMap["backward"]:
            position.y -= (15 * deltaTime)

        self.plrCharacter.setPos(position)
        self.camera.setH(heading)

        return Task.cont

game = Main()
game.run()

In this case, you’re turning the camera but not the player.

To be specific, this line:

self.camera.setH(heading)

Rotates the camera.

From your previous code, this line:

self.plrCharacter.setH(heading)

Rotated the character.

So, in order to have your character turn, and the camera turn with it–without extra camera-rotations interfering–you would want the latter line (“self.plrCharacter.setH(heading)”), but not the former line (“self.camera.setH(heading)”), if I’m not much mistaken.

1 Like

Woops forgot to add that. Its working now! Now I understand how the camera inherits from the player as its child better now, thank you for the help!

I replaced the self.camera.setH(heading) with plrCharacter and its working how I want it to work now! Thanks again! :smiley:

1 Like