Showing posts with label Unity. Show all posts
Showing posts with label Unity. Show all posts

Wednesday, July 17, 2013

Unity: Detecting a Cursor over an Object

Many indie games feature a single avatar moving through a world. (If I've already lost you, Mario is an avatar, because you control his murderous waltz through an enemy-occupied Mushroom Kingdom.)

Having a single avatar makes things easy for both implementation and comprehension: it's easy to make because you just test to see what's close to the player, and it's easy to understand because the player knows who he/she is on screen. "I am that plumber," they think, and they push a button, and since it's easy to check if a button is pressed, the game responds instantly, and all is well.

But! Some games don't have a single avatar, and that's when you need to use the mouse (or your finger) to select an object, or point to a position on the terrain. These games are almost always strategic or tactical in nature, like StarCraft or SimCity, and here we're going to see how to do that in Unity.

In this example, it waits for a mouse click, then sends a ray from the cursor (via the camera) into the game world. If it strikes this object (and it has to have a collider for this to work), you can then run special commands.

 void Update ()
 {
  // This will only fire the first frame after the button was pressed
  if (Input.GetButtonDown("Fire1"))
  {
   // Fire a ray from the camera into the world
   Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
   RaycastHit hit;
   // Test it out to 1000 units. If it hits something, continue.
   if (Physics.Raycast (ray, out hit, 1000f))
   {
    // If it hit THIS object, do something.
    if (hit.collider.gameObject.Equals(this.gameObject))
    {
     Debug.Log("You tapped a " + gameObject.name);
    }
   }
  }
 }

If you're curious, here's how you'd do it on an Android or iOS device. Since there's no debug log that's visible, we'll just destroy the game object when it's tapped.

void Update () 
 {
  RaycastHit hit;
  foreach (Touch touch in Input.touches)
  {
   Ray ray = Camera.main.ScreenPointToRay(touch.position);
   if (Physics.Raycast (ray, out hit, 1000f))
   {
    if (hit.collider.gameObject.Equals(this.gameObject))
    {
     if (touch.phase == TouchPhase.Began)
     {
      Destroy (gameObject);
     }
    }
   }
  }
 }

This technique fine for most projects, and I used this code on every object I wanted the cursor to be "aware" of. However, once I needed a system where I always knew where the cursor was, I realized it didn't scale: a raycast would be calculated every frame, by every object. Furthermore, there's no clean way to handle multiple camera layers, such as a Menu Camera and Game Camera, and that can lead to some "fun" bugs and user frustration.

Now, I have a single cursor control object. Each frame, it sends out a ray, and brings back any GameObject it hit. There, your options split: you can Broadcast a generic command to each script on the object, or if you've standardized everything, you can get a script component and run the command directly. I'm using the Broadcast method, because *shrug*. Here is the (simplified) code I'm using for Chess Heroes.
 Camera menuCamera;
 Camera sceneCamera;
 GameObject hoverObject;
 GameObject menuObject;
 GameObject sceneObject;
 
 void Start () 
 {
  menuCamera = GameObject.Find("MenuCamera").GetComponent();
  sceneCamera = GameObject.Find("SceneCamera").GetComponent();
 }
 
 void Update ()
 {
  // First check if the cursor is over a menu object
  menuObject = GetObjectUnderCursorUsingCamera(menuCamera);
  
  // If it's not null, continue
  if (menuObject)
  {
   // If it's a different object, run MouseOver on it
   if (hoverObject != menuObject)
   {
    MouseOver(menuObject);
   }
  }
  // Otherwise, check the scene
  else
  {
   sceneObject = GetObjectUnderCursorUsingCamera(sceneCamera);
   
   // If it's not null, continue
   if (sceneObject)
   {
    // If it's a different object, run MouseOver on it
    if (hoverObject != sceneObject)
    {
     MouseOver(sceneObject);
    }
   }
   else
   {
    // Nothing of interest in the scene or menu, so run MouseAway
    MouseAway();
   }
  }

  // If the button has been tapped and there's an active object under the cursor, send it a message
  if (Input.GetButtonDown("Fire1") && hoverObject != null)
  {
   hoverObject.BroadcastMessage("OnTap", SendMessageOptions.DontRequireReceiver);
  }
 }
 
 GameObject GetObjectUnderCursorUsingCamera (Camera camera)
 {
  Ray ray = camera.ScreenPointToRay(Input.mousePosition);
  RaycastHit hit;
  if (Physics.Raycast (ray, out hit, 1000f))
  {
   return hit.collider.gameObject;
  }
  return null;
 }
 
 void MouseOver (GameObject targetObject)
 {
  if (hoverObject != null) 
  {
   hoverObject.BroadcastMessage("OnMouseAway", SendMessageOptions.DontRequireReceiver);
  }
  targetObject.BroadcastMessage("OnMouseOver", SendMessageOptions.DontRequireReceiver);
  hoverObject = targetObject;
 }
 
 void MouseAway ()
 {
  if (hoverObject != null)
  {
   hoverObject.BroadcastMessage("OnMouseAway", SendMessageOptions.DontRequireReceiver);
   hoverObject = null;
  }
 }

Wednesday, July 10, 2013

Unity: Creating dynamic lines

I learned how to program using Java, specifically a development interface called Processing, which I heartily recommend to anyone thinking about trying it. The work I did was in 2D, and as I grappled with vector math and time slices, I found it very handy to draw a line from an object I was moving to the point it was moving to. This illuminated what the object was thinking, something traditionally difficult to do during runtime, and it was as easy as saying "draw a line from this point to this point."

When I started using Unity, I didn't have this option. It was impossible to say "draw a line from this point to this point," at least in such simple terms, and as I grappled with vector math (again, but in three dimensions!) I felt out of touch and frustrated when I was forced to interpret what was going on, rather than knowing at a glance.

Lucky for us, I found a solution, and I've been using it ever since. Although my grasp of vectors is much tighter these days, it's always a treat to write artificial intelligence that, say, takes a mob through the floor and into the void beneath, rather than to a point of interest. In those cases, where the object is making its own decisions, it's good to know what it's "looking at." And that's when the Magic Lines come into play.

Okay, it's actually called Line Renderer, but where's the fun in that?

A Line Renderer component can be used for motion trails, telephone wires, hanging vines, and a whole lot of other fun visual tricks, but here we're just going to draw a line in space, from one point to another. Here's the code:
LineRenderer magicLine;

void DrawLineToPosition (Vector3 targetPosition)
{
 // If we haven't created the LineRenderer yet, do so
 if (!magicLine)
 {
  magicLine = gameObject.AddComponent;
  // Give it only two points: one for "here", one for "there
  magicLine.SetVertexCount(2); 
  // The "line" is actually a polygon, and here we can make it taper a bit at the end
  // In effect, it points at the target position
  magicLine.SetWidth(1, 0.1);
 }
    magicLine.SetPosition(0, transform.position);
    magicLine.SetPosition(1, targetPosition);
}

Wednesday, June 26, 2013

Unity: Creating GUI Labels Over 3D Objects

The GUI commands built in to Unity are a fast, convenient way to present information on screen. One way to take full advantage of this is creating GUI elements that line up over 3D objects. In a standard FPS action game, for example, it could show you a monster's health and any effects that are currently active. Add a simple toggle (I prefer the question mark), and you have an easy way to turn on debug information in-game.

Here's a screenshot from Chess Heroes that shows it in action. What's shown is the type of piece and the ID of the player that owns it.


Here's the code:
bool showDebugInfoGUI;
float boxW = 150f;
float boxH = 20f;

void OnGUI ()
{
 if (showDebugInfoGUI)
 {
  // As an example, this will show the name of each untagged gameobject over itself
  // But in general, this is not a good idea!
  foreach (GameObject targetObject in GameObject.FindGameObjectsWithTag("Untagged"))
  {
   ShowObjectNameInGUIForObject (targetObject);
  }
 }
}

void ShowObjectNameInGUIForObject (GameObject targetObject)
{
 // Find the 2D position of the object using the main camera
 Vector2 boxPosition = Camera.main.WorldToScreenPoint(targetObject.transform.position);

 // "Flip" it into screen coordinates
 boxPosition.y = Screen.height - boxPosition.y;

 // Center the label over the coordinates
 boxPosition.x -= boxW * 0.5f;
 boxPosition.y -= boxH * 0.5f;

 // Draw the box label
 GUI.Box(new Rect(boxPosition.x, boxPosition.y, boxW, boxH), targetObject.name);
}

void Update ()
{
 // Toggle the labels using the forward slash key (the one with the question mark)
 // Note that this can't be put in OnGUI, as OnGUI runs twice each frame
 if (Input.GetKeyDown(KeyCode.Slash)) showDebugInfoGUI = !showDebugInfoGUI;
}

Updated September 10, 2013

Wednesday, June 19, 2013

Unity: Making Meshes Fade Away

To make a model disappear in Unity over time, you lower the alpha of its materials until it reaches zero. ("alpha" is industry speak for transparency. I don't know why.)

The default material does not have an alpha channel, so set up a material that uses the Transparent/Diffuse shader.


For performance reasons, don't use a material with a Transparent/Diffuse shader unless you are fading it out. The GPU must make additional calculations for each pixel with alpha -- even if it's opaque!

Once the mesh has the right material, you can fade it out with this code:

float fadeTimerMax = 1;
float fadeTimer = 1;

void Update () 
{
 // Decrement the timer
 fadeTimer -= Time.deltaTime;

 // If the timer is less than or equal to zero, destroy the object
 if (fadeTimer <= 0)
 {
  Destroy (gameObject);
 }
 // Otherwise, adjust transparency
 else
 {
  // Get a normalized percentage value based on the fade timer
  float percentOpaque = fadeTimer/fadeTimerMax; 

  // Update the alpha of each renderer material on the object
  foreach (Material mat in renderer.materials)
  {
   Color color = mat.color;
   color.a = percentOpaque;
   mat.color = color;
  }
 }
}