Disclaimer: This is a solo project. All functionality you see here is of my creation.
I had an idea for timer-based behavior, but I didn't like the concept of rewriting the code for every actor that could utilize it. Rather than opting for a macro library, I decided to go with a custom component.
I wanted to keep this as agnostic as possible so that any actor could take advantage of this behavior without needing to rewrite a ton of code. They would just need to implement the correct blueprint interface, implement the right events, and voila.
I showed the basics of how I used this for volatile keys, but I wanted to go more in depth here.
So any actor that has this component needs to "Prepare" it on Event BeginPlay. The PrepareTimer event takes in the array of actors that we want to be held to a timer. For instance, a switch on the wall may have the timer enabled, but the door(s) it targets is the actor(s) that we would pass in; that's also the actor that needs to implement the blueprint interface for this.
We bind an event to the Timer Reset delegate for convenience (shown in Figure 5). This delegate is so the component owner can implement any extra behavior required on Timer Reset (reverting state, changing data, etc.) I also set defaults (I made this a separate event for a reason we'll see later). WipeMap just takes all of our target actors, adds them to a map, and sets all keys to false. I end up needing that later.
The next thing I do is set up a sound cue and spawn it as a component and then store the reference.
That's because later, I'll need to do some audio work that requires an audio component as a target.
Next up is the Start signal. This is where it can get confusing. If we have a switch that opens a door, when we use the switch, it'll call Timer Signal To Targets. The Timer Start BPI message will be sent to the target door. So the target needs to implement the interface behavior. We pass in reference to self so that the target actor knows which component to message back to when it 'checks in'. We'll see that next. I also added an inversion condition so that, if we flicked that switch again, we'd reset the timer.
The Check In is simple. The target actor (door, in our persisting example) will message the component once it's reached its target state (fully opened) and set its Boolean in the map to true. Then we try to run the timer, which won't run until we know all actors are checked in.
Let's look at what happens when we run that timer.
We check to make sure all targets are checked in. All this does is get the values of our map and see if that array contains a false value. If it doesn't, then we execute. Just for safety, we run a do once and make sure our audio component is valid before proceeding.
This is some primitive audio work. Basically, the component runs a sound cue that has two different tick sounds playing. One has more reverb and is punchier; that's what we use as a 'warning sound', which you can hear in the video. This set of functions just makes sure the sound plays properly.
After we stop our sound, we call the Timer Reset delegate. Remember that in Figure 1 we bound an event to this. Let's take a look at that event.
This event resets all of our timer behavior. This also showcases why Set Defaults from Figure 1 is its own event instead of tacked onto the Prepare Timer event.
So we set everything back to default, message the target actors with the Timer End function from the BPI. Then we just make sure we stop the audio component if it's still playing (in case we call this before the end of the audio cue). Both of the branch executions lead back to resetting the Do Once in Figure 4A.