Пример #1
0
    /// <summary>
    /// Handles the case where the current Audio AiTarget contains a current Audio Threat.
    /// </summary>
    /// <returns>A new state or NONE if nothing needed to be handled</returns>
    private AiStateType HandleAudioThreat()
    {
        AiStateType state = AiStateType.None;

        AiThreatManager manager     = zombieStateMachine.ThreatManager;
        AiTarget        audioThreat = zombieStateMachine.ThreatManager.CurrentAudioThreat;

        if (manager.IsTargeting(AiTargetType.Visual_Food))
        {
            state = AiStateType.Alerted;
        }
        else if (manager.IsTargeting(AiTargetType.Audio))
        {
            // Get unique ID of the collider of our target
            int currentID = zombieStateMachine.ThreatManager.CurrentTarget.GetColliderID();

            // If this is the same light
            if (currentID == zombieStateMachine.ThreatManager.CurrentAudioThreat.GetColliderID())
            {
                RepathToThreatIfNecessary(audioThreat);
                state = AiStateType.Pursuit;
            }
            else
            {
                state = AiStateType.Alerted;
            }
        }

        manager.TrackTarget(audioThreat);

        return(state);
    }
Пример #2
0
    /// <summary>
    /// Called by the state machine each frame.
    /// </summary>
    /// <returns>Either Idle or a new state based upon the threats that were processed</returns>
    public override AiStateType OnUpdate()
    {
        UpdateTimer();

        AiTarget?   potentialThreat = this.zombieStateMachine.ThreatManager.DeterminePotentialThreat();
        AiStateType state           = this.zombieStateMachine.ThreatManager.DetermineNextPotentialThreatState(potentialThreat);

        if (state == AiStateType.None)
        {
            if (HasReachedMaxTime())
            {
                zombieStateMachine.WaypointManager.TrackWayPoint();
                state = AiStateType.Alerted;
            }
            else
            {
                state = GetDefaultStateType();
            }
        }
        else
        {
            this.zombieStateMachine.ThreatManager.TrackTarget((AiTarget)potentialThreat);
        }

        return(state);
    }
Пример #3
0
    /// <summary>
    /// Called by the state machine each frame.
    /// </summary>
    /// <returns>Either the current state or a new state.</returns>
    public override AiStateType OnUpdate()
    {
        // setting it here so changes in inspector take immediate affect
        this.zombieStateMachine.Speed = this.speed;

        AiTarget?   potentialThreat = this.zombieStateMachine.ThreatManager.DeterminePotentialThreat();
        AiStateType state           = this.zombieStateMachine.ThreatManager.DetermineNextPotentialThreatState(potentialThreat);

        if (state == AiStateType.None)
        {
            state = GetDefaultStateType();
        }
        else
        {
            bool isFoodThreat = this.zombieStateMachine.ThreatManager.DoesFoodThreatExist();
            if (isFoodThreat && !this.zombieStateMachine.CanHungerBeSatisfied((AiTarget)potentialThreat))
            {
                state = GetDefaultStateType();
            }
            else
            {
                this.zombieStateMachine.ThreatManager.TrackTarget((AiTarget)potentialThreat);
            }
        }

        if (state == GetDefaultStateType())
        {
            state = HandleDefaultState();
        }

        return(state);
    }
Пример #4
0
    /// <summary>
    /// Called by the state machine each frame.
    /// </summary>
    /// <returns>Either the current state or a new state.</returns>
    public override AiStateType OnUpdate()
    {
        AiStateType state = GetDefaultStateType();

        AdjustSpeed();

        if (zombieStateMachine.ThreatManager.DoesPlayerThreatExist())
        {
            zombieStateMachine.ThreatManager.TrackTarget(zombieStateMachine.ThreatManager.CurrentVisualThreat);

            if (zombieStateMachine.IsInMeleeRange)
            {
                FaceTargetGradually(this.slerpSpeed);
                RandomlySetNextAttackAnimation();
            }
            else
            {
                state = AiStateType.Pursuit;
            }
        }
        else
        {
            // PLayer has stepped outside out FOV or hidden so face in his/her direction and then
            // drop back to Alerted mode to give the AI a chance to re-aquire target
            FaceTarget();
            state = AiStateType.Alerted;
        }

        return(state);
    }
Пример #5
0
    /// <summary>
    /// Handles the case where the current visual threat is a Visual_light.
    /// </summary>
    /// <returns>A new state or NONE if nothing needed to be handled</returns>
    private AiStateType HandleVisualLightThreats()
    {
        AiStateType state = AiStateType.None;

        AiThreatManager manager      = zombieStateMachine.ThreatManager;
        AiTarget        visualThreat = zombieStateMachine.ThreatManager.CurrentVisualThreat;

        // and we currently have a lower priority target then drop into alerted
        // mode and try to find source of light
        if (manager.IsTargeting(AiTargetType.Audio) || manager.IsTargeting(AiTargetType.Visual_Food))
        {
            state = AiStateType.Alerted;
        }
        else if (manager.IsTargeting(AiTargetType.Visual_Light))
        {
            // Get unique ID of the collider of our target
            int currentID = zombieStateMachine.ThreatManager.CurrentTarget.GetColliderID();

            // If this is the same light
            if (currentID == visualThreat.GetColliderID())
            {
                RepathToThreatIfNecessary(visualThreat);
                state = AiStateType.Pursuit;
            }
            else
            {
                state = AiStateType.Alerted;
            }
        }

        manager.TrackTarget(visualThreat);

        return(state);
    }
Пример #6
0
    /// <summary>
    /// Determines the next potential state according to the provided potentil threat.  Certain conditions
    /// may still need to be applied after the fact before the entity will assume that new state.
    /// I got lucky creating this early on.  It turned out that for the future videos, they nearly all returned
    /// the same states shown here.  Using it seems to simplify the clarity/readability of the code for me, but
    /// it did come at a cost (often I had issues, such as a typo, and I couldn't rely on Gary's code to help me resolve them).
    /// </summary>
    /// <param name="potentialThreat">A potential threat or null.</param>
    /// <returns>The next potential state according to the provided threat or NONE</returns>
    public AiStateType DetermineNextPotentialThreatState(AiTarget?potentialThreat)
    {
        AiStateType state = AiStateType.None;

        if (potentialThreat != null)
        {
            switch (((AiTarget)potentialThreat).Type)
            {
            case AiTargetType.Visual_Player:
                state = AiStateType.Pursuit;
                break;

            case AiTargetType.Visual_Light:
                state = AiStateType.Alerted;
                break;

            case AiTargetType.Audio:
                state = AiStateType.Alerted;
                break;

            case AiTargetType.Visual_Food:
                state = AiStateType.Pursuit;
                break;

            default:
                state = AiStateType.None;
                break;
            }
        }

        return(state);
    }
Пример #7
0
    /// <summary>
    /// Called by the state machine each frame.
    /// </summary>
    /// <returns>Either the current state or a new state.</returns>
    public override AiStateType OnUpdate()
    {
        UpdateTimers();

        if (HasReachedMaxTime())
        {
            // reset the waypoint but stay in the alerted state with a fresh timer
            zombieStateMachine.WaypointManager.TrackWayPoint();
            ResetMaxDurationTimer();
        }

        AiTarget?   potentialThreat = zombieStateMachine.ThreatManager.DeterminePotentialThreat();
        AiStateType state           = zombieStateMachine.ThreatManager.DetermineNextPotentialThreatState(potentialThreat);

        if (state == AiStateType.None)
        {
            state = GetDefaultStateType();
        }
        else
        {
            AiTarget newThreat = (AiTarget)potentialThreat;
            if (newThreat.Type == AiTargetType.Visual_Player)
            {
                zombieStateMachine.ThreatManager.TrackTarget(newThreat);
            }
            else
            {
                if (newThreat.Type == AiTargetType.Audio || newThreat.Type == AiTargetType.Visual_Light)
                {
                    zombieStateMachine.ThreatManager.TrackTarget(newThreat);
                    ResetMaxDurationTimer();
                }

                if (newThreat.Type == AiTargetType.Visual_Food)
                {
                    if (zombieStateMachine.ThreatManager.DoesTargetExist())
                    {
                        state = GetDefaultStateType(); // food is less of a priority, so reset back to alerted state (audio or light)
                    }
                    else
                    {
                        zombieStateMachine.ThreatManager.TrackTarget(newThreat);
                    }
                }
            }
        }

        if (state == GetDefaultStateType())
        {
            state = HandleDefaultState();
        }

        return(state);
    }
Пример #8
0
    /// <summary>
    /// Retrieves the AiState from the cache according to the provided type.
    /// </summary>
    /// <param name="type">The type of state to fetch.</param>
    /// <returns>The AiState if it was found or null.</returns>
    private AiState GetStateFromCache(AiStateType type)
    {
        AiState state;

        if (this.stateCache.TryGetValue(type, out state))  // wow, not a huge fan of trygetvalue with out parameter)
        {
            return(state);
        }
        Debug.LogWarningFormat(
            "State of type {0} not found in cache!  Did you forget to drag it's AiState script to the AI Entity in the inspector?",
            type
            );
        return(null);
    }
Пример #9
0
    /// <summary>
    /// Handles threats that tend to produce the Alerted state, such as an Audio or Visual_light threat.
    /// </summary>
    /// <returns>A new state or NONE if nothing needed to be handled</returns>
    private AiStateType HandleAlertedStateThreats()
    {
        AiStateType state = AiStateType.None;

        if (zombieStateMachine.ThreatManager.DoesLightThreatExist())
        {
            state = HandleVisualLightThreats();
        }
        else if (zombieStateMachine.ThreatManager.DoesAudioThreatExist())
        {
            state = HandleAudioThreat();
        }

        return(state);
    }
Пример #10
0
    /// <summary>
    /// Handles the patrol by continuing to pursue waypoints, etc.
    /// </summary>
    /// <returns>Either the patrol state or any new state if certain conditions were met.</returns>
    private AiStateType HandleDefaultState()
    {
        AiStateType state = GetDefaultStateType();

        if (zombieStateMachine.NavAgent.pathPending)
        {
            // let the navmeshagent path complete before checking for state change, etc.
            zombieStateMachine.Speed = 0;
            return(state);
        }

        zombieStateMachine.Speed = this.speed;

        // Calculate angle we need to turn to be facing our target
        angleNeededForTurning = this.zombieStateMachine.ThreatManager.DetermineAngleNeededToTurnTowardsTarget();

        // If its too big then drop out of Patrol and into Alerted
        if (Mathf.Abs(angleNeededForTurning) > this.turnOnSpotThreshold)
        {
            state = AiStateType.Alerted;
        }
        else
        {
            // If root rotation is not being used then we are responsible for keeping zombie rotated
            // and facing in the right direction.
            if (!this.zombieStateMachine.RootMotionProperties.ShouldUseRootRotation)
            {
                // Generate a new Quaternion representing the rotation we should have
                Quaternion newRotation = Quaternion.LookRotation(this.zombieStateMachine.NavAgent.desiredVelocity);

                // Smoothly rotate to that new rotation over time
                this.zombieStateMachine.transform.rotation = Quaternion.Slerp(
                    this.zombieStateMachine.AiEntityBodyTransform.rotation,
                    newRotation,
                    Time.deltaTime * this.slerpSpeed
                    );
            }

            // If for any reason the nav agent has lost its path then send it to next waypoint
            if (zombieStateMachine.HasLostNavMeshPath())
            {
                zombieStateMachine.WaypointManager.SetNextWayPoint();
                zombieStateMachine.WaypointManager.TrackWayPoint();
            }
        }

        return(state);
    }
Пример #11
0
    /// <summary>
    /// Changes the current state to the new state, but only if it exists in the cache.
    /// </summary>
    /// <param name="newState">The new state to use.</param>
    /// <returns>true if the new state exists in the cache and the state was changed.</returns>
    private bool ChangeState(AiStateType newStateType)
    {
        bool didStateChange = false;

        AiState newState = GetStateFromCache(newStateType);

        if (newState != null)
        {
            if (currentState != null)
            {
                currentState.OnExitState(); // give the old state a chance to cleanup
            }
            newState.OnEnterState();        // give the new state a chance to initialize
            currentState          = newState;
            this.currentStateType = newStateType;
            didStateChange        = true;
        }

        return(didStateChange);
    }
Пример #12
0
    /// <summary>
    /// Build the state cache based upon the AiStates that have been added to this GameObject this script is also a part of.
    /// There may not be any state scripts that have been added.
    /// </summary>
    private void BuildStateCache()
    {
        AiState[] states = GetComponents <AiState>();
        if (states == null || states.Length == 0)
        {
            Debug.LogWarning("No Child AiState Script Components were added to the parent AI Entity GameObject!");
            return;
        }

        foreach (AiState state in states)
        {
            AiStateType key = state.GetDefaultStateType();
            if (state != null && !stateCache.ContainsKey(key))
            {
                state.SetStateMachine(this);
                stateCache[key] = state;
            }
        }

        ChangeState(this.currentStateType);
    }
Пример #13
0
    /// <summary>
    /// Called by the state machine each frame.
    /// </summary>
    /// <returns>Either Idle or a new state based upon the threats that were processed</returns>
    public override AiStateType OnUpdate()
    {
        IncrementTimer();

        AiStateType state = GetDefaultStateType();

        // if we got to this state, but its not hungry, go back to alert state
        if (!zombieStateMachine.IsHungery())
        {
            zombieStateMachine.WaypointManager.TrackWayPoint();
            state = AiStateType.Alerted;
        }
        else
        {
            AiThreatManager manager = zombieStateMachine.ThreatManager;

            if (manager.DoesPlayerThreatExist() || manager.DoesLightThreatExist())
            {
                manager.TrackTarget(zombieStateMachine.ThreatManager.CurrentVisualThreat);
                state = AiStateType.Alerted;
            }
            else if (manager.DoesAudioThreatExist())
            {
                manager.TrackTarget(zombieStateMachine.ThreatManager.CurrentAudioThreat);
                state = AiStateType.Alerted;
            }
            else if (manager.IsTargeting(AiTargetType.Visual_Food))
            {
                if (IsZombieCurrentlyEating())
                {
                    ReplenishSatisfaction();
                }
            }

            FaceTargetGradually(this.slerpSpeed);
        }

        return(state);
    }
Пример #14
0
    /// <summary>
    /// Handles state change if it occurs.
    /// </summary>
    private void HandlePotentialStateChange()
    {
        if (this.currentState == null)
        {
            return;
        }

        // allow the current state to execute for a single frame
        // (i.e. notify it that monobehavior life-cycle method update() was called (one frame))
        AiStateType potentialNewStateType = this.currentState.OnUpdate();

        if (potentialNewStateType != this.currentStateType)
        {
            if (!ChangeState(potentialNewStateType))
            {
                // if here, it most likely means that state's script wasn't added to the AI Entity
                if (!ChangeState(AiStateType.Idle))
                {
                    // if here, it most likely means the Idle state script wasn't added to the AI Entity, so just set the type
                    this.currentStateType = AiStateType.Idle; // TODO: maybe make an AiState that has a NONE type so this isn't standalone
                }
            }
        }
    }
Пример #15
0
    /// <summary>
    /// Called by the state machine each frame.
    /// </summary>
    /// <returns>Either the current state or a new state.</returns>
    public override AiStateType OnUpdate()
    {
        UpdateTimers();

        if (HasReachedMaxTime())
        {
            return(AiStateType.Patrol);
        }

        // TODO: change method so it doesn't have so many returns (returning at top is ok) - maybe use if/else

        // IF we are chasing the player and have entered the melee trigger then attack
        if (zombieStateMachine.ThreatManager.IsTargeting(AiTargetType.Visual_Player) && zombieStateMachine.IsInMeleeRange)
        {
            return(AiStateType.Attack);
        }

        // Otherwise this is navigation to areas of interest so use the standard target threshold
        if (zombieStateMachine.IsTargetReached)
        {
            switch (zombieStateMachine.ThreatManager.CurrentTarget.Type)
            {
            // If we have reached the source
            // example, flashlight was shown behind, and player ran away, zombie arrived there, so it goes into alerted
            case AiTargetType.Audio:
            case AiTargetType.Visual_Light:
                zombieStateMachine.ThreatManager.StopTrackingTarget();
                return(AiStateType.Alerted);    // Become alert and scan for targets

            case AiTargetType.Visual_Food:
                return(AiStateType.Feeding);
            }
        }

        // If for any reason the nav agent has lost its path then call then drop into alerted state
        // so it will try to re-aquire the target or eventually giveup and resume patrolling
        if (zombieStateMachine.HasLostNavMeshPath())
        {
            return(AiStateType.Alerted);
        }

        if (zombieStateMachine.NavAgent.pathPending)
        {
            zombieStateMachine.Speed = 0;
        }
        else
        {
            zombieStateMachine.Speed = this.speed;

            // zombie is very close, so make sure it is still facing target; or it reached it and we need to change state
            if (HandleZombieIsCloseOrAtTarget())
            {
                return(AiStateType.Alerted);
            }
        }

        // player is probably still current target and so we need to repath continually to ensure we are still pursing it
        if (HandlePlayerIsVisualThreat())
        {
            return(AiStateType.Pursuit);
        }

        // If our target is the last sighting of a player then remain in pursuit as nothing else can override
        if (zombieStateMachine.ThreatManager.IsTargeting(AiTargetType.Visual_Player))
        {
            return(AiStateType.Pursuit);
        }

        // If here, current player is not the threat, but some other visual threat exists (e.g. light, sound)
        AiStateType type = HandleAlertedStateThreats();

        if (type != AiStateType.None)
        {
            return(type);
        }

        return(GetDefaultStateType());
    }
Пример #16
0
    /// <summary>
    /// Handles the alerted state by continuing to pursue waypoints, etc.
    /// </summary>
    /// <returns>Either the alerted state or any new state if certain conditions were met.</returns>
    private AiStateType HandleDefaultState()
    {
        AiStateType state = GetDefaultStateType();

        if (
            !zombieStateMachine.IsTargetReached &&
            (zombieStateMachine.ThreatManager.DoesAudioThreatExist() || zombieStateMachine.ThreatManager.DoesLightThreatExist())
            )
        {
            // we got close to the target due to light or sound, so now we need to know if we should pursue it or seek to find it
            float angle = CalculationUtil.FindSignedAngle(
                zombieStateMachine.AiEntityBodyTransform.forward,
                zombieStateMachine.ThreatManager.CurrentTarget.Position - zombieStateMachine.AiEntityBodyTransform.position
                );

            if (zombieStateMachine.ThreatManager.DoesAudioThreatExist() && Mathf.Abs(angle) < this.threatAngleThreshold)
            {
                // it's a sound and we are capable of heading to it, so pursue it
                state = AiStateType.Pursuit;
            }
            else if (HasReachedMaxDirectionChangeTime())
            {
                // it's not a sound and we are not capable of turning towards it, so determine which way we should turn
                if (Random.value < zombieStateMachine.Intelligence)
                {
                    SeekTowards(angle); // smartly turn
                }
                else
                {
                    SeekRandomly();          // randomly turn because we are a stupid zombie :)
                }
                ResetDirectionChangeTimer(); // TODO: this does sometimes happen often and it makes the zombie appear as if it is stuck in the alerted state
            }
        }
        else if (
            zombieStateMachine.ThreatManager.IsTargeting(AiTargetType.Waypoint) &&
            !zombieStateMachine.NavAgent.pathPending
            )
        {
            // we were targeting a waypoint and we arrived at it, so determine if we can head to next one or turn towards it
            float angle = zombieStateMachine.ThreatManager.DetermineAngleNeededToTurnTowardsTarget();

            if (Mathf.Abs(angle) < this.turnOnSpotThreshold)
            {
                state = AiStateType.Patrol;
            }
            else if (HasReachedMaxDirectionChangeTime())
            {
                SeekTowards(angle);
                ResetDirectionChangeTimer();
            }
        }
        else if (HasReachedMaxDirectionChangeTime())
        {
            // we didn't find what we were looking for and our clock ran out, so turn randomly and repeat the entire alert process
            // TODO: this does sometimes happen often and it makes the zombie appear as if it is stuck in the alerted state
            SeekRandomly();
            ResetDirectionChangeTimer();
        }

        return(state);
    }