Files
YachtDice/Assets/Scripts/Dice/DiceRoller.cs
T
2026-02-26 16:06:30 +07:00

153 lines
5.9 KiB
C#

using System;
using System.Collections;
using UnityEngine;
using Random = UnityEngine.Random;
/// <summary>
/// Красиво подбрасывает и раскручивает кубик, ждёт пока он остановится,
/// плавно доворачивает до ровного положения и сообщает результат.
/// </summary>
public sealed class DiceRoller : MonoBehaviour
{
[Header("References")]
[SerializeField] private Dice dice;
[SerializeField] private Rigidbody rb;
[Header("Throw Settings")]
[Tooltip("Сила подброса вверх")]
[SerializeField] private float throwUpForce = 6f;
[Tooltip("Горизонтальный разброс")]
[SerializeField] private float throwScatter = 1.5f;
[Tooltip("Минимальный крутящий момент")]
[SerializeField] private float torqueMin = 15f;
[Tooltip("Максимальный крутящий момент")]
[SerializeField] private float torqueMax = 30f;
[Header("Settle Detection")]
[Tooltip("Порог скорости, ниже которого кубик считается остановившимся")]
[SerializeField] private float settleSpeed = 0.05f;
[Tooltip("Сколько секунд кубик должен быть неподвижен, чтобы засчитать остановку")]
[SerializeField] private float settleDelay = 0.3f;
[Header("Snap Alignment")]
[Tooltip("Длительность плавного доворота к ровному положению")]
[SerializeField] private float snapDuration = 0.35f;
[SerializeField]
private AnimationCurve snapCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
/// <summary>
/// Вызывается когда кубик полностью остановился. Аргумент — выпавшее значение.
/// </summary>
public event Action<int> OnRollFinished;
/// <summary>
/// Идёт ли сейчас бросок.
/// </summary>
public bool IsRolling { get; private set; }
private Coroutine rollRoutine;
private void Reset()
{
dice = GetComponent<Dice>();
rb = GetComponent<Rigidbody>();
}
/// <summary>
/// Бросить кубик. Повторный вызов во время броска игнорируется.
/// </summary>
public void Roll()
{
if (IsRolling) return;
if (rollRoutine != null)
StopCoroutine(rollRoutine);
rollRoutine = StartCoroutine(RollSequence());
}
private IEnumerator RollSequence()
{
IsRolling = true;
// ── 1. Подготовка ────────────────────────────────────────────
rb.isKinematic = false;
rb.useGravity = true;
rb.linearVelocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
// ── 2. Импульс: подбросить вверх с лёгким разбросом ─────────
Vector3 force = new Vector3(
Random.Range(-throwScatter, throwScatter),
throwUpForce,
Random.Range(-throwScatter, throwScatter)
);
rb.AddForce(force, ForceMode.Impulse);
// ── 3. Случайный крутящий момент — красивое вращение ────────
Vector3 torque = Random.onUnitSphere * Random.Range(torqueMin, torqueMax);
rb.AddTorque(torque, ForceMode.Impulse);
// Даём кубику взлететь
yield return new WaitForSeconds(0.2f);
// ── 4. Ждём пока кубик успокоится ───────────────────────────
float stillTimer = 0f;
float sqrThreshold = settleSpeed * settleSpeed;
while (stillTimer < settleDelay)
{
yield return new WaitForFixedUpdate();
bool isSlow = rb.linearVelocity.sqrMagnitude < sqrThreshold
&& rb.angularVelocity.sqrMagnitude < sqrThreshold;
stillTimer = isSlow ? stillTimer + Time.fixedDeltaTime : 0f;
}
// ── 5. Замораживаем физику и читаем верхнюю грань ───────────
rb.linearVelocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
rb.isKinematic = true;
if (!dice.TryGetTopValue(out int topValue))
{
Debug.LogWarning("DiceRoller: не удалось определить верхнюю грань.");
IsRolling = false;
yield break;
}
// ── 6. Плавный доворот до ровного положения ─────────────────
Quaternion startRot = transform.rotation;
// Вычисляем целевой поворот через Dice.AlignToTopByLocalAngles
dice.AlignToTopByLocalAngles();
Quaternion targetRot = transform.rotation;
// Откатываемся обратно — будем интерполировать
transform.rotation = startRot;
float elapsed = 0f;
while (elapsed < snapDuration)
{
elapsed += Time.deltaTime;
float t = snapCurve.Evaluate(Mathf.Clamp01(elapsed / snapDuration));
transform.rotation = Quaternion.Slerp(startRot, targetRot, t);
yield return null;
}
transform.rotation = targetRot;
// ── 7. Готово ───────────────────────────────────────────────
IsRolling = false;
OnRollFinished?.Invoke(topValue);
Debug.Log($"{gameObject.name} | Выпало: <b>{topValue}</b>");
}
}