(For more resources related to this topic, see here.)
As a developer, we have been asked numerous times about how to implement parallax scrolling in a 2D game. Cerulean Games, my game studio, has even had the elements of parallax scrolling as the "do or die" requirement to close a project deal with a client. In reality, this is incredibly easy to accomplish, and there are a number of ways to do this.
In Power Rangers Samurai SMASH! (developed by Cerulean Games for Curious Brain; you can find it in the iOS App Store), we implemented a simple check that would see what the linear velocity of the player is and then move the background objects in the opposite direction. The sprites were layered on the Z plane, and each was given a speed multiplier based on its distance from the camera. So, as the player moved to the right, all parallax scrolling objects would move to the left based on their multiplier value. That technique worked, and it fared us and our client quite well for the production of the game. This is also a common and quick way to manage parallax scrolling, and it's also pretty much how we're going to manage it in this game as well.
OK, enough talk! Have at you! Well, have at the code:
-using UnityEngine; using System.Collections; public class ParallaxController : MonoBehaviour { public GameObject[] clouds; public GameObject[] nearHills; public GameObject[] farHills; public float cloudLayerSpeedModifier; public float nearHillLayerSpeedModifier; public float farHillLayerSpeedModifier; public Camera myCamera; private Vector3 lastCamPos; void Start() { lastCamPos = myCamera.transform.position; } void Update() { Vector3 currCamPos = myCamera.transform.position; float xPosDiff = lastCamPos.x - currCamPos.x; adjustParallaxPositionsForArray(clouds, cloudLayerSpeedModifier, xPosDiff); adjustParallaxPositionsForArray(nearHills, nearHillLayerSpeedModifier, xPosDiff); adjustParallaxPositionsForArray(farHills, farHillLayerSpeedModifier, xPosDiff); lastCamPos = myCamera.transform.position; } void adjustParallaxPositionsForArray(GameObject[] layerArray, float layerSpeedModifier, float xPosDiff) { for(int i = 0; i < layerArray.Length; i++) { Vector3 objPos = layerArray[i].transform.position; objPos.x += xPosDiff * layerSpeedModifier; layerArray[i].transform.position = objPos; } } }
Done? Good. Now we can move some sprites. Import the sprites from SpritesParallaxScenery. Start placing sprites in the three layer containers you created earlier. For the hills you want to be closer, place the sprites in the _NearHillsLayer container; for the hills you want to be further away, place the sprites in _FarHillsLayer; and place the clouds in the _CloudLayer container. The following screenshot shows an example of what the layers will now look like in the scene:
Pro tip
Is this the absolute, most efficient way of doing parallax? Somewhat; however, it's a bit hardcoded to only really fit the needs of this game. Challenge yourself to extend it to be flexible and work for any scenario!
Wait, you say that the objects are layered in the wrong order? Your hills are all mixed up with your platforms and your platforms are all mixed up with your hills? OK, don't panic, we've got this.
What you need to do here is change the Order in Layer option for each of the parallax sprites. You can find this property in the Sprite Renderer component. Click on one of the sprites in your scene, such as one of the clouds, and you can see it in the Inspector panel. Here's a screenshot to show you where to look:
Rather than changing each sprite individually, we can easily adjust the sprites in bulk by performing the following steps:
With the layers all set up, let's finish setting up the parallax layers. First, finish placing any additional parallax sprites; I'll wait. Brilliant! Now, go to the _ParallaxLayers object and let's play around with that Parallax Controller component. We're going to want to add all of those sprites to Parallax Controller. To make this easy, look at the top-right corner of the Inspector panel. See the little lock icon? Click on it. Now, regardless of what you do, the Parallax Controller component will not be deselected. Since it can't be deselected, you can now easily drag-and-drop all of the Cloud sprites into the Clouds array in the ParallaxController component, and all of the _FarHillsLayer child objects into the Far Hills array—you see where this is going.
Set the My Camera field to use the Main Camera object. Finally, let's set some values in those Layer Speed Modifier fields. The higher the number, the faster the object will move as the camera moves. As an example, we set the Cloud layer to 0.05, the Near layer to 0.2, and the Far layer to 0.1. Feel free though to play with the values and see what you like!
Go ahead and play the game. Click on the play button and watch those layers move! But, what's this? The particles that burst when an enemy is defeated render behind the sprites in the background—actually, they render behind all the sprites! To fix this, we need to tell Unity to render the particles on a layer in front of the sprites. By default, the sprites render after the particles. Let's change that.
First, we need to create a new sorting layer. These are special types of layers that tell Unity the order to render things in. Go to the Tags & Layers window and look out for the drop-down menu called Sorting Layers. Add a new layer called ParticleLayer on Layer 1, as shown in the following screenshot:
With this in place, it means anything with the Sorting Layers menu of ParticleLayer will render after the Default layer. Now, we need a way to assign this Sorting Layer to the particle system used when enemies are defeated. Create a new script called ParticleLayering and make it look like the following code:
using UnityEngine; using System.Collections; public class ParticleLayering : MonoBehaviour { public string sortLayerString = ""; void Start () { particleSystem.renderer.sortingLayerName = sortLayerString; } }
Add this script to the EnemyDeathFX Prefab and set the Sort Layer String field to ParticleLayer. Go ahead and play the game again now to watch those particles fly in front of the other objects.
Finally, if you want a solid color background to your scene, you don't need to worry about adding a colored plane or anything. Simply select the Main Camera object, and in the Inspector panel, look for the Background field in the Camera component. Adjust the color there as per your need. For the example game, we made this color a nice sky blue with the following values: R: 128, G: 197, B: 232, and A: 0.
The one thing you may notice we're missing is something at the bottom of the scene. Here's a nice little challenge for you. We've given you a Lava sprite. Now, add in a lava layer of parallax sprites in the foreground using all the info you've read in this article. You can do this!
One of the most important elements to a game is being able to track progress. A quick and simple way to do this is to implement a score system. In our case, we will have a score that increases whenever you defeat an enemy.
Now, Unity does have a built-in GUI system. However, it has some drawbacks. With this in mind, we won't be relying on Unity's built-in system. Instead, we are going to create objects and attach them to the camera, which in turn will allow us to have a 3D GUI.
Pro tip
If you want to use what this author believes is the best UI system for Unity, purchase a license for NGUI from the Unity Asset Store. I'm not the only one to think it's the best; Unity hired the NGUI developer to build the new official UI system for Unity itself.
Let's build out some GUI elements:
Pro tip
Unity's default 3D text font looks rather low quality in most situations. Try importing your own font and then set the font size to something much higher than you would usually need; often around 25 to 40. Then, when you place it in the world, it will look crisp and clean.
Let's make it so that the Score visual element can actually track the player's score. Create a new script called ScoreWatcher and write the following code in it:
using UnityEngine; using System.Collections; public class ScoreWatcher : MonoBehaviour { public int currScore = 0; private TextMesh scoreMesh = null; void Start() { scoreMesh = gameObject.GetComponent<TextMesh>(); scoreMesh.text = "0"; } void OnEnable() { EnemyControllerScript.enemyDied += addScore; } void OnDisable() { EnemyControllerScript.enemyDied -= addScore; } void addScore(int scoreToAdd) { currScore += scoreToAdd; scoreMesh.text = currScore.ToString(); } }
You may notice that in the preceding script, we are listening to the enemyDied event on the EnemyControllerScript. What we did here was we allowed other objects to easily create scoring events that the Score object can optionally listen to. There is lots of power to this!
Let's add that event and delegate to the enemy. Open up EnemyControllerScript, and in the beginning, add the following code:
// States to allow objects to know when an enemy dies public delegate void enemyEventHandler(int scoreMod); public static event enemyEventHandler enemyDied;
Then, down in the hitByPlayerBullet function, add the following code just above Destroy(gameObject,0.1f);, right around line 95:
// Call the EnemyDied event and give it a score of 25. if(enemyDied != null) enemyDied(25);
Add the ScoreWatcher component to the Score object. Now, when you play the game and defeat the enemies, you can watch the score increase by 25 points each time! Yeeee-haw!
Sorry 'bout that... shootin' things for points always makes me feel a bit Texan.
So, you defeated all your enemies and now find yourself without enemies to defeat. This gets boring fast; so, let's find a way to get more enemies to whack. To do this, we are going to create enemy spawn points in the form of nifty rotating vortexes and have them spit out enemies whenever we kill other enemies. It shall be glorious, and we'll never be without friends to give gifts—and by gifts, we mean bullets.
First things first. We need to make a cool-looking vortex. This vortex will be a stacked, animated visual FX object that is built for a 2D world. Don't worry, we've got you covered on textures, so please go through the following steps:
using UnityEngine; using System.Collections; public class EnemyRespawner : MonoBehaviour { public GameObject spawnEnemy = null; float respawnTime = 0.0f; void OnEnable() { EnemyControllerScript.enemyDied += scheduleRespawn; } void OnDisable() { EnemyControllerScript.enemyDied -= scheduleRespawn; } // Note: Even though we don't need the enemyScore, we still need to accept it because the event passes it void scheduleRespawn(int enemyScore) { // Randomly decide if we will respawn or not if(Random.Range(0,10) < 5) return; respawnTime = Time.time + 4.0f; } void Update() { if(respawnTime > 0.0f) { if(respawnTime < Time.time) { respawnTime = 0.0f; GameObject newEnemy = Instantiate(spawnEnemy) as GameObject; newEnemy.transform.position = transform.position; } } } }
Now attach the preceding script to your Vortex object, populate the Spawn Enemy field with the Enemy Prefab, and save the Vortex object as a Prefab. Scatter a bunch of Vortex Prefabs around the level and you can get the hydra effect, where killing one enemy will create two more enemies or even more than two!
Also, if you haven't already done so, you may want to go to the Physics Manager option and adjust the settings so that enemies won't collide with other enemies.
One more thing—those enemies sort of glide out of their portals very awkwardly. Let's boost the gravity so they fall faster. Click on the main Enemy Prefab and change the Gravity Scale value of the RigidBody 2D component to 30. Now, they'll fall properly!
Pro tip
There are so many things you can do with enemy spawners that go far, far outside the context of this article. Take a shot at adding some features yourself! Here are a few ideas:
- Make the spawn vortexes play a special visual effect when an enemy is spawned
- Give vortexes a range so that they only spawn an enemy if another enemy was killed in their range
- Make vortexes move around the level
- Make vortexes have multiple purposes so that enemies can walk into one and come out another
- Have a special gold enemy worth bonus points spawn after every 100 kills
- Make an enemy that, when defeated, spawns other enemies or even collectable objects that earn the player bonus points!
So, what have we learned here today aside from the fact that shooting enemies with bullets earns you points? Well, check this out.
You now know how to use parallax scrolling, 2D layers, and generate objects; and how to use a scoring system.
Enemies dying, enemies spawning, freakin' vortexes? I know, you're sitting there going, "Dude, OK, I'm ready to get started on my first 2D game... the next side scrolling MMO Halo meets Candy Crush with bits of Mass Effect and a little Super Mario Bros!"
Further resources on this subject: