Unity3D - C# 360 Orbital Camera Controller (Gimbal Lock Issue) - c#

I have a stationary cube in my scene that I'm orbiting a camera around. I have my MainCamera nested under a GameObject that I'm calling 'OrbitalCamera'.
I setup the script so that a click (or tap) and drag will rotate the camera around the object in space so it feels like you're rotating the cube (ie: if I click the top of a face on the cube, and pull down, I'm rotating the X value) but you'll actually be rotating the camera.
For the most part, my script works. However, after rotating the Y so much, the camera is upside down and the X gets inverted. Here's my script:
public class OrbitalCamera : MonoBehaviour {
public bool cameraEnabled;
[SerializeField] private float touchSensitivity;
[SerializeField] private float scrollSensitivity;
[SerializeField] private float orbitDampening;
protected Transform xFormCamera;
protected Transform xFormParent;
protected Vector3 localRotation;
protected float cameraDistance;
void Start () {
cameraEnabled = true;
xFormCamera = transform;
xFormParent = transform.parent;
cameraDistance = transform.position.z * -1;
}
void LateUpdate () {
if (cameraEnabled) {
// TODO:: FIX PROBLEM WHERE WHEN CAMERA IS ROTATED TO BE UPSIDEDOWN, CONTROLS GET INVERSED
if (Input.GetMouseButton(0)) {
if (Input.GetAxis("Mouse X") != 0 || Input.GetAxis("Mouse Y") != 0) {
localRotation.x += Input.GetAxis("Mouse X") * touchSensitivity;
localRotation.y -= Input.GetAxis("Mouse Y") * touchSensitivity;
}
}
}
Quaternion qt = Quaternion.Euler(localRotation.y, localRotation.x, 0);
xFormParent.rotation = Quaternion.Lerp(xFormParent.rotation, qt, Time.deltaTime * orbitDampening);
}
}
Is there a good method to achieve this type of 360 camera? I'd like dragging from right to left to always move the camera left and dragging left to right to always move the camera right -- no matter how the camera is oriented.

Perhaps you could clamp the above/below pan at 89 degrees?
I recently helped a friend make a mouse gimbal, and found allowing freedom beyond 89 degrees was problematic and unnecessary. It seems like your application is the same, at least for one of the two planes.
In your LateUpdate() call, you could perhaps add:
localRotation.x += Input.GetAxis("Mouse X") * touchSensitivity;
localRotation.x = Clamp(localRotation.x);
Then, of course, create your clamp function, which should be fairly straight forward:
float Clamp(float val) // prevent values from ~90 - ~270
{
int lowVal = 89;
int highVal = 271;
int midVal = 180;
if (val > lowVal & val < highVal)
{
if (val > midVal) val = highVal;
else val = lowVal;
}
return val;
}
A slightly different application, but I'm sure you can see how I've set this up: I apply rotation in two steps. Step 1 - a simple Rotate() call, Step 2 - clamping some/all of the rotation:
using UnityEngine;
public class MouseGimbal : MonoBehaviour
{
[SerializeField] [Range(0,89)] float maxRotationDegrees = 10.0f; // At 90+ gimbal oddities must be dealt with.
[SerializeField] bool ClampToMaxRotationDegrees = true; // Disable for free rotation.
[SerializeField] float rotationSpeed = 10.0f;
const float fullArc = 360.0f;
const float halfArc = 180.0f;
const float nullArc = 0.0f;
void Update () { Tilt(); }
void Tilt()
{
// Apply the 'pre-clamp' rotation (rotation-Z and rotation-X from X & Y of mouse, respectively).
if (maxRotationDegrees > 0) { SimpleRotation(GetMouseInput()); }
// Clamp rotation to maxRotationDegrees.
if (ClampToMaxRotationDegrees) { ClampRotation(transform.rotation.eulerAngles); }
}
void ClampRotation(Vector3 tempEulers)
{
tempEulers.x = ClampPlane(tempEulers.x);
tempEulers.z = ClampPlane(tempEulers.z);
tempEulers.y = nullArc; // ClampPlane(tempEulers.y); // *See GIST note below...
transform.rotation = Quaternion.Euler(tempEulers);
///Debug.Log(tempEulers);
}
float ClampPlane(float plane)
{
if (OkayLow(plane) || OkayHigh(plane)) DoNothing(); // Plane 'in range'.
else if (BadLow(plane)) plane = Mathf.Clamp(plane, nullArc, maxRotationDegrees);
else if (BadHigh(plane)) plane = Mathf.Clamp(plane, fullArc - maxRotationDegrees, fullArc);
else Debug.LogWarning("WARN: invalid plane condition");
return plane;
}
Vector2 GetMouseInput()
{
Vector2 mouseXY;
mouseXY.x = -Input.GetAxis("Mouse X"); // MouseX -> rotZ.
mouseXY.y = Input.GetAxis("Mouse Y"); // MouseY -> rotX.
return mouseXY;
}
void SimpleRotation(Vector2 mouseXY)
{
Vector3 rotation = Vector3.zero;
rotation.x = mouseXY.y * Time.deltaTime * rotationSpeed;
rotation.z = mouseXY.x * Time.deltaTime * rotationSpeed;
transform.Rotate(rotation, Space.Self);
}
void DoNothing() { }
bool OkayHigh(float test) { return (test >= fullArc - maxRotationDegrees && test <= fullArc); }
bool OkayLow(float test) { return (test >= nullArc && test <= maxRotationDegrees); }
bool BadHigh(float test) { return (test > halfArc && !OkayHigh(test)); }
bool BadLow(float test) { return (test < halfArc && !OkayLow(test)); }
}

When I developed an orbital camera, I created 3 objects:
- x_axis (rotate with vertical mouse moviment)
- y_axis (rotate with horizontal mouse moviment)
- camera (look_at object) (translated Vector3(0,0,-10))

Related

Replicating the rotation of one game object on another game object using Quaternions

Good day everybody. I'm trying to learn more about rotations in unity and would appreciate some help. I have been bashing my head over this problem for a while now. The purpose of the script I've created is to take another game objects transform in and replicate the difference in rotations of that transform on the game object the script is attached too.
The script needs to be able to follow each independent axis rotation solely, or two of the axes, or even all three. The code below works if all three of the axes are monitored, when the x and z axis are solely monitored, when the y and z axis are solely monitored, but NOT when the x and y axis are solely monitored (The game object that's following (with the script attached)) generates a value for the z axis when trying to rotate just the x and y axis.
It becomes clear that whenever I'm trying to replicate the rotation of two of the axes and the z axis is not one of those axes I'm running into issues. I have done this with quaternions to avoid gimbal lock. I do believe there is an issue with the implementation of my w axis when creating the Quaternion (Which represents the rotation I want to apply to the Game object).
If anyone may provide some insight I'd be very grateful, I am a junior developer and have only started working with unity this year.
Please find the code below
[ExecuteInEditMode]
public class RotationSync2 : MonoBehaviour
{
[SerializeField]
public Transform ExternalTransform;
private Quaternion PreviousExternalTransform;
private Quaternion RotationDifference;
private Quaternion Temp = new Quaternion();
private Quaternion PreviousTemp;
private float xToUse;
private float yToUse;
private float zToUse;
private bool RotationComplete = false;
[HideInInspector] public bool rotateX;
[HideInInspector] public bool rotateY;
[HideInInspector] public bool rotateZ;
[HideInInspector] public bool InvertX;
[HideInInspector] public bool InvertY;
[HideInInspector] public bool InvertZ;
[HideInInspector] public float percentageOfRotationToFollowX = 1f;
[HideInInspector] public float percentageOfRotationToFollowY = 1f;
[HideInInspector] public float percentageOfRotationToFollowZ = 1f;
private float rotationToSetX;
private float rotationToSetY;
private float rotationToSetZ;
// Start is called before the first frame update
void Start()
{
PreviousExternalTransform = ExternalTransform.rotation;
}
// Update is called once per frame
void Update()
{
ClearPreviousRotationValues();
BuildQuarternion();
FindRotateDifference();
SetRotationValues();
SetRotations();
}
public void ClearPreviousRotationValues()
{
rotationToSetX = 0;
rotationToSetY = 0;
rotationToSetZ = 0;
}
private void BuildQuarternion()
{
if (ExternalTransform.rotation.x == PreviousExternalTransform.x && rotateX)
{
Temp.x = 0f;
}
else
{
Temp.x = ExternalTransform.rotation.x;
}
if (ExternalTransform.rotation.y == PreviousExternalTransform.y && rotateY)
{
Temp.y = 0f;
}
else
{
Temp.y = ExternalTransform.rotation.y;
}
if (ExternalTransform.rotation.z == PreviousExternalTransform.z && rotateZ)
{
Temp.z = 0f;
}
else
{
Temp.z = ExternalTransform.rotation.z;
}
Temp.w = ExternalTransform.rotation.w;
}
private void FindRotateDifference()
{
Quaternion a = Quaternion.identity * Quaternion.Inverse(Temp);
Quaternion b = Quaternion.identity * Quaternion.Inverse(PreviousTemp);
if (ExternalTransform.rotation != PreviousExternalTransform && PreviousTemp != null)
{
RotationDifference = b * Quaternion.Inverse(a);
RotationComplete = false;
}
PreviousTemp = Temp;
PreviousExternalTransform = ExternalTransform.rotation;
if (PreviousTemp == null)
{
return;
}
}
public void SetRotationValues()
{
if (rotateX) //Checks if this axis is being used
{
if (InvertX)
{
rotationToSetX = (RotationDifference.x * percentageOfRotationToFollowX) * -1; // set the rotation to the value of the given transform * the percentage to follow * -1 to invert it
}
else
{
rotationToSetX = RotationDifference.x * percentageOfRotationToFollowX; // set the rotation to the value of the given transform * the percentage to follow
}
}
if (rotateY)
{
if (InvertY)
{
rotationToSetY = (RotationDifference.y * percentageOfRotationToFollowY) * -1;
}
else
{
rotationToSetY = RotationDifference.y * percentageOfRotationToFollowY;
}
}
if (rotateZ)
{
if (InvertZ)
{
rotationToSetZ = (RotationDifference.z * percentageOfRotationToFollowZ) * -1;
}
else
{
rotationToSetZ = RotationDifference.z * percentageOfRotationToFollowZ;
}
}
}
public void SetRotations() //Checks which of axis' rotation the transform should follow of the given transform and applies the respective rotations.
{
if (RotationComplete == false)
{
if (rotateX && rotateY && rotateZ)
{
transform.rotation *= new Quaternion(rotationToSetX, rotationToSetY, rotationToSetZ, RotationDifference.w);
}
else if (rotateX && rotateY)
{
transform.rotation *= new Quaternion(rotationToSetX, rotationToSetY, 0f, RotationDifference.w);
}
else if (rotateX && rotateZ)
{
transform.rotation *= new Quaternion(rotationToSetX,0f, rotationToSetZ, RotationDifference.w);
}
else if (rotateY && rotateZ)
{
transform.rotation *= new Quaternion(0f, rotationToSetY, rotationToSetZ, RotationDifference.w);
}
else if (rotateX)
{
transform.rotation *= new Quaternion(rotationToSetX, 0f, 0f, RotationDifference.w);
}
else if (rotateY)
{
transform.rotation *= new Quaternion(0f, rotationToSetY, 0f, RotationDifference.w);
}
else if (rotateZ)
{
transform.rotation *= new Quaternion(0f, 0f, rotationToSetZ, RotationDifference.w);
}
RotationComplete = true;
}
}
}
I think you overcomplicated this a lot and also note that you never want to directly access/assign the individual components of a Quaternion except you are absolutely sure what you are doing!
The components x, y, z alone are not the rotation Euler axes!
I don't know if I fully understand your use case but it sounds to me like what you have/want is
There are two objects A and B
Both have different rotations when starting the app
Whenever object A is rotated you want to perform the same rotation also on object B but maintaining the original rotation difference
additionally there is the option to deactivate or invert individual axes
I think this is a use case where it is actually allowed/the only chance to go through Euler axes instead of Quaternion!
So what you actually want to do is
get the delta rotation of object A between the last and current frame in its local space for each axis individually
assign this same delta rotation(s) to object B in its local space
So I think I would do something like
public Transform ExternalTransform;
[HideInInspector] public bool rotateX;
[HideInInspector] public bool rotateY;
[HideInInspector] public bool rotateZ;
[HideInInspector] public bool InvertX;
[HideInInspector] public bool InvertY;
[HideInInspector] public bool InvertZ;
[HideInInspector] [Min(0)] public float percentageOfRotationToFollowX = 1f;
[HideInInspector] [Min(0)] public float percentageOfRotationToFollowY = 1f;
[HideInInspector] [Min(0)] public float percentageOfRotationToFollowZ = 1f;
private Vector3 lastExternalEuler;
private void Awake ()
{
lastExternalEuler = NormalizeEuler(ExternalTransform.localEulerAngles);
}
// Note: I would use LateUpdate so the object is already rotated for this frame
private void LateUpdate ()
{
var currentExternalEuler = NormalizeEuler (ExternalTransform.localEulerAngles);
var localDelta = NormalizeEulerDelta(currentExternalEuler - lastExternalEuler);
localDelta = ApplySettings(localDelta);
// And then either
transform.Rotate(localDelta);
// or if you want to do something else like smoothing later on
//transform.localRotation *= Quaternion.Euler(localDelta);
lastExternalEuler = currentExternalEuler;
}
// Map all Euler axes into the positive range between 0 and 360
private static Vector3 NormalizeEuler (Vector3 euler)
{
while(euler.x >= 360f) euler.x -= 360;
while(euler.x < 0f) euler.x += 360;
while(euler.y >= 360f) euler.y -= 360;
while(euler.y < 0f) euler.y += 360;
while(euler.z >= 360f) euler.z -= 360;
while(euler.z < 0f) euler.z += 360;
return euler;
}
// map the delta axes into range between -180 and 180
private static Vector3 NormalizeEulerDelta (Vector3 delta)
{
if(delta.x > 180) delta.x = -180 - (180 - delta.x);
if(delta.x < -180) delta.x = -(180 + delta.x);
if(delta.x > 180) delta.y = -180 - (180 - delta.y);
if(delta.y < -180) delta.y = -(180 + delta.y);
if(delta.x > 180) delta.z = -180 - (180 - delta.z);
if(delta.z < -180) delta.z = -(180 + delta.z);
return delta;
}
private static Vector3 ApplySettings (Vector3 delta)
{
delta.x = rotateX ? delta.x : 0f;
delta.y = rotateY ? delta.y : 0f;
delta.z = rotateZ ? delta.z : 0f;
delta.x *= invertX ? -1 : 1;
delta.y *= invertY ? -1 : 1;
delta.z *= invertZ ? -1 : 1;
delta.x *= percentageOfRotationToFollowX;
delta.y *= percentageOfRotationToFollowY;
delta.z *= percentageOfRotationToFollowZ;
return delta;
}
Though basically instead of having individual flags and values
rotateX
invertX
percantageX
why not rather have one single
public float multiplicatorX = 1f;
and accordingly for the other axes? ;)
You only need this single value and it can be 0 (rotateX = false) it can be negative (invertX = true) and it can be an arbitrary percentage
Note: Typed on the phone but I hope the idea gets clear

Unity Rotate Object to Change Direction

I have a fish which is swimming across the screen. When it gets within 10% of the edge of the screen, I want it to turn begin turning around until it has completely reversed and is now swimming in the opposite direction. This should be more gradual like a fish would swim. I don't want it to rotate on its own axis.
I'm not having any luck getting it to turn around. It only turns partially.
Update
Here's the fish
public class FishSwim : MonoBehaviour
{
public enum Direction { LeftToRight, RightToLeft };
public Direction moveDirection;
[SerializeField]
private float speedMin = 0.5f;
[SerializeField]
private float speedMax = 1f;
[SerializeField]
private bool useOnlySpeedMax = false;
private float speed;
[HideInInspector]
public float removeBeyond;
private void Start()
{
var dist = (transform.position - Camera.main.transform.position).z;
if (moveDirection == Direction.RightToLeft)
removeBeyond = Camera.main.ViewportToWorldPoint(new Vector3(1, 0, dist)).x;
else
removeBeyond = Camera.main.ViewportToWorldPoint(new Vector3(1, 0, dist)).x + FindObjectOfType<SkinnedMeshRenderer>().bounds.size.x;
}
private void OnEnable()
{
speed = Random.Range(speedMin, speedMax);
if (useOnlySpeedMax)
{
speed = speedMax;
}
if (moveDirection == Direction.RightToLeft)
{
speed = -speed;
}
}
// Update is called once per frame
void Update()
{
float realSpeed = speed * Time.deltaTime;
transform.position += Vector3.right * realSpeed;
if (moveDirection == Direction.RightToLeft && transform.position.x < -Mathf.Abs(removeBeyond))
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.right), Time.deltaTime * 40f);
moveDirection = Direction.LeftToRight;
}
else if (moveDirection == Direction.LeftToRight && transform.position.x > Mathf.Abs(removeBeyond))
{
}
}
}
I would use Coroutines to do this. Explanation in comments:
bool isTurning = false;
[SerializeField] float turnPeriod = 0.5f; // how long it takes to turn, smaller=faster
private void OnEnable()
{
speed = Random.Range(speedMin, speedMax);
if (useOnlySpeedMax)
{
speed = speedMax;
}
// Get rid of negative speed
}
void Update()
{
float realDistance = speed * Time.deltaTime;
transform.position += transform.right * realDistance;
// Surely there is a better way than calculating
// Mathf.Abs(removeBeyond) every frame... but that is off-topic
if (!isTurning && (
(moveDirection == Direction.RightToLeft
&& transform.position.x < -Mathf.Abs(removeBeyond)
) || (moveDirection == Direction.LeftToRight
&& transform.position.x > Mathf.Abs(removeBeyond)
) ) )
{
// If we aren't already turning and we should be, start turning:
StartCoroutine(Turn());
}
}
IEnumerator Turn()
{
isTurning = true;
Vector3 startForward = transform.forward;
// find end speed and direction
Direction endDirection = moveDirection == Direction.RightToLeft ?
Direction.LeftToRight:Direction.RightToLeft;
// keep track of how much of our turning time has elapsed
float elapsedTimePortion = Time.deltaTime/turnPeriod;
// turn until you've spent enough time turning
while (elapsedTimePortion < 1f)
{
// by whatever portion we've spent time turning, turn starting forward
// 180 degrees around up axis
float angle = 180f * elapsedTimePortion;
Vector3 newForward = Quaternion.AngleAxis(angle, Vector3.up) * startForward;
transform.rotation = Quaternion.LookRotation(newForward);
yield return null;
// next frame - update how long we've been turning
float newElapsedTimePortion = elapsedTimePortion + Time.deltaTime/turnPeriod;
if (newElapsedTimePortion >= 0.5f && elapsedTimePortion < 0.5f)
{
// If we've just passed 50% of the turn,
// make fish move opposite direction
moveDirection = endDirection;
}
elapsedTimePortion = newElapsedTimePortion;
}
// we're done turning, just set the rotation to the end rotation.
isTurning = false;
transform.rotation = Quaternion.LookRotation(-startForward);
// Does removeBeyond need to be updated here? There are a few lines in Start()
// which would suggest that.
}

Rotate Camera around a gameObject on Mouse drag in Unity

I want to rotate camera around a gameObject (Say a cube) on a drag of my mouse to simulate a feeling that the gameObject is rotating (Just like we rotate object in scene editor and like in shopping websites).
The script below is the one that I'm using. But sometimes the script behaves very weirdly. The camera rotates in the opposite direction than the anticipated direction. Why is this happening? What changes do I need to make in the code to make it work? Please help.
using UnityEngine;
using System.Collections;
public class ExampleBehaviourScript : MonoBehaviour
{
public Camera cameraObj;
public GameObject myGameObj;
public float speed = 2f;
void Update()
{
RotateCamera();
}
void RotateCamera()
{
if(Input.GetMouseButton(0))
{
cameraObj.transform.RotateAround(myGameObj.transform.position,
Vector3.up,
-Input.GetAxis("Mouse X")*speed);
cameraObj.transform.RotateAround(myGameObj.transform.position,
Vector3.right,
-Input.GetAxis("Mouse Y")*speed);
}
}
}
I think it is caused by the reference axis.
Because you used Vector3.up, Vector3.right, not the camera's one, it wasn't rotated the anticipated direction.
So, you should change like below.
void RotateCamera()
{
if(Input.GetMouseButton(0))
{
cameraObj.transform.RotateAround(myGameObj.transform.position,
cameraObj.transform.up,
-Input.GetAxis("Mouse X")*speed);
cameraObj.transform.RotateAround(myGameObj.transform.position,
cameraObj.transform.right,
-Input.GetAxis("Mouse Y")*speed);
}
}
My solution to rotate the camera in Unity with Mouse or Touch
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
public class CameraRotator : MonoBehaviour
{
public Transform target;
public Camera mainCamera;
[Range(0.1f, 5f)]
[Tooltip("How sensitive the mouse drag to camera rotation")]
public float mouseRotateSpeed = 0.8f;
[Range(0.01f, 100)]
[Tooltip("How sensitive the touch drag to camera rotation")]
public float touchRotateSpeed = 17.5f;
[Tooltip("Smaller positive value means smoother rotation, 1 means no smooth apply")]
public float slerpValue = 0.25f;
public enum RotateMethod { Mouse, Touch };
[Tooltip("How do you like to rotate the camera")]
public RotateMethod rotateMethod = RotateMethod.Mouse;
private Vector2 swipeDirection; //swipe delta vector2
private Quaternion cameraRot; // store the quaternion after the slerp operation
private Touch touch;
private float distanceBetweenCameraAndTarget;
private float minXRotAngle = -80; //min angle around x axis
private float maxXRotAngle = 80; // max angle around x axis
//Mouse rotation related
private float rotX; // around x
private float rotY; // around y
private void Awake()
{
if (mainCamera == null)
{
mainCamera = Camera.main;
}
}
// Start is called before the first frame update
void Start()
{
distanceBetweenCameraAndTarget = Vector3.Distance(mainCamera.transform.position, target.position);
}
// Update is called once per frame
void Update()
{
if (rotateMethod == RotateMethod.Mouse)
{
if (Input.GetMouseButton(0))
{
rotX += -Input.GetAxis("Mouse Y") * mouseRotateSpeed; // around X
rotY += Input.GetAxis("Mouse X") * mouseRotateSpeed;
}
if (rotX < minXRotAngle)
{
rotX = minXRotAngle;
}
else if (rotX > maxXRotAngle)
{
rotX = maxXRotAngle;
}
}
else if (rotateMethod == RotateMethod.Touch)
{
if (Input.touchCount > 0)
{
touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
//Debug.Log("Touch Began");
}
else if (touch.phase == TouchPhase.Moved)
{
swipeDirection += touch.deltaPosition * Time.deltaTime * touchRotateSpeed;
}
else if (touch.phase == TouchPhase.Ended)
{
//Debug.Log("Touch Ended");
}
}
if (swipeDirection.y < minXRotAngle)
{
swipeDirection.y = minXRotAngle;
}
else if (swipeDirection.y > maxXRotAngle)
{
swipeDirection.y = maxXRotAngle;
}
}
}
private void LateUpdate()
{
Vector3 dir = new Vector3(0, 0, -distanceBetweenCameraAndTarget); //assign value to the distance between the maincamera and the target
Quaternion newQ; // value equal to the delta change of our mouse or touch position
if (rotateMethod == RotateMethod.Mouse)
{
newQ = Quaternion.Euler(rotX , rotY, 0); //We are setting the rotation around X, Y, Z axis respectively
}
else
{
newQ = Quaternion.Euler(swipeDirection.y , -swipeDirection.x, 0);
}
cameraRot = Quaternion.Slerp(cameraRot, newQ, slerpValue); //let cameraRot value gradually reach newQ which corresponds to our touch
mainCamera.transform.position = target.position + cameraRot * dir;
mainCamera.transform.LookAt(target.position);
}
public void SetCamPos()
{
if(mainCamera == null)
{
mainCamera = Camera.main;
}
mainCamera.transform.position = new Vector3(0, 0, -distanceBetweenCameraAndTarget);
}
}
To move your cube in the direction of mouse moves change your code like blow:
void RotateCamera()
{
if (Input.GetMouseButton(0))
{
cameraObj.transform.RotateAround(myGameObj.transform.position,
Vector3.up,
Input.GetAxis("Mouse X") * speed);
cameraObj.transform.RotateAround(myGameObj.transform.position,
Vector3.right,
-Input.GetAxis("Mouse Y") * speed);
}
}

How to move 2D Object within camera view boundary

I have a scene that my camera doesn't follow my player. When player reaches the end of camera I want player to can't go further (out of camera view). How can I do this?
My codes for movement
public class PlayerBlueController : MonoBehaviour {
public float speed;
private float x;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void FixedUpdate () {
x = Input.GetAxis ("Horizontal") / 100 * speed;
transform.Translate (x,0,0);
}
}
As you can see from this. It gets out of camera's view.
I noticed you used a Collider2D. You should be using Rigidbody2D.MovePosition instead of transform.Translate or you'll likely run into issues when transform.Translate is used.
1.Take the final move position and convert it to new position in ViewPortPoint with Camera.main.WorldToViewportPoint
2.Apply a limit with Mathf.Clamp to the result in #1.
3.Convert the ViewPortPoint back to world point with Camera.main.ViewportToWorldPoint.
4.Finally, move it with Rigidbody2D.MovePosition.
The code below is modified from this answer to include restriction to screen boundary.
Move without Rigidbody:
Use only if collision and physics are NOT required:
public float speed = 100;
public Transform obj;
public void Update()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
//Move only if we actually pressed something
if ((h > 0 || v > 0) || (h < 0 || v < 0))
{
Vector3 tempVect = new Vector3(h, v, 0);
tempVect = tempVect.normalized * speed * Time.deltaTime;
Vector3 newPos = obj.transform.position + tempVect;
checkBoundary(newPos);
}
}
void checkBoundary(Vector3 newPos)
{
//Convert to camera view point
Vector3 camViewPoint = Camera.main.WorldToViewportPoint(newPos);
//Apply limit
camViewPoint.x = Mathf.Clamp(camViewPoint.x, 0.04f, 0.96f);
camViewPoint.y = Mathf.Clamp(camViewPoint.y, 0.07f, 0.93f);
//Convert to world point then apply result to the target object
obj.position = Camera.main.ViewportToWorldPoint(camViewPoint);
}
Move Object with Rigidbody2D:
Use if collision and physics are required:
public float speed = 100;
public Rigidbody2D rb;
public void Update()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
//Move only if we actually pressed something
if ((h > 0 || v > 0) || (h < 0 || v < 0))
{
Vector3 tempVect = new Vector3(h, v, 0);
tempVect = tempVect.normalized * speed * Time.deltaTime;
//rb.MovePosition(rb.transform.position + tempVect);
Vector3 newPos = rb.transform.position + tempVect;
checkBoundary(newPos);
}
}
void checkBoundary(Vector3 newPos)
{
//Convert to camera view point
Vector3 camViewPoint = Camera.main.WorldToViewportPoint(newPos);
//Apply limit
camViewPoint.x = Mathf.Clamp(camViewPoint.x, 0.04f, 0.96f);
camViewPoint.y = Mathf.Clamp(camViewPoint.y, 0.07f, 0.93f);
//Convert to world point then apply result to the target object
Vector3 finalPos = Camera.main.ViewportToWorldPoint(camViewPoint);
rb.MovePosition(finalPos);
}
image not respond .
but you can check player location
x = Input.GetAxis ("Horizontal") / 100 * speed;
if(gameobject.transform.x > someValue)
x=0
gameobject will be OBJECT in scene that u attach class to it.
another way is place 2 empty gameobject with collider as invisibleWall and get collider to player
Possible solution is:
1.Get the coordinates of your screen cornes (top left, top right, bottom left, bottom right). You can get this coordinates using Screen.height and Screen.width.
2.Convert this coordinates using the camera you need with Camera.ScreenToWorldPoint.
3.Get your player coordinates and check that they are inside the rect which is formed by 4 coordinates of the screen corners.
You need to limit your transform's position based on the edges of the camera. Here is an answer describing the different coordinate systems in unity
You're probably looking to do something like this:
float xMin = Camera.main.ViewportToWorldPoint(Vector3.zero).x;
float xMax = Camera.main.ViewportToWorldPoint(Vector3.one).x;
Vector3 currentPos = transform.position;
float dx = Input.GetAxis ("Horizontal") / 100 * speed;
Vector3 desiredPos = new Vector3(currentPos.x + dx, currentPos.y, currentPos.z);
Vector3 realPos = desiredPos;
if(desiredPos.x > xMax)
realPos.x = xMax;
else if(desiredPos.x < xMin)
realPos.x = xMin;
transform.position = realPos;
Read up here for more info on ViewportToWorldPoint(), it's extremely useful to become comfortable with the different coordinate spaces and how you can convert between them.

C# with Unity 3D: How do I make a camera move around an object when user moves mouse

I am trying to make a 3d viewing simulation in Unity 4 where the user can select an object and move their mouse to rotate around it (360 degrees) I have taken many shots to try get it to work, but I fail each time, any help will be appreciated and if it is written in C# that would be great! (But it doesn't have to)
Thanks in advance!
This is a different and interesting way :) (I use it)
(Here, the cube is the target)
1) Create sphere - Name: "Camera Orbit" - Add material: Transparent (Alpha = 0) - As scale as you want - Rotation: (0,0,0.1f)
2) Add the camera as a "child" to Camera Orbit's surface. Position = (0,"y = camera orbit scale",0)
Rotation = (90,0,0)
3) Create empty GameObject - Name: Input Control.
InputControl.cs:
public class InputControl : MonoBehaviour
{
public GameObject cameraOrbit;
public float rotateSpeed = 8f;
void Update()
{
if (Input.GetMouseButton(0))
{
float h = rotateSpeed * Input.GetAxis("Mouse X");
float v = rotateSpeed * Input.GetAxis("Mouse Y");
if (cameraOrbit.transform.eulerAngles.z + v <= 0.1f || cameraOrbit.transform.eulerAngles.z + v >= 179.9f)
v = 0;
cameraOrbit.transform.eulerAngles = new Vector3(cameraOrbit.transform.eulerAngles.x, cameraOrbit.transform.eulerAngles.y + h, cameraOrbit.transform.eulerAngles.z + v);
}
float scrollFactor = Input.GetAxis("Mouse ScrollWheel");
if (scrollFactor != 0)
{
cameraOrbit.transform.localScale = cameraOrbit.transform.localScale * (1f - scrollFactor);
}
}
}
CameraController.cs:
public class CameraController : MonoBehaviour
{
public Transform cameraOrbit;
public Transform target;
void Start()
{
cameraOrbit.position = target.position;
}
void Update()
{
transform.rotation = Quaternion.Euler(transform.rotation.x, transform.rotation.y, 0);
transform.LookAt(target.position);
}
}
4) Add CameraController.cs to Camera.
5) Add InputControl.cs to Input Control.
6) Set public variables in scripts. ("Camera Orbit" and "Target")
That's all. Mouse click and drag: Rotate - Mouse whell: Zoom in-out.
ps. If you want, you can change target as runtime.
The MouseOrbit script do that:
http://wiki.unity3d.com/index.php?title=MouseOrbitImproved#Code_C.23
Just attach this script into your Camera Object, and link the target object in inspector.
-- Use this for the Mouse Press down and drag
-- I modified the code here: http://wiki.unity3d.com/index.php?title=MouseOrbitImproved#Code_C.23
public Transform target;
public float distance = 5.0f;
public float xSpeed = 120.0f;
public float ySpeed = 120.0f;
public float yMinLimit = -20f;
public float yMaxLimit = 80f;
public float distanceMin = .5f;
public float distanceMax = 15f;
private Rigidbody rigidbody;
float x = 0.0f;
float y = 0.0f;
float mouseX = 0f;
float mouseY = 0f;
// Use this for initialization
void Start()
{
Vector3 angles = transform.eulerAngles;
x = angles.y;
y = angles.x;
rigidbody = GetComponent<Rigidbody>();
// Make the rigid body not change rotation
if (rigidbody != null)
{
rigidbody.freezeRotation = true;
}
}
void LateUpdate()
{
if (target)
{
GetMouseButtonDown_XY();
x += mouseX * xSpeed * distance * 0.02f;
y -= mouseY * ySpeed * 0.02f;
y = ClampAngle(y, yMinLimit, yMaxLimit);
Quaternion rotation = Quaternion.Euler(y, x, 0);
distance = Mathf.Clamp(distance - Input.GetAxis("Mouse ScrollWheel") * 5, distanceMin, distanceMax);
RaycastHit hit;
if (Physics.Linecast(target.position, transform.position, out hit))
{
distance -= hit.distance;
}
Vector3 negDistance = new Vector3(0.0f, 0.0f, -distance);
Vector3 position = rotation * negDistance + target.position;
transform.rotation = rotation;
transform.position = position;
}
}
public static float ClampAngle(float angle, float min, float max)
{
if (angle < -360F)
angle += 360F;
if (angle > 360F)
angle -= 360F;
return Mathf.Clamp(angle, min, max);
}
Vector3 mousePosPrev;
void GetMouseButtonDown_XY()
{
if (Input.GetMouseButtonDown(0))
{
mousePosPrev = Camera.main.ScreenToViewportPoint(Input.mousePosition);
}
if (Input.GetMouseButton(0))
{
Vector3 newMousePos = Camera.main.ScreenToViewportPoint(Input.mousePosition);
if (newMousePos.x < mousePosPrev.x)
{
mouseX = -1;
} else if (newMousePos.x > mousePosPrev.x)
{
mouseX = 1;
} else
{
mouseX = -0;
}
if (newMousePos.y < mousePosPrev.y)
{
mouseY = -1;
}
else if (newMousePos.y > mousePosPrev.y)
{
mouseY = 1;
}
else
{
mouseY = -0;
}
mousePosPrev = Camera.main.ScreenToViewportPoint(Input.mousePosition);
}
}
This is perfect. The only change I made is to add a script to the Camera Orbit:
public class FollowPlayer : MonoBehaviour {
public GameObject player;
private Vector3 playerPos;
// Update is called once per frame
void Update () {
if (this.transform.localScale.x <= 1)
{
this.transform.localScale = new Vector3(1, 1, 1);
}
if (this.transform.localScale.x >= 15)
{
this.transform.localScale = new Vector3(15, 15, 15);
}
playerPos = player.transform.position;
this.transform.position = playerPos;
}
}
Then attach your "player" object to the Input Control, and the input control will go where ever the player does, allowing you to track the player, as well as rotate and mouse wheel zoom. Fancy.
The localScale if statements means you can only zoom in and out so far.
The only problem with this script now is that if you zoom out to 15 and then keep trying to zoom out, the camera bounces. I'm sure that's an easy fix, though, I just haven't put the time in yet.
You don't need the CameraController at all, just set the camera's z rotation to -90.

Categories

Resources