SimpleMobileControls.unitypackage

Posted by in Unity

The prefab included in this unitypackage is intended to replace the First Person Controls prefab located in the Standard Assets (Mobile) folder.

It didn’t take long after playing through a bit of MADFINGER Games’ SHADOWGUN to decide I needed to take a stab at recreating their control scheme.

SMC-Unity 3.4.2 SMC-Unity 3.5 ProjectFile-built with Unity3.4.2

First, the SimpleMobileControls class which is basically a modified FirstPersonControls script to use the MobileJoystick class and remove jumping and tilt fallback controls.

using UnityEngine;
using System.Collections;

public class SimpleMobileControls : MonoBehaviour
{
	public MobileJoystick moveTouchPad;
	public Transform cameraPivot;
	float forwardSpeed = 3;
	float backwardSpeed = 2;
	float sidestepSpeed = 2;
	float inAirMultiplier = 0.25f;
	Vector2 rotationSpeed = new Vector2 (85, 33);
	public Transform thisTransform;
	private CharacterController character;
	private Vector3 cameraVelocity;
	private Vector3 velocity;
	private float idleTime = 1f;
	private float lastInputReceived;
	private bool idle = true;
	public delegate void SimpleMobileControlsHandler (bool idle);
	public static event SimpleMobileControlsHandler onCharacterIdle;

	void Start ()
	{
		thisTransform = GetComponent (typeof(Transform)) as Transform;
		character = GetComponent (typeof(CharacterController)) as CharacterController;
	}
	void Update ()
	{
		Vector3 movement = thisTransform.TransformDirection (moveTouchPad.position.x, 0.0f, moveTouchPad.position.y);
		movement.y = 0.0f;
		movement.Normalize ();
		Vector2 absJoyPos = new Vector2 (Mathf.Abs (moveTouchPad.position.x), Mathf.Abs (moveTouchPad.position.y));
		if (absJoyPos.y > absJoyPos.x) {
			if (moveTouchPad.position.y > 0)
				movement *= forwardSpeed * absJoyPos.y;
			else
				movement *= backwardSpeed * absJoyPos.y;
		} else
			movement *= sidestepSpeed * absJoyPos.x;
		if (!character.isGrounded) {
			velocity.y += Physics.gravity.y * Time.deltaTime;
			movement.x *= inAirMultiplier;
			movement.z *= inAirMultiplier;
		}
		movement += velocity;
		movement += Physics.gravity;
		movement *= Time.deltaTime;
		//MOVE!
		character.Move (movement);

		if (character.isGrounded)
			velocity = Vector3.zero;

		//ROTATE!
		if (character.isGrounded) {
			Vector2 camRotation = Vector2.zero;
			camRotation = moveTouchPad.rotation;
			camRotation.x *= rotationSpeed.x;
			camRotation.y *= rotationSpeed.y;
			camRotation *= Time.deltaTime;
			//move character left and right
			thisTransform.Rotate (0, camRotation.x, 0, Space.World);
			//move camera up and down
			cameraPivot.Rotate (-camRotation.y, 0, 0);
		}
		//when walking around, try to center the camera back to forward position
		if (moveTouchPad.position.sqrMagnitude > 0.0f) {
			cameraPivot.rotation = Quaternion.Slerp (cameraPivot.rotation, thisTransform.rotation, Time.deltaTime);
		}
		//fires character idle events (I use this to hide certain UI elements while walking around)
		if (moveTouchPad.position.sqrMagnitude > 0.0f || moveTouchPad.rotation.sqrMagnitude > 0.0f) {
			lastInputReceived = Time.time;
			if (idle) {
				idle = false;
				if (onCharacterIdle != null)
					onCharacterIdle (idle);
			}
		}
		if (Time.time > lastInputReceived + idleTime) {
			if (!idle) {
				idle = true;
				if (onCharacterIdle != null)
					onCharacterIdle (idle);
			}
		}
	}
}

A modified joystick class using two GUITextures, one for the background and one for the joystick nub.

Requires iTween

using UnityEngine;
using System.Collections;

public class MobileJoystick : MonoBehaviour {

	public GameObject movementbg;
	public GameObject movementjoystick;
	public GameObject rotationbg;
	public GameObject rotationjoystick;
	private Color activeColor;
	private float colorTweenTime = 0.125f;
	private Rect validMovementStartingArea;
	private Rect validRotationStartingArea;
	private float tapTolerance = 0.1f;
	private float touchedMovementFingerDownAt;
	private float touchedRotationFingerDownAt;
	private bool validMovementClick = false;
	private bool validRotationClick = false;
	private bool movementStickShowing = false;
	private bool rotationStickShowing = false;
	private float max = 0.1f;
	public Vector2 position;
	public Vector2 rotation;
	private int movementFingerId = -1;
	private int rotationFingerId = -1;

	void Start ()
	{
		activeColor = new Color(0.5f,0.5f,0.5f,0.25f);
		validMovementStartingArea = new Rect (50, 50, Screen.width/2 - 100, Screen.height - 100);
		validRotationStartingArea = new Rect (Screen.width / 2 + 50, 50, Screen.width / 2 - 100, Screen.height - 100);
	}
	void Update ()
	{
		if (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.Android) {
			if (Input.touchCount == 0) {
				HideMovementJoystick();
				HideRotationJoystick();
			}
			foreach (Touch touch in Input.touches)
			{
				if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled) {
					if (movementFingerId == touch.fingerId) {
						movementFingerId = -1;
						position = Vector2.zero;
						HideMovementJoystick();
					}
					if (rotationFingerId == touch.fingerId) {
						rotationFingerId = -1;
						rotation = Vector2.zero;
						HideRotationJoystick ();
					}
				}
				if (movementFingerId == touch.fingerId && Time.time > touchedMovementFingerDownAt + tapTolerance) {
					if (movementStickShowing)
						UpdateMovement(touch.position);
					else
						ShowMovementJoystick();
				}
				if (rotationFingerId == touch.fingerId && Time.time > touchedRotationFingerDownAt + tapTolerance) {
					if (rotationStickShowing)
						UpdateRotation (touch.position);
					else
						ShowRotationJoystick ();
				}
				if (touch.phase == TouchPhase.Began) {
					if ( movementFingerId == -1 && validMovementStartingArea.Contains (touch.position)) {
						SetMovementAnchor (touch.position);
						touchedMovementFingerDownAt = Time.time;
						movementFingerId = touch.fingerId;
					}
					if ( rotationFingerId == -1 && validRotationStartingArea.Contains (touch.position)) {
						SetRotationAnchor (touch.position);
						touchedRotationFingerDownAt = Time.time;
						rotationFingerId = touch.fingerId;
					}
				}
			}
		}
		else {
			if (Input.GetButtonDown ("Fire1")) {
				if (validMovementStartingArea.Contains (Input.mousePosition)) {
					validMovementClick = true;
					validRotationClick = false;
					SetMovementAnchor(Input.mousePosition);
					touchedMovementFingerDownAt = Time.time;
				} else { validMovementClick = false; }
				if (validRotationStartingArea.Contains (Input.mousePosition)) {
					validRotationClick = true;
					validMovementClick = false;
					SetRotationAnchor (Input.mousePosition);
					touchedRotationFingerDownAt = Time.time;
				} else { validRotationClick = false; }
			}
			if (Input.GetButton ("Fire1") ) {
				if (validMovementClick && Time.time > touchedMovementFingerDownAt + tapTolerance) {
					if (movementStickShowing)
						UpdateMovement (Input.mousePosition);
					else
						ShowMovementJoystick ();
				}
				if (validRotationClick && Time.time > touchedRotationFingerDownAt + tapTolerance) {
					if (rotationStickShowing)
						UpdateRotation (Input.mousePosition);
					else
						ShowRotationJoystick ();
				}
			}
			if (Input.GetButtonUp ("Fire1")) {
				rotation = Vector2.zero;
				position = Vector2.zero;
				HideMovementJoystick();
				HideRotationJoystick();
			}
		}
	}
	void UpdateRotation (Vector3 newRot) {
		rotationjoystick.transform.position = new Vector3 (newRot.x / Screen.width, newRot.y / Screen.height, 0);
		rotationjoystick.transform.position = LimitJoystickPosition (rotationbg.transform.position, rotationjoystick.transform.position);
		rotation = (rotationjoystick.transform.position - rotationbg.transform.position) * 10;
	}
	void UpdateMovement(Vector3 newPos) {
		movementjoystick.transform.position = new Vector3 (newPos.x / Screen.width, newPos.y / Screen.height, 0);
		movementjoystick.transform.position = LimitJoystickPosition(movementbg.transform.position, movementjoystick.transform.position);
		position = (movementjoystick.transform.position - movementbg.transform.position) * 10;
	}
	Vector3 LimitJoystickPosition (Vector3 anchor, Vector3 downPosition)
	{
		if (Vector3.Distance (anchor, downPosition) > max) {
			Vector3 dir = downPosition - anchor;
			dir.Normalize ();
			return (dir * max) + anchor;
		}
		return downPosition;
	}
	public void Disable () {

	}
	void SetRotationAnchor(Vector3 newRot) {
		rotationbg.transform.position = rotationjoystick.transform.position = new Vector3 (newRot.x / Screen.width, newRot.y / Screen.height, 0);
		rotation = Vector2.zero;
	}
	void SetMovementAnchor(Vector3 newPos) {
		movementbg.transform.position = movementjoystick.transform.position = new Vector3 (newPos.x / Screen.width, newPos.y / Screen.height, 0);
		position = Vector2.zero;
	}
	void ShowMovementJoystick() {
		iTween.ColorTo (movementjoystick, activeColor, colorTweenTime);
		iTween.ColorTo (movementbg, activeColor, colorTweenTime);
		Invoke ("SetMovementStickShowing", colorTweenTime);
	}
	void HideMovementJoystick() {
		iTween.ColorTo (movementjoystick, Color.clear, colorTweenTime);
		iTween.ColorTo (movementbg, Color.clear, colorTweenTime);
		Invoke("SetMovementStickHidden",colorTweenTime);
	}
	void ShowRotationJoystick ()
	{
		iTween.ColorTo (rotationjoystick, activeColor, colorTweenTime);
		iTween.ColorTo (rotationbg, activeColor, colorTweenTime);
		Invoke ("SetRotationStickShowing", colorTweenTime);
	}
	void HideRotationJoystick ()
	{
		iTween.ColorTo (rotationjoystick, Color.clear, colorTweenTime);
		iTween.ColorTo (rotationbg, Color.clear, colorTweenTime);
		Invoke ("SetRotationStickHidden", colorTweenTime);
	}
	void SetMovementStickShowing() {
		movementStickShowing = true;
	}
	void SetMovementStickHidden() {
		movementStickShowing = false;
	}
	void SetRotationStickShowing () {
		rotationStickShowing = true;
	}
	void SetRotationStickHidden () {
		rotationStickShowing = false;
	}
}


Share

Disclaimer

These posts contain my own opinions and work and do not necessarily reflect the opinions of my employer or any other organization with which I have been affiliated. Feel free to use and share the content of this post.
Read More

SimpleControls.cs – First Person Keyboard/Mouse Control

Posted by in Unity

This script is intended to replace all of the scripts attached to the standard first person controller (FPSInputController, CharacterMotor, both MouseLooks)

The first person controller in Unity’s standard assets isn’t bad for getting a shooter prototype done, but I needed a controller with simple controls for an audience that might not have much experience with gaming. Watching someone try to navigate with a mouse look and WASD movement control scheme for the first time is usually painful.

This controller is very simple. The vertical input axis (W, S, Up Arrow, Down Arrow by default) moves the player forward and backward. The horizontal input axis (A, D, Left Arrow, Right Arrow by default) rotates the player instead of the usual strafing. This allows a player to navigate around the environment without needing the mouse to change the direction in which the player is looking. The rotation speed increases over time so it doesn’t take forever to turn around.

The biggest problem I have with a mouse look is interacting with objects in a scene or GUI elements. This script alleviates that problem by freeing up the mouse to click anywhere on the screen. Additionally, clicking and dragging will move the camera around. When you begin to walk around again the camera straightens back out to the forward position.

Attach this script to the GameObject on which your CharacterController is attached. You’ll have to assign the CharacterController and cameraPivot in the inspector. I still need to add a Y-axis rotation constraint.

using UnityEngine;
using System.Collections;

public class SimpleControls : MonoBehaviour {
	public Transform cameraPivot;
	public CharacterController character;
	private Vector3 initialPosition;
	private Vector3 currentPosition = Vector3.zero;
	private float speed = 2.5f;
	private Vector2 rotationSpeed = new Vector2 (75, 50);
	private float currentRotation = 75f;
	private float rotationLimitX = 150f;
	private bool changingView = false;

	void Update ()
	{
		if (Mathf.Abs (Input.GetAxis ("Horizontal")) < 0.1f)
			currentRotation = rotationSpeed.x;
		else
			currentRotation += 1f;
		if (currentRotation > rotationLimitX) currentRotation = rotationLimitX;
		character.SimpleMove(character.transform.forward * speed * Input.GetAxis("Vertical"));
		transform.Rotate(0, currentRotation * Time.deltaTime * Input.GetAxis ("Horizontal"), 0, Space.World);
		if (Input.GetMouseButtonDown(0)) {
			changingView = true;
			initialPosition = Input.mousePosition;
		}
		if (Input.GetMouseButtonUp(0))
			changingView = false;
		if (changingView) {
			currentPosition.x = Mathf.Clamp ((Input.mousePosition.x - initialPosition.x) / 100, -3, 3);
			currentPosition.y = Mathf.Clamp ((Input.mousePosition.y - initialPosition.y) / 100, -2, 2);
			currentPosition.x *= rotationSpeed.x;
			currentPosition.y *= rotationSpeed.y;
			currentPosition *= Time.deltaTime;
			transform.Rotate (0, currentPosition.x, 0, Space.World);
			cameraPivot.Rotate (-currentPosition.y, 0, 0);
		}
		if (Mathf.Abs( Input.GetAxis("Vertical")) > 0.25f )
			cameraPivot.rotation = Quaternion.Slerp (cameraPivot.rotation, transform.rotation, Time.deltaTime);
	}
}


Share

Disclaimer

These posts contain my own opinions and work and do not necessarily reflect the opinions of my employer or any other organization with which I have been affiliated. Feel free to use and share the content of this post.
Read More

CollisionAlert.cs with an Example

Posted by in Unity

I created this class to attach to the player GameObject that contains a CharacterController. Its only purpose is to pass OnCollision and OnTrigger messages to any listeners. For more info about the events and delegates, check out Prime31′s tutorial on this subject. Actually, watch all of his tutorials!

There isn’t much happening at all here, but I use this method to detect when my player enters specifically named triggers in a scene.

using UnityEngine;
using System.Collections;

public enum CollisionAlertType
{
	enter,
	exit,
	stay
}
public class CollisionAlert : MonoBehaviour {

	public delegate void CollisionAlertEventHandler (string colName, CollisionAlertType alertType);
	public static event CollisionAlertEventHandler onCollisionAlert;

	void OnTriggerEnter (Collider other)
	{
		if (onCollisionAlert != null)
			onCollisionAlert(other.gameObject.name,CollisionAlertType.enter);
	}
	void OnTriggerExit (Collider other)
	{
		if (onCollisionAlert != null)
			onCollisionAlert (other.gameObject.name, CollisionAlertType.exit);
	}
	void OnTriggerStay (Collider other)
	{
		if (onCollisionAlert != null)
			onCollisionAlert (other.gameObject.name, CollisionAlertType.stay);
	}
	void OnCollisionEnter (Collision col)
	{
		if (onCollisionAlert != null)
			onCollisionAlert (col.gameObject.name, CollisionAlertType.enter);
	}
	void OnCollisionExit (Collision col)
	{
		if (onCollisionAlert != null)
			onCollisionAlert (col.gameObject.name, CollisionAlertType.exit);
	}
	void OnCollisionStay (Collision col)
	{
		if (onCollisionAlert != null)
			onCollisionAlert (col.gameObject.name, CollisionAlertType.stay);
	}
}

Here’s an example of waiting for my player to enter a trigger in my scene. You would need to have a GameObject named ‘ApproachTheBench’ with a collider or trigger attached for this to work.

using UnityEngine;
using System.Collections;

public class Example : MonoBehaviour {

	private bool waitingOnTriggerCollision = false;
	private CollisionAlertType alertType = CollisionAlertType.enter;
	private string goTriggerName = "";

	// Use this for initialization
	void Start ()
	{
		CollisionAlert.onCollisionAlert += onCollisionAlert;
		StartCoroutine(WaitForTriggerCollision());
	}

	IEnumerator WaitForTriggerCollision ()
	{
		goTriggerName = "ApproachTheBench";
		alertType = CollisionAlertType.enter;
		waitingOnTriggerCollision = true;
		//this stops the coroutine from ending until the trigger is entered
		while (waitingOnTriggerCollision) {
			yield return new WaitForEndOfFrame ();
		}
		Debug.Log("You entered the trigger!");
	}

	void onCollisionAlert (string colName, CollisionAlertType currentAlertType)
	{
		if (colName == goTriggerName && alertType == currentAlertType)
		{
			waitingOnTriggerCollision = false;
			goTriggerName = "none";
		}
	}
}


Share

Disclaimer

These posts contain my own opinions and work and do not necessarily reflect the opinions of my employer or any other organization with which I have been affiliated. Feel free to use and share the content of this post.
Read More

OnTouchOrClick.cs – Simple Input

Posted by in Unity

Nearly every project on which I work targets multiple platforms. My typical workflow involves working with an iOS build target and testing functionality whenever I can in the editor. This is crucial when I don’t have a device available to use the Unity Remote app or create a test build. Since the corresponding mouse code is most likely needed anyway, I typically combine both touch and click detection into one script.

The following code shows an implementation of testing whether or not a touch or click has hit something in your scene. Basically it shoots a ray from the camera out into the scene toward the spot on the screen where you touch or mouse is positioned. I’ve put in a preprocessor check to only use touch portion if you’re on a mobile build, otherwise the mouse click will be used. Either way, the same CheckHit function will be called with the appropriate ray passed.

Let’s assume either of the following conditions is true:

  • A camera exists in the scene and this script will be attached to it OR
  • The camera is tagged as ‘MainCamera’ and this script is attached to another GameObject

If our Raycast hits something with a collider or trigger on it, we will execute the code in our ‘if (Physics.Raycast… ‘ conditional statement. In this example I’m performing three different actions if we hit something.

The first thing we’ll do is a fire an event. This is a clean way to allow other objects to listen for these touches or clicks as needed. In this scenario, I’m just passing along the name of object with which our ray collided to any listeners of that event. The other script can figure out what to do with this info. For more info about events and delegates, I highly recommend Prime31′s tutorial on the subject.

The next line of code just shows an instantiation of a particle prefab at the point where our ray collided with another object. By checking the name or tag of the collider, you could easily switch to a different prefab depending on the surface your touching or clicking (think about kicking up dust when touching the ground or spurting blood when touching an enemy).

The final action is probably the weakest but it makes prototyping easier and faster. If any of the components on the collider’s GameObject have a ‘Touched’ method, it will be called. Very simple. I tend to end up with a handful of throwaway scripts with a few variables and a ‘Touched’ method to accomplish simple behaviors when I’m starting on a project. I’ll share some examples in future updates. One of the most helpful is an ObjectRotate class that I throw on objects that I need to toggle between two positions, primarily for doors.

using UnityEngine;
using System.Collections;

public class OnTouchOrClick : MonoBehaviour
{
	public delegate void TouchClickHandler (string touchedObj);
	public static event TouchClickHandler onTouchedGameObjectName;

	//in the inspector, drag over a particle prefab with OneShot and AutoDestruct enabled
	public GameObject firework;
	//how far in Unity units our ray will shoot
	public float range = 25f;
	//camera component from which our ray will shoot
	private Camera cam;

	void Start () {
		//if there isn't a camera attached to this GameObject, use the main camera
		if (!camera) { cam = Camera.main; }
	}
	void Update () {
		//sanity check, return if no camera found
		if (!cam)
			return;
		#if (UNITY_IPHONE || UNITY_ANDROID)
		foreach (Touch touch in Input.touches) {
			if (touch.phase == TouchPhase.Began) {
				Ray ray = cam.ScreenPointToRay (touch.position);
				CheckHit(ray);
			}
		}
		#else
		if (Input.GetButtonDown ("Fire1")) {
			Ray ray = cam.ScreenPointToRay (Input.mousePosition);
			CheckHit (ray);
		}
		#endif
	}
	void CheckHit (Ray ray) {
		//the Raycast() call will return true if you click or touch an object
		//with a collider attached within the specified range.
		RaycastHit hit;
		//if our touch or click hits anything, do something
		if (Physics.Raycast (ray, out hit, range)) {
			//fire an event for any listeners to onTouchedGameObjectName
			if (onTouchedGameObjectName != null)
				onTouchedGameObjectName (hit.collider.gameObject.name);

			//create a firework at the point of collision
			Instantiate (firework, hit.point, Quaternion.identity);

			//inefficient method I lazily use frequently, if any component on the colliding GO
			//has a method named 'Touched', it will be called whenever clicked or touched
			//a common use case is toggling 3d objects between two rotations (opening doors, etc)
			hit.collider.gameObject.SendMessage ("Touched", SendMessageOptions.DontRequireReceiver);
		}
	}
}
Share

Disclaimer

These posts contain my own opinions and work and do not necessarily reflect the opinions of my employer or any other organization with which I have been affiliated. Feel free to use and share the content of this post.
Read More