SimpleMobileControls.unitypackage
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.
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.
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;
}
}
TweetShare
Disclaimer
SimpleControls.cs – First Person Keyboard/Mouse Control
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);
}
}
TweetShare
Disclaimer
CollisionAlert.cs with an Example
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";
}
}
}
TweetShare
Disclaimer
OnTouchOrClick.cs – Simple Input
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);
}
}
}
Tweet
Share