Move a RigidBody inside another one

Hi,
I’m trying since 2 week to make a character playable in a moving SubMarine.
I tried different method, and this is the best I did:

This is the script where I move the player.

using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class FPSController : MonoBehaviour
{
    public float mouseSensitivity = 100f;
    public float moveSpeed = 5f;

    public Rigidbody sousMarinRb; 

    private Rigidbody rb;
    private float xRotation = 0f;
    private float lastSubmarineYRotation;
    private Camera playerCamera;
    private bool isMoving = false;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
        playerCamera = Camera.main;
        Cursor.lockState = CursorLockMode.Locked;

        if (sousMarinRb == null)
        {
            Debug.LogError("Référence au sous-marin manquante !");
        }
        else
        {
            lastSubmarineYRotation = sousMarinRb.rotation.eulerAngles.y;
        }
    }

    void Update()
    {
        GestionCamera();
    }

    void FixedUpdate()
    {
        
        float moveZ = Input.GetAxis("Vertical");
        float moveX = Input.GetAxis("Horizontal");

        isMoving = (moveZ != 0 || moveX != 0);

        if (isMoving)
        {
           
            // No constraint if player move
            rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ;

            Vector3 move = (playerCamera.transform.right * moveX + playerCamera.transform.forward * moveZ).normalized * moveSpeed;
            move.y = 0;

            // Use submarine velocity
            if (sousMarinRb != null)
            {
                Vector3 submarineVelocity = sousMarinRb.linearVelocity;
                move += submarineVelocity;
            }
                        
            rb.linearVelocity = move;
        }
        else
        {
            
            // Adjust rotation for player if submarine is rotating
            float currentSubmarineYRotation = sousMarinRb.rotation.eulerAngles.y;
            float rotationDifference = Mathf.DeltaAngle(lastSubmarineYRotation, currentSubmarineYRotation);
            transform.Rotate(Vector3.up * rotationDifference);

            // Update last submarine rotation
            lastSubmarineYRotation = currentSubmarineYRotation;

            // Constraint if player doesn't move
            rb.constraints = RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionZ | RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ;

            // Stop player imediatly if no key is pressed
            rb.linearVelocity = new Vector3(0, rb.linearVelocity.y, 0);
        }
    }

    public void GestionCamera()
    {
        float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
        float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;

        transform.Rotate(Vector3.up * mouseX);

        xRotation -= mouseY;
        xRotation = Mathf.Clamp(xRotation, -90f, 90f);

        playerCamera.transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);
    }
}

And this is how I move the submarine:

using UnityEngine;

public class GestionVitesseSousMarin : MonoBehaviour
{
    //Speed 
    public int actualSpeed;  // Vitesse actuelle
    public int minSpeed;     // Vitesse minimale autorisée
    public int maxSpeed;     // Vitesse maximale autorisée
    public int speed;        // Multiplicateur de vitesse
    public Rigidbody rb;     // Référence au Rigidbody du sous-marin
    //

    //Rotation Y
    public int actualRot;    // Vitesse actuelle de rotation
    public int minRot = -3;  // Vitesse minimale autorisée
    public int maxRot = 3;   // Vitesse maximale autorisée
    public float speedRot = 100f; // Multiplicateur de vitesse pour la rotation
    //

    //Rotation X
    public int actualAngle;      // Angle actuel de l'inclinaison
    public int minAngle = -3;    // Valeur minimale d'inclinaison vers le bas
    public int maxAngle = 3;     // Valeur maximale d'inclinaison vers le haut    
    public float rotationHauteurSpeed = 2f;  // Vitesse d'interpolation pour l'inclinaison
    private Quaternion targetRotation;
    //


    void Start()
    {
        if (rb == null)
        {
            rb = GetComponent<Rigidbody>();
        }
        targetRotation = this.transform.rotation;
    }

    void FixedUpdate()
    {
        SetSubmarineSpeed();
        SetRotation();
    }

    public void SetSubmarineSpeed()
    {       
        Vector3 movement = transform.forward * actualSpeed * speed * Time.fixedDeltaTime;        
        rb.MovePosition(rb.position + movement);
    }

    public void SetRotation()
    {
        //Y Rotation
        float rotationAmount = actualRot * speedRot * Time.fixedDeltaTime;
        Quaternion rotationY = Quaternion.Euler(0f, rotationAmount, 0f);

        // X Rotation
        float angleX = -actualAngle * 15f; // Chaque unité d'actualAngle correspond à 15°
        targetRotation = Quaternion.Euler(angleX, rb.rotation.eulerAngles.y, 0f); // Assurer qu'il n'y ait pas de rotation sur l'axe Z

        // Rotate
        Quaternion globalRotation = Quaternion.Slerp(rb.rotation * rotationY, targetRotation, Time.fixedDeltaTime * rotationHauteurSpeed);
        rb.MoveRotation(globalRotation);
    }


}

The issue is when the submarine rotate on Y, if my character try to go forward, he rotate while he’s moving.

I set submarine as a parent of player.

Can someone help me ?

It sounds like you should be adding the Submarine’s velocity to that of the player while they’re inside.

An alternative is a fake interior. But that would mean grafting cameras onto windows and faking impacts.

Hey, I think i’ll try to make a fake interior, but how can I simulate the movement ?
There is like a big cockpit where you can see the sea, how can I simulate this ?

Parenting is irrelevant to physics and will likely just cause great confusion to the physics system as the two rigidbodies fight each other. See below.

Instead use joints to connect them, or else drive the interior rigidbody position / rotation always as a function of relative to the exterior rigidbody’s relative-local motion.

Physics (both 3D and 2D) live completely outside and apart from the scene transforms and send data back and forth.

With Physics (or Physics2D), never manipulate the Transform directly. If you manipulate the Transform directly, you are bypassing the physics system and you can reasonably expect glitching and missed collisions and other physics mayhem.

This means you may not change transform.position, transform.rotation, you may not call transform.Translate(), transform.Rotate() or other such methods, and also transform.localScale is off limits. You also cannot set rigidbody.position or rigidbody.rotation directly. These ALL bypass physics.

Always use the .MovePosition() and .MoveRotation() methods on the Rigidbody (or Rigidbody2D) instance in order to move or rotate things. Doing this keeps the physics system informed about what is going on.

Thanks mate,

There is a lot of things I missed here =)

I would second the gentleman who suggested a ‘fake’ interior unless you have a scenario in which you need to move from inside the sub to outside seamlessly. Trying to go the other route is going to eat up a lot of cpu cycles that are not necessary unless you need to account for the situation I just mentioned. It would be much more efficient to put them inside a static environment and use render textures to create the ‘windows’ and hook them to cameras on your actual submarine. This will provide you many benefits, not just in terms of code and efficiency of resource consumption, but in world building as well. You can make much smaller environments that don’t match your player’s scale.

1 Like

can approve the fake interior method, the only “simple” way to keep moving inside of another moving rigidbody.

Two cameras, or fancy layers, or texture rendering will improve the illusion.
Also, for seamless leaving of vehicle, you could define a condition when it can be static and fixed to ground, teleport interior to real exterior, and let the door open for player to leave. Tricky, yet possible

2 Likes

Yep, I’ll finally create a fake interior.
Not what I wanted, but it’ll be enough.

Thank you guys

If you mirror the rotation of the fake interior, but not its movement, it should realistically tip with the real ship without leaving the player behind. For impacts, having a series of fake impact shudders triggered by the location of impact would be a lot more real-feeling than the standard Unity physics, which has a tendency to feel gamey. For those, you would need to mirror the effect on the real craft to keep things consistent.

Or, as Kurt-Dekker says, use ‘add force’ as your method of movement.