How to negate this force?

I’m making a videogame about submarines. I need the player to be able to move around the submarine. I’ve implemented this with a player using a rigidbody and a controller script and a submarine gameobject using a rigidbody and a controller script. The rigidbody has a script which is called buoyancy.


Depending on the volume of water displaced and the weight of the rigidbody, it will sink up to a certain height. This works fine.
The problem is since there is drag on each rigidbody, when the player moves, it will act this force upon the submarine and move it. This also happens when the player is pushing on a wall connect to the interior of the submarine.
I want the player not to be able to move the submarine from walking inside it but if it is light enough, to be able to push it from the outside.
[Here's a link to a video showing this][1]
Buoyancy Script
using System;
using UnityEngine;

public class Buoyancy : MonoBehaviour
{
    private const float surfaceHeight = 0f;
    private const float waterDensity = 1f;

    [SerializeField]
    private float area = 0f;
    [SerializeField]
    private Rigidbody objectRigidbody = null;

    private Vector3 buoyantForce = Vector3.zero;

    public Vector3 Force => buoyantForce;

    private void FixedUpdate()
    {
        if (transform.position.y >= surfaceHeight)
        {
            buoyantForce = Vector3.zero;
            return;
        }

        buoyantForce = GetBuoyantForce();

        objectRigidbody.AddForce(buoyantForce, ForceMode.Force);
    }

    private Vector3 GetBuoyantForce()
    {
        Vector3 u = Vector3.up;

        float magnitude = waterDensity * -Physics.gravity.y * (surfaceHeight - transform.position.y) * area;

        return magnitude * u;
    }
}

Player Movment Script

using System;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    [SerializeField]
    private Rigidbody playerRigidbody = default;
    [SerializeField]
    private float jumpHeight = 1f;
    [SerializeField]
    private float airPenalty = 0.2f;
    [SerializeField]
    private float topSpeed = 2f;
    [Header("Ground")]
    [SerializeField]
    private Transform groundCheck = default;
    [SerializeField]
    private float groundRadius = 1f;
    [SerializeField]
    private LayerMask groundMask = default;

    private Input input = default;
    private Vector2 move = default;
    private bool grounded = false;
    private bool jump = false;

    private void Awake()
    {
        input = new Input();

        input.Player.Move.performed += ctx => move = ctx.ReadValue<Vector2>();
        input.Player.Move.canceled += _ => move = Vector2.zero;

        input.Player.Jump.performed += _ => jump = true;
        input.Player.Jump.canceled += _ => jump = false;
    }

    private void FixedUpdate()
    {
        CheckGrounded();

        Vector3 force = Vector3.ClampMagnitude(transform.right * move.x + transform.forward * move.y, 1f) * topSpeed - playerRigidbody.velocity;

        force.y = 0f;

        if (grounded)
        {
            if (jump)
            {
                playerRigidbody.AddForce(Vector3.up * Mathf.Sqrt(-2f * Physics.gravity.y * jumpHeight), ForceMode.VelocityChange);
            }
        }
        else
        {
            force *= airPenalty;
        }

        if (force != Vector3.zero)
        {
            playerRigidbody.AddForce(force, ForceMode.VelocityChange);
        }
    }

    private void CheckGrounded()
    {
        grounded = Physics.CheckSphere(groundCheck.position, groundRadius, groundMask);
    }

    private void OnEnable()
    {
        input.Enable();
    }

    private void OnDisable()
    {
        input.Disable();
    }

    private void OnDrawGizmos()
    {
        if (groundCheck != null)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireSphere(groundCheck.position, groundRadius);
        }
    }
}

If you know for sure the rigidbody Drag is the one causing the forces (which I didn’t get tbh), then I’d simply go with a script that toggles that property whn you leave/enter the submarine.

Also, I don’t understand why the buoyancy is affected by height but maybe it’s due to pressure, not sure.

To ensure a one directional interaction between the submarine and your player you could split up the physics into two parts. Unfortunately Unity’s standard physics implementation does not support mutiple physics scenes in order to have seperate local physics for the player. However you can work with collision layers and have a seperate collison object for the submatine with a kinematic rigidbody. This seperate collision model of the submarine would be simply a child of the actual submarine. Now the player shouldn’t collide with the actual submarine but only with this extra interior collider object. Since this object has a kinematic rigidbody it can push the player around when the submarine moves, but never the other way round because the player does not interact with the actual (non kinematic) submarine rigidbody.

Note if you don’t want external forces and movement on the submarine to affect the player at all, it’s usually the easiest solution to have the interior physics seperate and stationary somewhere in the level and just map the results into the actual submarine. Of course this can get tricky when the player can actually leave and enter the submarine. The first approach is more robust in this regards. Though it’s also possible to combine both solutions depending on the wanted behaviour.

Always keep in mind that the physics engine is just a rough approximation and game or simulation development is just about getting the look and feel right and not to be physically accurate. PhysX is not a viable solution when you’re looking for actual scientific physcis simulations.