Code

Movement vectors

Vector3.forward   (0, 0, 1)
Vector3.back      (0, 0, -1)
Vector3.right     (1, 0, 0)
Vector3.left      (-1, 0, 0)
Vector3.up        (0, 1, 0)
Vector3.down      (0, -1, 0)

These are relative to the world space, but you can also use transform.forward, transform.right, and transform.up for directions relative to the object’s local space.

Exporting Variables In Unity

Method 1: public variables

public int health = 100; 
  • This makes the variable editable in the Inspector.

  • Also exposes it to other scripts (which may not always be desirable).


Method 2: [SerializeField] with private or protected

[SerializeField] private float speed = 5f; 
  • This keeps the variable private (encapsulation = good) but still visible and editable in the Inspector.

  • Best practice for most cases.


🛑 NOT shown in Inspector:

private int score = 0; 
  • Private and not serialized → won't show up in the Inspector.


🧠 Bonus: Custom Inspector Name

[SerializeField, Tooltip("The speed the player moves.")] private float speed = 5f; 
  • Tooltip gives a helpful hover description in the Inspector.


Unity Functions

what is PlayerInput?

a built-in mechanism for connecting input actions defined in an Input Actions asset to scripts on a GameObject, typically the player's game object.

PlayerInput playerInput;

playerInput = GetComponent<PlayerInput>();
moveAction = playerInput.actions.FindAction("Move");

What is GetComponent<>()

GetComponent<>() is a method provided by Unity that lets you access other components attached to the same GameObject.

What is a Component?

In Unity, a GameObject (like a player, an enemy, or a tree) is made up of components. Some examples:

  • Transform (position, rotation, scale)

  • Rigidbody (physics)

  • Collider (collision detection)

  • PlayerInput (input system stuff)

  • Camera (for rendering a view)

Each of these is a component attached to a GameObject.

// Get a reference to the Rigidbody component on the current GameObject

Rigidbody rb = GetComponent<Rigidbody>();
these are the componets


What is InputAction?

InputAction moveAction;

moveAction = playerInput.actions.FindAction("Move");
  • playerInput references the PlayerInput component.

  • .actions gives access to all input actions defined in your Input Actions asset.

  • .FindAction("Move") grabs the specific action named "Move".


Vector Movement

Vector2 direction = moveAction.ReadValue<Vector2>();
  • You're reading the current value of the "Move" input action.

  • That action is expected to give you a Vector2 — usually from:

    • WASD keys

    • Arrow keys

    • Left analog stick on a controller

For example:

  • Pressing W gives you (0, 1)

  • Pressing A gives you (-1, 0)

  • Pressing W and D at the same time gives you (1, 1) (but normalized)

So at this point, direction holds something like Vector2(1, 0) if the player is pressing D.

transform.position += new Vector3(direction.x, 0, direction.y)   * speed * Time.deltaTime;
  • Converts the 2D movement direction into a 3D direction.

  • x goes to x

  • y (from the Vector2) becomes z in 3D space

  • y (up/down) is set to 0 so the player doesn't fly up or fall through the ground

more information about Vector 2 and Vector3 -

🎮 1. Why is input usually a Vector2? -

Because most player movement comes from a 2D input device:

  • Keyboard (WASD / Arrow keys)

  • Gamepad left stick

  • Touch joystick on mobile

These all only give input on two axes:

  • Left–rightx

  • Forward–backwardy

So Unity’s Input System is designed to give you a Vector2 because that’s what those devices naturally produce.


Input - Binding Actions

Type

Use For

Example

Add Binding

Simple input

Jump on Spacebar

🔄 Composite (Up/Down/Left/Right)

Directional input

WASD or Arrow Keys for movement

🎮 Binding with One Modifier

Combo input

Sprint with Shift + W

🎮🎮 Binding with Two Modifiers

Advanced combo input

Dev menu with Ctrl + Shift + M

sqrMagnitude

The function sqrMagnitude returns the square of the vector's length, which is the sum of the squares of its components (e.g., x² + y² for Vector2, or x² + y² + z² for Vector3)

when to use - You’re comparing distances or lengths, not needing the exact number.

Need

Use

Just comparing distance

sqrMagnitude

Need exact distance

magnitude

atan2() function

  • Quadrant Handling: Unlike Mathf.Atan(y/x), which only gives a result between -90 and 90 degrees (or -π/2 and π/2 radians), Mathf.Atan2 correctly determines the angle based on the signs of both x and y, covering all four quadrants (0 to 360 degrees or 0 to 2π radians). 

  • Significance: It's crucial for accurately determining angles and directions, especially in 2D and 3D games, where you need to know the angle between a target and a point or between a character and a direction. 

  • Usage: It's commonly used in game development for tasks like:

    • Calculating the angle of a vector 

    • Rotating game objects towards a target 

    • Implementing player movement and aiming systems

Mathf.SmoothDampAngle()

Mathf.SmoothDampAngle is a super useful Unity function for smoothly rotating something over time, especially when dealing with angles that can wrap around (like 0° and 360°).

🧩 Parameters Explained:

Parameter

Meaning

current

The current angle (in degrees)

target

The target angle you want to rotate to

ref currentVelocity

A reference float Unity uses to track speed between frames (you must declare and keep this value)

smoothTime

How long it should take to reach the target angle (lower = faster)

maxSpeed (optional)

Max speed (in degrees per second). Defaults to infinite

deltaTime (optional)

Usually just use Time.deltaTime

Hiding and locking the mouse

Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;

Awake() vs Start()

🟦 Awake():

  • Happens first.

  • Think: “Set up my stuff.”

  • Called right when the object loads.

  • Good for: Getting components, setting default values.

🟩 Start():

  • Happens after Awake(), just before the game starts running.

  • Think: “Do things once everything is ready.”

  • Good for: Starting actions, using other objects that are now ready.

🔁 Simple Example:

Imagine you're baking:

  • Awake() = gathering ingredients and preheating the oven.

  • Start() = actually putting the cake in the oven when it's ready.


Character Movement Code

Character Controller vs RigidBody

Feature

Character Controller

Rigidbody

Physics-Based

No

Yes

Collision Handling

Built-in (via Move)

Physics-based (via forces or velocity)

Gravity

Must apply manually

Handled automatically

Jumping

You calculate manually

Use AddForce or change velocity

Movement Style

Controlled, smooth, game-like

Realistic, physics-driven

Ground Check

Built-in (isGrounded)

You must write custom checks

Pushing Objects

Not supported

Yes (via collisions)

Use Cases

FPS, RPG, third-person movement

Platformers, physics puzzles, ragdolls

Which to Use?

Use Character Controller if:

  • You want fine control over movement (like in an FPS or RPG).

  • Your character isn't meant to interact physically (pushing objects, ragdolls, etc.).

  • You prefer manually coding gravity and jump logic.

Use Rigidbody if:

  • You need physics interactions (knockbacks, bouncing, falling naturally).

  • Your game relies on realism, physics puzzles, or ragdoll effects.

  • You’re comfortable managing forces and drag.

🧪 Example Use Cases:

Game Type

Better Choice

First-Person Shooter

Character Controller

Puzzle Platformer (like Portal)

Rigidbody

3D Adventure (like Zelda)

Character Controller (usually)

Physics Sandbox

Rigidbody

Multiplayer FPS

Character Controller (often combined with Rigidbody for knockbacks)

Code Reference

using System;
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.InputSystem;

public class CharacterMovement : MonoBehaviour
{
    private Vector2 _input;
    private CharacterController _characterController;
    private Vector3 _direction;


    [SerializeField] private float smoothTime = 0.05f;
    [SerializeField] private float speed;
    [SerializeField] private float jumpPower;
    private int _numberOfJumps;
    [SerializeField] private int maxNumberOfJumps = 2;

    private float _currentVelocity;
    private float _gravity = -9.81f;
    [SerializeField] private float gravityMultiplier = 3.0f;
    private float _velocity;


    private void Awake()
    {
        _characterController = GetComponent<CharacterController>();
    }

    private void Update()
    {   
        ApplyGreavity();
        ApplyRotation();
        ApplyMovement();        
    }

     private void ApplyGreavity() 
     {
        if (IsGrounded() && _velocity < 0.0f)
        {
            _velocity = -1.0f;
        }
        else
        {
            _velocity += _gravity * gravityMultiplier * Time.deltaTime;
        }

         _direction.y = _velocity;
     }

    private void ApplyRotation()
    {
        if (_input.sqrMagnitude == 0) return;

        var targetAngle = MathF.Atan2(_direction.x, _direction.z) * Mathf.Rad2Deg;
        var angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref _currentVelocity, smoothTime);
        transform.rotation = Quaternion.Euler(0.0f, angle, 0.0f);
    }

    private void ApplyMovement() 
    {
        _characterController.Move(_direction * speed * Time.deltaTime);

    }

    public void Move(InputAction.CallbackContext context) 
    {
        _input = context.ReadValue<Vector2>();
        _direction = new Vector3(_input.x, 0.0f, _input.y);
    }

    public void Jump(InputAction.CallbackContext context)
    {
        if (!context.started) return;
        if (!IsGrounded() && _numberOfJumps >= maxNumberOfJumps) return;
        if (_numberOfJumps == 0) StartCoroutine(WaitForLanding());
        
        _numberOfJumps++;
        _velocity = jumpPower;
    }

    private IEnumerator WaitForLanding()
    {
        yield return new WaitUntil(() => !IsGrounded());
        yield return new WaitUntil(IsGrounded);

        _numberOfJumps = 0;
    }

    private bool IsGrounded() => _characterController.isGrounded;
}

ApplyGravity Function

private void ApplyGreavity() {
   if (IsGrounded() && _velocity < 0.0f)
   {
      _velocity = -1.0f;
   }

   else
   {
      _velocity += _gravity * gravityMultiplier * Time.deltaTime;
   }

   _direction.y = _velocity;

   }

checking for velocity < 0.0f because

  • When you land on the ground, your character might still have leftover downward velocity from falling.

  • If you don’t clamp/reset it, the character may:

    • sink into the floor slightly before popping back up

    • or have jittery contact with the ground

why _velocity -1.0f?

  • It’s a small downward value to keep the character “snapped” to the ground.

  • Unity’s Character Controller needs a bit of down movement to keep isGrounded stable, due to how collisions work.

  • 0.0f or positive values could cause isGrounded to return false unexpectedly.

why direction.y = velocity.y?

  • _direction is the full movement vector: left/right (x), forward/back (z), and up/down (y).

  • _velocity is your vertical speed — like how fast you're falling or jumping.

"Plug my current jump or fall speed into the movement direction, so the character knows how to move vertically."

You already set x and z from the player input. Now you're just filling in the y part:

🎯 In Summary:

Line

Meaning

if (IsGrounded() && _velocity < 0)

“If I'm grounded and falling, stop falling.”

_velocity = -1.0f;

“Apply a small downward pull to stay snapped to ground.”

_velocity += ...

“Apply gravity over time if airborne.”

_direction.y = _velocity;

“Update my final move vector with vertical velocity.”


Apply Rotation Function

private void ApplyRotation()
{
    if (_input.sqrMagnitude == 0) return;
    
    var targetAngle = MathF.Atan2(_direction.x, _direction.z) * Mathf.Rad2Deg;

    var angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref _currentVelocity, smoothTime);

    transform.rotation = Quaternion.Euler(0.0f, angle, 0.0f);
}

Why Input.sqrtMagnitude?

"If the player isn’t pressing any movement input, then stop — don’t bother rotating the character."

What is sqrMagnitude?

  • _input is a Vector2 (probably from your joystick or WASD keys).

  • sqrMagnitude tells you how far the stick is pushed, but it skips the square root math that magnitude does.

📦 It's a faster version of checking if the vector has any length:

_input.magnitude == 0     // Slower (uses square root) _input.sqrMagnitude == 0  // Faster (no square root) 

And since we only care whether there's any input at all, this is the better choice.

var targetAngle

“Calculate the angle (in degrees) that the player should be rotated toward, based on movement input.”

_direction.x and _direction.z
  • These come from your movement input (_input), turned into a 3D direction vector.

  • Example:

    • Pressing W might give _direction = (0, 0, 1)

    • Pressing D gives _direction = (1, 0, 0)

  • So x is sideways movement, z is forward/backward.

🟡 MathF.Atan2(x, z)
  • This is the arctangent function, and it returns the angle (in radians) between the vector (x, z) and the positive Z-axis.

  • MathF.Atan2(0, 1) = 0 radians → 0 degrees……Character should face forward (0°)

Var angle

SmoothDampAngle - "Gently turn from the current angle to the target angle over time."

Part

What It Means

transform.eulerAngles.y

Your current Y rotation (the way the character is facing right now).

targetAngle

The angle you want to face (calculated from movement direction).

ref _currentVelocity

Keeps track of how fast the angle is changing — needed for smoothness.

smoothTime

How quickly to reach the target angle (smaller = faster rotation).

what is ref? - used to pass arguments to methods by reference, rather than by value. This means that when a variable is passed with ref, any changes made to it within the method will be reflected in the original variable outside the method. 

Transform.Rotation

“Rotate the character around the Y-axis (up/down) by a certain number of degrees.”

Part

What it does

transform.rotation

This is how you set the rotation of your character or object in 3D space.

Quaternion.Euler(x, y, z)

Creates a rotation based on Euler angles — that’s just a fancy way of saying angles in degrees.

0.0f, angle, 0.0f

You're rotating only on the Y-axis (left/right). The X and Z axes (tilt and lean) stay at 0°.


ApplyMovement Function

private void ApplyMovement() 
{
    _characterController.Move(_direction * speed * Time.deltaTime);
}

Code

What it does

_characterController

This is Unity's built-in CharacterController component — it handles movement without needing rigidbodies or physics.

.Move(...)

This is the function that actually moves the character.

_direction

This is the direction the player wants to go — based on keyboard or joystick input (like forward, left, right, etc.) plus gravity/jump (vertical movement).

* speed

Controls how fast the character moves.

* Time.deltaTime

Makes the movement frame-rate independent (so it moves the same speed no matter how fast or slow your computer is running).

move function()

public void Move(InputAction.CallbackContext context) 
{
    _input = context.ReadValue<Vector2>();
    _direction = new Vector3(_input.x, 0.0f, _input.y);
}
InputAction.CallbackContext context
  • This is a parameter passed by Unity’s new Input System.

  • It contains info about the player’s input — like whether a key is pressed, released, or held, and what the input value is.

_input = context.ReadValue<Vector2>();
  • This reads the player’s movement input.

  • Vector2 means it gets a 2D value (like a joystick or WASD keys).

    • .x → horizontal input (A/D or left/right)

    • .y → vertical input (W/S or up/down)

_direction = new Vector3(_input.x, 0.0f, _input.y);
  • This turns the 2D input into a 3D direction for your character.

    • X → left/right (sideways movement)

    • Y → vertical (up/down — we set this to 0 because we're not jumping here)

    • Z → forward/backward


Jump Function

public void Jump(InputAction.CallbackContext context)
{
    if (!context.started) return;
    if (!IsGrounded() && _numberOfJumps >= maxNumberOfJumps) return;
    if (_numberOfJumps == 0) StartCoroutine(WaitForLanding());
    
    _numberOfJumps++;
    _velocity = jumpPower;
}

🔧 The Jump Method (line by line):

public void Jump(InputAction.CallbackContext context) 

This runs when the player presses the jump button (like spacebar or a controller button).

What is context? - context is a special object that comes from Unity's Input System.
It contains information about the input action — like:

  • When it was pressed (started)

  • If it's being held (performed)

  • If it's released (canceled)

  • What value was input (like a direction or a button state)


Step 1:
if (!context.started) return; 

If the player is holding the button down or releasing it — we don’t want to jump again.
We only care about the exact moment they press it.

Unity's Input System tracks the life cycle of a button press:

  • started → the moment it's first pressed

  • performed → when it's being held or moved

  • canceled → when it's released

So context.started checks: "Did the player just press the button down?"

If they didn’t — the line return; says: "Stop here. Don’t run the rest of the Jump() method."


Step 2:
if (_numberOfJumps == 0) StartCoroutine(WaitForLanding()); 
  • This checks if the character is making the first jump (i.e., _numberOfJumps is 0).

  • When you press jump for the first time, the code starts the coroutine WaitForLanding().

  • The coroutine runs separately from the rest of your code, which means it runs in the background.

private IEnumerator WaitForLanding()
{
    yield return new WaitUntil(() => !IsGrounded()); // Wait until they're airborne
    yield return new WaitUntil(IsGrounded);          // Wait until they land
    _numberOfJumps = 0;                              // Reset the jump counter
}

Let’s explain what this is doing:

  1. yield return new WaitUntil(() => !IsGrounded());
    → Waits for the moment they leave the ground.

  2. yield return new WaitUntil(IsGrounded);
    → Then waits until they land again.

  3. _numberOfJumps = 0;
    → Finally, resets the jump counter so they can jump again next time.

What is IEnumerator? - In Unity, IEnumerator is used to create coroutines — methods that wait or pause and then continue without freezing your game.

private IEnumerator MyRoutine()
{
    // Step 1
    Debug.Log("Start");

    // Step 2: Wait 2 seconds
    yield return new WaitForSeconds(2);

    // Step 3
    Debug.Log("Done waiting!");
}

This runs like:

  1. "Start" gets printed

  2. Waits 2 seconds

  3. Then prints "Done waiting!"

And all of this happens without freezing the game


Camera Rotation Code

using UnityEngine;

public class CameraManager : MonoBehaviour
{
    [SerializeField] private Transform target;
    private Vector3 _offset;

    [SerializeField] private float smoothTime;
    private Vector3 _currentVelocity = Vector3.zero;

    private void Awake()
    {
        _offset = transform.position - target.position;
    }

    private void LateUpdate()
    {

        var targetPosition = target.position + _offset;

        transform.position = Vector3.SmoothDamp(transform.position, targetPosition, ref _currentVelocity, smoothTime);
    }
}

[SerializeField] private Transform target; //the character 

🧠 What is Transform?

In Unity, every GameObject (player, camera, enemy, etc.) has a Transform component.

The Transform stores three key things:

  1. Position – where the object is in the world (e.g., (0, 5, -10))

  2. Rotation – which way it’s facing

  3. Scale – how big it is

You're creating a reference to the Transform component of another GameObject — in this case, probably the player.This allows the camera to track the player's position using:

target.position

And because of [SerializeField], you can drag the player GameObject into this field in the Unity Inspector — and Unity will automatically assign its Transform.


[SerializeField] private Transform target;
private Vector3 _offset;
  • _offset: Stores the distance between the camera and the target when the game starts. This keeps the camera the same distance away during gameplay.

  • _currentVelocity: Used internally by SmoothDamp to track how fast the camera is moving. It changes every frame, so it needs to be stored across frames (which is why it’s not a var inside Update()).


private void Awake()
{
    _offset = transform.position - target.position;
}
  • This runs before the first frame.

  • It calculates the initial distance between the camera and the target.

  • This offset makes sure the camera stays the same distance behind the target, no matter where it moves.

Example: If the target is at (0, 0, 0) and the camera is at (0, 5, -10), _offset becomes (0, 5, -10), and the camera will always try to stay at that relative position.


private void LateUpdate()
    {

        var targetPosition = target.position + _offset;

        transform.position = Vector3.SmoothDamp(transform.position, targetPosition, ref _currentVelocity, smoothTime);
    }
}
  • LateUpdate() runs after all Update() calls, which is perfect for camera movement — it lets the player move first, and then the camera follows.

  • target.position + _offset: Calculates the new camera position based on the target’s new position and the original offset.

  • Vector3.SmoothDamp(...): Smoothly moves the camera from its current position toward targetPosition.

    It takes:

    • currenttransform.position (where the camera is now)

    • targettargetPosition (where the camera wants to go)

    • ref _currentVelocity → needed by Unity to calculate smooth movement

    • smoothTime → how fast it moves (you set this in the Inspector)