Tuesday, 28 January 2014

2D Platforming Physics With Raycasts: Jumping

Hello, faithful reader(s)!

It occurs to me that I didn't actually explain how I do the jumping in my system. It's simple, but there's a couple of nuances that should be discussed.

Basic Version

Jumping in Unity can be as simple as:

void Update (){
     if (grounded && Input.GetButtonDown("Jump"){
          velocity = new Vector2(velocity.x, impulse);
     }
}

This works, but there's a hidden mechanic in most of the platformers you play that you might not realise: you can actually press the jump button right before you land, and jump immediately as you hit the ground. If this feature is absent, your game will feel glitchy. The player expects this leeway. This means we're going to have to record when, not just whether,  the jump button is pressed.

Disclaimer: Unity has a function, Input.GetButtonDown(). I don't trust it - I find sometimes it just doesn't register. So I work around it with Input.GetButton, which returns true every frame the button is pressed.

Better version

My code looks something like this:

void Update (){
     bool input = Input.GetButton("Jump");
     if (input && !lastInput){
          jumpPressedTime = Time.time;
     }
     else if (!input){
          jumpPressedTime = 0;
     }

     if (grounded && Time.time - jumpPressedTime < jumpPressLeeway){
          velocity = new Vector2(velocity.x, impulse);
          jumpPressedTime = 0;
     }

     lastInput = input;
}

So a couple of things at work here. First, the time we pressed the jump button is recorded only if the previous frame, it wasn't pressed. Every frame we're grounded, we'll test the current time against the pressed jump time, to see if it's less than our jumpPressedLeeway variable (which is usually like 0.1). Then we jump if that's true (if we're grounded).

Setting the lastInput at the end is essential to check if this is the 'JumpDown' frame.

Disclaimer: I haven't tested this code. How fun! Check it, see if it works. Let me know what's wrong!

You can also add other conditions to the jump check. If you have a double jump, your condition may want to read:

if ((grounded || !hasDoubleJumped) && Time.time - jumpPressedTime < jumpPressedLeeway)

This way, grounded and not having double jumped will both satisfy the jump condition - but you'll also have to add:

     if (!grounded)
          hasDoubleJumped = true;

Then when you land, set it to false again.


That's it for now. Soon, I will go into doing slopes with this kind of system. I will also discuss jumping in a little more detail as it pertains to the system of checking downward for stuff. You'll see.

Thanks for reading!

Until next time: stay real, natural numbers.
-mysteriosum(the deranged hermit)

Monday, 20 January 2014

2D Platformer Collision Detection with Raycasts, Part 2: Lateral Movement

This is part 2 of a series; click here for part one.

Hello again, faithful squirrels - err, readers. This is the second post in a series wherein I discuss my home-brewed 2D platformer physics in Unity3D. Last time I talked about the basics of Raycasts, then applied them to movement in one direction (gravity). This time I'll be talking about applying my Raycasting method to lateral movement. The process, as you may have guessed, is the same for lateral movement, so it won't be as long to explain as gravity since we're already familiar with the method. I'll then explain jumping, which is also super easy (1-5 lines of code, tops).

DISCLAIMER: I discovered after writing the last article that Unity implemented native 2D physics and collision detection in a version of Unity I hadn't updated to yet. I've implemented my whole thing with 2D box colliders and 2D Raycasts, but it works the same either way. So I'm going to continue this tutorial with 3D box colliders which will be handy for anyone doing a 2.5D platformer, or for other fun experimentations.

Recap

To implement gravity and collision detection, I use the following steps:


  1. Set up variables, like gravity and max fall speed, and gather information about the GameObject;
  2. Apply acceleration to my movement: in this case, downward acceleration due to gravity;
  3. Determine whether it makes sense for me to check below me for collisions (am I on the ground? or if not, am I falling?);
  4. Determine where the rays I'm casting will begin (a few of them in a line);
  5. Determine the length of all rays (they'll be the same, and check as far as my downward velocity);
  6. Cast each ray and ask if it hit anything;
  7. Use the result (if I'm on the ground and I didn't hit something, now I'm falling, and vice versa).
Here's what the rays would look like if we drew them:


For checking above, the lines will look identical but face up, and for lateral movement they will start along a vertical cross-section and reach out to the side.

Let's Boogie

Now let's take a look at the system applied to lateral movement. Here's what the code looks like:




There are lots of similarities but also some key differences. Let's go through them.




Input

Since we're now responding to the player's input, we have to get it from somewhere. My input code looks like this:




and that's it! Unity's inputs are generally set up well enough for this purpose, but if you want to play with it then go to Edit > Project Settings > Input to look at the data structure.

One thing to note about this is I use Input.GetAxisRaw instead of Input.GetAxis. The difference between the two is when it comes to keyboard input. All Input.Axis functions return a float between -1 and 1. When reading input from a keyboard, Input.GetAxis moves slowly toward the limit rather than snapping to it (which GetAxisRaw does). For example, if I want to move the character left using the keyboard, I'll press A or the left arrow. The first frame, using Input.GetAxis, will return something like -0.15. Then each frame it will go up by that amount until it caps at -1. This isn't what we want here, since we'll be multiplying things by our input. So, we use GetAxisRaw, which will only ever return -1, 0, or 1 when using a keyboard.

Applying movement

This was simpler with Gravity. We just checked if the player was grounded, and if they weren't, we subtracted from their vertical velocity (capping at a maximum fall speed). This is what I do to apply lateral movement.












I like to work with local variables - in this case newVelocityX. In C#, there's no way to alter one component of a Vector directly (in Javascript you could write "velocity.x += acceleration * input").  So, it's simpler to work with a new variable.

We're going to deal with an input of 0 differently, since that's deceleration and not an acceleration. Add acceleration * horizontalAxis to newVelocityX, then make sure it's not gone past the maximum speed. The principle is similar for deceleration.

Raycasts

This operation is nearly identical to that of gravity, with one exception: the direction of the rays is determined by our velocity. See here:



If we are going left, we check left, otherwise we check right. If there's no input we don't check at all. If the player holds a direction towards the wall, we know it connects on that side. There is the unfortunate side effect of doing a Translate of 0 every frame this happens but the pros outweigh the cons here. If I want to code Megaman's wall jump for example, I just have to set a variable like "bool wallHanging" to true if one of my rays connects. Then I can use the input and invert it to determine the direction he'll be jumping away from the wall (or hitInfo.normal for that matter).

Only the beginning

From here, the possibilities are endless. Try something fancy! For starters I suggest figuring out how to check above the character as it's jumping, to react to ceilings using this method.

I never even got into layer masks in this explanation. A layer mask will let you pick and choose what objects you want to detect. So if you want to make a platform you can jump through from underneath, just tell the rays you send up not to detect those platforms. Then when you're falling the downward ray will find it and you'll be golden.

Using this method you can implement: moving platforms; slanted surfaces; icy surfaces or other movement detriments; or a whole thwack of other things I can't think of right now.

I am really enjoying doing this sort of work, and I believe my next tutorial will be about bouncing off walls (like for an astronaut in a pinball machine, let's say).

Thank you so much for reading. Until next time, keep moving, kinetic beings!

-mysteriosum, the deranged hermit.

Saturday, 4 January 2014

2D Platformer Collision Detection with Raycasts, Part 1: Gravity

Hello, squirrelfriends! Today I'll be writing about one of the things I've done in Unity: specifically, coding my own collision detection using certain aspects of Unity's physics, and simple maths & physics (for Recess Race!).

Why not just use Unity's physics?

Good question! The short answer is Unity's physics are meant for 3D movement, and they're great for that. Their basic character controller uses a capsule collider. It's pretty essential for 3D, but it doesn't work for classic SNES era 16 bit platformer tropes.

Consider this image:
Wile E. Coyote wishes he could do this

Now, that's not realistic. You can't stand like that! but it makes sense when you think about the strictly-rectangular collision detection they had at the time. It was a hardware/software restriction but they ran with it and made the restriction into an asset. Here's the same image with an approximation of his hit box:

He benefits from intangible toes, forehead, and right fist

The tiny part of the box touching the collision tiles supports him. Walk any further and you fall off. This feels very fair for the player: he's allowed to put his centre of gravity over a pit, but he can't support himself with just his toes. A little 'cheating' is enough. It also allows for what feels like really close calls - if an enemy swoops down and skims your helmet, but you don't get hurt, it creates a "WOW that was close" moment, which is fantastic.

Consider the same scene with a capsule collider now:

Paint capsules!

The only point that will touch the floor on a capsule is the bottom point. Since it's not touching anything, you'll fall. Then the curve of the capsule hits the corner of the tile, and you slide away from the wall and you fall. It's a different effect, and not one I'm going for in Recess Race,

Build it from the ground up (using Unity's construction materials and power tools)

So it's clear we can't use the packaged Unity character controller. It is impossible to separate from the capsule collider. So we'll have to build our own, using a box collider!

Aside: Unity Colliders, Events, and Rigidbodies

When you make a new script in Unity, it gives you the Start() and Update() functions built in. These are events that are called when Unity feels like it. Among these are the OnCollisionEnter() and OnTriggerEnter() functions. Their utility is paramount, but there are some issues. Both of them require having a Rigidbody class on one of your objects. It makes sense, because if it didn't discriminate it would be checking every collider against every collider every frame and that would take aeons.

Solution: put a Rigidbody (Component > Physics > Rigidbody) on your controller, uncheck 'Use gravity', open the Constraints class and check all of the boxes. This way we can have it use the Collision events and also control its movements at all times.

This is what your Rigidbody component should look like

Intro to Raycasts

If you're familiar as heck with Raycasts, a "Ray-man" if you will, you can skip this section.

Setup

The Raycast is a versatile tool in Unity, and it's surprisingly cheap. You can probably do hundreds of raycasts per frame and it won't do much to slow down your processing speed (but, the fewer the better).

A basic raycast in code looks like this:



This bit of code shoots a ray straight up from the object's pivot (transform.location) and tells you if there's anything above you (Raycast always returns a boolean). Not super useful on its own, but that's just the beginning. There are multiple ways we can call the Physics.Raycast function (12 overloads!). Let's add a couple more to add some more utility:


Here, we've added 2 new arguments to the Raycast function: a float, 'distance', and a RaycastHit. The float just tells the engine how far to check. In this instance, it will only check as far as I'm going to move this frame, which is very useful. It will tell me if I'm going to hit the ceiling! (This code is a little dubious and I'll explain why later).

The other addition is the RaycastHit, which uses the 'out' keyword next to its argument because it receives information (is assigned values) from the function if the Raycast returns true. This is supremely useful, as it tells you exactly how far the ray travelled before it connected, the normal of the surface it connected with, as well as a reference to the collider it hit. We will be using this to great effect later on.

The last thing we'll add to the Raycast function is the Layer mask. This tells the system to look for (or ignore) specific Layers. In Unity, you can give every object a different layer (up to 32 different ones), so this is handy when checking for collisions. In this case, I've set my collision objects to the layer, "normalCollisions". Here's what it should look like in code:



You can use a layer mask to check more than one layer. This post has an explanation of how.

So now we have a ray that only looks for collisions, it's time to make multiple rays shooting out in every direction to look for collisions ^_^

The Nitty Gritty

This is the method I've developed. It's probably been developed other places, but I don't have a single place to attribute my ideas, just a lot of bits and pieces which culminated in this system.

The Gist

The idea is to shoot a number of raycasts out each side of your box collider, and if any of them connect with something, do stuff. If you're checking underneath you, you're now grounded. If you're looking left or right, you hit a wall, and if you're checking up you've hit the ceiling. It's elegant, but tricky.

Setup

So first, let's set up some variables. Here's what I've got

Disclaimer: I use FixedUpdates for my velocity calculations, and update movement in the LateUpdate() function using Time.deltaTime as a multiplier. This gives precise, predictable movements.


This should keep us going through most of the explanation
Here's what I have in functions already:
  • set up of Layer Mask;
  • definition of 'box' and 'position'. On their own they do nothing, but it saves us a lot of typing later on. Mark every time I use 'box.x' I could instead type 'collider.bounds.x' but it's much longer;
  • and the application of movement at the end. Since it happens after everything else (LateUpdate), I can use the velocity variable knowing the object hasn't moved that distance yet.

Gravity

Let's start with the simplest operation: gravity. Gravity is present in every platformer (uh, I think), so it's a good place to start. This is all in the FixedUpdate() function.

Here's what the final thing looks like:



First, let's the actual gravity numbers to the velocity. We do this first so we can predict its location when we look for the ground.



Next, I figure out exactly where I want to check beneath me. For this I'll use the 'box' variable I define at the beginning of my FixedUpdate. One of the tricky things about Raycasts is that it only connects going in to a collider, not coming out of one. We can take advantage of this, though, by starting the rays in the middle of the player's box collider, so that it doesn't ever detect it, but will detect anything outside.

My method for determining where each of my rays starts uses the Vector3.Lerp function. I figure out where  my first ray and my last ray will start, then use the Lerp function to iterate from one to the other. These are vertical rays, and will all start from the same y position in the scene.

We also have to determine how long we want these rays to be. If we're on the ground, just using a flat number (in this case I'm just using my 'margin' variable). Otherwise, we want to base it on our velocity. The trouble is, if we're falling, our velocity is negative, and Raycasts need the 'distance' variable to be positive (this is the dubiousness I spoke of earlier). So, we're going to check down only if our 'falling' variable is set to true, which happens if our velocity is below 0. Then we have to make it positive with a Mathf.Abs() function. Also add half the box height since we're starting in the middle of the box.


These two points should look like this on our box:


All of our rays will start between those two points (inclusively). To iterate between them, use a 'for' loop. Before going into the loop, set up a boolean so we can use it afterwards.



Use the Lerp to pick the start point, then add your already-determined ray length.

Finally, when we determine if all of the rays hit or not, we deal with the results.

There is a mistake here - I multiply the ray's distance by Time.deltaTime which is unnecessary and causes you to float to the ground. Ignore it!


If we're in the loop, and we meet something, we're going to want to get out of the loop so the next ray doesn't set connected to false or something silly. You'll also want to make sure you're flush with the ground, by immediately translating down to the surface, and set your vertical velocity to 0. If you're not finding anything, you're no longer grounded (then the rest takes care of itself).

Also note: you can slip in an Event or Delegate function to call when you land (or as Unity likes to do, use the "Send Message" function for something like "OnLand"). This makes it easy to set land animations, or for other objects to pass functionality for this kind of event (like if you want to poison your character and have it end when they hit the ground or something).

That's it for now

That's it for gravity, really. There are fancier things you can do like determining the slope of the platform you're on, or move with a moving platform, which are all pretty easy to do with this foundation.

All the code is in screen shots, deliberately - I have always found it more beneficial to type stuff out yourself, even copying it directly, than pasting it and checking to see if it works. I hope you agree!

Next time I'll be talking about moving left and right and hitting the ceiling. However, the techniques are largely the same, so I encourage you to give it a go yourself :)

Thanks a lot for reading. Until next time!

Stay grounded, earth wizard.

-mysteriosum(the deranged hermit)


Wednesday, 1 January 2014

A New Year, A New Process

It has been a while since I've posted on my devlog - too long. I haven't felt like there's been enough to update you all about, I suppose. Now, there is! It's the new year, and new things are happening.

First, I, inspired by my sister, The Singing Nerdess, have developed a routine. This is something I will follow to the best of my abilities every day in the new year. The goal is to get my doing stuff, as often as I can.

Routine, process, habit, practice - all words describing the ritual execution of specific tasks to the end of being productive and building skills and experience. This article does a very good job of describing why we should (as creative people) develop a routine (called Process here).


My new process is this:


  • Every day I will:
    • get up at 7;
    • do 30 minutes of yoga;
    • do 15 minutes of calligraphy (general practice or letter-writing);
    • work on Recess Race for 4 hours;
    • miscellaneous internet-related tasks (web site, twitter, blog, forums);
    • act or write for 30 minutes.
These figures are all guesses, and I may do more or less depending on how I feel or time restrictions or whatevs. They should resemble my weekly averages, on the other hand. 

I have completed the majority of the new design document for Recess Race. It's a bit of a bummer because I wanted to get it done for today (New Year's Day). But, it's the holidays gosh darn it and I enjoyed myself. That's important too! Anyway, the whole point of the article referenced above is that you don't need goals. Goals hold you back in a weird way. They are somewhat counter-productive, which is counter-intuitive, but I certainly feel it happening now. Don't worry about what you have and haven't done, just do stuff and it will be done eventually.

"It doesn't matter how slowly you go, so long as you never stop."
-Paraphrased

I believe I can keep up this routine, and I will be all the better for it. If you're a creative person and don't feel like you're getting enough shit done, I highly suggest this method. 

Thanks for reading. Until next time!

Stay consistent, rolling stone.

-mysteriosum(the deranged hermit)