# Calculating 3D character movement based on YAW and Pitch

I'm not sure how to calculate an XYZ immediately to my characters left based on the direction they are facing.

I am learning to create a 3D game in Monogame on top of .Net Core 3.1 in c# using MonoGame.Framework.WindowsDX.Core.

Right now I'm just drawing a cube in the game world and learning to wire up the controls to freely move a camera around to look at the cube from different angles.

I believe my math to calculate the camera target based on pitch/yaw from the character's position is correct.

I have wired up the mouse so that if I move the mouse up the camera looks up, and if I move it down the camera looks down, and if I move it left|right the camera rotates left or right (yaw). I am using 360 on the X access and 360 degrees on the Y access for the pitch yaw for now.

Here is my player class.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Text;
using YAVBG.Core.Input;

namespace YAVBG.Core.Objects
{
public class Player : Updateable
{
#region Fields
private float _x;
private float _y;
private float _z;
private float _pitch;
private float _yaw;
private float _height;
private float _zoomLevel;
private int _health;
private int _level;
private int _food;
private float _experience;
#endregion

#region Properties
public bool IsMainPlayer { get; private set; }
public Vector3 Position { get => new Vector3(_x, _y, _z); }
public float Pitch { get => _pitch; set => _pitch = value; }
public float Yaw { get => _yaw; set => _yaw = value; }
public float Height { get => _height; set => _height = value; }
public float ZoomLevel { get => _zoomLevel; set => _zoomLevel = value; }
#endregion

#region Constructor/Destructor
public Player(bool isMainPlayer = false) : this()
{
this.IsMainPlayer = isMainPlayer;
}
public Player()
{

_x = 0f;
_y = 0f;
_z = -12f;
_yaw = 0;
_pitch = 0;
_zoomLevel = 500f;
}
#endregion

#region Abstract Implementation
public override void Update(GameTime gameTime)
{
//TODO
}
#endregion

#region Methods
public void StrafeLeft(float units)
{
_x += units;
}
public void StrafeRight(float units)
{
this._x -= units;
}
public void MoveForward(float units)
{
this._z += units;
}
public void MoveBackward(float units)
{
this._z -= units;
}
public void MoveUp(float units)
{
this._y += units;
}
public void MoveDown(float units)
{
this._y -= units;
}
public void MoveYaw(float y)
{
this._yaw += y;
}
public void MovePitch(float x)
{
this._pitch += x;
}
/// <summary>
/// Get the matrix of where the player is looking based on their current position, zoom level, pitch, and yaw
/// </summary>
/// <returns></returns>
public Matrix GetCameraViewMatrix()
{

var position = this.Position;
var zoomLevel = this.ZoomLevel;
var yaw = this.Yaw;
var pitch = this.Pitch;

//todo make headCamera higher than player position
Vector3 headCamera = new Vector3(position.X, position.Y, position.Z);
var cx = Math.Sin(yaw) * Math.Cos(pitch);
var cy = Math.Sin(pitch);
var cz = Math.Cos(yaw) * Math.Cos(pitch);
var pX = zoomLevel * cx;
var pY = zoomLevel * cy;
var pZ = zoomLevel * cz;
var camTarget = new Vector3((float)pX, (float)pY, (float)pZ);
var matrix = Matrix.CreateLookAt(headCamera, camTarget, Vector3.Up);
return matrix;
}
#endregion
}
}


And here is my World Class

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Text;
using YAVBG.Core.Input;
using YAVBG.Core.Primitives;

namespace YAVBG.Core.Objects
{
public class World : Updateable
{
#region Fields
private Cube _cube;
private InputManager _input;
#endregion

#region Properties
public MainPlayer MainPlayer { get; private set; }
public Matrix ProjectionMatrix { get; private set; }
public Matrix WorldMatrix { get; private set; }
public Cube Cube { get => _cube; }
#endregion

#region Constructor/Destructor
public World(float aspectRatio, float fieldOfView, int gameWidth, int gameHeight)
{
this._input = new InputManager(gameWidth, gameHeight);
this._cube = new Cube();
this.MainPlayer = new MainPlayer();
this.ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(fieldOfView), 1.5f, 0.01f, 10000f);
this.WorldMatrix = Matrix.CreateWorld(new Vector3(0f, 0f, 0f), Vector3.Forward, Vector3.Up);

this._input.KeysBeingHeldDown += InputManager_KeysBeingHeldDown;
this._input.MouseMovedX += _input_MouseMovedX;
this._input.MouseMovedY += _input_MouseMovedY;
}
#endregion

#region Abstract Implementation
public override void Update(GameTime gameTime)
{
this._input.Update(gameTime);
this.MainPlayer.Update(gameTime);
}
#endregion

#region Input Events
private void _input_MouseMovedY(object sender, int e)
{
var modified = e < 0 ? 0.001f : -0.001f;
this.MainPlayer.MovePitch(modified);
}

private void _input_MouseMovedX(object sender, int e)
{
var modified = e < 0 ? 0.001f : -0.001f;
this.MainPlayer.MoveYaw(modified);
}
private void InputManager_KeysBeingHeldDown(object sender, Microsoft.Xna.Framework.Input.Keys e)
{
if (e == Keys.W)
this.MainPlayer.MoveForward(0.1f);
if (e == Keys.A)
this.MainPlayer.StrafeLeft(0.1f);
if (e == Keys.S)
this.MainPlayer.MoveBackward(0.1f);
if (e == Keys.D)
this.MainPlayer.StrafeRight(0.1f);
if (e == Keys.Space)
this.MainPlayer.MoveUp(0.1f);
if (e == Keys.LeftShift)
this.MainPlayer.MoveDown(0.1f);
}
#endregion
}
}


Notice that right now, my code to move the player when pressing "A" to the left (strafe left if you will) is just decreasing x, same thing with the other WASD keys.

This works great if the camera is pointed straight ahead, then you're moving on a 2D plane with WASD.

But when the camera changes positions to any YAW/Pitch, WASD is no longer correct, it will still move you on 1 axis and you're no longer oriented on that access.

So I'm looking for guidance on what kind of math I need to understand to be able to calculate the full xyz that's directly to the left.

Basically I want to calculate a line that is at axis 0 based on orientation. So Say I'm facing 45 degrees to the North East Exactly with 0 yaw (looking straight ahead), then I need to be able to calculate any point on the line exactly to the north west on the x/z plane.

In monogame the Y axis is height, X axis is (left/right) and Z is depth (forward backward).

I might not be using all the correct terms. Been programming for 20 years, this is my first shot at a 3D game.

A vector to your camera's right is the same as the forward vector of a camera that's looking 90 degrees further in yaw, and at zero pitch. So you can modify your look vector code to give you your right vector.

var rx = Math.Cos(yaw);
var ry = 0;
var rz = -Math.Sin(yaw);

this._right = new Vector3(rx, ry, rz);


(Here I used the identities that Sin(a + 90°) = Cos(a) & Cos(a + 90°) = -Sin(a))

To strafe right, it's just:

this._position += this._right * strafeSpeed;


(Or negate it to strafe left)

• That worked for left/right, could you help me with forward backward? – Ryan Mann Feb 21 at 5:49
• I think I got it: var bz = (float)Math.Sin(Yaw); _backVector = new Vector3(this.Position.X, 0, bz == 0 ? -1: -bz); var fz = (float)Math.Sin(Yaw); _forwardVector = new Vector3(this.Position.X, 0, fz == 0 ? 1 : fz); Problem is, when Yaw is on the left (negative) the forward/back reverse, so I need to fix that. – Ryan Mann Feb 21 at 6:05
• ah nah, so my Yaw is in radians in 360 degrees, so it's never negative, when you look left is just goes way up around to what that would be. So dead ahead is 0 degrees, but slightlty to the left would be 359.99999 degrees. I will tweak it. – Ryan Mann Feb 21 at 6:10
• You shouldn't mix positions and offsets in a single vector like that. Use the same strategy as before: your forward vector is the same as your look vector, but with zero pitch. Your back vector is just it's negation. You do not need to flip anything when the angle is negative — your trig functions already change sign where appropriate. – DMGregory Feb 21 at 12:33
• Ah, yeah, ok that all works now. I need to study trig so I understand this better... But I have a full First Person Camera with WASD and pitch/yaw working now. Thanks. – Ryan Mann Feb 21 at 14:28