Unity3D handles math with xyz points in the standard way. This first part is those
rules, plus how they translate in Unity:
3D points are named Vector3, and have dot-x, y and z. They’re structs, so using new is optional:
They have a constructor. p=new Vector3(0,6,30); is the same as the lines
above.
You can add two xyz’s, and it happens pairwise. Each pair adds, and the result also has three parts. More specific: add x to x, y to y and z to z. The code below shows this:
It’s useful, and we’ll use it lots, but it’s only a shortcut. C=A+B; is the same as
C.x=A.x+B.x; C.y=A.y+B.y; C.z=A.z+B.z;.
Subtraction is also pairwise. C=A-B subtracts each column:
The other useful shortcut is multiplying by a single float. Each part is multiplied by that number:
We call that 3 a scalar since it scales the vector by that amount.
As usual, operators can be combined and mixed, with times going before plus.
A+C*3 triples everything in C, then adds it pairwise to A. We can also use shortcuts
like A+=B; and A*=2;
Unity doesn’t define pairwise multiplication: A*B isn’t allowed. If you need it,
which you rarely will, you can write it out in three lines. It also doesn’t define scalar
addition, such as A+2, but you also rarely need it.
There are shortcuts for common Vector3’s. Two handy ones are all 1’s and all 0’s:
The last is a nice way to blank something: A=Vector3.zero;. The first one is
often used with scalars. A=Vector3.one*0.5f is an easy way to make (0.5, 0.5,
0.5).
There are also shortcuts for 1 unit in all six directions. These use the Unity orientation, so forward is positive z. Ex’s:
These are nice for creating points. Vector3.up*5 is a nice-looking way to write (0,5,0). Or we can combine them (scalars and addition) to make any point:
That’s the same as new Vector3(0,9,-4);, except longer. But maybe it’s easier to read it as “4 backwards and 9 up”.
The first trick is knowing an x,y,z can be either a point, or an offset, depending on how we think about it. A point is the easy one – a spot on the map. Offsets are arrows, which we add to points. An offset of (0,5,0) means “5 above some other spot”.
Vector3.right and it’s friends could be used as either, but are usually offsets,
which is why they have those names. A typical use would be
transform.position+Vector3.right, meaning “one space right, from me”.
Suppose we’re building a game board. We’ll start by letting the user set the lower-left corner. It’s a simple point:
The board squares will run sideways and forward from there. To help place them we’ll make two offsets:
These are offsets because of how we plan to use them. sqSdways isn’t a spot on
the map – the board may be nowhere near the point (0,0,1.2). But it’s great as a
single-square sideways arrow.
Using our vector math, we can compute positions of some sample squares. We start at the corner, and walk squares over:
The second one is so cool, how it clearly says “three squares sideways from the
corner”. It also shows a rule: an arrow times a scalar is going the same direction, but
a different distance.
Adding two offsets is like following two different end-to-end arrows. This line says to start at the corner and walk 3 sideways and 2 forwards:
It puts us exactly at the corner of that particular square.
For more fun, assume we’re placing 3D square models, which have their origin centered. Instead of walking to corners, we need to walk to centers – an extra 1/2 a square sideways and forwards. We can do that painlessly with another offset vector:
The end result is a short diagonal arrow. Dividing by 2 is using a scaler – it’s the
same as *0.5f, but I though it looked nicer this way.
To place our squares we add that extra offset:
A board lined-up on x and z isn’t really showing the advantage of offsets very well. We know sideways is just +x and forward is +z. But with a tilted board, vector math is our best friend.
Instead of only one corner, let’s have someone hand-enter three corners. Depending, the board could be tilted any which way (assume they make both sides the same length, and at right angles):
Since we know the board is 8 squares across, each square is 1/8th of the way. Instead of entering the sideways and forward square offsets, we can compute them:
The new thing here is that subtracting 2 points creates an arrow. The right corner minus the left corner computes an arrow along the whole bottom.
1/8th of that arrow is our sideways 1-square arrow. Pretty slick.
The really neat thing is how finding the square centers is the same as before:
One more neat arrow trick: we can compute the missing upper-right corner, using the other three. The plan is: get the arrow going up along the left side; then add that to the lower-right corner:
Another plan would be to start at the upper left, and march 8 squares over:
One last thing about this entire game board example: if we knew how to rotate an arrow, we could have them enter only the two bottom corners. We’d find the arrow between them, rotate it 90 degrees, and use that to get the other two corners.
Let’s assume we’re a Cube, with a script on us. Our location, which is a point, is in
transform.position. We can use that, plus offsets, to place things around
us.
Assume that besides us, we have three cubes: red, green and blue. No scripts, just Cubes waiting for us to reach out and position them. This simple code places them at various spots around us:
The point+offset math is nothing new. But now we’re really placing something there.
If you want to see this really work, put a Rigidbody on us, add a bouncy material,
make a floor, a ramp …and let us knock around. The colored cubes will track us
perfectly. Even when we spin, they stay locked in.
For a variant, let’s say someone enters one long arrow, coming from us, where we want the colored cubes evenly spaced. We can do that with scalars:
The scalar determines where they go on the line.
A cool idea that gives us: what if we take just one cube and slide the scalar from 0 to 1 (in the code)? It will move along the line:
Stepping back, writing code to shoot out along any line seems like it
might involve lots of math. But thinking of it as offsets and scalars makes it
easy.
To jazz that up, we can use the trick of computing an arrow between any two points. Instead of entering cubeLine, let’s say the red cube is the other end. Where ever we are, we want the green cube to shoot from us to the red one.
The only change is computing cubeLine as the red cube’s position minus ours:
This next one is simpler. Multiplying by a negative scalar flips an arrow. We’d like the green cube to hide behind us (from the red cube). We’ll can take that same arrow from us to red, and take it times -0.1:
Instead of placing a green cube nearby, we can use the same tricks to place the camera. It will appear to be tracking us.
This code puts the camera above us, and a little ahead:
The camera would need to be aimed downwards (a 90 degree spin on x). If we
have the rigidbody bouncey set-up from before, the camera-tracking looks pretty
cool. But if you go to Scene view and watch, it’s no different than when we made the
cubes track us.
We can give it a camera zoom by scaling the offset. We’ll add a zoomPct variable and have up/down arrow keys change it:
Real zooms don’t go straight to our center, like this does. They have a little offset – like going to a spot above our shoulder or something. That’s easy to make by using two arrows, and scaling only the second one:
The code is pretty simple: add offset#1, then offset#2 times the zoom percent:
Mathwise, it’s just a point plus two offsets, which we’re done before. We just never had a reason to scale only one of the arrows.
Vectors are a nice way of storing “this is how much I move each frame”. They say which direction, and how fast. We like to say the arrow is how much we move each second. Each update we’ll move a fraction of that.
This moves the green cube along whatever vector the user enters:
I like how the += feels like what it does. x+=0.1f; moves x a little. greenCube.position += someVector3; does the same thing, except in 3D space.
Of course, we could move ourself, using the same method, with transform.position
+= mvArrow/60;
We we moving the green cube before, using a more complicated method. We needed to know both end points (us and the red cube) and the percent. It always stayed on the line, even if one end moved. Since we always knew what percent of the way we were, it was easy to stop when we got there, and go back to the start.
This method is simpler, for when we’re happy to just go in a direction.
One note: I’m assuming Update runs 60 times/second. If you know about
Time.deltaTime (or want to look up that trick), we’d definitely use that instead of
60.
An average of two points looks and works just like a regular average. (A+B)*0.5f; gives you a point exactly between A and B. If works no matter where they are.
In the game board example we can average the two bottom corners to get the bottom middle: bottomMiddle = (cornerLL+cornerLR)/2;.
Or, sneakier, the center of the board is the average of two diagonal corners:
Vector3 center = (cornerUL+cornerLR)*0.5f;. That’s pretty cool, since it works
for tilted boards, too.
We can also find the average using our old point+arrow*percent method. This also computes the bottom middle:
The code is a little longer, but we can adjust the 0.5 to whatever we need. Average is really just a shortcut for this, when we’re sure we’ll only want 50% of the way and won’t need to adjust it later.
Above, most examples had one new rule or trick. Here they are written all together:
I think of these rules when things break. Suppose you have sq.position=A+B+C;
and it’s working wrong. Check whether A counts as a point, and B and C are offsets.
That’s the only way the math makes sense. If something with B*2 is working funny,
check “does B count as an arrow?” and “am I wanting to double how long it
is?”
Finally, two fun errors. It’s easy to accidentally use an offset by itself, forgetting to to add the starting point:
This is like moving the line to start at (000). It’s the same length and direction, but in the wrong place. It tends to be really confusing since 000 usually isn’t any special spot in your game. Depending where you are, the line can be nearly correct or completely off the screen.
If you get a line that appears to shift in funny way, check that you added the
starting point.
It’s also easy to flip the arrow direction by mistake. It’s the end point minus the start point. If you see transform.position - redCube.position, that’s as arrow going to you, from the red cube. Nothing wrong with that, but if you follow it from you, it goes away instead of towards.