Customizable Navigation Bar

Sunday, February 27, 2011

Building A Game In Unity Part 2: Basic Character and Background Movement

OK, so the game that I have decided to build for these tutorials is a top down shooter. This way I can create a simple game and can spend more time going over how to do stuff within the engine. This week I am covering basic character movement using the mouse, and how to set up a parallax background.

Also, follow me on twitter at http://twitter.com/#!/purdyjo!







So the first thing I recommend doing since we are creating a top down shooter is create the camera movement script. The way this game is going to be set up down the road is so that either the main character or the camera passes through triggers and spawns enemies.

So what we are going to start with is the main camera movement script. This is a very simple script that just makes any object that it is attached to move at the set speed and velocity.

using UnityEngine;
using System.Collections;

public class ConstantVelocity : MonoBehaviour
{

/*I keep the movement direction and speed as separate variables because it is easier to keep track of and change when needed*/

        public Vector3 movementDirection = new Vector3(0.0f, 0.0f, 1.0f);
        public float movementSpeed = 5.0f;

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

/*Simply just add the direction times speed to the position. Time.deltaTime adds the time since the last frame, which will make it so that no matter what the framerate is, it will move at 5 units per second for example, instead of 5 units per frame.*/

                transform.position += movementSpeed * movementDirection * Time.deltaTime;
        }
}



That's all you need for the constant velocity script. Pretty easy eh? Now just attach it to the camera and you're good to go for the next script.


The next script makes whatever object is attached to it follow the mouse.



using UnityEngine;
using System.Collections;

public class MouseFollow : MonoBehaviour
{

/*Let's make a public Camera variable so that in the editor we can choose what camera this script should take into account.*/

        public Camera mainCamera;


/*Our start function should be making sure that the main camera variable is set to something. If it's not then we should try and find something to take its place. If there is nothing that we can find then we just throw an error.*/

        void Start ()
        {
                if(mainCamera == null)
                {

/*If the mainCamera is set to null, then we start looking for a camera. The main camera has a default name of "Main Camera", so we look for that.*/

                mainCamera = GameObject.Find("Main Camera").GetComponent<Camera>();

/*If that is null we check Camera.mainCamera, which returns the camera with a tag of Main Camera*/

                        if(mainCamera == null)
                        {
                                mainCamera = Camera.mainCamera;
                        }


/*If the mainCamera is still set to null then we throw an error because there is no other camera that we know to look for*/

                if(mainCamera == null)
                {
                        Debug.LogError("No camera designated as main game camera. Either set camera manually in editor or set tag to Main Camera");
                        Debug.Break();
                        }
                }

/*Then we move the object to the center of the screen*/

                transform.position = new Vector3(mainCamera.transform.position.x, mainCamera.transform.position.y - 60, mainCamera.transform.position.z );
        }


/*In the update function we want to find where the object would be in relation to the mouse, and then we want to ease the object into that position*/

        void Update ()
        {
                Vector3 mousePosition = Input.mousePosition;

/*The Z of the mousePosition is 0, so if we just project the mouse position to the world it will show up ontop of the camera, so if we set to to 60, it will project the position outwards 60 units, this is useful because in this case, we keep the main character 60 units away from the camera at all times.*/

                mousePosition.z = 60;



/*Now we want to make sure that the object doesn't go off the screen. This next section makes sure of that by comparing the mouse position to boundaries created in the code*/

                mousePosition.x = Mathf.Max(Screen.width * 0.05f, Mathf.Min(mousePosition.x, Screen.width * 0.95f));
                mousePosition.y = Mathf.Max(Screen.height * 0.05f, Mathf.Min(mousePosition.y, Screen.height * 0.95f));

/*We then project the mouse into world space and smoothly transition the position of the object to the mouse by using Vector3.Lerp which stands for linear interpolation*/

                mousePosition = mainCamera.ScreenToWorldPoint(mousePosition);

                transform.position = Vector3.Lerp(transform.position, mousePosition, 0.4f);
        }
}


Now if you attach this object to your ship or main player or whatever it is that you are using, it should smoothly move to follow the mouse around on the screen. Make sure that the main camera is set in the script otherwise you will just get errors.


So now that we have these first two scripts, we should be able to move the player around and the camera should be moving forward at a constant velocity.


So moving on, now we need a parallax background. If you don't know what a parallax background is, it's basically two or more planes that form a continuously moving background. Now what that means is, once one of the backgrounds is off of the screen, it moves to a position behind the other background, and so on and so forth, so that the background will keep scrolling forever without any breaks. Since we are making a top down shooter, we need something like this so that the player feels as though he/she is moving. 


It is a very basic script and is completed by doing the following:

using UnityEngine;
using System.Collections;

public class ParallaxPlane: MonoBehaviour
{



/*We want to create a public GameObject, which will hold the other parallax plane that it will be moving around*/

        public GameObject parallaxPartner;



/*We then want to create two variables, on float for speed and one Vector3 for the movement direction.*/

        public float speed;
        public Vector3 movementDirection = new Vector3(0.0f, 0.0f, -1.0f);

/*What we want to do in the start function is make sure that the parallaxPartner variable had something put into it, otherwise there is going to be a whole ton of errors in the update function*/

        void Start ()
        {
                if(parallaxPartner == null)
                {
                        Debug.LogError("parallax partner set to null");
                        Debug.Break();
                }
        }

/*In the update function we want to move the background and check to see if the appropriate edge is not visible by the camera*/

        void Update ()
        {
                transform.Translate(movementDirection * speed * Time.deltaTime);

/*Here we make 3 variables to hold the position of the edge we are checking or affect it in some way */

                Vector3 objectCorner = transform.position;
                Vector2 multiplier = new Vector2();



/*gameObject.GetComponent<MeshFilter>().mesh.bounds.size gets the size of the original bounding box of this object. This is useful for finding the size of the object for these specific movement purposes*/

                Vector3 objectSize = gameObject.GetComponent<MeshFilter>().mesh.bounds.size;

                objectSize.Scale(transform.localScale);

/*Here we check to see if the movement direction is set to 0 for anything. If it is then we just skip over that chunk of code, if not we figure out the actual direction and set the affector, which will determine which part of the parallax plane we should be checking for leaving the screen*/

                if(movementDirection.x != 0.0f)
                {
                        multiplier.x = movementDirection.x / Mathf.Abs(movementDirection.x);
                        objectCorner.x += multiplier.x * objectSize.x / -2.0f;
                }

                if(movementDirection.z != 0.0f)
                {
                        multiplier.y = movementDirection.z / Mathf.Abs(movementDirection.z);
                        objectCorner.z += multiplier.y * objectSize.z / -2.0f;
                }

/*We then find the size of and position of the parallax partner so that we can move to the proper position com time*/

                Vector3 viewportCoord = Camera.mainCamera.WorldToViewportPoint(objectCorner);
                Vector3 newPosition = transform.position;
                Vector3 partnersize = parallaxPartner.gameObject.GetComponent<MeshFilter>().mesh.bounds.size;
                partnersize.Scale(parallaxPartner.transform.localScale);

/*We then check to see which part of the screen we should be checking against and move to the proper position if we should*/

                if(multiplier.x != 0.0f)
                {
                        if(multiplier.x > 0.0f && viewportCoord.x > 1.0f)
                        {
                                newPosition.x = parallaxPartner.transform.position.x - (partnersize.x + objectSize.x) / 2.0f;
                        }
                        else if(multiplier.x < 0.0f && viewportCoord.x < 0.0f) 

                        { 
                                newPosition.x = parallaxPartner.transform.position.x + (partnersize.x + objectSize.x) / 2.0f;
                        }
                 }
                if(multiplier.y != 0.0f) 
                { 
                        if(multiplier.y > 0.0f && viewportCoord.y > 1.0f)
                        {
                                newPosition.z = parallaxPartner.transform.position.z - (partnersize.z + objectSize.z) / 2.0f;
                        }
                        else if(multiplier.y < 0.0f && viewportCoord.y < 0.0f)

                       {
                                newPosition.z = parallaxPartner.transform.position.z + (partnersize.z+ objectSize.z) / 2.0f;
                       } 
                }


/*We then set the position of this object to the newly modified position*/


                transform.position = newPosition; 
        } 
}

Now that we have this script completed, we can now attach it to the parallax planes and let them do there thing, except there is one problem. Run the game. They aren't moving! Well actually they are, but they are set to go the same speed as the main camera, and we need to change this. We are going to make one additional script that will move the planes based on the main camera's current velocity so that it will appear as though they are actually moving. This is very easy, and is done like so:


using UnityEngine; 
using System.Collections; 


 public class ParallaxController : MonoBehaviour 
{


/*We want to create an array of the parallax objects so that we can move them properly*/


         public Transform[] parallaxObjects; 


/*We also want to access the ConstantVelocity script that we attached to the main camera for this*/


         private ConstantVelocity velocityObj; 


         // Use this for initialization
        void Start () 
        { 


/*Since this is attached to the camera we can just grab the ConstantVelocity component off of the GameObject*/

                velocityObj = gameObject.GetComponent<ConstantVelocity>();
        }

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



/*for each object that is in the array, we just translate it the same amount that the camera is moving*/

                for(int i = 0; i < parallaxObjects.Length; i ++)
                {
                        parallaxObjects[i].Translate(velocityObj.movementSpeed * velocityObj.movementDirection * Time.deltaTime);
                }
        }
}



Once you have this script completed you should be able to attach it to the camera, drag the parallax objects into the array, and it should be good to go.