I am going for a RuneScape-style camera that rotates around the player using WASD. Rotating horizontally works fine but when I mix the two (as in pitching up or down) the camera rotates around the player really awkwardly, the camera might invert or will sort of gimbal I guess.
Here's my code:
public float pitch;
public float zoomSpeed = 4f;
public float minZoom = 5f;
public float maxZoom = 15f;
public Transform target;
public Vector3 offset;
public float yawSpeed = 100f;
private float currentZoom = 10f;
private float currentYaw = 0f;
private float currentPitch = 0f;
void Update()
{
currentZoom -= Input.GetAxis("Mouse ScrollWheel") * zoomSpeed;
currentZoom = Mathf.Clamp(currentZoom, minZoom, maxZoom);
currentYaw -= Input.GetAxis("Horizontal") * yawSpeed * Time.deltaTime;
currentPitch -= Input.GetAxis("Vertical") * yawSpeed * Time.deltaTime;
Debug.Log("Yaw: " + currentYaw + " Pitch: " + currentPitch);
}
void LateUpdate()
{
transform.position = target.position - offset * currentZoom;
transform.LookAt(target.position + Vector3.up * pitch);
transform.RotateAround(target.position, Vector3.up, currentYaw);
transform.RotateAround(target.position, Vector3.forward, currentPitch);
}
Any help would be gladly appreciated!
It looks to me that you are using currentPitch, but rotating it around the forward axis? Which would create roll on the world foward axis?
If your up vector is always world up, then the yaw you have will work. But what you want to do is recalculate the right vector from your current location to your target after you apply the yaw.
void LateUpdate() {
transform.position = target.position - offset * currentZoom;
transform.LookAt(target.position + Vector3.up * pitch);
transform.RotateAround(target.position, Vector3.up, currentYaw);
transform.RotateAround(target.position, Vector3.Cross((target.position - transform.position).normalized, Vector3.up), currentPitch);
}
Related
I recently started on unity and I wanted to make simple movement which can be seen below.
using System.Collections.Generic;
using UnityEngine;
public class movement : MonoBehaviour
{
//Variables
float speed = 5.0f;
public float turnSpeed = 4.0f;
public float moveSpeed = 2.0f;
public float minTurnAngle = -90.0f;
public float maxTurnAngle = 90.0f;
private float rotX;
//Other
Vector3 Char_velocity;
Rigidbody Physics;
void Start()
{
Physics = GetComponent<Rigidbody>();
}
void FixedUpdate()
{
//Get axis on which we want to move
if (Input.GetButton("Vertical"))
{
//Creating vector which velocity is calculated based on vect3, speed and gets FPS compensation via fixed
//delta time
Char_velocity = new Vector3(Input.GetAxis("Horizontal"), 0.0f, Input.GetAxis("Vertical"));
Physics.MovePosition(transform.position + Char_velocity * speed * Time.fixedDeltaTime);
}
if (Input.GetButton("Horizontal"))
{
Char_velocity = new Vector3(Input.GetAxis("Horizontal"), 0.0f, Input.GetAxis("Vertical"));
Physics.MovePosition(transform.position + Char_velocity * speed * Time.deltaTime);
}
float y = Input.GetAxis("Mouse X") * turnSpeed;
rotX += Input.GetAxis("Mouse Y") * turnSpeed;
rotX = Mathf.Clamp(rotX, minTurnAngle, maxTurnAngle);
transform.eulerAngles = new Vector2(-rotX, transform.eulerAngles.y + y);
}
}
Mouse movement has been the problem for me. I got it to somewhat work but I have 2 issues. The camera should move the whole body while rotating from side to side but instead rotates the body when moving along the Y axis.
I have a problem with my camera movement. I want to limit my camera rotation, but I don't know how to do this. I searched for a solution, but I was not able to find one.
In my current code, my camera rotates rapidly in any direction and I don't know how to implement the Mathf.Clamp correctly.
Here is my code:
public float rightSpeed = 20.0f;
public float leftSpeed = -20.0f;
public float rotationLimit = 0.0f;
void Update()
{
//EdgeScrolling
float edgeSize = 40f;
if (Input.mousePosition.x > Screen.width - edgeSize)
{
//EdgeRight
transform.RotateAround(this.transform.position, Vector3.up, rotationLimit += rightSpeed * Time.deltaTime);
}
if (Input.mousePosition.x < edgeSize)
{
//EdgeLeft
transform.RotateAround(this.transform.position, Vector3.up, rotationLimit -= leftSpeed * Time.deltaTime);
}
rotationLimit = Mathf.Clamp(rotationLimit, 50f, 130f);
}
RotateAround method rotates by an angle. It doesn't assign the angle value you pass it. In this case I would suggest using a different approach. Instead of RotateAround method use the eulerAngles property and assign the rotation values directly.
Example
using UnityEngine;
public class TestScript : MonoBehaviour
{
public float rightSpeed = 20.0f;
public float leftSpeed = -20.0f;
private void Update()
{
float edgeSize = 40f;
if (Input.mousePosition.x > Screen.width - edgeSize)
RotateByY(rightSpeed * Time.deltaTime);
else if (Input.mousePosition.x < edgeSize)
RotateByY(leftSpeed * Time.deltaTime);
}
public void RotateByY(float angle)
{
float newAngle = transform.eulerAngles.y + angle;
transform.eulerAngles = new Vector3(
transform.eulerAngles.x,
Mathf.Clamp(newAngle, 50f, 130f),
transform.eulerAngles.z);
}
}
I can make the camera move the way I want, but I need it to be confined to within the 3D hollow sphere that I created. Currently the player can fly the camera out of the game area.
I've tried parenting the camera to a spherical game object, but the camera still leaves the game area but the sphere stays inside.
This was my attempt at using the BoundingSphere method to keep the camera inside the sphere.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SphericalBoundary : MonoBehaviour
{
public Vector3 pos;
public float r;
void Start()
{
BoundingSphere();
}
private void FixedUpdate()
{
BoundingSphere();
}
public void BoundingSphere()
{
pos = new Vector3(0, 0, 0);
r = (100);
}
}
============================================================================
============================================================================
This is the script that makes the camera fly around. Works perfectly.
using UnityEngine;
using System.Collections;
public class CameraFlight : MonoBehaviour
{
/*
EXTENDED FLYCAM
Desi Quintans (CowfaceGames.com), 17 August 2012.
Based on FlyThrough.js by Slin (http://wiki.unity3d.com/index.php/FlyThrough), 17 May 2011.
LICENSE
Free as in speech, and free as in beer.
FEATURES
WASD/Arrows: Movement
Q: Climb
E: Drop
Shift: Move faster
Control: Move slower
End: Toggle cursor locking to screen (you can also press Ctrl+P to toggle play mode on and off).
*/
public float cameraSensitivity = 90;
public float climbSpeed = 4;
public float normalMoveSpeed = 10;
public float slowMoveFactor = 0.25f;
public float fastMoveFactor = 3;
private float rotationX = 0.0f;
private float rotationY = 0.0f;
void Start()
{
// Screen.lockCursor = true;
}
void Update()
{
rotationX += Input.GetAxis("Mouse X") * cameraSensitivity * Time.deltaTime;
rotationY += Input.GetAxis("Mouse Y") * cameraSensitivity * Time.deltaTime;
rotationY = Mathf.Clamp(rotationY, -90, 90);
transform.localRotation = Quaternion.AngleAxis(rotationX, Vector3.up);
transform.localRotation *= Quaternion.AngleAxis(rotationY, Vector3.left);
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
{
transform.position += transform.forward * (normalMoveSpeed * fastMoveFactor) * Input.GetAxis("Vertical") * Time.deltaTime;
transform.position += transform.right * (normalMoveSpeed * fastMoveFactor) * Input.GetAxis("Horizontal") * Time.deltaTime;
}
else if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
transform.position += transform.forward * (normalMoveSpeed * slowMoveFactor) * Input.GetAxis("Vertical") * Time.deltaTime;
transform.position += transform.right * (normalMoveSpeed * slowMoveFactor) * Input.GetAxis("Horizontal") * Time.deltaTime;
}
else
{
transform.position += transform.forward * normalMoveSpeed * Input.GetAxis("Vertical") * Time.deltaTime;
transform.position += transform.right * normalMoveSpeed * Input.GetAxis("Horizontal") * Time.deltaTime;
}
if (Input.GetKey(KeyCode.Q)) { transform.position += transform.up * climbSpeed * Time.deltaTime; }
if (Input.GetKey(KeyCode.E)) { transform.position -= transform.up * climbSpeed * Time.deltaTime; }
//if (Input.GetKeyDown(KeyCode.End))
//{
// Screen.lockCursor = (Screen.lockCursor == false) ? true :][1]
false;
//}
}
}
No errors are showing up. During play the player can fly the camera out of the sphere but I want them confined to the sphere.
I think you must look your camera position on every frame and also check if it's going out of bound of the sphere here is some code which might help you(it is not tested)
public class TestScriptForStackOverflow : MonoBehaviour {
public Transform CameraTrans, SphereTrans;
private float _sphereRadius;
void Start() {
//if we are talking about sphere its localscale.x,y,z values are always equal
_sphereRadius = SphereTrans.localScale.x / 2;
}
void Update() {
if (IsOutsideTheSphere(_sphereRadius, CameraTrans.position, SphereTrans.position)) {
//lets find direction of camera from sphere center
Vector3 direction = (CameraTrans.position - SphereTrans.position).normalized;
//this is bound point for specific direction which is point is on the end of the radius
Vector3 boundPos = SphereTrans.position + direction * _sphereRadius;
//finally assign bound position to camera to stop it to pierce the sphere bounds
CameraTrans.position = boundPos;
}
}
private bool IsOutsideTheSphere(float sphereRadius, Vector3 cameraPosition, Vector3 sphereCenterPosition) {
//distance betweeen cameraPosition and sphereCenterPosition
float distanceBetween = (cameraPosition - sphereCenterPosition).magnitude;
//returns true if distance between sphere center and camera position is longer then sphere radius
return distanceBetween > sphereRadius;
}
If there is something you don't understand ask me in comments
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseOrbit : MonoBehaviour {
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 minFov = 15f;
float maxFov = 90f;
float sensitivity = 10f;
// 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 Update()
{
// Updating camera distance on every frame
distance = RayCast3.distance3;
//Setting maximum distance so the camera doesnt go too far
if (distance > 2)
{
distance = 2;
}
}
void LateUpdate()
{
if (target)
{
x += Input.GetAxis("Mouse X") * xSpeed * distance * 0.02f;
y -= Input.GetAxis("Mouse Y") * 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);
//distance += Input.GetAxis("Mouse ScrollWheel") * sensitivity;
float fov = Camera.main.fieldOfView;
fov += Input.GetAxis("Mouse ScrollWheel") * sensitivity;
fov = Mathf.Clamp(fov, minFov, maxFov);
Camera.main.fieldOfView = fov;
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);
}
}
Now i'm using fov:
And it's working fine.
float fov = Camera.main.fieldOfView;
fov += Input.GetAxis("Mouse ScrollWheel") * sensitivity;
fov = Mathf.Clamp(fov, minFov, maxFov);
Camera.main.fieldOfView = fov;
But now i want to use the distance variable and not fov.
So i tried first the line:
distance = Mathf.Clamp(distance - Input.GetAxis("Mouse ScrollWheel") * 5, distanceMin, distanceMax);
It didn't work so i tried the line:
distance += Input.GetAxis("Mouse ScrollWheel") * sensitivity;
But in both lines the character is stuttering and it's not zooming in out with the mouse wheel.
It's really simple. Get mouse scroll wheel speed then multiply it by some speed value. You can also multiply it by Time.deltaTime if you want. Finally use transform.Translate to move the camera with that value.
This will move in z-axis:
private float zoomSpeed = 2.0f;
void Update()
{
float scroll = Input.GetAxis("Mouse ScrollWheel");
transform.Translate(0, 0, scroll * zoomSpeed, Space.World);
}
Or where camera is facing:
private float zoomSpeed = 5.0f;
void Update()
{
float scroll = Input.GetAxis("Mouse ScrollWheel");
transform.position += this.transform.forward * scroll * zoomSpeed;
}
You should calculate a delta between your camera point and your target point. Normalize it and multiply it with the scrollwheel delta. Add this on your camera position. I used the camera target as angle to zoom-in.
Pseudo:
var delta = cameraTarget - cameraPosition;
delta.Normalize();
cameraPosition += delta * ScrollWheel.delta * sensitivity;
// you can even move your cameraTarget in the same direction
cameraTarget += delta * ScrollWheel.delta * sensitivity;
You can use the distance instead of calculating a delta.
Have this camera working perfect on my target object with one caveat. Can't seem to get the character to look up and down. Left and right move perfectly, up and down don't move at all. What am I doing wrong with the "Mouse Y" part?
public GameObject target;
public float rotateSpeed = 7;
Vector3 offset;
void Start() {
offset = target.transform.position - transform.position;
}
void LateUpdate() {
float horizontal = Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime;
float verticle = Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime;
target.transform.Rotate(0, horizontal, 0);
float desiredAngle = target.transform.eulerAngles.y;
Quaternion rotation = Quaternion.Euler(0, desiredAngle, verticle);
transform.position = target.transform.position - (rotation * offset);
transform.LookAt(target.transform);
}
You're not using verticle in your Transform.Rotate call (vertical?).
Edit: Sorry, I just stopped looking once I got to the first problem, looking further there's another problem that's similar to the one I addressed in this question. The "order of operations" is wrong (see the new comments):
public GameObject target;
public float rotateSpeed = 7;
Vector3 offset;
void Start() {
offset = target.transform.position - transform.position;
}
void LateUpdate() {
float horizontal = Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime;
float verticle = Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime;
//You didn't use verticle before. Depending on your model/setup you might verticle as the 3rd (Z) parameter to get rotation in the right direction
target.transform.Rotate(verticle, horizontal, 0); //This line rotates the transform
float desiredAngle = target.transform.eulerAngles.y;
Quaternion rotation = Quaternion.Euler(0, desiredAngle, verticle);
transform.position = target.transform.position - (rotation * offset);
transform.LookAt(target.transform); //This sets the absolute rotation of the transform. This call will "override" the above call to Rotate
}
To give more information you'll have to give an explanation of what your end goal is with this code, because looking closer I'm seeing "oddities" with what the code sample is trying to do.
The last line of code (transform.Lookat) overrides the previous code ... basically says 'always look at the target no matter what else happens'.