There has been talk about Unity in shoutbox and in a few posts here and there so I thought I could get the ball rolling on a short and relatively easy tutorial and expand on it if there is interest. Since there seem to be many Sonic fans here I'll try to create a simple Sonic clone as we go. This tutorial requires at least fundamental understanding of programming (variables, functions, classes/objects, instantiations and inheritance) and some basic math/programming concept (especially 2d/3d vectors). I expect you to be able to do some simple Unity things yourself such as install the application and create a new project. You add files to a project by drag/drop to the Project folder. NOTE: I will sometime be brief in my explanations. Feel free to ask and I will explain and perhaps extend the tutorial text. No question is dumb. Only swedes are dumb. Requirements (so far): Unity 5 Green Hill Zone Act 1 png file from https://info.sonicretro.org/Sonic_the_Hedgehog_(16-bit)_level_maps 1 - Create a new project Start a new project. Add the map file to the project folder. 2 - Create the background Add a GameObject -> 3D Object -> Quad. This will create a Quad (a square made out of two triangles) within the game world. Make sure its selected and look at the right hand side of the window where it says "Inspector". That is the properties of the selected object. Set its Rotation (under Transform) to 180/0/180. Press F to center screen on object. Might have to fly around in the world to view it at the correct side. Drag and drop image over to the object. 3 - Scale background correctly Image size is 10240x1280. That is equal to 8:1. Set Transform.Scale to Vector3(8,1,1); As you can see the image now has the correct aspect ratio. It is however too small. Sonics collision model is going to be the equalent to 28 pixels in height. This is measured from one of the chutes he rolls around in. Since we are going to be simulating physics we want to retain real world sizes, so we want sonic to be about 1 meter in height in the game world. 1 meter is the same as 1 unit inside of Unity. In other words we want 28 pixels to be the same as Unity 1 unit. So we take the height of the image 1280 pixels and divide by 28 to find its height ratio. Answer is 45.7(...) so we set the image Transform.Scale.Y to to 45.7. Since we know that the X scale is 8 times longer I just hit * 8 on my calculator and we get 365.7(...). Input this value in Transform.Scale.X. You now get a nice, big and blurry image inside Unity. Don't worry about it being blurry for now! I have added a 1x1x1 unit cube at one of the chutes to see that we now have the correct size background for a 1 unit high Sonic model. 4 - Set background properties Select the Quad in the Hierarchy. Notice in the inspector that there is a checkbox next to "Mesh Collider". Remove it so that it is in off position. Now select the image in your project files. Set texture type to "Editor GUI and Legacy GUI", Filter Mode to Point (no filter), Max Size at 8192 and Format at 16 bits. Hit apply. This is temporary for now and will make the image look better inside the editor. 5 - Move and set up camera Time to set the camera up correctly. Select "Main Camera" in the Hierarchy and change its Transform.Position to Vector3(175,-9,10); and its Transform.Rotation to Vector3(0,180,0); Further down under Camera set its Clear Flags to Solid Color, its Background to Hex color 0088EEFF (which I found in Photoshop to be the background color in the map) and most importantly set its Projection to be Orthographic. Above your 3d view click the Game tab to get a look at what we have so far. 6 - Change ambient lighting The solid color on the background to the left of the game image is how we want the background in the image to look like. Right now that isn't happening because everything in the game world is being shaded and lit by some default settings. First thing we do is select the Direction Light in the Hierarchy and delete it. Now there is no light source in the game world, there is just the default light settings (ambient coloring). Go to Window -> Lighting and set Ambient Source to be Color. Click the color-picker underneath and set it all the way to white. If you look at your image this is as close to the same color you get after Unity is done processing the image. 7 - Create temporary platform Go from Game view back to Scene view. Select GameObject -> 3d Object -> Cube. Position it at Transform.Position(180,-12.1,0); to position it just under Sonics feet. Remember the F command to center camera on the selected object. Now lets make this a bit wider by settings its Transform.Scale.X to 10. This block will serve as a platform to walk on when testing. 8 - Create character Now we want to create our character. For that we will use a capsule as collision shape. When Sonic rolls into a ball it is better to change shape into a ball instead. Whatever the case, set the Transform.Position of the newly created Capsule to Vector3(180,-10.8,0); and its Transform.Scale to Vector3(0.8,0.71,0.8); There are many ways to move an object around in a game world. Most of the time you don't want your character to be moved around by the physics simulation (by applying forces to the object) but rather to be manipulated directly. In Sonics case however his movement (especially as a ball) is highly dependent on physics and so it would be beneficial to just push the guy around the level using the built-in physics system in Unity. To make the capsule behave like a physical object make sure that the Capsule is still selected, then click on the Add Component button in the Inspector. Select Physics -> Rigidbody. The Rigidbody class adds the correct physical behavior of the object inside the game world. When we want to manipulate and control the object we don't control its Transform directly but rather apply forces to its Rigidbody class. Anyways, click on the Game tab again and let's try to run this game for the first time. Press the Play button and see your game in action. The capsule should first fall to the block (since it is positioned slightly above it) and then slowly tilt to the side and fall off. 9 - Set movement and rotation constraints We don't want the capsule to rotate at all and instead stay fixed. Luckily we can control that in the Rigidybody. Click on the little arrow next to Constraints and select all three Freeze Rotation boxes. Since we also don't want our character to move in the Z direction (towards/away from the screen) we also select the Z Freeze Position box. 10 - Create script and set development environment We have now set up the game world about as much as we need before we start coding. Just a word about that though; the default editor for Unity seems to have been switched from Mono to Visual Studio. While I use Visual Studio for non-Unity related projects I for some reason prefer Mono when working with Unity. If you want to use the same then go to Edit -> Preferences -> External Tools -> External Script Editor: MonoDevelopt (built-in). Whatever the case, go the the Projects folder (where the level image is) and right-click and select Create -> C# Script. NOW WAIT A SECOND! Before you press enter on naming the C# just make sure you don't change your mind about the file name. When you press enter there will be information inside the script that refers to the filename. Should you later change the filename you will get an error. I named by script CharacterController. Now double-click on it to open it in your code editor. edits: Changed #2 for clarity.
11 - Character movement Part 1 A script in Unity has special purpose functions that gets called at specific times. When creating a new script there are two of these functions already set up; Start and Update. The Start function gets run only once when the object itself is loaded. The Update function runs once per frame. I will get back to why that matters in a second. If the player releases the movement key we want to register that change immediately. Likewise if the player presses a second button or some other condition change we want to respond straight away. The best place to put a script that checks for state changes (is d-pad right pressed = yes/no, for example) is in the Update function. When d-pad right is pressed we want to start pushing Sonic with a given value in the right direction. Now pay extra good attention: If that value is for example 10 (or any other arbitrary value) then how hard you push Sonic in that direction will be directionally proportional to the number of frames per second. If for example Update is called 120 times per second (120fps) then the force applied to Sonic will be 120 * 10 = 1200. If however there is a ton of stuff going on and you CPU is busy handling other stuff you might end up with 3fps. That means Sonic was pushed with 3 * 10 = 30. Bugger! So how do we solve this? Luckily Unity has a timing mechanism for each frame. The Time.deltaTime value is the time (in seconds, although it is a float so its mostly 0.00... something) it took to create the last frame. The solution is then to push Sonic with 10 * Time.deltaTime to make sure that the speed is the same for each frame. Yay! Now let's start coding. First we want to add a reference to the character model itself. For now we create a public variable of type Rigidbody and call it Character. Add it outside of any function calls. Leave it be for now, we'll add a reference to the actual object later. Code: public Rigidbody Character; Now let's add a function to check if the D character (of WASD fame) is being pressed. In the Update function add a an if that checks if the KeyCode.D is being pressed. Code: if (Input.GetKey (KeyCode.D)) { } Now inside this function we want to apply force to the Character. First let's calculate the force as a float. Code: float force = -1000 * Time.deltaTime; Then let's apply it to the right axis with the AddForce function: Code: Character.AddForce(force,0,0); Refer to this link for complete script so far: http://pastebin.com/di5PfU0u That's it! Now go back to the Unity editor. This script is only available in our project as a file and is not part of the game world, hence it wont run yet (just like an image that isn't applied to any object in the game world isn't part of the game, just available in the project as a resource). Select the Capsule in your Hierarchy, press F2 and rename it to Character. Drag the CharacterController script from your Project files and drop it on the highlighted Character object in the Hierarchy. You can see at the bottom of the Inspector that a script has been added. It also has an available field called "Character". That is the "public Rigidbody Character" variable we created. Drag the Character object in the Hierarchy and drop it the empty box in the script. It should create a reference to the Rigidbody on the Character object. Time to save! Ctrl+S and give the level you are working on a name. I just called mine "world". 12 - Character movement Part 2 Press play to test out your game. Holding D will send the character flying off the screen. Notice that it picks up speed as you hold the button down. Right now there is no maximum speed. But before we do anything about that, let's add movement in the other direction as well as a jump function. Open the CharacterController script and duplicate the right movement. Switch KeyCode.D with KeyCode.A and invert the force (remove the minus sign). That's it, now your character can move in both directions. Time to create jump functionality. Jumping will be done by directly setting its upwards velocity. That means that if the character is falling down with a -10 velocity and you press jump we will directly set its -10 velocity on the Y axis to the jump value. The result is that you will have the same jump height if you are standing still vs. falling or in an already positive trajectory. We will only affect the Y axis and make sure that we conserve and can manipulate the X axis. Create a new function called Jump (no return value so void, and no input). Inside this function add the following: Code: Character.velocity = new Vector3 (Character.velocity.x, 10, 0); This basically sets the velocity of our Rigidbody to whatever it had as x value + 10 in the Y axis. If we had used AddForce instead of setting the velocity we would have had to add force over time. Now instead we simply set it at a fixed speed straight away with no delay (which rhymes, yay!). Go back to the Update function and add a new if but this time use GetKeyDown and refer to KeyCode.Space. Make it execute the jump function. Code: if (Input.GetKeyDown (KeyCode.Space)) { Jump (); } The difference between GetKey and GetKeyDown is that GetKey returns true continuously as the key is held down (hence executes for every Update() call). GetKeyDown only returns true once until the key is pressed again. Now press play and run your fanfastic game! 13 - Character movement Part 3 Time to add max speed. We use the following logic; first we create a single float value called MaxSpeed. That is the maximum speed for any direction. Code: private float MaxSpeed = 5.0f; We then create and if/if-else pair to check if the character has a velocity in above a certain threshold in either positive or negative direction of x. If we are above the threshold we check if the character is above the maximum speed (and use minus sign to test in opposite direction). If we are above max speed we simply set the velocity in that direction to have the value of the defined max speed. That way we will never go over. Code: if (Character.velocity.x > 1) { if (Character.velocity.x > MaxSpeed) { Character.velocity = new Vector3 (MaxSpeed, Character.velocity.y, 0); } } else if (Character.velocity.x < 1) { if (Character.velocity.x < -MaxSpeed) { Character.velocity = new Vector3 (-MaxSpeed, Character.velocity.y, 0); } } Refer to this link for complete script so far: http://pastebin.com/Xj39R3ZQ 14 - Camera Movement When you move around the camera doesn't follow. That sucks and we should do something about it. Create a new C# script called CameraController and open it up. Create a public Transform variable called Character. Code: public Transform Character; With a reference to the transform of the character we can find its position. That means that we can move our camera accordingly. The idea is as follows: If the character moves away from the center of the camera view (which is the same as the cameras X/Y position) more then a certain threshold then the camera will start following the character. We split up X/Y axis and check each of them separately. For that we require two values that makes up out threshold: Code: private float XThreshold = 3.0f; private float YThreshold = 3.0f; Next we go inside the Update function. We do a check if the players position is larger than its camera position + threshold. We also do an else if with the opposite value. Code: if (Character.position.x > transform.position.x + XThreshold) { } else if (Character.position.x < transform.position.x - XThreshold) { } Now we move the camera as follows: Code: transform.position = new Vector3(Character.position.x-XThreshold, transform.position.y, transform.position.x); We set our (the Camera) transform.position to be a new Vector3. Y and Z of that Vector3 we set to be whatever value it had before, so the only thing that is changing is the X direction. We set it to be the character position - the threshold. That means that whenever the character moves further then that then we will move the camera in that direction as much as the character walk over the threshold. If the character moves 5 over the threshold we will move the camera 5 units. This is a continuous process and the result is a smooth camera-follow. Add this to the else if statement: Code: transform.position = new Vector3(Character.position.x+XThreshold, transform.position.y, transform.position.x); Duplicate the whole thing and invert for Y axis. Remember to use the correct axis in the Vector3. Check out the whole script here: http://pastebin.com/Y63ZvTVB Close and drag the script to the Main Camera in the Hierarchy. Drag the Character object from the Hierarchy over to the Character reference in the CameraController Inspector reference. Press play and have fun! Oh and remember that pressing enter several times can make you fly around. That's it for now! Let me know if there are any questions. I will continue on the tutorial when I have time
Damn, this is brillant ! Thanks a lot for this amazing tutorial ! Makes me want to start using Unity right now ! Thanks Thomas !