Você está na página 1de 37

Shooting

Our player ship is facing a terrifying Poulpi, but cannot do anything…

Let’s grant him a weapon and some ammo! This will involve a bit more of
scripting, but be confident. It’s worth it.

Projectile
First, before allowing the player to shoot, we need to define a game
object that represents the projectiles he will use.

Here is the sprite:

(Right click to save the image)

The projectile is an object that we will use a lot: There is going to be a


few instances on the screen when the player will be shooting.

What should we use in this case ? A Prefab of course!

Preparing the Prefab


You should be used to the procedure now:

1. Import the image.


2. Create a new Sprite in the scene.
3. Set the image on the sprite.
4. Set the “Sprite Layer” to “Bullets”.
5. Add a “Rigidbody 2D” with 0 “Gravity Scale” and “Fixed Angles”.
6. Add a “Box Collider 2D”.

Set the scale to (0.5, 0.5, 1) so it will look good.

However, this time, we need to set a new parameter in the “Inspector”:

1. In the “Box Collider 2D”, check the “Is Trigger” property.

A trigger collider raises an event when colliding but is not used by the
physics simulation.
It means that a shot will pass through an object on touching — there
won’t be any “real” interaction at all. Yet, the other collider is going to
have its “OnTriggerEnter2D” event raised.

Tada, you have a shot! Time to script the behavior.

Create a new script called “ShotScript”:

using UnityEngine;

/// <summary>
/// Projectile behavior
/// </summary>
public class ShotScript : MonoBehaviour
{
// 1 - Designer variables

/// <summary>
/// Damage inflicted
/// </summary>
public int damage = 1;

/// <summary>
/// Projectile damage player or enemies?
/// </summary>
public bool isEnemyShot = false;

void Start()
{
// 2 - Limited time to live to avoid any leak
Destroy(gameObject, 20); // 20sec
}
}

1. Some designer variables for the speed, direction and damages.


2. We destroy the object after 20 seconds to avoid any leak.

Attach the “ShotScript” to the sprite. Add a “MoveScript” too so your


shots will move.

Then drag the shot game object in the “Project” pane to create
a Prefab from it. We will need it in a few steps.

You should have this configuration:


If you start the game with the “Play” button, you will see that the shot is
moving.

Collisions and damages


However, the shot is not (yet) destroying anything.

Don’t be surprised, we didn’t script the damage behavior. We only need


one new script, “HealthScript”:

using UnityEngine;

/// <summary>
/// Handle hitpoints and damages
/// </summary>
public class HealthScript : MonoBehaviour
{
/// <summary>
/// Total hitpoints
/// </summary>
public int hp = 1;

/// <summary>
/// Enemy or player?
/// </summary>
public bool isEnemy = true;

/// <summary>
/// Inflicts damage and check if the object should be destroyed
/// </summary>
/// <param name="damageCount"></param>
public void Damage(int damageCount)
{
hp -= damageCount;
if (hp <= 0)
{
// Dead!
Destroy(gameObject);
}
}

void OnTriggerEnter2D(Collider2D otherCollider)


{
// Is this a shot?
ShotScript shot = otherCollider.gameObject.GetComponent<ShotScript>();
if (shot != null)
{
// Avoid friendly fire
if (shot.isEnemyShot != isEnemy)
{
Damage(shot.damage);

// Destroy the shot


Destroy(shot.gameObject); // Remember to always target the game object, otherwise you will
just remove the script
}
}
}
}

Attach this “HealthScript” on the Poulpi Prefab.

Attention: it’s better to work on the Prefab directly.

By doing so, every instantiated enemy in the scene is going to be


modified to reflect the Prefab. It is particularly important here because we
will have a lot of enemies in the scene.

If you have worked on a game object instance instead of the Prefab, don’t
be scared: you can click on the “Apply” button at the top of the
“Inspector” to add the changes to the Prefab.

Make sure the shot and the Poulpi are on the same line to test the
collision.

Note: the 2D physics engine, Box2D, doesn’t use the Z position. Colliders
2D will always be in the same plane even if your game objects are not.

Now play the scene and observe:


If the enemy has more health than the shot damages, he will survive. Try
to change the hp value in the enemy’s “HealthScript”:

Firing
Delete the shot in the scene. It has nothing to do there now that we have
finished it.

We need a new script to fire shots. Create one called “WeaponScript”.

This script will be reused everywhere (players, enemies, etc.). Its purpose
is to instantiate a projectile in front of the game object it is attached to.

Here’s the full code, bigger than usual. The explanations are below:

using UnityEngine;
/// <summary>
/// Launch projectile
/// </summary>
public class WeaponScript : MonoBehaviour
{
//--------------------------------
// 1 - Designer variables
//--------------------------------

/// <summary>
/// Projectile prefab for shooting
/// </summary>
public Transform shotPrefab;

/// <summary>
/// Cooldown in seconds between two shots
/// </summary>
public float shootingRate = 0.25f;

//--------------------------------
// 2 - Cooldown
//--------------------------------

private float shootCooldown;

void Start()
{
shootCooldown = 0f;
}

void Update()
{
if (shootCooldown > 0)
{
shootCooldown -= Time.deltaTime;
}
}

//--------------------------------
// 3 - Shooting from another script
//--------------------------------

/// <summary>
/// Create a new projectile if possible
/// </summary>
public void Attack(bool isEnemy)
{
if (CanAttack)
{
shootCooldown = shootingRate;

// Create a new shot


var shotTransform = Instantiate(shotPrefab) as Transform;

// Assign position
shotTransform.position = transform.position;

// The is enemy property


ShotScript shot = shotTransform.gameObject.GetComponent<ShotScript>();
if (shot != null)
{
shot.isEnemyShot = isEnemy;
}

// Make the weapon shot always towards it


MoveScript move = shotTransform.gameObject.GetComponent<MoveScript>();
if (move != null)
{
move.direction = this.transform.right; // towards in 2D space is the right of the sprite
}
}
}

/// <summary>
/// Is the weapon ready to create a new projectile?
/// </summary>
public bool CanAttack
{
get
{
return shootCooldown <= 0f;
}
}
}

Attach this script to the player.

The script is divided in three parts:

1. Variables availables in the “Inspector” pane


We have two members here : shotPrefab and shootingRate.

The first one is needed to set the shot that will be used with this weapon.

Select your player in the scene “Hierarchy”. In the “WeaponScript”


component, you can see the property “Shot Prefab” with a “None” value.

Drag the “Shot” prefab in this field:


Unity will automatically fill the script with this information. Easy, right?

The shootingRate variable has a default value set in the code. We will not
change it for the moment. But you can start the game and experiment
with it to test what it does.

Be careful: changing a variable value in the Unity “Inspector” does not


apply the change to the default value of the script. If you add the script
onto another object, the default value will be the one from the script.
It’s logical, but you need to be careful. If you want to keep the tweaked
value definitively, you have to open your code editor and backport the
change yourself.

2. Cooldown
Guns have a firing rate. If not, you would be able to create tons of
projectiles at each frame.

So we have a simple cooldown mechanism. If it is greater than 0 we


simply cannot shoot. We substract the elapsed time at each frame.

3. Public attack method


This is the main purpose of this script: being called from another one.
This is why we have a public method that can create the projectile.

Once the projectile is instantiated, we retrieve the scripts of the shot


object and override some variables.

Note: the GetComponent<TypeOfComponent>() method allows you to get a


precise component (and thus a script, because a script is a component
after all) from an object. The generic (<TypeOfComponent>) is used to indicate
the exact component that you want.

There is also a GetComponents<TypeOfComponent>() that gets an array instead of


the first one, etc.

Using the weapon with the player entity


If you launch the game at this point, nothing has changed at all. We have
created a weapon but it’s completely useless.

Indeed, even if a “WeaponScript” was attached to an entity,


the Attack(bool)method would never be called.

Let’s go back to “PlayerScript”.

In the Update() method, put this snippet:

void Update()
{
// ...

// 5 - Shooting
bool shoot = Input.GetButtonDown("Fire1");
shoot |= Input.GetButtonDown("Fire2");
// Careful: For Mac users, ctrl + arrow is a bad idea
if (shoot)
{
WeaponScript weapon = GetComponent<WeaponScript>();
if (weapon != null)
{
// false because the player is not an enemy
weapon.Attack(false);
}
}

// ...
}

It doesn’t matter at this point if you put it after or before the movement.

What did we do ?

1. We read the input from a fire button (click or ctrl by default).


2. We retrieve the weapon’s script.
3. We call Attack(false).

Button down: you can notice that we use the GetButtonDown() method to
get an input. The “Down” at the end allows us to get the input when the
button has been pressed once and only once. GetButton() returns true at each
frame until the button is released. In our case, we clearly want the
behavior of the GetButtonDown() method.

Try to use GetButton() instead, and observe the difference.

Launch the game with the “Play” button. You should get this:
The bullets are too slow? Experiment with the “Shot” prefab to find a
configuration you’d like.

Bonus: Just for fun, add a rotation to the player, like (0, 0, 45). The shots
have a 45 degrees movement, even if the rotation of the shot sprite is not
correct as we didn’t change it too.

Next step
We have a shooter! A very basic one, but a shooter despite everything.
You learned how to create a weapon that can fire shots and destroy other
objects.

Try to add more enemies. ;)

But this part is not over! We want enemies that can shoot too. Take a
break, what comes next is mainly reusing what we did here.

Our magnificent ship is now shooting innocent flying


octopuses.

It can’t stay that way. They need to respond. To shoot


back. To fight for freedo… Oups. Sorry.
Using what we did in the last part, we will modify the
enemy behavior so it can shoot projectiles too.

The enemy projectile


We will create a new projectile using this sprite:

If you are as lazy as I am, duplicate the “PlayerShot”


prefab, rename it to “EnemyShot1” and change the
sprite with the new one above.

To duplicate, you can create an instance by doing a


drag and drop on the scene, renaming the created
game object and finally saving it as a Prefab.
Or you could simply duplicate the Prefab directly inside the folder with
the cmd+D (OS X) or ctrl+D (Windows) shortcuts.

If you like to do it the hard way, you could also recreate a whole new
sprite, rigibody, collider with trigger, etc.

The right scale is (0.35, 0.35, 1) .

You should have something like this:


If you hit “Play”, the shot will move and potentially
destroy the enemy. This is because of the “ShotScript”
properties (which is harmful for the Poulpi by default).

Don’t change them. Remember the “WeaponScript”


from the last part? It will set those values properly.

We have an “EnemyShot1” Prefab. Remove the instances


from the scene if there are some.
Firing
Like we did for the player, we need to add a weapon to
the enemy and make him call Attack(), thus creating
projectile.

New scripts and assignments


1. Add a “WeaponScript” to the enemy.
2. Drag and drop the “EnemyShot1” Prefab into the
“Shot Prefab” variable of the script.
3. Create a new script called “EnemyScript”. It will
simply try to trigger the weapon at each frame. A
kind of auto-fire.
using UnityEngine;

/// <summary>
/// Enemy generic behavior
/// </summary>
public class EnemyScript : MonoBehaviour
{
private WeaponScript weapon;

void Awake()
{
// Retrieve the weapon only once
weapon = GetComponent<WeaponScript>();
}

void Update()
{
// Auto-fire
if (weapon != null && weapon.CanAttack)
{
weapon.Attack(true);
}
}
}

Attach this script to our enemy.

You should have this (observe the slight increase of the


shooting rate to 0.75):
Remark: if you are modifying the game object in the scene, remember to
save all the changes to the Prefab using the “Apply” button on the top right
of the “Inspector”.

Try to play and look!

Okay, it’s kinda working. The weapon is firing on its


right because that’s what we told it to do.

If you rotate the enemy, you can make it fire on its


left, but, erm… the sprite is also upside down. That’s
not what we want.
So what?! Obviously, we made this mistake for a
reason.

Shooting in any direction


The “WeaponScript” has been made in a particular
way: you can choose its direction simply by rotating
the game object onto which it is attached. We’ve seen
that when we rotated the enemy sprite before.

The trick is to create an empty game object as a child


of the enemy Prefab.

We need to:

1. Create an “Empty Game Object”. Call it “Weapon”.


2. Delete the “WeaponScript” attached to your enemy
prefab.
3. Add a “WeaponScript” to the “Weapon” object and
set the shot prefab property like you did before.
4. Rotate the “Weapon” to (0, 0, 180).

If you did the process on the game object (and not


the Prefab), do not forget to “Apply” the changes.

You should have this:


However, we have a small change to make on the
“EnemyScript” script.

In its current state, the “EnemyScript” call


to GetComponent<WeaponScript>() is going to return null. Indeed,
the “WeaponScript” is not attached to the same game
object anymore.

Fortunately, Unity provides a method that can also look


in the children hierarchy of the game object calling it:
the GetComponentInChildren<Type>()method.
Note: like for GetComponent<>(), GetComponentInChildren<>() also exists in a plural
form : GetComponentsInChildren<Type>(). Notice the s after “Component”. This
method returns a list instead of the first corresponding component.

In fact, just for fun, we have also added a way to


manage multiple weapons. We are just manipulating a
list instead of a single instance of the component.

Take a look at the whole “EnemyScript”:


using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Enemy generic behavior
/// </summary>
public class EnemyScript : MonoBehaviour
{
private WeaponScript[] weapons;

void Awake()
{
// Retrieve the weapon only once
weapons = GetComponentsInChildren<WeaponScript>();
}

void Update()
{
foreach (WeaponScript weapon in weapons)
{
// Auto-fire
if (weapon != null && weapon.CanAttack)
{
weapon.Attack(true);
}
}
}
}

Finally, update the shot speed by tweaking the public


property of the “MoveScript” of the “enemyShot1” Prefab.
It should move faster than the Poulpi speed:

Great, we have a super dangerous Poulpi now.

Bonus: firing in two directions

Firing in two directions is just a few clicks and a


duplication in the editor. It doesn’t involve any script:

1. Add another weapon to the enemy (by duplicating


the first “Weapon”).
2. Change the rotation of the second “Weapon”.
The enemy should fire in two directions now.

A possible result:

It’s a good example of using Unity properly: by


creating independent scripts like this one and making
public some useful variables, you can reduce the
amount of code drastically. Less code means less
errors.

Hurting the player


Our Poulpies are dangerous, right? Erm, nope. Even if
they can shoot, it won’t harm the player character.

We are still invincible. No challenge there.

Simply add a “HealthScript” on the player. Make sure


to uncheck the “IsEnemy” field.

Run the game and observe the difference:


Bonus
We are going to give you some hints to go further on
the shooting aspect of your game. You can skip this
part if you are not interested into more
specific shmup thoughts.

Player-enemy collision
Let’s see how we can handle the collision between the
player and an enemy, as it is quite frustrating to see
them block each other without consequences…

The collision is the result of the intersection of two non-


triggers Colliders 2D. We simply need to handle the
event OnCollisionEnter2D in our PlayerScript:
//PlayerScript.cs
//....

void OnCollisionEnter2D(Collision2D collision)


{
bool damagePlayer = false;

// Collision with enemy


EnemyScript enemy = collision.gameObject.GetComponent<EnemyScript>();
if (enemy != null)
{
// Kill the enemy
HealthScript enemyHealth = enemy.GetComponent<HealthScript>();
if (enemyHealth != null) enemyHealth.Damage(enemyHealth.hp);

damagePlayer = true;
}

// Damage the player


if (damagePlayer)
{
HealthScript playerHealth = this.GetComponent<HealthScript>();
if (playerHealth != null) playerHealth.Damage(1);
}
}

On collision, we damage both the player and the


enemy by using the HealthScript component. By doing so,
everything related to the health/damage behavior is
linked to it.

Parallax scrolling
For the moment, we have created a static scene with a player and some
enemies. It’s a bit boring. Time to enhance our background and scene.

An effect that you find in every single 2D game for 15 years is “parallax
scrolling”.

To make it short, the idea is to move the background layers at different


speeds (i.e., the farther the layer is, the slower it moves). If done
correctly, this gives an illusion of depth. It’s a cool, nice and easy-to-do
effect.

Moreover, many shmups use a scrolling in one — or more — axis (except


the original one, Space Invaders).

Let’s implement that in Unity.

Theory: defining the scrolling in our game


Adding a scrolling axis need a bit of thinking on how we will make the
game with this new aspect.

It’s good to think before coding. :)


What do we want to move?
We have a decision to take here :

1. First choice: The player and the camera move. The rest is fixed.
2. Second choice: The player and the camera are static. The level is a
treadmill.

The first choice is a no-brainer if you have a Perspective camera. The


parallax is obvious: background elements have a higher depth. Thus, they
are behind and seems to move slower.

But in a standard 2D game in Unity, we use an Orthographic camera. We


don’t have depth at render.

About the camera: remember the “Projection” property of your camera


game object. It’s set to Orthographic in our game.

means that the camera is a classic 3D camera, with depth


Perspective
management. Orthographic is a camera that renders everything at the same
depth. It’s particularly useful for a GUI or a 2D game.

In order to add the parallax scrolling effect to our game, the solution is to
mix both choices. We will have two scrollings:

o The player is moving forward along with the camera.


o Background elements are moving at different speeds (in addition to
the camera movement).

Note: you may ask: “Why don’t we just set the camera as a child of the
player object?”. Indeed, in Unity, if you set an object (camera or not) as a
sub-child of a game object, this object will maintain its relative position to
its parent. So if the camera is a child of the player and is centered on
him, it will stay that way and will follow him exactly. It could be a
solution, but this would not fit with our gameplay.

In a shmup, the camera restricts the player movement. If the camera


moves along with the player for both horizontal and vertical axis, then the
player is free to go where he wants. We DO want to keep the player
inside a restricted area.

We would also recommend to always keep the camera independent in a


2D game. Even in a platformer, the camera isn’t strictly linked to the
player: it follows him under some restrictions. Super Mario World has
probably one the best camera possible for a platformer. You may have a
look at how it is done.
Spawning enemies
Adding a scrolling to our game has consequences, especially concerning
enemies. Currently, they are just moving and shooting as soon as the
game starts. However, we want them to wait and be invincible until
they spawn.

How do we spawn enemies? It depends on the game, definitely. You could


define events that spawn enemies when they are triggered, spawn points,
pre-determined positions, etc.

Here is what we will do: We position the Poulpies on the scene directly
(by dragging the Prefab onto the scene). By default, they are static and
invincibles until the camera reaches and activates them.

The nice idea here is that you can use the Unity editor to set the enemies.
You read right: without doing anything, you already have a level editor.

Once again, it’s a choice, not science. ;)


Note: on a bigger project, you may need a dedicated level editor such as
“Tiled” or a custom one you made. Your levels can be text files (plain
text, XML, JSON, etc.) that you read in Unity for example.

Planes
First, we must define what our planes are and for each,
if it’s a loop or not. A looping background will repeat
over and over again during the level execution. E.g.,
it’s particularly useful for things like the sky.

Add a new layer to the scene for the background


elements.

We are going to have:

Layer Loop
Background with the sky Yes
Background (1st row of flying platforms) No
Middleground (2nd row of flying platforms) No
Foreground with players and enemies No
We could add as many layers of background objects as
we want.
Careful with that axe, Eugene: if you add layers ahead of the
foreground layer, be careful with the visibility. Many games do not use
this technique because it reduces the clearness of the game, especially in
a shmup where the gameplay elements need to be clearly visible.

Practice: Diving into the code


Okay, we saw how implementing a parallax scrolling
affects our game.
Did you know? “Scrolling shooters” is another name used for
the shmups.

But enough thoughts, time to practice!

Unity has some parallax scrolling scripts in its standard


packages (take a look at the 2D platformer demo on
the Asset Store). You can of course use them, but we
found it would be interesting to build one from scratch
the first time.
Standard packages: these are practicals, but be careful to not abuse of
them. Using standard packages can block your thoughts and will not
make your game stand out of the crowd. They give a Unity feel to your
gameplay.

Remember all the flash game clones?

Simple scrolling
We will start with the easy part: scrolling
backgrounds without looping.

Remember the “MoveScript” we used before? The basis


is the same: a speed and a direction applied over time.

Create a new “ScrollingScript” script:


using UnityEngine;
/// <summary>
/// Parallax scrolling script that should be assigned to a layer
/// </summary>
public class ScrollingScript : MonoBehaviour
{
/// <summary>
/// Scrolling speed
/// </summary>
public Vector2 speed = new Vector2(2, 2);

/// <summary>
/// Moving direction
/// </summary>
public Vector2 direction = new Vector2(-1, 0);

/// <summary>
/// Movement should be applied to camera
/// </summary>
public bool isLinkedToCamera = false;

void Update()
{
// Movement
Vector3 movement = new Vector3(
speed.x * direction.x,
speed.y * direction.y,
0);

movement *= Time.deltaTime;
transform.Translate(movement);

// Move the camera


if (isLinkedToCamera)
{
Camera.main.transform.Translate(movement);
}
}
}

Attach the script to these game objects with these


values:

Layer Speed Direction Linked to Camera


Background (1, 1) (-1, 0, 0) No
Background elements (1.5, 1.5) (-1, 0, 0) No
Middleground (2.5, 2.5) (-1, 0, 0) No
Foreground (1, 1) (1, 0, 0) Yes
For a convincing result, add elements to the scene:

o Add a third background part after the two previous


ones.
o Add some small platforms in the layer `
Background elements`.
o Add platforms in the layer Middleground.
o Add enemies on the right of the layer Foreground, far
from the camera.

The result:

Not bad! But we can see that enemies move and shoot
when they are out of the camera, even before they
spawn!

Moreover, they are never recycled when they pass the


player (zoom out in the “Scene” view, and look at the
left of the scene: the Poulpies are still moving).
Note: experiment with the values. :)

We’ll fix these problems later. First, we need to


manage the infinite background (the sky).
Infinite background scrolling
In order to get an infinite background, we only need to
watch the child which is at the left of the infinite layer.

When this object goes beyond the camera left edge, we


move it to the right of the layer. Indefinitely.

For a layer filled with images, notice that you need a


minimum size to cover the camera field, so we never
see what’s behind. Here it’s 3 parts for the sky, but it’s
completely arbitrary.

Find the correct balance between resource consumption


and flexibility for your game.

In our case, the idea is that we will get all the children
on the layer and check their renderer.
A note about using the renderer component: This method won’t work
with invisible objects (e.g., the ones handling scripts). However, a use
case when you need to do this on invisible objects is unlikely.

We will use an handy method to check whether an


object’s renderer is visible by the camera. We’ve found
it on the community wiki. It’s neither a class nor a
script, but a C# class extension.
Extension: the C# language allows you to extend a class with
extensions, without needing the base source code of the class.

Create a static method starting with a first parameter which looks like
this: this Type currentInstance. The Type class will now have a new method
available everywhere your own class is available.

Inside the extension method, you can refer to the current instance calling
the method by using the currentInstance parameter instead of this.

The “RendererExtensions” script


Create a new C# file named “RendererExtensions.cs”
and fill it with:
using UnityEngine;

public static class RendererExtensions


{
public static bool IsVisibleFrom(this Renderer renderer, Camera camera)
{
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(camera);
return GeometryUtility.TestPlanesAABB(planes, renderer.bounds);
}
}

Simple, isn’t it?


Namespaces: you might have already noted that Unity doesn’t add a
namespace around a MonoBehaviour script when you create it from the
“Project” view. And yet Unity does handle namespaces…

In this tutorial, we are not using namespaces at all. However, in your real
project, you might consider to use them. If not, prefix your classes and
behaviors to avoid a collision with a third-party library (like NGUI).

The real reason behind not using namespaces was that during the Unity 4
days (this tutorial was originally written for Unity 4.3), a namespace
would prevent the use of default parameters. It’s not a problem anymore,
so: use namespace!

We will call this method on the leftmost object of the


infinite layer.

Full “ScrollingScript”
Observe the full “ScrollingScript” (explanations below):
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

/// <summary>
/// Parallax scrolling script that should be assigned to a layer
/// </summary>
public class ScrollingScript : MonoBehaviour
{
/// <summary>
/// Scrolling speed
/// </summary>
public Vector2 speed = new Vector2(10, 10);

/// <summary>
/// Moving direction
/// </summary>
public Vector2 direction = new Vector2(-1, 0);

/// <summary>
/// Movement should be applied to camera
/// </summary>
public bool isLinkedToCamera = false;

/// <summary>
/// 1 - Background is infinite
/// </summary>
public bool isLooping = false;

/// <summary>
/// 2 - List of children with a renderer.
/// </summary>
private List<SpriteRenderer> backgroundPart;

// 3 - Get all the children


void Start()
{
// For infinite background only
if (isLooping)
{
// Get all the children of the layer with a renderer
backgroundPart = new List<SpriteRenderer>();

for (int i = 0; i < transform.childCount; i++)


{
Transform child = transform.GetChild(i);
SpriteRenderer r = child.GetComponent<SpriteRenderer>();

// Add only the visible children


if (r != null)
{
backgroundPart.Add(r);
}
}

// Sort by position.
// Note: Get the children from left to right.
// We would need to add a few conditions to handle
// all the possible scrolling directions.
backgroundPart = backgroundPart.OrderBy(
t => t.transform.position.x
).ToList();
}
}

void Update()
{
// Movement
Vector3 movement = new Vector3(
speed.x * direction.x,
speed.y * direction.y,
0);

movement *= Time.deltaTime;
transform.Translate(movement);

// Move the camera


if (isLinkedToCamera)
{
Camera.main.transform.Translate(movement);
}

// 4 - Loop
if (isLooping)
{
// Get the first object.
// The list is ordered from left (x position) to right.
SpriteRenderer firstChild = backgroundPart.FirstOrDefault();

if (firstChild != null)
{
// Check if the child is already (partly) before the camera.
// We test the position first because the IsVisibleFrom
// method is a bit heavier to execute.
if (firstChild.transform.position.x < Camera.main.transform.position.x)
{
// If the child is already on the left of the camera,
// we test if it's completely outside and needs to be
// recycled.
if (firstChild.IsVisibleFrom(Camera.main) == false)
{
// Get the last child position.
SpriteRenderer lastChild = backgroundPart.LastOrDefault();

Vector3 lastPosition = lastChild.transform.position;


Vector3 lastSize = (lastChild.bounds.max - lastChild.bounds.min);

// Set the position of the recyled one to be AFTER


// the last child.
// Note: Only work for horizontal scrolling currently.
firstChild.transform.position = new Vector3(lastPosition.x + lastSize.x,
firstChild.transform.position.y, firstChild.transform.position.z);

// Set the recycled child to the last position


// of the backgroundPart list.
backgroundPart.Remove(firstChild);
backgroundPart.Add(firstChild);
}
}
}
}
}
}

(The numbers in the comments refer to the


explanations below)

Explanations

1. We need a public variable to turn on the “looping”


mode in the “Inspector” view.
2. We also have to use a private variable to store the
layer children.
3. In the Start() method, we set the backgroundPart list with
the children that have a renderer. Thanks to a bit
of LINQ, we order them by their X position and put
the leftmost at the first position of the array.
4. In the Update() method, if the isLooping flag is set to true,
we retrieve the first child stored in
the backgroundPart list. We test if it’s completely outside
the camera field. When it’s the case, we change its
position to be after the last (rightmost) child.
Finally, we put it at the last position
of backgroundPartlist.

Indeed, the backgroundPart is the exact representation of


what is happening in the scene.

Remember to enable the “Is Looping” property of the


“ScrollingScript” for the 0 - Background in the “Inspector”
pane. Otherwise, it will (predictably enough) not work.
Yes! We finally have a functional “parallax scrolling”
implementation.
Note: why don’t we use
the OnBecameVisible() and OnBecameInvisible() methods? Because they are
broken.

The basic idea of these methods is to execute a fragment of code when


the object is rendered (or vice-versa). They work like
the Start() or Stop() methods (if you need one, simply add the method in
the MonoBehaviour and Unity will use it).

The problem is that these methods are also called when rendered by the
“Scene” view of the Unity editor. This means that we will not get the
same behavior in the Unity editor and in a build (whatever the platform
is). This is dangerous and absurd. We highly recommend to avoid these
methods.

Bonus: Enhancing existing scripts


Let’s update our previous scripts.

Enemy v2 with spawn


We said earlier that enemies should be disabled until
they are visible by the camera.

They should also be removed once they are completely


off the screen.

We need to update “EnemyScript”, so it will:

1. Disable the movement, the collider and the auto-


fire (when initialized).
2. Check when the renderer is inside the camera
sight.
3. Activate itself.
4. Destroy the game object when it’s outside the
camera.
(The numbers refer to the comments in the code)
using UnityEngine;

/// <summary>
/// Enemy generic behavior
/// </summary>
public class EnemyScript : MonoBehaviour
{
private bool hasSpawn;
private MoveScript moveScript;
private WeaponScript[] weapons;
private Collider2D coliderComponent;
private SpriteRenderer rendererComponent;

void Awake()
{
// Retrieve the weapon only once
weapons = GetComponentsInChildren<WeaponScript>();

// Retrieve scripts to disable when not spawn


moveScript = GetComponent<MoveScript>();

coliderComponent = GetComponent<Collider2D>();

rendererComponent = GetComponent<SpriteRenderer>();
}

// 1 - Disable everything
void Start()
{
hasSpawn = false;

// Disable everything
// -- collider
coliderComponent.enabled = false;
// -- Moving
moveScript.enabled = false;
// -- Shooting
foreach (WeaponScript weapon in weapons)
{
weapon.enabled = false;
}
}

void Update()
{
// 2 - Check if the enemy has spawned.
if (hasSpawn == false)
{
if (rendererComponent.IsVisibleFrom(Camera.main))
{
Spawn();
}
}
else
{
// Auto-fire
foreach (WeaponScript weapon in weapons)
{
if (weapon != null && weapon.enabled && weapon.CanAttack)
{
weapon.Attack(true);
}
}

// 4 - Out of the camera ? Destroy the game object.


if (rendererComponent.IsVisibleFrom(Camera.main) == false)
{
Destroy(gameObject);
}
}
}

// 3 - Activate itself.
private void Spawn()
{
hasSpawn = true;

// Enable everything
// -- Collider
coliderComponent.enabled = true;
// -- Moving
moveScript.enabled = true;
// -- Shooting
foreach (WeaponScript weapon in weapons)
{
weapon.enabled = true;
}
}
}

Start the game. Yes, there’s a bug.

Disabling the “MoveScript” as a negative effect: The


player never reaches the enemies as they’re all moving
with the Foreground layer scrolling:
Remember: we’ve added a “ScrollingScript” to this
layer in order to move the camera along with the
player.

But there is a simple solution: move the


“ScrollingScript” from the Foregroundlayer to the player!

Why not after all? The only thing that is moving in this
layer is him, and the script is not specific to a kind of
object.

Push the “Play” button and observe: It works.

1. Enemies are disabled until they spawn (i.e., until


the camera reaches their positions).
2. Then they disappear when they are outside the
camera.

Keeping the player in the camera bounds


You might have noticed that the player is not (yet)
restricted to the camera area. “Play” the game, push
the “Left Arrow” and watch him leaves the camera.

We have to fix that.

Open the “PlayerScript”, and add this at the end of the


“Update()” method:
void Update()
{
// ...

// 6 - Make sure we are not outside the camera bounds


var dist = (transform.position - Camera.main.transform.position).z;

var leftBorder = Camera.main.ViewportToWorldPoint(


new Vector3(0, 0, dist)
).x;

var rightBorder = Camera.main.ViewportToWorldPoint(


new Vector3(1, 0, dist)
).x;

var topBorder = Camera.main.ViewportToWorldPoint(


new Vector3(0, 0, dist)
).y;

var bottomBorder = Camera.main.ViewportToWorldPoint(


new Vector3(0, 1, dist)
).y;

transform.position = new Vector3(


Mathf.Clamp(transform.position.x, leftBorder, rightBorder),
Mathf.Clamp(transform.position.y, topBorder, bottomBorder),
transform.position.z
);

// End of the update method


}

Nothing complicated, just verbose.

We get the camera edges and we make sure the player


position (the center of the sprite) is inside the area
borders.

Tweak the code to better understand what is


happening.

Você também pode gostar