using System; using System.Collections.Generic; using Unity.MLAgents; using Unity.MLAgents.Actuators; using Unity.MLAgents.Sensors; using UnityEngine; [RequireComponent(typeof(MovementController),typeof(BufferSensorComponent))] public class NPC : Agent, ICharacter { [HideInInspector] private Character AgentCharacter; public CharacterCondition Condition; private FlagZone flagZone = null; public INpcBaseState NpcState { get; private set; } public INpcBaseBodyState NpcBodyState { get; private set; } public Character GetCharacter => AgentCharacter; private NpcDirectPointState DirectState; private NpcInCoverState CoverState; private NpcRunningState RunningState; private NpcStandingState StandingState; private NpcCrouchingState CrouchingState; private MovementController moveController; private BufferSensorComponent bufferSensor; private Dictionary navPointIdDict; #region UnityEvents and ML private void Awake() { DirectState = new NpcDirectPointState(); CoverState = new NpcInCoverState(); RunningState = new NpcRunningState(); NpcState = DirectState; CrouchingState = new NpcCrouchingState(); StandingState = new NpcStandingState(); NpcBodyState = StandingState; AgentCharacter = new Character(); Condition = AgentCharacter.Condition; moveController = gameObject.GetComponent(); bufferSensor = gameObject.GetComponent(); flagZone = GameObject.FindObjectOfType(); if (flagZone is null) Debug.LogError("Flag Is Not Set"); navPointIdDict = MapManager.Instance.IDToNavPoint; if (navPointIdDict is null) Debug.LogError("Cant Find Nav Point Dictionary"); } private void OnDestroy() { Debug.LogWarning("Pooled object was destroyed"); } public override void OnEpisodeBegin() { if (navPointIdDict is null) Debug.LogError("Cant Find Nav Point Dictionary"); NpcState = DirectState; flagZone = GameObject.FindObjectOfType(); } public override void CollectObservations(VectorSensor sensor) { navPointIdDict = MapManager.Instance.IDToNavPoint; if (navPointIdDict is null) Debug.LogError("Cant Find Nav Point Dictionary"); var candidates = moveController.GetPointsCandidate(); //common sensors sensor.AddObservation(GameManager.IsHaveSeenByEnemy(AgentCharacter.Team.GetOppositeTeam(), NpcBodyState.GetPointToHit(gameObject)).ToInt()); sensor.AddObservation(AgentCharacter.LastTimeHit); sensor.AddObservation((!flagZone.IsNotOccup).ToInt()); sensor.AddObservation(Condition.GetHealthPointsInQuantile()); sensor.AddObservation(Condition.GetArmourPointsInQuantile()); sensor.AddObservation(candidates.Count); sensor.AddObservation(moveController.PointStartID); sensor.AddObservation(moveController.PointEndID); //state sensors sensor.AddObservation((int)NpcState.State); sensor.AddObservation((int)NpcBodyState.State); sensor.AddObservation(GameManager.IsEnemyNearby(gameObject.transform.position, AgentCharacter.Team)); sensor.AddObservation(navPointIdDict[moveController.PointStartID].DeathAttr); sensor.AddObservation(navPointIdDict[moveController.PointEndID].DeathAttr); sensor.AddObservation(moveController.FlagDistance); //point sensors foreach (var point in candidates) { var position = transform.position; bufferSensor.AppendObservation(new float[] { point.DeathAttr, (int)point.navType, //4 flagEnemyDistance GameManager.IsCloserToFlagFromNextNavPoint(point, position).ToInt(), //5 EnemyVsNavPointDistance GameManager.IsCloserToEnemyThanToNextNavPoint(point, position, AgentCharacter.Team.GetOppositeTeam()).ToInt(), //6 Have been seen by enemy in this point GameManager.IsHaveSeenByEnemy(AgentCharacter.Team.GetOppositeTeam(), point.Position).ToInt() }); } } public override void OnActionReceived(ActionBuffers actions) { var result = actions.DiscreteActions; if (result[0] == 0) { if (navPointIdDict[moveController.PointStartID].navType != NavPointType.Cover) return; NpcState = CoverState; switch (result[1]) { case 0: Peek(); break; case 1: Cover(); break; case 3: Peek(); moveController.GoToNextNavPoint(navPointIdDict[result[2]]); break; case 4: NpcState = DirectState; break; default: throw new ArgumentException("Undefined Action recieved"); } } if (result[0] == 1) { if (navPointIdDict[moveController.PointStartID].navType != NavPointType.Direction) return; switch (result[1]) { case 0: moveController.GoToNextNavPoint(navPointIdDict[result[2]]); NpcState = RunningState; break; case 1: NpcState = DirectState; break; default: throw new ArgumentException("Undefined Action recieved"); } } if (result[0] == 2) { if (moveController.PointStartID == moveController.PointEndID && moveController.PointEndID != -1) return; switch (result[1]) { case 0: moveController.StopOnPath(); NpcState = DirectState; break; case 1: moveController.ReturnToStartPoint(); NpcState = RunningState; break; default: throw new ArgumentException("Undefined Action recieved"); } } } #endregion public event Action OnChangePosition; private void Peek() { OnChangePosition?.Invoke(global::NpcBodyState.Standing); NpcBodyState = StandingState; } private void Cover() { OnChangePosition?.Invoke(global::NpcBodyState.Crouching); NpcBodyState = CrouchingState; } public event Action OnDamageRecieved; public void GetDamage(int damage) { AgentCharacter.LastTimeHit = TimeManager.Instance.CurrentTime; Condition.GiveHealth(-Mathf.RoundToInt(damage * (1 - Condition.ArmourPoints * 0.5f))); Condition.GiveArmour(-Mathf.RoundToInt(Mathf.Sqrt(damage) * 5)); OnDamageRecieved?.Invoke(damage, AgentCharacter.Team); if (Condition.HealthPoints < 0) { MapManager.AddDeathAttributeToPoints(moveController.PointStartID, moveController.PointEndID, moveController.DistanceToGo, moveController.RemainingDistance); var pos = gameObject.transform.position; var id = moveController.PointStartID; CharacterFactory.Instance.ReSpawn(this, ref pos, ref id); } } public void ResetCharacter() { Condition.Reset(); EndEpisode(); } }