3rd Person Camera Math

Hey. So, I’ve been trying to work on a 3rd person camera setup, but I can’t quite get it to work. I’m trying to emulate the ‘classic’ 3rd person camera setup in which you can control the camera (rotation and tilt) with the mouse or right analog stick of a joystick, and move with the WASD or left analog stick.

I’ve got it working alright so far, but when the camera tilts downward toward the player, he moves forward or backward very slowly (I’m guessing because local forward +Y for the player is down toward the ground). If anyone could help with the math, it would be greatly appreciated.



           mv = obj['mv'].copy()
	
	mv.z = 0.0

	mv = mv.xzy

	mv.z *= -1
	
	mv = cam.worldOrientation * mv 	# Orient towards the camera

	mv.z = 0
				
	obj.setLinearVelocity([mv.x, mv.y, obj['mv'].z])
	
	if mv.magnitude > 0:
		
		fv = mv.yxz
		fv.y *= -1
		
		obj.alignAxisToVect(fv, 0, 0.5)


Hard to say exactly which part of your code is the problem since I don’t know what the ‘mv’ vector is, but I can tell you that you only want to use the camera’s rotation about the global Z axis when determining which direction to move your character (assuming your character only moves on the ground and doesn’t freely fly around). Otherwise the movement vector you come up with will be rotated towards the ground or sky depending on where the camera is facing. This gives you less “force” in the xy plane, which is why your character moves slowly.

Sorry about that - mv is the movement vector, and is the player’s global movement speed. And yeah, that makes a lot of sense. Do you happen to know how I can cancel out the X and Y portions of the camera’s orientation?

Something like this should do the trick:

camAngle = camera.worldOrientation.to_euler()[2]
camEul = mathutils.Euler((0.0, 0.0, camAngle), ‘XYZ’)


vec = -cam.worldOrientation.col[2].copy() #get the vector "forward" of camera (-Z)
vec[2] = 0.0 #cut the Z axis global
vec.normalize()  #normalize X and Y (z should still 0)
obj.alignAxisToVect(vec, 1 , 0.5)

:wink:

PS: this not solve the speed question, but i not nderstand well the code
obj.localLinearVelocity.y = 3.0 (after the alignament)
should work

Hi Solarlune,

you can simple orbit the camera around a center (as with MouseOrbiter). This is pretty simple.
Or do I miss something in your requirements?

@MarcoIT - Thanks for the suggestion, but that didn’t solve the problem (at least, not the way that I implemented it).
@Monster - The problem isn’t that I can’t make the camera orbit around a point, but rather that the player’s movement changes depending on the camera’s rotation. When it’s more parallel to the player’s plane of movement, it works fine, but when the camera tilts upward to go for an overhead-type of view, the player’s forward and backward movement slows down (probably because ‘forward’ for the player relative to the camera is into the ground).

I might just try doing it with a setup to align the player’s positive Y axis to the camera, and then rotate it around to the direction that the player’s moving. Thanks for the tips, guys.

So it is more a camera-relative movement?

i re-try



vec = -cam.worldOrientation.col[2].copy()
vec.z = 0.0
vec.normalize()
if vec.magnitude < 0.5 : #this should be unnecessary
    vec[1] = 1.0 

    
moveSpeed = 2.0 # not necessary if is 1.0
vec *= moveSpeed 
mv = obj["mv"]
obj.setLinearVelocity((vec.x, vec.y, mv.z ))


# to make a bit of debug:
bge.render.drawLine(cam.worldPosition, obj.worldPosition,[0,0,1])
bge.render.drawLine(obj.worldPosition, obj.worldPosition+vec*5, [1,0,0])

@Monster - Right.
@MarcoIT - Thanks a lot! That worked for me.

Now I need to move local to the camera, but still move local to the directions that I’m pressing (I have a movement vector that I’m using, obj[‘mv’]). The vector is global units (i.e. doesn’t change direction according to which direction the player object is facing). A solution would be to keep the player object facing the camera with alignAxisToVect, and then align the armature child to the movement vector (with some tweaking), but I’d still like to do this with pure math if possible.

The previous way I did this in my older projects was to multiply the camera’s world orientation matrix by the player’s movement vector (after some swizzling), but that causes the original problem that I outlined in the first post.

Any ideas?

If I’m not misunderstanding you, all you should have to do is rotate the movement vector by the rotation of the camera.

I’m not sure how you would do it with MarcoIT’s method, but if you used my method just use obj[‘mv’].rotate(cameraEul)

I tried that, and it works pretty well, but there seems to be a problem when the camera looks into a certain angle. When it looks up, from ‘under’ the player, it turns from 3.0 radians to 0.1, which makes the player flip direction (for example, going from straight forward to suddenly walking backwards). I should put up a test file, but this is pretty complex, so I can’t isolate my code easily. Here’s what I’ve got so far.



camAngle = cam.worldOrientation.to_euler()[2]

	camEul = mathutils.Euler((0.0, 0.0, camAngle), 'XYZ')	# Some sort of gimbal locking problem

	mv = obj['mv'].copy()
	
	mv.rotate(camEul)
		
	if mv.magnitude > 0.5:
		
		obj.alignAxisToVect(mathutils.Vector([0, 0, 1]), 2)
		obj.alignAxisToVect(mv, 1, 0.4)


I had the same problem actually. This should fix it.

camAngle = camera.localOrientation.to_euler()
        
        if camAngle[1] > math.pi / 2:
            camAngle = camAngle[2] + math.pi
        else:
            camAngle = camAngle[2]

camEul = mathutils.Euler((0.0, 0.0, camAngle), 'XYZ')

From what I can tell with the testing I’ve done, the reason you get the wrong rotation when the camera is looking up is because the camera’s vector along its local z-axis goes from having a positive z component to a negative z component. This causes the representation of camera’s rotation about the global z-axis to effectively be inverted, which is why the movement vector is 180 degrees off when rotated. Checking when the camera’s rotation about the y-axis is > pi/2 (which happens whenever the camera is looking up) and adding pi/2 to the z rotation corrects this.

You may be able to avoid this problem all together by using another form of rotation besides Eulers, but I haven’t tried since I find things like matrices and quaternions nonintuitive and hard to work with, so I avoid them when possible.

Yo, I got around to trying this out, and it works. Thanks a lot, Mobious! Yeah, I’d rather use Matrices or Quaternions, but I couldn’t really figure out how to set the Matrix up so that it only takes into account its Z-rotation, not its X or Y rotations, and I’ve yet to fully understand Quaternions.

Aligning a matrix vertically is pretty simple in theory but as far as I know adds more needless complication in execution; as an orientation matrix consists of three vectors (representing the facing of the three axes) just set the Z component to 0 in each axis and then normalize the result (except the Z axis which we set to [0,0,1]).
On a side note, in case you’re not familiar with matrices, they’re normally arranged so matrix[0] is the X component of each axis and matrix[1]is the Y components and so forth, so I’m using matrix.col to access the matrix arranged instead such that matrix[0] is all three components of the X axis, matrix [1] is the Y axis, and so on.


camMat=camera.worldOrientation.copy()
camMat.col[2]=[0,0,1] #align the Z axis of the matrix to the global Z
for axis in camMat.col[:2]: #as for the X and Y axes:
    axis[2]=0 #remove the Z component
    axis=axis.normalize() #and then get them back to length 1

that returns a matrix that’s facing the same way as the camera except aligned with the global Z. I’m sure there’s a much more straightforward way to do that, but one of the side effects of being self-taught is that I don’t know a lot of things that I probably should.

one useful function with quaternion for the camera is this:

target = obs["Cube"]

cam.worldOrientation = (target.worldPosition - cam.worldPosition).to_track_quat("-Z","Y")

as say Capitain Oblivion , to read the matrix use matrix.col
worldOrientation.col[0] is the vector red (X) that you see in edit mode (with “normal” orientation wiew)

:wink:

Attachments


Thanks a lot for this, Captain Oblivion. That’s exactly what I needed to get my third person camera setup to work. I knew matrices were just vectors for each axis in the BGE, but I didn’t know how to isolate the Z-axis like you did. Thanks again!

@MarcoIT - Thanks to you as well.