using UnityEngine; using UnityEngine.VFX; namespace StormBreakers { public class BoatController : MonoBehaviour { [Header("Physics properties")] [Tooltip("Propeller forward and backward force in Newtons.")] public float propellerForce = 100f; [Tooltip("Rudder lateral force in Newtons.")] public float rudderForce = 100f; [Tooltip("The depth in meter at which the controller make full rudder and propeller forces. Should be close to the propeller and rudder actual size, increase this value for powerfull watercraft.")] public float effectiveDepth = 1f; private Rigidbody rb; private Vector3 undeformedPosition; private float depth; private Vector3 waterVelocity; private float propellerUsage = 0f; private float rudderUsage = 0f; private float depthForceFactor; private float effectiveness = 0f; private bool isPlayerControlling = false; [Space(10)] [Header("Automatic pilot properties")] [Tooltip("Wether to activate automatic pilot for this controller")] public bool automaticPilot = false; [Tooltip("The percentage of the propeller force to permanently use.")] [Range(0f, 1f)] public float throttle = 1f; [Tooltip("The gain of the regualtor that maintain the course.")] [Range(0f, 10f)] public float rudderRegulatorGain = 2f; [HideInInspector] public Vector3 course; [Space(10)] [Header("Particles properties (more in the VFX component)")] [Tooltip("The distance between each wake particles creation.")] [Range(0.01f, 2.5f)] public float swirlDistanceRate = 0.5f; [Tooltip("The lifetime of the particles when the propeller is at full force.")] [Range(0.1f, 25f)] public float swirlLifetime = 5f; [Tooltip("Whether to generate the foam burst particles.")] public bool generateBurstParticles = true; [Tooltip("The local position where the burst particles are generated.")] public Vector3 burstGenerationPosition = Vector3.zero; [Tooltip("The local backward speed of the burst particle generated.")] [Range(-1f, 5f)] public float burstBackwardSpeed = 0f; [Tooltip("The local vertical speed of the burst particle generated.")] [Range(0f, 25f)] public float burstUpwardSpeed = 5f; private VisualEffect wakeVFX; private VFXEventAttribute eventAttribute; private float particlesCreationDeltaTime; private int positionID; private int velocityID; private int undeformedPositionID; private int alphaID; private int lifetimeID; private int swirlEventId; private int burstEventID; private float chrono = 0f; [Space(10)] [Header("Audio properties")] [Tooltip("The volume of the audio source when the engine is idle.")] [Range(0f, 1f)] public float idleVolume = 0.5f; [Tooltip("The volume of the audio source when the engine is full throttle.")] [Range(0f, 1f)] public float fullThrottleVolume = 1f; [Tooltip("The pitch of the audio source when the engine is idle.")] [Range(0f, 3f)] public float idlePitch = 0.5f; [Tooltip("The pitch of the audio source when the engine is full throttle and inside water.")] [Range(0f, 3f)] public float fullThrottlePitch = 1f; [Tooltip("The additional pitch of the audio source when the engine is full throttle and outside water.")] [Range(0f, 3f)] public float unloadedAdditionalPitch = 1f; private AudioSource audioSource; [Space(10)] [Header("Debugging")] [Tooltip("Whether to draw forces with lines in gizmos mode.")] public bool drawForce = true; [Tooltip("The size of the vector drawn in gizmos mode to visualize the forces.")] public float vectorSize = 0.001f; void Start() { rb = transform.parent.GetComponent(); if (rb == null) { Debug.LogWarning("Please attach this component to a game object child of a rigid body"); this.enabled = false; return; } undeformedPosition = transform.position; if (effectiveDepth > 0f) { depthForceFactor = 1f / effectiveDepth; } else { depthForceFactor = 1000f; } SetCurrentCourse(); wakeVFX = GetComponent(); if (wakeVFX != null) { if (wakeVFX.HasMatrix4x4("_LIDR") && wakeVFX.HasMatrix4x4("_NKVW") && wakeVFX.HasVector4("_totalLigthColor") && wakeVFX.HasVector3("_wind")) { positionID = Shader.PropertyToID("position"); velocityID = Shader.PropertyToID("velocity"); undeformedPositionID = Shader.PropertyToID("targetPosition"); alphaID = Shader.PropertyToID("alpha"); lifetimeID = Shader.PropertyToID("lifetime"); swirlEventId = Shader.PropertyToID("swirl"); burstEventID = Shader.PropertyToID("burst"); particlesCreationDeltaTime = swirlLifetime * 0.5f; eventAttribute = wakeVFX.CreateVFXEventAttribute(); UpdateVFXProperties(); chrono = 0f; } else { Debug.LogWarning("Wrong VFX template, please use wakeVFX.vfx"); wakeVFX = null; } } audioSource = GetComponent(); } public void UpdateVFXProperties() { if (wakeVFX != null) { wakeVFX.SetMatrix4x4("_LIDR", Ocean.LIDR); wakeVFX.SetMatrix4x4("_NKVW", Ocean.NKVW); wakeVFX.SetVector4("_totalLigthColor", Ocean.totalLight); wakeVFX.SetVector3("_wind", new Vector3(Ocean.Wind.speed * Ocean.Wind.cosDirection, Ocean.Wind.inverseHeight, Ocean.Wind.speed * Ocean.Wind.sinDirection)); if (Ocean.useTerrain) { if (Ocean.terrain != null) { wakeVFX.SetTexture("_terrainHeightmap", Ocean.terrain.terrainData.heightmapTexture); wakeVFX.SetVector3("_terrainPosition", Ocean.terrain.transform.position); wakeVFX.SetVector3("_terrainScale", Ocean.terrain.terrainData.size); } else { Debug.LogWarning("No terrain detected !"); } } else { wakeVFX.SetTexture("_terrainHeightmap", new RenderTexture(1, 1, 0)); wakeVFX.SetVector3("_terrainPosition", new Vector3(0f, -200f, 0f)); wakeVFX.SetVector3("_terrainScale", new Vector3(1f, 1f, 1f)); } } } public void SetCurrentCourse() { course = Vector3.ProjectOnPlane(transform.right, Vector3.up).normalized; } void Update() { // "E" tuşuna basıldığında kontrolü aç/kapa if (Input.GetKeyDown(KeyCode.E)) { isPlayerControlling = !isPlayerControlling; } float groundDepth = 200f; if (Ocean.useTerrain) { groundDepth = -Ocean.terrain.SampleHeight(undeformedPosition) - Ocean.terrain.transform.position.y; } float oceanHeight = Ocean.GetHeight(Time.time, transform.position, ref undeformedPosition, out Vector3 deformation, groundDepth, false); depth = oceanHeight - transform.position.y; if (depth > 0f) { waterVelocity = Ocean.GetVelocity(Time.time, undeformedPosition, deformation, out _); } else { waterVelocity = Vector3.zero; depth = 0f; } if (wakeVFX != null && depth > 0f) { float lifetime = propellerUsage * swirlLifetime; if (lifetime < 0f) { lifetime *= -1f; } chrono += Time.deltaTime; if (lifetime > 0f) { Vector3 relativeVelocity = waterVelocity - rb.GetPointVelocity(transform.position); float relativeSpeed = relativeVelocity.magnitude; relativeSpeed = Mathf.Max(swirlDistanceRate / lifetime * 4f, relativeSpeed); particlesCreationDeltaTime = swirlDistanceRate / relativeSpeed; if (chrono > particlesCreationDeltaTime) { chrono = 0f; eventAttribute.SetVector3(undeformedPositionID, undeformedPosition); eventAttribute.SetVector3(positionID, transform.position); eventAttribute.SetVector3(velocityID, relativeVelocity); eventAttribute.SetFloat(lifetimeID, lifetime); eventAttribute.SetFloat(alphaID, propellerUsage); wakeVFX.SendEvent(swirlEventId, eventAttribute); } } if (generateBurstParticles && propellerUsage > 0f && depthForceFactor * depth * propellerUsage > 1.1f) { eventAttribute.SetVector3(positionID, transform.TransformPoint(burstGenerationPosition)); eventAttribute.SetVector3(velocityID, burstBackwardSpeed * transform.forward + burstUpwardSpeed * transform.up); wakeVFX.SendEvent(burstEventID, eventAttribute); } } if (drawForce) { if (effectiveness > 0f) { if (propellerUsage > 0f) { Vector3 force = effectiveness * propellerUsage * propellerForce * depthForceFactor * depth * transform.forward; Gizmos.DrawRay(transform.position, vectorSize * force); } if (rudderUsage != 0f) { Vector3 force = effectiveness * rudderUsage * rudderForce * depthForceFactor * depth * transform.right; Gizmos.DrawRay(transform.position, vectorSize * force); } } } } void FixedUpdate() { if (depth > 0f) { effectiveness = Mathf.Min(depthForceFactor * depth, 1f); } else { effectiveness = 0f; } if (effectiveness > 0f) { if (automaticPilot) { propellerUsage = throttle; rudderUsage = Mathf.Clamp(Vector3.SignedAngle(course, Vector3.ProjectOnPlane(transform.right, Vector3.up).normalized, Vector3.up) * rudderRegulatorGain, -1f, 1f); } else if (isPlayerControlling) // Sadece oyuncu gemiyi kontrol ediyorsa bu işlemleri yap { // W ve S tuşlarıyla gemiyi ileri ve geri hareket ettirmek için propellerUsage = Mathf.Clamp(Input.GetAxis("Horizontal"), -1f, 1f); // A ve D tuşlarıyla gemiyi sola ve sağa döndürmek için rudderUsage = Mathf.Clamp(Input.GetAxis("Vertical"), -1f, 1f); } else { propellerUsage = 0f; rudderUsage = 0f; } if (propellerUsage != 0f) { rb.AddForceAtPosition(effectiveness * propellerUsage * propellerForce * depthForceFactor * depth * transform.forward, transform.position); } if (rudderUsage != 0f) { rb.AddForceAtPosition(effectiveness * rudderUsage * rudderForce * depthForceFactor * depth * transform.right, transform.position); } if (audioSource != null) { audioSource.volume = Mathf.Lerp(idleVolume, fullThrottleVolume, Mathf.Abs(propellerUsage)); audioSource.pitch = Mathf.Lerp(idlePitch, fullThrottlePitch, Mathf.Abs(propellerUsage)); if (depth == 0f) { audioSource.pitch += unloadedAdditionalPitch; } } } } } }