This chapter is more Unity-type things that show off various pointer and class tricks.
A label on the screen is just another thing you can create: GameObject->UI->Text makes one. It makes something called Canvas, which we can ignore, with the Text inside of it.
If you select the Game tab, on the big window, you should see a small, ugly “New Text” in the lower-left corner. That’s good enough for testing, but here are some non-programming Text notes:
Go to Scene view and double-click either Canvas or your Text. This should snap your view there. Use scroll-zoom and right-button-spin or the upper-right gizmo to get a mostly head-on view. You should see a big, white border – that’s the entire “Text screen.” If you click on your Text object, you should see a faint white border around it.
If the white borders are hard to see, you could turn off the fancy sky. In Scene, on the top bar of that window, there’s a little mountains icon. Select the drop-down next to it and uncheck SkyBox. That should give you a nice grey background.
Your text can’t be any larger than the white border around it. You can grow that using the new Width and Height values (in the Inspector, in RectTransform.) It uses the same numbers as position – about 800 makes it across the entire screen. For Height, it’s fine to increase it to have plenty of room.
Again, we can run our code with the crummy, small greyish text in the corner.
Making it look nicer is just for fun.
Adding text labels wouldn’t have been helpful before, since we didn’t know how
to find them. But now we do – using a GameObject pointer. All we need to know is
the magic command to change the text part. It’s
GetComponent<UnityEngine.UI.Text>().text = "cow";. The part after the = is
just a string.
Here’s the left-to-right laps program, using an on-screen lap-counter:
It’s not much different from our old program – we’re just slapping laps into the label using a pointer and the new rule for using Text.
Now that we know about GetComponent<Text>().text="cow";, we can probably change some more things. Text is just a class, which means it probably has more fields.
For example, there’s a color picker in the Inspector under Text. If you look, you’ll see dot-color works for Text. This turns it orange:
The really neat thing about this is it’s just old rules. We know classes like Text
have fields, like color. And once we see color is just a Color struct we know how to
use that.
Our code can change the text’s alignment. Make sure the text has extra room on the sides, then click the Inspector pictures for Left/Center/Right alignment and watch the text readjust. We can also do that in the code – there’s an alignment field which says it’s a TextAnchor.
Looking up TextAnchor, it’s an enumerated type. It has values like UpperLeft and LowerCenter, which pop-up when we type TextAnchor-dot.
Here’s some code using it:
The second version looks funny, declaring TextAnchor ach;, but it’s no different
than pre-computing color using Color col; then assigning col.
Ways to change built-ins through code are usually called the API – Application
Programming Interface. We say something is exposed in the API if a program can
find and change it. Text color and alignment are exposed. It’s not automatic, and
not everything has to be – there are a few sliders that the program has
no way to change – but they try to expose as much as they think you’ll
need.
I want to use color and alignment for something interesting, but first I want to show the official Unity timing trick, to really make something that delays for 1 second – not just counting out 60 ticks and hoping.
The trick is to read from the built-in clock. Most systems have one. Unity’s
counts in seconds, starting from when you press Play. Each time you press Play, it
resets to 0, and it’s always in just seconds. If you press Play and wait a minute and
10 seconds, it will say 70.
To read it, just look in the built-in global Time.time (you might remember this from the namespace section. It’s the time variable in the Time namespace.) Here’s a test program showing it change in the Inspector:
The trick is that, sure, Time.time is just a variable, but Unity is always secretly
increasing it, so it really is the time.
Here’s the plan for using Time.time to make a delay: suppose dinner is ready is 20 minutes, and it’s 6:10 now. That means dinner is ready at 6:30. That’s the only number we have to remember. We can walk around, check the clock occasionally, and at 6:30 we can open the oven.
In computer terms, suppose you want a 3-second delay. Look up Time.time now and add 3. Save that in a global. For example, if we want to wait for 3 seconds at time 15, we save 18. Then, in Update we keep checking if the time is past 18.
Here’s a very simple program which prints every 3 seconds:
The last line is resetting the time. In the old version, it was delayCounter=60;.
Now, it’s like saying “OK, I just printed at time 15, the next one is 3 seconds from
now, at 18.”
Here’s a slightly silly program using everything at once. It counts by 1’s, every 1.5 seconds, and moves the text back and forth, with a color change as it gets higher:
The thing I like about this example is on one hand, it’s just a cascading if and setting a few values. The most complicated thing about it is if(n%2==0) to check whether n is even. But on the other hand it’s full of gibberish like UnityEngine.UI and TextAlign.
A lot of real code is like this. What’s it’s doing is pretty simple, but there happen to be a lot of pre-defined classes and types making it look complicated.
I’d like to use one script to move three platforms back-and-forth in customizable
ways. Obviously, this is going to use the trick with GameObject pointers, plus we can
make some fun custom classes and use a function.
I’ll assume we have a front -7 to 7 view. We’ll pre-make the three platforms. Just cubes will work, but we could make them nicer: make a Cube, hand-set scale to long and flat, about(1, 0.2, 1), then make 2 copies. May as well name them A, B and C.
So they don’t overlap, we might stack them at about (0,2,0), (0,0,0) and
(0,-2,0). Our old code ignored where the Cube started – this improved code will
start Cubes moving from where we placed them. The exact spots won’t matter (I put
my platform A on top.)
Each platform will move left and right, but will have it’s own settable left/right sides, speed and a pause at each edge. For example, we want to be able to set platform A to slowly move between -7 and 3, but set platform B to move quickly from -5 and 0, pausing for 1.5 seconds before reversing.
Each platform needs the same group of variables, so this is a good place to make a class:
I’m sneaking in a small C# rule here. A class allows you to set starting values for
variables. minX=-7 isn’t a real assignment statement. But when you new the class,
you get that value instead of 0. You can’t use this trick with structs, just
because.
The point of this class is we can easily create variables for all three platforms by declaring 3 of them. These are public, to put them in the Inspector. GUI-magic will let us pop each open and closed:
A new thing is how our real link to the platform – public GameObject g; – is
inside the class. We’ve never put a GameObject as a field in a class before, but there’s
no reason we can’t. Pop them open and drag your three platforms into the slots for
PA.g, PB.g and PC.g.
We need the usual extra variables to run the movement: a Vector3 for the current position, one to remember if we’re moving left or right, and one to time the hit-an-edge delays. We don’t need these to be in the Inspector, but still need one set per platform, so it seems fine to group them into another class:
Then we make one set for each platform:
The Inspector variables are new’d and filled already, but we’ll have to create and set these in Start:
The last three lines are a mouthful, but it’s a good trick. PA.g is a link to the real platform A, which means PA.g.transform.position is where we hand-placed platform A before the game started. padA.pos is our standard variable controlling how it moves.
So those lines say to start each platform’s position at the actual spot we
hand-placed them. A nice bonus is our code doesn’t have to decide how high up each
platform is – we did that when we arranged them in Unity.
Since we have to run the same code for all three platforms, a function seems like a good idea. It needs all the data for that platform, which is now just two variables. This looks messy, but it’s the same old move code, with a possible edge-delay added:
Notice how this counts on pa being a pointer. It needs to reach inside and change
pa’s position and such.
Update merely needs to call that function for each platform:
The fun thing about prefabs is they don’t feel like simple copies – using one feels like making something pop into existence. It’s extra fun to do that with “physics” objects.
For this we need a falling ball from before: create a Sphere, add a Rigidbody
component, possibly color it, drag it into Project to make a prefab of it, delete the
original.
This script randomly fires them from the left, in a little arc. For fun, I’ll shoot a few, wait, shoot a few more, in 5 waves. I’m using the new timer trick to wait:
Nothing new here, but a lot at once. Some notes about the design:
Instead of blasting out the balls in each wave all at once, a wave could quickly spit 4 balls one-at-a-time, wait longer, and repeat (you can really see the difference).
There’s nothing new here, just being clever with variables and if’s:
ballsThisWave is like the loop counter, sort of, except it goes up by 1 every 0.1
seconds.
An interesting-looking thing is creating objects at our position. This moves us back and forth along the bottom of the screen, and lets the space bar fire balls from us:
The lines moving us aren’t the most efficient – if we aren’t pressing a key it checks the edges and “moves” us by 0. But they’re short and easy to read.
It would be fun to have the balls we shoot shrink and then wink out of existence. Way, way back I mentioned that in a typical game set-up, each object was driven by its own script. We’ll do that here. Balls will have a simple script that slowly shrinks them. It would go on the original – the prefab ball in the Project panel.
The system know that prefabs are frozen, and won’t run there. But each created ball gets a copy. It runs exactly like it looks:
Destroy isn’t a C# command. It’s like the opposite of an Instantiate – it removes
you from the Scene.
If you’ve shot 4 balls, there will be a copy of this on each ball, with different variables. Each ball shrinks and vanishes indepedently.
It seems like cheating, but not really. A “real” program has one main spot controlling everything. But it’s common for that to be pre-written. We write our little parts and plug them in. Writing separate programs and letting Unity run them all isn’t that far off from the way serious programs work.