Rotate camera based on mouse position

After successfully finishing my keyboardcontrols code. I’m now trying to get this code to work with the mouse instead of the keyboard.

This is really, really hard, because I can’t find any examples to work from. Anyway, what I’m trying to do, is make the camera rotate left or right around the player when the mouse pointer moves to the edges of the screen (I believe this is known as 'Edge Screen Tracking).

This is what I have so far:

I have a function to rotate the camera around the player:

def cameraTurn(self,dir):
        self.camTurn = LerpHprInterval(self.camera_dummy_node, self.speed, Point3(self.camera_dummy_node.getH()-(10*dir), 0, 0))
        self.camTurn.start()

And I have a task that tracks the position of the mouse pointer each frame:

def mousePosition(self, task):
        mousePos = base.win.getPointer(0)
        mouseX = mousePos.getX()
        return Task.cont

I also have a function that returns the width of the window:

def windowProperties(self):
        props = base.win.getProperties()
        winX = props.getXSize()

What I haven’t been able to figure out though, is how to put it all together to execute the cameraTurn function when the mouse pointer moves to the edges of the screen.

I’m not sure if I need another function for this or what, but so far, everything I’ve tried hasn’t worked, so any advice (or better yet, an example :smiley:) would be most welcome.

Cheers

Something like this?

(pseuso-code)


#put this in your loop task
if mouseX > (screensizeX - 20):
    self.rotateCamera(1)
elif mouseX < 20:
    self.rotateCamera(-1)

I have a camera class that moves the camera around the screen when the mouse pointer is near the screens edge (yes, edge tracking).

I can post that when I get home if you need more help, but ti shouldnt be to hard.

That’s brilliant Yellow! That’s just what I was after, I can’t thankyou enough for this. I had a vague idea that I needed to do something like this, but no idea of how to actually implement it in code. Thanks very much.

There’s just one slight problem though, when I move the pointer to the left edge of the screen, it works perfectly (it waits for the pointer to almost touch the edge of the screen before it starts to rotate the camera).

But when I move the pointer to the right edge of the screen, the camera starts to rotate well before the pointer ever gets near the edge of the screen. I’d say it starts to rotate when the pointer is a good quarter of the screen away from the right edge.

Anyway, this is how I implemented your code, so I’ve probably messed it up somewhere :unamused:.

# I had to move props and screensizeX out of the function I'd written and put them into def__init__(self). Because I was getting an error telling me that they weren't defined.
  
def __init__(self): 
       base.disableMouse() # Disable default camera.
       self.speed = .10 # Controls speed of camera rotation and zoom.
       self.props = base.win.getProperties()
       self.screensizeX = self.props.getXSize()

This is my task to handle monitoring the pointer’s position and to rotate the camera when the pointer moves to the edge of the screen:

def mousePosition(self, task):
        mousePos = base.win.getPointer(0)
        mouseX = mousePos.getX()
        if mouseX > (self.screensizeX - 2):
            self.cameraTurn(1)
        elif mouseX < 2:
            self.cameraTurn(-1) 
        return Task.cont

As you can see, I’ve been playing with the (self.screensizeX - 2) and ‘elif mouseX < 2’ values, but sadly, it hasn’t solved the problem. Any ideas where I went wrong?

Thanks heaps

Hmm, I can’t see anything wrong with your code. Try doing…


print self.screensizeX

… inside your task, and check the command prompt to see what screen size it’s using.

Now that’s very strange. I just used the ‘print self.screensizeX’ command and it’s returning 800, even when I maximise the window to full screen.

This could explain the problem. You see, if I run the code in windowed mode, then the camera doesn’t rotate at all when I move the pointer to the right edge of the screen (the left side works just fine).

But if I run the code in full screen mode, then the camera rotates as I described above, but I think I now understand what it’s doing, it appears to be starting its rotation when the pointer reaches 800 instead of 1024.

I have no idea why it’s doing this though. When I first created a function to get the screen size, I used the print command to be sure it was working properly.

self.accept("p",self.windowProperties)

def windowProperties(self):
        props = base.win.getProperties()
        screensizeX = props.getXSize()
        print screensizeX

It worked just fine. In window mode the left edge of the screen was 0 and the right edge was 800, and in full screen mode the left edge was 0 and the right edge was 1024.

Could it have something to do with the fact I moved the:

 self.props = base.win.getProperties()
 self.screensizeX = self.props.getXSize() 

out of a function and into def init(self): ?

Thanks for trying to help.

Ah just looked at my own code, mixed it up with edge tracking in irrlicht :S

Here is the correct code:


def camLoop(self,task):
        # check if the mouse is available
        if not base.mouseWatcherNode.hasMouse():
            return Task.cont
        
        # get the relative mouse position, 
        # its always between 1 and -1
        mpos = base.mouseWatcherNode.getMouse()
        
        if mpos.getX() > 0.9:
            self.cameraTurn(1)

        elif mpos.getX() < -0.9:
            self.cameraTurn(-1)

        return Task.cont

Hooray! That fixed it! Thanks very, very much :smiley:.

I have just one more quick question, the rotate camera function is now starting when the mouse pointer is about an inch from either edge of the screen(which is much better :smiley:) but I’d like it to start when the mouse pointer is touching (or almost touching) the edge of the screen, is this possible?

I did try increasing and decreasing the 0.9 value, but that just seemed to move the rotation point even further away from the edge of the screen :unamused:.

Again, thankyou very, very much for taking the time to help me with this.

Cheers

using 0.95 and -0.95 instead should do the trick the entire width of the screen is -1.0 till 1.0 any number inbetween should be usable.

Yellow, you are the best! I didn’t know that I should use numbers between 1.0 and -1.0, I was putting in things like 0.5 and 2.0, no wonder it wouldn’t work :blush:.

This time I used 0.99 and it works perfectly. I can’t tell you how grateful I am for this, I know in my heart that I’d never have been able to figure this out by myself. Thanks heaps.

Well, onto my next big challenge ‘point and click’ character movement :smiley:.

Cheers

Oops, I nearly forgot, I must share my new found knowledge :smiley:. So here is the entire code that I have so far:

# MouseControls.py
# Move the mouse pointer to the edge of the screen to rotate the camera.
# The Left and Right arrows also rotate the camera. The Mouse Wheel zooms the
# camera in and out. The Up and Down arrows also zoom the camera in and out.
# Move the player with the W, A, S, D keys. 

import direct.directbase.DirectStart # Start Panda
from pandac.PandaModules import* # Import the Panda Modules
from direct.showbase.DirectObject import DirectObject # To listen for Events
from direct.task import Task # To use Tasks
from direct.actor import Actor # To use animated Actors 
from direct.interval.IntervalGlobal import * # To use Intervals
import math # To use math (sin, cos..etc)
import sys 

class Controls(DirectObject):
    #Constructor
    def __init__(self): 
       base.disableMouse() # Disable default camera.
       self.speed = .10 # Controls speed of camera rotation and zoom.
       self.loadModels()
       self.setupAnimations()
       # Setup key controls
       self.accept("escape", sys.exit)
       self.accept("arrow_left", self.cameraTurn,[-1])
       self.accept("arrow_right", self.cameraTurn,[1])
       self.accept("arrow_up", self.cameraZoom,[-1])
       self.accept("arrow_down", self.cameraZoom,[1])
       self.accept("wheel_up", self.cameraZoom,[-1])
       self.accept("wheel_down", self.cameraZoom,[1])
       self.acceptOnce("w", self.forward)
       self.acceptOnce("s", self.backward)
       self.acceptOnce("a", self.turn,[-1])
       self.acceptOnce("d", self.turn,[1])
       self.accept("w-up",self.stopForward)
       self.accept("s-up",self.stopBackward)

       self.accept("a-up",self.stopTurn)
       self.accept("d-up",self.stopTurn)
       taskMgr.add(self.mousecamTask, "mousecamTask")
       # end __init__

    # Define a task to monitor the position of the mouse pointer & rotate 
    # the camera when the mouse pointer moves to the edges of the screen.
    def mousecamTask(self,task):
        # Check if the mouse is available
        if not base.mouseWatcherNode.hasMouse():
            return Task.cont
        # Get the relative mouse position, its always between 1 and -1
        mpos = base.mouseWatcherNode.getMouse()
        if mpos.getX() > 0.99:
            self.cameraTurn(1)
        elif mpos.getX() < -0.99:
            self.cameraTurn(-1)
        return Task.cont 
        # end mousecamTask
        
    def loadModels(self):
        # Load the player and its animations
        self.player = Actor.Actor("MODELS/ralph",{"walk":"MODELS/ralph-walk"})
        self.player.reparentTo(render) # Make it display/render on the screen.
        self.player.setScale(.005)
        self.player.setPos(0, 0, 0) # Position it at the center of the world.
        # Load an environment
        self.environ = loader.loadModel("MODELS/env")
        self.environ.reparentTo(render)
        self.environ.setPos(0, 0, 0)
        #Create a camera dummy node

        self.camera_dummy_node = render.attachNewNode("camera_dummy_node")
        #Position the camera dummy node.
        self.camera_dummy_node.setPos( 0, 0, 0)
        # Make it view from behind the player.
        self.camera_dummy_node.setHpr(180, 0, 0) 
        # Attach the camera dummy node to the player.
        self.camera_dummy_node.reparentTo(self.player)
        # Attach the camera to the dummy node.
        camera.reparentTo(self.camera_dummy_node)
        # Position the camera
        camera.setPos(0, -30, 7) # X = left & right, Y = zoom, Z = Up & down.
        camera.setHpr(0, -15, 0) # Heading, pitch, roll.
        camera.lookAt(self.player) # Make the camera follow the player.
        # end loadModels
    
    # Define the CameraTurn function.
    def cameraTurn(self,dir):
        self.camTurn = LerpHprInterval(self.camera_dummy_node, self.speed, Point3(self.camera_dummy_node.getH()-(10*dir), 0, 0))
        self.camTurn.start()
        # end cameraTurn
    
    # Define the cameraZoom function.
    def cameraZoom(self,dir):
        self.camZoom = LerpPosInterval(camera, self.speed, Point3(camera.getX(), camera.getY()-(2*dir), camera.getZ()))
        self.camZoom.start()
        # end cameraZoom
    
    # Define the setupAnimations function
    def setupAnimations(self):
        self.playerWalk = self.player.actorInterval("walk")
        self.playerBack = self.player.actorInterval("walk")
        self.playerTurn = self.player.actorInterval("walk")
        # end setupIntervals
        
    # Define the forward function
    def forward(self):
        taskMgr.add(self.forwardTask,"forwardTask")
        self.playerWalk.loop() # Play the actor's animations
        # end forward
    
    # Define the forwardTask    
    def forwardTask(self,task):
        speed = 0.05 # controls how far the actor moves
        dt = globalClock.getDt()
        dist = speed*dt
        angle = self.player.getH()*math.pi/180
        dx = dist*math.sin(angle)
        dy = dist*-math.cos(angle)
        self.player.setPos(Vec3(self.player.getX()+dx,self.player.getY()+dy,0))
        return Task.cont
    # end forwardTask
    
    # Define the stopForward function
    def stopForward(self):
        taskMgr.remove("forwardTask")
        self.playerWalk.pause()
        self.player.pose("walk",17)
        self.acceptOnce("w",self.forward)
        #end stopForward
    
    # Define the backward function    
    def backward(self):
        taskMgr.add(self.backwardTask,"backwardTask")
        self.playerBack.loop()
        # end backward
    
    # Define the backwardTask    
    def backwardTask(self,task):
        speed = 0.05 # controls how far the actor moves
        dt = globalClock.getDt()
        dist = speed*dt
        angle = self.player.getH()*math.pi/180
        dx = dist*math.sin(angle)
        dy = dist*-math.cos(angle)
        self.player.setPos(Vec3(self.player.getX()-dx,self.player.getY()-dy,0))
        return Task.cont
    # end backwardTask
    
    # Define the stopBackward function
    def stopBackward(self):
        taskMgr.remove("backwardTask")
        self.playerBack.pause()
        self.acceptOnce("s",self.backward)
        #end stopForward
    
    # Define the turn function    
    def turn(self,dir):
        taskMgr.add(self.turnTask, "turnTask",extraArgs =[dir])
        self.playerTurn.loop()
        self.ignore("a")
        self.ignore("d")
        #end turn
    
    # Define the turnTask 
    def turnTask(self,dir):
        speed = 50.0
        dt = globalClock.getDt()
        angle = dir*speed*dt
        self.player.setH(self.player.getH()-angle)
        return Task.cont
    # end turnTask
    
    # Define the stopTurn function
    def stopTurn(self):
        taskMgr.remove("turnTask")
        self.playerTurn.pause()
        self.acceptOnce("a",self.turn,[-1])
        self.acceptOnce("d",self.turn,[1])
        #end stopTurn
        
# end class Controls

c = Controls()

run()

I hope it helps other newbies who are trying to write their own game controls code :smiley:.

Cheers

TipToe,

I have added your example to Examples Contributed by the Community.

I am curious. What does the inside of your ralph.egg file look like?

panda3d.org/manual/index.php/E … _Community

AIM

Look at this

Oh wow! That’s really flattering. Thanks Andre. I’m so pleased that you think it’s worthy of inclusion :smiley:.

As for the Ralph model, Martin is right, it’s the Ralph model that’s available on this site.

Cheers

TipToe,

I borrowed env.egg, env_ground.jpg and env_sky.jpg from the Panda 1.1.0 tutorial Basic-Tutorials–Lesson-2-Intervals.

Everything works well and great like you predicted.

Just awsome!!

AIM

Thanks Andre. I’m glad you like it. Now, if I can just get the point & click movement code working, we’ll have both keyboard AND mouse controls :smiley:.

Cheers

Full code of the camera FPS rotation. Hope it helps.

import ctypes, os
from panda3d.core import WindowProperties
from direct.showbase.ShowBase import ShowBase
from direct.task import Task

class CursorRotate(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        self.props             = base.win.getProperties()
        self.WindowProps	   = WindowProperties()
        self.GetLocalPID       = os.getpid()
        self.SceneMap          = self.loader.loadModel("models/environment")

        self.CameraFOV         = 70
        self.RotationSpeed     = 0.5
        self.MouseMove         = [0, 0]
        self.MouseX            = 0
        self.MouseY            = 0

        self.WindowProps.setCursorHidden(True)
        base.win.requestProperties(self.WindowProps)
        base.disableMouse()

        self.SceneMap.setScale(0.25, 0.25, 0.25)
        self.SceneMap.setPos(-8, 42, 0)
        self.SceneMap.reparentTo(self.render)

        base.camLens.setFov(self.CameraFOV)
        self.taskMgr.add(self.FallowCursor, "FallowRotation")


    def WindowProp(self, task):
        self.WindowProps.setCursorHidden(True)
        base.win.requestProperties(self.WindowProps)
        return Task.done

    def FallowCursor(self, task):
        hwnd = ctypes.windll.user32.GetForegroundWindow()
        lpdw_process_id = ctypes.c_ulong()
        result = ctypes.windll.user32.GetWindowThreadProcessId(hwnd, ctypes.byref(lpdw_process_id))
        process_id = lpdw_process_id.value
        if base.mouseWatcherNode.hasMouse():
            self.Cursor2D_X = base.mouseWatcherNode.getMouseX()             ## Mouse X position
            self.Cursor2D_Y = base.mouseWatcherNode.getMouseY()             ## Mouse Y position

        if process_id == self.GetLocalPID:
            self.taskMgr.add(self.WindowProp, "BackToGame")                 ## Return hidden cursor
            try:
                base.win.movePointer(0, self.props.getXSize() // 2, self.props.getYSize() // 2)         ## Center cursor
                self.MouseMove = [int(self.Cursor2D_X*100), int(self.Cursor2D_Y*100)]                   ## Amplificate movement
            except:
                pass
            self.MouseX    += self.MouseMove[0] * self.RotationSpeed                                    ## Smooth roll rotation
            self.MouseY    += self.MouseMove[1] * self.RotationSpeed                                    ## Smooth pitch rotation
            if self.MouseY > 90: self.MouseY = 90
            elif self.MouseY < -90: self.MouseY = -90
        else:
            self.WindowProps.setCursorHidden(False)
            base.win.requestProperties(self.WindowProps)
        self.camera.setHpr(-self.MouseX, self.MouseY, 0)
        return Task.cont

if __name__ == "__main__":
    CursorRotate().run()

Check my github for features updates on this.

1 Like