Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon

The Game World

Save for later
  • 39 min read
  • 23 Feb 2016

article-image

In this article, we will cover the basics of creating immersive areas where players can walk around and interact, as well as some of the techniques used to manage those areas.

This article will give you some practical tips and tricks of the spritesheet system introduced with Unity 4.3 and how to get it to work for you.

Lastly, we will also have a cursory look at how shaders work in the 2D world and the considerations you need to keep in mind when using them. However, we won't be implementing shaders as that could be another book in itself.

The following is the list of topics that will be covered in this article:

  • Working with environments
  • Looking at sprite layers
  • Handling multiple resolutions
  • An overview of parallaxing and effects
  • Shaders in 2D – an overview

(For more resources related to this topic, see here.)

Backgrounds and layers

Now that we have our hero in play, it would be nice to give him a place to live and walk around, so let's set up the home town and decorate it.

Firstly, we are going to need some more assets. So, from the asset pack you downloaded earlier, grab the following assets from the Environments pack, place them in the AssetsSpritesEnvironment folder, and name them as follows:

  • Name the ENVIRONMENTS STEAMPUNKbackground01.png file Assets SpritesEnvironmentbackground01
  • Name the ENVIRONMENTSSTEAMPUNKenvironmentalAssets.png file AssetsSpritesEnvironmentenvironmentalAssets
  • Name the ENVIRONMENTSFANTASYenvironmentalAssets.png file Assets SpritesEnvironmentenvironmentalAssets2

To slice or not to slice

It is always better to pack many of the same images on to a single asset/atlas and then use the Sprite Editor to define the regions on that texture for each sprite, as long as all the sprites on that sheet are going to get used in the same scene. The reason for this is when Unity tries to draw to the screen, it needs to send the images to draw to the graphics card; if there are many images to send, this can take some time. If, however, it is just one image, it is a lot simpler and more performant with only one file to send.

There needs to be a balance; too large an image and the upload to the graphics card can take up too many resources, too many individual images and you have the same problem.

The basic rule of thumb is as follows:

  • If the background is a full screen background or large image, then keep it separately.
  • If you have many images and all are for the same scene, then put them into a spritesheet/atlas.
  • If you have many images but all are for different scenes, then group them as best you can—common items on one sheet and scene-specific items on different sheets. You'll have several spritesheets to use.

You basically want to keep as much stuff together as makes sense and not send unnecessary images that won't get used to the graphics card. Find your balance.

The town background

First, let's add a background for the town using the AssetsSpritesEnvironmentbackground01 texture. It is shown in the following screenshot:

game-world-img-0

With the background asset, we don't need to do anything else other than ensure that it has been imported as a sprite (in case your project is still in 3D mode), as shown in the following screenshot:

game-world-img-1

The town buildings

For the steampunk environmental assets (AssetsSpritesEnvironmentenvironmentalAssets) that are shown in the following screenshot, we need a bit more work; once these assets are imported, change the Sprite Mode to Multiple and load up the Sprite Editor using the Sprite Editor button.

game-world-img-2

Next, click on the Slice button, leave the settings at their default options, and then click on the Slice button in the new window as shown in the following screenshot:

game-world-img-3

Click on Apply and close the Sprite Editor. You will have four new sprite textures available as seen in the following screenshot:

game-world-img-4

The extra scenery

We saw what happens when you use a grid type split on a spritesheet and when the automatic split works well, so what about when it doesn't go so well? If we look at the Fantasy environment pack (AssetsSpritesEnvironmentenvironmentalAssets2), we will see the following:

game-world-img-5

After you have imported it and run the Split in Sprite Editor, you will notice that one of the sprites does not get detected very well; altering the automatic split settings in this case doesn't help, so we need to do some manual manipulation as shown in the following screenshot:

game-world-img-6

In the previous screenshot, you can see that just two of the rocks in the top-right sprite have been identified by the splicing routine. To fix this, just delete one of the selections and then expand the other manually using the selection points in the corner of the selection box (after clicking on the sprite box). Here's how it will look before the correction:

game-world-img-7

After correction, you should see something like the following screenshot:

game-world-img-8

This gives us some nice additional assets to scatter around our towns and give it a more homely feel, as shown in the following screenshot:

game-world-img-9

Building the scene

So, now that we have some nice assets to build with, we can start building our first town.

Adding the town background

Returning to the scene view, you should see the following:

game-world-img-10

If, however, we add our town background texture (AssetsSpritesBackgroundsBackground.png) to the scene by dragging it to either the project hierarchy or the scene view, you will end up with the following:

game-world-img-11

Be sure to set the background texture position appropriately once you add it to the scene; in this case, be sure the position of the transform is centered in the view at X = 0, Y = 0, Z = 0.

Unity does have a tendency to set the position relative to where your 3D view is at the time of adding it—almost never where you want it.

Our player has vanished!

The reason for this is simple: Unity's sprite system has an ordering system that comes in two parts.

Sprite sorting layers

Sorting Layers (Edit | Project Settings | Tags and Layers) are a collection of sprites, which are bulked together to form a single group. Layers can be configured to be drawn in a specific order on the screen as shown in the following screenshot:

game-world-img-12

Sprite sorting order

Sprites within an individual layer can be sorted, allowing you to control the draw order of sprites within that layer. The sprite Inspector is used for this purpose, as shown in the following screenshot:

game-world-img-13

Sprite's Sorting Layers should not be confused with Unity's rendering layers. Layers are a separate functionality used to control whether groups of game objects are drawn or managed together, whereas Sorting Layers control the draw order of sprites in a scene.

So the reason our player is no longer seen is that it is behind the background. As they are both in the same layer and have the same sort order, they are simply drawn in the order that they are in the project hierarchy.

Updating the scene Sorting Layers

To resolve the update of the scene's Sorting Layers, let's organize our sprite rendering by adding some sprite Sorting Layers. So, open up the Tags and Layers inspector pane as shown in the following screenshot (by navigating to Edit | Project settings | Tags and Layers), and add the following Sorting Layers:

  • Background
  • Player
  • Foreground
  • GUI

game-world-img-14

You can reorder the layers underneath the default anytime by selecting a row and dragging it up and down the sprite's Sorting Layers list.

With the layers set up, we can now configure our game objects accordingly. So, set the Sorting Layer on our background01 sprite to the Background layer as shown in the following screenshot:

game-world-img-15

Then, update the PlayerSprite layer to Player; our character will now be displayed in front of the background.

You can just keep both objects on the same layer and set the Sort Order value appropriately, keeping the background to a Sort Order value of 0 and the player to 10, which will draw the player in front. However, as you add more items to the scene, things will get tricky quickly, so it is better to group them in a layer accordingly.

Now when we return to the scene, our hero is happily displayed but he is seen hovering in the middle of our village. So let's fix that next by simply changing its position transform in the Inspector window.

Setting the Y position transform to -2 will place our hero nicely in the middle of the street (provided you have set the pivot for the player sprite to bottom), as shown in the following screenshot:

game-world-img-16

Feel free at this point to also add some more background elements such as trees and buildings to fill out the scene using the environment assets we imported earlier.

Working with the camera

If you try and move the player left and right at the moment, our hero happily bobs along. However, you will quickly notice that we run into a problem: the hero soon disappears from the edge of the screen. To solve this, we need to make the camera follow the hero.

When creating new scripts to implement something, remember that just about every game that has been made with Unity has most likely implemented either the same thing or something similar. Most just get on with it, but others and the Unity team themselves are keen to share their scripts to solve these challenges. So in most cases, we will have something to work from. Don't just start a script from scratch (unless it is a very small one to solve a tiny issue) if you can help it; here's some resources to get you started:

Unity sample projects: http://Unity3d.com/learn/tutorials/projects

Unity Patterns: http://unitypatterns.com/

Unity wiki scripts section: http://wiki.Unity3d.com/index.php/Scripts (also check other stuff for detail)

Once you become more experienced, it is better to just use these scripts as a reference and try to create your own and improve on them, unless they are from a maintained library such as https://github.com/nickgravelyn/UnityToolbag.

To make the camera follow the players, we'll take the script from the Unity 2D sample and modify it to fit in our game. This script is nice because it also includes a Mario style buffer zone, which allows the players to move without moving the camera until they reach the edge of the screen.

Create a new script called FollowCamera in the AssetsScripts folder, remove the Start and Update functions, and then add the following properties:

using UnityEngine;

 

public class FollowCamera : MonoBehavior {

 

  // Distance in the x axis the player can move before the

  // camera follows.

  public float xMargin = 1.5f;

 

  // Distance in the y axis the player can move before the

  // camera follows.

  public float yMargin = 1.5f;

 

  // How smoothly the camera catches up with its target

  // movement in the x axis.

  public float xSmooth = 1.5f;

 

  // How smoothly the camera catches up with its target

  // movement in the y axis.

  public float ySmooth = 1.5f;

 

  // The maximum x and y coordinates the camera can have.

  public Vector2 maxXAndY;

 

  // The minimum x and y coordinates the camera can have.

  public Vector2 minXAndY;

 

  // Reference to  the player's transform.

  public Transform player;

}

The variables are all commented to explain their purpose, but we'll cover each as we use them.

First off, we need to get the player object's position so that we can track the camera to it by discovering it from the object it is attached to. This is done by adding the following code in the Awake function:

 void Awake()

    {

        // Setting up the reference.

        player = GameObject.Find("Player").transform;

  if (player == null)

  {

    Debug.LogError("Player object not found");

  }

 

    }

An alternative to discovering the player this way is to make the player property public and then assign it in the editor. There is no right or wrong way—just your preference.

It is also a good practice to add some element of debugging to let you know if there is a problem in the scene with a missing reference, else all you will see are errors such as object not initialized or variable was null.

Next, we need a couple of helper methods to check whether the player has moved near the edge of the camera's bounds as defined by the Max X and Y variables. In the following code, we will use the settings defined in the preceding code to control how close you can get to the end result:

  bool CheckXMargin()

    {

        // Returns true if the distance between the camera and the

  // player in the x axis is greater than the x margin.

        return Mathf.Abs

(transform.position.x - player.position.x) > xMargin;

    }

 

    bool CheckYMargin()

    {

        // Returns true if the distance between the camera and the

  // player in the y axis is greater than the y margin.

        return Mathf.Abs

(transform.position.y - player.position.y) > yMargin;

    }

To finish this script, we need to check each frame when the scene is drawn to see whether the player is close to the edge and update the camera's position accordingly. Also, we need to check if the camera bounds have reached the edge of the screen and not move it beyond.

Comparing Update, FixedUpdate, and LateUpdate

There is usually a lot of debate about which update method should be used within a Unity game. To put it simply, the FixedUpdate method is called on a regular basis throughout the lifetime of the game and is generally used for physics and time sensitive code. The Update method, however, is only called after the end of each frame that is drawn to the screen, as the time taken to draw the screen can vary (due to the number of objects to be drawn and so on). So, the Update call ends up being fairly irregular.

For more detail on the difference between Update and FixedUpdate see the Unity Learn tutorial video at http://unity3d.com/learn/tutorials/modules/beginner/scripting/update-and-fixedupdate.

As the player is being moved by the physics system, it is better to update the camera in the FixedUpdate method:

void FixedUpdate()

    {

        // By default the target x and y coordinates of the camera

        // are it's current x and y coordinates.

        float targetX = transform.position.x;

        float targetY = transform.position.y;

 

        // If the player has moved beyond the x margin...

        if (CheckXMargin())

            // the target x coordinate should be a Lerp between

            // the camera's current x position and the player's

 // current x position.

            targetX = Mathf.Lerp(transform.position.x,

 player.position.x, xSmooth *

Time.fixedDeltaTime );

 

        // If the player has moved beyond the y margin...

        if (CheckYMargin())

            // the target y coordinate should be a Lerp between

            // the camera's current y position and the player's

            // current y position.

            targetY = Mathf.Lerp(transform.position.y,

 player.position.y, ySmooth *

Time. fixedDeltaTime );

 

        // The target x and y coordinates should not be larger

        // than the maximum or smaller than the minimum.

        targetX = Mathf.Clamp(targetX, minXAndY.x, maxXAndY.x);

        targetY = Mathf.Clamp(targetY, minXAndY.y, maxXAndY.y);

 

        // Set the camera's position to the target position with

        // the same z component.

        transform.position =

         new Vector3(targetX, targetY, transform.position.z);

    }

As they say, every game is different and how the camera acts can be different for every game. In a lot of cases, the camera should be updated in the LateUpdate method after all drawing, updating, and physics are complete. This, however, can be a double-edged sword if you rely on math calculations that are affected in the FixedUpdate method, such as Lerp. It all comes down to tweaking your camera system to work the way you need it to do.

Once the script is saved, just attach it to the Main Camera element by dragging the script to it or by adding a script component to the camera and selecting the script.

Finally, we just need to configure the script and the camera to fit our game size as follows:

game-world-img-17

Set the orthographic Size of the camera to 2.7 and the Min X and Max X sizes to 5 and -5 respectively.

The perils of resolution

When dealing with cameras, there is always one thing that will trip us up as soon as we try to build for another platform—resolution.

By default, the Unity player in the editor runs in the Free Aspect mode as shown in the following screenshot:

game-world-img-18

The Aspect mode (from the Aspect drop-down) can be changed to represent the resolutions supported by each platform you can target. The following is what you get when you switch your build target to each platform:

game-world-img-19

To change the build target, go into your project's Build Settings by navigating to File | Build Settings or by pressing Ctrl + Shift + B, then select a platform and click on the Switch Platform button. This is shown in the following screenshot:

game-world-img-20

When you change the Aspect drop-down to view in one of these resolutions, you will notice how the aspect ratio for what is drawn to the screen changes by either stretching or compressing the visible area. If you run the editor player in full screen by clicking on the Maximize on Play button () and then clicking on the play icon, you will see this change more clearly. Alternatively, you can run your project on a target device to see the proper perspective output.

The reason I bring this up here is that if you used fixed bounds settings for your camera or game objects, then these values may not work for every resolution, thereby putting your settings out of range or (in most cases) too undersized. You can handle this by altering the settings for each build or using compiler predirectives such as #if UNITY_METRO to force the default depending on the build (in this example, Windows 8).

To read more about compiler predirectives, check the Unity documentation at http://docs.unity3d.com/Manual/PlatformDependentCompilation.html.

A better FollowCamera script

If you are only targeting one device/resolution or your background scrolls indefinitely, then the preceding manual approach works fine. However, if you want it to be a little more dynamic, then we need to know what resolution we are working in and how much space our character has to travel. We will perform the following steps to do this:

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
  1. We will change the min and max variables to private as we no longer need to configure them in the Inspector window. The code is as follows:
      // The maximum x and y coordinates the camera can have.
    
        private Vector2 maxXAndY;
    
     
    
        // The minimum x and y coordinates the camera can have.
    
        private Vector2 minXAndY;
  2. To work out how much space is available in our town, we need to interrogate the rendering size of our background sprite. So, in the Awake function, we add the following lines of code:
    // Get the bounds for the background texture - world       size
    
        var backgroundBounds = GameObject.Find("background")      .renderer.bounds;
  3. In the Awake function, we work out our resolution and viewable space by interrogating the ViewPort method on the camera and converting it to the same coordinate type as the sprite. This is done using the following code:
      // Get the viewable bounds of the camera in world
    
        // coordinates
    
        var camTopLeft = camera.ViewportToWorldPoint      (new Vector3(0, 0, 0));
    
        var camBottomRight = camera.ViewportToWorldPoint      (new Vector3(1, 1, 0));
  4. Finally, in the Awake function, we update the min and max values using the texture size and camera real-world bounds. This is done using the following lines of code:
     // Automatically set the min and max values
    
        minXAndY.x = backgroundBounds.min.x - camTopLeft.x;
    
        maxXAndY.x = backgroundBounds.max.x - camBottomRight.x;

In the end, it is up to your specific implementation for the type of game you are making to decide which pattern works for your game.

Transitioning and bounds

So our camera follows our player, but our hero can still walk off the screen and keep going forever, so let us stop that from happening.

Towns with borders

As you saw in the preceding section, you can use Unity's camera logic to figure out where things are on the screen. You can also do more complex ray testing to check where things are, but I find these are overly complex unless you depend on that level of interaction.

The simpler answer is just to use the native Box2D physics system to keep things in the scene. This might seem like overkill, but the 2D physics system is very fast and fluid, and it is simple to use.

Once we add the physics components, Rigidbody 2D (to apply physics) and a Box Collider 2D (to detect collisions) to the player, we can make use of these components straight away by adding some additional collision objects to stop the player running off.

To do this and to keep things organized, we will add three empty game objects (either by navigating to GameObject | Create Empty, or by pressing Ctrl + Shift +N) to the scene (one parent and two children) to manage these collision points, as shown in the following screenshot:

game-world-img-21

I've named them WorldBounds (parent) and LeftBorder and RightBorder (children) for reference. Next, we will position each of the child game objects to the left- and right-hand side of the screen, as shown in the following screenshot:

game-world-img-22

Next, we will add a Box Collider 2D to each border game object and increase its height just to ensure that it works for the entire height of the scene. I've set the Y value to 5 for effect, as shown in the following screenshot:

game-world-img-23

The end result should look like the following screenshot with the two new colliders highlighted in green:

game-world-img-24

Alternatively, you could have just created one of the children, added the box collider, duplicated it (by navigating to Edit | Duplicate or by pressing Ctrl + D), and moved it. If you have to create multiples of the same thing, this is a handy tip to remember.

If you run the project now, then our hero can no longer escape this town on his own. However, as we want to let him leave, we can add a script to the new Boundary game object so that when the hero reaches the end of the town, he can leave.

Journeying onwards

Now that we have collision zones on our town's borders, we can hook into this by using a script to activate when the hero approaches.

Create a new C# script called NavigationPrompt, clear its contents, and populate it with the following code:

using UnityEngine;

 

public class NavigationPrompt : MonoBehavior {

 

  bool showDialog;

 

  void OnCollisionEnter2D(Collision2D col)

  {

    showDialog = true;

  }

 

  void OnCollisionExit2D(Collision2D col)

  {

    showDialog = false;

  }

}

The preceding code gives us the framework of a collision detection script that sets a flag on and off if the character interacts with what the script is attached to, provided it has a physics collision component. Without it, this script would do nothing and it won't cause an error.

Next, we will do something with the flag and display some GUI when the flag is set. So, add the following extra function to the preceding script:

 void OnGUI()

    {

      if (showDialog)

      {

        //layout start

        GUI.BeginGroup(new Rect(Screen.width / 2 - 150, 50, 300,           250));

 

        //the menu background box

        GUI.Box(new Rect(0, 0, 300, 250), "");

 

        // Information text

        GUI.Label(new Rect(15, 10, 300, 68), "Do you want to           travel?");

 

        //Player wants to leave this location

        if (GUI.Button(new Rect(55, 100, 180, 40), "Travel"))

        {

          showDialog = false;

 

          // The following line is commented out for now

          // as we have nowhere to go :D

          //Application.LoadLevel(1);}

 

        //Player wants to stay at this location

        if (GUI.Button(new Rect(55, 150, 180, 40), "Stay"))

        {

          showDialog = false;

        }

 

        //layout end

        GUI.EndGroup();

      }

    }

The function itself is very simple and only activates if the showDialog flag is set to true by the collision detection. Then, we will perform the following steps:

  1. In the OnGUI method, we set up a dialog window region with some text and two buttons.
  2. One button asks if the player wants to travel, which would load the next area (commented out for now as we only have one scene), and close the dialog.
  3. One button simply closes the dialog if the hero didn't actually want to leave. As we haven't stopped moving the player, the player can also do this by moving away.

If you now add the NavigationPrompt script to the two world border (LeftBorder and RightBorder) game objects, this will result in the following simple UI whenever the player collides with the edges of our world:

game-world-img-25

We can further enhance this by tagging or naming our borders to indicate a destination. I prefer tagging, as it does not interfere with how my scene looks in the project hierarchy; also, I can control what tags are available and prevent accidental mistyping.

To tag a game object, simply select a Tag using the drop-down list in the Inspector when you select the game object in the scene or project. This is shown in the following screenshot:

game-world-img-26

If you haven't set up your tags yet or just wish to add a new one, select Add Tag in the drop-down menu; this will open up the Tags and Layers window of Inspector. Alternatively, you can call up this window by navigating to Edit | Project Settings | Tags and layers in the menu. It is shown in the following screenshot:

game-world-img-27

You can only edit or change user-defined tags. There are several other tags that are system defined. You can use these as well; you just cannot change, remove, or edit them. These include Player, Respawn, Finish, Editor Only, Main Camera, and GameController.

As you can see from the preceding screenshot, I have entered two new tags called The Cave and The World, which are the two main exit points from our town.

Unity also adds an extra item to the arrays in the editor. This helps you when you want to add more items; it's annoying when you want a fixed size but it is meant to help. When the project runs, however, the correct count of items will be exposed.

Once these are set up, just return to the Inspector for the two borders, and set the right one to The World and the left to The Cave.

Now, I was quite specific in how I named these tags, as you can now reuse these tags in the script to both aid navigation and also to notify the player where they are going. To do this, simply update the Do you want to travel to line to the following:

//Information text

GUI.Label(new Rect(15, 10, 300, 68), "Do you want to travel to " +   this.tag + "?");

Here, we have simply appended the dialog as it is presented to the user with the name of the destination we set in the tag. Now, we'll get a more personal message, as shown in the following screenshot:

game-world-img-28

Planning for the larger picture

Now for small games, the preceding implementation is fine; however, if you are planning a larger world with a large number of interactions, provide complex decisions to prevent the player continuing unless they are ready.

As the following diagram shows, there are several paths the player can take and in some cases, these is only one way. Now, we could just build up the logic for each of these individually as shown in the screenshot, but it is better if we build a separate navigation system so that we have everything in one place; it's just easier to manage that way.

game-world-img-29

This separation is a fundamental part of any good game design. Keeping the logic and game functionality separate makes it easier to maintain in the future, especially when you need to take internationalization into account (but we will learn more about that later).

Now, we'll change to using a manager to handle all the world/scene transitions, and simplify the tag names we use as they won't need to be displayed.

So, The Cave will be renamed as just Cave, and we will get the text to display from the navigation manager instead of the tag.

So, by separating out the core decision making functionality out of the prompt script, we can build the core manager for navigation. Its primary job is to maintain where a character can travel and information about that destination.

First, we'll update the tags we created earlier to simpler identities that we can use in our navigation manager (update The Cave to Cave01 and The World to World).

Next, we'll create a new C# script called NavigationManager in our AssetsScripts folder, and then replace its contents with the following lines of code:

public static class NavigationManager

{

 

 

  public static Dictionary<string,string> RouteInformation =     new Dictionary<string,string>()

  {

    { "World", "The big bad world"},

    { "Cave", "The deep dark cave"},

  };

 

  public static string GetRouteInfo(string destination)

  {

    return RouteInformation.ContainsKey(destination) ?     RouteInformation[destination] : null;

  }

 

  public static bool CanNavigate(string destination)

  {

    return true;

  }

 

  public static void NavigateTo(string destination)

  {

    // The following line is commented out for now

    // as we have nowhere to go :D

    //Application.LoadLevel(destination);

  }

}

Notice the ? and : operators in the following statement:

RouteInformation.ContainsKey(destination) ?   RouteInformation[destination] : null;

These operators are C# conditional operators. They are effectively the shorthand of the following:

if(RouteInformation.ContainsKey(destination))

{

  return RouteInformation[destination];

}

else

{

  return null;

}

Shorter, neater, and much nicer, don't you think?

For more information, see the MSDN C# page at http://bit.ly/csharpconditionaloperator.

The script is very basic for now, but contains several following key elements that can be expanded to meet the design goals of your game:

  • RouteInformation: This is a list of all the possible destinations in the game in a dictionary.
    • A static list of possible destinations in the game, and it is a core part of the manager as it knows everywhere you can travel in the game in one place.
  • GetRouteInfo: This is a basic information extraction function.
    • A simple controlled function to interrogate the destination list. In this example, we just return the text to be displayed in the prompt, which allows for more detailed descriptions that we could use in tags. You could use this to provide alternate prompts depending on what the player is carrying and whether they have a lit torch, for example.
  • CanNavigate: This is a test to see if navigation is possible.
    • If you are going to limit a player's travel, you need a way to test if they can move, allowing logic in your game to make alternate choices if the player cannot. You could use a different system for this by placing some sort of block in front of a destination to limit choice (as used in the likes of Zelda), such as an NPC or rock. As this is only an example, we can always travel and add logic to control it if you wish.
  • NavigateTo: This is a function to instigate navigation.
    • Once a player can travel, you can control exactly what happens in the game: does navigation cause the next scene to load straight away (as in the script currently), or does the current scene fade out and then a traveling screen is shown before fading the next level in? Granted, this does nothing at present as we have nowhere to travel to.

The script you will notice is different to the other scripts used so far, as it is a static class. This means it sits in the background, only exists once in the game, and is accessible from anywhere. This pattern is useful for fixed information that isn't attached to anything; it just sits in the background waiting to be queried.

Later, we will cover more advanced types and classes to provide more complicated scenarios.

With this class in place, we just need to update our previous script (and the tags) to make use of this new manager. Update the NavigationPrompt script as follows:

  1. Update the collision function to only show the prompt if we can travel. The code is as follows:
    void OnCollisionEnter2D(Collision2D col)
    
    {
    
      //Only allow the player to travel if allowed
    
      if (NavigationManager.CanNavigate(this.tag))
    
      {
    
        showDialog = true;
    
      }
    
    }
  2. When the dialog shows, display the more detailed destination text provided by the manager for the intended destination. The code is as follows:
    //Dialog detail - updated to get better detail
    
    GUI.Label(new Rect(15, 10, 300, 68), "Do you want to travel   to " + NavigationManager.GetRouteInfo(this.tag) + "?");
  3. If the player wants to travel, let the manager start the travel process. The code is as follows:
    //Player wants to leave this location
    
    if (GUI.Button(new Rect(55, 100, 180, 40), "Travel"))
    
    {
    
      showDialog = false;
    
      NavigationManager.NavigateTo(this.tag);
    
    }

The functionality I've shown here is very basic and it is intended to make you think about how you would need to implement it for your game. With so many possibilities available, I could fill several articles on this kind of subject alone.

Backgrounds and active elements

A slightly more advanced option when building game worlds is to add a level of immersive depth to the scene. Having a static image to show the village looks good, especially when you start adding houses and NPCs to the mix; but to really make it shine, you should layer the background and add additional active elements to liven it up.

We won't add them to the sample project at this time, but it is worth experimenting with in your own projects (or try adding it to this one)—it is a worthwhile effect to look into.

Parallaxing

If we look at the 2D sample provided by Unity, the background is split into several panes—each layered on top of one another and each moving at a different speed when the player moves around. There are also other elements such as clouds, birds, buses, and taxes driving/flying around, as shown in the following screenshot:

game-world-img-30

Implementing these effects is very easy technically. You just need to have the art assets available. There are several scripts in the wiki I described earlier, but the one in Unity's own 2D sample is the best I've seen.

To see the script, just download the Unity Projects: 2D Platformer asset from https://www.assetstore.unity3d.com/en/#!/content/11228, and check out the BackgroundParallax script in the AssetsScripts folder.

The BackgroundParallax script in the platformer sample implements the following:

  • An array of background images, which is layered correctly in the scene (which is why the script does not just discover the background sprites)
  • A scaling factor to control how much the background moves in relation to the camera target, for example, the camera
  • A reducing factor to offset how much each layer moves so that they all don't move as one (or else what is the point, might as well be a single image)
  • A smoothing factor so that each background moves smoothly with the target and doesn't jump around

Implementing this same model in your game would be fairly simple provided you have texture assets that could support it. Just replicate the structure used in the platformer 2D sample and add the script. Remember to update the FollowCamera script to be able to update the base background, however, to ensure that it can still discover the size of the main area.

Foreground objects

The other thing you can do to liven up your game is to add random foreground objects that float across your scene independently. These don't collide with anything and aren't anything to do with the game itself. They are just eye candy to make your game look awesome.

The process to add these is also fairly simple, but it requires some more advanced Unity features such as coroutines, which we are not going to cover here. So, we will come back to these later.

In short, if you examine the BackgroundPropSpawner.cs script from the preceding Unity platformer 2D sample, you will have to perform the following steps:

  1. Create/instantiate an object to spawn.
  2. Set a random position and direction for the object to travel.
  3. Update the object over its lifetime.
  4. Once it's out of the scene, destroy or hide it.
  5. Wait for a time, and then start again.

This allows them to run on their own without impacting the gameplay itself and just adds that extra bit of depth. In some cases, I've seen particle effects are also used to add effect, but they are used sparingly.

Shaders and 2D

Believe it or not, all 2D elements (even in their default state) are drawn using a shader—albeit a specially written shader designed to light and draw the sprite in a very specific way. If you look at the player sprite in the inspector, you will see that it uses a special Material called Sprites-Default, as shown in the following screenshot:

game-world-img-31

This section is purely meant to highlight all the shading options you have in the 2D system. Shaders have not changed much in this update except for the addition of some 2D global lighting found in the default sprite shader.

For more detail on shaders in general, I suggest a dedicated Unity shader book such as https://www.packtpub.com/game-development/unity-shaders-and-effects-cookbook.

Clicking on the button next to Material field will bring up the material selector, which also shows the two other built-in default materials, as shown in the following screenshot:

game-world-img-32

However, selecting either of these will render your sprite invisible as they require a texture and lighting to work; they won't inherit from the Sprite Renderer texture. You can override this by creating your own material and assigning alternate sprite style shaders.

To create a new material, just select the AssetsMaterials folder (this is not crucial, but it means we create the material in a sensible place in our project folder structure) and then right click on and select Create | Material. Alternatively, do the same using the project view's Edit... menu option, as shown in the following screenshot:

game-world-img-33

This gives us a basic default Diffuse shader, which is fine for basic 3D objects. However, we also have two default sprite rendering shaders available. Selecting the shader dropdown gives us the screen shown in the following screenshot:

game-world-img-34

Now, these shaders have the following two very specific purposes:

  • Default: This shader inherits its texture from the Sprite Renderer texture to draw the sprite as is. This is a very basic functionality—just enough to draw the sprite. (It contains its own static lighting.)
  • Diffuse: This shader is the same as the Default shader; it inherits the texture of Default, but it requires an external light source as it does not contain any lighting—this has to be applied separately. It is a slightly more advanced shader, which includes offsets and other functions.

Creating one of these materials and applying it to the Sprite Renderer texture of a sprite will override its default constrained behavior. This opens up some additional shader options in the Inspector, as shown in the following screenshot:

game-world-img-35

These options include the following:

  • Sprite Texture: Although changing the Tiling and Offset values causes a warning to appear, they still display a function (even though the actual displayed value resets).
  • Tint: This option allows changing the default light tint of the rendered sprite. It is useful to create different colored objects from the same sprite.
  • Pixel snap: This option makes the rendered sprite crisper but narrows the drawn area. It is a trial and error feature (see the following sections for more information).

Achieving pixel perfection in your game in Unity can be a challenge due to the number of factors that can affect it, such as the camera view size, whether the image texture is a Power Of Two (POT) size, and the import setting for the image. This is basically a trial and error game until you are happy with the intended result.

If you are feeling adventurous, you can extend these default shaders (although this is out of the scope of this article). The full code for these shaders can be found at http://Unity3d.com/unity/download/archive.

If you are writing your own shaders though, be sure to add some lighting to the scene; otherwise, they are just going to appear dark and unlit. Only the default sprite shader is automatically lit by Unity. Alternatively, you can use the default sprite shader as a base to create your new custom shader and retain the 2D basic lighting.

Another worthy tip is to check out the latest version of the Unity samples (beta) pack. In it, they have added logic to have two sets of shaders in your project: one for mobile and one for desktop, and a script that will swap them out at runtime depending on the platform. This is very cool; check out on the asset store at https://www.assetstore.unity3d.com/#/content/14474 and the full review of the pack at http://darkgenesis.zenithmoon.com/unity3dsamplesbeta-anoverview/.

Going further

If you are the adventurous sort, try expanding your project to add the following:

  • Add some buildings to the town
  • Set up some entry points for a building and work that into your navigation system, for example, a shop
  • Add some rocks to the scene and color each differently using a manual material, maybe even add a script to randomly set the pixel color in the shader instead of creating several materials
  • Add a new scene for the cave using another environment background, and get the player to travel between them

Summary

This certainly has been a very busy article just to add a background to our scene, but working out how each scene will work is a crucial design element for the entire game; you have to pick a pattern that works for you and your end result once as changing it can be very detrimental (and a lot of work) in the future.

In this article, we covered the following topics:

  • Some more practice with the Sprite Editor and sprite slicer including some tips and tricks when it doesn't work (or you want to do it yourself)
  • Some camera tips, tricks, and scripts
  • An overview of sprite layers and sprite sorting
  • Defining boundaries in scenes
  • Scene navigation management and planning levels in your game
  • Some basics of how shaders work for 2D

For learning Unity 2D from basic you can refer to https://www.packtpub.com/game-development/learning-unity-2d-game-development-example.

Resources for Article:

 


Further resources on this subject: