// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using NWH.Common.CoM;
using NWH.Common.Input;
using NWH.DWP2.WaterObjects;
using UnityEngine;
using UnityEngine.Serialization;
#endregion
namespace NWH.DWP2.ShipController
{
///
/// Enables submarine functionality by controlling ballast mass for diving and surfacing.
/// Manages depth control through variable mass and can automatically maintain horizontal orientation.
/// Works with VariableCenterOfMass to adjust buoyancy for depth changes.
///
[RequireComponent(typeof(AdvancedShipController))]
[RequireComponent(typeof(VariableCenterOfMass))]
[DefaultExecutionOrder(500)]
public class Submarine : MonoBehaviour, IMassAffector
{
///
/// Speed of change of the ballast mass, as a percentage of maxBallastMass.
///
[Tooltip("Speed of change of the ballast mass, as a percentage of maxBallastMass.")]
public float ballastChangeSpeed = 0.05f;
///
/// If enabled submarine will try to keep horizontal by shifting the center of mass.
///
[Tooltip("If enabled submarine will try to keep horizontal by shifting the center of mass.")]
public bool keepHorizontal;
///
/// Sensitivity of calculation trying to keep the submarine horizontal. Higher number will mean faster reaction.
///
[Tooltip(
"Sensitivity of calculation trying to keep the submarine horizontal. Higher number will mean faster reaction.")]
public float keepHorizontalSensitivity = 1f;
///
/// Maximum ballast mass in kg that can be added to make the submarine sink.
/// Higher values allow diving deeper and faster but require more time to surface.
///
[FormerlySerializedAs("maxAdditionalMass")]
[FormerlySerializedAs("maxMassFactor")]
[Tooltip(
"Maximum additional mass that can be added (taking on water) to the base mass of the rigidbody to make submarine sink.")]
public float maxBallastMass = 200000f;
///
/// Maximum rigidbody center of mass offset that can be used to keep the submarine level.
///
[Tooltip("Maximum rigidbody center of mass offset that can be used to keep the submarine level.")]
public float maxMassOffset = 5f;
///
/// Reference to the WaterObject used for water level detection.
///
public WaterObject ReferenceWaterObject;
private Vector3 _centerOfMass;
private float _mass;
private VariableCenterOfMass _vcom;
private float _zOffset;
[HideInInspector]
[SerializeField]
private float depthInput;
///
/// Input for depth control from -1 (surface) to 1 (dive).
/// Positive values add ballast mass to sink, negative values reduce it to surface.
///
public float DepthInput
{
get { return depthInput; }
set { depthInput = Mathf.Clamp(value, -1f, 1f); }
}
public float GetMass()
{
return _mass;
}
public Vector3 GetWorldCenterOfMass()
{
return _centerOfMass;
}
public Transform GetTransform()
{
return transform;
}
private void Awake()
{
if (ReferenceWaterObject == null)
{
ReferenceWaterObject = GetComponentInChildren();
}
}
private void Start()
{
_vcom = GetComponentInParent();
if (_vcom == null)
{
Debug.LogError(
$"VariableCenterOfMass script not found on object {name}. If updating from older versions" +
"of DWP2 replace CenterOfMass [deprecated] script with VariableCenterOfMass [new] script.");
}
_vcom.useMassAffectors = true;
_vcom.useDefaultMass = false;
_vcom.useDefaultCenterOfMass = false;
Debug.Assert(ReferenceWaterObject != null, "ReferenceWaterObject not set.");
}
private void FixedUpdate()
{
DepthInput = InputProvider.CombinedInput
(i => i.SubmarineDepth());
_mass -= DepthInput * maxBallastMass * ballastChangeSpeed * Time.fixedDeltaTime;
_mass = Mathf.Clamp(_mass, 0f, Mathf.Infinity);
if (keepHorizontal)
{
float angle = Vector3.SignedAngle(transform.up, Vector3.up, transform.right);
_zOffset = Mathf.Clamp(Mathf.Sign(angle) * Mathf.Pow(angle * 0.2f, 2f) * keepHorizontalSensitivity,
-maxMassOffset, maxMassOffset);
Vector3 position = transform.position;
_centerOfMass = new Vector3(position.x, position.y, position.z + _zOffset);
}
}
}
}