Build a 2D Portal Puzzle Game With Unity: Getting Started

Build a 2D Portal Puzzle Game With Unity: Getting Started

Tutorial Details
  • Difficulty: Intermediate
  • Platform: Unity3D
  • Language: C#
  • Software Used: Sprite Manager 2, Vectrosity
  • Estimated Completion Time: Two hours
This entry is part 1 of 4 in the series Build a 2D Portal Puzzle Game With Unity

In this tutorial (part free, part Premium), you’ll learn how to create a 2D puzzle game in Unity which uses a Portal-style game mechanic to teleport objects across the level. In this first part, we’ll lay out the main concepts of the game, put some graphics together, and get the basic (portal-less) physics working.


Final Result Preview

Let’s take a look at the final result we will be working towards, across the whole of this multi-part tutorial:

Hit the number keys 1-8 to try different levels. The aim is to get the little yellow Roly character to the designated end point, using portal mechanics. The demo shows off a few of the different mechanics we’ll introduce.

It’s going to take us a while to get to that point, though! In this first part of the tutorial, we’ll make a good start.

Important Note: This project does make use of a few commercial Unity plugins: Sprite Manager 2 and Vectrosity. Also, while the first two parts of the tutorial are free to read, the third and fourth will be available exclusively to our Premium members.


Step 1: Project Overview

When creating your game you never want to jump right away into the code, first, you need an idea.

Some people like to simply start doodling whatever they have on mind, some take a couple of random words and make a concept of the game based on thoughts that gravitate around those. Sometimes you don’t really want to make something new but rather do something that’s already done, but then again, you want to do it your way.

Once you’ve got a pretty clear image of what you want to do, you need to be able to present your idea in a way that other people can see what the heck is this all about. Usually it takes a form of a concept art, a mock up picture, or a quick prototype. Even if you think the idea is great at the beginning, you should look objectively at those concepts and with an eye of a critic review your idea.

If it doesn’t seem very appealing after the review, it surely won’t be later on, it’s better to simply try again with something else. If you still think it is the best game ever, you should show it to your friends, they surely won’t mind a few minutes lost, and their opinions are invaluable because they are given from other perspective than your own.

The game we’re going to make in this tutorial is a 2D puzzle game that uses portals as its core mechanic. I’m sure you checked out the demo above so I don’t really have to explain much. I should probably show here some of my concepts for the game, but unfortunately many of them either weren’t saved or simply weren’t in digital form to begin with, I can present only a couple of them that I found lying here and there.

The first level of the game.
The sixth level of the game.
The concept of the main character.

You already know that we’re going to use Unity3D, but that’s not a very obvious choice especially if you want to make a 2D game. The main reason why I chose it is because it’s multiplatform. It can deploy a game to web browser via custom plugin, windows, mac, android, iOS, wii, and soon to xbox360 and ps3. There’s also a flash exporter in works. You may be sure that if you develop the game in unity then there are no worries about porting the game to other platforms.

To make a 2D game we need to be able to deal with sprites, unfortunately unity doesn’t provide any built-in means to do that yet, but there are various plugins that are made specifically for this task by 3rd party developers. For this game I chose Sprite Manager 2 to serve this purpose. Aside from that, I’m also using Vectrosity to render lines and curves. I want to note that you don’t really need to use those plugins. If you have your own solution for rendering the sprites, that’s fine, but you must be prepared to fiddle with the code so you do replace what’s done with Sprite Manager 2 with the adequate code using your plugin. Of course the same applies to Vectrosity.


Step 2: Create a New Project

The first thing we need to do is to create a new project in Unity from the project wizard.

Create new project wizard.

Name it however you like and press Create, then the project will be created and opened. Now we should import all the the plugins so everything is on its place. The Sprite Manager 2 is in .unitypackage, so all you have to do is to double click this file and then press all so all the files will be imported and then import inside the unity editor.

Import assets wizard.

For Vectrosity we need to copy the folders into our Assets folder ourselves. Navigate to both, the Assets directory in your porject folder and VectrosityScripts.zip in your Vectrosity folder. Now extract the files from VectrosityScripts.zip. In the extracted folder there should be two others: Editor and Standard Assets. Simply copy those to the Assets directory. Here’s how your project’s directories should look like for now.

Project window shows our project tree with all the assets and folders.

Step 3 Setup the Scene

First thing we should do is to set the resolution of our project. Let’s say we want our game to be 480 pixels wide and 320 pixels high. In that case we need to go to the Edit->Project Settings->Player.

That's where you can find Player Settings.

Now in the inspector window you can see player settings. We need to change the resolution in the web player, because that’s where we’ll be deploying our game.

I bet you know what device this resolution fits.

As you can see there are various settings here, what interests us is the Default Screen Width and the Default Screen Height. Change them accordingly to our needs. It’s reasonable to leave Run In Background unchecked, because that will pause the game when the browser is minimazed. The game still will be running if the user marely changes tabs though.

Now go to File->Build Settings.

Ctrl+Shift+B is the shortcut for Build Settings.
Unity Build Settings.

And switch the platform to Web Player. Also remember to check Streaming, this basically allows level streaming so if someone wants to play your game, they don’t have to wait for it to load completely. You can read a bit more on this topic at the Unity Manual page on streaming.

You need to ise the new resolution.

Now go ahead and change the resolution in Game window from default Free Aspect to Web (480×320). Also resize this window so you can see the dark grey border around blue area, because if the window is too small to fit our web resolution then it will be resized to do so, even if we have our Web (480×320) selected. It will still keep the aspect ratio though.

Remember that unity allows you to drag and drop every window wherever you want, so you should setup your environment the way you are comfortable with it. I’m using a bit modified 2 by 3 view, I left everything as it was except draggin the Project window below the Hierarchy.

My unity editor layout.

Step 4 Setup the Camera

If you are familiar with 3D editors of any kind, you should know that the concept of pixel kind of disappears and what is introduced are standardized physics units. When it comes to length, one unit doesn’t have to be equal to one pixel, and we don’t really care about it in a 3D world, but when we are working in 2D then it’s very useful to have one pixel to one unit binding. We’ll be able to do that by tweaking the camera in our scene. First select the Main Camera object from the Hierarchy window.

All the objects in the scene can be found in hierarchy window.

Once you selected it, the inspector should show our camera settings. The first setting we should change is Projection. Since our game is not 3D, we don’t really need a perspective, we rather want a flat orthrographic view.

Setting properly the camera is important for 2D game.

Once you selected Orthographic projection, there should appear a new property called Size right below it. It basically indicates how long in world units is half of screen height. Since we want to have one to one pixel to world unit correlation, we need to set it to half of our resolution height. The width is worked out behind the scenes using the aspect ratio. Go ahead and change the default value of 100 to 160, which is our resolution height (320) divided by 2.


Step 5 Import the First Asset

Before we create our first sprite, we need to import the assets into unity. It’s as simple as to drag and drop the files to the project’s folder. Here is the texture we want to import for our sprite.

Our first asset. We'll use it for quite a while.

Remember that if you are using photoshop files in unity then you have to deal with white borders around the sprite. To solve this problem you can use a method presented in this Blurst Technology blog post or this Unity Manual page.

To make everything clean we should create a folder for our textures. To create a folder you can simply create one in the explorer or do so directly from editor by right-clicking in the project window and then select Create->Folder.

It's better to keep everything in folders.

Rename the folder to Textures and drag our ball texture to it.

If you didn’t do this yet, we should save the scene. But before that, let’s create the folder the same way we just did. This time let’s call it Scenes. Then go to File->Save Scene, navigate to our Scenes folder and save it there. Call the saved scene demo.

Save the scene often!

Remember to save often, it should become a habit.


Step 6 Create a Sprite

Let’s create our first sprite. First thing you need to do is to create a game object inside our editor. To create an object simply go to GameObject and then click on Create Empty. This will create an empty object in the scene, it will also be visible in the hierarchy window. It’s named GameObject by default.

The base of every object.

Let’s change the name of our object to Ball, because the first sprite will be just that, a ball. You can change the name of an object by double-clicking on it in the hierarchy window or changing it in the inspector window, it’s the first edit box on the very top of it. Now change the ball’s position to the very center of the world – change all its position components to zero. You can edit X, Y and Z position values in the Transform component attached to our sprite. After you do that, the ball’s inspector should look like this.

Transform is a component that every GameObject possess.

Remember that no matter how far from the camera the sprite is, it will be the same size due to camera being in orthographic mode. Nonetheless, since our camera’s z position is -10, we need to remember that our sprites need to be less than that to allow the camera capturing them. Either that, or we could change camera’s own Z position.

Now it’s time to make use of Sprite Manager 2. Before we do that though, I would like to recommend watching the introductory videos from the developer of Sprite Manager 2. You can find them at Sprite Manager 2 page. I will guide you through the process of creating the sprite anyway, but diving into the greater details won’t hurt you. :)

In our project window expand Plugins->Sprite Scripts, so you can see a bunch of C# scripts there. To create a sprite, we need to use either PackedSprite or Sprite script. The basic difference between those two is that the PackedSprite creates and menages the texture atlas on which sit all the frames of the sprite, while Sprite requires a premade texture atlas and a couple of additional settings for it to work. To attach the script to our ball, you can either drag and drop it on the object in the hierarchy window you want it attached to, or if you have the object selected, then drag and drop it in the inspector window or go to Component->Script->PackedSprite. Now below our Transform component, there are a couple of others with PackedSprite on top.

We're making the first use of Sprite Manager 2 here.

As you can see, there is a lot of things to set here. We’ll take care of it later. For now, go to the Window->Sprite Timeline.

Here you can also edit and add animations.

A new window should pop up. If you don’t have our ball selected, you should do that now in order to be able to work on it. You can drag the window next to our Game window so it’ll work as a tab next to it. Here’s how the window should look like.

For now we don't have anything to display.

As you can see, there isn’t much going on here. There is a place for Static Texture, which is basically a texture that will be displayed if the sprite won’t be animating. Let’s drag and drop our ball texture here.

Now there's a texture that we'll use for sprite frame.

Now let’s go back to our inspector. In order to see the sprite, it has to have positive width and height. We want our sprite to display in its original size, to achieve that, we need to check Pixel Perfect checkbox. By doing that Auto Resize is checked automatically, it makes sure that the sprite proportions are the same as the original texture.

Remember to tick it if you want to render your sprite most accurately.

Now we need to assign the material for our sprite. There’s a default material for sprites situated at Plugins/AnBSoft Common/Standard Material, it’s called Sprite Material. Let’s drag it from the project window on our Ball object, that’s all we need to do to assign this material to it. Now in the inspector there will be a material assigned to the sprite’s Mesh Renderer.

We need to assign a material before we can build an atlas.

Now it’s time to build an atlas for our sprite. To do that, we need to go to Tools->AB Software->Build Atlases.

Alt+A if you don't like wasting those precious seconds.

A small wizard will pop up. All settings are set to suit our needs by default, so all we need to do is to click Create.

Build Atlases Settings are optimal by default.

All that’s left is to press play and see how our sprite looks.

Sprite rendered not accurately.

As you can see, it’s far from perfect. We’ll take care of it in the next step.


Step 7: Align the Sprite

The issue at hand is explained at this MSDN page. We need to offset our sprite by half of the pixel to make renderer display it accurately. Let’s see if that works. let’s change our sprite’s position to (0.5, 0.5, 0.0).

Offsetting the sprite.
Sprite rendered accurately this time.

As you can see, it worked well, the image displayed is now very sharp. Generally this kind of quality drop doesn’t matter for objects that move a lot. It also doesn’t matter for high resolution sprites, because then it’s unnoticable. Since our ball will be in constant movement, it won’t matter whether we align it every frame or not because the changes won’t be noticable as well. For static objects, we can align them right away in the editor and they will stay in a good shape. The problem could arise if we were dealing with low resolution sprites that are not in a constant movement. In that case we would need to align it every frame, which isn’t a huge problem at all. In fact, we’ll create a script which does that right now.


Step 8: Create a Script

The first thing we need to do is to create a script file. We can do that in our project window, but before we do that, let’s create a new folder and name it Game Scripts. After it’s created, you can right-click on it and then go to Create->C Sharp Script. You can rename the script by pressing F2 or clicking on its name. Name it Align.

Align script in our Game Scripts folder.

Everyone has their favourite script editor of choice, I’m using the one that comes with unity – MonoDevelop. It integrates with unity really well, and it’s the only one that allows for script debugging. If you use it too, I recommend to go with these preferences, which you can find under Edit->Preferences….

Scripting tools preferences.

Now double-click on the Align script, so it will open in our script editor.

using UnityEngine;
using System.Collections;

public class NewBehaviourScript : MonoBehaviour {

	// Use this for initialization
	void Start () {

	}

	// Update is called once per frame
	void Update () {

	}
}

There’s already some code in here. When using the C# with unity, you always need those first two lines.

using UnityEngine;
using System.Collections;

The next thing is a class declaration. In our case, we want to name it Align, so let’s change NewBehaviourScript to that.

public class Align : MonoBehaviour {

Note that everything with exception of two first lines goes inside the class. By default we’ve got two function declared here, void Update(), which is called every frame and void Start(), which is called once and that’s before the first Update() call. We don’t need the latter, so let’s remove it from the script.

using UnityEngine;
using System.Collections;

public class Align : MonoBehaviour
{

	// Update is called once per frame
	void Update() {

	}
}

Step 9: Script the Alignment

Alright, let’s think of how should our aligning work. Since we are going to align the position of sprite, we will need another vector to hold its true position, the way it would be unaligned. Let’s call it realPos. We need a Vector3 for holding a position, so that’s the type the realPos is going to be. You can declare class variable like this.

public class Align : MonoBehaviour {

	Vector3 realPos;

	// Update is called once per frame
	void Update() {

	}
}

The next thing which we need to do is to think how will we be able to return from the aligned position to the original one. That’s pretty simple. We need to know how much we aligned it in the first place, and then unalign it. So we know that we will need to know how much alignment did take place, we need to align in both, x and y axes. A 2D vector would suffice, but since we’re working with positions in 3D space here, it would be better to keep using 3D vectors. Let’s declare another Vector3 and name it offset.

public class Align : MonoBehaviour {

	Vector3 realPos;
	Vector3 offset;

	// Update is called once per frame
	void Update() {

	}
}

Alright, it seems like everything is in place. Let’s write down our alignment code. Firstly, let’s save our original position to realPos variable.

public class Align : MonoBehaviour {

	Vector3 realPos;
	Vector3 offset;

	// Update is called once per frame
	void Update() {
		realPos = transform.position;
	}
}

To get to our current position, we need to access the Transform component. In unity, you can access built-in components by simply using a reference which is always called by the name of the component. The only difference is that it doesn’t start with a capital letter. So if we want to access Transform component attached to our sprite, we can simply use transform reference for it. If we’ve got that, then all that’s left to do is to access the position vector.

We need to calculate the offset. The offset is a difference between our original position and the aligned one. Well, we know what our position is, so we only need to calculate the aligned one. To do that we simply round the original position to integers, and then add 0.5 to that so it’s aligned properly. Now to do that in code.

new Vector3(Mathf.Floor(realPos.x) + 0.5f, Mathf.Floor(realPos.y) + 0.5f, realPos.z);

So we created new Vector3, to package our aligned position into a 3D vector. Notice that we used a math library here, you can access it whenever you need to do any math. In this case we used Mathf.Floor(), which simply returns the nearest integer lower or equal to the float value we submitted. Now we only need to substract it from the realPos, and that’s our offset!

public class Align : MonoBehaviour {

	Vector3 realPos;
	Vector3 offset;

	// Update is called once per frame
	void Update() {
		realPos = transform.position;

		offset = realPos - new Vector3(Mathf.Floor(realPos.x) + 0.5f, Mathf.Floor(realPos.y) + 0.5f, realPos.z);
	}
}

Now it’s a simple matter to align our position. It’s our offset substracted from realPos.

public class Align : MonoBehaviour {

	Vector3 realPos;
	Vector3 offset;

	// Update is called once per frame
	void Update() {
		realPos = transform.position;

		offset = realPos - new Vector3(Mathf.Floor(realPos.x) + 0.5f, Mathf.Floor(realPos.y) + 0.5f, realPos.z);

		transform.position = realPos - offset;
	}
}

Remember that since we don’t want to alter the original position in any way, we need to unalign it before we start our calculations. To do that, simply add the previously substracted offset to the realPos, which is our original position.

public class Align : MonoBehaviour {

	Vector3 realPos;
	Vector3 offset;

	// Update is called once per frame
	void Update() {
		realPos = transform.position + offset;

		offset = realPos - new Vector3(Mathf.Floor(realPos.x) + 0.5f, Mathf.Floor(realPos.y) + 0.5f, realPos.z);

		transform.position = realPos - offset;
	}
}

Finnaly, we shoud remove the constant, 0.5f and replace it with a variable, so we can align our object’s however we want, be it integers or not. Let’s declare a float and call it alignment.

	Vector3 realPos;
	Vector3 offset;
	public float alignment = 0.5f;

Making the variable public will allow us to change it in object’s inspector. Notice that this time we created a default value for this variable, it’s 0.5f, so if we attach our script to an object, it will have alignment equal to 0.5f by default. Let’s swap our constants with this variable.

offset = realPos - new Vector3(Mathf.Floor(realPos.x) + alignment, Mathf.Floor(realPos.y) + alignment, realPos.z);

That’s it. To check out the script simply drag and drop it from the project window on our Ball in hierarchy window. After doing so, it should be visible in the inspector window, near the bottom.

Script is also a component.

To test the script out, you can start the game and drag the sprite around in the scene window. You’ll see in the inspector that the position is aligned properly, and in the game window the sprite never appears to be blurry. As I stated before, this treatment wasn’t really necessary to our ball, because it will be moving constantly, but I hope that writing such a simple script served as a good introduction into scripting in unity. To remove the component, simply right-click on it in the inspector window and then select Remove Component.


Step 10: Add Components to the Sprite

It’s time to add some physics into the game. It’s a very simple process. The first thing we want to do is to add a Collider component. With our Ball selected go to Components->Physics->Sphere Collider.

Radius is set automatically.

As you should see in the editor, a green colored circle appears which represents the collider. Its size automatically fits the sprite’s mesh, so we don’t have to worry about that.

Gizmo around the sprite.

There is also a checkbox for Is Trigger, checking that would make our sprite a trigger, and we don’t really want that. Triggers receive a collision callback, but they are permeable. We want the ball to be a proper physical object. Now let’s attach Rigidbody. This component brings our object to life in our game world. Go to Components->Physics->Rigidbody to attach it.

Rigidbody settings.

We need to change a few settings here. If a Rigidbody Is Kinematic, then it’s pretty much static, no physical force can move it, the only way to change its position is to do so directly through Transform. That’s not the kind of object we want our ball to be, so we better leave that option unchecked. We want to check collisions of our ball to the Continuous Dynamic mode, by doing this the physics solver will always be detecting collisions against both, static and dynamic objects. The next thing are constraints. We don’t really want our sprite to move along z axis, nor do we want it to rotate around any other than z axis, so we’ve got to check those appropiately.

Rigidbody settings adjusted for our needs.

You can now hit play and should see the ball falling very slowly.

Click here to try the demo.


Step 11: Tweak the Physics

The ball falls too slowly and that’s because our world unit is very small. Because we want it to be one pixel, we need to tweak the gravity so the ball falls faster. Go to Edit->Project Settings->Physics.

Physics settings.

After playing with it a bit, I set the gravity to -250. Another value that we should change while we tweak the physics is Min Penetration For Penalty. It’s set to 0.01 in world units, which is incredibly small value in our case. Let’s change it to one pixel.

Physics settings adjusted for our needs.

Click here to try the demo.


Step 12: Create Prefabs

The most common explanation for prefab is that it’s a blueprint of an object. It’s basic function is that if you have multiple objects of the same kind in the scene, if you want to modify them all you can modify the prefab and apply the changes to all objects at once. Every sprite needs a prefab. When generating the atlas, Sprite Manager 2 looks up which textures it will need in the current scene, and there is no way to access other scenes to see which textures they need. Creating atlases per scene is pretty much unacceptable, so what Sprite Manager 2 does is looking for prefabs in the project folder and looking up which textures they need, because prefabs are not assigned to any particular scene. This way it can build atlases that cover all of the sprites in the project.

To create a prefab, you can right-click in the project window area and go to Create->Prefab. The next thing you need to do is to drag and drop the object which you need a prefab of from the hierarchy window on the prefab in project window.

Prefab of our ball sprite.

To keep everything clean, let’s create a folder named Prefabs, and throw the Ball prefab there. Note that objects that are connected to a prefab have blue font color in hierarchy window.

Ball is now blue in the hierarchy window.

Step 13: More Sprites

Now it’s time to create something our ball can roll on. Here are two textures, first is for the solid ground.

Tile for our solid ground.

The second is for the background.

Tile for the background.

Now import them, preferably to Textures folder, and before you turn them into sprites, let’s clean up the project a bit more. Instead of using the same material for ball and tiles, let’s duplicate the one we already have. It’s sometimes useful to have objects that differentiate so much from each other on separate materials, because that allows you to treat them in a different way. You can duplicate any asset or object by either going to Edit->Duplicate or using a shortcut, Ctrl+D, of course the object we want to duplicate must be selected.

Our materials inside Plugins folder.

We should move our materials to folder exclusive for them, let’s call it Materials.

Material moved to Materials folder

Now create two new sprites the same way we created the first one. Remember that you need to assign Tiles Material to each of them. The sprites may look broken or not render at all right after building atlases, just hit play and everything should set up automatically.

Ball, ground and background.

Step 14: Ground and Background

To make our ground tiles inpenetrable, we need to attach a Collider for them. Since our tile is basically a square, we need to attach a Box Collider instead of Sphere Collider. Go to Components->Physics->Box Collider while having our ground tile selected to attach the collider.

Gizmo around our ground tile.

If you move our tile below the ball and hit play, you will see that the ball doesn’t go through the ground. Note that the z value of collider’s size is 0. That’s because the mesh is basically a quad. We should change the collider’s z value to 30, so more objects with varying depth can fit on it.

Size along Z axis should be pretty big.

Click here to try the demo.

Alright, let’s not forget about making every sprite into a prefab. Let’s create two new prefabs, each for one of our new sprites.

Two other prefabs join the Ball.

Let’s duplicate our tiles and fill the scene with them. First let’s set up the ground tiles.

Ground laid out.

Two rows look well. After duplicating the object so many times, the hierarchy window isn’t in a very good state.

Lots of ground tiles in our hierarchy window.

The solution for this is very simple. Create an empty object and drag and drop all of the platforms on it. This way, the empty object becomes the parent object of all those platforms. It basically means that if you do any actions on parent’s transform, like moving, scaling or rotating, the same action will be applied to all of its children. We can also collapse the parent, so children are invisible in the inspector.

Parent of our tiles.

Remember to name the parent appropiately. In our case Ground Tiles will suit it will.

Do the same action with the background, but this time make sure that the background is actually behind both, ball and ground tiles. It would be wise to create the parent at the beginning, change its z position to something like 20 so it’s pretty far from our main plane. Make sure that childrens’ z position is equal to 0 in that case, because the parent is pretty far away already, and the parent’s position stacks on top of childrens’. If you both, children and parent would have Z position equal to 20, then in fact the childrens’ Z position in world space would be equal to 40. After you created the first child, you can duplicate and the copy will have autoamtically the same parent assigned.

Background tiles laid out.
Background needs to be far away.

Step 15: More Physics Tweaking

Drag the ball in the editor really high up, let’s say to the top of our screen and then hit play.

Click here to try the demo.

As you can see, the ball doesn’t bounce at all. That’s because there’s no physic material assigned to our ball, and because of that the default one is used. The default material isn’t very bouncy, and so we have to create our own. The first step would be to create a folder called Physic Materials, to keep things clean. Once you have it, right-click it and go to Create->Physic Material.

New Physic Material.

As you can see, there’s quite a few options to tweak here. Before we get into them though, it would be nice to change the material’s name to Ball, it’s going to be a material for our ball after all. The most interesting option for us is Bounciness. The default value is equal to 0, and that simply means that the object with this material will not bounce. We can also tweak Dynamic Friction, which is the friction applied when the object is moving and Static friction, which is the friction applied when the object stays still. Besides that, there’s also Friction Combine and Bounce Combine, which define how the material will react upon colliding with another object. You can read more on the physic materials at this Unity Reference Manual page. I played a bit with those settings, and here’s what I came up with.

Physic material settings.

Of course creating the material isn’t enough. We have to assign it to our ball. Simple drag and drop will do the work, but you could assign it in the Sphere Collider component attached to the ball.

Collider with physic material.

Hit play and see the results.

Click here to try the demo.

As you can see, the ball bounces properly now.


Step 16: Approaching Portal Creation

The general idea of a portal is that whatever enters it, it immidiately appears at another portal, so the object gets warped. Additionally, we want portals to be two-sided, so a portal has two sides which correspond to the other two of another portal. Since we grasp the general idea what do we want to do, it’s time to think of how exactly are we going to do it. As always, there are multiple options to consider. We’ll choose the one that suits us best. We should initially assume that we won’t be cutting part of an object and then change its position, because that would be a very hard thing to do.

The easiest way to approach this problem is to create a copy of an object that goes through first portal, and this copy will be synchornized with the first one so it appears like it’s the second part of the first object.

Two objects that seem to be one.

Now we need to figure out how to display only a certain part of a sprite. Since we want portals to be usable from two sides, the standard trick in which we could make a ball disappear behind a wall does not come into play.

Part of an object hides behind the wall.

As you can see, by this method we would be able to use only one side of portal, and additionally the portals could be positioned only on the wall or the floor, so, though it’s simple, it’s not flexible at all.

Another idea that might pop into a head while thinking about this, is to make use of render targets. It would basically mean that we would have to overlay the area of where the ball is going with a texture that was there before the ball moved into that area.

Part of an object hides behind the texture.

This option is not very flexible too, because we would be required to keep the area we replaced with the texture static, so nothing could move into it. Not to mention that render textures are a feature of Unity3D Pro only.


Step 17: Hide Part of a Sprite

What’s left for us is instead of manipulating the world so it hides part of our sprite, is to make part of a sprite disappear. There is a very nice functionality in Sprite Manager 2 that allows us to hide any part of sprite by simply declaring a rectangle in which our sprite should be visible.

Hiding everything besides the rectangular area.

It makes use of sprite’s ClippingRect, but it doesn’t actually define the area in which sprite is visible, but rather adjusts the UVs of the sprite to display only the pixels contained in this ClippingRect. Unfortunately, because of the way it works, it doesn’t work for rotated sprites, or to be more accurate: the sprites can be clipped correctly when unrotated. Since our warping object is a ball, this method isn’t really acceptable.

Another way to solve this problem is to write a custom shader which would cut off part of our sprite’s texture along any axis. This way we will be able to have rotated sprite rendered properly.

Cutom shader work.

It will be a lot of work, but creating a shader seems like the best option for us. It will be as flexible as we will make it, so we can precisely create what do we need.


Step 18: Create a Shader

Let’s create a shader. You can do so by right-clicking in the project window and then goto Create->Shader. Now let’s create a folder named Shaders and put the new shader in it, you should also rename it so it makes more sense than NewShader. Cutoff will be OK.

Shader "Cutoff" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200

		CGPROGRAM
		#pragma surface surf Lambert

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		void surf (Input IN, inout SurfaceOutput o) {
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

As you can see, there’s quite a lot of code here already. You can delete it whole and replace it with the following template.

Shader "Transparent/Cutoff" {
Properties {
	_MainTex ("Main Texture", 2D) = "white" {}
}

Category {
	Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
	ZWrite Off
	Blend SrcAlpha OneMinusSrcAlpha
	SubShader {
        Pass  {

ColorMaterial AmbientAndDiffuse
Lighting Off
Cull Off

CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag

sampler2D _MainTex;
float4 _MainTex_ST;

struct v2f {
    float4  pos : SV_POSITION;
    float2  uv : TEXCOORD0;
};

v2f vert (appdata_base v)
{
}

half4 frag (v2f i) : COLOR
{
}

ENDCG
		}

		}
	}
}

There’s even more code here. Now, if you’re not familiar with writing shaders in unity, or even shaders in general this code must seem very unapproachable, in fact I’m not a graphics programmer and I’m not familiar with shaders at all, but with some patience if you grasp the concept then creating a simple shader like the one we are creating now is not that hard. A lot of what you see here are simple settings that you have little control of, you can see what are they about at this Unity Reference Manual page. For example Cull Off allows us to see sprites from both sides, front and back, ZWrite Off disables writing to Z Buffer, Lighting Off disables lighting and so on. There’s also a syntax specific for Shader Lab which unity uses, so if you want to know what goes where and with what, then you should really see that Unity Reference Manual page. The meat of the shader in our case is what goes between CGPROGRAM and ENDCG. Aside from that, what interests us is in the Properties{} brackets.

Between CGPROGRAM and ENDCG we can write a custom shader in CG language. That’s all there is to it, though we need to remember here to #include "UnityCG.cginc", so we can use custom unity functionality in the shader. Another thing that we must do is to define the names of our vertex and fragment programs. To do that, we use #pragma vertex vert and #pragma fragment frag. This way the compiler will know that our vertex program is named vert and our fragment program is called frag. Next thing is sampler2D _MainTex. This is basically our texture input, the texture on which sit our sprite’s frames. Right next to it is float4 _MainTex_ST. We don’t do anything with it, we just need it defined and it can sit here, it is used to specify tiling and offset for our texture. We won’t use it directly, but as always, nothing is without purpose. The next thing is struct v2f which is basically a struct for our vertex program output and fragment program input. It contains two variables, float4 pos and float2 uv. They are containers for our vertex position ad UV respectively. Next is our vertex program. Its input is of appdata_base type, which means that it’s a vertex with position, normal and texture coordinate. It returns our output structure which we defined earlier. The last thing is our fragment program, its input is the output of the vertex shader, so it’s type is our struct v2f. It returns the COLOR, which is basically four 16-bits floats. ENDCG ends our shader.

The properties , which go inside Properties{} brackets, allow us to submit input data directly from unity to the shader. For now we’ve got only one, _MainTex which is the texture our sprite’s frames sit on. Notice that they need to be declared here as well as in the CG shader.


Step 19: Vertex Shader

v2f vert (appdata_base v)
{
    v2f o;
    o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
    return o;
}

First thing we do in our vertex shader is creating our output structure which we need to set here. To calculate the position, we simply need to multiply the position of the vertex (in object space) by UNITY_MATRIX_MVP, which is a predefined matrix. Next, we need to calculate our UV, we do that by using TRANSFORM_TEX macro. We only need to submit the texcoord from our vertex data and the the texture our sprite is using. TRANSFORM_TEX uses our _MainTex_ST, that’s why we had to define it. Finally we can return our output.


Step 20: Fragment Shader

In fragment shader we need it to make any pixels that go behind the axis invisible. In fact, this is the meat of our shader, here we’ll tell which texels should be displayed, and which shouldn’t. For that we need a linear equation. Linear equation can be easly used to split the sprite into two parts, and that’s all we need to do in this shader. If you forgot about how does linear equation work, you can visit this wikipedia page on linear equation. We’re going to use the standard form. So what kind of parameters does linear equation have? Those are A, B and C. The first two set the direction of the axis, and the third one only offsets the whole axis. That’s very useful, because by offsetting the whole axis we can tweak how much of the sprite should be invisible. So we need three new properties to declare, let’s call them _A, _B and _Cutoff.

Properties {
	_MainTex ("Main Texture", 2D) = "white" {}
	_A ("_A", float) = 0.5
	_B ("_B", float) = 0.5
	_Cutoff ("_Cutoff", Range(-1.0, 1.0)) = 0.0
}

The _Cutoff is of Range type. This will give us a nice slider in editor to edit this value. Notice that its range is between -1.0 and 1.0, suggesting that the zero is minimum, and one is maximum cutoff. The first two parameters for our linear equation are normal floats, with initial value set to 0.5.

Properties of our shader appearing in the inspector.

Remember that we also need to define our properties in the CG program.

sampler2D _MainTex;
float4 _MainTex_ST;
float _Cutoff;
float _A;
float _B;

Alright, now let’s go back to our fragment shader.

half4 frag (v2f i) : COLOR
{

}

The first thing we should do, is to sample the appropiate color of the current pixel from the texture and assign it to our output color. Let’s name our output color c.

half4 frag (v2f i) : COLOR
{
	half4 c = tex2D(_MainTex, i.uv);
}

Now, let’s check whether the pixel crosses the line from our line equation. If it’s below it, let’s change its alpha value to 0, so it will be invisible. We know our current position on the texture thanks to i.uv. It’s equal to (i.uv.x, i.uv.y).

half4 frag (v2f i) : COLOR
{
	half4 c = tex2D(_MainTex, i.uv);

	if (i.uv.x*_A + i.uv.y*_B < _Cutoff)
    	c.a = 0;
}

Now let’s return our color.

half4 frag (v2f i) : COLOR
{
	half4 c = tex2D(_MainTex, i.uv);

	if (i.uv.x*_A + i.uv.y*_B < _Cutoff)
    	c.a = 0;

	return c;
}

Also, it would be nice if we allowed to tint the sprites with color of choice. Let’s create a new property called _Color.

Properties {
	_Color ("Main Color", Color) = (1,1,1,1)
	_MainTex ("Main Texture", 2D) = "white" {}
	_A ("_A", float) = 0.5
	_B ("_B", float) = 0.5
	_Cutoff ("_Cutoff", Range(-1.0, 1.0)) = 0.0
}

Now we need to do the same in CG..

float4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float _A;
float _B;
float _Cutoff;

And multyply it with our output color in the fragment shader.

And we're done. You can try it out and play with _A and _B parameters.

half4 frag (v2f i) : COLOR
{
	half4 c = tex2D(_MainTex, i.uv);

	if (i.uv.x*_A + i.uv.y*_B < _Cutoff)
    	c.a = 0;

	return c*_Color;
}

That's it for the shader. You can assign it to the material by drag and drop action. Drop our shader on the Ball Material, if you want you can fiddle with factors and see how does it cut the texture. Also, if the sprite appears darker after the change of the material's shader, change Main Color to the white in the Ball Material.

Part of the sprite is invisible.

Now we have a lot of work to do to supply the shader with the right data. That'll come in Part 2, where we work on letting the Roly character move through a portal, hiding the part of him that's "inside" it.


Other parts in this series:Build a 2D Portal Puzzle Game With Unity: Adding the Portals»

Add Comment

Discussion 4 Comments

  1. GiorgiAabelashvili says:

    OMG! What a Great tutorial! after this tutorial i can write any 2D game in Unity!!! thanks!!! i give you 10/10 points!!!!!

  2. Guillermo says:

    Wow, thanks a lot! I’ve been searching a lot for something like this,.. there are plenty tutorials over there, but this is a great one! (And a little hard to find to).

    Regards!!

  3. Arun Chandran says:

    Great tutorial with SM2. But I do have a small problem, when I reopen, the Sprites shrinks in size. ie the height and width becomes 42.29 in all cases while the prefabs have their original 90×90 and 64×64? Any idea, where I’ve gone wrong?

Add a Comment

To add a code snippet to your comment, please wrap your code like so: <pre name="code" class="html">YOUR CODE</pre>. You can replace the class name with "js," "css," "sql," or "php." If there are any "<" or ">" within your code, please search and replace them with: &lt; and &gt; respectively.