Preperations
We will want to actually do some setup before we create our knock mechanic. Start by going into PrincipalScript
. At the top of the script, with all the using namespaces. Add in these new namspaces
using System.Collections;
using System.Collections.Generic;

We will be using lists and IEnumerators today. Hence the namespaces
Now let's get to the copy and pasting coding! Add this above the Start()
function
private List<DoorScript> doorsToKnock = new List<DoorScript>();
private AudioSource knockingSource;
[SerializeField] private AudioClip aud_KnockOnDoor;
[SerializeField] private float knockingDistance = 7f;
[SerializeField] private float knockingTime = 3f;
public bool inFaculty;
private void ResetDoors()
{
doorsToKnock.Clear();
agent.isStopped = false;
}

This creates the variables we will be needing, as well as our function for clearing the Principal's memory of doors. Now head over to the Start()
function and append these lines of code below
knockingSource = base.gameObject.AddComponent<AudioSource>();
knockingSource.playOnAwake = false;
knockingSource.spatialBlend = 1;
knockingSource.rolloffMode = AudioRolloffMode.Linear;
knockingSource.minDistance = 10;
knockingSource.maxDistance = 100;

We create our own AudioSource from scratch because audioDevice
has non-ideal 3D sound settings. AKA the knock will be heard all throughout the schoolhouse
Next. Head on over to Update()
. Find the logic where the Principal finds the player, and add in ResetDoors();

Next, go to TargetBully()
and add in ResetDoors();
as well

With this change. If the principal spots any tardiness in the schoolhouse, he shall forget his manners for a bit to chase after the evildoer. This just prevents any strange interactions that may occur.
Making the Principal knock
Now let's actually create the Principal knocking mechanic! Create a new IEnumerator called GenerateDoors()
as shown below
private IEnumerator GenerateDoors()
{
ResetDoors();
while (!agent.hasPath)
{
yield return null;
}
if (agent.path.corners.Length > 1)
{
for (int i = 0; i < agent.path.corners.Length - 1; i++)
{
Vector3 direction = agent.path.corners[i + 1] - agent.path.corners[i];
if (Physics.Raycast(agent.path.corners[i], direction, out this.hit, direction.magnitude))
{
DoorScript door = hit.transform.GetComponent<DoorScript>();
if (door != null && door.outside.sharedMaterial.name.Contains("Faculty"))
{
doorsToKnock.Add(door);
}
}
}
}
yield break;
}

Let's call this coroutine whenever the script calls the Wander()
function

Let me explain the logic for a bit. GenerateDoors()
gets ran whenever the Principal starts to Wander the schoolhouse, as that is when he should be knocking at faculty doors. The coroutine waits until the agent has finished generating the path. Once it does finish, it loop through all the waypoints in the path, and checks if a faculty door is inbetween them. If so, it adds that door to the list of doors to knock.
But now we need to actually make the Principal knock. Create another IEnumerator called KnockAtDoor()
as shown
private IEnumerator KnockAtDoor()
{
agent.isStopped = true;
coolDown = knockingTime + 1f;
knockingSource.PlayOneShot(aud_KnockOnDoor);
yield return new WaitForSeconds(knockingTime);
agent.isStopped = false;
yield break;
}

Next. Head on over to the Update()
function and add this at the bottom
if (doorsToKnock.Count > 0)
{
DoorScript currentDoor = doorsToKnock[0];
if (Vector3.Distance(currentDoor.transform.position, base.transform.position) < knockingDistance)
{
if (!(inFaculty || currentDoor.outside.sharedMaterial == currentDoor.open))
{
StartCoroutine(this.KnockAtDoor());
}
doorsToKnock.RemoveAt(0);
}
}

We first check to see if there are any knockable doors in our path, and checks for the distance between the Principal and the earliest door in our path. If the distance is less than our knocking distance (which we set to 7f), the Principal will knock on the door! We make sure that the Principal doesn't knock if the door is already open or if the Principal is currently inside the faculty room himself
Is the Principal inside Faculty?
Now, we need to actually check if the Principal is inside a faculty room. Head on over to FacultyTriggerScript
. Create a new variable as shown below
public PrincipalScript principal;

Go to the Start()
function and add these two lines of code
principal = FindFirstObjectByType<PrincipalScript>(FindObjectsInactive.Include);
Physics.IgnoreLayerCollision(base.gameObject.layer, principal.gameObject.layer, false);

We find PrincipalScript
via code using FindFirstObjectByType
. And we tell the physics that we want the trigger and the Principal to allow interactions with each other, so that OnTriggerEnter
can work. Speaking of which!
Head over to OnTriggerStay
and add the following
if (other.gameObject.name == "Principal of the Thing")
{
principal.inFaculty = true;
}

And also include an OnTriggerExit
as shown below
private void OnTriggerExit(Collider other)
{
if (other.gameObject.name == "Principal of the Thing")
{
principal.inFaculty = false;
}
}

This simply updates the inFaculty
boolean that we have made in the PrincipalScript
Final Touches
Save both scripts and head on over to the Unity Inspector. Find the Principal GameObject and look for his script. You will see three new variables on the top

All you need to do is set aud_KnockOnDoor
to your Principal knock audio. So that audio would play when he does knock the door.
There are also two values you can change here
knockingDistance
determines how close the Principal has to be to the door to knock at itknockingTime
determines how long the principal waits after his knock to move again
You do not need to change them if you are just going for an accurate knock. But feel free to customize these to your hearts content
Playtest
Now we can actually playtest our script! Run the scene and watch as the Principal knocks at one of the doors!