UI fixes, add npc base class

This commit is contained in:
Vova
2024-05-23 14:33:48 +03:00
parent cc17949b9c
commit b83539aef4
28 changed files with 184 additions and 80 deletions
+152
View File
@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class BlockingAnimation : Attribute { }
public abstract class BaseCharacter : MonoBehaviour
{
[SerializeField]
protected NavMeshAgent _navAgent;
protected Animator _animator;
private const string WALK_VELOCITY = "WalkVelocity";
private readonly Queue<PlayerTasks> _tasks = new Queue<PlayerTasks>();
private PlayerTasks _currentTask;
private CharacterDescriptor _characterDescriptor;
private Action _OnAnimationFinish;
private AnimationStates _currentAnimation;
private CharacterSex _characterSex;
protected void IntCharacter()
{
_animator = GetComponentInChildren<Animator>();
_characterDescriptor = GetComponentInChildren<CharacterDescriptor>();
_characterSex = _characterDescriptor.Sex;
SetPlayerAnimation(AnimationStates.Walking);
}
private void Update()
{
if (PlayerHelper.IsBlockingAnimation(_currentAnimation))
{
if (IsAnimationStatePlaying(0))
{
return;
}
else
{
_OnAnimationFinish?.Invoke();
_OnAnimationFinish = null;
}
}
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._interactionPoint.forward));
break;
case Tasks.Move:
if (_currentAnimation == AnimationStates.Sitting)
{
SetPlayerAnimation(AnimationStates.Standing);
return;
}
_navAgent.SetDestination(_currentTask.TagretObject._interactionPoint.position);
_currentTask.UpdateStatus(MoveToPoint());
break;
case Tasks.Interact:
_currentTask.UpdateStatus(TaskStatus.Waiting);
_currentTask.UpdateStatus(_currentTask.TagretObject.Interact());
break;
}
}
}
private TaskStatus MoveToPoint()
{
_navAgent.isStopped = false;
SetPlayerAnimation(AnimationStates.Walking);
_animator.SetFloat(WALK_VELOCITY, _navAgent.velocity.magnitude);
return IsPathComplete(_navAgent.destination) ? TaskStatus.Complete : TaskStatus.InProgress;
}
public bool IsPathComplete(Vector3 destination)
{
var dest = new Vector3(destination.x, 0, destination.z);
var pos = new Vector3(_navAgent.transform.position.x, 0, _navAgent.transform.position.z);
if (Vector3.Distance(dest, pos) <= _navAgent.radius)
{
transform.position = destination;
if (!_navAgent.hasPath || _navAgent.velocity.sqrMagnitude < 0.2f)
{
SetPlayerAnimation(AnimationStates.Idle);
_navAgent.isStopped = true;
return true;
}
}
return false;
}
protected 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;
}
protected void AddTask(PlayerTasks task)
{
_tasks.Enqueue(task);
}
public bool IsAnimationStatePlaying(int animLayer)
{
string stateName = PlayerHelper.GetEnumMemberValue(_currentAnimation);
var stateInfo = _animator.GetCurrentAnimatorStateInfo(animLayer);
return stateInfo.IsName(stateName) && stateInfo.normalizedTime < 1.0f;
}
public void SetPlayerAnimation(AnimationStates newState, Action onAnimationFinish)
{
_OnAnimationFinish = onAnimationFinish;
SetPlayerAnimation(newState);
}
public void SetPlayerAnimation(AnimationStates newState)
{
if (newState == _currentAnimation)
{
return;
}
var stringState=PlayerHelper.GetEnumMemberValue(newState);
stringState= string.Format(stringState,_characterSex.ToString());
Debug.Log($"Animation state {stringState}");
_animator.Play(stringState);
_currentAnimation = newState;
}
private bool IsApproximate(Quaternion q1, Quaternion q2, float precision)
{
return Mathf.Abs(Quaternion.Dot(q1, q2)) >= 1 - precision;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ae6d3e3aea34ae447a6aa1914a953e8e
@@ -0,0 +1,19 @@
using UnityEngine;
using UnityEngine.AI;
public class CharacterDescriptor : MonoBehaviour
{
[SerializeField]
public CharacterSex Sex;
private NavMeshAgent _navMeshAgent;
private void Start()
{
_navMeshAgent = GetComponentInParent<NavMeshAgent>();
}
private void OnAnimatorMove()
{
transform.parent.position = _navMeshAgent.nextPosition;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cf9ddd6d77ff9a7479edaeb2c8ae1078
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 64bc13a0b6923c14ab5d87e8338aca0c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+6
View File
@@ -0,0 +1,6 @@
using UnityEngine;
public class HoldPoint : MonoBehaviour
{
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 99527458161e16442b3fb76c8f585ec8
+10
View File
@@ -0,0 +1,10 @@
namespace Assets.Scripts.Characters
{
public class Npc : BaseCharacter
{
private void Awake()
{
base.IntCharacter();
}
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 57bc7fa5d71cfbf4685a51d4fd4be2ab
+49
View File
@@ -0,0 +1,49 @@
using Assets.Scripts.Interfaces;
public class NumericStat : INumericStat
{
public string Name { get; private set; }
public float Value { get; private set; }
public float Price { get; private set; }
public float Quantity { get; private set; }
public float MaxValue { get; private set; }
public NumericStat(string name, float startValue, float maxValue)
{
Name = name;
Value = startValue;
MaxValue = maxValue;
}
public NumericStat(string name, float price, float quantity, float maxValue)
{
Name = name;
Price = price;
Quantity = quantity;
MaxValue = maxValue;
}
public void increase(float byAmount)
{
if (Value < MaxValue)
{
Value += byAmount;
}
}
public bool deduct(float amount)
{
if (Value >= amount)
{
Value -= amount;
return true;
}
return false;
}
public void forceDeduct(float amount)
{
Value -= amount;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d941c5108c66a44da3ab31f96e40d8e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+168
View File
@@ -0,0 +1,168 @@
using Assets.Scripts.Interfaces;
using System;
using System.Collections.Generic;
using UnityEngine;
public class Player : BaseCharacter
{
public event EventHandler<bool> OnContainerChanged;
public static Player Instance { get; private set; }
private HoldPoint _holdPoint;
private PlayerStates _currentActing;
public Dictionary<StatsId, object> Stats;
public JobPositions JobPosition { get; set; }
private List<EducationInfoSO> _completedCourses = new();
public EducationInfoSO ActiveCourse { get; set; }
public EducationSkill Education { get; set; }
private ContainerItem _containerItem;
private string _locationName;
private void Awake()
{
if (Instance != null)
{
Destroy(gameObject);
Debug.Log("There's more than one player instance");
return;
}
PlayerPrefs.SetString("lastExitName", string.Empty);
Instance = this;
Stats = PlayerStats.CreateInitialStats();
JobPosition = JobPositions.Unemployed;
DontDestroyOnLoad(gameObject);
}
private void Start()
{
base.IntCharacter();
_holdPoint = GetComponentInChildren<HoldPoint>();
GameManager.Instance.Time.OnMinuteChanged += UpdateStatsByClock;
_animator.applyRootMotion = true;
_navAgent.updatePosition = false;
_currentActing = PlayerStates.Awake;
}
private void OnDestroy()
{
GameManager.Instance.Time.OnMinuteChanged -= UpdateStatsByClock;
}
public void SetPosition(Transform desiredPosition)
{
_navAgent.Warp(desiredPosition.position);
_navAgent.updatePosition = false;
Rotate(desiredPosition.forward * -1);
}
public void GoToPoint(BaseInteractableObject point)
{
AddTask(new PlayerTasks(Tasks.Move, point));
}
public async void Interact(BaseInteractableObject interactionItem)
{
var result = await interactionItem.ShowPopupMenu(this);
if (result != InteractionStatus.Complete)
{
if (!IsPathComplete(interactionItem._interactionPoint.position))
{
AddTask(new PlayerTasks(Tasks.Move, interactionItem));
AddTask(new PlayerTasks(Tasks.Rotate, interactionItem));
AddTask(new PlayerTasks(Tasks.Interact, interactionItem));
}
else
{
AddTask(new PlayerTasks(Tasks.Interact, interactionItem));
}
}
}
public void SetPlayerActing(PlayerStates state)
{
_currentActing = state;
}
public void UpdateStatsByClock()
{
switch (_currentActing)
{
case PlayerStates.Eating:
(Stats[StatsId.Food] as INumericStat).increase(10f);
break;
case PlayerStates.Sleeping:
(Stats[StatsId.Energy] as INumericStat).increase(0.2f);
(Stats[StatsId.Food] as INumericStat).deduct(0.03f);
break;
default:
(Stats[StatsId.Food] as INumericStat).deduct(0.05f); // 48 hours it's 100, 100/2880=~0.034 per minute
(Stats[StatsId.Energy] as INumericStat).deduct(0.1f); // 24 hours it's 100, 100/1440=~0.096 per minute
break;
}
}
public void Pay(float amount)
{
(Stats[StatsId.Money] as INumericStat).deduct(amount);
}
public void AddMoney(float amount)
{
(Stats[StatsId.Money] as INumericStat).increase(amount);
}
public void SetContainerItem(ContainerItem containerItem)
{
containerItem.transform.parent = _holdPoint.transform;
containerItem.transform.localPosition = Vector3.zero;
_containerItem = containerItem;
OnContainerChanged.Invoke(this, true);
}
public void ClearContainerItem()
{
if (_containerItem == null)
return;
Destroy(_containerItem.gameObject);
OnContainerChanged.Invoke(this, false);
_containerItem = null;
}
public ContainerItem GetContainerItem()
{
return _containerItem;
}
public bool IsHoldContainerItem()
{
return _containerItem != null;
}
public void SetLocationName(string locationName)
{
_locationName = locationName;
}
public string GetLocationName()
{
return _locationName;
}
internal void Learn(TimeSpan time)
{
ActiveCourse.PlayerProgress += time.Hours;
if (ActiveCourse.PlayerProgress >= ActiveCourse.Duration)
{
Education++;
print($"Congratulation player finish curse {ActiveCourse.Description} and his education now {Education.ToString()}");
ActiveCourse = null;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c802a0d9d32b0c04b841113e87b83e4b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+28
View File
@@ -0,0 +1,28 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
public static class PlayerHelper
{
public 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;
}
public 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;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1e0c9736e301f4f4d89a127e9efde6c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+46
View File
@@ -0,0 +1,46 @@
using System.Collections.Generic;
public class PlayerStats
{
public static Dictionary<StatsId, object> CreateInitialStats()
{
return new Dictionary<StatsId, object>()
{
{StatsId.Money, new NumericStat("Money", 100.0f,10000000f)},
{StatsId.RentAccount, new NumericStat("Rent Account", 0,10f)},
{StatsId.Food, new NumericStat("Food Energy", 50f,100f) },
{StatsId.Energy,new NumericStat("Energy", 50f,100f) },
{StatsId.LocationName,new StringStat("Location","Nowhere") },
};
}
//// Knowledge for University Jobs
//public Stat literatureKnowledge = new Stat("LiteratureKnowledge", 0);
//public Stat mathematicsKnowledge = new Stat("MathematicsKnowledge", 0);
//public Stat computerScienceKnowledge = new Stat("ComputerScienceKnowledge", 0);
//// Knowledge for Factory Jobs
//public Stat electronicsKnowledge = new Stat("ElectronicsKnowledge", 0);
//public Stat roboticsKnowledge = new Stat("RoboticsKnowledge", 0);
//public Stat industrialDesignKnowledge = new Stat("IndustrialDesignKnowledge", 0);
//public Stat careerPoints = new Stat("careerPoints", 0);
//// Achievements - to WIN the game
//public Stat whealthAchievement = new Stat("whealthAchievement", 999999);
//public Stat educationAchievement = new Stat("educationAchievement", 999999);
//public Stat careerAchievement = new Stat("careerAchievement", 999999);
//public Stat happinessAchievement = new Stat("happinessAchievement", 999999);
//// Inventory items
//public Stat freezerItem = new Stat("Freezer Item", 400, 1);
//public Stat clothesItem = new Stat("Clothes Item", 200, 3);
//public Stat booksItem = new Stat("Books Item", 150, 2);
//public Stat tvItem = new Stat("Tv Item", 1500, 1);
//public Stat laptopItem = new Stat("Laptop Item", 3000, 1);
// Update is called once per frame
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 90d55fcea99d3d14db40fb1ce5deca37
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+18
View File
@@ -0,0 +1,18 @@
public class PlayerTasks
{
public Tasks Task { get; private set; }
public BaseInteractableObject TagretObject { get; private set; }
public TaskStatus Status { get; private set; }
public PlayerTasks(Tasks task, BaseInteractableObject gameObject)
{
Task = task;
TagretObject = gameObject;
Status = TaskStatus.Waiting;
}
public void UpdateStatus(TaskStatus status)
{
Status = status;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a64ec35bd6b0f784cba543d437d7cdd4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+18
View File
@@ -0,0 +1,18 @@
using Assets.Scripts.Interfaces;
public class StringStat : IStringStat
{
public string Name { get; private set; }
public string Value { get; private set; }
public StringStat(string name, string startValue)
{
Name = name;
Value = startValue;
}
public void SetValue(string value)
{
Value = value;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c57f2033235f86b4585b18775c652653