1
\$\begingroup\$

Let's say I'm developing a horror game. For a jumpscare when entering room #1, the easiest way would be like this example:

void gameLoop() {
    if (playerEntersRoom1 && !playerHasBeenInRoom1) {
        playerHasBeenInRoom1 = true;
        jumpscareForRoom1();
    }
}

However this doesn't seem like a good solution to me, especially because the code would become quiet large when adding more and more of such events at some point. What's the way to go in a game engine like Unity?

\$\endgroup\$
1
  • 3
    \$\begingroup\$ Playerentersroom1 could be as well a trigger when you enter it by a collider. Or a static method that gets loaded when you load the room. Or a timed event. It depends on how you structure your game. Is each room it's own scene? Is it point and click or first person perspective? Or both? What is your own experience? There is no correct answer that fits all \$\endgroup\$
    – Zibelas
    Jul 17, 2022 at 17:28

3 Answers 3

3
\$\begingroup\$

I think using trigger objects with their own behaviour scripts, as suggested by @Zibelas, is probably the easiest way to manage this in unity. So, you would have a GameObject in your room1 that has a Collider marked as trigger (technically, you might be able to do this with a normal collider as well, as long as you are not using physics, but whatever). That object would also contain a script (lets call it JumpscareScript for the sake of argument) like this:

public class JumpscareScript : MonoBehaviour{
    bool playerHasBeenInRoom1;
    
    void Start(){
        playerHasBeenInRoom1 = false;
    }

    void OnTriggerEnter() {
        if (!playerHasBeenInRoom1) {
            playerHasBeenInRoom1 = true;
            jumpscareForRoom1();
        }
    }

    void jumpscareForRoom1(){
        // boo!
    }
}

That way, the rest of your code does not even need to know that this jumpscare stuff exists, it's all perfectly encapsulated in this one object. Of course, you could make the jumpscare class more generalizable by either having the jumpscare method be virtual, so you could override it in every object that has a jumpscare, or even have it be a delegate so each instace of the class has its own jumpscare without having to create lots of inheriting classes. But I'll leave those details to you, since I don't know your project or how you like to work.

The thing is, Unity is kind of "meant to" be used through this component-based way of organizing your code, where separate scripts are completely autonomous. So, a lot of the time, you wouldn't even have a main loop (of course, one does probably exist on the hidden C++ code of the engine) but you mostly are meant to use the "magical" functions in monobehaviours (Start, Update, OnCollisionEnter, etc) as the way to control the flow of the execution.

That is, of course, not to say it is wrong to circunvent all of this and make your own game loop, but it is definitely swimming against the tide.

\$\endgroup\$
3
  • \$\begingroup\$ The Start method is unnecessary here, because bool variables automatically get initialized as false. And even if you want to initialize a variable to a specific value, you can do that when you declare it (bool playerHasNotBeenInRoom = true). \$\endgroup\$
    – Philipp
    Jul 18, 2022 at 9:31
  • \$\begingroup\$ Well, yes. I guess it's a very subjective thing, but I find explicitly initializing the variable is more pleasing than relying on the default value. Also, I don't like setting the value of class members in the declaration because if at some point you serialize the variable it can get very confusing (when the serialized value does not match the initialization in the script). But yes, in this case, both of those do indeed work. \$\endgroup\$
    – PepeOjeda
    Jul 18, 2022 at 9:55
  • \$\begingroup\$ I think you can make this script more generic by scrubbing out references to "room1" - use a UnityEvent to allow the level designer to link up this one-time collider trigger to any outcome method they want. Then they can re-use the same script to trigger the jump scare in room 1, to trigger a door opening in room 2, to cue the music change in room 5, etc. \$\endgroup\$
    – DMGregory
    Jul 18, 2022 at 13:20
2
\$\begingroup\$

Here is a short script which is surprisingly simple, yet surprisingly powerful:

using UnityEngine;
using UnityEngine.Events;

[RequireComponent(typeof(Collider))]
public class TriggerArea : MonoBehaviour
{
    public GameObject triggeredBy;
    public UnityEvent TriggerEnter;

    private void OnTriggerEnter(Collider other) {
        if (other.gameObject == triggeredBy) {
            TriggerEnter.Invoke();
        }
    }
}

What does this script do? When you add it to a game object with a collider which has "Is Trigger?" enabled (important!), you will see this in the inspector:

Trigger Inspector

First you have a "Triggered By" field for a GameObject. Assign the object you want to detect, which is probably your Player.

The box below that is a collection of event actions. When you click the plus icon, then you can assign any method* of any components of any game object in your scene. And you can assign as many as you want. When those methods take parameters, then you can even state what values you want to pass to them.

Then, when the object you assigned enters the trigger-collider, all those methods will be called at once.

This allows you to do... basically anything you can think of. Activate objects, deactivate objects, play animations, play sounds, call your own methods of your own scripts... whatever. And you don't need to write a single additional line of code for that. You can do it all in the inspector. So it's a super designer-friendly script which enables level-designers without programming knowledge to create simple scripted events.

Want the event to trigger only once? Easy, then just assign GameObject.SetActive(false) of the trigger area itself, so the trigger area just disables itself when triggering.

Here is an example use-case of this script which plays an audio clip, activates a previously deactivated monster, causes a door to open and then disables itself so it does not trigger again:

trigger area inspector with several actions assigned

Happy jumpscaring!

* OK, most methods. It is limited to methods which take either no parameters or a single parameter that is either an elementary type or some kind of Unity Object. But that covers quite a lot.

\$\endgroup\$
-2
\$\begingroup\$

I don't use unity, but you wouldn't put that kind of thing directly in your main game loop for the reasons you already cite: the game loop gets too large and too difficult to keep organized. The archetypal game loop example is usually given as:

while running == True: #or maybe while state == 'running'
    processInputs();   #process inputs and events
    updateWorld();     #calculate things and update world state
    renderView();      #actually draw things to the display

Both your processInputs() and updateWorld() functions would dispatch out to lengthy lists of things like processEventQueue(), checkPlayerLocation(), checkMobs(), checkForJumpScares(), etc.

For a real-time first-person player moving through a live map you would probably want to have a playerEntersRoom event, with data indicating which room was being entered (room 1), and which gets added to the event queue when the player crosses an arbitrary point on the map. (By this point you would have created your own map format, probably based on an existing format, and written a map editor to help create and edit your maps.) That event would get processed by processEventQueue() to initiate jumpScareForRoom1().

If your game is a turn-based graphical horror instead of real time then your processing gets a lot easier. You may not necessarily have an event queue. processInputs() would get you into the room with a simple keypress or mouse click, and updateWorld() would take that information and initiate jumpScareForRoom1().

So you're correct that you wouldn't want to put that kind of thing directly in your main game loop. Also, I believe that this question is more one of general game organization than for unity specifically.

\$\endgroup\$
5
  • \$\begingroup\$ Most of this would be good advise for a game made without a game engine, but almost nothing of it is applicable to Unity. \$\endgroup\$
    – Philipp
    Jul 18, 2022 at 9:29
  • \$\begingroup\$ His question isn't about Unity, but thanks for the downvote. 'Ppreciate it. \$\endgroup\$
    – Ian Moote
    Jul 18, 2022 at 17:15
  • \$\begingroup\$ The question is about unity. Please note the Unity-tag below the question text. \$\endgroup\$
    – Philipp
    Jul 18, 2022 at 21:12
  • \$\begingroup\$ It also literally ends with the sentence: "What's the way to go in a game engine like Unity?" \$\endgroup\$
    – DMGregory
    Jul 18, 2022 at 22:01
  • \$\begingroup\$ 1) "a game engine LIKE unity", 2) I literally said in the first sentence of my reply, "I don't use Unity", 3) even though the OP felt that this was a "game engine LIKE Unity" question, and tagged it as such, the code example makes it obvious the problem centres more around game loops in general than game engines, much less a specific game engine, and 4) the title of the post, "How to handle many event triggers without hard-coding checks for all of them into the game loop?", you'll see that it's really asking about game loops. In fact, it was you who rewrote the OPs title to what it is. \$\endgroup\$
    – Ian Moote
    Jul 21, 2022 at 14:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .