Saturday, February 1, 2014

Unity 2D!

Excited to be working with Unity 4 with its new 2D capabilities built in. In the past, I used 3rd party plugins with Unity to build 2D games, but they always felt clunky. To get better at Unity, I took a challenge upon myself to make an simplified versions of one of my favorite mobile games, Happy Street. My goals are to:

  1. Build or find a swipe camera class to allow the player to swipe the camera along the x-axis./li>
  2. Build or find a class allowing pinch zoom in and out.
  3. Build a repeatable, non-animated terrain for 4 locations: City, Forest, Beach, and Mines
  4. Have an animated sprite for a character that randomly walks left and right, and has idle frames for both directions called at random.
  5. Have a UI that allows players to change locations and build.
  6. Have resources to collect in forest, beach, and mine locations. 
  7. Allow players to build buildings in city based on the amount of resources they have. 
  8. Constrain swipe and zoom. 
  9. Connect the game to Facebook's Unity API.
  10. Have players trade resources through the Facebook api. 
Task #1 - Scrolling camera gesture
Initially, I created my own class that allowed for swiping since it was such a simple task. The class below is to be placed directly on my main camera...

using UnityEngine;
using System.Collections;

public class CameraController : MonoBehaviour 
{

    public float SwipeSpeed = 0.1f;

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

        switch (Input.touchCount)// number of fingers on the screen.
        {
            case 0:
                break;
            case 1:
                Scroll();
                break;
            case 2:
                Zoom();
                break;            
        }
        
    }

    private void Zoom()
    {
        // Todo
    }
    
    private void Scroll()
    {
        if (Input.GetTouch(0).phase == TouchPhase.Moved) // if the finger has moved since the last update
        {
            Vector2 touchDeltaPosition = Input.GetTouch(0).deltaPosition; // just to make it neater, I set a variable.
            // in the line below, I move this GameObject (the camera) in the opposite direction of the swipe for a swipe scroll effect.
            transform.Translate(-touchDeltaPosition.x * SwipeSpeed, 0, 0);    
        }
    }
}
This worked well except that I don't have the nice scrolling tween upon release of a fast swipe like Happy Street does. I decide to come back to that later recalling that NGUI had a built-in camera scroll class that may be promising. If it meets my criteria, I'll use it, if not, adding a tween should be simple; I suspect that could be done using the Mathf.SmoothDamp function.

Task #2 - Zooming
Let's get that Zoom function working...
using UnityEngine;
using System.Collections;

public class CameraController : MonoBehaviour {
    public bool isActive;
    public float SwipeSpeed = 0.1f;
    public float ZoomSpeed = 0.001f;
    private float _DeltaZoomDistance = 0;
    public GameObject groundController;
 void Start () {
           isActive = true;
 }

 void Update () {

           switch (Input.touchCount)
            {
                case 0:
                    ResetTouch();
                    break;
            case 1:
                    Scroll();
                    break;
            case 2:
                    Zoom();
                    break;            
            }
        }

    private void ResetTouch()
    {
        _DeltaZoomDistance = 0; // resets distance to prevent 
    }

    private void Zoom()
    {
        Vector2 touch0, touch1;
        float distance;
        touch0 = Input.GetTouch(0).position;
        touch1 = Input.GetTouch(1).position;
        distance = Vector2.Distance(touch0, touch1); // How far apart are the fingers?
        float zTranslation = distance - _DeltaZoomDistance; 
        // zTranslation stores the new position of the z axis based on whether or not the fingers got further apart or closer together.
        // I do this using _DeltaZoomDistance which keeps track of the previous distance zoomed to ensure it only moves upon change in the touch distance.
        // Otherwise, I suspect it would immediately move when I first touch and zTranslation would be an incorrect value anyway.
        if (_DeltaZoomDistance != 0)
            transform.Translate(0, 0, zTranslation * ZoomSpeed);
        _DeltaZoomDistance = distance; // set the zoom distance 
    }
    
    private void Scroll()
    {
        if (Input.GetTouch(0).phase == TouchPhase.Moved)
        {
            Vector2 touchDeltaPosition = Input.GetTouch(0).deltaPosition;
            transform.Translate(-touchDeltaPosition.x * SwipeSpeed, 0, 0);
        }
    }
}
I decide to wait to constrain the camera until all my assets are in so I can see how much I need to constrain it, and because I'm not quite sure how exactly I want to approach that yet anyway.

Item #3 repeatable non-animated terrain.
I decide to use the same method I've used for 3D games which is to basically make prefabs and place them next to each other. The difference is that now I have to learn to use Unity's new Sprite GameObject, which is pretty simple once you get the hang of it. In your inspector, simply switch your texture asset's "Texture Type" to sprite. Create a new sprite in the GameObject menu, then assign your sprite texture to it. To save a step, if you have your sprite texture selected when you create a new Sprite GameObject, it'll put the texture on it for you. I ran into an issue where the material on the Sprite GameObject was not set the first time I used it. I'm not sure why that happened, but if it ever happens again, the correct default material is "Sprites-Default".
I notice the ground in Happy Street's terrain is not exactly 2D. There is some perspective on the nearest terrain that is noticeable upon camera swipe. Everything else in the game appears to be 2D, So I decide the best way to copy their effect is to rotate the ground portion of the terrain along the x axis to give it some slight perspective.
 
To make the terrain repeatable, I put a box collider around the terrain matching its dimensions, then I put the following script on an empty GameObject named "GroundController" and set the "Chunk Prefab" property to my terrain prefab.
using UnityEngine;
using System.Collections;

public class GroundController : MonoBehaviour {
    public int chunckLength = 10;
    public GameObject ChunkPrefab;
    private GameObject[] Chunks;
 void Start () {
            addChunks();
 }

    private void addChunks()
    {
        // I keep my initializations out of my for loop to prevent memory from being reallocated with each iteration
        int i = 0;
        float nextXPosition = 0;
        Vector3 newPosition;
        GameObject chunk;
        for (i = 0; i < chunckLength; i++)
        {
            chunk = Instantiate(ChunkPrefab) as GameObject;
            newPosition = new Vector3(nextXPosition, chunk.transform.position.y, chunk.transform.position.z); 
            chunk.transform.position = newPosition;
            nextXPosition += chunk.GetComponentInChildren().size.x;
        }
    }

    void Update()
    {
       // left this function in because I may need it later.
    }
}


Item #4 - UI
Item for is where I left off. I am new to using NGUI, which seems to be favored by the Unity programmers I've talked to. That said, I like it a lot.
I set up my tree as so:
 
HeadsUp, MenuUi and TravelUi are empty game objects. The sprites of the background and the buttons are actually on the children of those GameObjects to keep them organized. I use NGUI's anchor script to anchor the buttons to each menu, and each sub menu to the HeadsUp menu. Anchoring allows one to link the positioning of one 2D object relative to another. This way if the positioning changes on the object the relative position changes to on the subservient object. In my case, I anchor the MenuUi to the HeadsUpUi, and make TravelUi a sub menu of the MenuUi, so I anchor it to that. I offset everything so it looks good in the Game preview panel. After I create the Menus, test an APK on my obnoxiously large Android phone. I notice that the menu is not bannered across the top of the screen like it is in my Game panel. I can't anchor it to the camera I don't think because the camera doesn't have dimension. I anchor it to NGUI's root UI instead. Initially, when I first worked with the UIAnchor script, I thought the Container property always had to be set. After working with it for much too long, I came to the conclusion that if one does not set a property on the Container property of the UIAnchor script, it defaults to the camera. This conclusion turns out to be wrong. I suspect it may be an issue with various resolutions on Android phones if it's not something I'm doing wrong with UIAnchor.

1 comment: