using System; using DG.Tweening; using DG.Tweening.Core; using UnityEngine; using UnityEngine.Events; using UnityEngine.Rendering; using Random = UnityEngine.Random; namespace RND { public abstract class LootBase : MonoBehaviour { [field: SerializeField] public Transform Target { get; set; } [field: SerializeField] public Vector3 TargetOffset { get; set; } = Vector3.zero; [field: SerializeField] public MoveTargetType TargetType { get; set; } = MoveTargetType.World; [field: SerializeField] public LootJumpType JumpType { get; set; } [field: SerializeField] public Vector3 RotationAxis { get; set; } = new Vector3(0, 1, 0); [field: SerializeField] private float RotationSpeed { get; set; } = 180f; public bool OnGround => JumpType == LootJumpType.JumpOnGround; public bool AutoMove { get; set; } = true; public float DropDelay { get; set; } = 0; public bool IsTouched { get; private set; } public bool DropOnStart = true; public bool MoveToPicker = true; public bool DestroyOnTake { get; set; } = true; public Vector2 JumpPowerRange { get => _jumpPower; set => _jumpPower = value; } public Vector3 MoveOffset { get; set; } public enum MoveTargetType { World = 0, UI = 1, } [SerializeField] private Transform _view; [SerializeField] private float _jumpDuration = 1f; [SerializeField] private float _halfJumpDuration = 0.5f; [SerializeField] private Vector2 _jumpRadius = new Vector2(0.9f, 1.5f); [SerializeField] private Vector2 _jumpEndHeight = new Vector2(0.9f, 1.5f); [SerializeField] private Vector2 _jumpPower = new Vector2(1.2f, 1.4f); [SerializeField] private float _waitDuration = 1f; [SerializeField] private float _moveDuration = 1f; [SerializeField] private Ease _worldMoveEase = Ease.Linear; [SerializeField] private Ease _uiMoveEase = Ease.InQuad; [Header("VFX")] [SerializeField] private GameObject _destroyFx; private Vector3 _startPos = default; private Sequence _dropSequence = null; private Sequence _moveSequence = null; private Tweener _rotateTween; [Header("Events")] public UnityEvent OnTaken = new UnityEvent(); public UnityEvent OnMoveStarted = new UnityEvent(); public UnityEvent OnPlayerTouched = new UnityEvent(); public UnityEvent OnDropFinished = new UnityEvent(); protected virtual void Start() { transform.localRotation = Quaternion.AngleAxis(Random.Range(0, 360), RotationAxis); StartRotating(); if (DropOnStart) Drop(); } public void Drop() { _startPos = transform.position; _dropSequence = DOTween.Sequence(); _dropSequence.AppendInterval(DropDelay); switch (JumpType) { case LootJumpType.None: break; case LootJumpType.HalfJump: SetupHalfJumpTween(_dropSequence); break; case LootJumpType.JumpSingle: SetupSingleJumpTween(_dropSequence); break; case LootJumpType.Jump: case LootJumpType.JumpOnGround: AlignView(); SetupJumpTween(_dropSequence); break; default: throw new ArgumentOutOfRangeException(); } _dropSequence.AppendCallback(OnDropFinishedHandler); if (AutoMove) { SetupMoveTween(); _dropSequence.AppendInterval(Random.Range(_waitDuration * 0.5f, _waitDuration * 1.5f)); _dropSequence.Append(_moveSequence); } _dropSequence.Play(); } private void OnDropFinishedHandler() { OnDropFinished?.Invoke(); } private void AlignView() { float lootSize = 0; if (_view.TryGetComponent(out Collider viewCol)) lootSize = viewCol.bounds.size.y; else if (_view.TryGetComponent(out Renderer viewRenderer)) lootSize = viewRenderer.bounds.size.y; _view.transform.SetLocalPositionY(lootSize * 0.5f); } private void SetupJumpTween(Sequence seq) { Vector3 jumpDirection = GetJumpDirection(); float jumpRadius = Random.Range(_jumpRadius.x, _jumpRadius.y); Vector3 jumpVector = jumpDirection * jumpRadius; if (!OnGround) jumpVector += Vector3.up * Random.Range(_jumpEndHeight.x, _jumpEndHeight.y); seq.Append(GetJumpTowards(jumpVector)); } public DG.Tweening.Tween GetJumpTowards(Vector3 translation) { Sequence seq = DOTween.Sequence(); Vector3 pos = transform.position; Vector3 jumpPos1 = OnGround ? GetGroundPosition(pos + translation * 0.67f) : pos + translation * 0.67f; Vector3 jumpPos2 = OnGround ? GetGroundPosition(pos + translation) : pos + translation; float jumpPower = Random.Range(JumpPowerRange.x, JumpPowerRange.y); seq.Append(transform.DOJump(jumpPos1, jumpPower * 0.67f, 1, _jumpDuration * 0.67f).SetEase(Ease.Linear)); seq.Append(transform.DOJump(jumpPos2, jumpPower * 0.33f, 1, _jumpDuration * 0.33f).SetEase(Ease.Linear)); return seq; } public void JumpTowards(Vector3 translation) { if (IsTouched) return; GetJumpTowards(translation).Play(); } private void SetupHalfJumpTween(Sequence seq) { Vector3 jumpDirection = GetJumpDirection(); float jumpRadius = Random.Range(_jumpRadius.x, _jumpRadius.y); Vector3 jumpVector = jumpDirection * jumpRadius + Vector3.up * Random.Range(_jumpEndHeight.x, _jumpEndHeight.y); seq.Append(GetHalfJumpTowards(jumpVector)); } private void SetupSingleJumpTween(Sequence seq) { Vector3 jumpDirection = GetJumpDirection(); float jumpRadius = Random.Range(_jumpRadius.x, _jumpRadius.y); Vector3 jumpVector = jumpDirection * jumpRadius + Vector3.up * Random.Range(_jumpEndHeight.x, _jumpEndHeight.y); seq.Append(GetSingleJumpTowards(jumpVector)); } public DG.Tweening.Tween GetHalfJumpTowards(Vector3 translation) { Sequence seq = DOTween.Sequence(); float jumpPower = Random.Range(JumpPowerRange.x, JumpPowerRange.y); seq.Append( transform.DOBlendableLocalMoveBy(Vector3.up * jumpPower, _halfJumpDuration).SetEase(Ease.OutQuad)); seq.Join(transform.DOBlendableLocalMoveBy(translation, _halfJumpDuration).SetEase(Ease.Linear)); return seq; } public DG.Tweening.Tween GetSingleJumpTowards(Vector3 translation) { Sequence seq = DOTween.Sequence(); float jumpPower = Random.Range(JumpPowerRange.x, JumpPowerRange.y); seq.Append(transform.DOJump(transform.position + translation, jumpPower, 1, _jumpDuration) .SetEase(Ease.Linear)); return seq; } protected virtual Vector3 GetJumpDirection() { return Random.insideUnitCircle.FromXY(); } private Vector3 GetGroundPosition(Vector3 targetPos) { return Physics.Raycast(targetPos + Vector3.up, Vector3.down, out RaycastHit hit, float.MaxValue, 1 << LayerMask.NameToLayer("Ground")) ? hit.point : targetPos; } public void MoveToTarget() { if (Target == null) { Log.Warn("Target is not set up"); return; } if (_moveSequence == null) SetupMoveTween(); if (!_moveSequence.IsActive() || _moveSequence.IsPlaying()) return; _moveSequence.Play(); } private void SetupMoveTween() { _moveSequence = DOTween.Sequence(); _moveSequence.AppendCallback(OnStartMovingHandler); DOSetter moveFunc; var currentMoveEase = Ease.Linear; switch (TargetType) { case MoveTargetType.World: moveFunc = MoveToWorldTransform; currentMoveEase = _worldMoveEase; break; case MoveTargetType.UI: moveFunc = MoveToUITransform; currentMoveEase = _uiMoveEase; break; default: throw new ArgumentOutOfRangeException(nameof(TargetType), TargetType, null); } _moveSequence.Append(DOTween.To(moveFunc, 0, 1, _moveDuration).SetEase(currentMoveEase)); _moveSequence.Join(_view.DOLocalMoveY(0, _moveDuration / 2f)); _moveSequence.AppendCallback(OnTake); _moveSequence.Pause(); } private void OnStartMovingHandler() { if (!AutoMove) _dropSequence.Kill(); _startPos = transform.position; OnMoveStarted?.Invoke(); if (TargetType == MoveTargetType.UI) { foreach (Renderer renderer in _view.GetComponentsInChildren()) { renderer.shadowCastingMode = ShadowCastingMode.Off; } } } protected virtual void OnTake() { _rotateTween.Kill(); if (DestroyOnTake) Destroy(gameObject); if (_destroyFx != null) { GameObject prefab = Instantiate(_destroyFx, transform.position + Vector3.up * 0.3f, Quaternion.identity); var particle = prefab.GetComponent(); particle.Play(); } OnTaken?.Invoke(); } private void MoveToWorldTransform(float t) { Vector3 targetPos = Target.position + MoveOffset; Vector3 endPosition = targetPos + Target.InverseTransformVector(TargetOffset); transform.position = Vector3.Lerp(_startPos, endPosition, t); } private void MoveToUITransform(float t) { Camera cam = CameraManager.Main; Vector3 startPosition = cam.WorldToViewportPoint(_startPos); Vector3 startViewportPosition = cam.ViewportToWorldPoint(startPosition); Vector3 targetWorldPosition = Target.position + Target.InverseTransformVector(TargetOffset); Vector3 targetViewportPosition = cam.WorldToViewportPoint(targetWorldPosition); float targetZ = cam.transform.InverseTransformPoint(_startPos).z; Vector3 targetPosition = cam.ViewportToWorldPoint(new Vector3(targetViewportPosition.x, targetViewportPosition.y, targetZ)); transform.position = Vector3.Lerp(startViewportPosition, targetPosition, t); } public void StartRotating() { float rotateDuration = 360 / RotationSpeed; _rotateTween = _view.transform.DORotate(Quaternion.AngleAxis(359.9f, RotationAxis).eulerAngles, rotateDuration, RotateMode.WorldAxisAdd) .SetLoops(-1) .SetEase(Ease.Linear) .OnKill(() => { if (_view != null) _view.transform.localRotation = Quaternion.identity; }) .Play(); } protected virtual void OnTriggerEnter(Collider other) { if (IsTouched || !other.CompareTag("Player")) return; Pickup(other); } private void Pickup(Collider other) { if (MoveToPicker) { TargetType = MoveTargetType.World; Target = other.attachedRigidbody.transform; TargetOffset = other.bounds.center - other.transform.position; } OnPlayerTouched?.Invoke(); MoveToTarget(); IsTouched = true; } private void OnDestroy() { if (_rotateTween.IsActive()) _rotateTween.Kill(); if (_moveSequence.IsActive()) _moveSequence.Kill(); if (_dropSequence.IsActive()) _dropSequence.Kill(); } } }