Dragging mouse to control 3rd person camera

Hello all,

I am trying to move a third-person camera by left-clicking and dragging the mouse, I have the issue where the camera constantly points in a certain direction every time the left mouse button is clicked and another where the camera movement is really chunky no matter the constants I keep changing in my code, here is a gif of whats happening.

here is the code:

        if (self.keyMap["leftClick"] == True) and (self.toggleFPCam == False):
            if (base.mouseWatcherNode.hasMouse() == True):
                mouseposition = base.mouseWatcherNode.getMouse()
                self.mouseSeconds.append(mouseposition)
            if len(self.mouseSeconds) == 3:
                lookConstant = 100
                upperconstant = 50
                lowerconstant = 1
                moveX = ((self.mouseSeconds[2].getX())*upperconstant - (self.mouseSeconds[0].getX())*lowerconstant)*lookConstant*deltaTime
                moveY = ((self.mouseSeconds[2].getY())*upperconstant - (self.mouseSeconds[0].getY())*lowerconstant)*lookConstant*deltaTime
                if (moveX > 0.1 or moveX < -0.1) and (moveY > 0.1 or moveY < -0.1):
                    self.thirdPersonNode.setP(self.thirdPersonNode.getY()+moveY)
                    self.playerBase.setH(self.playerBase.getX() - moveX)
                self.mouseSeconds = []

*mouseSeconds is an array defined at def __init, it is initially set as an empty array.
I tried to track the change of position of the mouse and add the difference between initial position and final position of every 3 frames onto the pitch of a node of the model (because I didn’t want the actual ball to rotate, just the camera, so I added a node that is a child of playerBase and parent of the camera), however I do want to change the heading of the playerBase

I also tried a different approach prior to this

props = WindowProperties()
            props.setMouseMode(WindowProperties.M_relative)
            base.win.requestProperties(props)
            if (base.mouseWatcherNode.hasMouse() == True):
                mouseposition = base.mouseWatcherNode.getMouse()
                self.thirdPersonNode.setP(mouseposition.getY() * 30)
                self.playerBase.setH(mouseposition.getX() * -50)

                if (mouseposition.getX() < 0.1 and mouseposition.getX() > -0.1):
                    self.playerBase.setH(self.playerBase.getH())
                else:
                    self.playerBase.setH(self.playerBase.getH() + mouseposition.getX() * -1)

Thanks in advance.

It’s simple: you haven’t disabled the base camera, so it takes control.

# Disable the camera trackball controls.
self.disableMouse()

It seems that the problem is that in the documentation it was forgotten, only on the last page it was added to the code.

https://docs.panda3d.org/1.10/python/introduction/tutorial/controlling-the-camera

1 Like

I have already disabled the default mouse controls, here is my full code.

game.py

from direct.showbase.ShowBase import ShowBase   # import the bits of panda
import sys
from panda3d.core import Vec4, AmbientLight
from arcticEnvironment import environment
from player import Player

class MyApp(ShowBase):                          # our 'class'
    def __init__(self):
        ShowBase.__init__(self)                        # initialise
        world = environment(self.render,self.loader)
        #self.world.setScale(100)
        base.setFrameRateMeter(True)
        self.disableMouse()
        self.player = Player(self.camera,self.accept,base,self.render,self.loader)
        self.updateTask = taskMgr.add(self.player.playermove, "update")

app = MyApp()
app.run()

player.py

import sys
from panda3d.core import Vec4, Vec3
from panda3d.core import WindowProperties
import math

class Player():                          # our 'class'
    def __init__(self,camera,accept,base,render,loader):

        self.playerHolder = render.attachNewNode('player')
        self.character = loader.loadModel('assets/base/player.bam')
        self.toggleFPCam = False
        self.character.setPos(0,0,0)
        self.character.reparentTo(self.playerHolder)
        self.playerBase = self.playerHolder.attachNewNode('camParent')
        self.thirdPersonNode = self.playerBase.attachNewNode('thirdPersonCam')
        camera.reparentTo(self.thirdPersonNode)
        self.mouseSeconds = []

        self.keyMap = {
            "left": False,
            "right": False,
            "forward": False,
            "backwards": False,
            "change_camera": False,
            "leftClick": False
        }

        accept("escape", sys.exit)
        accept("w", self.updateKey, ["forward", True])  #
        accept("w-up", self.updateKey, ["forward", False])

        accept("a", self.updateKey, ["left", True])
        accept("a-up", self.updateKey, ["left", False])

        accept("s", self.updateKey, ["backwards", True])
        accept("s-up", self.updateKey, ["backwards", False])

        accept("d", self.updateKey, ["right", True])
        accept("d-up", self.updateKey, ["right", False])

        accept("c",self.updateKey,["change_camera", True])
        #accept("c-up",self.updateKey,["change_camera", False])

        accept("mouse1",self.updateKey,["leftClick",True])
        accept("mouse1-up",self.updateKey,["leftClick",False])


    def updateKey(self,key,value):
        self.keyMap[key] = value
        if key == "change_camera":
            self.changeCamera()

    def changeCamera(self):
        if self.toggleFPCam == False:
            self.toggleFPCam = True

        else:
            self.toggleFPCam = False

    def recenterMouse(self):
        base.win.movePointer(0, int(base.win.getProperties().getXSize() / 2), int(base.win.getProperties().getYSize() / 2))

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

        if self.toggleFPCam:
            camera.setPos(self.character.getPos())  # 0,-50,-10
            props = WindowProperties()
            props.setCursorHidden(True)
            props.setMouseMode(WindowProperties.M_relative)
            base.win.requestProperties(props)
            self.character.hide()

            if (base.mouseWatcherNode.hasMouse() == True):
                mouseposition = base.mouseWatcherNode.getMouse()
                camera.setP(mouseposition.getY() * 30)
                self.playerBase.setH(mouseposition.getX() * -50)

                if (mouseposition.getX() < 0.1 and mouseposition.getX() > -0.1):
                    self.playerBase.setH(self.playerBase.getH())
                else:
                    self.playerBase.setH(self.playerBase.getH() + mouseposition.getX() * -1)

        else:
            props = WindowProperties()
            props.setCursorHidden(False)
            props.setMouseMode(WindowProperties.M_absolute)
            base.win.requestProperties(props)
            self.character.show()
            camera.setPos(0, -50, 10)  # 0,-50,-10
            camera.lookAt(self.character)

        self.walkConstant = 4000
        self.rotateConstant = 100
        if self.keyMap["forward"]:
            self.playerHolder.setY(self.playerBase, (self.walkConstant*deltaTime))
            self.character.setP(self.character.getP() + (-self.rotateConstant*deltaTime*(math.cos(math.radians(self.playerBase.getH())))))
            self.character.setR(self.character.getR() - (self.rotateConstant*deltaTime*(-math.sin(math.radians(self.playerBase.getH())))))

        if self.keyMap["right"]:
            self.playerHolder.setX(self.playerBase, (self.walkConstant*deltaTime))

        if self.keyMap["left"]:
            self.playerHolder.setX(self.playerBase, (-self.walkConstant*deltaTime))

        if self.keyMap["backwards"]:
            self.playerHolder.setY(self.playerBase, (-self.walkConstant * deltaTime))
            self.character.setP(self.character,(self.rotateConstant*deltaTime))

        if (self.keyMap["leftClick"] == True) and (self.toggleFPCam == False):

            if (base.mouseWatcherNode.hasMouse() == True):
                mouseposition = base.mouseWatcherNode.getMouse()
                self.mouseSeconds.append(mouseposition)
            if len(self.mouseSeconds) == 3:
                lookConstant = 100
                upperconstant = 50
                lowerconstant = 1
                moveX = ((self.mouseSeconds[2].getX())*upperconstant - (self.mouseSeconds[0].getX())*lowerconstant)*lookConstant*deltaTime
                moveY = ((self.mouseSeconds[2].getY())*upperconstant - (self.mouseSeconds[0].getY())*lowerconstant)*lookConstant*deltaTime
                if (moveX > 0.1 or moveX < -0.1) and (moveY > 0.1 or moveY < -0.1):
                    self.thirdPersonNode.setP(self.thirdPersonNode.getY()+moveY)
                    self.playerBase.setH(self.playerBase.getX() - moveX)
                self.mouseSeconds = []














            #props = WindowProperties()
            #props.setMouseMode(WindowProperties.M_relative)
            #base.win.requestProperties(props)

            #if (base.mouseWatcherNode.hasMouse() == True):
            #    mouseposition = base.mouseWatcherNode.getMouse()
            #    self.thirdPersonNode.setP(mouseposition.getY() * 30)
            #    self.playerBase.setH(mouseposition.getX() * -50)

            #    if (mouseposition.getX() < 0.1 and mouseposition.getX() > -0.1):
            #        self.playerBase.setH(self.playerBase.getH())
            #    else:
            #        self.playerBase.setH(self.playerBase.getH() + mouseposition.getX() * -1)

        return task.cont

environment.py

class environment():                         
    def __init__(self,render,loader):
        self.world = loader.loadModel('assets/arctic2021/nature/world.bam')
        self.world.reparentTo(render)
        #self.world.setScale(100)
        self.world.setZ(-350)

If you want to make a third-person camera, then you need to study the example of Ralph. At the moment, your code is chaotic. And it is not quite clear what you expect, how it should work.

1 Like

One thing that I note is that you seem to be applying delta-time when you rotate your camera. While applying delta-time is usually appropriate, I believe that it generally isn’t when making use of mouse-offsets as movement inputs.

You see, the distance that the mouse is moved in a given frame depends on the amount of time between frames: for a given mouse-movement speed, the greater the time between frames, the further the mouse will have been moved by the end of that frame.

Thus, for such purposes as this, you like want to eschew applying the delta-time–in a sense, it has already been applied within the movement of the mouse.

As to the view jumping, that may be due to the fact that when you set the character’s H-value, you’re doing so relative not to its current H-value, but to its X-coordinate. Since the X-coordinate is likely arbitrary, the result of the user moving the object around, you’re essentially setting its H-value, its rotation, to an arbitrary value.

1 Like