The problem:
Currently, variables have a few usability problems. I think the solution would be to rework them to be returnable expressions. First of all, though, let’s take a look at some of those problems:
Commons mistakes while using variables
Users often will write code like this to get a variable: ToString(Variable(MyVar))
and not understand why the return value is a number. Since Variable
returns a numerical value, this makes sense to experienced users. To beginners though, all they see is “I take a variable then take its string”.
Duplicate instructions
Every instruction that takes in a variable has currently to be duplicated for each variable scope: Object, Scene, and global. This makes it hard to modify as it has to be changed 3 times when modifying that instruction, tedious for developers as they need to do 3 times more work, tedious for translators that need to translate 3 times almost the same sentence, and of course tedious for users who have to search around 3 times more variable instructions to find the correct one. Wouldn’t it be nicer to be able to specify the scope of the variable in the variable parameter field directly?
Getting a variable result from an action
Some actions use a variable to give out a result, for example, the JSON to Variable action, getting a document/query firebase Firestore, the pop function of the array tools extension…
This is a bit counterintuitive as you usually use an expression for something that gives back a value, but that is not the case with variables, and it is inconvenient when you want to use one child variable. This is like in JavaScript being forced to always do
var myVar = myAction();
otherAction(myVar.x);
Instead of being able to do
otherAction(myAction().x)
My solution:
Variable expressions! The basic of it is, instead of having scope-bound variable parameters where you specify the path to the variable, you obtain the variable and pass it around. For example, instead of such a scene variable specific action:
You would have a single action to modify variables and use an expression to get the variable from the scene:
You could also use a global variable by switching expressions, not actions:
When using in a non-variable field, some
ToNumber
/ToString
expressions would handle the conversion (instead of the current Variable
/VariableString
expressions):
VariableString(MyVar)
→ ToString(SceneVariable("MyVar"))
ToString(GlobalVariable(MyVar))
→ ToString(ToNumber(GlobalVariable("MyVar")))
VariableString(MyVar[0].x)
→ ToString(GlobalVariable("MyVar")[0].x)
Object.Variable(MyVar.a[1+2])
→ ToNumber(Object.Variable("MyVar").a[1+2])
This is more explicit about the type conversions, and also allows using a string expression for the name of a top-level variable, a much-requested feature. Then let me explain again in detail how it helps with each problem above:
Commons mistakes while using variables
As I explained, this adds a layer of explicitness to every conversion and provides a more intuitive flow. With ToString(Variable(MyString))
, it is not intuitive as a new user that it will convert the string value to a number before converting it to a string again, losing the actual text. With the new flow, one can use this intuitive way of writing “Get a text from a variable”: ToString(SceneVariable("MyString"))
, and converting it to a number is explicit disallowing any mistakes due to the unclarity: ToString(ToNumber(SceneVariable("MyString")))
.
Duplicate instructions
As one instruction can accept variables from any source, it would allow getting rid of all duplicate functions. For example, the “Change the value of a scene variable”, “Change the value of a global variable”, and “Change the value of an object variable” could be merged into one single “Change value of a variable” action that takes a variable expression that would define the scope of the variable.
Getting a variable result from an action
GDevelop has already special tools to automatically create a condition with an expression at once. Using those as a base, it should be easy to make another one to create an action and variable expression at once. That would allow creating automatically for actions like “parse variable from JSON” or the ArrayTools pop function an equivalent expression that would return the variable. That way, one could for example do FromJSON("[1,2,3,4,5]")
to create and pass an array to a function, or pop a variable and get its child in a single expression instead of needing a separate action to run before: ArrayTools::Pop(SceneVariable("MyArray")).MyChildVariable
.
This also allows avoiding errors, as if you would have to use a scene variable as temporary between two actions, you have to ensure that you clear it before and after to not accidentally use old data or leak temporary data that could impact other parts using a similarly named temporary variable. If you never need a temporary as you can pass around everything directly, such risks disappear
Problems with this approach
Of course, this approach has a few problems, as nothing can be perfect.
It’s a new way of doing things
The first issue may be obvious, but it’s just not something we had before, so it would take some time for users to get used to that new way of using variables.
It’s leading to breaking changes
While that would not be a breaking change in itself, to really get out all benefits from it, we would need to remove the old scoped variable fields and duplicated instructions/expressions that are using those. This would be a breaking change, making many older resources invalid, and requiring projects to migrate to the new approach.
It’s longer to write
As you may have noticed earlier in the examples comparing this new way with the old way of getting the value of a variable, it is longer and takes more time to write. It’s the cost of explicitness since we have to use an expression to define the scope of a variable each time we use one instead of letting the field parameter determining it for you. It would take a bit of time, but I think that all-in-all considering other productivity benefits it is worth writing a few more characters, especially considering that most use the expression builder that allows not to type anything at all.