SamsonGame/Assets/Scripts/Game/Loot/LootBase.cs

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();
}
}
}