Lesson 55 - Movement Using Physics

Tutorial Series: Introduction to Unity with C# Series

Previous Article  |  Next Article


Transcript

In this lesson, we're going to set up our CharacterController script, this time moving our Player1 character using physics, right? But before we move forward I want to just do a little quick setup, first tidying up and setting up our folder structure. So here with our Assets, create some new folders. So make one for Characters, and in there we will create folders for our CheeseHead and for EyeBall. And stick that in there, and create a folder for Scenes. Although we’ll only have one scene, but it’s good to be in the habit of cleanliness, being neat and tidy. And also for Scripts, we’ll have folders for Player. So for now we'll just do Player, Tests and World, and put the Test script here in Tests, and we're not really going to need it anymore. So we should remove it from our CheeseHead, or else it’ll just get in the way. Right, so this time we're going to do is we're going to start out with the Input Manager, mapping our user inputs through the Input Manager, as opposed to using the GetKey() method as we did before.

So we’ll go right to the Input Manager. So go to Project Settings, Input. And we'll set up the following axis that we’ll need, we’ll start off with 2 for horizontal motion jump and vertical motion as well. So, make that 2. And some of it's already predefined here, we’ll just leave these predefined settings. Actually, all of it's pre-defined. We don't really have to change anything there. So for vertical, well we'll just call this Jump to make it explicit what it does. And will set the positive button to the space bar. So you have to type in space, there's no negative button. There's also no alternative negative button, but for the alternative positive button we’ll just allow mouse clicks, so mouse 2, which I think is the middle mouse. Well, we’ll see. And so we’ll create a CharacterController script now. So go to the Player folder in the Scripts folder, and make a new script called PlayerController, and we’ll attach that right away to the CheeseHead GameObject. And we’ll start off by setting up a few fields we're going to need, so Rigidbody2D, call it CheesyBody. This will give us a reference to the Rigidbody2D component.

Float HorizontalMotion. This will get the, for the InputManager that we just setup for horizontal, it will return a float if you're using an analog stick, which we aren't. Although you could set that up with a gamepad, nonetheless it will return a float. With the analog, you'll be maybe a a non-whole number but in this case, basically even though it’s a float, it will be a whole number. Either -1 or 1 or 0 if there is no motion detected for the input. And we’ll store that to a local float called HorizontalMotion. And we’ll also have an int for MoveSpeed. And we’ll just assign those with initial values in the Start() MoveSpeed, say 3 for now, we’l tweak these maybe later to get just the right values for our character speed. Then CheesyBody, just use the GetComponent< >() to grab the Rigidbody2D. So now, we’ll use that HorizontalMotion field to store the result of our input. So, that's going to be Input.GetAxis() now this would return a float of a varying amount if again using something like an analog thumbstick. But we're not using any analog control, we're just want a whole number value. It's going to digital basically, so we’ll just use the GetAxisRaw() variation of this method.

Even if it's a small or even if it's a float value between -1 and 1 that's returned, it'll just return the whole number value equivalent. So I guess .5 will round up to 1 and .-5 will round to -1, and otherwise it will just stay at zero for idle basically. So GetAxisRaw() and we’ll pass in the string that refers to the InputManager axis that we’re referring to here, so that's called “Horizontal”. And we’ll just assign the value that comes out of that to the HorizontalMotion field. Now we’ll say if (HorizontalMotion != 0) in other words an input has been read. We’ll first use it to determine the orientation of the Player1 character if it's on the right side. Because the input returns 1, we’ll set the localScale to 1. Otherwise, if we press left, it’ll be -1 we’ll use that to set the localScale to -1 and flip the character to the left, facing left side. So we’ll say transform.localScale, not ular angles. LocalScale = new Vector3() we’ll pass in the HorizontalMotion.

And you know, leave the rest of the scaling unaffected. And now we have this impact the Rigidbody, we're going to use of course, FixedUpdate() So void FixedUpdate() and we’ll assign to the CheeseBody the velocity, we’ll say new Vector2().We’ll pass in HorizontalMotion * MoveSpeed, and because we're not concerned with setting the Y axis, through Input, we’ll just pass in whatever the Rigidbody’s existing Y axis velocity is. And notice here how, because we're working with physics right now, with the physics engine we don't use Time.deltaTime, it doesn't really make a lot of sense. So we didn’t have to multiply any of this to make sure that it has the same perception of speed regardless of the framerate. We don't have to do that because we're using physics. So I’ll just quickly test this out to make sure it's setup right. Yep, see localScale changes depending on if if our Input returns -1 or 1 from the GetAxisRaw() Input method. Right, now for jumping.

Let's go back to our script and we'll need another field. We’ll call it, a bool, JumpActivated and, in Update() we’ll grab this input for the jumping mechanic. So, below here will just have another conditional, if Input… We don't need to GetAxis() because we don't need a number of turns, so we’ll just use GetButtonDown() and that’ll grab the relevant button from our InputManager, just as well. “Jump” is the name of that button. If the “Jump” button is pressed, set the bool to true.,. And then, in FixedUpdate() we’ll simply have a conditional here that says if (JumpActivated) So, if that bool is detected by FixedUpdate() as being true, then we will use an AddForce() which goes back to what we looked at in the previous lesson. And it's going to need a Vector2 Force. So let's just say a new Vector2() and we're not going to be concerned with adding force to the X-axis, so we’lljust pass in the existing X-axis velocity. And we’ll add our force, we’ll give it 6. And there's an optional argument here that we’ll talk about more in a minute, called ForceMode.

So we’ll say ForceMode2D.Impulse, and we’ll then switch off the JumpActivated bool. So that solves the issue that we looked on the previous lesson. So go back to that if you're not too sure of what we're referring to in this code. Fix that. Now we should be able to jump. But look at that, I’m still able to use that AddForce() to jump as much as I want to, and of course if I'm going downwards, the force relative to the downwards velocity is going to result in less of a jump. See, it's just kind of more like an airbrake effect. Anyways, we're going to have to fix that. But before that, I want just briefly mention a bit of this, what this ForceMode is all about. So this is actually an enum. If I hover over that, it says enum and it gives us one of two possible choices. There's the Impulse that we used here, and Force. So I think if you ignored the optional argument entirely, I think it just defaults to ForceMode2D.Force. But basically the difference between these two ForceModes is ForceMode.Force is kind of like a constant pushing occurring over successive frames, whereas ForceMode.Impulse is a sort of a single explosive force, typically meant to be applied on a single frame.

So that's why we chose this for the jumping mechanic. Right. So we can fix this issue of having infinite jumping basically, as well as being able to manage all of these different possible states that our character can be in, by using enums for holding exclusive states. Remember my analogy of how enums are like buttons on a blender that only hold a single state. You know that exclude other states when it's in any one state? Well here we’ll make good use of this for holding exclusive states with enums. So for example, you can't be both moving left and moving right, or idle for that matter, at the same time. So we can use those exclusive states for one enum. And meanwhile, have another enum for, you know if you're airborne or grounded, you can't be in those two states at the same time. So we’ll go on down the line with all of these different states that we can have for our characters and separate them with different enums for each set of exclusive states. And what's great about this state-keeping functionality is it's really easy to understand what's going on when you use the enum to sort of summarize the code that determines that enum state.

If you don't really understand what I'm getting on about, you'll see if you follow along. Enums will do a lot of heavy lifting for us as we move along here. So let's create a new script called PlayerState to manage our different player states. And we're not going to need to attach it to a GameObject, you'll see why in a minute. We're going to do something a little bit special here that's different from the other scripts that we've been writing. So call this PlayerState. So for our PlayerState MonoBehaviour, we're going to actually leave that for now and just create a enums in the script. So we'll say public enum Horizontal for one state, for one set of possible states. So Idle, and here will make use of the numerical, the ability to assign numerical values for our enums. If you need to refresh your memory on enums, just go back to that lesson. I can’t remember a lesson number, and this will make sense. So MovingLeft = -1, and MovingRight = 1. And then we’ll move on to another enum that we’ll need for a Vertical, we’ll call it. And we’ll be either Grounded or Airborne. Those are the two mutually exclusive states we can have, vertical states for our character.

And also, make a public enum DirectionFacing because that also might be important for summarizing in our code, what direction our player’s facing. So Left; again we’ll make use of these numerical assignments that we can add to the enum. Right = 1. Right, now before we make use of these states we need to incorporate them as fields within our main PlayerState class. Which we’ll then use as a sort of single access point for the rest of our scripts to manage the state of these enums, depending on what's happening in code. Now, the obvious way to do this is just to make these fields static so that they're easily accessible from anywhere in code, right? But I also want you to be able to see these states, particularly during testing, in the inspector. But Unity doesn't allow you to expose static fields in the inspector. So to solve this, I'm going to take the opportunity to show you a bit of an intermediate to advanced concept, which is actually not that difficult to to write in code, called a Singleton Pattern. This is part of a topic called design patterns, which is pretty much way outside of the scope of what we're dealing with in this lesson, but basically design patterns are just an accepted set of standards for solving coding problems.

Programmers over the years stumbled on these common problems and solutions in code, they recognized these common patterns that exist throughout coding regardless of programming languages, typically. And sort of settled on accepted ways to go about solving these common problems. That's the basis for design patterns. Now a Singleton is a very common design pattern, especially in Unity. I think you'll see it floating around quite a bit in code out there, and basically a Singleton is a design pattern which ensures that you have only a single instance of a class that you're creating. That's pretty much it, it just limits the class instances of the the Singleton class that you create to one. And another benefit when creating a Singleton is usually to have this single point of reference by making the instance static, which allows outside scripts to easily access the singleton instance. In our case we're going to make a Singleton with a globally-accessible static instance in order to get and set relevant player states. So we’ll then be able to see these states in the inspector because those states aren’t going to be static themselves.

Alright, so maybe a little confusing, the wording, but hopefully you'll understand what I was driving at when we start writing the Singleton pattern here. So for our PlayerState class, we’ll need a property for the instance to hold the single instance that we're allowing for this PlayerState class. So we’ll say private static PlayerState, so this will be the backing field, _instance. And then we'll say public static PlayerState, capital I Instance, and then we'll have a getter. So for the getter, we’ll say… This is kind of where the magic happens with Singletons, so if the the backing field is null, then the backing field will get a new GameObject. Which we’ll kind of assign the name of it here, calling it “PlayerState”. And we’ll also at the same time we’ll add a component of this Singleton script, right? PlayerState with the AddComponent() method. And a getter isn't quite a getter unless you return that backing field. So if it's if the backing field is null, we create the GameObject and attach an instance of this script to that GameObject. Otherwise, if that instance then exists after it's already done this and we run the getter again by accessing the instance and script somewhere else, then we'll just return this static instance.

And here we’ll have, that instance will have a public Horizontal enum, which we’l also call Horizontal, capital H. C# is good about detecting when you’re referring to the type versus the field name like in this case. So hopefully that's not confusing, but shouldn't be confusing when you're accessing it in code in your different classes as we move along. We’ll say the same for Vertical, public DirectionFacing, call it DirectionFacing. So yeah as I mentioned, this gets instantiated when we first run the getter for the instance anywhere in code, which is basically any time we we retrieve a field within the instance. So for example, when we do this in the PlayerController. We’ll just say PlayerState.Instance, running the getter, we’ll set Horizontal to Horizontal.Idle to start off with. Maybe this will give you a little preview on the usefulness of how we will be using these PlayerStates as we move forward. The Vertical is going to be Vertical.Airborne, and DirectionFacing, it's going to be DirectionFacing.Right, to start off with. And I’ll show you how that now creates that getter, when we run that getter, it creates that GameObject PlayerState. And from here, we’ll be able to see our publicly exposed enums since these are not static themselves, we’ll be able to see these when we're testing.

So that'll be pretty useful. So yeah, probably the weirdest thing about Singletons on first glance is that the class has an instance of it inside of itself, right? Seems strange that you're allowed to do this, but there's nothing preventing us from doing that in C#. So anyways, we could have chosen other ways to view our PlayerStates. Maybe it would have been more convenient to simply output it to the screen with something like the OnGUI() method, but I figured you already know how to do that. We did that in a previous lesson with our scorekeeping and so on, so I took this is a chance to show you a common coding concept. So don't let the peculiarity of it put you off too much. If you don't like how Singletons appear and how they operate, you don't have to use them. It is just as an option that you'll see often and taking this opportunity to show it to you. Alright, so now let's make use of these states in the PlayerController. So here, we flipped our localScale depending on our motion, we can set the PlayerState for our DirectionFacing enum. It's going to be somewhat different than maybe you're expecting. So what we'll do is we will do this, we’ll take the HorizontalMotion, and then we’ll cast that numerical value, which it is 0, -1, or 1.

We’ll cast it to the enum DirectionFacing and that works. I can set our enum state, again because of this handy little option to have a numerical equivalent for our enum. Alright, so to fix the infinite jumping problem that we had, we can just right here we’ll just say if (CheesyBody.velocity.y == 0) then PlayerState.Instance.Vertical = Grounded, Vertical.Grounded. And here where JumpActivated, that conditional is, we’ll have an added conditional within that to make sure we’re first Grounded before actually allowed to apply that. AddForce() If ( PlayerState.Instance.Vertical == Vertical.Grounded) then we’ll allow the AddForce() to be applied. And we’ll set, also the state PlayerState.Instance.Vertica = Vertical.Airborne now. And we’re not really going to use this, well not anytime soon at least, may as well set that Horizontal PlayerState, here in the Update() Again, using that casting functionality that we saw before. So we'll just take the HorizontalMotion this time and cast it to Horizontal enum.

Terminate that, alright. So let's just see how this all looks when we press play. So let's watch our little states here as we're playing the game. Wait a second, something is wrong. I'm not sure I made that error, Vertical.Grounded. I'm sure you saw that before I picked up on it. So right, so once again we’ll test this out, keeping an eye on our different states. Can’t jump unless I'm grounded. And well, not much going on in the game but it's a start. So before we wrap up here, let's just refactor this a little bit. Just to get the stuff out of FixedUpdate() there's not much going on there but just to make it obvious as to what's happening.. Let’s create a couple of private methods to hold this functionality, so private void WalkMotion() And for that we’ll just take that one line, that’s all we need. And call that here, WalkMotion() and for Jump, we’ll just say private void JumpMotion() and call that here. Alright, so that's about it for this lesson. In the next lesson, we will look at making an attacking scrip. I’ll see you there.


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