# 4. Physics 2D - Effectors and Joints
Part of [[Physics 2D]]. Previous: [[3. Physics 2D Queries ContactFilter2D and Layers]]. Next: [[5. Physics 2D Kinematic Platformer with Slide]]. 3D counterpart: [[4. Joints]].
> [!note] Verification status
> Effector property names (`useOneWay`, `surfaceArc`, `rotationalOffset`, `speed`, `forceAngle`, `forceMagnitude`, `surfaceLevel`, `density`, `flowMagnitude`, ...) and `Collider2D.usedByEffector` checked against the 6000.5 Scripting Reference; joint list from the 6000.5 Manual "2D joints" section (2026-11-07). **Code not compiled in Unity.**
## 4.1 How effectors work
An Effector 2D is a component that changes how contacts or forces behave for colliders touching a collider marked **Used By Effector** (`Collider2D.usedByEffector`). No code needed for the basic behaviour - which is why they're a great classroom tool for "mechanics as data".
| Effector | Collider is usually | Does | Level Up! use (Level 12 mechanics/hazards) |
|---|---|---|---|
| **Platform Effector 2D** | solid | One-way collision, side friction/bounce control | Jump-through ledges, "drop down" platforms |
| **Surface Effector 2D** | solid | Tangential speed along the surface | Conveyors, treadmills, rivers that carry you |
| **Area Effector 2D** | trigger | Directional force + damping in a volume | Wind tunnels, fans, updrafts |
| **Point Effector 2D** | trigger | Attract/repel from a point | Magnets, black holes, explosion push |
| **Buoyancy Effector 2D** | trigger | Fluid density, surface level, flow | Water, lava (with damage), quicksand |
## 4.2 Step-by-step: one-way platform
1. Create a sprite `Ledge`, add **Box Collider 2D**, tick **Used By Effector**. Layer `OneWay`.
2. Add **Platform Effector 2D**. Defaults: **Use One Way** on, **Surface Arc** 180.
3. Lower **Surface Arc** to ~160 so steep side contacts don't count as "top" - prevents a Dynamic player catching the corner while jumping past.
4. Turn off **Use Side Friction** so the player doesn't stick to the platform's sides.
5. If the player has several colliders (body + feet), turn on **Use One Way Grouping**.
Drop-through (hold Down + Jump) for a **Dynamic** player:
```csharp
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
/// Lets a Dynamic player drop through one-way platforms by ignoring the pair briefly.
[RequireComponent(typeof(Collider2D))]
public class DropThroughOneWay2D : MonoBehaviour
{
[SerializeField] InputActionReference move; // Vector2
[SerializeField] InputActionReference jump; // Button
[SerializeField] LayerMask oneWayMask;
[SerializeField] float ignoreSeconds = 0.35f;
Collider2D _self;
ContactFilter2D _filter;
readonly Collider2D[] _touching = new Collider2D[4];
void Awake()
{
_self = GetComponent<Collider2D>();
_filter = new ContactFilter2D { useTriggers = false };
_filter.SetLayerMask(oneWayMask);
}
void OnEnable() { jump.action.performed += OnJump; jump.action.Enable(); move.action.Enable(); }
void OnDisable() { jump.action.performed -= OnJump; }
void OnJump(InputAction.CallbackContext _)
{
if (move.action.ReadValue<Vector2>().y > -0.5f) return; // need Down held
int n = _self.GetContacts(_filter, _touching);
for (int i = 0; i < n; i++)
StartCoroutine(IgnoreFor(_touching[i]));
}
IEnumerator IgnoreFor(Collider2D platform)
{
Physics2D.IgnoreCollision(_self, platform, true);
yield return new WaitForSeconds(ignoreSeconds);
if (platform != null) Physics2D.IgnoreCollision(_self, platform, false);
}
}
```
> [!warning] Code not compiled in Unity
> Not compiled or play-tested. Your jump script will also see the Jump press - gate it on "not holding Down" or consume the input in one place ([[Input System]]). The Kinematic controller in Part 5 handles one-way platforms with layer masks instead, because effectors act on simulation contacts, not on queries/casts.
## 4.3 Conveyors, wind and water (data-only hazards)
- **Conveyor:** Box Collider 2D (Used By Effector) + Surface Effector 2D, `speed` = 3. Negative speed reverses. Turn on `useContactForce` if heavy crates should feel it.
- **Updraft:** Box Collider 2D **Is Trigger** + Used By Effector + Area Effector 2D, Force Angle 90, Force Magnitude ~20, Linear Damping ~2. Set **Force Target** to Rigidbody so it ignores collider offsets.
- **Water:** trigger + Buoyancy Effector 2D, Surface Level at the top of the volume, Density ~2 (things lighter than this float), Flow Magnitude for currents.
A beat-driven hazard - toggles an effector on a rhythm with a telegraph window (Rogers: hazards must telegraph; mechanics on a readable rhythm):
```csharp
using UnityEngine;
/// Turns an Area Effector 2D (e.g. a fan) on and off on a beat, with a warning phase.
[RequireComponent(typeof(AreaEffector2D))]
public class RhythmicEffectorHazard : MonoBehaviour
{
[SerializeField] float offSeconds = 2f;
[SerializeField] float warnSeconds = 0.75f;
[SerializeField] float onSeconds = 1.5f;
[SerializeField] ParticleSystem warnFx; // dust/sparks while warning
[SerializeField] AudioSource warnAudio; // wind-up sound
AreaEffector2D _fx;
float _t;
void Awake() => _fx = GetComponent<AreaEffector2D>();
void Update()
{
float cycle = offSeconds + warnSeconds + onSeconds;
float prev = _t;
_t = (_t + Time.deltaTime) % cycle;
bool warning = _t >= offSeconds && _t < offSeconds + warnSeconds;
bool active = _t >= offSeconds + warnSeconds;
_fx.enabled = active;
bool enteredWarn = prev < offSeconds && _t >= offSeconds;
if (enteredWarn)
{
if (warnFx) warnFx.Play();
if (warnAudio) warnAudio.Play();
}
if (!warning && warnFx && warnFx.isPlaying && active) warnFx.Stop();
}
}
```
> [!warning] Code not compiled in Unity
> Not compiled or play-tested. For sample-accurate beats drive this from `AudioSettings.dspTime` or [[Timeline Basics]] signals instead of `Time.deltaTime`. The warning audio routing is covered in [[Interactable Audio]].
## 4.4 2D joints (6000.5 Manual list)
| Joint | Constrains | Classic use |
|---|---|---|
| Distance Joint 2D | fixed max/exact distance | rope swing, chain link |
| Fixed Joint 2D | relative pose (springy) | breakable welded props |
| Friction Joint 2D | reduces relative motion | top-down "floor friction" |
| Hinge Joint 2D | rotation around a point, optional motor/limits | doors, flippers, seesaws |
| Relative Joint 2D | keeps relative offset with force limits | follower drones |
| Slider Joint 2D | movement along a line, motor/limits | elevators, pistons |
| Spring Joint 2D | spring distance | bouncy bridges, soft bodies |
| Target Joint 2D | pulls toward a world target | mouse/touch dragging |
| Wheel Joint 2D | suspension + motor | 2D vehicles (compare [[Car Controller]]) |
Step-by-step rope bridge:
1. Create 8 plank sprites in a row; each gets Box Collider 2D + Rigidbody 2D (Dynamic, mass 0.5).
2. Each plank gets a **Hinge Joint 2D** whose **Connected Rigidbody** is the previous plank; anchors on the touching edges.
3. First and last plank hinge to Static anchor posts.
4. Set **Break Force** on the middle hinge for a breakable bridge; listen with `OnJointBreak2D(Joint2D)`.
## 4.5 Target Joint 2D drag (touch or mouse)
```csharp
using UnityEngine;
using UnityEngine.InputSystem;
/// Click/touch-drag any Dynamic body using a Target Joint 2D created on demand.
public class TargetJointDragger2D : MonoBehaviour
{
[SerializeField] Camera cam;
[SerializeField] LayerMask draggableMask;
[SerializeField] float frequency = 5f;
[SerializeField] float dampingRatio = 1f;
[SerializeField] float maxForce = 1000f;
TargetJoint2D _joint;
ContactFilter2D _filter;
readonly Collider2D[] _pick = new Collider2D[1];
void Awake()
{
_filter = new ContactFilter2D { useTriggers = false };
_filter.SetLayerMask(draggableMask);
}
void Update()
{
var pointer = Pointer.current;
if (pointer == null) return;
Vector2 world = cam.ScreenToWorldPoint(pointer.position.ReadValue());
if (pointer.press.wasPressedThisFrame)
{
Collider2D hit = Physics2D.OverlapPoint(world, _filter, _pick) > 0 ? _pick[0] : null;
if (hit && hit.attachedRigidbody && hit.attachedRigidbody.bodyType == RigidbodyType2D.Dynamic)
{
_joint = hit.attachedRigidbody.gameObject.AddComponent<TargetJoint2D>();
_joint.autoConfigureTarget = false;
_joint.anchor = hit.attachedRigidbody.transform.InverseTransformPoint(world);
_joint.frequency = frequency;
_joint.dampingRatio = dampingRatio;
_joint.maxForce = maxForce;
}
}
if (_joint)
{
_joint.target = world;
if (pointer.press.wasReleasedThisFrame)
{
Destroy(_joint);
_joint = null;
}
}
}
}
```
> [!warning] Code not compiled in Unity
> Not compiled or play-tested. Uses the `Physics2D.OverlapPoint(Vector2, ContactFilter2D, Collider2D[])` overload listed in the 6000.5 reference. Fits the drag-and-drop pattern in [[Unity 2D Point-and-Click System]].
## Docs
- Effectors 2D: https://docs.unity.com/en-us/engine/6000.5/manual/unity2d/2d-physics/effectors-2d
- Platform Effector 2D reference: https://docs.unity.com/en-us/engine/6000.5/manual/unity2d/2d-physics/effectors-2d/platform-effector-2d-reference
- `PlatformEffector2D`: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/PlatformEffector2D.html
- 2D joints: https://docs.unity.com/en-us/engine/6000.5/manual/unity2d/2d-physics/2d-joints
- `Physics2D.IgnoreCollision` (listed on): https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Physics2D.html