Moderator
Joined: Dec 2011
Posts: 5,074
|
2. Variables and variable values
Variables are simple textual names to your eyes, they don't have any graphical form.
The purpose of the variables is to store some value. (I.e. the value is assigned to the textual variable name.)
You can use the value stored in the variable, anywhere in your game, if you simply refer to the textual variable name.
If you are already an advanced or expert TRNG builder, then you surely used some variables there previously. - Let's sum up the most important things about the TRNG variables:
- You need triggers to set a value for a variable: one of the trigger parameter is the variable itself, and the other trigger parameter is that value.
- All the variables initially exist. I.e. they are all there, even if you use only one or a few of them. Or even if you don't use them at all.
So you can choose one of the existing variables if you need a variable, you can't create a custom variable. So if you are just using all the existing variables, then you can't use more variables - except if you first stop using one of these variables for something else.
- Variable names are constant, you can't change them.
The name refers to the general properties of the variable, like eg. "Local", "Delta" or "Short".
- Variable values could be textual or numerical values. Some variables are able to handle only texts, while all the other variables are able to handle only numbers.
- The numerical variable values could be either positive or negative, but always integers.
- None of the variables have a preset task. Feel free to use a textual variable for any textual task or a numerical variable for any numerical task.
- There are some special numerical variables, but the most of them belong to a variable group.
There are six variable groups: Local Alfa, Local Beta, Local Delta, Global Alfa, Global Beta, Global Delta:
- Each group has seven variables. Basically you can choose freely a variable in any group to use, but if you already chose a variable of a group, then perhaps you can't choose another variable of the same group, because the two variables may interfere with each other.
- Local variables keep their values only in their levels. (I.e. each level has its own Local Alfa/Beta/Delta groups.) On the other hand, global variables keep their values in the whole game. (I.e. there is only one Global Alfa/Beta/Delta group in each game.)
- The numerical variables have different sizes: "byte" variables can store only tiny numbers, "short" variable can store even bigger numbers, and "long" variables can store even really huge numbers.
- You can set directly a value in a variable. (Eg. "The variable value is 6.")
Or there are the so-called "memory zone fields". The game automatically stores different values in these fields - eg. the current amount of big medipacks in the inventory. These fields are also existing initially. You can easily identify any field, because each of them has a specific name, referring to its task.
What you can do is to copy the current value of this field into a variable, to set the value of the variable.
- You can do mathematical operations to change the current value of a variable.
Or you can also check this value eg. if it is bigger than the current value of another variable.
- There is a plugin which extends the amount of variables. You can use these "new variables" in a more flexible way, you don't need to follow the strict rules of the "casual" variables for this.
- There are further variables in the engine code, but we don't need to care about them.
And now let's see that how the same things work in TEN:
- Values stored in the variables can be set with trigger parameters. But this time these triggers are nodes in volumes. (Later, in another tutorial, we will discuss these nodes.)
On the other hand, you can set variables directly in the script: first you type the variable name, then you type an equals sign, then you type the value of the variable, eg. X = 6.
- There are no preset variables in TEN. So none of the variables are existing when you start building your level. I.e. if you need a variable, then first you need to create it.
This creation is very easy: the variable will be created in the moment when you set its value. I.e. when you set a variable value in a node, or when you type in the script that X = 6, then in that moment that variable has been created. - I.e. feel free to have as many custom variables as you wish.
If you don't need a custom variable any more, then you can delete it. (You can also use nodes for it. Or, for further methods, see later in the chapter how.)
- Feel free to find out any name for a custom variable, when you create it. (I suggest names which tell nicely what that variable is for.) However, I recommend to examine here, that how developers named preset function arguments, and try to follow the same naming patterns for your variable names.
- Variable values could be still textual or numerical values. (Or they can also have a special value: true or false.)
A variable still can handle only one value type. The type (i.e. text, number, true/false) will be declared in the moment when you set the value for the variable. So eg. if you type 6 after "X =", then it is obvious that it is a variable to store a number.
- The numerical variable values could be either positive or negative - but this time it is not integer everway, it can be a floating number. (You don't need to declare that a custom variable is integer or floating. So eg. when X variable value is 6, and you want a floating value for the variable, then just type in a new script row eg. that X = 9.12, to change X value to this.)
According to my experiences, the amount of digits after the decimal point could be even high.
- Feel free to define any task for your custom variables.
- TEN variables can also be groupped - but these are custom groups, i.e. you can make groups only if you wish. (The method is just like at the custom function groups: declare the group name with a "group_name = {}" script line, so it can be followed by a "group_name.variable_name =" script line for a variable in this group.)
- TEN variables will never interfere with each other.
- TEN variables are also local or global - see later in the chapter that what those mean in this engine.
- TEN variables don't need to declare their size and they have no size limits. So eg. a numerical variable value can be even very-very huge. (However, there are some limits in the amount of digits you use, if you want to print this value nicely on the screen. - But this limit is still very huge, so you don't need to worry about it.)
- The value of a variable naturally could be also set in a direct way in TEN.
On the other hand, we don't talk about memory zone fields in TEN. But there is something similar here. I mean, you could see in that "functions" tutorial that a variable value can be set as the result of a function.
- Naturally in TEN you can also do mathematical operations on variables, or you can also check them here.
- There is no "variable extension" in TEN. (You have so many possibilities, that you don't need an extension, anyway. Besides, TEN does not have any plugins.)
- There are further variables even in the TEN engine code (also including as preset function contents), but we still don't need to care about them.
Let's see some examples to set (i.e. define) the value of "X" variable in TEN script:
X = 6
The variable value is a positive integer number.
X = -122
The variable value is a negative integer number.
X = 52.487
The variable value is a positive floating number. (Note that that is a decimal point, not a comma.)
X = Y
The value of Y variable will be copy/pasted to be the value of X variable.
X = X + 1
The variable value is bigger with 1 than the previous value of the same variable.
X = true
The variable value is a boolean (true or false).
X = OCBValue == 1
The variable value is "true", if the value of OCBValue variable is 1, or "false", if the value of OCBValue variable is not 1.
X = "Hello, Lara!"
The variable value is some text. (Note the quotation marks.) The text don't need to be repeated in any strings LUA file.
X = flame:GetOCB()
The variable value is the result of a function.
X = TEN.Objects.GetMoveableByName("flame_emitter2_117" )
The variable value is an object - as the result of a function, anyway.
X = nil
If a variable can't have a valid value, then the variable will be deleted automatically. - For example:
Code:
-- FILE: Levels\My_level.lua
LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function()
objectSize = (...)
if objectSize < 600 then
X = 8
end
A = X + 46
end
LevelFuncs.OnEnd = function() end
I marked with (...) that objectSize variable means anything now, it doesn't matter. However, objectSize is set inside OnLoop, so its value can be changed, even in each moment of the game loop.
"If" naturally will be checked in each moment of the loop. The "end" inside the loop belongs to "if", which means that all the contents between that "if" and that "end" are something which are checked by the "if". So if objectSize value is less than 600, any time during the loop, then 8 value will be set for X variable - while if it is not true, then won't be any value set for X variable, during the loop.
So, again, the game reads all the OnLoop script lines again and again, in each moment of the game loop. So A = X + 46 line will be also read again and again. If the game reads that line when objectSize is less than 600, then the line will use X=8 variable value, so A will be 8+46=54. However, if the game reads that line when objectSize is equal to or bigger than 600, then the line can't use any X variable value, because X value doesn't exist for this objectSize value, X variable is deleted automatically for this case. - I.e. this is seemingly a bad scripting, variable A value cannot be computed when objectSize >= 600.
"Nil" is not zero, nil means "nothing" - i.e. this is the value of a variable before the creation and after the deletion of the variable.
Forcing this value in a variable is the way to remove the variable manually.
(Note that neither "true", nor "nil" are set in quotation marks for the variable, because these values are technically not texts.)
Text or number
What if you want to print a number on the screen? Eg. the current X value (8 or nil) of the previous example.
Then you can't do it directly. I mean, printing functions can print only texts. So, if you want to print a number on the screen, then you need to interpret it as some text. It will be done for you with "tostring" function:
So eg. if X=8, then tostring will interpret 8 for a printing function as some text:
printX = DisplayString(tostring(X), 250, 250, Color(255,0,0))
ShowString(printX, 10)
But don't misunderstand: "8" or "nil" will be printed, i.e. 8 won't be printed as "eight".
Linking values
If the text you want to print on the screen has some non-constant contents, then you can use the "two dots operation" to link the text parts to each other. - For example (please, note the spaces inside the quotation marks, to also have spaces in the printed text):
A = 5
B = 7
C = 18
D = 100
printtext = DisplayString(tostring(A) .. ", " .. tostring(B) .. " and " .. tostring(C) .. " is less than " .. tostring(D) .. ", but bigger than 3.", 250, 250, Color(255,0,0))
ShowString(printtext, 10)
And now this is what will be printed on the screen:
5, 7 and 18 is less than 100, but bigger than 3.
Local or global variables
In TEN the fact that a variable is global, is not a guarantee that the global variable which you defined in Level A, will be also available in Level B.
I.e. when you say in TEN that a variable is local or global, that has basically a different meaning, compared to TRNG:
- Local variables are available only in their blocks.
- Global variables are available anywhere in the level.
I call a "block" now anything in the script, which is closed with an "end" sign. You have already met some blocks like that in TEN: an initial function (i.e. a callback), a "casual" function, an "if" procedure. - In the example below you can find all the three of them:
Code:
-- FILE: Levels\My_level.lua
function PrintA()
A = 200
printA = DisplayString(tostring(A), 250, 100, Color(255,0,0))
ShowString(printA, 10)
end
function PrintAnyNumber(numberany)
printAny = DisplayString(tostring(numberany), 250, 250, Color(255,0,0))
ShowString(printAny, 10)
end
LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function()
A = 100
PrintA()
if A == 100 then
PrintAnyNumber(A)
end
if A == 200 then
A = 300
PrintAnyNumber(A)
end
end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
This situation prints two values on the screen, when the level starts: one value in (250, 100) position, and another value in (250, 250) position.
The printed values are based in different variations of A variable:
- A = 100 is defined in the OnStart callback block.
- A = 200 is defined in the PrintA function block.
- A = 300 is defined in the "if A == 200" block.
Values printed on the screen (mixed - i.e. some variables are local and some are global - setups are not examined now):
- In the case of all A definitions are local: (250, 100) = 200, (250, 250) = 100.
- In the case of all A definitions are global: (250, 100) = 200, (250, 250) = 300.
Why? Let's see the explanations - first the one for the local cases:
- When the level starts, the game first defines that A=100, for all the script lines of OnStart callback.
- Then PrintA function starts running. First it defines its own A value for this function block (200), which is why A=100 will be overwritten - but only for this block.
- Then the function prints A value in (250, 100) position, so 200 will be printed on the screen.
- Then an "if" checks if A=100. This "if" isn't in PrintA block, but it is in OnStart block, so value 100 for OnStart is the valid A value now.
- So the condition is true, PrintAnyNumber function will start running. The value of the "numberany" argument of the function is A variable value this time, that is why the function prints 100 on the (250, 250) position of the screen.
- Then an "if" checks if A=200. We are still have the A value of OnStart block, which is 100.
- So the condition is false, all the condition contents will be ignored. (Including that A=300 definition.)
And now let's see how it works for global variables:
- When the level starts, the game first defines that A=100, for the whole level.
- Then PrintA function starts running. First it gives another A value (200) for the whole level, which is why A=100 will be overwritten.
- Then the function prints A value in (250, 100) position, so 200 will be printed on the screen.
- Then an "if" checks if A=100. But the current A value is 200 for the level, so the contents of the condition will be ignored.
- Then an "if" checks if A=200. The value is still 200, so the condition is true - which is why a new A value (300) will be defined for the whole level.
- PrintAnyNumber function will start running. The value of the "numberany" argument of the function is A variable value this time, that is why the function prints 300 on the (250, 250) position of the screen.
But how will the game know that a variable is local or global? - Well:
- A variable will be local if you label its definition as "local".
- A variable will be global if you won't label its definition at all.
So the example above is only true in the case when all the variables are global.
Why? Because none of the variable definitions are labelled there.
So in the case when all the variables are local, all of their definitions should be labelled as "local":
Code:
-- FILE: Levels\My_level.lua
function PrintA()
local A = 200
printA = DisplayString(tostring(A), 250, 100, Color(255,0,0))
ShowString(printA, 10)
end
function PrintAnyNumber(numberany)
printAny = DisplayString(tostring(numberany), 250, 250, Color(255,0,0))
ShowString(printAny, 10)
end
LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function()
local A = 100
PrintA()
if A == 100 then
PrintAnyNumber(A)
end
if A == 200 then
local A = 300
PrintAnyNumber(A)
end
end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
Saved variable values
When you save a savegame, and then load it, then will the variable value be restored from it? I.e. will variables be saved in savegames?
Well, you don't need to worry about it, in the case of continuous variable definitions. I mean, see the example above when the value of Z variable in OnLoop callback is the current value of Lara's animation index. I.e. when you load a savegame, then you don't need to restore the value of this variable from the savegame, because Z variable will get the current index immediately from the current playtime.
But the situation is different with simple variable definitions like X = 8.
Let's suppose you define X = 8 global variable value in OnStart callback, i.e. when the level starts. Then you start playing. The variable keeps its value, because it is global. After that you save the game, and then load that savegame. - Bad surprise! The variable has been automatically deleted.
I.e. variable values usually won't be saved in savegames. (Either they are local or global, anyway.)
You can have some tricks to prevent that:
- Type another X = 8 definition in OnLoad callback, to restore the value when the level has just been loaded.
But it is really boring and tiresome.
Besides, if you changed the variable value in the meantime, then X = 8 will restore an obsolete value.
- Type the variable definitions always in OnLoop callback. So each moment of the playtime automatically sets this value again and again - including the moments just after a savegame having been loaded.
But, as I said above, usually it is a bit odd to place simple variable definitions in the game loop.
Besides, now you need to find out some tricky conditions, when you want to change this value, otherwise the loop keeps restoring 8 value for X variable.
The real solution is:
Use variables having "LevelVars" or "GameVars" tags in their name. - For example:
LevelVars.objectSize = 8
GameVars.FlipMapNumber = 16
LevelVars and GameVars variables are special global variables, because their value will be automatically saved when you save a savegame, and will be automatically restored when you load that savegame.
Variable values transferred to other levels
The values of local, "casual" global or LevelVars variables cannot be transferred to other levels.
That is why the difference between LevelVars and GameVars variables is important:
- A LevelVars variable is valid only on its level.
- A GameVars variable is valid on its level, plus it will be transferred to the next level, whereto a FinishLevel trigger (or a similar volume/scripted event) loads Lara.
So, for example, if you define a value for GameVars.X variable on Level A, and then Lara will be loaded to Level B, then feel free to do an operation with GameVars.X in Level B script. I.e. you don't need to define the value for GameVars.X variable in Level B script, because the value will be used which was defined in Level A script.
----------
Notes:
- Function arguments are not variables. I mean, eg. in a FunctionX(object) syntax, the argument name "object" is important only to identify that argument for the function contents.
But when you run this function eg. as FunctionX(A), that means that the value of "object" argument is the value of A variable.
- If the variable value is an object (or anything else, which can be exactly identified, eg. a room), then that value is not a number or a text.
So eg. if you want to print this X variable value on the screen:
flame = TEN.Objects.GetMoveableByName("flame_emitter_1" )
printflame = DisplayString(tostring(flame), 250, 250, Color(255,0,0))
ShowString(printflame, 10)
then that technically will look like some text - but it is clear that it is not a text which is useable by a level builder. Something similar like this:
sol.sol::detail::unique_usertype<Moveable>: 0000013F47299778
Instead, try to to print eg. the name ("flame_emitter_1") of the object:
flame = TEN.Objects.GetMoveableByName("flame_emitter_1" )
printflame = DisplayString(flame:GetName(), 250, 250, Color(255,0,0))
ShowString(printflame, 10)
- This condition naturally means "if X variable doesn't exist...":
if X == nil then
- As you can see, in TEN script you can use even non-TEN specific functions, like tostring.
I.e. not all the preset functions are made by the developers, there are ones which are initial LUA functions.
I will introduce further initial LUA functions in my TEN script tutorials. But feel free to browse the web to identify any initial LUA functions. (Naturally it is recommended only for the ones who are already advanced or expert in TEN scripting.)
However, you don't need to follow any name hierarchy (as I told it for developer-made TEN preset functions), just use the initial TEN function syntax as it is introduced to you.
- Tostring function can interpret characters as texts, even if the characters are not numbers. See eg. the "true" boolean.
If you don't know the type you use for a printing, then use tostring everyway. I mean, eg. in the case of tostring(a) the function will print nicely even if the actual value of a variable is some text (i.e. when you don't need tostring), not a number.
- If you want to print the current value of a TEN constant on the screen, then the constant will be printed with the numerical ID value which belongs to that constant. - For example, you print the current state of the fall in the level:
printtext = DisplayString(tostring(GetCurrentLevel().weather), 250, 350, Color(255,0,0))
Possible values are:
- There is no falling. Constant name: WeatherType.None. Constant value: 0.
- There is raining. Constant name: WeatherType.Rain. Constant value: 1.
- There is snowing. Constant name: WeatherType.Snow. Constant value: 2.
In my tutorials I won't care about TEN constant numerical values.
- The linking feature can be used even instead of tostring function.
Just indicate with empty quotation marks that you want to show some texts there, then stuff some contents between these empty quotation marks, adding (linking) eg. some number to them. - For example:
printtext = DisplayString("" .. A .. ", " .. B .. " and " .. C .. " is less than " .. D .. ", but bigger than 3.", 250, 250, Color(255,0,0))
As you can see, the quotation marks don't need to be empty, if you want to link the number to some texts.
Besides, now you can see even something else - that in the example above with the many tostrings, only the first tostring is necessary (because that is not linked to any text), so you can simply type the variables in the other cases:
printtext = DisplayString(tostring(A) .. ", " .. B .. " and " .. C .. " is less than " .. D .. ", but bigger than 3.", 250, 250, Color(255,0,0))
Even if you type the variable value in the place of the variable:
printtext = DisplayString(tostring(A) .. ", " .. 7 .. " and " .. 18 .. " is less than " .. 100 .. ", but bigger than 3.", 250, 250, Color(255,0,0))
(According to my experiences, in some cases perhaps only tostring or only "" works nicely to start the first printable value. And sometimes you cannot avoid tostring for the second, third etc. printable values.)
- Be careful with the complex using of variables, like the example above.
I mean, if eg. A variable cannot be printed for some reason, then the whole text cannot be printed, even if everything is okay with B, C and D variables.
- You can type variables even in a part of the level script, where there are no blocks: it is naturally the part of the script out of any callbacks.
So if you type a variable there (but out of any custom block), then it could be either local or global, because if it is local, then its "block" means the whole level script. I.e. it works just like a global one now.
(For technical reasons, I recommend to use local variables in these cases.)
- Please notice that the label of local should be typed only when you set the first value or a new value for a local variable. But if you call it for an action or check it in a condition, then you shouldn't do it.
- In the case of groupped local variables, the label of local should be typed at the group name, ignoring it at the variable definition:
local object = {}
object.objectSize = 1024
- LevelVars and GameVars variables can also be groupped, the same way as the other variables - for example:
LevelVars.object = {}
GameVars.FlipMap = {}
LevelVars.object.objectSize = 8
GameVars.FlipMap.FlipMapNumber = 16
(Like in the case of custom function group names, "Engine" group name of LevelVars/GameVars is reserved for the developers.)
- LevelVars values will be deleted when you leave the level.
So when you want to keep the value of a variable for the time when you come back to this level, then you should use GameVars for that variable.
- Unfortunately, I discovered two important GameVars bugs:
- It seems that if you once set a GameVars variable value in a level (either setting it there, or transferring it from another level), then you cannot transfer another value of the same variable to that level. (But you can still set another value for that variable in that level.)
For example: Lara is on Level A, where GameVars.X = 8 is set. She is loaded to Level B, transferring GameVars.X = 8 value there. Later she is loaded back to Level A. GameVars.X here gets another value: 7. Now she goes back to Level B again - but X remains 8 there. However, while she is in Level B, GameVars.X can get another value there. Even 7, for example.
- GameVars values cannot be transferred to the title. (Perhaps it is the same issue I told above at EndReason constants.)
So I don't recommend to try to transfer GameVars values to levels whereto Lara is just going back, or to title.
- And one more thing about GameVars - and also LevelVars. Not a bug, only some limitation!
If the value of the LevelVars/GameVars variable is an identified thing (see above what that means: eg. an object), then that cannot work well with GameVars/LevelVars. For example: an action with that variable value (eg. to spawn a baddy) won't be executed. Or the variable works like it as if it were not be LevelVars/GameVars: i.e. it will loose its value after loading a savegame.
Eg. this can be wrong:
LevelVars.Flames = GetMoveableByName("flame_emitter_1")
LevelVars.Flames:Enable()
The solutions:
- In these cases, don't use the variable as a LevelVars/GameVars.
- Or store only the identifier (eg. the name) in the variable, identifying the thing by this variable value out of the variable:
LevelVars.Flames = "flame_emitter_1"
GetMoveableByName(LevelVars.Flames):Enable()
- In the case when you want to use variables in other LUA files, not in script files, then seemingly you can do it. For example, in gameflow.lua you want to set the same value for a parameter of your levels, then set it as a variable. So, when you change the variable value, then it will change at all the levels, right away.
You can declare it either local or global, because the block for this variable is seemingly is used as the whole LUA file. (Mostly that you won't use other blocks there, eg. for an "if".) - So level "blocks" of gameflow.lua are not blocks in this meaning.
On the other hand, it is meaningless to use GameVars here, because GameVars variables will transfer values only between level script files. (And naturally it is also meaningless to use LevelVars variables here, because these LUA files cannot understand what saving/loading a savegame is.)
Last edited by AkyV; 19-11-24 at 20:49.
|