Guide to the Expression Editor
tatiang
Member, Sous Chef, PRO, Senior Sous-Chef Posts: 11,949
One of my clients asked me how people figure out how to come up with expressions in GameSalad such as 768+(1/2)*self.Size.Width. Is there some guide they follow? Well as far as I can tell there is no guide, so this is my attempt to make one.
First, the short answer: there are three ways that people figure out expressions: (1) Copycatting - using someone else's expression and making small changes to see how it affects the actor. This is generally how I approach trig functions such as sin & cos because I have only a very basic understanding of them. This tool is a good example of a way to tinker with a trig expression; (2) Trial & Error - this is my favorite method because well, I like math, and I like a good challenge. If I know what I want to accomplish (e.g. keep the actor within the boundaries of the screen), I will try out different expressions until I hit upon the correct one. Sometimes this is quick and sometimes it takes hours or days of thinking and calculating and trying and making mistakes before it works; (3) Asking for help - the forums are a great equalizer. If you know what you want to do but not how to do it, ask!
And now the long answer: okay, first an explanation of the expression editor. Here's what the expression editor looks like (it's accessed from that little 'e' symbol in behaviors such as Change Attribute and Constrain Attribute, but also in many other behaviors):
The expression editor is how GameSalad incorporates mathematical functions*. These functions can be as straightforward as 3+4 or they can be as complex as a recent one I created 176+self.Size.Width*floor(log10(game.currentScore))-(log10(self.divisor)-1)). If you're curious about that one, see this thread for details.
You're probably thinking "why would I ever put 3+4 into the expression editor?" And you might not. But here's an example of when I use a simple expression like that. Let's say that I want to place an actor on the edge of the screen. I could just drag it into place and hope it's in the right spot or I could manually edit its position attributes in the attribute inspector for that actor, but if I want to automate it a bit and save myself time, I can use the expression editor. I know that an iPad Portrait scene is 768 pixels wide. If I do this:
And preview the scene, the actor gets cut off at the edge of the scene:
The reason for this is that GameSalad calculates the position of an actor from its center. If an actor is 100x40 pixels, then its x position is 50 pixels (half of 100) from the right or left edge of the actor. So placing an actor at x=768 means that its center is at the edge of the screen. Half of the actor is on the screen and half of it is off. To fix this, I have to move the actor half of its width to the left. So I can put 718 (768-50) in the expression editor and it will work correctly (although in that case I don't actually need the expression editor since it's just a single value):
I like to keep my coding as clear as possible so I would actually put 768-50 or 768-100/2 into the expression editor instead:
That way when I come back to look at the code, I will be able to quickly understand what I've done. If I see 718, I'm initially confused and have to work backwards to figure out where that came from (although the value of the Note behavior cannot be overstated). But what if I decide to resize the actor so that it is 75x30 pixels? I then have to go back in and change the expression to 730.5 or 768-37.5 or 768-75/2. There has to be a better way! And there is. I would enter 768- and then add self.Size.Width from the attribute drop-down menu (attributes MUST be selected in this way and cannot be typed in directly) and then type in /2 for divide by two, leaving me with this:
[As an aside, if you ever need to take a screenshot of a behavior that includes an expression, make sure to click the 'e' first to display the full expression. You can even move the expression window that pops up to position it better for the screenshot.]
No matter what the width of the actor is, that expression will place it on the edge of the scene. Try it for yourself! You can make the width 205.7168 if you like. Because it's based in math, it will always place the actor at the edge of the screen minus half of its width.
Let's take it one step further in terms of complexity. Sometimes it's useful to be able to keep an actor from falling off of the edge of the screen while allowing the player to drag the actor. The basic rule for dragging an actor is:
(The attribute game.Mouse.Position.X can be found in the Devices section of the attributes drop-down menu, which is accessed by clicking the 'e' and then clicking the triangle right below the expression editor's text entry area.)
But if you try this, you'll quickly learn that the actor can be dragged beyond the edges of the scene. I want the actor to stop at the edge and I should be able to use what I learned above. But I need an additional function: min() or max(). These two functions and many others can be found in the functions drop-down menu:
Most, if not all, of the functions in GameSalad are based in traditional mathematical functions used outside of game development. To learn more about a particular function, see the Cookbook definitions or just google something like mod function, which returns this Wikipedia entry:
But let's get back to what I was doing. The min() function returns the minimum value of two inputs. So min(3,18) = 3 and min(-22,0) = -22. The max() function returns the maximum value of two inputs. Note that inputs can be attributes, functions, or numbers. To keep the actor from falling off the screen, I need to make its maximum x position equal to 768-self.Size.Width/2 (look familiar?). I can do this by constraining the actor's x position as follows:
(Note that functions can either be selected from the drop-down menu or typed in directly... your choice! Just make sure to count your parentheses because you must always have an equal number of left-facing and right-facing parentheses, just like in this note).
The Constrain Attribute behavior keeps the attribute that value indefinitely, whereas the Change Attribute behavior changes the value once and allows it to be changed again later. So the above expression will constrain/keep the actor's self.Position.X attribute equal to the smaller value of the current mouse position or the edge of the scene (minus half of the actor's width). If the mouse position is greater than 768-self.Size.Width/2, the actor will remain there. If the mouse position is less than 768-self.Size.Width/2, the actor will move to whatever the mouse position is. But that's only half of the solution. If I drag the actor to the left side of the screen, there is no constraint in place and the actor will fall off of the edge of the screen. If I want to prevent this, I can do:
In this case, I am constraining the x position to the left edge of the scene (x=0) plus half of the actor's width.
So I can have those two Constrain Attribute behaviors in place and they should keep my actor on the screen. But unfortunately, in practice that doesn't work. The two constraints work independently and the result is that only one seems to constrain at any given time. So I need to combine them into one function. Plus, I like to simplify rules and behaviors as much as possible (and by simplify I don't mean "keep it simple and easy" but rather "keep it concise"):
That may seem complex because... it is! I've placed one function, min inside of another function, max. To evaluate this expression, I have to start with the innermost function, the min function. That function starts to the right of the first right-facing parenthesis and ends at the first left-facing parenthesis. This function returns the smaller value of the mouse position and the right edge of the screen. But as I move the actor/mouse to the left edge of the screen, the smallest value of that function is going to be wherever the mouse position is (e.g. 200, 100, 0, -100, etc.; it's not constrained yet). Next, I evaluate the outermost function, the max function. This returns the smaller value of the innermost function (which is somewhere between the mouse position and the right edge of the screen) and the left edge of the screen (0+self.Size.Width/2). So wherever the mouse position is, it's going to be no further left than the left edge of the screen (plus half of the actor's width).
In conclusion: I took you through my thought process for creating one complex expression. You may be thinking, "but I wasted half an hour of my time reading through that wall of text and all I can do is constrain the actor's x position to the boundaries of the screen!" And you would be right. My intention wasn't to teach you everything there is to know about using the expression editor. That would be the equivalent of teaching you all there is to know about math. But hopefully you have a grasp of how the expression editor can be used to build expressions based on what you decide you need for your game.
*I focused on mathematical functions but the expression editor can also be used for text expressions (e.g. "Hello and welcome, "..game.Name; two periods in a row will contactentate or join text strings and attributes; use option/alt+space to display a space within quotes) and even certain logical expressions (e.g. NOT game.boolean). Those are outside of the scope of this post but perhaps I will tackle them in a later post.
First, the short answer: there are three ways that people figure out expressions: (1) Copycatting - using someone else's expression and making small changes to see how it affects the actor. This is generally how I approach trig functions such as sin & cos because I have only a very basic understanding of them. This tool is a good example of a way to tinker with a trig expression; (2) Trial & Error - this is my favorite method because well, I like math, and I like a good challenge. If I know what I want to accomplish (e.g. keep the actor within the boundaries of the screen), I will try out different expressions until I hit upon the correct one. Sometimes this is quick and sometimes it takes hours or days of thinking and calculating and trying and making mistakes before it works; (3) Asking for help - the forums are a great equalizer. If you know what you want to do but not how to do it, ask!
And now the long answer: okay, first an explanation of the expression editor. Here's what the expression editor looks like (it's accessed from that little 'e' symbol in behaviors such as Change Attribute and Constrain Attribute, but also in many other behaviors):
The expression editor is how GameSalad incorporates mathematical functions*. These functions can be as straightforward as 3+4 or they can be as complex as a recent one I created 176+self.Size.Width*floor(log10(game.currentScore))-(log10(self.divisor)-1)). If you're curious about that one, see this thread for details.
You're probably thinking "why would I ever put 3+4 into the expression editor?" And you might not. But here's an example of when I use a simple expression like that. Let's say that I want to place an actor on the edge of the screen. I could just drag it into place and hope it's in the right spot or I could manually edit its position attributes in the attribute inspector for that actor, but if I want to automate it a bit and save myself time, I can use the expression editor. I know that an iPad Portrait scene is 768 pixels wide. If I do this:
And preview the scene, the actor gets cut off at the edge of the scene:
The reason for this is that GameSalad calculates the position of an actor from its center. If an actor is 100x40 pixels, then its x position is 50 pixels (half of 100) from the right or left edge of the actor. So placing an actor at x=768 means that its center is at the edge of the screen. Half of the actor is on the screen and half of it is off. To fix this, I have to move the actor half of its width to the left. So I can put 718 (768-50) in the expression editor and it will work correctly (although in that case I don't actually need the expression editor since it's just a single value):
I like to keep my coding as clear as possible so I would actually put 768-50 or 768-100/2 into the expression editor instead:
That way when I come back to look at the code, I will be able to quickly understand what I've done. If I see 718, I'm initially confused and have to work backwards to figure out where that came from (although the value of the Note behavior cannot be overstated). But what if I decide to resize the actor so that it is 75x30 pixels? I then have to go back in and change the expression to 730.5 or 768-37.5 or 768-75/2. There has to be a better way! And there is. I would enter 768- and then add self.Size.Width from the attribute drop-down menu (attributes MUST be selected in this way and cannot be typed in directly) and then type in /2 for divide by two, leaving me with this:
[As an aside, if you ever need to take a screenshot of a behavior that includes an expression, make sure to click the 'e' first to display the full expression. You can even move the expression window that pops up to position it better for the screenshot.]
No matter what the width of the actor is, that expression will place it on the edge of the scene. Try it for yourself! You can make the width 205.7168 if you like. Because it's based in math, it will always place the actor at the edge of the screen minus half of its width.
Let's take it one step further in terms of complexity. Sometimes it's useful to be able to keep an actor from falling off of the edge of the screen while allowing the player to drag the actor. The basic rule for dragging an actor is:
(The attribute game.Mouse.Position.X can be found in the Devices section of the attributes drop-down menu, which is accessed by clicking the 'e' and then clicking the triangle right below the expression editor's text entry area.)
But if you try this, you'll quickly learn that the actor can be dragged beyond the edges of the scene. I want the actor to stop at the edge and I should be able to use what I learned above. But I need an additional function: min() or max(). These two functions and many others can be found in the functions drop-down menu:
Most, if not all, of the functions in GameSalad are based in traditional mathematical functions used outside of game development. To learn more about a particular function, see the Cookbook definitions or just google something like mod function, which returns this Wikipedia entry:
In computing, the modulo (sometimes called modulus) operation finds the remainder of division of one number by another.Adding gamesalad to the search term can result in much more useful information such as tutorials, demos, and videos:
Given two positive numbers, a (the dividend) and n (the divisor), a modulo n (abbreviated as a mod n) is the remainder of the Euclidean division of a by n. For instance, the expression "5 mod 2" would evaluate to 1 because 5 divided by 2 leaves a quotient of 2 and a remainder of 1, while "9 mod 3" would evaluate to 0 because the division of 9 by 3 has a quotient of 3 and leaves a remainder of 0; there is nothing to subtract from 9 after multiplying 3 times 3. (Notice that doing the division with a calculator won't show you the result referred to here by this operation, the quotient will be expressed as a decimal fraction.)
But let's get back to what I was doing. The min() function returns the minimum value of two inputs. So min(3,18) = 3 and min(-22,0) = -22. The max() function returns the maximum value of two inputs. Note that inputs can be attributes, functions, or numbers. To keep the actor from falling off the screen, I need to make its maximum x position equal to 768-self.Size.Width/2 (look familiar?). I can do this by constraining the actor's x position as follows:
(Note that functions can either be selected from the drop-down menu or typed in directly... your choice! Just make sure to count your parentheses because you must always have an equal number of left-facing and right-facing parentheses, just like in this note).
The Constrain Attribute behavior keeps the attribute that value indefinitely, whereas the Change Attribute behavior changes the value once and allows it to be changed again later. So the above expression will constrain/keep the actor's self.Position.X attribute equal to the smaller value of the current mouse position or the edge of the scene (minus half of the actor's width). If the mouse position is greater than 768-self.Size.Width/2, the actor will remain there. If the mouse position is less than 768-self.Size.Width/2, the actor will move to whatever the mouse position is. But that's only half of the solution. If I drag the actor to the left side of the screen, there is no constraint in place and the actor will fall off of the edge of the screen. If I want to prevent this, I can do:
In this case, I am constraining the x position to the left edge of the scene (x=0) plus half of the actor's width.
So I can have those two Constrain Attribute behaviors in place and they should keep my actor on the screen. But unfortunately, in practice that doesn't work. The two constraints work independently and the result is that only one seems to constrain at any given time. So I need to combine them into one function. Plus, I like to simplify rules and behaviors as much as possible (and by simplify I don't mean "keep it simple and easy" but rather "keep it concise"):
That may seem complex because... it is! I've placed one function, min inside of another function, max. To evaluate this expression, I have to start with the innermost function, the min function. That function starts to the right of the first right-facing parenthesis and ends at the first left-facing parenthesis. This function returns the smaller value of the mouse position and the right edge of the screen. But as I move the actor/mouse to the left edge of the screen, the smallest value of that function is going to be wherever the mouse position is (e.g. 200, 100, 0, -100, etc.; it's not constrained yet). Next, I evaluate the outermost function, the max function. This returns the smaller value of the innermost function (which is somewhere between the mouse position and the right edge of the screen) and the left edge of the screen (0+self.Size.Width/2). So wherever the mouse position is, it's going to be no further left than the left edge of the screen (plus half of the actor's width).
In conclusion: I took you through my thought process for creating one complex expression. You may be thinking, "but I wasted half an hour of my time reading through that wall of text and all I can do is constrain the actor's x position to the boundaries of the screen!" And you would be right. My intention wasn't to teach you everything there is to know about using the expression editor. That would be the equivalent of teaching you all there is to know about math. But hopefully you have a grasp of how the expression editor can be used to build expressions based on what you decide you need for your game.
*I focused on mathematical functions but the expression editor can also be used for text expressions (e.g. "Hello and welcome, "..game.Name; two periods in a row will contactentate or join text strings and attributes; use option/alt+space to display a space within quotes) and even certain logical expressions (e.g. NOT game.boolean). Those are outside of the scope of this post but perhaps I will tackle them in a later post.
New to GameSalad? (FAQs) | Tutorials | Templates | Greenleaf Games | Educator & Certified GameSalad User
Comments
✮ FREE templates at GSinvention ✮
✮ Available for hire! [email protected] ✮
Excellent post/article/tutorial, @tatiang. :-)
""You are in a maze of twisty passages, all alike." - Zork temp domain http://spidergriffin.wix.com/alphaghostapps
My GameSalad Academy Courses! ◦ Check out my quality templates! ◦ Add me on Skype: braydon_sfx
Definitely deserves to be pinned!
One thing I think would be very beneficial to add would be an explanation of %.
Contact me for custom work - Expert GS developer with 15 years of GS experience - Skype: armelline.support
Send and Receive Data using your own Server Tutorial! | Vote for A Long Way Home on Steam Greenlight! | Ten Years Left
@Armelline The mod() function which can also be written as % can be confusing but I didn't want to include too much that wasn't directly related to the example of setting scene boundaries and the topic of the expression editor in general.
The best place to start learning about the mod() function is actually by searching or clicking on the links I provided above. But if you have specific questions, feel free to start a new thread. I'd be happy to explain further in a separate thread.
New to GameSalad? (FAQs) | Tutorials | Templates | Greenleaf Games | Educator & Certified GameSalad User
Brilliant stuff as usual ! >- Should be 'stickied'. Definitely the best way to learn stuff, pull apart stuff made by people who know what they are doing and steal the good bits ! I could teach you sin and cos in a few posts, all that stuff is surprisingly simple (probably much more simple than you think) but pretty powerfully, in fact you'd only need a basic understanding of sin (cos is just sin with a slightly offset value).
@Armelline % = modulus, think of it as looping a value, so if you had a count that went like this [ Change XXX to XXX+1 ] you would get: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 . . . .
But if you use the '%' function you can get that value to 'loop' at a certain point, so for example: [ Change XXX to (XXX+1)%5 ] would give you this: 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 . . . (basically it loops the first 5 values).
And [ Change XXX to (XXX+1)%3 ] would give you this: 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2 . . . . .
- Thomas
As a side note to what socks wrote, if your using the mod function this (XXX+1)%5 would look like this mod(XXX+1,5).
New to GameSalad? (FAQs) | Tutorials | Templates | Greenleaf Games | Educator & Certified GameSalad User
It’s not a bug – it’s an undocumented feature
Also @Socks, your quoting makes it look like I wrote the post you're referring to in the first half of your response - tatiang is too gracious to say, I'm sure, but the credit should be his there!
Contact me for custom work - Expert GS developer with 15 years of GS experience - Skype: armelline.support
And yes, I can imagine Googling '%' is going to be pretty useless. Double whoops ! I'll fix that !
New to GameSalad? (FAQs) | Tutorials | Templates | Greenleaf Games | Educator & Certified GameSalad User
New to GameSalad? (FAQs) | Tutorials | Templates | Greenleaf Games | Educator & Certified GameSalad User
-- The higher the level you are on, the more likely an enemy will appear.
-- I want it to go up by 1% chance every 2 levels, up to maximum of 20% and to start at 5%.
if you are level 1, the chance is 5% an enemy would appear.
if you are level 10, the chance would be 10%. 10/2 + original 5% chance.
If you are level 20, the chance would be 15%
if you are level 30, the chance would be 20% (max)
if you are level 99, the chance would still be 20%
I know I am missing a simple math solution but it has thus far eluded me and to save time, I went with some < and > if statements.
Constrain attribute game.enemyChancePercentage to max(20,5+ceil(game.Level/2)-1).
Change attribute game.enemyAttempt to random(1,100)
this would need to be in a timer or a rule so that it fires more than once
When attribute game.enemyAttempt ≤ game.enemyChancePercentage
Spawn [enemy actor]
New to GameSalad? (FAQs) | Tutorials | Templates | Greenleaf Games | Educator & Certified GameSalad User
You mention logical expressions are possible -- how do you implement that?
I cannot get it to work. Tried:
When: Attribute game.box is
NOT..self.open
NOTself.open
NOT self.open
not..self.open
notself.open
not self.open
not..(self.open)
not(self.open)
not (self.open)
!=..self.open
!=self.open
!= self.open
But none of them register. Tips?
Thanks!
P.S. I did it in the expression editor.
That seems like an exhaustive list... hmm... there was a thread where someone mentioned it but I've never actually tried to get it to work. I recall that the person said it only worked for boolean attributes and I just couldn't see the point since you can already create a rule where a boolean is true or one where a boolean is false.
I don't think it was leading to any sort of more complex logical expression (e.g. NOT(attribute1 OR attribute 2) ).
New to GameSalad? (FAQs) | Tutorials | Templates | Greenleaf Games | Educator & Certified GameSalad User
I just tried a DisplayText behavior with not(self.attribute) and it worked to display the opposite of the boolean attribute value.
New to GameSalad? (FAQs) | Tutorials | Templates | Greenleaf Games | Educator & Certified GameSalad User
I have a project in nightly, which I wanted to port to stable, since the GS team is going on break for the holidays and I might be release-ready before they start addressing some of the issues like animations etc. that mean it's impossible to release through the nightly. I make extensive use of 'is not' to check text strings. I'll have to figure out another way round it.
just discovered this thread. I will be studying this as i still need to understand this area more. thanks @tatiang
Just what I was looking for! Thanks
You're welcome @dreichelt and @floatingwoo! I forgot I'd even written this.
New to GameSalad? (FAQs) | Tutorials | Templates | Greenleaf Games | Educator & Certified GameSalad User