Lesson 4: Player Movement & Controls
Objective
In this lesson, you will implement responsive 2D character movement, jumping, running, and basic controls for your platformer game. You'll learn how to create smooth, professional-feeling movement that responds perfectly to player input.
What You'll Learn
- How to set up a 2D character controller
- Implementing responsive movement and jumping
- Adding running mechanics and speed variations
- Creating smooth camera following
- Input handling and control customization
Prerequisites
- Unity 2D project set up from Lesson 2
- Character sprite created from Lesson 3
- Basic understanding of C# scripting
Step 1: Setting Up Your Character Controller
Step 1.1: Create the Player GameObject
- In your scene, create a new GameObject and name it "Player"
- Add a Sprite Renderer component to display your character sprite
- Add a Rigidbody2D component for physics
- Add a Collider2D component (Capsule Collider 2D works well for characters)
- Set the Gravity Scale to 3 (adjust for your game's feel)
Step 1.2: Configure Physics Settings
- Select your Player GameObject
- In the Rigidbody2D component:
- Set Body Type to "Dynamic"
- Set Gravity Scale to 3
- Set Freeze Rotation Z to true (prevents character from tipping over)
- Set Linear Drag to 0.5 (adds air resistance)
Step 2: Creating the Movement Script
Step 2.1: Create the Player Controller Script
- Create a new C# script called "PlayerController"
- Attach it to your Player GameObject
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[Header("Movement Settings")]
public float moveSpeed = 5f;
public float jumpForce = 12f;
public float runMultiplier = 1.5f;
[Header("Ground Check")]
public Transform groundCheck;
public float groundCheckRadius = 0.2f;
public LayerMask groundLayer;
private Rigidbody2D rb;
private bool isGrounded;
private bool isRunning;
private float horizontalInput;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
HandleInput();
CheckGrounded();
Move();
}
void HandleInput()
{
horizontalInput = Input.GetAxis("Horizontal");
isRunning = Input.GetKey(KeyCode.LeftShift);
if (Input.GetButtonDown("Jump") && isGrounded)
{
Jump();
}
}
void Move()
{
float currentSpeed = moveSpeed;
if (isRunning)
{
currentSpeed *= runMultiplier;
}
rb.velocity = new Vector2(horizontalInput * currentSpeed, rb.velocity.y);
}
void Jump()
{
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
}
void CheckGrounded()
{
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
}
}
Step 2.2: Set Up Ground Detection
- Create an empty GameObject as a child of your Player
- Name it "GroundCheck"
- Position it at the bottom of your character's feet
- Assign this GameObject to the Ground Check field in your script
- Create a Ground layer in your project
- Assign your ground/platform objects to this layer
Step 3: Advanced Movement Features
Step 3.1: Add Coyote Time (Advanced Jumping)
Coyote time allows players to jump for a brief moment after leaving a platform:
[Header("Advanced Movement")]
public float coyoteTime = 0.2f;
public float jumpBufferTime = 0.2f;
private float coyoteTimeCounter;
private float jumpBufferCounter;
void Update()
{
HandleInput();
CheckGrounded();
HandleCoyoteTime();
Move();
}
void HandleCoyoteTime()
{
if (isGrounded)
{
coyoteTimeCounter = coyoteTime;
}
else
{
coyoteTimeCounter -= Time.deltaTime;
}
if (Input.GetButtonDown("Jump"))
{
jumpBufferCounter = jumpBufferTime;
}
else
{
jumpBufferCounter -= Time.deltaTime;
}
if (jumpBufferCounter > 0f && coyoteTimeCounter > 0f)
{
Jump();
jumpBufferCounter = 0f;
}
}
Step 3.2: Add Variable Jump Height
Allow players to control jump height by holding the jump button:
[Header("Jump Control")]
public float jumpCutMultiplier = 0.5f;
void Update()
{
HandleInput();
CheckGrounded();
HandleCoyoteTime();
Move();
HandleJumpCut();
}
void HandleJumpCut()
{
if (Input.GetButtonUp("Jump") && rb.velocity.y > 0)
{
rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y * jumpCutMultiplier);
}
}
Step 4: Camera Following
Step 4.1: Set Up Camera Follow Script
- Create a new C# script called "CameraFollow"
- Attach it to your Main Camera
using UnityEngine;
public class CameraFollow : MonoBehaviour
{
[Header("Follow Settings")]
public Transform target;
public float smoothSpeed = 0.125f;
public Vector3 offset;
[Header("Camera Bounds")]
public bool useBounds = false;
public float leftBound;
public float rightBound;
public float topBound;
public float bottomBound;
void LateUpdate()
{
if (target == null) return;
Vector3 desiredPosition = target.position + offset;
if (useBounds)
{
desiredPosition.x = Mathf.Clamp(desiredPosition.x, leftBound, rightBound);
desiredPosition.y = Mathf.Clamp(desiredPosition.y, bottomBound, topBound);
}
Vector3 smoothedPosition = Vector3.Lerp(transform.position, desiredPosition, smoothSpeed);
transform.position = smoothedPosition;
}
}
Step 4.2: Configure Camera Settings
- Assign your Player to the Target field
- Set the Offset to (0, 2, -10) for a good starting position
- Adjust Smooth Speed to 0.125 for smooth following
- Enable Use Bounds if you want to limit camera movement
Step 5: Input Customization
Step 5.1: Create Custom Input Actions
- Go to Edit > Project Settings > Input Manager
- Create new input axes for better control:
Horizontal Movement:
- Name: "Horizontal"
- Type: Key or Mouse Button
- Positive Button: "d"
- Negative Button: "a"
- Alt Positive Button: "Right"
- Alt Negative Button: "Left"
Jump:
- Name: "Jump"
- Type: Key or Mouse Button
- Positive Button: "space"
- Alt Positive Button: "w"
Step 5.2: Add Controller Support
void HandleInput()
{
// Keyboard input
horizontalInput = Input.GetAxis("Horizontal");
isRunning = Input.GetKey(KeyCode.LeftShift);
// Controller input
if (Input.GetAxis("ControllerHorizontal") != 0)
{
horizontalInput = Input.GetAxis("ControllerHorizontal");
}
if (Input.GetButtonDown("Jump") && (isGrounded || coyoteTimeCounter > 0f))
{
Jump();
}
}
Step 6: Movement Polish and Feel
Step 6.1: Add Movement Acceleration
[Header("Movement Polish")]
public float acceleration = 10f;
public float deceleration = 10f;
void Move()
{
float targetSpeed = horizontalInput * moveSpeed;
if (isRunning)
{
targetSpeed *= runMultiplier;
}
float currentSpeed = rb.velocity.x;
float speedDifference = targetSpeed - currentSpeed;
float accelerationRate = (Mathf.Abs(targetSpeed) > 0.01f) ? acceleration : deceleration;
float movement = speedDifference * accelerationRate;
rb.AddForce(movement * Vector2.right, ForceMode2D.Force);
}
Step 6.2: Add Landing Effects
[Header("Landing Effects")]
public ParticleSystem landingEffect;
public AudioClip landingSound;
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.layer == groundLayer && rb.velocity.y <= 0)
{
if (landingEffect != null)
{
landingEffect.Play();
}
if (landingSound != null)
{
AudioSource.PlayClipAtPoint(landingSound, transform.position);
}
}
}
Step 7: Testing and Debugging
Step 7.1: Add Debug Visualization
void OnDrawGizmosSelected()
{
if (groundCheck != null)
{
Gizmos.color = isGrounded ? Color.green : Color.red;
Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius);
}
}
Step 7.2: Test Your Movement
- Basic Movement: Test left/right movement
- Jumping: Test single jumps and multiple jumps
- Running: Test the run button functionality
- Coyote Time: Test jumping just after leaving a platform
- Variable Jump: Test holding vs. tapping jump
Troubleshooting Common Issues
Issue 1: Character Sliding on Slopes
Problem: Character slides down slopes when standing still Solution: Add friction material to your collider or increase linear drag
Issue 2: Jumping Too High/Low
Problem: Jump feels wrong
Solution: Adjust the jumpForce
value in your script (try values between 8-15)
Issue 3: Movement Feels Sluggish
Problem: Character doesn't respond quickly to input
Solution: Increase acceleration
value or reduce deceleration
Issue 4: Camera Jitter
Problem: Camera shakes when following player
Solution: Increase smoothSpeed
value or use FixedUpdate
instead of LateUpdate
Pro Tips for Better Movement
Tip 1: Fine-tune Your Values
- Move Speed: 5-8 for most platformers
- Jump Force: 10-15 depending on gravity
- Run Multiplier: 1.5-2.0 for noticeable difference
Tip 2: Add Movement Feedback
- Use particle effects for landing
- Add screen shake for impacts
- Include audio cues for different actions
Tip 3: Test on Different Devices
- Mobile: Use touch controls
- Console: Test with gamepad
- PC: Ensure keyboard feels responsive
Summary & Next Steps
Congratulations! You've successfully implemented responsive 2D character movement with jumping, running, and smooth camera following. Your character now feels professional and responsive to player input.
Key Takeaways:
- Rigidbody2D provides realistic physics-based movement
- Ground detection ensures proper jumping mechanics
- Coyote time and jump buffering improve player experience
- Smooth camera following enhances gameplay feel
- Input customization allows for different control schemes
In the next lesson, "Lesson 5: Level Design & Platforms," you'll learn to design and implement platform mechanics, create level layouts, and build engaging obstacle courses for your platformer game.
Mini Challenge: Add a double jump ability to your character. Modify the jump system to allow one additional jump while in the air, and add a visual effect to show when the double jump is available.
Found this lesson helpful? Bookmark it for quick reference and share your movement implementation with the community!