Lesson 53 - Introduction to the Unity Physics Engine

Tutorial Series: Introduction to Unity with C# Series

Previous Article  |  Next Article


Transcript

Alright, so with everything we've learned up to this point sort of tucked away in our toolkit, what we’re going to do now is turn our attention towards quickly building another project that this time makes use of some more powerful features of Unity. The physics engine in particular. So in this lesson I'll introduce you to some basics of this underlying physics engine, which I think you'll find quite intuitive and rewarding to work with. Now although the term itself, the term physics engine, it might sound daunting to those of us, myself included, who are unfamiliar with things like calculus or calculating forces and so on, in reality it's something that ultimately simplifies the emulation of a multitude of complex interactions that you can have in a game world. This all requires not much greater understanding then basic common sense and sort of plugging in a few specific methods and properties that then handle the sort of heavy lifting in the background, making the resulting calculations in the background.

So you may be wondering then, “Why didn't we start off with the Unity physics engine to begin with, if in many ways it's a simpler approach than the roll-your-own-solutions that we came up with in the first project? ” Well I delayed looking at the physics engine for a few key reasons. One is that I wanted to start off with an approach that was a stripped down as possible. You know, just moving objects by their transforms, and using simple handmade formulas of sorts, to produce a dynamic world that we can easily understand how it's working in predict how it will behave. By contrast, one of the sacrifices of using the physics engine is you lose some granularity of control. There is just a lot more hidden behind a sort of a black box which, in this case, are all those hidden calculations you're handing over to Unity to figure out for you. And because of this, it's a bit harder to employ the physics engine to result in a very specific end result rendered on your screen, and then also understand at a granular level how that's all occurring.

So, with emulating natural physics, there's just a lot more that goes into moving things than just artificially manipulating the transform, and understanding this interplay I feel is not very conducive to learning game design for beginners. But having become familiar with Unity through the previous lessons, you're more than ready to delve into this subject matter. And besides all that, the game that we've made in previous lessons was a fairly linear, sort of top-down game and there were very few physics-type emulations that we had come up with. There were, you know, a few collisions we needed to emulate. For example, when we sort of hit the boundaries of the game world or the enemy, and of course movement itself, but as we saw there wasn't really anything fancy needed to make our game world behave as we wanted to. So the physics engine becomes a lot more useful when creating a more realistic world, where the interaction between objects are much more familiar to the ones that we see in our world. So that's why we’ll be using the physics engine while creating a classic style 2D side-scrolling platformer.

So being viewed from a sideways perspective, we now have a bunch of notions that we didn't have before, such as that of gravity including jumping and falling bodies with a sense of mass. We also have a notion of forces; sometimes opposing. You know, hitting, pushing, exploding and so on, and we have collisions. You know, between enemies, walls, obstacles and a slew of other things. As well, we also have the resulting force changes that might occur from all these different occurrences happening in the context of one another. So with all that said, let's make a fresh start. Start a new project. Open up Unity and create a new 2D Project called Physics2DSideScroller. And we’ll also create a new scene. So, we’ll just say MainScene. As we did before, one of the first things a lot to do is set up the Main Camera. So just like before, we’ll have, it will be 720p. So you’ll want it to be an orthographic camera, which is basically a 2D plane, or rather the perspective, which is sort of three dimensional.

And so for the size, it’ll be 3.6, which is half of the screen width in units and you get this actually by dividing the screen within pixels. So that 720, and then divide that by 100, divide that by 2, and you get 36, half the screen width. And that's represented in units, right? So orthographic, 36, and we'll put this resolution to the same one we had before. If you didn't have this already there, just remember to make that 1280, and that 720, fixed resolution, and create that preset. We’ll sort of recycle some of our assets from the previous project to make this new game. So import our familiar CheeseHead sprite. Actually, a slightly updated version of it. And we'll set that to Point. It’s going to be another pixilated sort of looking game. And we'll create a new GameObject. We'll call that CheeseHead, and we'll set it’ SpriteRenderer with the sprites, so Add Component. And may as well start creating some sorting layers here, some basic sorting layers. So go to Sorting Layer, Add Sorting Layer.

We’ll create a Background layer that we’ll make use of probably later. And Enemy layer and Player layer, and just our default objects that we do not set to a layer, we make sure it's in front of the Background. And of course, we’ll set this to the Player layer and order at 0 for now. Alright, so the two main differences between an ordinary GameObject that doesn't interact with the physics engine, and one that can interact with the physics engine is basically just a couple of basic components. One is called a rigid body component, which gives the game object some type of mass in the physics engine, as well as a collider component which does exactly what it says.It detects whether or not the collider intersects with another collider. So let's add these components to our CheeseHead character, giving him some more corporeal qualities than he had before. So first off go to Add Component, and we’ll say Box Collider, and make sure choose Box Collider 2D, because this this is the collider that specifically used for a 2D game. And we’ll set this to .5 and .5. And just to show you how I came up with that, if you go your scene view here, I'll turn off that camera gizmo, so you can see what's going on here.

Where is that? I’m not sure I do that, so I'll just instead move our CheeseHead a little bit. So yeah, in our scene view it creates this green Box Collider, it's hard to see. But that's our collider, it's a little bit offset from our actual sprite. But you can manipulate this to change the X value, the width of the collider in other words, and the Y, so just going to set this to .5 and .5. Whenever you make a collider, it's often helpful to go to scene view and set it that way. And so for rigid body, we’ll want to add that too, so choose again Rigidbody 2D, not the the non-2D version. And we’ll basically leave this as is for now. Alright, so now our CheeseHead has a Rigidbody, so it has mass, it has a collider. So let's press play and see how this affects our CheeseHead character. There you see it falls and keeps falling because it doesn't collide with anything. But otherwise, it's responding to gravity, so that's a good start. Just to show you, if I pause this while our CheeseHead is in freefall, if I remove the Rigidbody component. Now unpause, it no longer is falling, so it has to have the Rigidbody component.

That was only a temporary change while we were playing the game, so there is our Rigidbody once again. So what we're going to need now is a collidable floor, basically. So let's create another Collider. Might as well add it to the camera here, that’s the easiest way of doing this. So, Box Collider 2D. And, just again, I can set this in the scene view. We’ll make sort of a floor out of it. I’ll just use these settings that I found out before were optimal. -3.55 for the Y, offset 17.83 and 0.15. There you go, so we got a little bit of a floor there. Going to move the CheeseHead a little bit higher up, and now when we play the game, it should collide with the floor. And there we go, it no longer keeps falling, it now has a collider. If we take out this Box Collider, then now see it just keeps falling. So now we have two colliders interacting with each other, really easy. So now the rate of our CheeseHead game object, the rate that it's falling right now is determined by the global gravity settings. So for that, go to Edit, Project Settings, and in Physics 2D, you'll see gravity here is defaulted to -9.81, so minus because it's basically creating a force downwards. So let's say you do something really dramatic, let's say -500.

Let's see what happens there. So I'll press play, it looks like it just fell right through, our CheeseHead GameObject just fell right through the collider. So the physics engine just isn't basically able to catch that intervening space where the colliders meet. Because it’s just moving too fast, so in order to fix this we’ll want to set the Rigidbody collision detection to continuous for the CheeseHead. So, this just gives us a little bit of more precise collision detection. There you go, falls like a stone. Now it actually stops at the collider. So, I'm going to put the Physics 2D settings back to the original, which was -9.81, we’ll leave that for now. Also, may as well set this to interpolate, this is basically to make sure that there's no jitteriness when the object is moving. You might start seeing that at some point if you don't have interpolate on. So it basically just smooths out the calculated motion by the physics engine. Right, so we typically control how the GameObjects interacts with the physics engine, you know gravity in the world, other Rigidbodies, colliders and so on, in code by accessing the Rigidbody component with our familiar GetComponent() So let's create a script called PhysicsTest, we’ll attach it to the CheeseHead.

So PhysicsTest. We're going to use a new kind of Update() method which, again we’ll delve more into in the next lesson, called FixedUpdate() And in FixedUpdate() what we're going to want to do is, we'll get a reference to the Rigidbody component. So we’ll call it cheeseBody, a local reference in this FixedUpdate() method. And so, we’ll run GetComponent() which will return Rigidbody2D, that will be the type. And now that we have a reference to that component, we’ll say cheeseBody, see all these different properties and methods available to Rigidbody2D? Some of these you may notice from the component itself. First of all we’ll just access velocity, which should be pretty self-explanatory. So cheeseBody.velocity.y, which you’d imagine is is what exactly? Is the the vertical velocity. We’ll assign the entire velocity which is a Vector2. And it's a struct, so we’ll assign it a new Vector2() and 1 will be its constant velocity on the X. And for the Y, we'll just take in whatever the Y velocity happens to be. And we’ll set the Y velocity here, which normally gravity takes care of, but we’ll settle a case here. So, if (cheeseBody.velocity.y == 0) So if it's not moving vertically at all in other words, on the ground more or less. We’ll access the rigidBody, and we’ll use the AddForce() method, which all all this will become a lot more familiar in in subsequent lessons.

And we’ll pass into the out, it needs a Vector2. So for the out force, we’ll say new Vector2() and on the X, we won't give it any added force on the X axis. And we’l ljust give an added force of 300 for the Y axis, so this will sort of push the object upwards. Basically creating a jumping, you know an automated jumping mechanic. And so save that. Alright, so I’ll quickly play this, so you see the result of that very simple bit of code. See it’s moving constantly on the X axis because of that 1 value we gave it, and then when it's reaching zero velocity it bounces it. Right, so just little bit of a primer on what you're looking at here, first and foremost there is this new Update() method that we haven't seen before called FixedUpdate() That’s because it's really particularly used for the physics engine, which we'll look at in the next lesson, once ,again but thankfully this is the last Update() method that you really have to become familiarized with. I'll go into all of this in the next lesson, but for now all you need to know is that FixedUpdate(). runs exactly like Update() It gets called at regular intervals creating a loop-like state, just like Update() The only difference between FixedUpdate() and Update() is that the intervals are not tied into the framerate.

They occur at fixed intervals of every .02 seconds. So whereas Update() runs every .016 seconds at sixty frames per second, and .032 seconds at thirty frames per second, meanwhile FixedUpdate() will always run every .02 seconds, regardless of the framerate. So that's it that's the only distinction between FixedUpdate() and Update() but an important one. Now you might think this would produce consistent results, but it does. The fixed time step is actually necessary for the physics engine to follow a deterministic path. But anyways, put that thought on hold until we come across it in the next lesson. But I just wanted to give you a quick little primer on that. So what we did here is we affected the velocity, which is a physics property via the Rigidbody component. And notice we're not directly changing the transform, but rather determining the object's velocity relative to the global physics settings. And the result of that physics calculation itself then determines the object’s transform position. That's kind of what I meant earlier when I said it's difficult to predict exactly where your your GameObject that uses the physics engine ends up. You're kind of handing that all over to the physics engine.

Alright, so now what we want to do is add another physics object. So our CheeseHead can interact with it, we’ll sort of make a dummy GameObject. So actually, we’ll just call it Dummy and we want to import the familiar Eyeball enemy that we had from before. So put that point, and we want a SpriteRenderer. Put that on the Enemy Sorting Layer. And so for the enemy here, what we’ll do is we’ll give it a Circle Collider 2D. And of course a Rigidbody 2D, just like before. And I want to put this particular spot in our scene, so you can see the results of the interaction between our CheeseHead and the Eyeball. So the Circle Collider, change its radius here. Figured .21 is about right. And again, we’ll leave the Rigidbody pretty much alone for now. Let’s put the CheeseHead a little bit further back. Watch the CheeseHead interact with our Eyeball, now that they're both physics-based. Well they interacted, maybe not the way we're expecting them to. Specifically, our CheeseHead now is bouncing on its head, so that's no good.

That's easily fixed, here we just go to the constraints here for the Rigidbody for our CheeseHeadand, we freeze the Z-Rotation, right? So he won't fall over with the Z rotation, basically being immune to the physics interactions. And here, what we’ll do with the dummy is show you another thing. There is this property are called Is Kinematic. So when we make the Eyeball Is Kinematic, what happen? Well, it no longer interacts with the physics engine, but it still has a collider. So that's why a Cheesehead still interacts with it so this is useful for some particular scenarios. When you need a Rigidbody, but you don't really want it applicable to the physics engine. And another important thing for collision that you should be aware of, is there is a global collision matrix, where you can set what kind of colliders collide with other colliders. So for example, if we have a bunch of enemies with colliders, but you don't want them to collide with one another, but only with the player and the floor, platforms and so on, you can do this in the global matrix. So for the global collision matrix, go to Project Settings, Physics 2D, and there's our global collision matrix. Now, we’ll need to set up our own Collision Layers, sort of like our Sorting Layers.

So go here to the Layer on either GameObject, and we’ll add the layers that we need. So for example, we have a CheeseHead Collision Layer, and we have an Eyeball Collision Layer. And now we do is just set CheeseHead to CheeseHead, we’ll set our Dummy to Eyeball. And now in our collision matrix, there is our CheeseHead and Eyeball. So right now Eyeball can collide with an Eyeball, as well as a CheeseHead. So right now if we remove this this part, where the CheeseHead and Eyeball intersect, they won't collide with each other anymore, but they'll still remain colliding with other collidable element. So put that back on. Alright, we’ll talk a little bit about the Rigidbody that has somewhat obvious properties here, for example Mass. So you can make it more massive, a Gravity Scale, so how it reacts to gravity. You might want to change the Gravity Scale for your individual Rigidbody, rather than globally if you want to fix certain elements such as the floatiness of a particular Rigidbody. So if I make the Gravity Scale, well it's a little bit too large. Let's say just say 5. So five times the amount it will react to gravity. There you go, you see it falls a lot quicker and now its bounces are also shorter.

Another thing is the sleep mode for a Rigidbody. And so with the sleep mode here, if we start asleep, then basically when the game starts the Rigidbody for this for the GameObject it’s attached to, it starts off not interacting with the physics engine. It only starts to interact with the physics engine when some other physics element interacts with it. So that's useful for that, but we'll be keeping it on Start Awake for our purposes. OK that's about it for getting our feet wet with the Unity physics engine. Hopefully this gave you some sense for how easy it is to make some cool things happen using the physics engine. I find that one of the most fun things with using the physics engine is to press play and experiment with modifying values in the physics settings, or in the inspector for scripts with your exposed fields, or your Rigidbody component for example, and see how it changes the resulting interactions. Since when you press play, these changes are nondestructive. It's a really fun way to test things out in this sort of virtual playground type of sandbox, so by all means do that and test it out and have fun with that. But for now, we’ll leave all that we learned aside and we’ll learn more about how this works in code, particularly with how FixedUpdate() works in the next lesson.


Related Articles in this Tutorial:

Lesson 1 - Who This Course is For

Lesson 2 - What to Expect from this Course

Lesson 3 - Installation and Getting Started

Lesson 4 - Starting the First Project

Lesson 5 - Prototype Workflow

Lesson 6 - Basic Code Review

Lesson 7 - Game Loop Primer

Lesson 8 - Prototyping Continued

Lesson 9 - C# Fundamentals and Hello World

Lesson 10 - Variables and Operations

Lesson 11 - Variables and Operations Continued

Lesson 12 - Floats, Bools and Casting

Lesson 13 - If Statement Conditionals

Lesson 14 - If Statements Continued

Lesson 15 - Complex Evaluations and States

Lesson 16 - Code Syntax vs. Style

Lesson 17 - Variable Scope

Lesson 18 - Object-Oriented Programming Intro

Lesson 19 - OOP, Access Modifiers, Instantiation

Lesson 20 - Object Containment and Method Returns

Lesson 21 - "Has-A" Object Containment

Lesson 22 - "Is-A" Inheritance Containment

Lesson 23 - Static Fields and Methods

Lesson 24 - Method Inputs and Returns

Lesson 25 - Reference vs. Value Types

Lesson 26 - Introduction to Polymorphism

Lesson 27 - Navigating the Unity API

Lesson 28 - Applying What You've Learned and Refactoring

Lesson 29 - Constructors, Local Variables in the Update Method

Lesson 30 - Collecting Collectibles, Items and Powerups

Lesson 31 - Spawning and Managing Prefab Powerups

Lesson 32 - Implementing Powerup State Logic

Lesson 33 - Displaying Text, OnGUI, Method Overloading

Lesson 34 - Referencing Instantiated GameObjects, Parenting

Lesson 35 - Understanding the Lerp Method

Lesson 36 - Creating Pseudo Animations in Code

Lesson 37 - Understanding Generic Classes and Methods

Lesson 38 - Animations Using SpriteSheets and Animator

Lesson 39 - Working with Arrays and Loops

Lesson 40 - Debugging Unity Projects with Visual Studio

Lesson 41 - Camera Movement and LateUpdate

Lesson 42 - Playing Audio Clips

Lesson 43 - Routing Audio, Mixers and Effects

Lesson 44 - Adding Scoring Mechanics and Enhancements

Lesson 45 - Scene Loading and Game Over Manager

Lesson 46 - Understanding Properties

Lesson 47 - Controller Mapping and Input Manager

Lesson 48 - Understanding Enums

Lesson 49 - Dealing with Null References

Lesson 50 - Handling Variable Framerates with time.DeltaTime

Lesson 51 - Preparing the Project for Final Build

Lesson 52 - Final Build and Project Settings

Lesson 53 - Introduction to the Unity Physics Engine

Lesson 54 - Understanding FixedUpdate vs. Update

Lesson 55 - Movement Using Physics

Lesson 56 - Attack Script and Collision Events with OnCollisionEnter2D

Lesson 57 - Projectiles and Stomping Attack

Lesson 58 - Parallax Background and Scrolling Camera

Lesson 59 - Infinitely Tiling Background Sprites

Lesson 60 - OOP Enemy Classes

Lesson 61 - OOP Enemy Classes Continued

Lesson 62 - Trigger Colliders and Causing Damage

Lesson 63 - Multi-Dimensional Arrays and Procedural Platforms

Lesson 64 - Finishing Touches

Lesson 65 - Series Wrap


Comments

Please login or register to add a comment