# 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