Reworking variables to be returnable by expressions

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.

2 Likes

An expression or by the editor, we have almost the same idea.

1 Like

Well allowing to select the scope of a variable is one of the advantages but not the advantage, I have stated other reasons why i think those would be great. We have all been discussing a parameter field for all variables scopes for some time with 4ian as well, but this is the first clear & complete specification of a way to do that. A selector UI would not truly work as it wouldn’t allow using variables in expressions parameters.

1 Like

I think this is a good idea, and I think it doesn’t have to be a breaking change.

Just like Object.X(“Pointname”) and Object.PointX(“Pointname”) work, I don’t see any reason why both the current expressions and the new expressions can’t exist at the same time.

It doesn’t has to in the short term, but keeping both options will be confusing to new users, defeating the purpose of being easier to understand, and in the long run variable references should ultimately replace that “old way” of using variables. Just like physics 1 can coexist with physics 2 but the later is meant to replace the former in the long run.

Oh absolutely. I would hope eventually the music system would be phased out/depreciated the same way.

1 Like