1. using UdonSharp;
  2. using UnityEngine;
  3. using VRC.SDKBase;
  4.  
  5. public class WaterSystem : UdonSharpBehaviour
  6. {
  7. [Header("--- Настройки синевы ---")]
  8. // Смещение синего экрана относительно центра триггера (уровень глаз)
  9. [SerializeField] private float offset = -0.4f;
  10. // Прозрачность/плотность тумана под водой (от 0 до 1)
  11. [SerializeField] [Range(0f, 1f)] private float waterDensity = 1.0f;
  12.  
  13. [Header("--- Настройки плавания ---")]
  14. // Скорость, с которой персонаж взлетает/всплывает вверх
  15. [SerializeField] private float swimSpeed = 2.5f;
  16. // Расстояние от глаз до плеч аватара (чтобы не вылетать из воды слишком высоко)
  17. [SerializeField] private float shouldersOffset = 0.22f;
  18.  
  19. // Внутренние технические переменные
  20. private VRCPlayerApi localPlayer; // Ссылка на самого игрока
  21. private bool inWater = false; // Находится ли игрок в воде прямо сейчас
  22. private float mobileVerticalInput = 0f; // Сюда мы будем записывать движение мобильного джойстика
  23. void Start()
  24. {
  25. // Находим локального игрока (того, кто запустил игру на своем устройстве)
  26. localPlayer = Networking.LocalPlayer;
  27. }
  28. public override void OnPlayerTriggerEnter(VRCPlayerApi player)
  29. {
  30. // Проверяем, что в триггер вошел именно наш (локальный) игрок
  31. if (player != null && player.isLocal)
  32. {
  33. inWater = true;
  34.  
  35. // Настройка физики: включаем "невесомость" и замедляем бег/ходьбу
  36. player.SetGravityStrength(0.01f);
  37. player.SetWalkSpeed(2f); // Скорость шага в воде
  38. player.SetRunSpeed(2.5f); // Скорость бега в воде
  39. }
  40. }
  41. public override void OnPlayerTriggerExit(VRCPlayerApi player)
  42. {
  43. // Проверяем, что триггер покинул именно наш (локальный) игрок
  44. if (player != null && player.isLocal)
  45. {
  46. inWater = false;
  47.  
  48. // Полностью отключаем подводный туман экрана
  49. RenderSettings.fog = false;
  50. RenderSettings.fogDensity = 0f;
  51.  
  52. // Возвращаем стандартные настройки физики для суши
  53. player.SetGravityStrength(1.0f);
  54. player.SetWalkSpeed(4f);
  55. player.SetRunSpeed(4f);
  56. }
  57. }
  58. public override void OnInputMoveVertical(float value, VRCInputMethod inputMethod)
  59. {
  60. // VRChat сам передает сюда значение джойстика:
  61. // 1.0 — джойстик зажат полностью вперед, -1.0 — полностью назад
  62. mobileVerticalInput = value;
  63. }
  64. void Update()
  65. {
  66. // Если игрок на суше или ссылка на него пропала — ничего не делаем
  67. if (!inWater || localPlayer == null) return;
  68.  
  69. // Находим текущую высоту головы (глаз) игрока по Y
  70. float headY = localPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position.y;
  71.  
  72. // Вычисляем верхнюю границу воды с учетом нашего смещения offset
  73. float triggerTopY = transform.position.y + offset;
  74.  
  75. // Вычисляем высоту плеч аватара
  76. float shouldersY = headY - shouldersOffset;
  77.  
  78. // --- ЛОГИКА СИНЕВЫ ЭКРАНА (ВИЗУАЛ) ---
  79. if (headY < triggerTopY)
  80. {
  81. // Если глаза под водой: включаем туман
  82. RenderSettings.fog = true;
  83. RenderSettings.fogDensity = waterDensity;
  84. }
  85. else
  86. {
  87. // Если глаза над водой: выключаем туман
  88. RenderSettings.fog = false;
  89. RenderSettings.fogDensity = 0f;
  90. }
  91. // --- ЛОГИКА ПЛАВАНИЯ (ФИЗИКА) ---
  92. // Получаем направление взгляда игрока (куда повернута голова)
  93. Vector3 lookDir = localPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).rotation * Vector3.forward;
  94.  
  95. // Условие для мобилок и ПК: зажат джойстик/кнопка "Вперед"
  96. bool isMovingForward = mobileVerticalInput > 0.1f;
  97.  
  98. if (isMovingForward)
  99. {
  100. // ПЛЫВЕМ ВВЕРХ: если смотрим вверх (lookDir.y > 0.2) и плечи еще под водой
  101. if (lookDir.y > 0.2f && shouldersY < triggerTopY)
  102. {
  103. Vector3 vel = localPlayer.GetVelocity();
  104. vel.y = swimSpeed; // Задаем скорость движения вверх
  105. localPlayer.SetVelocity(vel);
  106. }
  107. // ПЛЫВЕМ ВНИЗ: если смотрим вниз (lookDir.y < -0.2)
  108. else if (lookDir.y < -0.2f)
  109. {
  110. Vector3 vel = localPlayer.GetVelocity();
  111. vel.y = -swimSpeed; // Задаем скорость движения вниз (отрицательная)
  112. localPlayer.SetVelocity(vel);
  113. }
  114. }
  115.  
  116. // Защита от "взлета": если плечи поднялись выше воды, обнуляем вертикальную скорость
  117. if (shouldersY >= triggerTopY)
  118. {
  119. Vector3 vel = localPlayer.GetVelocity();
  120. if (vel.y > 0)
  121. {
  122. vel.y = 0;
  123. localPlayer.SetVelocity(vel);
  124. }
  125. }
  126. } // Закрывается метод Update()
  127. } // Закрывается класс WaterSystem