Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combine camera pan to with touch controls in Unity

Tags:

c#

unity3d

I've been asked to look into creating a simple iterative application with Unity. This application has 2 major functions regarding the camera.

  1. LERP-ing the camera to focus on a target object.
  2. Once moved relinquish control to the user and allow the user to rotate and zoom around the object.

I'm new to this but I've managed to create two scripts that achieve these goals in isolation. Now I'm struggling to fit them together.

I'll start with the relevant code for user interaction.

First, I use TouchKit to set the delta values on each frame this is in Start.

// set the delta on each frame for horizontal and vertical rotation
var oneTouch = new TKPanRecognizer();
oneTouch.gestureRecognizedEvent += (r) =>
{
    HorizontalDelta += r.deltaTranslation.x * rotateSpeed * Time.deltaTime;
    VerticalDelta -= r.deltaTranslation.y * rotateSpeed * Time.deltaTime;
};

// do the same for pinch
var pinch = new TKPinchRecognizer();
pinch.gestureRecognizedEvent += (r) =>
{
    rotateDistance -= r.deltaScale * 200.0f * Time.deltaTime;
};

TouchKit.addGestureRecognizer(oneTouch);
TouchKit.addGestureRecognizer(pinch);

And on Update:

VerticalDelta = Mathf.Clamp(VerticalDelta, verticalPivotMin, verticalPivotMax);

var direction = GetDirection(HorizontalDelta, VerticalDelta);

var currentTarget = targetsSwitched ? target2 : target;

transform.position = currentTarget.position - direction * rotateDistance;
transform.LookAt(currentTarget.position);

// ...

private Vector3 GetDirection(float x, float y)
{
    Quaternion q = Quaternion.Euler(y, x, 0);
    return q * Vector3.forward;
}

This works beautifully and does exactly what I want. The problem comes when I try to integrate this code with my camera moving script. This shows where I want to add the Update code

void Update ()
{
    if (currentlyMoving)
    {
        FocusTarget(currentTarget);
    }
    else
    {
        // accept user input if not moving
        if (Input.GetKeyDown(KeyCode.Space))
        {
            SetMoveToTarget(mainTargetObject);
        }

        if (Input.GetKeyDown(KeyCode.Q))
        {
            SetMoveToTarget(subTargetObject1);
        }

        if (Input.GetKeyDown(KeyCode.E))
        {
            SetMoveToTarget(subTargetObject2);
        }
    }
}

These are the functions that actually move the camera:

void SetMoveToTarget(GameObject target)
{
    if (currentlyMoving == false)
    {
        currentlyMoving = true;
        fromRotation = currentTarget.transform.rotation;
        currentTarget = target;
        toRotation = currentTarget.transform.rotation;

        timeStartedLerping = Time.time;
    }
}

void FocusTarget(GameObject target)
{

    float timeSinceStarted = Time.time - timeStartedLerping;
    float percentageComplete = timeSinceStarted / (lerpSpeed);

    transform.position = Vector3.MoveTowards(transform.position, target.transform.position, moveSpeed * Time.deltaTime);
    transform.rotation = Quaternion.Lerp(fromRotation, toRotation, Mathf.Pow(percentageComplete, (float)1.2));

    if (Vector3.Distance(transform.position, target.transform.position) < 0.1 && percentageComplete > 0.99)
    {
        transform.position = target.transform.position;
        transform.rotation = target.transform.rotation;
        currentlyMoving = false;
    }
}

I think what I need to do (and I may be wrong on this) is set rotateDistance to be the difference between the currentTarget in the second script and currentTarget in the first script.

Thank you in advance, it's quite a tricky one for me.

like image 427
Richard Vanbergen Avatar asked Oct 31 '22 06:10

Richard Vanbergen


1 Answers

In the end I had to change how I was dealing with moving the camera in the first place. The problem was that moving to a set game object worked and was easy to set-up but it's much easier to integrate with the look script if you actually calculate when the next position of the camera should be and move to that.

I'm going to paste the working product here for posterity, it's missing some stuff from the end game but the camera is working.

using UnityEngine;
using UnityEngine.UI;

public class NewCamera : MonoBehaviour {

    // targets
    public GameObject target;
    public GameObject target2;

    // settings
    public float RotateSpeed = 50.0f;
    public float RotateDistance = 3;
    public float CameraMoveSpeed = 20f;

    public float VerticalPivotMin = 5;
    public float VerticalPivotMax = 65;

    public float MinZoomIn = 1.7f;
    public float MaxZoomIn = 4f;

    // private
    private GameObject lookTarget;

    private bool currentlyMoving = false;
    private Vector3 targetVector;
    private float timeStartedLerping;

    private float HorizontalDelta;
    private float VerticalDelta;

    void Start ()
    {
        lookTarget = target;

        var oneTouch = new TKPanRecognizer();
        oneTouch.gestureRecognizedEvent += (r) =>
        {
            if (currentlyMoving == false)
            {
                HorizontalDelta += r.deltaTranslation.x * RotateSpeed * Time.deltaTime;
                VerticalDelta -= r.deltaTranslation.y * RotateSpeed * Time.deltaTime;

                VerticalDelta = Mathf.Clamp(VerticalDelta, VerticalPivotMin, VerticalPivotMax);
            }
        };

        var pinch = new TKPinchRecognizer();
        pinch.gestureRecognizedEvent += (r) =>
        {
            if (currentlyMoving == false)
            {
                RotateDistance -= r.deltaScale * 200.0f * Time.deltaTime;
                RotateDistance = Mathf.Clamp(RotateDistance, MinZoomIn, MaxZoomIn);
            }
        };

        TouchKit.addGestureRecognizer(oneTouch);
        TouchKit.addGestureRecognizer(pinch);
    }

    void Update ()
    {
        if (currentlyMoving)
        {
            FocusTarget();
        }
        else
        {
            var direction = GetDirection(HorizontalDelta, VerticalDelta);

            transform.position = lookTarget.transform.position - direction * RotateDistance;
            transform.LookAt(lookTarget.transform.position);
        }
    }

    public void OnButtonClick()
    {
        var currentTarget = target.GetInstanceID() == lookTarget.GetInstanceID() ? target : target2;
        var nextTarget = currentTarget.GetInstanceID() == target.GetInstanceID() ? target2 : target;

        var cameraPosition = transform.position;
        var targetPosition = currentTarget.transform.position;

        SetMoveToTarget(nextTarget, cameraPosition - targetPosition);
    }

    void SetMoveToTarget(GameObject moveTo, Vector3 offset)
    {
        currentlyMoving = true;
        targetVector = moveTo.transform.position + offset;
        lookTarget = moveTo;
    }

    void FocusTarget()
    {
        transform.position = Vector3.Lerp(transform.position, targetVector, CameraMoveSpeed * Time.deltaTime);

        if (Vector3.Distance(transform.position, targetVector) < 0.01)
        {
            transform.position = targetVector;
            currentlyMoving = false;
        }
    }

    private Vector3 GetDirection(float x, float y)
    {
        Quaternion q = Quaternion.Euler(y, x, 0);
        return q * Vector3.forward;
    }
}

If you want to use it for any reason, just add this script to a camera and set the two targets. I have to make a more robust version where you can add new targets easily but this will do for now.

Happy hacking!

like image 170
Richard Vanbergen Avatar answered Nov 13 '22 05:11

Richard Vanbergen