Scripting Tutorial Pressure Plate

From the Fallout4 CreationKit Wiki
Jump to navigation Jump to search

Pressure Plate Scripting Tutorial[edit | edit source]

In this tutorial, we are going to script a pressure plate.

Pressure Plate Art/Design Functionality[edit | edit source]

Even thought scripting plays a major role in the functionality of the function of the pressure plate, there are many other systems that play a part in how the pressure plate will work in the game. The files for the pressure plate, (Traps\PressurePlate\TrapStonePressurePlate01.nif as well as the havok behavior files), have other things incorporated into the art that we can use to help with the scripting process. Those useful things include:

  • Baked-in Trigger Box (Hit F4 in the editor to see the white box around the pressure plate.)
  • Animation Called "Trigger"
  • Animation Event Called "Reset"

On the design side of things, we can assume that traps and similar activators will start to function when activated. When in the editor, a pressure plate is an ideal object for being an activate parent of a trap or similar object.

The goal we are going for with the pressure plate is as follows:

  1. Something enters the trigger volume of the pressure plate (an actor steps on it, usually).
  2. The pressure plate animates as though it was pushed down.
  3. The pressure plate sends an activate signal to some other thing.
  4. Nothing interferes with the pressure plate during its operation.
  5. After the pressure plate is finished animating, it can be stepped on again and repeat the process.

Starting Your Script - Extending from ObjectReference[edit | edit source]

A blank page can be a daunting thing. Luckily, you can take an existing script and Extend it. This means that you can take all of the existing Properties and Functions of a script, and attach them to a new, blank script. This is the basic concept of Inheritance if you are familiar with object-oriented programming. If you aren't familiar with object-oriented programming, don't worry - things will make sense soon enough.

First, we're going to start with declaring the script name:

scriptName PressurePlate

Ok, we're off to a good start. But what was that part about extending ObjectReference I said earlier? First, it would help to know what an ObjectReference is. An ObjectReference could be basically any Object that is placed in the world. Extending the ObjectReference script essentially means that the script you're about to make is a more specific type of ObjectReference that can do special things that a normal ObjectReference can't. In order to do this, change your script declaration to:

scriptName PressurePlate extends ObjectReference

Now the PressurePlate script can do everything that an ObjectReference script can do.

Adding An Event[edit | edit source]

Now we want to have our script actually do something, and that something is to send an activate signal when something enters the pressure plate's trigger volume. To do this, we are going to use an OnTriggerEnter event.

 scriptName PressurePlate extends ObjectReference
 
    Event OnTriggerEnter( objectReference triggerRef )
       ...
    endEvent

What's going on here is we are making a new event called OnTriggerEnter. If you open the ObjectReference.psc script file, you'll see that there's an event called OnTriggerEnter already in there. There's some special stuff going on there, particularly the word Native in the declaration. This keyword means that this particular event will use c++ code in the game to determine exactly how OnTriggerEnter works. Beings we're extending ObjectReference, we get the special functionality that comes along, namely that the event will be called if a scripted object's trigger volume is entered.
You may be wondering what the words in the parentheses mean, the "( objectReference triggerRef )" part. The first part, objectReference, is a variable declaration, sort of like declaring an int or float. If you're familiar with the old scripting language, it's very similar to the keyword "ref". The second part, triggerRef, is a variable that holds what just caused the trigger event to occur. If the thing that caused the trigger event to occur, then triggerRef would equal Player.

Now that we're detecting that something happened, we should actually do something about it. In particular, we should animate the "trigger" animation and call the activate function. The activate function should activate itself (which does nothing) and then that activation will spread to all activate children, which is set up in the editor. These children could be anything - traps, activators, etc.

 scriptName PressurePlate extends ObjectReference
 
    Event OnTriggerEnter( objectReference triggerRef )
       activate( self )
       PlayAnimation( "trigger" )
    endEvent

Now we have a functioning script. This will actually function as a working system right now, although it would be buggy. There are a few things to note, however.

In the activate function, the "self" part doesn't mean that it's activating itself, rather that it's being activated by itself. It doesn't need to be activated by anything in particular, but it needs to be activated by something. Self is about as good a thing as anything.

In the PlayAnimation function, "trigger" is the name of the animation. The artist can name this animation just about anything, so make sure you know what animations are available to you.

Using Latent Functions[edit | edit source]

At this point, you can hook the script up in game and check it out if you want. You may notice that you can step on the pressure plate and cause it to send activate signals very rapidly, as there is nothing causing the script to wait for a bit before setting off the pressure plate again.
To fix this, we're going to incorporate a wait function.

 scriptName PressurePlate extends ObjectReference
 
    Event OnTriggerEnter( objectReference triggerRef )
       activate( self )
       PlayAnimation( "trigger" )
       wait(1.0)
    endEvent

Now there is a wait command. This will cause the script to stop running for 1 second (actually, a little longer than that, as it takes some time for the multithreading system to get back to the script).

But is there a better way to do this? It just so happens that there is a function called PlayAnimationAndWait that plays an animation and then waits until a specific event in that animation occurs. This event is defined in havok behavior. This way, if the animation changes, the timing of the script will still coincide with the animation.

 scriptName PressurePlate extends ObjectReference
 
    Event OnTriggerEnter( objectReference triggerRef )
       activate( self )
       playAnimationAndWait( "trigger", "reset" )
    endEvent

This script will wait until the "reset" event is hit in the pressure plate's animation.

Using Events[edit | edit source]

You may notice that adding a pause didn't actually seem to fix the problem. This is because one single script will create multiples of the same event, and these can be running at the same time. We can still use the pause to our advantage, however. We can basically "turn off" the functionality of the OnTriggerEnter event in a few different ways. Without knowing how states work, you may think to do it in some way similar to this:

 scriptName PressurePlate extends ObjectReference
 bool canBeTriggered = 1
  
    Event OnTriggerEnter( objectReference triggerRef )
       if canBeTriggered == True
          canBeTriggered = False
          activate( self )
          playAnimationAndWait( "trigger", "reset" )
          canBeTriggered = True    
       endIf
    endEvent

So with this, we're disabling the functionality of the OnTriggerEnter event with an If statement. We could potentially be done right here, but I want to use this as an opportunity to demonstrate States. With a State, you can change what happens when an event occurs depending on the State that the script is in.

 scriptName PressurePlate extends ObjectReference
 
 auto State Inactive      ;The auto here means "Start in this state"
    event OnTriggerEnter( objectReference triggerRef )	
        goToState( "Active" )
        activate( self )
        playAnimationAndWait( "trigger", "reset" )
        goToState( "Inactive" )
    Endevent
 endState
 
 State Active     ;Dummy state, don't do anything if animating
    event OnTriggerEnter( objectReference triggerRef )	
    endEvent
 EndState

What's happening here is that I'm making a new state called "inactive". It's created similarly to how you create anything else, although the word "auto" is a special keyword that means "start in this state." As soon as you enter the OnTriggerEnter event, the state gets set to a different state, "Active". If another OnTriggerEnter event happens, it will run the OnTriggerEnter event that's inside the "Active" state and not the "Inactive" state. In the case of the example script above, that means that nothing will happen if it gets triggered again. Even though the script is now in a different state, the particular event that's running will run to completion. The script will now activate, and play the animation. Once the animation gets to the "reset" event, the state gets set back to the "Inactive" state. Now if it gets triggered, it will go through the whole process again.

Why would you use states instead of an if statement and a condition? In this example, adding states causes there to be more code, and it's arguably more stuff to understand. Either way, this is a very simple script. In the future, more complicated scripts may benefit greatly from multiple states. You'll have to use fewer giant If statements, and in cases where you want multiple events to function differently based on the state, it's much easier to just place different versions of those events in each state.