using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
public class WaterSystem : UdonSharpBehaviour
{
[Header("--- Настройки синевы ---")]
// Смещение синего экрана относительно центра триггера (уровень глаз)
[SerializeField] private float offset = -0.4f;
// Прозрачность/плотность тумана под водой (от 0 до 1)
[SerializeField] [Range(0f, 1f)] private float waterDensity = 1.0f;
[Header("--- Настройки плавания ---")]
// Скорость, с которой персонаж взлетает/всплывает вверх
[SerializeField] private float swimSpeed = 2.5f;
// Расстояние от глаз до плеч аватара (чтобы не вылетать из воды слишком высоко)
[SerializeField] private float shouldersOffset = 0.22f;
// Внутренние технические переменные
private VRCPlayerApi localPlayer; // Ссылка на самого игрока
private bool inWater = false; // Находится ли игрок в воде прямо сейчас
private float mobileVerticalInput = 0f; // Сюда мы будем записывать движение мобильного джойстика
void Start()
{
// Находим локального игрока (того, кто запустил игру на своем устройстве)
localPlayer = Networking.LocalPlayer;
}
public override void OnPlayerTriggerEnter(VRCPlayerApi player)
{
// Проверяем, что в триггер вошел именно наш (локальный) игрок
if (player != null && player.isLocal)
{
inWater = true;
// Настройка физики: включаем "невесомость" и замедляем бег/ходьбу
player.SetGravityStrength(0.01f);
player.SetWalkSpeed(2f); // Скорость шага в воде
player.SetRunSpeed(2.5f); // Скорость бега в воде
}
}
public override void OnPlayerTriggerExit(VRCPlayerApi player)
{
// Проверяем, что триггер покинул именно наш (локальный) игрок
if (player != null && player.isLocal)
{
inWater = false;
// Полностью отключаем подводный туман экрана
RenderSettings.fog = false;
RenderSettings.fogDensity = 0f;
// Возвращаем стандартные настройки физики для суши
player.SetGravityStrength(1.0f);
player.SetWalkSpeed(4f);
player.SetRunSpeed(4f);
}
}
public override void OnInputMoveVertical(float value, VRCInputMethod inputMethod)
{
// VRChat сам передает сюда значение джойстика:
// 1.0 — джойстик зажат полностью вперед, -1.0 — полностью назад
mobileVerticalInput = value;
}
void Update()
{
// Если игрок на суше или ссылка на него пропала — ничего не делаем
if (!inWater || localPlayer == null) return;
// Находим текущую высоту головы (глаз) игрока по Y
float headY = localPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position.y;
// Вычисляем верхнюю границу воды с учетом нашего смещения offset
float triggerTopY = transform.position.y + offset;
// Вычисляем высоту плеч аватара
float shouldersY = headY - shouldersOffset;
// --- ЛОГИКА СИНЕВЫ ЭКРАНА (ВИЗУАЛ) ---
if (headY < triggerTopY)
{
// Если глаза под водой: включаем туман
RenderSettings.fog = true;
RenderSettings.fogDensity = waterDensity;
}
else
{
// Если глаза над водой: выключаем туман
RenderSettings.fog = false;
RenderSettings.fogDensity = 0f;
}
// --- ЛОГИКА ПЛАВАНИЯ (ФИЗИКА) ---
// Получаем направление взгляда игрока (куда повернута голова)
Vector3 lookDir = localPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).rotation * Vector3.forward;
// Условие для мобилок и ПК: зажат джойстик/кнопка "Вперед"
bool isMovingForward = mobileVerticalInput > 0.1f;
if (isMovingForward)
{
// ПЛЫВЕМ ВВЕРХ: если смотрим вверх (lookDir.y > 0.2) и плечи еще под водой
if (lookDir.y > 0.2f && shouldersY < triggerTopY)
{
Vector3 vel = localPlayer.GetVelocity();
vel.y = swimSpeed; // Задаем скорость движения вверх
localPlayer.SetVelocity(vel);
}
// ПЛЫВЕМ ВНИЗ: если смотрим вниз (lookDir.y < -0.2)
else if (lookDir.y < -0.2f)
{
Vector3 vel = localPlayer.GetVelocity();
vel.y = -swimSpeed; // Задаем скорость движения вниз (отрицательная)
localPlayer.SetVelocity(vel);
}
}
// Защита от "взлета": если плечи поднялись выше воды, обнуляем вертикальную скорость
if (shouldersY >= triggerTopY)
{
Vector3 vel = localPlayer.GetVelocity();
if (vel.y > 0)
{
vel.y = 0;
localPlayer.SetVelocity(vel);
}
}
} // Закрывается метод Update()
} // Закрывается класс WaterSystem