Lesson 58 - Parallax Background and Scrolling Camera

Tutorial Series: Introduction to Unity with C# Series

Previous Article  |  Next Article


Transcript

Alright, today I'm going to show you how to create that cool kind of scrolling effect that you see in platformers, where basically the background elements scroll at a different rate than foreground elements. It's it's actually called parallax and it's quite easy to do so we'll jump right into it. The first thing we'll do is import some new sprite assets for a background. So here in the Assets pane, create a new folder called Background and important new assets, and we'll want to import each one of these separate Background elements, and put them all to "Point". And now, we'll want to create all of the GameObjects as well as the children for holding these Background elements. So over here, just create all these GameObjects one by one, and this will be parented to the Main Camera, the furthest Background GameObject in the sky. BG1_Foreground. And then as a child for that, we'll put the Floor sprite there, we'll do that in a minute. We'll set up the rest of these, we'll just copy and paste these and rename them accordingly. And BG2_Closer.

And that will be the mound sprite. BG3_Close. That will be the hills sprite, and we'll add a slightly different variation of this for the fourth Background element. BG5_Far, and that'll be for clouds. And another set of clouds that are even further. That will be on BG6_Farther, and this will be the five version of the clouds. Alright, so let's now set these Sprite Renderers accordingly. So this will be Background and let's just place it to order in layer 5. That will be 6_Sky. And ,inherited the transform properties of our Camera, so just let's zero that out. Bump it up a little bit. And for the Floor, it'll be this one, and we'll set it to the eleventh order in layer and we'll offset this here. -3.4, should be good. And for this, offset it to -2.52 and if you put this one, 2_Hills, put it on order in layer 10 on the Background layer. And for BG3_Close, offset it to -2.4 and put the 3_Hills sprite there. Background Layer 9 and all we'll reuse that sprite here for the BG4_Far child. So create a Sprite Renderer, and assign it. Make it ordered 8 in layers and we'll want to offset that with 4.6. Just so visually it's a little bit offset from the other hills and you don't quite catch the pattern as easily on the X axis. And -0.17.

And we'll actualy flip it too, so that it's a little bit less of a pattern with the other three hills sprites because we're reusing this, right? And actually we should just darken this a little bit to add some variation. So darken it with that hex color. And for the BG5_Far, put the 4_Clouds on that Sprite. And it will be Layer 7 and for the furthest clouds, we'll offset it first. And we can put 5_Clouds there. On the sixth order in layer. And well, we can flip that too to make it a little bit different, otherwise it looks also kind of pattern similar to the other clouds. And we'll set the opcaity slightly differently here, you'll see why later, we'll add a little nifty effeft that creates like an overcast cycle. So we'll start off by setting the opacity as... Having a little bit of opacity start out with. Alright, now we are ready to attach some script that handles the parallax for each individual GameObject. We'll first create the script and attach it later. So in Scripts, create a new folder called Parallax. And create a new script called ParallaxController. Alright, so let's start off with a couple fields that we can set in the inspector later to each individual script. So public float Speed for our scroll speed.

And then for elements that have some automatic scrolling, like clouds will have, a public float AutoScroll as an option to set in the inspector. And we'll also want to reference to the MainCamera. And we'll want a Vector3, we'll call it ParallaxFollowCamera. And we'll also have a private float Scroll., And a private float Offset. And in Start() we'll assign the MainCamera = Camera.main. And parallax. And ParallaxFollowCamera will be the transform.position. And Offset will be transform.position.x, this will just be concerned with the X position when it starts, it will grab that from the inspector for the Offset. And because this is camera-dependent code, and we learned in previous lessons camera-dependent code should be all handled in a LateUpdate() right? We went all into that in the lessons that dealt with LateUpdate() So we're going to want to use LateUpdate() for this. And we'll say Scroll += AutoScroll. Whether or not it has an AutoScroll. If it does, we set this in the inspector.

Otherwise it will just be zero. And we'll say ParallaxFollowCamera..x = MainCamera.transform.position.x * Speed + Scroll + Offset. And then we'll just assign that back to the transform.position. So depending on the Speed, that will influence how much of a scrolling we get and we'll set that again in the inspector, for each GameObject. Further away GameObjects scrolling slower than up close GameObjects of course. And so now we'll attach these to the relevant GameObjects that will require this script for the parallax effect. The Sky will be stationary, so it's not going to need it. But we'll need it on, we'll put it on the parents here, so BG2_Closer. And that'll be this value: 0.15 for the speed. For BG3_Close, also add it to the parent and give it a speed of 0.45. So the closer to one that this float value is, the further away the object is going to appear, travelling slower in other words. It's a multiplier but it's basically going to have the effect of dividing the speed of the camera and by that amount. So the lower that amount, the slower that the element will scroll. And for BG4_Far, we'll set it to 0.55. For BG5, we'll set it to 0.75, and this is going to have an Auto Scroll value because the clouds, we'll want it to automatically scroll.

So put it to a relatively minute value that will increment on each frame by that amount. And for the furthest element, our further set of clouds, we'll have it divide the Main Camera scroll speed by that amount, actually multiply by that amount. And Auto Scroll a little slower by that amount. Alright so temporarily we'll see the sum of this in effect. We're not going to have any scrolling yet because we need to have the camera follow player, right? That's what we'll want, so we'll set that up now in a separate script. So in the World folder, let's create a script called CameraController and I'l try to write this fairly quickly and describe some of it as we're going along. So public GameObject, we're going to need to reference the CheeseHead, and we'll want to have a state, an emum state for the camera, so as to determine how the camera behaves. So we'll create an enum state here called public enum CameraState and it will be either Stationary, Following or Recemeter. So it'll be little bit more dynamic than just following the player. We'll see pretty soon what I had in mind.

So here we need a public CameraState. So we can see the the state keeping in our inspector, we'll have a public state. May as well just call it upper-case CameraState for consistency's sake. And in Start() we'll just give it a default value as CameraState.Stationary. And again this is camera stuff so we're going to do the LateUpdate() We'll house all the code that manipulates the camera transform in LateUpdate() So we'll want an offset, a local offset float. And assign to it the Camera.main.orthographicSize, which is 3.6, which is half of the screen height, right? So multiply that by Camera.main.aspect, the aspect ratio. And then divide that by two. So this basically will grab a quarter of the screen position and subtract it from our CheeseHead and we'll use this for following our CheeseHead, you'll see in a minute. And we'll need a Vector3, a local Vector3 called Cheese.. Ah, this would be a fairly long looking variable so I'll just say CheeseScreenPosition. I'm sure you'll divine what I mean by that.

And we'll assign to that Camera.main, I will use this method called WorldToViewportPoint(CheeseHead) we'll pass in the CheeseHead transform.position. And what this will do is basically it will, it will grab the the CheeseHead's position on the screen relative to where the camera is at. So a quarter of the way, you know point 2.5 would be a quarter of the way from the left, .75 would be three quarters away from the right, or a quarter of the way left from the right. .5 would be in the middle, alright? So we want this value for later we'll be using it for camera movement.. And we'll want a conditional here, so if (CheeseScreenPosition.x < 0.25f) So less than a quarter of the way from the left-hand of the screen. Or CheeseScreenPosition > 0.75f. So we'll detect whether or not the CheeseHead is at those kind of a quarter of the way from the left, or three quarters away from the left opposite sides of the screen. That's when we'll engage the camera movement. So if that's the case, CameraState = CameraState.Following, it'll start following. Based on the code that we set up, according to what happens when we are in this state.

We'll set this up with this conditional. If (CameraState == CameraState.Following && PlayerState.Instance.Horizontal == Horizontal.Idle) then actually we'll be recentering. So if the camera's moving, and because the CheeseHead's at that either .25 relative position on the screen or .75 and if it's idle, it stops moving then the camera recenters. So say CameraState = CameraState.Recentering. Else if.. I never did put the X value there, fix that really quick. Else if (CameraState.Following) so if we're in CameraState.Following, but we're not idle, we'll have the camera follow the player. So transform.position = new Vector3() we'll take in the CheeseHead.transform.position.x for the X value. Minus the offset, because we want to always have the camera, you know just behind the the CheeseHead by that quarter of a screen width, right? So it'll be basically always at the center point of the camera. Even though it's following the CheeseHead, this is how we're doing it, so it works. A littl ugly looking code, but it works. And we'll want to, depending on if we're going to the left or the right, we'll want to multiply that by the DirectionFacing.

So we'll cast to int the PlayerState.Instance.DirectionFacing, and we have to pass in also transform.position.z. Sorry, that was just for.. We just did the X position, so this will be Y, and because it's a Vector3, we'll also need transform.position.z. Now we haven't yet determined what happens when the CameraState is Recentering. So we'll do just that. This is how we create that Recentering effect, we'll have another conditional that says if (CameraState == Recentering), then we'll say, "Take this temporary X value" and we'll use a Lerp to recenter the camera. Mathf.Lerp() We'll take in its current transform.position.x. And we'll recenter to the CheeseHead's transform.position. So transform.position.x for the CheeseHead, that will be our "to" value for the Lerp. And we'll just have a little multiplier here for the speed of that. .025f * Time.deltaTime * 60.. We'll say transform.position = new Vector3() We'll pass in that temporary X value from the Lerp to recenter the camera.

We'll also want transform.position.y and z, again. And we'll change the CameraState once it actually is fully centered.. So we can do that with this conditional. If.. Well, we'll need a rounding here because otherwise we would be dealing with floats and it's not to be terribly precise with floats. So just to make sure we get exactly, round it to the nearest tenth decimal place. And we get 0.5, which is what we're going to look for when the Recentering is done with and the camera is stationary. We'll say if (Mathf.Round(CheeseScreenPosition)),which is a method. We'll round the CheeseScreenPosition.x and we'll round to the first decimal place. If it's equivalent to 0.5, then CameraState = CameraState.Stationary, right? And I guess we need to use a using statement for this. I think it's available in the System namespace., That should resolve that. It's not Math, it's Math.Round() alright? Simple mistakes like the what make a difference.

Alright we're going to want to attach this now to the camera, and now with this moving of the camera we'll also be able to see our parallax effect. It will need a reference to that CheeseHead here, so I'll just click and drag that there and play this and see how it all looks. You see, the camera is following when the character is at those quarter and three quarter positions on screen, so that's good. And it's Recentering properly, but everything looks fine you know, the parallax is working. But as you can see we're getting to a point where our backgrounds are being cut off, so that has to do with.. The solution for that has to do with tiling, and this will be exactly what we look at in the next lesson, as well as adding a few extra finishing touches to our parallax backgrounds. 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