Learn how to make a 2D game in Unity, using the new built-in 2D toolset introduced in Unity 4.3!
If you’ve tried making a 2D game with earlier versions of Unity, you know it was certainly possible, but you also know you had to jump through a few hoops to do it.
Maybe you applied textures to quads, adjusting their offsets with a script to create animations. If you used physics, they were in 3D, so you had to make sure your objects had sufficient depth to interact with each other while ensuring they didn’t accidentally rotate around their x- or y-axes. Or maybe you chose to use one of the various add-ons available on Unity’s Asset Store, for example 2D Toolkit or the Orthello 2D Framework, any one of which includes some great features but also forces you to work within its own set of constraints.
While all of these options are still available, Unity 4.3 introduces native tools that add a new dimension to your workflow options: the 2nd dimension!
This is the first in a planned series of tutorials that explore Unity’s native 2D support. Over the series you’ll create Zombie Conga, a game originally conceived for our Sprite Kit book, iOS Games by Tutorials. You’ll do things differently here, but the end result will be the same – a scrolling 2D game about a happy-go-lucky zombie who just wants to dance, the cats who join him in the afterlife party, and the old ladies who try to put a stop to this adorable abomination.
This Unity 4.3 2D tutorial focuses on Unity’s new asset type – Sprite. You’ll learn everything you need to know about Sprites here, and in future tutorials you’ll learn how to control animations through Unity’s Animators and you’ll get an introduction to Unity’s new 2D physics support.
There’s a lot to cover, so you should get going.
Note: This tutorial assumes you have at least some experience with Unity. You should know the basics of working with Unity’s interface, GameObjects and Components, and you should understand an instruction like, “Add a new cat to your scene by dragging cat from the Project browser into the Hierarchy.”
If you think any of that sounds like crazy talk, or if you’d like a moment to get yourself into the right mindset for dragging cats, you may want to go through a tutorial that gives a more thorough introduction to Unity, such as this one.
Finally, note that the instructions in this tutorial are tailored toward OS X. However, if you’re running on Windows don’t worry – since Unity works the same on Windows most of these instructions will still work just fine. There will be a few minor differences (such as using Windows Explorer instead of Finder) but you’ll get through it. Or just start using OS X – reader’s choice!
Getting Started
Unity introduced native 2D tools in version 4.3 (both free and pro), so be sure you have the latest version installed. You can download it from Unity’s website.
You’ll also need some art to make a 2D game. Fortunately, Mike Berg made some cool images for Zombie Conga. Download Mike’s art here and unzip it to someplace convenient.
Create Your Project
Open Unity and create a new project by choosing File\New Project…. Click Set… in the Create new Project tab of the Project Wizard dialog that appears.
Name the project ZombieConga, choose a folder in which to create it, and click Save.
Finally, choose 2D in the combo box labeled Set up defaults for:, as shown below, and click Create Project:
The above-mentioned combo box is the first 2D-related feature you’ll come across in Unity. It’s supposed to change the default import settings for your project’s art assets, but so far I haven’t seen it work properly. Fortunately, this isn’t a problem because you can change this setting in your project at any time, and doing so works fine.
To ensure it’s set properly, and so you know how to change it if you ever want to, choose Edit\Project Settings\Editor to open the Editor Settings in the Inspector. In the Default Behavior Mode section, choose 2D for the Mode value, as shown below:
The Default Behavior Mode defines the default import settings for your project’s art assets. When set to 3D, Unity assumes you want to create a Texture asset from an imported image file (e.g. a .PNG file); when set to 2D, Unity assumes you want an asset of type Sprite. You’ll read more details about Sprite assets and import settings throughout this tutorial.
The Scene View’s 2D Mode
The next 2D feature you’re faced with is the 2D toggle button in the Scene view’s control bar.
Click the 2D toggle button to enable 2D mode, as shown below:
This button toggles the Scene view’s camera between perspective and orthographic projections. What’s the difference?
When viewed with a perspective projection, objects appear smaller as they move further away from the camera, just like objects in the real world look when you see them with your eyes. However, when viewed with an orthographic projection, an object’s distance from the camera doesn’t affect its size. Therefore, in 2D mode, an object that is further away from the camera will appear behind any closer objects, but its size will remain unchanged regardless of its position.
The following image shows two Scene views, each looking at the same two cubes from the same location. The top view is in 2D mode while the bottom one is not:
The previous screenshot also shows how 2D mode hides the Scene Gizmo that lets you change the orientation of the Scene view’s camera. With 2D mode enabled, the orientation is fixed so the positive y-axis points up and the positive x-axis points to the right.
Important: Toggling this setting has no effect on how your game finally appears when played – that’s determined by the camera(s) you set up in your scene – but it can be helpful when arranging objects. You’ll probably move back and forth between these two modes while creating your own 2D games, and even sometimes while creating 3D games, but this tutorial’s screenshots all show the Scene view in 2D mode.
Question: Are you someone who feels better following along with a tutorial when your interface matches the one you see in the screenshots? Then check out the next spoiler to ease your mind!
Solution Inside: Copy my Unity layout, if you want to. |
SelectShow> |
Each of Unity’s panels is helpfully labeled, so you should have no problem following along using your preferred layout. However, if you’d like your screen to more closely match this tutorial’s screenshots, here’s how:
I’m running on OS X with Unity’s Dark skin, which is only available in Unity Pro because professionals have sensitive baby eyes that must never be exposed to bright lights. Ever.
I generally have 8 tabs open, arranged as shown below:
As you can see, in the upper left I have the Scene, Console, and Animator views grouped together as tabs, while I have the Game, Project, and Animation views grouped together as tabs in the lower left. The Project browser is in Two Columns Layout.
To the right of those two groups I keep the Hierarchy, and to the right of that the Inspector.
Of course, sometimes I change that setup. There’s no right way, so feel free to arrange your interface any way that makes you happy!
|
Sprites, Made Easily
How easy is it to add a sprite to your scene using Unity’s new features? Try the following experiment to find out.
Step 1: Drag cat.png from your Finder window into the Scene view, as demonstrated below:
Step 2: Use some of the time you save making your game to send a thank you note to the Unity devs.
Phew! That was pretty tricky! If you got lost, don’t feel bad, just re-read those instructions and try again. ;]
Note: Wondering why there are two cat images shown in the animation above? Don’t worry, I’ll explain that later on.
This demonstration was simplified by relying on Unity’s default import settings, which oftentimes won’t be correct for your images. However, this serves to illustrate a point – Unity’s new features make working in 2D amazingly easy! The rest of this tutorial covers everything you’ll need to know to really get started working with 2D graphics in Unity.
Sprite Assets
Select cat in the Hierarchy and look in the Inspector. Your Inspector most likely won’t show the same position that you see in the following screenshot, but don’t worry about that right now. What’s important to note here is that, in order to display the cat in the scene, Unity attached a Sprite Renderer component to a GameObject.
It’s not obvious, but Unity created geometry for the object, too. For each Sprite, Unity creates a mesh that basically fits the non-clear pixels in your image. Notice the blue mesh in the following image of the zombie:
By creating a mesh like this rather than applying your sprites as textures on a quad, Unity can improve your scene’s fill rate at render-time. It also makes creating polygon colliders easy, but that will have to wait for a tutorial later in this series.
Note: Don’t let the sudden appearance of the zombie startle you. I just showed the zombie because its mesh was more interesting than the one generated for the cat sprite.
You’ll learn about the Sprite Renderer’s properties throughout this tutorial, but for now, look at the field labeled Sprite. This shows the name of the Sprite asset assigned to this renderer. You can only assign one Sprite at a time, but later you’ll learn how to update this field at runtime to create animations.
As you can see in the following image, the cat
GameObject has a Sprite named cat
assigned to its renderer:
Be sure the Project browser is visible. Then click inside the Sprite field in the Inspector to locate and highlight the Sprite asset in the Project browser, as shown here:
Note: The highlighted border fades away after a few seconds, so if you don’t notice it, click the Sprite field again. Of course, with only one asset in your project, it’s unlikely you’ll miss it. ;]
As you can see in the previous screenshot, Unity highlighted an item named cat inside the Project browser, which is a child of another object, also named cat. Two cats in the Project browser? Yeah, that could be confusing. Here’s what’s going on:
- The parent cat is the Texture asset. It’s a reference to the original art file you imported, cat.png, and controls the import settings used to create the Sprites from your artwork. As you can see, it shows a nice thumbnail of the file’s contents.
- The child cat is a Sprite asset that Unity created when it imported cat.png. In this case, there is only one child because Unity only created a single Sprite from the file, but later in the section on slicing sprite sheets you’ll see how to create multiple Sprites from a single image.
Note: While I’ve been claiming Unity renders Sprite objects, the Sprite
class actually only contains the information needed to access a Texture2D
object, which is what stores the real image data. You can create your own Texture2D
objects dynamically if you want to generate Sprite
s at runtime, but that discussion will have to wait for a future tutorial.
As you saw with cat.png, you can add Sprites to your scene by dragging art assets from the Finder directly into the Scene view (or the Hierarchy, if you’d like). But more commonly, you’ll add assets to your project prior to adding objects to your scene.
Add to your project the remaining image files you downloaded: background.png, enemy.png, and zombie.png.
Solution Inside: Not sure how to add image assets to your project? Find out here. |
SelectShow> |
Unity gives you the following five options to get assets into your project:
- Drag files from your Finder window into the Project browser.
- Go to Assets\Import New Asset…, select your files and click Import.
- Right-click within the Project browser, choose Assets\Import New Asset…, select your files and click Import.
- Within your OS, add the files directly to your project’s Assets directory, or one of its subdirectories. Unity refreshes your project automatically to keep assets up to date. Warning: Although it’s ok to add assets this way, you should never delete assets directly from your file system. Instead, always delete assets from within Unity, because Unity maintains metadata about your project’s assets and modifying the file system directly could corrupt it.
- Of course, you can also drag files directly into the Hierarchy or the Scene view, but doing so has the additional effect of creating a GameObject in the current scene.
|
Add an enemy to your scene by dragging enemy from the Project browser to the Hierarchy.
Just like with cat, there are two items named enemy in the Project browser, but it doesn’t matter which one you choose. That’s because dragging a Sprite asset (the child) always uses that specific Sprite, whereas dragging a Texture asset (the parent) uses the first child Sprite, which is the same thing in a case like this where there is only one child.
Select enemy in the Hierarchy and set its Transform component’s Position to (2, 0, 0), as shown below:
Before your scene starts getting sloppy, select cat in the Hierarchy and set its Position to (0, 2, 0), like this:
Your scene should now be arranged like the following image:
Finally, drag background from the Project browser to the Hierarchy, and set its Position to (0,0,0), as shown below:
You’ll improve the background’s image quality a bit later, so don’t worry if it doesn’t look quite right. (Hint: Importing background.png is one of those times where Unity’s default settings aren’t correct.) Your Scene view will now look something like this:
Don’t be alarmed by the fact that you can no longer see the cat or the old lady in your Scene view. They’re each just taking a brief dirt nap, but you’ll dig them up soon enough. However, before you do that, you need to slice up a corpse! Err, a corpse’s sprites, that is.
Slicing Sprite Sheets
You already imported zombie.png into your project, but this file is different from the other ones you imported. Instead of a single zombie image, it contains several, as shown below:
Such a file is usually referred to as a sprite sheet, and you’ll want Unity to create a separate Sprite asset for each of the sheet’s individual images.
Expand zombie in the Project browser. As you can see in the following screenshot, Unity created a single child – a Sprite containing the entire image – which is not what you wanted.
Fortunately, Unity offers a simple solution you can use to treat this image as a sprite sheet. Select the top-level zombie in the Project browser to open its Import Settings in the Inspector.
Set Sprite Mode to Multiple (see the following image) and click Apply:
Choosing this option caused a new button labeled Sprite Editor to appear. It also removed the Pivot property, because each individual sprite will define its own pivot point elsewhere.
Solution Inside: Not sure about pivot points? Look inside for more info. |
SelectShow> |
A Sprite’s Pivot defines the origin of its local coordinate system. This point, for example, the Sprite’s Center or its Top Left corner, will be placed at the position defined by the GameObject’s Transform; rotations will occur around this point; and scaling will shrink or grow the Sprite from this point.
You can assign a custom point by choosing Custom from the Pivot field combo box. If you do so, you’ll be presented with the following new fields:
The values you supply for X and Y are normalized, so a value of 0.5 is the center. However, you are free to use values that are less than 0 or greater than 1 if you want the origin to lie outside of the sprite’s bounds.
|
Notice in the Project browser (shown in the following image) that the zombie texture asset no longer has any children, as indicated by the lack of a small arrow on its right side:
In this state, the zombie texture is unusable. If you tried to drag it into the Hierarchy, you would get a message indicating it has no Sprites. That’s because you need to tell Unity how you want to slice the sprite sheet.
With zombie selected in the Project browser, click Sprite Editor in the Inspector to open the following window:
The Sprite Editor lets you define which portions of an image contain its individual sprites. Click the Slice button in the upper left of the window to start defining sprites, as shown below:
Unity can find your sprites automatically, but you can adjust its results. Start with the default settings shown below and click Slice.
Unity uses the transparency in the texture to identify possible sprites and displays a bounding box around each one. In this case, it found the following four sprites:
In my tests, Unity’s automatic slicing works best when images are laid out with unambiguous empty space between each item. Notice how Unity only finds the smiley face in the following image, but finds three sprites in the image after that:
Unity doesn’t find all the sprites because it cannot create mutually exclusive bounding boxes.
Unity finds all three sprites because it can create a bounding box around each one of them.
The above images point out that you should arrange the images in your sprite sheets carefully. They also point out exactly why Mike had to draw the game’s sprites.
Click on any of the sprites that Unity identified to edit the details of that sprite, including its name, position, bounds, and pivot point. The following image shows the window with the second sprite selected:
You can make changes in the window’s fields, and you can adjust the bounds and pivot point directly within the image.
Normally, after you’ve made changes, you would hit Apply or Revert in the upper right of the Sprite Editor to save or discard them, respectively.
However, while the option to tweak Unity’s findings is great, you won’t need to do that here because you aren’t going to use the sprites it found. The images in zombie.png are arranged in four equally sized rectangles, and Unity has a separate option to handle cases like this one.
Click Slice in the upper left of the Sprite Editor to open the slice settings again, but this time, set Type to Grid. The splice settings change to those shown below:
The Pixel size fields allow you to specify the size of your grid’s cells. X defines the width of each cell; Y defines the height. Unity will use those values to divide the image up equally, starting in the upper left corner of the image.
Set X to 157, and Y to 102, as shown below:
Click Slice and Unity finds the following four sprites:
You can still select individual cells in the grid and tweak their settings like you could when using Unity’s Automatic slicing option, but that’s unnecessary for these sprites.
Click Apply in the upper-right of the Sprite Editor to commit your changes. Notice how Unity updates the Project browser so that the top-level zombie texture asset now contains four child Sprites, named zombie_0, zombie_1, and so on, as shown below:
To see another way to add Sprites to your scene, you’ll create the zombie’s GameObject a bit differently. But be aware that this has nothing to do with the fact that the zombie texture is sliced into multiple Sprites – you could still make it the same way you made the enemy or background objects, by simply dragging an asset into the Hierarchy.
Create a new empty GameObject by choosing GameObject\CreateEmpty. Rename the object zombie, and set its Position to (-2, 0, 0), as shown below:
With zombie selected in the Hierarchy, add a Sprite Renderer component by clicking Add Component in the Inspector. In the menu that appears, choose Rendering and then choose Sprite Renderer, as shown below:
Click the small circle/target icon on the right of the Sprite Renderer‘s Sprite field to open the Select Sprite dialog. The icon is shown below:
The dialog that appears contains two tabs, Assets and Scene. These show you all the Sprites you have in your project and in the current scene, respectively.
Choose the Assets tab and then click on zombie_0 to assign that Sprite to the renderer, as shown below:
Inside the Scene view, you now have a zombie relaxing on the beach, with an old lady and her cat buried somewhere below it. Pleasant.
With all the necessary sprites in your scene, it’s time to fix a few problems.
Configure Your Game View
The artwork for Zombie Conga was created for an iPhone game, so it’s meant to look good in a specific resolution. To match that environment, set your Game view’s size to a fixed resolution of 1136 x 640.
Solution Inside: Not sure how? Find out here. |
SelectShow> |
You change the Game view’s aspect ratio or fixed resolution by using the drop down menu in the view’s control bar, highlighted in the image below:
Clicking the menu reveals several default options that differ based on the editor’s current player settings. If you happen to have an option for an 1136 x 640 resolution, choose it and you’re done. Otherwise, click the + button at the bottom of the menu, as shown below:
Create a new size option with a Type of Fixed Resolution, and Width and Height values of 1136 and 640, respectively, as shown below:
Click OK and then make sure your new setting is selected in the menu.
|
Your Game view now looks something like this:
Note: Your view may not look exactly like this image, because Unity resizes the Game view to maintain your chosen aspect ratio within the available space. Regardless of its scale, you should see the same amount of the scene in your view.
Obviously, that isn’t quite right. You’re seeing the results of three different problems here, and you’ll correct each one in turn:
- The scene’s camera is not set up properly, so the background doesn’t fill the view properly.
- The scene is rendering your game objects in the wrong order, so the cat and enemy are both buried in the sand.
- The image quality is not very good. This one might be hard to detect with the current camera settings, especially if you aren’t familiar with how the background image should look. But you can trust me, right?
Start by fixing the camera.
Fix Your Camera’s Projection
In 2D games, you’ll usually want the camera to use an orthographic projection rather than a perspective one. You already read about these two projections earlier in this tutorial regarding the Scene view’s 2D mode, but what you may not have realized is that Unity may default your game’s cameras to use a perspective projection.
Select Main Camera in the Hierarchy. Then, inside its Camera component, make sure the Projection is set to Orthographic.
Center the camera vertically on the scene by setting its Transform component’s Position to (0, 0, -10). Your Inspector now looks like this:
And your Game view now looks like this:
Right now it’s not much different from how it looked with a perspective projection. If sprites don’t change size based on their distance from the camera, how do you zoom in so that the background fills the screen? You could try scaling your GameObjects, but there’s a much better option – change the camera’s Size property.
The camera’s Size defines the dimensions of its viewport. It’s the number of units from the center of the view to the top of it. In other words, it’s half the height of the view. The width of the view is calculated at run time based on the view’s aspect ratio, as shown below:
In this case, you want the background image to fill the screen perfectly from top to bottom, but allow it to scroll horizontally. The background image is 640 pixels tall, so half of that would be 320 pixels. So that’s your size, right?
Not quite.
Select the top-level background in the Project browser to see its Import Settings in the Inspector.
Look at the Sprite Renderer’s Pixels to Units property. It is currently set to the default value of 100, shown below:
In Unity, units do not necessarily correspond to pixels on the screen. Instead, you usually size your objects relative to each other, possibly assuming a scale such as 1 unit = 1 meter. For Sprites, Unity uses Pixels to Units to determine their unscaled size in units.
For example, consider a Sprite imported from a 500 pixels wide image. The following table shows the different widths your GameObject would have when rendering that Sprite at different scales along the x-axis, using different values for Pixels to Units:
background.png is 640 pixels tall, and the background Sprite has a Pixel to Unit ratio of 100, so the background
object in the Hierarchy will be 6.4
units tall. However, the orthographic camera’s Size property measures half the height of the screen, so it should be half the height of the background, in units, or 3.2
.
Select Main Camera in the Hierarchy and set the Size property of the Camera component to 3.2, shown below:
Now your background fills the Game view properly, as shown below:
With the background image appearing properly, now you should be able to see problems with the image quality. The following two images point out some areas of the current view compared to what they should look like:
The beach with your current settings.
The beach with the desired settings.
The problems shown above are the result of over compressing the background texture during import. You can fix that by changing the file’s Import Settings.
Correct Your Import Settings
Select the top-level background in the Project browser to view its Import Settings again, but this time look at the Preview pane at the bottom of the Inspector.
The Preview pane displays the texture generated from the import, along with the texture’s dimensions, color information, and memory usage. As you can see in the screenshot below, the background texture is currently sized at 1024 x 320 pixels. But background.png is 2048 x 640 pixels! That means Unity shrank the original image by 50% in order to fit it in a 1024 x 1024 texture.
To fix your texture, look at the Max Size and Format settings in the tabs at the bottom of the Import Settings, shown below:
Max Size defines the maximum allowed size of the generated texture, defined as a square, and it defaults to 1024 pixels. Meanwhile, Format specifies the color depth of the image and defaults to Compressed.
You can set different values for each target platform (e.g. iOS, Web, Android), but for this app you’ll just deal with the Default tab.
In the Default tab, change Max Size to 2048 and click Apply. Your Import Settings should look like this:
Immediately you’ll notice both the Scene and Game views look nicer because the background is less compressed. The following image shows the Game view:
Notice in the Inspector‘s Preview area shown below that the background texture is now 0.6 MB, up from its earlier 160 KB:
Increasing the size of the texture increased its memory footprint by 4 times (the numbers you see are rounded a bit).
For some textures, you may want to adjust their Format value to improve their color quality, but that will increase the size even further. For example, if you try changing background‘s Format to 16 bits, you’ll see the texture grows to 2.5 MB, while changing it to Truecolor results in a texture of 3.8 MB.
However, if you look at the following two versions of the background, you’ll notice that the Compressed setting results in an image that looks pretty good compared to the Truecolor version:
Background with Compressed texture.
Background with Truecolor texture.
Because the compressed image looks good enough while saving so much memory, leave Format set to Compressed. In your own games, try different combinations of these settings and choose the one that results in the smallest texture that still produces your desired results.
Ok, the camera is set up and the background looks good. Now you need to find that old lady and her kitty cat.
Controlling Draw Order
You still can’t see the cat or the enemy sprites because the scene is drawing them behind the background sprite. You could adjust the Z positions of your game objects, so that objects closer to the camera render in front of objects that are further away from it. In fact, that’s a perfectly good way to do things and is self explanatory. However, Unity now supports a great feature for ordering sprites that you should try: Sorting Layers.
Select cat in the Hierarchy and notice its Sprite Renderer’s Sorting Layer value is set to Default, as shown below:
Click the Sorting Layer drop down box and you’ll be presented with a list of all the sorting layers defined in your project, which right now is only Default.
You’ll also see an option called Add Sorting Layer…. Click it.
This brings up the same Tags & Layers editor that you can get to from various other places in Unity, but with the Sorting Layers group open while the Tags and Layers groups are conveniently closed. See the following image:
Click + in the Sorting Layers group to create a new sorting layer and name it Cats. Do that two more times to create a sorting layer named Enemies and one named Zombie. Your editor should now look like the following screenshot:
These layers define the draw order – Layer 0, named Default, is the furthest in the back, with Layer 1, named Cats, in front of it, and so on.
Right now, each of the GameObjects you’ve added is using the Default Sorting Layer. For the background
object, that’s fine because you want it in the back anyway, but you need to change the Sorting Layer for the other sprites.
Select cat in the Hierarchy and set its Sorting Layer to Cats. You’ll immediately notice that the cat is now visible in both the Scene and Game views.
With the cat on the Cats sorting layer…
…the cat is now visible in the scene
Select enemy in the Hierarchy and set its Sorting Layer to Enemies. This way, the old ladies will walk on top of the cats. Who knows, maybe they’ll trip?
Finally, select zombie in the Hierarchy and set its Sorting Layer to Zombie to ensure your player renders on top of all the other sprites. Your Game view now looks like this:
Note: The Sprite Renderer also has a property named Order in Layer. You can use this to set a specific sort order to GameObjects within the same Sorting Layer.
You won’t use this in Zombie Conga because I haven’t seen any Z-fighting problems in my tests. It seems that Unity renders the sprites within the layer based on when they were added to the scene, so the newest sprite added to a layer is always on top. A sprite displayed on top of another sprite in one frame won’t suddenly appear behind it in the next frame, and that behavior is good enough for this game.
Using Scripts with Sprites
You’ve got some sprites strewn about the beach, but they don’t do anything. To finish up this part of the tutorial series, you’ll write two small scripts: one to animate the zombie and one to allow the player to control the zombie’s movement. You’ll add the rest of the game behavior in later installments of this series.
Note: You’ll write your scripts using C# (pronounced “see-sharp”), but it should be easy to convert this code to Unity’s variant of JavaScript if you prefer that language. Feel free to ask questions in the comments section if you need any help. (I’ve never used Boo, the other language Unity supports, so you’re on your own with that one.)
Animating Sprites
First you’ll add a simple script to animate the zombie. Select zombie in the Hierarchy and click Add Component in the Inspector. Choose New Script in the menu that appears, then name the script ZombieAnimator, choose CSharp as the Language, and click Create and Add. The following animation demonstrates these steps:
Note: You’ll replace this script-based animation with a Unity Animator in Part 2 of this tutorial series, but this example demonstrates how to access a SpriteRenderer
from your scripts.
Open ZombieAnimator.cs in MonoDevelop, the code editor that ships with Unity. There are several ways to do so, but it’s easiest to double-click ZombieAnimator wherever happens to be most convenient, either in the Inspector with zombie selected or in the Project browser, as shown in the following images:
SpriteAnimator script in Inspector.
SpriteAnimator in Project browser.
In Zombie Conga your zombie will simply walk, mindlessly, undeterred; much like you’d expect a good zombie to do. To achieve this simple animation, you’ll need a list of Sprite
s and a speed at which to cycle through them. To store that information, add the following public instance variables to ZombieAnimator
:
public Sprite[] sprites;
public float framesPerSecond; |
Note: In C# you place instance variables within the bounds of the curly braces that mark the class definition, but outside of any function. While it technically doesn’t matter, it’s usually good practice to place them at the top of the class before any function definitions.
Public variables are exposed within Unity’s editor, so you’ll be able to modify their values in the GUI without changing your code – even while running the scene! You’ll see in a moment how easy that makes it to tweak in-game values and get them just right.
You’re going to render the animation by assigning different Sprite
s to your GameObject
‘s SpriteRenderer
component. Rather than getting the component in every call to Update
, you’ll cache it in an instance variable when the script first starts running.
Add the following private variable to ZombieAnimator
:
private SpriteRenderer spriteRenderer; |
Private variables are not exposed within Unity’s editor. In this case, you’ll initialize the variable in code by adding the following line to Start
:
spriteRenderer = renderer as SpriteRenderer; |
Your script subclasses MonoBehaviour
, which gives it access to a variable named renderer
. For GameObject
s that display Sprite
s, renderer
will be a SpriteRenderer
object. Therefore, you cast renderer
to type SpriteRenderer
before storing it.
Note: While it’s always better to test your game’s performance to see what optimizations are necessary, it is often a good idea to store references to objects that you’re script will access frequently. Common examples of this are a GameObject
‘s Transform
, the scene’s main Camera
, or any objects you might have to otherwise use a command like GameObject.Find
to access. To keep things easier to understand, this is the only time you’ll cache an object in this tutorial, even in cases where it would make sense.
To finish this short script, add the following few lines to Update
:
int index = (int)(Time.timeSinceLevelLoad * framesPerSecond);
index = index % sprites.Length;
spriteRenderer.sprite = sprites[ index ]; |
This takes the number of seconds since the level loaded (consult the Time
class docs for more info) and multiplies it by the number of frames that should render per second. If the frames were stored in an infinitely long array, that would give you the index into the array for the current frame.
However, since you know your array won’t be infinite, you need to loop back to the start when you reach its end. You do that by performing a modulus (%) operation, which performs an integer division between two numbers and returns the remainder.
In other words, you’re getting a whole number between 0 and one less than the size of the array, which will be a valid index into the sprites
array (assuming the array doesn’t have a length of zero, of course).
You’re done with MonoDevelop for now, so save your script (File\Save) and switch back to Unity.
Note: Unity compiles your scripts automatically, so if any errors appear in the Console, correct them before moving on.
Select zombie in the Hierarchy and notice the Zombie Animator (Script) component now displays fields for your two public variables. Thanks, Unity!
Right now, the Sprites array field (Unity capitalizes your variable names, and adds spaces between words) has no items. Your Sprite assets are named zombie_0 through zombie_3. These images are meant to be displayed in a specific order, so you’ll want the Sprites array to contain the necessary Sprites for a single cycle of animation, and then your script will loop through those Sprites forever.
In order to define that single animation cycle, you’ll need to add the following six elements to the Sprites array: zombie_0, zombie_1, zombie_2, zombie_3, zombie_2, zombie_1. There are several different ways you can do this, but here is my favorite:
With zombie still selected in the Hierarchy, click the lock button in the upper right of the Inspector so that it displays as locked, as shown below:
This will keep the current Inspector information displayed even if you select another object in the project, which is useful in cases like this.
With the zombie texture expanded in the Project browser, left click zombie_0 to select it, then shift+left click zombie_3. That selects all four zombie Sprites.
Now drag your selected objects over to the Inspector and hover anywhere over the Sprites row in the Zombie Animator (Script) component. You should see a green plus icon appear under your cursor when you are in a good spot, as shown below:
Release the mouse button and Unity automatically increases the size of the Sprites
array and adds your selected items to it.
Note: It doesn’t matter whether or not you have the Sprites item expanded when you perform the previous steps. The screenshot above shows it expanded only because Unity automatically expands any closed item if you hover over it for long enough during a drag action. If you were trying to take a timed screenshot, for example. :]
Your Zombie Animator (Script) component now looks like this:
Now select only zombie_2 in the Project browser and drag it over in the same way. Unity increases the size of the Sprites array again and appends your new element.
Do this one more time for zombie_1, and your Sprites array should now contain all six elements in the correct order, like this:
Before you move on, click the lock button in the upper right of the Inspector again so that it displays as unlocked, as shown below:
If you had forgotten to do that, you’d get annoyed later when Unity started ignoring all your selections. ;]
Finally, set Frames Per Second to 10, as shown below:
Run the scene and marvel at your shuffling zombie!
Note: You can adjust Frames Per Second in the Inspector while playing the scene to find a speed you like, but Unity resets your values when you stop running. Therefore, be sure to remember what value you liked so you can change it for real after you’ve stopped playing.
Now that you’ve managed to animate (or is it reanimate?) the zombie, he’ll be looking to party. The next section shows you how to create a simple controller script so you can point him point him in the right direction.
Controlling Sprites
Select zombie in the Hierarchy and add a new C# script component named ZombieController, just like how you added ZombieAnimator.
Do you like an old school, plodding zombie, or one of those new-fangled runners? To get it just right, you’ll want to tweak your zombie’s movement speed while you test your scene, which calls for a public variable in your script.
Open ZombieController.cs in MonoDevelop and add the following variable to it:
moveSpeed
will store the number of units – not pixels – that the zombie should move per second. Because you’re Sprite sizes are 1 unit for every 100 pixels, you’ll probably want to keep this value fairly low.
As you can see in the following animations, you’ll make the zombie in Zombie Conga move in a straight line toward, and then past, the location of the latest mouse click (or the latest mouse location in cases where the user holds down the mouse button while dragging):
Zombie walking toward and then past where you click. “Over here, zombie! No, over here!”
Zombie following the cursor as it is dragged. “Who’s the good little zombie who wants to bite the yummy cursor? You are! Yes, you are.”
You most likely won’t have an input event every frame, so you’ll need to store the direction in which the zombie is headed whenever the destination changes. To accomplish this, you’ll calculate a normalized vector (a vector of length 1) that points toward the input location from the zombie.
Add the following variable to ZombieController:
private Vector3 moveDirection; |
You’re making a 2D game, but Unity is still working with a 3D coordinate system. As such, Transform
s store their positions as Vector3
objects. While you could use a Vector2
here because you know the zombie will never change its position on the z-axis, you’re storing the zombie’s direction of movement in a Vector3
to avoid having to cast between the two types later in the script. It’s really a matter of personal preference.
Add the following code to Update
in order to update moveDirection
whenever the game receives an input event:
// 1
Vector3 currentPosition = transform.position;
// 2
if( Input.GetButton("Fire1") ) {
// 3
Vector3 moveToward = Camera.main.ScreenToWorldPoint( Input.mousePosition );
// 4
moveDirection = moveToward - currentPosition;
moveDirection.z = 0;
moveDirection.Normalize();
} |
Here’s what you’re doing with the preceding code:
- Since you’ll be using the zombie’s current position a few times in this method, you copy the position to a local variable.
- Then you check to ensure the
Fire1
button is currently pressed, because you don’t want to calculate a new direction for the zombie otherwise. See the upcoming note for more information about Input
and Fire1
.
- Using the scene’s main (and in this case, its only)
Camera
, you convert the current mouse position to a world coordinate. With an orthographic projection, the z
value in the position passed to ScreenToWorldPoint
has no effect on the resulting x
and y
values, so here it’s safe to pass the mouse position directly.
- You calculate the direction to move by subtracting the zombie’s current position from the target location. Because you don’t want the zombie changing its position along the z-axis, you set
moveDirection
‘s z
value to 0
, meaning, “Move zero units along the z-axis.” Calling Normalize
ensures moveDirection
has a length of 1
(also known as “unit length”). Unit length vectors are convenient because you can multiply them by a scalar value (like moveSpeed
) to make a vector pointing in the same direction, but a certain length (like a moveSpeed
-long vector pointing from the zombie in the direction toward the mouse cursor). You’ll use this next.
Note: You use methods on Input
to access input data in a generic way. A project defines various input names by default, referred to as axes, such as Horizontal
, Vertical
, and Jump
. Horizontal
monitors the joystick’s position along its x-axis, as well as the state of the left and right arrow buttons on the keyboard. If you needed to know when your game received input in a horizontal direction, you could simply check for the value of the Horizontal
axis without concerning yourself with the specifics of where the input originated.
Fire1
is one of the virtual button axes defined by default. It registers events for button 0 on a joystick or mouse, and the left control key on the keyboard. Input.GetButton
returns true
while the specified virtual button is down, so the code you wrote will update moveDirection
every frame that the mouse button is down (not just on the frame when it was initially pressed). Yes, this also means you’ll be able to change the zombie’s direction by pressing your keyboard’s left control key, as long as you remember that you still need the mouse to steer!
If you want to see or customize your project’s input options, go to Edit\Project Settings\Input to bring up the InputManager
‘s settings in the Inspector.
Now start the zombie walking by adding the following code to the end of Update
:
Vector3 target = moveDirection * moveSpeed + currentPosition;
transform.position = Vector3.Lerp( currentPosition, target, Time.deltaTime ); |
The first line calculates a target location that is moveSpeed
units away from the zombie’s current position. That is, it finds the point the zombie would reach if it traveled from its current position in the direction pointed to by moveDirection
for a duration of one second.
The second line uses Vector3.Lerp
to determine the zombie’s new location along the path between its current and target locations. Lerp
is a convenient method that interpolates between two values based on a third value. Lerp
clamps the third value between zero and one and interprets it as a fraction along the path between the two points. That means a value of 0
would return currentPosition
, a value of 1
would return target
, and a value of 0.5
would return the midpoint between them.
In your case, you use Time.deltaTime
as the third value because it is a fraction of one second and you know it will most likely be less than one, giving you some point along the path that is not quite at the end point. Your game would need to be running horrendously for Time.deltaTime
to be anywhere near 1
, so you should get nice, smooth motion.
Save your script in MonoDevelop and switch back to Unity.
Run the scene and click somewhere to get the zombie walking. Or not. You never set ZombieController
‘s moveSpeed
, so it’s “moving” at a rate of zero!
While the scene is still running, select zombie in the Hierarchy and find the Zombie Controller (Script) component in the Inspector. Change Move Speed to 2, click on the beach, and your zombie should be on his way!
Adjust Move Speed in the Inspector until you’re happy with it. Depending on the speed you choose, you may want to adjust the Frames Per Second on the Zombie Animator (Script) component, too. Otherwise his animation may not match his motion.
When you find values you like, remember them and stop the scene. Then set the values in the Inspector again so they’ll be correct the next time you run.
While you were busy tweaking his ambulatory system, you probably noticed a few things wrong with your zombie.
- When you start playing, his legs are moving but he’s not going anywhere.
- He happily walks right off the screen.
- He doesn’t look where he’s going.
You’ll fix the zombie so he no longer wanders off screen in a later installment of this tutorial series, so ignore that issue for now. Besides, if he leaves the screen, simply click on the beach again and he’ll come strolling back eventually. Zombies are good like that.
Go back to ZombieController.cs in MonoDevelop.
The script uses moveDirection
when moving the zombie, but you only assign it a value when you get an input event. To spur the zombie forward when the scene starts, you simply need to initialize moveDirection
to point him in the right direction.
Add the following line inside Start
:
moveDirection = Vector3.right; |
This points in the direction of the positive x-axis. In other words, it points toward the right side of the screen.
Save ZombieController.cs and then play your scene again in Unity. Now your zombie is off and running! Next you’ll make sure he faces the right direction so he doesn’t trip.
Back inside ZombieController.cs in MonoDevelop, add a public variable to ZombieController
so you can tweak the rate at which the zombie turns.
You’ll use turnSpeed
in your calculations to control how quickly the zombie reorients himself to a new direction.
Unity uses quaternions internally to represent rotations. If you’re curious about the details of the math, check out this info. Then, after you’ve cleaned up the mess from your head exploding, take solace in the fact that you really don’t need to know anything about quaternions when working with 2D.
That’s because the Quaternion.Euler
method lets you create a Quaternion
object from an Euler angle. Euler angles are the ones most people are accustomed to, consisting of individual x, y and z rotations. While they aren’t ideal for 3D work because of problems like gimbal lock, Euler angles are just fine for 2D games where you probably only want to rotate around the z-axis.
Add the following code to the end of Update
:
float targetAngle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg;
transform.rotation =
Quaternion.Slerp( transform.rotation,
Quaternion.Euler( 0, 0, targetAngle ),
turnSpeed * Time.deltaTime ); |
First you use Mathf.Atan2
to find the angle between the x-axis and moveDirection
. Mathf.Atan2
returns the angle in radians, so you convert it to degrees by multiplying by Mathf.Rad2Deg
.
You use Quaternion.Slerp
to turn towards the target angle you calculated. Quaternion.Slerp
performs a spherical linear interpolation between the two angles you specify. It’s similar to what you did earlier with Vector3.Lerp
, except you’re calculating a new rotation instead of a new position.
Earlier, when you called Vector3.Lerp
, you used moveSpeed
to adjust the distance the zombie traveled. In this case, you’re using turnSpeed
to do something similar; the larger its value, the faster the zombie will arrive at the target angle.
Note: Some of you might be thinking to yourselves, “Hey, that math’s no good! I want the zombie to take the shortest path to the new angle, and just finding the arctangent doesn’t guarantee that! You, sir, are a charlatan!”
To you I would suggest some calming yoga and maybe a bit more fiber in your diet. I would then point out that Quarternion.Slerp
is awesome and always interpolates through the shortest route between two angles.
That’s it for ZombieController.cs. Save it and switch back to Unity.
Select zombie in the Hierarchy. Set Turn Speed to 5, as shown below:
Run the game and click around on the beach. No matter how hard you try, zombies never get dizzy!
That’s all the work you’ll do for Zombie Conga in this tutorial. Save your scene by going to File\Save Scene as…. Name the scene CongaScene and click Save.
The next section describes a feature useful for 2D games that is only available in the paid version of Unity. It isn’t necessary for Zombie Conga, but it’s something you’ll probably want to know about for more complex projects — Sprite Packing.
Sprite Packing – For Professionals Only (Sort of)
Note: This section describes a feature that is only available in Unity Pro. You can certainly still use texture atlases in the free version of Unity, but you’ll need to use separate tools to create and support them. You could also use sprite slicing like you did with zombie.png to achieve the same runtime benefit you’d get from texture atlases, because all of the Sprites you create from a single art asset will use the same texture by default, but it would require you to organize and access your assets in ways that may not be intuitive and therefore will not be covered here.
Play your scene and check out its rendering statistics by clicking the Stats button in the Game view’s control bar, as shown below:
Notice that the game is currently making four draw calls and saving none by batching. Not cool!
Of course, this makes sense, because it’s rendering exactly four Sprites in the scene, and each one uses a unique texture. While a real game with only four draw calls would probably be fine, that number will likely grow as the number of objects and effects in your scene increases. Too many draw calls hurts performance, so you’ll probably need to be a bit more careful about how you organize your Sprite textures. Fortunately, there’s a basic technique you can use to help: pack your sprite images into texture atlases.
Texture atlases — large textures made up of several smaller textures, used to optimize rendering calls to the GPU — aren’t anything new, but before now you had to create them yourself. Now Unity can build them for you!
Note: As of Unity 4.3.2, Unity’s ability to create texture atlases, which they refer to as Sprite Packing, is still in Developer Preview mode. But it’s a great tool, so why not learn about it right now?
In order to pack your Sprites into texture atlases, you first need to modify their Import Settings.
Select the top level cat in the Project browser to open the Import Settings for cat.png. Notice the property named Packing Tag. Packing Tag defines the name of the texture atlas to which you want this asset’s sprites added, and it can be any string you’d like.
Set Packing Tag to toons by typing in the text field, as shown below:
Don’t forget to hit Apply whenever you make changes to any asset’s Import Settings, or the changes will not take effect. Don’t worry: you’ll get a warning dialog if you select another object in your project while you have uncommitted changes.
Now repeat that process to set the Packing Tag to toons for the enemy and zombie assets as well.
Open the Sprite Packer window by choosing Window\Sprite Packer. The menu item may read Window\Sprite Packer (Developer Preview) if it’s still in beta, but you get the idea.
What’s this, an error?
As you can see, Sprite packing is disabled by default. To enable it, go to Edit\Project Settings\Editor and set the Mode for Sprite Packer to Always Enabled, as shown below:
Choosing Always Enabled packs your Sprites when you play the game from within Unity as well as when you export a build of your project; you also have the option of only packing your Sprites for builds.
Choose Window\Sprite Packer again, and this time you see an empty Sprite Packer window, like this:
It currently warns you that Sprite packing is a developer preview feature.
Note: If Sprite packing is no longer in beta, than you won’t see the yellow message shown in the above image.
Press Pack in the upper left and you’ll see a new texture with your sprites arranged within it, as shown below:
Play your game again and check the stats in the Game view. As you can see in the following screenshot, Unity is now using only two draw calls, saving two by batching. It all adds up!
This makes sense, because even though you still have four Sprites in the scene, three of them are now sharing the same texture! And to get that performance savings, which would be more evident with more Sprites on screen simultaneously, you barely had to do anything at all! Sweet.
Sprite Packer — Options and Issues
There are a few other things you may run into when packing Sprites. The top of the Sprite Packer window contains a control bar, shown below:
It shows:
- The current atlas you’re viewing. This drop down contains each of the Packing Tags you’ve used in your project, allowing you to choose which one to view.
- The current page in the atlas. The atlas you created only contains one page, but if it had more, this dropdown would let you choose which one to view. More on this in a bit.
- The packing policy used to arrange the sprites in the atlas. There is only one policy available at this time – DefaultPackerPolicy – but you can create your own by implementing the
IPackerPolicy
interface. This is an advanced feature beyond the scope of this tutorial.
The notion of a current atlas makes sense, but what you might not expect is that Unity sometimes splits up the atlas you intended to create into multiple atlases, appending group numbers to their names. This occurs when the Import Settings for the component textures don’t match.
For example, the following image shows what the toons texture atlas would look like if zombie.png had been imported with a color Format of 16 bits while cat.png and enemy.png used a Format of Compressed. Notice how it results in two atlases, named toons (Group 1) and toons (Group 2):
Even though all three Sprites have the same Packing Tag, Unity created multiple atlases out of them. To ensure you’re getting the best performance from your atlases, be sure you don’t use incompatible import settings for sprites that you intend to store in the same atlas.
As for the current page, Unity creates multiple pages for an atlas if there are too many to fit within its texture size limit. If you end up with an atlas with multiple pages, Unity has basically created different texture atlases. You generally want to arrange your textures in such a way to ensure the ones most likely to be used together end up on the same page to reduce draw calls.
Note: I must admit, I haven’t figured out how Unity decides the size limit for a texture atlas, or if you can modify it. If anyone knows, please post in the comment section and I’ll update this text. Thanks!
Where To Go From Here?
I hope you enjoyed this Unity 4.3 2D Tutorial. If you didn’t enjoy it, I hope you at least learned something. If you learned nothing, maybe you should get in touch with us about writing some tutorials of your own? ;]
You can download the completed project here.
At this point although there’s still a lot left to do with Zombie Conga, you’ve actually already seen enough to go write a lot of other 2D games. You’ll finish Zombie Conga in future tutorials, exploring how to control animations with Unity Animators, introducing yourself to Unity’s 2D physics engine, and getting some more scripting practice along the way.
In the meantime, there are some great resources for you to explore on Unity’s website, including:
As always, please consider discussing this post in the Comments section.
Unity 4.3 2D Tutorial: Getting Started is a post from: Ray Wenderlich
The post Unity 4.3 2D Tutorial: Getting Started appeared first on Ray Wenderlich.