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; } }
No comments:
Post a Comment