Files
YachtDice/Assets/Scripts/Dice/DiceRoller.cs
T

160 lines
6.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using UnityEngine;
using Random = UnityEngine.Random;
namespace YachtDice.Dice
{
/// <summary>
/// Красиво подбрасывает и раскручивает кубик, ждёт пока он остановится,
/// плавно доворачивает до ровного положения и сообщает результат.
/// </summary>
public class DiceRoller : MonoBehaviour
{
[Header("References")]
[SerializeField] private Dice dice;
[SerializeField] private Rigidbody rb;
[SerializeField] private DiсeDefinition definition;
/// <summary>Определение типа дайса (назначается в инспекторе).</summary>
public DiсeDefinition Definition => definition;
[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>");
}
}
}