Now that we can “think” with ifs, we can improve the moving, coloring, and resizing
examples from before. Even if you have no interest in making games, this is a pretty
good way to practice writing if’s.
Here’s the old “move across the screen” code with only x changing:
Instead of shooting past the right side, we could have it wrap around like the old
Asteroids game. We just need to figure out how to say “when it goes past the right
side, return to the left.”
That’s two separate things to figure out. Going to the left side is easy, we’ve done it before: x=-7;. You might think that can’t be right – putting x=-7; in Update will keep us glued to the left edge. But that’s the other part – how to do it only at the correct time.
Checking past the right side is if(x>7). Combining them is: if(x>7) x=-7;. Move with wrap-around looks like this:
That new line is pretty short. A chapter ago it would have been a really bad
example – why would we want to change from 7.1 to -7? But now that little if does
exactly what we need.
It probably looks a little “snappy”. It will look better if the pop-out and pop-in were both off the edge of the screen. Tweaking +7 and -7 could make this look really nice. The easiest way to do that is to turning them into variables. Then we can adjust them as the program runs:
The if is even a little bit easier to read this way. It says what it does “if you’re
past the maximum, drop down to the minimum”.
We can use the same trick to change the size. This grows from normal to double-wide then snaps back and repeats (remember 1 means normal-size):
Wrapping-around color is about the same. To make it more interesting I’ll have it go down (remember color numbers are from 0 to 1):
Notice it checks for less than 0, since it’s going down. I kept the red slot as always 1 for fun. It goes from (1,1,0) to (1,0,0), which is yellow to red (red and green makes yellow).
The other fun thing we can do when we hit the edge is bounce. This will flip the speed when we hit the right edge:
Again, pretty cool for just one if. It looks even cooler if you have a wall and
tweak the edge so it looks like it’s bouncing off it.
An improved version would bounces off both sides, and keep whatever speed it started with. If we use xSpd *= -1 we can turn a forward speed of 0.08 (or whatever) into the same speed going backwards, then positive again when it hits the left side:
You should be able to watch see xSpd flip between +0.1 and -0.1 as it moves back
and forth.
There’s also a cool bug. When we go past 7, it should be because we were going
forward and took 1 step too far. Flipping the speed will have us step back in bounds
on the next move. But if we start way off the edge, like at 12, that’s not true. We’ll
be past 7 every time, the speed will flip back and forth and the cube will just
vibrate.
Size change with bouncing works the same, except the numbers are smaller (1 to 2.) This makes a square grow to 2, then shrink back down to 1, repeating:
For fun, we can rewrite our movement bouncing using a different plan. We can have a variable dir, will be always be +1 or -1 for the direction we want to move:
That’s not quite as nice, but it fixes the off-edge bug, and it’s got a nested if.
In older movement examples, I had the speed get faster and faster without limit. Now
we can use an if to add a limit. Or to reset the speed, or whatever else can can think
of.
I’ll go back to the simple wrap-around Cube. This starts slow and speeds up. The new part is a maximum speed, using a simple if:
Once it reaches a cruising speed of 0.8, it’s only a simple wrap-around.
We could just as easily have it slow down to a minimum. We’d start xSpd high, subtract a tiny bit, and use a “can’t get below this” if:
If we copied these over the old lines, it would move quickly, but eventually settle
to be a simple, slow wrap-around.
You might remember the trick where I started the speed negative and always increased it. The Cube went backwards, slowed and then reversed direction. If we do that for y, it’s like we’re making gravity – a ball going up, slowing, then falling. We may as well also add a bounce off the floor:
We’ve seen a bounce, and we’ve seen the slow-down-then-change-direction trick.
But combined they look pretty cool – like a real ball bouncing.
Gravity and bouncing off the floor looks even cooler if we add the old x movement, bouncing off the sides. This cube will bounce in realistic-looking arcs:
It’s just a few simple tricks, combined, but it should look a lot like a ball
bouncing around in a box.
We can try a resetting speed increase using color. The code below starts with the yellow-to-red code. But it makes green go faster and faster, then resets it to change slowly again:
Hopefully this isn’t too bad. It’s a simple wrap-around, except going down, not up. Then another wrap-around for the speed. Then we had to figure out good numbers for the speed. 0.0005 seems too small, but we’re only going from 1 to 0, and the frames happen quickly.
Even so, it’s easy for a few simple tricks, combined, to start to look confusing.
We can do some interesting things if we count how many laps the cube makes. Making a lap counter is easy. Make an int variable, and add 1 on each reset:
If we watch in the Inspector, laps will go up each time, but nothing else
interesting will happen.
The next part is using the counter to do something. I’d like it to turn red after 3 laps, and green after 7 laps, then back to white on lap 8. All of that goes in the “do special lap stuff here” area:
This is probably the largest body of an if we’ve had yet. But I think it seems natural. When you get past the end you should: 1) wrap around, 2) count the lap, and 3) use ifs to check for the three special laps.
The color-changing if’s are a little new, but not really. We’ve used the line to
change color before, just never guarded by an if.
A possibly confusing thing about this is what color is the 4th lap? The code
doesn’t say. Of course it’s red since we turn it red on 3 and stays that way until we
change it again. Another confusion is I said it resets after the 8th lap, but the code
simply turns it white. That’s because there’s no “reset” command. Since it started
out as white, turning it white feels like a reset.
A little tricker use of a lap counter, we can do something every 3rd lap, using modulo. laps%3 (the remainder after dividing by 3,) will be 1,2,0,1,2,0 …after each lap. So (laps%3==0) is true every 3rd lap.
This sets the speed to “slow”, every third lap:
This sets us to slow at the end of lap 3, meaning that lap 4 is slow (and laps 7, 10, 13 and so on). That’s still every 3rd lap, so fine. We could adjust the test to if(laps%3==2) if we absolutely needed lap 3 to be the slow one.
If we want to wait for a second or two, we can do that with an int counter, an if
and a plan.
The simplest plan is having the counter be how many Updates we should skip, count down, and zero means we’re done waiting. If we want to wait for 60 Updates (about a second,) we set the counter to 60.
This moves a Cube in big steps, with an 80 update delay in-between each move. It should hop from left to right:
The movement code is now guarded by an if. That’s what if’s do – they say that
we might do something. An if-else is good for this: either we’re waiting on the
countdown, or we’re not. You could watch delayCounter spin down to 0 (the true
part of the if is running,) then see the Cube pop forward and the counter reset (the
else ran one time.)
When the delay ends, we don’t have to restart it right away. This version moves smoothly with a wrap-around, using the delay only when we hit the edge (it waits a little bit before moving across again):
Essentially, delayCounter is there for us whenever we need it. We want to delay
after a lap, so we set delayCounter. The it takes care of making it actually delay
us.
It might look funny having delay come first in the if. We can flip the order to put moving first and waiting last:
This next example uses a delay with a size change. The basic code makes the Cube be size 1, 2, 3, 4 then back to 1. A delay counter makes it do that once every second. We should see it gradually pop larger:
Notice how I got tricky with setting the delay. When it wraps around to 1 I give a longer 120-tick delay.
We could write the 60 or 120 delay a different way: always give a delay of 60, with an extra 60 for a wrap-around:
What I like about this is the best way depends on how you think about it. Maybe
you feel like it’s two different delay times. Or maybe it makes more sense as a base
delay with extra for wrapping.
Finally, I mentioned counting down was the simplest plan for a delay. Another plan would be counting up to the total. We’d need an extra variable. To wait for 20 Updates we’d set the total to 20 and the counter to 0. Here it is with the movement wrap-around:
This is more complicated, which usually means worse. But a possible advantage is if you want to show the delay as a percent – like one of those bars that fills in. It would be 1.0f*delayCount/delayTotal. We can’t compute that with a single count-down, since we don’t save where it started.
In the second version of bouncing, with dir, my idea was the program has two stages – moving right and moving left. dir’s job was to remember which stage we were on. I used -1 and +1 as easy-to-remember values, but it could have been 1=left and 2=right.
Sometimes we have a program with more complicated stages, and we formalize
the idea of a stage variable. Here’s the whole trick:
Draw out a picture with your stages and the rules for going to the next. Number them – the exact numbers don’t matter; the numbers are like their names.
To program it, make an int to remember which stage you’re on. Inside, use a big
“do only one of these” cascading if-else-if-else. Make one part for each stage.
Whenever you want to go to a stage, like stage 3, write stage=3
Here’s an example. The comments under stage explain what it does:
The trick is it only does one part each Update. For stage 0 it only runs those two
lines: move a little right, past 6 means done – set stage to 1. Then, starting on the
next Update, run only the two lines for stage 1. stage is the control for what Update
does.
This one changes color in stages, using a delay counter so it’s not a blur. Also, instead of stopping at the last stage, it repeats to stage 0:
This spends almost all of it’s time subtracting from delay. Each stage runs for only one update: it changes color, sets the new delay, and goes to the next stage (for when the delay is finally over.)
The important thing is how we’re still using our extra made-up stage variable to
remember which step we’re on.
Not really important for these examples, but the official name for this sort of
thinking is a State Machine. You can find state diagrams (circles with arrows,) or
GUI’s (Unity’s built-in Mechanim animation system is a drag&drop state
machine.)
For fun, why wouldn’t we use a word for the state, making it easier to read? Here’s what that would look like:
It’s easier to read, but it’s so easy to make a mistake. If we wrote "go up" or "Move up" (capital M) the code would stall, with no errors. It turns out that using strings to control what happens almost always causes hard-to-spot bugs. Later we’ll see a trick to make the numbers easier to read in a safer way.