2D Physics and Player Controls
Rigidbody, Collider, and keyboard input for a responsive 2D controller.
Step 1: Prepare Player and Ground
Start from the previous scene with Player and Ground. Add Box Collider 2D to Ground if it is missing. Ground should stay static — no Rigidbody — so it does not move under the player.
Add Rigidbody 2D to Player with Body Type set to Dynamic. Set Gravity Scale between 3 and 5 for a snappier platformer feel. Enable Constraints > Freeze Rotation Z so the player does not spin on landing. Set Collision Detection to Continuous to reduce tunneling at high speed.
Step 2: Write Horizontal Movement
Create PlayerController.cs in Assets/Scripts and attach it to Player. Cache Rigidbody2D in Awake with GetComponent
Apply movement in FixedUpdate, not Update. Set rb.velocity to new Vector2(moveInput * moveSpeed, rb.velocity.y) so vertical motion from gravity and jumping is preserved. Test with A/D or arrow keys until movement feels responsive on Ground.
Step 3: Implement Jumping
Add jumpForce (around 14f) and a private bool isGrounded. Only jump when isGrounded is true and Input.GetButtonDown("Jump") is pressed — Space maps to Jump by default.
Create an empty child object named GroundCheck at the player feet. Use Physics2D.OverlapCircle in FixedUpdate with a small radius (0.2f) and a LayerMask for Ground. Draw gizmos in OnDrawGizmosSelected to debug the check visually in the Scene view.
Step 4: Collisions and Triggers
Use solid colliders for walls and floors. Use Is Trigger for pickups, checkpoints, and kill zones. OnTriggerEnter2D fires when at least one collider is a trigger; OnCollisionEnter2D fires for solid collisions.
Tag Player and compare tags in scripts with CompareTag("Player"). Tune collider size slightly smaller than the sprite so feet do not snag on platform edges. Add a KillZone trigger below the level to reset or end the run.
Step 5: Optional — New Input System
Install the Input System package from Package Manager if you want gamepad and rebind support. Create an Input Actions asset with Move (Vector2) and Jump (Button) actions, generate the C# class, and read values from your controller script.
For this tutorial, the classic Input Manager is enough. If you switch to the new system, set Active Input Handling to Both during migration so existing Input.GetAxisRaw calls still work.
Step 6: Polish and Test
Flip the sprite with transform.localScale.x or SpriteRenderer.flipX based on move direction. Add a simple camera follow script that lerps toward the player with a Z offset of -10.
Playtest checklist: movement is responsive, jump only works on ground, no tunneling through floors, and the controller values are saved on the Player Prefab. You are ready to add UI and scoring in the next tutorial.
FAQ
Should movement run in FixedUpdate or Update?
Any direct change to rb.velocity or AddForce belongs in FixedUpdate because it runs on the physics timestep. You can read input in Update and store it for use in FixedUpdate — that is the standard pattern.
Why does the player fall through the ground?
Enable Continuous collision detection, avoid paper-thin colliders, and keep physics timestep at 0.02 by default. If using Tilemaps, add Composite Collider 2D for reliable ground contact.
Kinematic vs Dynamic Rigidbody2D?
Dynamic bodies respond fully to gravity and forces — use them for the player and free physics objects. Kinematic bodies move by script or animation and are ideal for moving platforms and doors. Static colliders have no Rigidbody.