273 lines
7.9 KiB
C#
273 lines
7.9 KiB
C#
using Assets.Scripts.Actions.Interfaces;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Runtime.Serialization;
|
|
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
|
|
public class BlockingAnimation : Attribute
|
|
{
|
|
|
|
}
|
|
public enum Tasks { Move, Interact, Rotate };
|
|
public enum TaskStatus { Waiting, InProgress, Complete };
|
|
public enum AnimationStates
|
|
{
|
|
[EnumMember(Value = "Idle")]
|
|
Idle,
|
|
[EnumMember(Value = "Move")]
|
|
Walking,
|
|
[EnumMember(Value = "Sleeping")]
|
|
Sleeping,
|
|
[EnumMember(Value = "StandToSit")]
|
|
[BlockingAnimation]
|
|
Sitting,
|
|
[EnumMember(Value = "SitToStand")]
|
|
[BlockingAnimation]
|
|
Standing
|
|
};
|
|
|
|
public class Player : MonoBehaviour
|
|
{
|
|
public static Player Instance { get; private set; }
|
|
|
|
[SerializeField]
|
|
public NavMeshAgent _navAgent;
|
|
[SerializeField]
|
|
public Animator _animator;
|
|
|
|
[SerializeField]
|
|
private Transform _holdPoint;
|
|
|
|
private AnimationStates _currentAnimation;
|
|
private Vector3 _groundDeltaPosition;
|
|
|
|
public Dictionary<StatsId, Stat> Stats;
|
|
public IWorkPlace WorkPlace { get; set; }
|
|
|
|
private readonly Queue<PlayerTasks> _tasks = new Queue<PlayerTasks>();
|
|
private PlayerTasks _currentTask;
|
|
|
|
private const string WALK_VELOCITY = "WalkVelocity";
|
|
|
|
private FoodObject _foodObject;
|
|
|
|
private void Awake()
|
|
{
|
|
if (Instance != null)
|
|
{
|
|
Debug.Log("There's more than one player instance");
|
|
return;
|
|
}
|
|
Instance = this;
|
|
}
|
|
private void Start()
|
|
{
|
|
TimeManager.OnMinuteChanged += UpdateStatsByClock;
|
|
_navAgent.updatePosition = false;
|
|
Stats = PlayerStats.CreateInitialStats();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
TimeManager.OnMinuteChanged -= UpdateStatsByClock;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (IsBlockingAnimation(_currentAnimation))
|
|
{
|
|
if (IsAnimationStatePlaying(0, GetEnumMemberValue(_currentAnimation)))
|
|
return;
|
|
}
|
|
|
|
if (_currentTask == null || _currentTask.Status == TaskStatus.Complete)
|
|
{
|
|
_tasks.TryDequeue(out _currentTask);
|
|
}
|
|
if (_currentTask != null)
|
|
{
|
|
if (_currentTask.Status == TaskStatus.Waiting)
|
|
Debug.Log($"Current task {_currentTask.Task}");
|
|
switch (_currentTask.Task)
|
|
{
|
|
case Tasks.Rotate:
|
|
_currentTask.UpdateStatus(Rotate(_currentTask.TagretObject._playerArrivePoint.forward));
|
|
break;
|
|
case Tasks.Move:
|
|
if (_currentAnimation == AnimationStates.Sitting)
|
|
{
|
|
SetPlayerAnimation(AnimationStates.Standing);
|
|
return;
|
|
}
|
|
_navAgent.SetDestination(_currentTask.TagretObject._playerArrivePoint.position);
|
|
_currentTask.UpdateStatus(MoveToPoint());
|
|
break;
|
|
case Tasks.Interact:
|
|
if (pathComplete(_currentTask.TagretObject._playerArrivePoint.position))
|
|
_currentTask.UpdateStatus(InteractWithObject(_currentTask.TagretObject));
|
|
else
|
|
{
|
|
AddTask(new PlayerTasks(Tasks.Move, _currentTask.TagretObject));
|
|
AddTask(new PlayerTasks(Tasks.Rotate, _currentTask.TagretObject));
|
|
AddTask(_currentTask);
|
|
_currentTask = null;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private TaskStatus MoveToPoint()
|
|
{
|
|
SetPlayerAnimation(AnimationStates.Walking);
|
|
|
|
var worldDeltaPosition = _navAgent.nextPosition - transform.position;
|
|
_groundDeltaPosition.x = Vector3.Dot(transform.right, worldDeltaPosition);
|
|
_groundDeltaPosition.y = Vector3.Dot(transform.forward, worldDeltaPosition);
|
|
|
|
Vector2 velocity = (Time.deltaTime > 1e-5f) ? _groundDeltaPosition / Time.deltaTime : Vector2.zero;
|
|
_animator.SetFloat(WALK_VELOCITY, velocity.y);
|
|
|
|
return pathComplete(_navAgent.destination) ? TaskStatus.Complete : TaskStatus.InProgress;
|
|
}
|
|
|
|
private bool pathComplete(Vector3 destination)
|
|
{
|
|
if (Vector3.Distance(destination, _navAgent.transform.position) <= _navAgent.radius)
|
|
{
|
|
if (!_navAgent.hasPath || _navAgent.velocity.sqrMagnitude < 0.2f)
|
|
{
|
|
SetPlayerAnimation(AnimationStates.Idle);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public TaskStatus Rotate(Vector3 target)
|
|
{
|
|
var targetRot = Quaternion.LookRotation(target);
|
|
Quaternion rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(target), 10 * Time.deltaTime);
|
|
rotation.x = 0;
|
|
transform.rotation = rotation;
|
|
if (IsApproximate(targetRot, transform.rotation, 0.000004f))
|
|
{
|
|
return TaskStatus.Complete;
|
|
}
|
|
return TaskStatus.InProgress;
|
|
}
|
|
|
|
public static bool IsApproximate(Quaternion q1, Quaternion q2, float precision)
|
|
{
|
|
return Mathf.Abs(Quaternion.Dot(q1, q2)) >= 1 - precision;
|
|
}
|
|
|
|
private TaskStatus InteractWithObject(BaseInteractableObject interactableObject)
|
|
{
|
|
interactableObject.Interact(this);
|
|
return TaskStatus.Complete;
|
|
}
|
|
|
|
public void SetPlayerAnimation(AnimationStates newState)
|
|
{
|
|
if (newState == _currentAnimation)
|
|
{
|
|
return;
|
|
}
|
|
_animator.Play(GetEnumMemberValue(newState));
|
|
_currentAnimation = newState;
|
|
}
|
|
|
|
private bool IsAnimationStatePlaying(int animLayer, string stateName)
|
|
{
|
|
if (_animator.GetCurrentAnimatorStateInfo(animLayer).IsName(stateName) &&
|
|
_animator.GetCurrentAnimatorStateInfo(animLayer).normalizedTime < 1.0f)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
private void OnAnimatorMove()
|
|
{
|
|
transform.position = _navAgent.nextPosition;
|
|
}
|
|
|
|
public void AddTask(PlayerTasks task)
|
|
{
|
|
_tasks.Enqueue(task);
|
|
}
|
|
|
|
public void UpdateStatsByClock()
|
|
{
|
|
Stats[StatsId.Food].deduct(0.034f); // 48 hours it's 100, 100/2880=~0.034 per minute
|
|
if (_currentAnimation != AnimationStates.Sleeping)
|
|
{
|
|
Stats[StatsId.Energy].deduct(0.1f); // 24 hours it's 100, 100/1440=~0.096 per minute
|
|
}
|
|
else
|
|
{
|
|
Stats[StatsId.Energy].increase(1f);
|
|
|
|
}
|
|
}
|
|
|
|
public void BuyObject(FoodObjectSO objectToBuy)
|
|
{
|
|
Stats[StatsId.Money].deduct(objectToBuy.objectPrice);
|
|
//action.ApplyAction(this);
|
|
}
|
|
|
|
public float Eat()
|
|
{
|
|
Stats[StatsId.Food].increase(10);
|
|
return Stats[StatsId.Food].Value;
|
|
}
|
|
|
|
private static string GetEnumMemberValue<T>(T value)
|
|
where T : struct, IConvertible
|
|
{
|
|
return typeof(T)
|
|
.GetTypeInfo()
|
|
.DeclaredMembers
|
|
.SingleOrDefault(x => x.Name == value.ToString())
|
|
?.GetCustomAttribute<EnumMemberAttribute>(false)
|
|
?.Value;
|
|
}
|
|
private static bool IsBlockingAnimation<T>(T value)
|
|
where T : struct, IConvertible
|
|
{
|
|
var enumType = typeof(T);
|
|
var memInfo = enumType.GetMember(value.ToString());
|
|
var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType<BlockingAnimation>().FirstOrDefault();
|
|
return attr != null;
|
|
}
|
|
|
|
|
|
public void SetFoodObject(FoodObject foodObject)
|
|
{
|
|
foodObject.transform.parent = _holdPoint;
|
|
foodObject.transform.localPosition = Vector3.zero;
|
|
_foodObject = foodObject;
|
|
Debug.Log($"player hold {foodObject.GetFoodObjectSO().name}");
|
|
|
|
}
|
|
public FoodObject GetFoodObject()
|
|
{
|
|
return _foodObject;
|
|
}
|
|
public void ClearFoodObject()
|
|
{
|
|
Destroy(_foodObject.gameObject);
|
|
_foodObject = null;
|
|
}
|
|
|
|
public bool HasFoodObject()
|
|
{
|
|
return _foodObject != null;
|
|
}
|
|
}
|