Comprehensive Tutorial on Custom Functions: Difference between revisions

Jump to navigation Jump to search
m
Convert source tags to syntaxhighlight
imported>Matthiaswagg
No edit summary
m (Convert source tags to syntaxhighlight)
 
Line 8: Line 8:


Essentially, functions are processes that you declare, enclosed within function tags, like so:
Essentially, functions are processes that you declare, enclosed within function tags, like so:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Function MyFunctionName(Parameters)
Function MyFunctionName(Parameters)


Line 14: Line 14:


EndFunction
EndFunction
</source>
</syntaxhighlight>
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Event OnDeath(Actor akKiller); akKiller is pre-defined as the actor who killed the actor who died
Event OnDeath(Actor akKiller); akKiller is pre-defined as the actor who killed the actor who died


Line 26: Line 26:


EndEvent
EndEvent
</source>
</syntaxhighlight>
Where's function parameters usually have to be defined when calling the function, like so:
Where's function parameters usually have to be defined when calling the function, like so:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Event OnInit()
Event OnInit()


Line 40: Line 40:


EndFunction
EndFunction
</source>
</syntaxhighlight>


Now lets talk about creating your own functions.
Now lets talk about creating your own functions.
Line 46: Line 46:
= Basic Custom Functions =
= Basic Custom Functions =
As you may have noticed earlier, function declarations are fairly simple. The structure looks like this:
As you may have noticed earlier, function declarations are fairly simple. The structure looks like this:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Function FunctionName(Type Parameter)
Function FunctionName(Type Parameter)


Line 52: Line 52:


EndFunction
EndFunction
</source>
</syntaxhighlight>
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
FunctionName(Parameter)
FunctionName(Parameter)
</source>
</syntaxhighlight>
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Scriptname MyFuncScript Extends Quest; or whatever extension
Scriptname MyFuncScript Extends Quest; or whatever extension


Line 74: Line 74:


EndFunction
EndFunction
</source>
</syntaxhighlight>
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Scriptname ThatScript Extends Quest
Scriptname ThatScript Extends Quest
{This script is soo much longer than it should be.}
{This script is soo much longer than it should be.}
Line 125: Line 125:


EndEvent
EndEvent
</source>
</syntaxhighlight>
You can just add a function to the script to shorten it significantly:
You can just add a function to the script to shorten it significantly:
<source lang="Papyrus">Scriptname ThatOtherScript Extends Quest
<syntaxhighlight lang="Papyrus">Scriptname ThatOtherScript Extends Quest


Int Count
Int Count
Line 165: Line 165:


EndFunction
EndFunction
</source>
</syntaxhighlight>
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Scriptname ThatBetterScript Extends Quest
Scriptname ThatBetterScript Extends Quest


Line 227: Line 227:


EndFunction
EndFunction
</source>
</syntaxhighlight>
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.  
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 =
= Returning Functions =
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
ReturnType Function FunctionName(Parameters)
ReturnType Function FunctionName(Parameters)


Line 238: Line 238:


EndFunction
EndFunction
</source>
</syntaxhighlight>
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Bool Function IsActorCool(Actor WhatActor);tell the function which actor you're calling the function on  
Bool Function IsActorCool(Actor WhatActor);tell the function which actor you're calling the function on  


Line 253: Line 253:


EndFunction
EndFunction
</source>
</syntaxhighlight>
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Bool Function LightSwitch(ObjectReference[] kArray)
Bool Function LightSwitch(ObjectReference[] kArray)


Line 285: Line 285:


EndFunction   
EndFunction   
</source>
</syntaxhighlight>
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.
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 =
= Global and Native Functions =
There are two other tags you can add to functions. The first is "native". It's added like this:
There are two other tags you can add to functions. The first is "native". It's added like this:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
ReturnType FunctionName(Parameters) native
ReturnType FunctionName(Parameters) native


Line 296: Line 296:


FunctionName(Parameters) native
FunctionName(Parameters) native
</source>
</syntaxhighlight>
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.
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
;how you call EnablePlayerControls
;how you call EnablePlayerControls


Game.EnablePlayerControls()
Game.EnablePlayerControls()
</source>
</syntaxhighlight>
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
;how you call EnablePlayerControls, with Game imported
;how you call EnablePlayerControls, with Game imported
Import Game
Import Game


EnablePlayerControls()
EnablePlayerControls()
</source>
</syntaxhighlight>
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
ReturnType FunctionName(Parameters) global; you can tack on a native flag after the global, but it wouldn't have a function body then
ReturnType FunctionName(Parameters) global; you can tack on a native flag after the global, but it wouldn't have a function body then


Line 319: Line 319:


EndFunction
EndFunction
</source>
</syntaxhighlight>
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:
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:


Line 327: Line 327:


So here's how we would achieve method one - just add a very simple parameter so the function declaration looks like this:
So here's how we would achieve method one - just add a very simple parameter so the function declaration looks like this:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameHour) global
Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameHour) global
</source>
</syntaxhighlight>
Everything else would be the same. Then, when you call the function, it would look something like this:
Everything else would be the same. Then, when you call the function, it would look something like this:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
GlobalVariable Property GameHour Auto; fill this property
GlobalVariable Property GameHour Auto; fill this property
ObjectReference[] Property MyObjects Auto; fill this array property
ObjectReference[] Property MyObjects Auto; fill this array property
Line 370: Line 370:


EndFunction   
EndFunction   
</source>
</syntaxhighlight>


= Function Design Tips =
= Function Design Tips =
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Quest Property MyQuest Auto
Quest Property MyQuest Auto
Int Property StageToSet Auto
Int Property StageToSet Auto
Line 383: Line 383:


EndEvent
EndEvent
</source>
</syntaxhighlight>
Is better than doing this:
Is better than doing this:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Quest Property MyQuest Auto
Quest Property MyQuest Auto


Line 393: Line 393:


EndEvent
EndEvent
</source>
</syntaxhighlight>
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.
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameTime, Int LowTime, Int HighTime) global
Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameTime, Int LowTime, Int HighTime) global


Line 427: Line 427:


EndFunction   
EndFunction   
</source>
</syntaxhighlight>
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.
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:
There's one last thing: You can have default parameters. For example, the Disable function has a default parameter:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Function Disable(bool abFadeOut = False) native
Function Disable(bool abFadeOut = False) native


; the parameter checks if you want the object to fade out of existence rather than just blink out
; the parameter checks if you want the object to fade out of existence rather than just blink out
</source>
</syntaxhighlight>
Usually when disabling objects, you simply call Disable on them like so:
Usually when disabling objects, you simply call Disable on them like so:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
MyObject.Disable()
MyObject.Disable()
</source>
</syntaxhighlight>
This will automatically execute your code as if you had called the function like this:
This will automatically execute your code as if you had called the function like this:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
MyObject.Disable(false)
MyObject.Disable(false)
</source>
</syntaxhighlight>
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:
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:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
ReturnType FunctionName(Parameter = DefaultValue); tack on the global and native flags here if you want
ReturnType FunctionName(Parameter = DefaultValue); tack on the global and native flags here if you want


Line 451: Line 451:


FunctionName(Parameter = DefaultValue)
FunctionName(Parameter = DefaultValue)
</source>
</syntaxhighlight>
And remember that not all of the parameters have to have default values, you can decide which ones when you declare the function. Example:
And remember that not all of the parameters have to have default values, you can decide which ones when you declare the function. Example:
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameTime, Int LowTime = 6, Int HighTime = 10)
Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameTime, Int LowTime = 6, Int HighTime = 10)
</source>
</syntaxhighlight>


= Some Common Functions =
= Some Common Functions =
Line 462: Line 462:
== Disable Array ==
== Disable Array ==
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.
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.
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Function DisableArray(ObjectReference[] kArray, bool kFadeObject = false) global
Function DisableArray(ObjectReference[] kArray, bool kFadeObject = false) global


Line 475: Line 475:


EndFunction
EndFunction
</source>
</syntaxhighlight>


== Set Hostile ==
== Set Hostile ==
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).
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).
<source lang="Papyrus">
<syntaxhighlight lang="Papyrus">
Function SetHostile(Actor kActor, Actor kTarget) global
Function SetHostile(Actor kActor, Actor kTarget) global


Line 486: Line 486:


EndFunction  
EndFunction  
</source>
</syntaxhighlight>

Navigation menu