콘텐츠로 이동

Unity 물리 조인트 완전 가이드

Unity의 물리 조인트 시스템은 두 Rigidbody를 물리적으로 연결해 현실적인 운동을 구현합니다. 문, 체인, 래그돌, 로봇 팔 등의 관절 구조에 활용됩니다. PhysX 기반의 기존 Joint와 Unity 2020.1+의 ArticulationBody 두 계열이 있습니다.


// 문 열기 구현
[RequireComponent(typeof(Rigidbody))]
public class Door : MonoBehaviour
{
private HingeJoint _hinge;
void Awake()
{
_hinge = GetComponent<HingeJoint>();
// 회전축 설정 (로컬 Y축)
_hinge.axis = Vector3.up;
// 회전 제한 설정
var limits = new JointLimits
{
min = 0f,
max = 90f,
bounciness = 0.1f,
bounceMinVelocity = 0.5f
};
_hinge.limits = limits;
_hinge.useLimits = true;
// 스프링으로 자동 닫힘
var spring = new JointSpring
{
spring = 50f,
damper = 10f,
targetPosition = 0f
};
_hinge.spring = spring;
_hinge.useSpring = true;
}
public void Open(float force)
{
var motor = new JointMotor
{
targetVelocity = 120f, // deg/s
force = force,
freeSpin = false
};
_hinge.motor = motor;
_hinge.useMotor = true;
}
}

public class RobotArm : MonoBehaviour
{
[SerializeField] private Rigidbody _upperArm;
[SerializeField] private Rigidbody _foreArm;
private ConfigurableJoint _elbow;
void Awake()
{
_elbow = _foreArm.gameObject.AddComponent<ConfigurableJoint>();
_elbow.connectedBody = _upperArm;
// X축만 회전 허용 (나머지 잠금)
_elbow.xMotion = ConfigurableJointMotion.Locked;
_elbow.yMotion = ConfigurableJointMotion.Locked;
_elbow.zMotion = ConfigurableJointMotion.Locked;
_elbow.angularXMotion = ConfigurableJointMotion.Limited;
_elbow.angularYMotion = ConfigurableJointMotion.Locked;
_elbow.angularZMotion = ConfigurableJointMotion.Locked;
// 각도 제한
var limit = new SoftJointLimit { limit = 120f, bounciness = 0f };
_elbow.highAngularXLimit = limit;
limit.limit = -10f;
_elbow.lowAngularXLimit = limit;
// 드라이브 (목표 각도 추종)
var drive = new JointDrive
{
positionSpring = 1000f,
positionDamper = 100f,
maximumForce = float.MaxValue
};
_elbow.angularXDrive = drive;
}
public void SetElbowAngle(float degrees)
{
_elbow.targetRotation =
Quaternion.Euler(degrees, 0f, 0f);
}
}

3. ArticulationBody — 로봇/래그돌 전용

섹션 제목: “3. ArticulationBody — 로봇/래그돌 전용”
// ArticulationBody는 안정적인 관절 시뮬레이션에 최적
// 기존 Joint보다 폭발/떨림 현상이 적음
public class ArticulatedLeg : MonoBehaviour
{
void Start()
{
var hip = GetComponent<ArticulationBody>();
var thigh = transform.GetChild(0)
.GetComponent<ArticulationBody>();
// 관절 타입: 구형 관절
thigh.jointType = ArticulationJointType.SphericalJoint;
// 각 축 운동 설정
var drive = new ArticulationDrive
{
stiffness = 10000f,
damping = 1000f,
forceLimit = 10000f,
target = 0f
};
thigh.xDrive = drive;
thigh.yDrive = drive;
thigh.zDrive = drive;
// 회전 한계
var limit = new ArticulationReducedSpace(45f);
thigh.xDrive = new ArticulationDrive
{
lowerLimit = -45f,
upperLimit = 45f,
stiffness = 10000f,
damping = 1000f,
forceLimit = 10000f
};
}
}

public class RagdollController : MonoBehaviour
{
private Rigidbody[] _bodies;
private CharacterJoint[] _joints;
private Animator _animator;
void Awake()
{
_bodies = GetComponentsInChildren<Rigidbody>();
_joints = GetComponentsInChildren<CharacterJoint>();
_animator = GetComponent<Animator>();
SetRagdoll(false);
}
public void SetRagdoll(bool active)
{
_animator.enabled = !active;
foreach (var rb in _bodies)
{
rb.isKinematic = !active;
rb.detectCollisions = active;
}
}
public void Die(Vector3 force, Vector3 hitPoint)
{
SetRagdoll(true);
// 피격 지점에 힘 적용
var hitBody = GetNearestBody(hitPoint);
hitBody.AddForceAtPosition(force, hitPoint,
ForceMode.Impulse);
}
private Rigidbody GetNearestBody(Vector3 point)
{
return _bodies
.OrderBy(b => Vector3.Distance(b.position, point))
.First();
}
}

public class Chain : MonoBehaviour
{
[SerializeField] private GameObject _linkPrefab;
[SerializeField] private int _linkCount = 10;
[SerializeField] private float _linkLength = 0.3f;
void Start()
{
Rigidbody prev = GetComponent<Rigidbody>();
for (int i = 0; i < _linkCount; i++)
{
var link = Instantiate(_linkPrefab,
transform.position - Vector3.up * _linkLength * (i + 1),
Quaternion.identity);
var rb = link.GetComponent<Rigidbody>();
var joint = link.AddComponent<HingeJoint>();
joint.connectedBody = prev;
joint.axis = Vector3.right;
// 스윙 제한
var limits = new JointLimits { min = -45f, max = 45f };
joint.limits = limits;
joint.useLimits = true;
prev = rb;
}
}
}

// 에디터에서 조인트 시각화
void OnDrawGizmos()
{
if (_hinge == null) return;
Gizmos.color = Color.yellow;
Gizmos.DrawLine(transform.position,
transform.position + transform.TransformDirection(_hinge.axis));
// 조인트 앵커 표시
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(
transform.TransformPoint(_hinge.anchor), 0.05f);
}

안정적인 관절이 필요하면 ArticulationBody, 레거시 호환이나 단순 구조는 HingeJoint/ConfigurableJoint를 사용하세요. 모든 조인트는 연결된 두 Rigidbody의 질량 비율이 10:1을 넘으면 수치적으로 불안정해지므로 Mass 값을 비슷하게 맞추고, Edit > Project Settings > Physics > Default Solver Iterations를 높여 안정성을 확보하세요.