Drag World to Move Camera

Hello, I am making a top-down view game and am wondering how to set up a camera movement system where I can grab the world and drag it to move my view, like many RTS games do. Any help is much appreciated!

Just an idea-- you could set up the camera parented to and facing a central Empty that represented the camera focus. Then, when Left Mouse Button was pressed, said empty would engage a Mouselook script. When LMB was released it would deactivate the Mouselook again. Depends what else you want LMB to do; if it served more functions this would be a bit more complicated, but, well… You get the idea.

@amuddypizza: Just using a regular mouse look script would only change the the rotation of the camera.
Maybe a better explanation would be the navigation on Google maps, when you drag a point on the map, i want the camera to move accordingly.

Right, you could do like amuddypizza said, and just change the mouselook script from changing the rotation to changing the position.

If I did that I could indeed move the camera, but not in the way I want. A mouselook script resets the mouse position to the center of the screen every frame, which is not what I want, the mouse should stay fixed to the location where I grabbed.

Does anyone have any ideas?

/uploads/default/original/4X/4/4/3/443f59bd73f0d4deee8e10dc262ec7b93df3bc46.blendd=1404150648

It’s in there,

look at Free movement block, you will need to add logic to only do it when clicking

Edit-Added logic and extracted only what you need

Attachments

JustWhatUNeed.blend (454 KB)

It would seem that you want to determine the relationship with the mouse panning and the field of view.

The mouse position attribute returns the normalised position of the mouse, and the fov attribute returns the angle of the field of view of the camera. I’m not that familiar with the camera maths, but assuming these assertions are correct, then you simply need to do the following:

When first left clicking the mouse, store the position of the cursor, and initial rotation of the camera.
Whilst the mouse is held, the camera orientation is (as an euler) the delta mouse position * fov for each XY axis


from bge import logic, render, events
from math import atan, radians, tan
from mathutils import Euler


def main(cont):
    own = cont.owner
    mouse = logic.mouse
    
    mouse_state = mouse.events[events.LEFTMOUSE]
    if mouse_state == logic.KX_INPUT_JUST_ACTIVATED:
        own['start_mouse'] = mouse.position
        own['start_ori'] = own.worldOrientation.to_euler()
    
    elif mouse_state == logic.KX_INPUT_ACTIVE:
        start_pos = own['start_mouse']
        start_ori = own['start_ori']
        
        mouse_delta_x = mouse.position[0] - start_pos[0]
        mouse_delta_y = mouse.position[1] - start_pos[1]


        height = render.getWindowHeight()
        width = render.getWindowWidth()
        
        h_fov = radians(own.fov)
        v_fov = 2 * atan(tan(h_fov / 2) * (height / width))
        
        theta_x = mouse_delta_x * h_fov
        theta_y = mouse_delta_y * v_fov
        
        orientation = Euler((theta_y, theta_x, 0.0))
        orientation.rotate(start_ori)
        
        own.worldOrientation = orientation

This works for me, with an always sensor, True pulse triggering, and a Python controller, Module mode (mouse.main) if the code is in a textblock called “mouse.py”

To be more detailed about what the code does:

The fov attribute seems to refer to the horizontal (x axis) Field Of View. So, you need to determine the vertical equivalent (see Wikipedia). Then, we do as aforementioned to determine the delta rotation. Now some transformations are required.

The world transformations transform from an origin to world space. So, the world orientation is a transform from axis aligned objects (0, 0, 0) to the current orientation in world. Cameras can be a little confusing, because their 0, 0, 0 orientation results in the camera facing the negative Z axis.

So, when applying the delta rotation, we want to rotate relative to the current orientation. Recall that the world orientation is from 0, 0, 0 to the current orientation, where 0, 0, 0 is axis aligned, and thus 0, 0, 0 is the axis alignment of the object. So, rotating relative to the object’s current axis is simply rotating the delta rotation by the world orientation. We account for the fact that cameras are naturally facing -Z by applying the Yaw (z rotation) to the +Y axis, which is the relative Z axis for a camera.

@BluePrintRandom: Thanks, but I’m wanting to actually be able to have the mouse cursor stay fixed to where I grab, not just move the camera when the cursor moves.
@agoose77: Thanks, that’s super close to what I’m looking for! I would like to change the location of the camera, instead of the rotation though.

hey, try this
drag_camera.blend (462 KB)

by assigning old position of the mouse with delay sensor to a property, i can substract it with current mouse position, and use the value i get as the movement of the camera

you can also set the camera physics to rigid body and lock z translation and all rotation, then change the movement in the script into a Force. it will make the camera looks like in google maps, when you drag fast, and quicly release, its still moving and slowing down.

This one with Force
drag_camera(with force).blend (458 KB)
Note: change the Translation damping in the camera physics as you want.

This script requires the same settings as my previous example (in terms of logic bricks)

from bge import logic, events

def project(height, direction, source):    
    factor = (height - source.z) / direction.z
    return source + direction * factor
    
def main(cont):
    own = cont.owner
    mouse = logic.mouse
    
    direction = -own.getScreenVect(*mouse.position)
    ray = own.rayCastTo(own.worldPosition + direction, 1000)
    
    if not ray:
        return


    mouse_state = mouse.events[events.LEFTMOUSE]
    if mouse_state == logic.KX_INPUT_JUST_ACTIVATED:
        own['start_mouse'] = mouse.position
        own['object'] = ray
        own['start_pos'] = own.worldPosition.copy()
    
    elif mouse_state == logic.KX_INPUT_ACTIVE:
        start_mouse = own['start_mouse']
        start_pos = own['start_pos']
        obj = own['object']
        
        height = obj.worldPosition.z
        position = own.worldPosition
        
        direction_start = -own.getScreenVect(*start_mouse)
        
        projection_start = project(height, direction_start, position)
        projection_end = project(height, direction, position)
    
        displacement = projection_end - projection_start
        position[:] = start_pos - displacement

Thanks everyone! I think I’ll use agoose77’s script since it has the least amount of setup. It would be nice if the cursor would stay perfectly stuck to where I started dragging, but this works good enough for what I need. Thanks!

This should realise that goal.camera.blend (477 KB)

Sorry, I meant in the 3D view. So if I started dragging by clicking on the arm of a statue for example, when I stopped dragging the cursor would be on that same spot on the statue.


from bge import logic, events


def project(height, direction, source):    
    factor = (height - source.z) / direction.z
    return source + direction * factor
    
def drag(cont):
    own = cont.owner
    mouse = logic.mouse
    
    direction = -own.getScreenVect(*mouse.position)
    hit_obj, hit_pos, hit_nor = own.rayCast(own.worldPosition + direction, own, 1000)


    mouse_state = mouse.events[events.LEFTMOUSE]
  
    if mouse_state == logic.KX_INPUT_JUST_ACTIVATED:
        if hit_pos is None:
            return
        
        own['start_mouse'] = mouse.position
        own['start_height'] = hit_pos.z
        own['start_pos'] = own.worldPosition.copy()
        own['active'] = True
    
    elif mouse_state == logic.KX_INPUT_ACTIVE:
        active = own['active']
        
        if not active:
            return
        
        start_mouse = own['start_mouse']
        start_pos = own['start_pos']
        height = own['start_height']
        
        position = own.worldPosition
        
        direction_start = -own.getScreenVect(*start_mouse)
        
        projection_start = project(height, direction_start, position)
        projection_end = project(height, direction, position)
    
        displacement = projection_end - projection_start
        position[:] = start_pos - displacement


    elif mouse_state == logic.KX_INPUT_JUST_RELEASED:
        own['active'] = False

Wow, thanks, that is exactly what I wanted! Now to look through and try to understand it. Once again, thanks a ton.

Nice work Goosey