Something that happened magically while developing this system was one-sided platforms. That is, platforms that you can go through easily on one side, but from the other side it doesn't work, it's a wall.
I stumbled upon an elegant solution I love very much, and I will now share it with you!
These platforms are rad
 |
| Lots of platforms in Kirby can be accessed from underneath |
So you may be asking yourself, "Why do I want these platforms?" The answer is usually: they're rad (sometimes, it's: you don't). They can add a lot of depth to your game. They give options during level design, opening up puzzles, dexterity challenges, and other such intricacies.
OK that's enough talking let's code.
Layermasks & Bitwise Operations
Layermasks are integers that inform a Raycast what to pay attention to. They are key. Once you start using them, you'll never go back and you'll be very glad you have this skill in your pocket.
What is a Bitwise operation? In this case, we'll be dealing with a bit shift. This requires a little bit of binary, so if you know all this stuff you can skip right to the line of code where the binary operation is.
Binary is a counting system exactly like our normal one (Decimal), but instead of counting all the way to 9 before reaching 10, we count to... 1. 0, 1, 10, 11, 100 are 0, 1, 2, 3, 4, respectively.
In any number system, the leftmost digit (or bit in binary) refers to how many of the highest exponent of the base are in the total value. In Decimal, 854 is literally 8 * 10^2 + 5 * 10^1 + 4 * 10^0. A 0 exponent always gives you 1 no matter what you're putting to it.
So, 11011001 is:
1 * 2^7 + 1 * 2^6 + 0 * 2^5 + 1 * 2^4 + 1 * 2^3 + 0 * 2^2 + 0 * 2^1 + 1 * 2^0
which simplifies to
2^7 + 2^6 + 2^4 + 2^3 + 2^0
which simplifies to
217
For Layermasks, we will go from 2 ^ 0 to 2 ^ 31. This is because we have 32 different layers in unity, each of which is assigned an integer from 0 to 31. If you say Layermask.NameToLayer("normalCollisions") in my code it will return 31.
So, the Layermask is always a 32 bit integer. But, what matters in a Layermask is not the value in Decimal, but the status of each bit. Each Let's say your Layermask look like this:
1000 0000
0000 0000
0000 0000
0000 0000
The value is 1, but that doesn't matter. In this case, the Layermask tells the Raycast only to pay attention to 1 layer, Layer 0. If it looked like this:
1000 0000
0000 0000
0000 0000
0000 0001
then it would look at the first and the last layer. Its value is 2147483649 but again, that doesn't matter.
So how do we make a number look like this? Luckily you don't have to spell out your own binary numbers and convert them to Decimal. You just have to do a bit shift. A bit shift looks like this:
int collisionLayer = 1 << 31;
This means you're shifting a 1 to the 31st position in a binary sense. But that's just one layer. What if we want to do more than one layer? Easy.
int collisionLayerPlus = 1 << 31 | 1 << 30 | 1 << 0;
The | character indicates we're shifting more than one bit in an integer. Now the Layermask will look for collisions in the 31st, 30th and 0th layers. For fun, this is what the bit will look like:
1100 0000
0000 0000
0000 0000
0000 0001
If you're having trouble with any of this, please google Bitwise operations, or bit shifting, or even just layer masks and someone else will give you a better explanation (because there's lots out there :).
What I did was set up a class with static variables that contain these bitwise operations so I don't have to keep doing them in my code. This is what it looks like.
That's it! That's all it is. Now we use it elsewhere.
...What? You want an explanation of this too? OK fine...
Well first you'll notice the class doesn't inherit from MonoBehaviour. That's because it doesn't need to! I'm not going to put it on an object - in fact, I'm never even going to instantiate one. All it needs is those static ints that can be accessed from anywhere, at any time. I'll do that later.
The other fun thing in here is the static constructor. I don't fully understand how a static constructor works, but I guess it probably calls that when it needs to to give values to the variables I declare above. I can't use functions like Layermask.NameToLayer in a variable declaration like that so I had to use a static constructor.
Last, softBottom is what I call the kind of platform you can jump up through. softTop is what I call platforms you can fall through but can't jump up through. They're evil. And fun! :D
Now let's use the darn things!
Let's start with platforms that you don't hit your head on. Ones you can jump through from underneath but will then land on stably. Find the code where you look for things to bump your head on.
....we haven't done that, have we? Oh...
Well, we'll do it at the same time! It's related.
Here's the final code.
You'll notice it looks a lot like our other directional checkings. That's because it is pretty much the same.
There are two main points of interest here:
Here is where the character hits his or her head. If a platform is there, teleport to the point of contact, and set your velocity to 0. The other option (a la Mario) is to have a little rebound. I like to use a fraction of the upward velocity you were moving at, assigning it as a negative velocity.
And...
This is where we use the Raylayers class. It dictates what I'm going to detect with my up-layers. In this case, I will only bump my head on things in the NormalCollisions or SoftTop layers.
Now all you have to do is assign a platform the SoftBottom layer and you won't hit your head on it!
It's really that simple. A few lines of code and a lot of functionality. It's just as easy to add the SoftTop platforms (just add the right layermask) and to add one-way sideways platforms (which are also quite interesting).
I know my explanations in this post may be a little hasty, so if you have any questions please feel free to leave them in the comments and I'll get back to you as soon as I can. This was supposed to be a really short tutorial but it ended up... not being. ^_^
Thanks for reading!
Until next time...
Stay smart, little mite.
-mysteriosum(the deranged hermit)