Unity에서 부모와 자식이 모두 nonkinematic rigidbody 일 때, 부모에게 AddForce를 한다고 해도 자식이 상대위치를 유지하지는 않습니다.
반대로 Transform을 조작하면 자식이 상대위치를 유지하며 부모를 따라오지만 충돌처리가 제대로 되지 않습니다.
따라서 이번에는 간단한 물리를 통해 실제 우주와 비슷한 행성계를 만들어보고자 합니다.
태양계와 비슷하게 만드려면 중앙에 가만히 있는 항성 1개와 그 주변을 도는 행성 여러개가 있습니다.
해당 행성들의 힘은 항성쪽으로 받으며, 그 수직방향으로 속도가 작용되기 때문에 원을 그리며 공전을 합니다.
다만 이렇게하면 힘의 균형이 깨질 경우 항성쪽으로 빨려가거나 밖으로 나가버리는데, 게임에서는 똑같은 궤도를 계속 그려야하기 때문에 힘이 아닌 속도로 컨트롤 해야합니다.


우선 궤도상에서 다음 프레임에 가야할 곳의 위치를 찾고, 해당 위치까지 Lerp시켜서 이동할 수 있도록 합니다.
먼저 원 궤도 위에서 삼각함수를 통해 1차로 위치를 잡고, (orbitAngle, 0, 0) 를 Quaternion으로 변환해서 localPos 를 곱하면 해당 Pos가 X 축 기준으로 orbitAngle 만큼 회전하게됩니다.
private void FixedUpdate()
{
if (isSun) return;
currentAngle += orbitSpeed * Time.fixedDeltaTime;
currentAngle %= 360f;
float radianAngle = currentAngle * Mathf.Deg2Rad;
Vector3 localOrbitPosition = new Vector3(
Mathf.Cos(radianAngle) * orbitRadius,
0,
Mathf.Sin(radianAngle) * orbitRadius
);
Quaternion orbitRotation = Quaternion.Euler(orbitAngle, 0, 0);
Vector3 worldTargetPosition = orbitRotation * localOrbitPosition;
Vector3 velocity = (worldTargetPosition - rigid.position) / Time.fixedDeltaTime;
rigid.linearVelocity = velocity;
rigid.angularVelocity = Vector3.zero;
}
뉴턴의 만유인력의 법칙을 사용하면 간단하게 구할 수 있습니다.

공식을 사용하여 중력을 계산하고 힘을 적용시키면 됩니다.
추가적으로 중력을 작용할 방향으로 캐릭터를 회전시키면 항상 바닥으로 떨어질 수 있습니다.
public void ApplyGravity()
{
if (planets == null) return;
float maxMag = 0f;
PlanetBody maxPlanet = null;
foreach (PlanetBody planet in planets)
{
Vector3 positionDiff = planet.Rigid.position - rigid.position;
Vector3 newtonForce = positionDiff.normalized * GravitationalConstant * planet.Rigid.mass * rigid.mass / positionDiff.sqrMagnitude;
newtonForce *= planet.GravityScale;
newtonForce *= Time.fixedDeltaTime;
rigid.AddForce(newtonForce);
float mag = newtonForce.magnitude;
if (maxMag < mag)
{
maxMag = mag;
maxPlanet = planet;
}
}
if (maxPlanet == null || maxPlanet.IsSun) return;
if ((maxPlanet.Rigid.position - rigid.position).magnitude > maxPlanet.GravityRadius)
{
isInGravity = false;
playerPlanet = null;
}
else
{
isInGravity = true;
RotateTowardsPlanet(maxPlanet);
playerPlanet = maxPlanet;
}
}

Hierarchy 창을 봐도 부모-자식 관계가 아니지만 중력으로 인해 행성을 따라가는 것을 볼 수 있습니다.
우주선도 똑같이 중력을 적용해줍니다.
BoxCollider 를 Trigger로 설정하여 들어온 플레이어들에게 아래쪽으로 힘을 적용해줍니다.
이 때, 여러 사람이 타면 우주선이 아래쪽으로 힘을 받을 수 있으므로 같은 힘을 반대방향으로 우주선에 적용해줍니다.
private void FixedUpdate()
{
for (int i = 0; i < insidePlayers.Count; i++)
{
insidePlayers[i].Rigidbody.AddForce(-transform.up * playerGravity);
rigid.AddForce(transform.up * playerGravity);
}
}
void OnTriggerEnter(Collider other)
{
if (other.GetComponent<PlayerController>() == null) return;
if (!insidePlayers.Contains(other.GetComponent<PlayerController>()))
insidePlayers.Add(other.GetComponent<PlayerController>());
}
void OnTriggerExit(Collider other)
{
if (other.GetComponent<PlayerController>() == null) return;
if (insidePlayers.Contains(other.GetComponent<PlayerController>()))
insidePlayers.Remove(other.GetComponent<PlayerController>());
}

우주선을 뒤집어서 빠른속도로 이동중에도 우주선 내부에서는 느껴지지 않습니다.
간단한 천체물리를 사용하여 행성의 궤도 및 중력을 구현했습니다.
아무래도 현실이 아니다보니 스케일을 크게 할 수가 없어서 생긴 문제들이 남아있습니다.

예를 들어, 공전 방향과 공전 방향의 반대 방향에서 해당 행성의 중력 범위 밖으로 나가기 위한 시간이 많이 다릅니다.
해당 문제는 중력을 강하게 하고 또 그에따라 플레이어의 추진력 또한 강하게 해서 해결했지만, 다른 문제가 더 발생할 경우 추가적인 보정을 통해 해결하겠습니다.