[Add] Dice return script
This commit is contained in:
@@ -7,13 +7,16 @@ namespace YachtDice.Dice
|
||||
{
|
||||
/// <summary>
|
||||
/// Красиво подбрасывает и раскручивает кубик, ждёт пока он остановится,
|
||||
/// плавно доворачивает до ровного положения и сообщает результат.
|
||||
/// плавно доворачивает до ровного положения, возвращает на старт и сообщает результат.
|
||||
/// </summary>
|
||||
public class DiceRoller : MonoBehaviour
|
||||
{
|
||||
private static DiceRoller _activeReturnRoller;
|
||||
|
||||
[Header("References")]
|
||||
[SerializeField] private Dice dice;
|
||||
[SerializeField] private Rigidbody rb;
|
||||
[SerializeField] private Collider diceCollider;
|
||||
|
||||
[Tooltip("Определение типа дайса")]
|
||||
[field: SerializeField] public DiceDefinition Definition { get; private set; }
|
||||
@@ -38,12 +41,25 @@ namespace YachtDice.Dice
|
||||
[Tooltip("Сколько секунд кубик должен быть неподвижен, чтобы засчитать остановку")]
|
||||
[SerializeField] private float settleDelay = 0.3f;
|
||||
|
||||
[Tooltip("Максимальная длительность броска до принудительного завершения")]
|
||||
[SerializeField] private float maxRollDuration = 3f;
|
||||
|
||||
[Header("Snap Alignment")]
|
||||
[Tooltip("Длительность плавного доворота к ровному положению")]
|
||||
[SerializeField] private float snapDuration = 0.35f;
|
||||
|
||||
[SerializeField]
|
||||
private AnimationCurve snapCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||||
|
||||
[Header("Return To Start")]
|
||||
[Tooltip("Длительность возврата в стартовую позицию")]
|
||||
[SerializeField] private float returnDuration = 0.4f;
|
||||
|
||||
[Tooltip("Высота дуги при возврате в стартовую позицию")]
|
||||
[SerializeField] private float returnArcHeight = 0.6f;
|
||||
|
||||
[SerializeField]
|
||||
private AnimationCurve returnCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается когда кубик полностью остановился. Аргумент — выпавшее значение.
|
||||
@@ -56,11 +72,36 @@ namespace YachtDice.Dice
|
||||
public bool IsRolling { get; private set; }
|
||||
|
||||
private Coroutine _rollRoutine;
|
||||
private Vector3 _startLocalPosition;
|
||||
private bool _startPoseCaptured;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (dice == null)
|
||||
dice = GetComponent<Dice>();
|
||||
|
||||
if (rb == null)
|
||||
rb = GetComponent<Rigidbody>();
|
||||
|
||||
if (diceCollider == null)
|
||||
diceCollider = GetComponent<Collider>();
|
||||
|
||||
CaptureStartPose();
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
dice = GetComponent<Dice>();
|
||||
rb = GetComponent<Rigidbody>();
|
||||
diceCollider = GetComponent<Collider>();
|
||||
}
|
||||
|
||||
private void CaptureStartPose()
|
||||
{
|
||||
if (_startPoseCaptured) return;
|
||||
|
||||
_startLocalPosition = transform.localPosition;
|
||||
_startPoseCaptured = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +112,12 @@ namespace YachtDice.Dice
|
||||
{
|
||||
if (IsRolling) return;
|
||||
|
||||
if (dice == null || rb == null)
|
||||
{
|
||||
Debug.LogError("DiceRoller: отсутствуют обязательные компоненты Dice/Rigidbody.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_rollRoutine != null)
|
||||
StopCoroutine(_rollRoutine);
|
||||
|
||||
@@ -80,12 +127,17 @@ namespace YachtDice.Dice
|
||||
private IEnumerator RollSequence()
|
||||
{
|
||||
IsRolling = true;
|
||||
CaptureStartPose();
|
||||
|
||||
// ── 1. Подготовка ────────────────────────────────────────────
|
||||
if (diceCollider != null)
|
||||
diceCollider.enabled = true;
|
||||
|
||||
rb.isKinematic = false;
|
||||
rb.useGravity = true;
|
||||
rb.linearVelocity = Vector3.zero;
|
||||
rb.angularVelocity = Vector3.zero;
|
||||
var rollStartedAt = Time.time;
|
||||
|
||||
// ── 2. Импульс: подбросить вверх с лёгким разбросом ─────────
|
||||
Vector3 force = new Vector3(
|
||||
@@ -105,9 +157,17 @@ namespace YachtDice.Dice
|
||||
// ── 4. Ждём пока кубик успокоится ───────────────────────────
|
||||
var stillTimer = 0f;
|
||||
var sqrThreshold = settleSpeed * settleSpeed;
|
||||
var didTimeout = false;
|
||||
var maxDuration = Mathf.Max(0.1f, maxRollDuration);
|
||||
|
||||
while (stillTimer < settleDelay)
|
||||
{
|
||||
if (Time.time - rollStartedAt >= maxDuration)
|
||||
{
|
||||
didTimeout = true;
|
||||
break;
|
||||
}
|
||||
|
||||
yield return new WaitForFixedUpdate();
|
||||
|
||||
bool isSlow = rb.linearVelocity.sqrMagnitude < sqrThreshold
|
||||
@@ -116,6 +176,9 @@ namespace YachtDice.Dice
|
||||
stillTimer = isSlow ? stillTimer + Time.fixedDeltaTime : 0f;
|
||||
}
|
||||
|
||||
if (didTimeout)
|
||||
Debug.LogWarning($"DiceRoller: бросок принудительно завершён по таймауту ({maxDuration:0.##} c).", this);
|
||||
|
||||
// ── 5. Замораживаем физику и читаем верхнюю грань ───────────
|
||||
rb.linearVelocity = Vector3.zero;
|
||||
rb.angularVelocity = Vector3.zero;
|
||||
@@ -124,8 +187,7 @@ namespace YachtDice.Dice
|
||||
if (!dice.TryGetTopValue(out int topValue))
|
||||
{
|
||||
Debug.LogWarning("DiceRoller: не удалось определить верхнюю грань.");
|
||||
IsRolling = false;
|
||||
yield break;
|
||||
topValue = 1;
|
||||
}
|
||||
|
||||
// ── 6. Плавный доворот до ровного положения ─────────────────
|
||||
@@ -139,20 +201,73 @@ namespace YachtDice.Dice
|
||||
transform.rotation = startRot;
|
||||
|
||||
var elapsed = 0f;
|
||||
while (elapsed < snapDuration)
|
||||
var snapTime = Mathf.Max(0.01f, snapDuration);
|
||||
var snapCurveToUse = snapCurve ?? AnimationCurve.Linear(0f, 0f, 1f, 1f);
|
||||
while (elapsed < snapTime)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
float t = snapCurve.Evaluate(Mathf.Clamp01(elapsed / snapDuration));
|
||||
float t = snapCurveToUse.Evaluate(Mathf.Clamp01(elapsed / snapTime));
|
||||
transform.rotation = Quaternion.Slerp(startRot, targetRot, t);
|
||||
yield return null;
|
||||
}
|
||||
transform.rotation = targetRot;
|
||||
|
||||
// ── 7. Готово ───────────────────────────────────────────────
|
||||
// ── 7. Возвращаемся в стартовую позицию без коллизий ───────
|
||||
if (diceCollider != null)
|
||||
diceCollider.enabled = false;
|
||||
|
||||
yield return ReturnToStartPosition();
|
||||
|
||||
if (diceCollider != null)
|
||||
diceCollider.enabled = true;
|
||||
|
||||
// ── 8. Готово ───────────────────────────────────────────────
|
||||
IsRolling = false;
|
||||
OnRollFinished?.Invoke(topValue);
|
||||
|
||||
// Debug.Log($"{gameObject.name} | Выпало: <b>{topValue}</b>");
|
||||
}
|
||||
|
||||
private IEnumerator ReturnToStartPosition()
|
||||
{
|
||||
var startPos = transform.localPosition;
|
||||
var duration = Mathf.Max(0.01f, returnDuration);
|
||||
|
||||
yield return WaitForReturnTurn();
|
||||
|
||||
try
|
||||
{
|
||||
var elapsed = 0f;
|
||||
var returnCurveToUse = returnCurve ?? AnimationCurve.Linear(0f, 0f, 1f, 1f);
|
||||
while (elapsed < duration)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
|
||||
float normalized = Mathf.Clamp01(elapsed / duration);
|
||||
float curved = returnCurveToUse.Evaluate(normalized);
|
||||
|
||||
var pos = Vector3.Lerp(startPos, _startLocalPosition, curved);
|
||||
pos.y += Mathf.Sin(normalized * Mathf.PI) * returnArcHeight;
|
||||
|
||||
transform.localPosition = pos;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
transform.localPosition = _startLocalPosition;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_activeReturnRoller == this)
|
||||
_activeReturnRoller = null;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator WaitForReturnTurn()
|
||||
{
|
||||
while (_activeReturnRoller != null && _activeReturnRoller != this)
|
||||
yield return null;
|
||||
|
||||
_activeReturnRoller = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user