Comprehensive Tutorial on Custom Functions
Introduction[edit | edit source]
Original tutorial can be found here.
I'm going to talk a bit about creating custom functions, calling them, and some other stuff. Custom functions have been touched upon in numerous places like the Creation Kit Wiki and Cipcis' site, but I'd like to go into some slightly more complex functions, as well as the basics, parameters, and what global functions are. So if you're interested, here we go.
What Are Functions?[edit | edit source]
Functions are very similar to events. They're little bits of code that you can "call" on, or activate. Once you've done that, the code inside of the function is processed, and whatever is necessary is done. Unlike events, however, you must call them manually (actually you can call events too, but that's a subject for another day), and they aren't activated by anything else unless you explicitly declare they must be.
Essentially, functions are processes that you declare, enclosed within function tags, like so:
Function MyFunctionName(Parameters)
;code within function, this will be processed when you "call" the function
EndFunction
As you can see, their structure is pretty similar to an event. Just like an event, you must declare the beginning and end of an event. Just like an event, you may declare parameters (for example, the OnDeath event has the parameter Actor akKiller). However, function parameters generally function a bit differently than events. Event parameters are usually pre-declared or set to a certain value, which can then be used within the event. An example, again using the OnDeath event:
Event OnDeath(Actor akKiller); akKiller is pre-defined as the actor who killed the actor who died
If akKiller == Game.GetPlayer(); a common use of akKiller - using it to get the value of the actor who killed the actor the script is on
;other stuff
EndIf
EndEvent
Where's function parameters usually have to be defined when calling the function, like so:
Event OnInit()
MyFunction(10, Game.GetPlayer())
EndEvent
Function MyFunction(Int Param1, Actor Param2)
;do stuff
EndFunction
Now lets talk about creating your own functions.
Basic Custom Functions[edit | edit source]
As you may have noticed earlier, function declarations are fairly simple. The structure looks like this:
Function FunctionName(Type Parameter)
;function content
EndFunction
So let's break it up really quick. You have to start and end the function with two words - Function to begin it, and EndFunction to end the function. Then, much like a property name, you have the function name. This is how you refer to the function within scripts. Then, you have any parameters enclosed within parentheses, in variable form (since they are variables) - Type VariableName. If you have no parameters, then you simply end the function with parentheses. When you call the function, you'll call it like this:
FunctionName(Parameter)
But you won't put in the Type Parameter for parameters. You'll put in what you want the parameter to be. Yet another example, where I have a function called WhatFunction and a string parameter:
Scriptname MyFuncScript Extends Quest; or whatever extension
;calling the function, must be within an event or function
Event OnInit()
WhatFunction("MyString")
EndEvent
;declaring the function, may be declared anywhere in the script but not within events or other functions
Function WhatFunction(String Param1)
;do functiony stuff
EndFunction
So some of you might be wondering what the purpose of this is. Well, the most basic of custom functions can be used to enclose certain bits of code that you use often in a script, so you can later call on them. So instead of typing this needlessly long script:
Scriptname ThatScript Extends Quest
{This script is soo much longer than it should be.}
Int Count
Event OnActivate()
If Count == 0
Debug.Notification("Don't use debugs in actual releases, use messages and message properties!"
Count += 1
ElseIf Count == 1
Debug.Notification("Be descriptive with names! This script has an awful name!")
Count += 1
ElseIf Count == 2
Debug.Notification("Other useful stuff...")
Count += 1
EndIf
RegisterForSingleUpdate(1.0)
EndEvent
Event OnUpdate()
If Count == 0
Debug.Notification("Don't use debugs in actual releases, use messages and message properties!"
Count += 1
ElseIf Count == 1
Debug.Notification("Be descriptive with names! This script has an awful name!")
Count += 1
ElseIf Count == 2
Debug.Notification("Other useful stuff...")
Count += 1
EndIf
EndEvent
You can just add a function to the script to shorten it significantly:
Scriptname ThatOtherScript Extends Quest
Int Count
Event OnActivate()
CountFunction()
RegisterForSingleUpdate(1.0)
EndEvent
Event OnUpdate()
CountFunction()
EndEvent
Function CountFunction(); no parameters necessary for this type of function
If Count == 0
Debug.Notification("Don't use debugs in actual releases, use messages and message properties!"
Count += 1
ElseIf Count == 1
Debug.Notification("Be descriptive with names! This script has an awful name!")
Count += 1
ElseIf Count == 2
Debug.Notification("Other useful stuff...")
Count += 1
EndIf
EndFunction
The size of that is significantly reduced, as you can see. However, you could use some parameters to make it more custom. So if you wanted something different to be printed to the screen based on a variable, you could try something like this:
Scriptname ThatBetterScript Extends Quest
Int Count
Event OnActivate()
CountFunction(1)
RegisterForSingleUpdate(1.0)
EndEvent
Event OnUpdate()
CountFunction(2)
EndEvent
Function CountFunction(Int PrintWhat); parameters we can set so we can define what the function will do
If PrintWhat == 1
If Count == 0
Debug.Notification("PrintWhat is 1. Don't use debugs in actual releases, use messages and message properties!"
Count += 1
ElseIf Count == 1
Debug.Notification("Be descriptive with names! This script has an awful name!")
Count += 1
ElseIf Count == 2
Debug.Notification("Other useful stuff...")
Count += 1
EndIf
ElseIf PrintWhat == 2
If Count == 0
Debug.Notification("PrintWhat is 2. This means that we called CountFunction with its parameter as 2."
Count += 1
ElseIf Count == 1
Debug.Notification("See how this could be useful? While it still processes the same amount of code, it's much cleaner.")
Count += 1
ElseIf Count == 2
Debug.Notification("I recommend putting functions at the bottom of your code. Much cleaner.")
Count += 1
EndIf
EndFunction
Hopefully you understand how this works. When you call the new CountFunction, then you can define the parameter for CountFunction. It exists when you declare it, but it doesn't really have any meaning. When you call CountFunction and set the parameter, then the parameter will equal what you called for that function call. Then, within the function, it checks for the value of PrintWhat, and depending on what you inputted (a word?), it will print a different notification to your game. So that's how you might use the functions - simply to clean up your scripts and save yourself some time. But there are a lot of other ways to use them too.
Returning Functions[edit | edit source]
One of the functions you may see more often is a type I call "returning functions". These functions return a value you can use, be it an integer, a form, or even a string. Their structure is a tad different from the ones before. They look like this:
ReturnType Function FunctionName(Parameters)
;Function content, and return
EndFunction
Everything is identical to the prior function structure, except for the ReturnType bit you see. Here, you'll put in the type of value the function will return - the value you'll "get". For example, a function like GetStage would have a ReturnType of Int, since it returns an integer value. Likewise, IsBeingRidden is a bool function, since it returns a value of true or false. Here's one of the examples I seem to love:
Bool Function IsActorCool(Actor WhatActor);tell the function which actor you're calling the function on
If WhatActor.GetRelationshipRank(Game.GetPlayer()) == 4
;WhatActor likes the player
Return True
Else
Return False
EndFunction
The function IsActorCool will return true if the actor I'm calling it on is the player's lover, and false if they are not. This is a pretty common application of returning functions. (No, not getting if an actor is the player's lover, but a bool function like this is pretty common.) Now I'm going to give an example of a more complex function. This function will enable every ObjectReference in an array if it is called at night time (anywhere between 10 PM and 6 AM), and disable all of the ObjectReferences in the array if it is called at day time (anywhere within the range of 6 AM-10 PM). It returns true if it was day time, and false if it was night time. The most common use for this function would be to turn on and off lights, but it could be applied however you want it:
Bool Function LightSwitch(ObjectReference[] kArray)
Int kIndex = kArray.Length
If GameHour.GetValueInt() >= 22 && GameHour.GetValueInt() < 6; if the time is bigger than or equal to 10 PM and less than 6 - 24 hour clock
While kIndex; while we haven't counted through the entire array
kArray[kIndex].Enable(true); enable the entry in the array, fading in
kIndex -= 1; move onto the next entry
EndWhile
Return False; it was nighttime
Else; time is bigger than or equal to 6, and less than 10 - 24 hour clock
While kIndex; while we haven't counted through the entire array
kArray[kIndex].Disable(true); disable the current entry in the array, fading out
kIndex -= 1; move onto the next entry
EndWhile
Return True; it was day time
EndIf
EndFunction
So hopefully you can see how this works. You call the function, declare the parameter as an ObjectReference array, and the function commences. If the current value of GameHour (a global - you'll have to declare a property for it somewhere else in the script for this function) is more than or equal to 22, which is the equivalent of 10 PM on a 24 hour clock, and if GameHour is less than 6, then it will enable all the lights, fading them in. It does this by cycling through an array. It then returns False, since it was night time. If the global GameHour doesn't meet those conditions, then the lights are disabled, fading out. This is done with the same method as in the other If statement. It then returns True, since it was day time.
Global and Native Functions[edit | edit source]
There are two other tags you can add to functions. The first is "native". It's added like this:
ReturnType FunctionName(Parameters) native
;or, if it's a non-returning function
FunctionName(Parameters) native
A native function means that the function is hard-coded into the game. Functions like GetStage, Kill, and BlockActivation are all native, along with hundreds of others. A native function also means that you can't see the content of the function, because it was hard-coded in and defined elsewhere. The only way to create your own native functions is by making your own SKSE plugin.
Then, there is a global function. A global function is one that can be called from any script, without having to define a property or cast to the script the function is in. For example, EnablePlayerControls is a global function, as is RandomInt. However, you do have to prefix global functions with the name of the script they came from, and a dot when calling them, like so:
;how you call EnablePlayerControls
Game.EnablePlayerControls()
EnablePlayerControls is a global (and native) function in the Game script. Therefore, you have to call EnablePlayerControls in Game to use it. However, you don't have to define a property for the Game script. Alternatively, you can import the owning script of the function, like so:
;how you call EnablePlayerControls, with Game imported
Import Game
EnablePlayerControls()
I don't recommend importing an outside script unless you are constantly using global functions in that script, since it's not really worth it. As far as I know there's no real speed impact or anything like that when importing, I just think it's only necessary to import when you've got several global functions or multiple instances of one global function in your script. The structure of global functions is like so:
ReturnType FunctionName(Parameters) global; you can tack on a native flag after the global, but it wouldn't have a function body then
;function content
EndFunction
Now, there's one important caveat to global functions: All of the variables used in the function must be defined within the global function. You cannot access any properties from the script that declares the global function, or from any other script. Since you cannot declare properties inside functions, you are limited to using variables from within the function and the parameters of the function. So if we made our LightSwitch function from earlier global, it wouldn't work correctly, since it relies on GameHour being defined outside of the function. There are two ways to rectify this:
1. Add GameHour as a parameter you have to define. 2. Use GetFormFromFile within the function. We're not going to get into this. It's not a method I would recommend, given the fact that this is a pretty simple function.
So here's how we would achieve method one - just add a very simple parameter so the function declaration looks like this:
Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameHour) global
Everything else would be the same. Then, when you call the function, it would look something like this:
GlobalVariable Property GameHour Auto; fill this property
ObjectReference[] Property MyObjects Auto; fill this array property
Event OnInit()
LightSwitch(MyObjects[], GameHour)
EndEvent
Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameHour) global
Int kIndex = kArray.Length
If kGameHour.GetValueInt() >= 22 && kGameHour.GetValueInt() < 6; if the time is bigger than or equal to 10 PM and less than 6 - 24 hour clock
While kIndex; while we haven't counted through the entire array
kArray[kIndex].Enable(true); enable the entry in the array, fading in
kIndex -= 1; move onto the next entry
EndWhile
Return False; it was nighttime
Else; time is bigger than or equal to 6, and less than 10 - 24 hour clock
While kIndex; while we haven't counted through the entire array
kArray[kIndex].Disable(true); disable the current entry in the array, fading out
kIndex -= 1; move onto the next entry
EndWhile
Return True; it was day time
EndIf
EndFunction
Function Design Tips[edit | edit source]
I'd like to touch upon one last thing before I finish: It's important to be as all-encompassing with your functions as possible. Properties are great because you can have the same script and have the property values differ for that script depending on what it is attached to. Therefore, doing this:
Quest Property MyQuest Auto
Int Property StageToSet Auto
Event OnActivate()
MyQuest.SetStage(StageToSet)
EndEvent
Is better than doing this:
Quest Property MyQuest Auto
Event OnActivate()
MyQuest.SetStage(10)
EndEvent
Why? Because then, rather than creating dozens of scripts almost identical to the second script with different stages that are being set, you can use one script (the first script) dozens of times with the property StageToSet at a different value for each.
The same applies to functions, especially global ones that you intend to use in lots of places. So, for example, I'd probably change LightSwitch to a far more versatile version that looks like this:
Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameTime, Int LowTime, Int HighTime) global
Int kIndex = kArray.Length
If kGameTime.GetValueInt() >= HighTime && kGameTime.GetValueInt() < LowTime; if the time is bigger than or equal to hightime and less than lowtime - 24 hour clock
While kIndex; while we haven't counted through the entire array
kArray[kIndex].Enable(true); enable the entry in the array, fading in
kIndex -= 1; move onto the next entry
EndWhile
Return False; it was nighttime
Else; time is bigger than or equal to lowtime, and less than hightime - 24 hour clock
While kIndex; while we haven't counted through the entire array
kArray[kIndex].Disable(true); disable the current entry in the array, fading out
kIndex -= 1; move onto the next entry
EndWhile
Return True; it was day time
EndIf
EndFunction
Now, rather than having a set range that the lights or objects will be switched on and off with, or even a set time interval, you can now choose between many different options. You can change the GameHour to GameMonth if you like, or GameYear. Or even any other global, if you felt like it. You could change it to PlayerFollowerCount, though that would be pretty unproductive. And, you can change the hours (or years, or months, etc.) range at which the lights are disabled and enabled. Furthermore, remember that it doesn't have to be lights. Any ObjectReference array will be proccessed.
There's one last thing: You can have default parameters. For example, the Disable function has a default parameter:
Function Disable(bool abFadeOut = False) native
; the parameter checks if you want the object to fade out of existence rather than just blink out
Usually when disabling objects, you simply call Disable on them like so:
MyObject.Disable()
This will automatically execute your code as if you had called the function like this:
MyObject.Disable(false)
It's pretty simple. You just set the variable to a certain value in the parameters like in the above example, and then that will be the default value of that parameter. The structure of the function:
ReturnType FunctionName(Parameter = DefaultValue); tack on the global and native flags here if you want
;if there is no return type
FunctionName(Parameter = DefaultValue)
And remember that not all of the parameters have to have default values, you can decide which ones when you declare the function. Example:
Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameTime, Int LowTime = 6, Int HighTime = 10)
Some Common Functions[edit | edit source]
Here are a few common functions you might want to use (the first two are just random ones that came to mind, not necessarily the most common). If you have a function you'd like to see, just ask and I'll add it here if possible.
Disable Array[edit | edit source]
This is a DisableArray function. It will disable all objects an array of your choice, and fade them out depending on what you input in place of kFadeObject, though by default it won't fade out. It's a global function, though you can delete the global flag, so whichever script you put this function in will be the prefix to the function (i.e. you need to call it like OwningScriptName.DisableArray(MyArray, true) to disable all the objects in MyArray, fading them out.) You could also replace all instances where I used Disable and instead replace it with Enable, if you want this to enable objects rather than disable them.
Function DisableArray(ObjectReference[] kArray, bool kFadeObject = false) global
Int kIndex = kArray.Length
While kIndex
kArray[kIndex].Disable(kFadeObject)
kIndex -= 1
EndWhile
EndFunction
Set Hostile[edit | edit source]
This is a SetHostile function. While you can usually use StartCombat, or SetRelationshipRank, sometimes individually they won't work. So SetHostile will set all of these to something that should make an actor target and attack another actor. This is also a global function, so whichever script you put this function in will be the prefix to the function (i.e. you need to call it like OwningScriptName.SetHostile(Actor1, Actor2) to make Actor1 hostile towards Actor2).
Function SetHostile(Actor kActor, Actor kTarget) global
kActor.StartCombat(kTarget)
kActor.SetRelationshipRank(kTarget, -4)
EndFunction