This chapter is all function examples and tricks, plus some new Unity functions that
let us do neat things.
One style for writing short functions is to change the input into the answer. It saves you having to make an extra variable, and often looks fine. Here’s the clamp function written in that style (recall if forces the first input to be between the other two.) n is the input and the output (after we fix it):
On longer functions it can be too confusing. You can forget that n isn’t the
original input any more.
Another style is putting all the math on the return statement. This is popular for short functions:
“Front-end” functions often use that trick. This uses the real clamp to do the work:
Putting it on one line like that makes it easy to see that all this function does is
run the real clamp with 1 and 10 already fllled in.
This uses the old bool closeEnough function to check for a number close to zero:
The same trick works on made-up non-mathy functions. Here’s a story-telling function, then a shortcut with the name filled in:
We run that a lot with the name "you", so we make a shortcut:
youAdventure("cow"); is you and a cow on the beach.
We often mix the tricks. This closeEnough has the equation on one line, and uses function abs (absolute value) to do the work:
In the old days, I would have computed abs(b-a) into a variable, then used
an if/else it check it. return abs(b-a)<0.01f; does the same thing, but
shorter.
You’re allowed to nest a function call in a function call. This uses 2-input max to find the max of 3 numbers:
It runs “inside out.” The inside max finds the largest out of a or b, then the outside max compares that to c.
A lot of value returning functions look like they change you, but, of course, they can’t. For example:
The confusion is, in our minds, clamp(cows,0,10) forces cows between 0 and 10. But what it really does is compute that number and return it. Like every other function, we have to “catch” the result:
To force cows between 1 and 10, use cows=clamp(cows,0,10);. Likewise, to make n positive, use n=abs(n);.
Functions using random feel different, so I’m putting them in their own section. None
of these are all that special. Just examples of how, if you use Random.Range a lot in a
certain way, you can write a function for it.
Rolling a 50% chance isn’t that long, but if we use it a lot, we could turn it into a function. A funny thing is it takes no inputs. heads() is true half of the time:
Now we can say if(heads()).
Rolling a percent, like a 20% chance, is also short but awkward. Here’s a common function to make that easier. It uses 1 to 100, so you can write chance(20) for a 20% chance:
if(chance(20)) is true 1 time in 5. If we have a game with lots of percent-based
odds, this will be useful.
Sometimes we want get a random plus/minus value, for example -0.1 to 0.1, or -2 to 2. We can make a shortcut function for that:
If we want about 50, we can use 50+randPM(2); to get 48 to 52. To have red to
be about 0.4 use 0.4f+randPM(0.1f);.
If we have a board game, we might roll two 6-sided dice a lot. The integer version of 1-6 is Random.Range(1,7) (it won’t roll the highest, just because.) We can write a function to roll that twice and add:
Now we can use int move = roll2d6();. If you need an 8 or more to beat the orc, use if(roll2d6()>=8).
We often break up a main program into slop-tastic chunks like this:
It’s pretty much understood that doAllMoves will be a sad excuse for a function.
It reads and sets a bunch of globals. The others will be the same way. All they do is
move code from out of Update. But that’s helpful. It makes things easier to find. We
can use the editor collapse-code-block feature to hide or show only the functions we
need to see now.
We love functions that only read from their inputs. We can see everything they use right there on the line calling them. But sometimes, rarely, we make a function that has a setting. Here’s a standard set color with a special option to not be too dark:
setColor(r1,g1,b1); runs it normally (since the 4th uses a Default setting).
Otherwise run it with setColor(r1,g1,b1,true);.
Another way to do that is using a global for the setting:
This can be nice. Suppose we can’t be too dark for the next 10 seconds. Set colorCantGetDark=true;, then false after 10 seconds. That’s way easier than needing it as a 4th input.
We rarely do things this way. It’s easy to forget about the setting – it’s more
hidden than if it was an input. And if we need this function in a new program we also
have to copy the global variable. But Unity has at least one thing that works like this
(there’s a global setting for raycasts).
Some functions mostly do something, but return a minor value, usually saying if it worked. For example, this function’s job is to move and wrap-around. It returns true when it wraps:
We can use it by itself: moveAndWrap(0.1f);. We ignore the return value. Or we can catch the answer and act on it:
The first line is as if we’re calling moveAndWrap(0.1f) to do things for us. But then we’re asking it “so, how did it go? Was there a lap?”
We often us it directly inside the if:
Inside of the if, the program actually moves us. Functions inside of if’s really,
really shouldn’t also change things. But in this case it’s fine. We understand it’s a
“do some stuff then tell me how it went” situation.
When we have a function that mostly computes, but also does something, we call those side effects. For example, this checks whether we’re off the edge, but also adjusts us:
This is confusing, since it feels like an answer-only function:
But if we renamed it bool forceIntoBounds it would feel like a do-something function that happened to have a minor return value.
We know enough now to use some new Unity built-ins and make some more complex,
fun movement. I’m going to barely skim some of the set-up – I’m assuming at this
point you either played with Unity enough to figure it out, or you’re happy just
reading these examples.
We need 3 new things. The Rotate function spins us. This spins us 2 degrees each frame, like a top:
Rotate has 3 inputs. The other 2 rotate us like a summersault, or rolling sideways.
We won’t use them.
The Translate function moves us on our personal forward. Running this with a tilted Cube will go whichever way it’s facing. The speed of 0.1 is about the same speed we were using before – takes 140 updates to cross from -7 to 7 at that speed:
We won’t use the other 2 input slots. They move us sideways and up.
Combined, the two new functions can move us in a circle, spinning and moving forward:
The last new rule is asking the cube where it is. transform.position.x. gives us our current x position, which is probably -7 to 7. As a test, this snaps us to the center whenever x goes past 3:
To see this we’ll need to be in top view. We can put the camera 10 units over the
center, at (0,10,0), Then tilted looking down with rotation (90,0,0). I put
a little ball as a child, just in front of it, so I could see which way it was
aimed.
Now we’re ready to write some code. Whatever we do, we want to stay in bounds. We’ll write a function to check for it:
If we slow down our spinning, we can rotate in circle big enough that we’ll go out of bounds. The code below will spin us 180 degrees when that happens:
This is nice, but gives us boring, predictable curves after watching it for a while.
We can change it up with the usual tricks. Our turn speed can be a variable and we can add a little randomness to the 180 degree flip:
This moves in semi-interesting curves, with fun bounces.
This has the same rare stuck-out-of-bounds bug as the old back-and-forth code. If it somehow gets too far out it will just spin in place. We can make an improved outOfBounds function to force it in. The name is changed so we remember it also can move us:
I think that’s a neat use of a bool variable.
We can try other things. If we move sideways and forward, we’re going diagonally. If we use a counter we can move left 10 times, right 10 times, left 10 times in a zig-zag:
This works – it moves in short, straight lines making a zig-zag – but I just don’t like the way it looks. My second try at zig-zagging is to always move forward, and change our facing. We’ll snap 30 degrees left and right as we move:
We finally know enough rules to decipher a built-in keyboard reading function. This uses the A and D keys to move back-and-forth:
You can probably figure out that Input.GetKey(KeyCode.A) is true when A is pressed. Here are some notes, mostly obvious, about it:
Here’s a different version, which also moves back&forth, but using GetKeyDown. Tapping A or D starts you moving left or right. Tapping S stops you. I don’t like this as much, but it’s fun to program:
We can tweak that so holding a key gradually speeds you up:
The first two ifs don’t limit the speed, but we’ll hit an edge before we get too
fast. The part where it slows down is an old movement trick. We want to make the
speed go towards zero, whether it’s positive or negative. Multiplying by 0.98
makes 1 and -1 both go towards zero. It’s only 2% smaller, but it adds up
fast. The last slowdown line says “when speed gets really small, just be
0.”
For something completely different, we can have A and D rotate, while W moves forward:
We could change this version so the W key is acceleration instead of movement:
Moving onto another completely different movement, we want the Cube to crawls around the sides of the screen. A moves clockwise and D moves counter clockwise.
The basic plan is using a 0-3 int to remember which side we’re on. I’ll use an enumerated type, written specially for this:
Wall wall; looks funny but it’s common. The type is Wall and the variable is lower-case wall since I couldn’t think of a better name.
wall will act as a state-variable. Each wall handles movement on itself, passing you to the next wall when you hit an edge:
The enumerated type really was helpful here. While writing this, I messed up some of the numbers and directions. Seeing lines like if(wall==Wall.right) made it easy to see I was in the correct section.
So far, I’ve been suggesting that all of your useful functions should be pasted into each new script. That will work, and we did it in the old days, but there’s a nicer way. Almost all languages and environments let you spread your program over several files. Common, similar functions are usually placed in a file by themselves.
The steps to putting functions by themselves are:
A sample file with some random functions:
Anyone can run if(rand.head() for a coin flip.
Unity uses this trick in a few places. Random holds several randomizing functions. Mathf has lots: Mathf.Approximately(a,b) is the close-enough function. Mathf.Abs, Mathf.Clamp and Mathf.Max are also there.