357 lines
13 KiB
C#
357 lines
13 KiB
C#
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<float> 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>())
|
|
{
|
|
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<ParticleSystem>();
|
|
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();
|
|
}
|
|
}
|
|
}
|