I'm trying to create a RTS-esque camera control system for my game and I've hit a wall and am unsure how to proceed.
What I want to happen is the following:
Mouse Click + Drag = Move the camera around the world (works)
Mouse Scroll Wheel Click + Drag = Rotate camera around the world (works)
Mouse Scroll Wheel = Zoom in/out using MoveToward/Transform.Forward..etc (sorta works)
In another script I have FoV zoom working, but it is not ideal for the type of experience I want to create. Instead I am trying to physically move the camera in the direction it is pointed.
This current script is 90% functional in that it can do all the things listed above, but when I click + drag to move to a new location after scrolling to zoom, the camera resets to the original position.
Here is what happens in Update():
void Update ()
{
currentPosition = transform.position;
scrollAxis = Input.GetAxis ("Mouse ScrollWheel");
if (Input.GetMouseButtonDown (0))
{
mouseOrigin = Input.mousePosition;
isPanning = true;
}
// cancel on button release
if (!Input.GetMouseButton (0))
{
isPanning = false;
}
//move camera on X & Y
else if (isPanning)
{
Vector3 pos = Camera.main.ScreenToViewportPoint (Input.mousePosition - mouseOrigin);
Vector3 move = new Vector3 (pos.x * -panSpeed, pos.y * -panSpeed, 0);
Camera.main.transform.Translate (move, Space.Self);
transform.eulerAngles = new Vector3(transform.eulerAngles.x, transform.eulerAngles.y, 0 );
transform.localPosition = new Vector3( transform.position.x, transform.position.y - (transform.position.y - (initialPosition.y)), transform.position.z );
}
//rotate camera with mousewheel click
if (Input.GetMouseButton(2)) {
yRotation -= Input.GetAxis("Mouse Y") * rotationSpeed * Time.deltaTime;
yRotation = Mathf.Clamp(yRotation, -80, 80);
xRotation += Input.GetAxis("Mouse X") * rotationSpeed * Time.deltaTime;
xRotation = xRotation % 360;
transform.localEulerAngles = new Vector3(yRotation + initialRotation.x, xRotation + initialRotation.y, 0);
}
//zoom with scroll
if ( Input.GetAxis("Mouse ScrollWheel") > 0 && !isPanning) {
transform.Translate(Vector3.forward * Time.deltaTime * 10000f * scrollAxis, Space.Self );
transform.localPosition = new Vector3( transform.position.x, transform.position.y, transform.position.z );
}
}
Any help is much appreciated, thanks!
Solved it with the help of a friend.
Needed to reset the initialPosition Vector3 each time I zoomed, to prevent from snapping back to the original location.
if ( Input.GetAxis("Mouse ScrollWheel") > 0) {
transform.Translate(Vector3.forward * Time.deltaTime * 10000f * scrollAxis, Space.Self );
initialPosition = transform.position;
}
Related
I'm having an issue with the free roam camera I'm trying to implement. The camera can be rotated via keyboard (only on the Y axis) and via mouse (on the X and Y axis) while holding the middle mouse button. With my current implementation, if I rotate the camera using the keyboard and the rotate it using the mouse, it remove any rotation done by the keyboard as soon as I hit the middle mouse button. Of course I would like to not do that... Code is below. Can someone give tell me what I'm doing wrong?
private void RotateCameraKeyboard()
{
if (Input.GetKey(KeyCode.Q))
{
transform.RotateAround(transform.position, Vector3.up, -rotationSpeed * Time.deltaTime * 30);
}
if (Input.GetKey(KeyCode.E))
{
transform.RotateAround(transform.position, Vector3.up, rotationSpeed * Time.deltaTime * 30);
}
}
private void RotateCameraMouse()
{
if (Input.GetMouseButton(2))
{
pitch -= rotationSpeed * Input.GetAxis("Mouse Y");
yaw += rotationSpeed * Input.GetAxis("Mouse X");
pitch = Mathf.Clamp(pitch, -90f, 90f);
while (yaw < 0f)
{
yaw += 360f;
}
while (yaw >= 360f)
{
yaw -= 360f;
}
transform.eulerAngles = new Vector3(pitch, yaw, 0f);
}
}
Instead of using RotateAround (which in your case of using the transform.position as pivot is redundant anyway .. you could as well just use Rotate) additionally also add the according amount to your pitch and yawn.
I would simply generalize and use the same method for both inputs like e.g.
private void RotateCameraKeyboard()
{
if (Input.GetKey(KeyCode.Q))
{
Rotate(-rotationSpeed * Time.deltaTime * 30, 0);
}
if (Input.GetKey(KeyCode.E))
{
Rotate(rotationSpeed * Time.deltaTime * 30, 0);
}
}
private void RotateCameraMouse()
{
if (Input.GetMouseButton(2))
{
var pitchChange = rotationSpeed * Input.GetAxis("Mouse Y");
var yawChange = rotationSpeed * Input.GetAxis("Mouse X");
Rotate(yawChange, pitchChange);
}
}
private void Rotate(float yawChange, float pitchChange)
{
pitch = Mathf.Clamp(pitch + pitchChange, -90f, 90f);
yaw += yawChange;
while (yaw < 0f)
{
yaw += 360f;
}
while (yaw >= 360f)
{
yaw -= 360f;
}
transform.eulerAngles = new Vector3(pitch, yaw, 0f);
}
I have a 3D game, where my camera is looking at all my terrain, and I have a character that moves with an Xbox controller. I want my character to only move in the direction that it is looking, not to the sides. I have the next code that makes my character move.
void Update()
{
MoveInput = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
moveVelocity = MoveInput * MoveSpeed;
Vector3 PlayerDirection = Vector3.right * -Input.GetAxisRaw("RHorizontal") + Vector3.forward * -Input.GetAxisRaw("RVertical");
if (PlayerDirection.sqrMagnitude > 0.0f)
{
transform.rotation = Quaternion.LookRotation(PlayerDirection, Vector3.up);
}
The character moves with the left side of the controller, and with the right side, I can move the direction that is facing.
Currently, you're using the inputs as x and z components of a world space direction. It doesn't take into account the rotation of the character at all.
Instead, you should multiply the inputs and the corresponding local direction in world space, and then combine them. In your case, this might look like this:
MoveInput = (
transform.right * Input.GetAxisRaw("Horizontal")
+ transform.forward * Input.GetAxisRaw("Vertical")
).normalized;
moveVelocity = MoveInput * MoveSpeed;
Vector3 PlayerDirection = Vector3.right * -Input.GetAxisRaw("RHorizontal")
+ Vector3.forward * -Input.GetAxisRaw("RVertical");
if (PlayerDirection.sqrMagnitude > 0.0f)
{
transform.rotation = Quaternion.LookRotation(PlayerDirection, Vector3.up);
}
The normalized is so you don't move faster diagonally.
I found a solution, here is the code:
MoveInput = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
moveVelocity = MoveInput * MoveSpeed;
Vector3 PlayerDirection = Vector3.right * Input.GetAxisRaw("Horizontal") + Vector3.forward * Input.GetAxisRaw("Vertical");
if (PlayerDirection.sqrMagnitude > 0.0f)
{
transform.rotation = Quaternion.LookRotation(PlayerDirection, Vector3.up);
}
I've only been working in unity for a few days and have the following problem that I don't know how to make the zoom in and zoom out smooth without the rest running smooth as well. Nevertheless I want to keep the function that I can decide if the rest can be smooth or not. As mentioned before, the main problem is that I don't know how to Smoothen the zoom in and zoom out ONLY. When I start the project with my current code, it feels like my mouse's dpi is pretty high. Also, every action is smooth, which I don't want either. I would be very happy about help.
Vace :)
if (thirdperson)
{
yaw += Input.GetAxis("Mouse X") * mouseSensitivity;
pitch -= Input.GetAxis("Mouse Y") * mouseSensitivity;
pitch = Mathf.Clamp(pitch, pitchMinMax.x, pitchMinMax.y);
// out
if (Input.GetAxis("Mouse ScrollWheel") < 0)
{
if (dstFromTarget < 12)
{
dstFromTarget++;
}
}
//in
if (Input.GetAxis("Mouse ScrollWheel") > 0)
{
if (dstFromTarget > 2)
{
dstFromTarget--;
}
}
currentRotation = Vector3.SmoothDamp(currentRotation, new Vector3(pitch, yaw), ref rotationSmoothVelocity, rotationSmoothTime);
transform.eulerAngles = currentRotation;
Vector3 test = Vector3.SmoothDamp(transform.position, target.position - transform.forward * dstFromTarget, ref zoomSmoothVelocity, zoomSmoothTime);
transform.position = new Vector3(test.x, test.y, test.z);
}
Use the float version of Vector3.SmoothDamp: Mathf.SmoothDamp.
Use it to find a smoothed target for dstFromTarget. change zoomSmoothVelocity to a float and initialize a current zoom variable to the same value as you initialize dstFromTarget.
Then, you can just assign the target you had for your Vector3.SmoothDamp directly to the camera's position.
Use transform.eulerAngles = new Vector3(pitch.yaw); to bypass your first call to Vector3.SmoothDamp
I'd also recommend using Mathf.Clamp to keep your zoom in a valid range.
Altogether, this may look like this:
private float zoomSmoothVelocity = 0f;
private float curZoom = 6f; // however dstFromTarget is initialized
// ...
if (thirdperson)
{
yaw += Input.GetAxis("Mouse X") * mouseSensitivity;
pitch -= Input.GetAxis("Mouse Y") * mouseSensitivity;
pitch = Mathf.Clamp(pitch, pitchMinMax.x, pitchMinMax.y);
// out
if (Input.GetAxis("Mouse ScrollWheel") < 0)
{
dstFromTarget++;
}
//in
if (Input.GetAxis("Mouse ScrollWheel") > 0)
{
dstFromTarget--;
}
dstFromTarget = Mathf.Clamp(dstFromTarget, 2f, 12f);
transform.eulerAngles = new Vector3(pitch, yaw);
curZoom = Mathf.SmoothDamp(curZoom , dstFromTarget, ref zoomVelocity , zoomSmoothTime);
transform.position = target.position - transform.forward * curZoom;
}
Using Unity, I'm trying to move my turrets rotation using first person camera by using touch, I grab from side of screen to center from the direction i want to go to like in google maps, the touch grab will move my camera there smoothly.
Problem is the camera being very jerky, not smooth in position.
In Global
private float smoothTouchFactor = 5;
This is the code I am using in Update()
Quaternion rotation;
if (Input.touchCount > 0)
{
if (Input.GetTouch(0).phase == TouchPhase.Moved)
{
Touch touch = Input.GetTouch(0);
x += touch.deltaPosition.x * xSpeed * 0.02f;
y -= touch.deltaPosition.y * ySpeed * 0.02f;
rotation = Quaternion.Euler(y, x, 0f);
turretHeadToMove.rotation = Quaternion.Slerp(turretHeadToMove.rotation, rotation, Time.deltaTime * smoothTouchFactor);
}
}
rotation = Quaternion.Euler(y, x, 0f);
turretHeadToMove.rotation = Quaternion.Slerp(turretHeadToMove.rotation, rotation, Time.deltaTime * smoothTouchFactor);
If I dont add the last 2 lines after the if(touchCount) then the camera goes back to initial position after i lift my finger off the screen.
Here is the mouse axis Mouse X/Y code that works and moves the turret with no jerky motion, smoothly.
private float xSpeed = 40.0f;
private float ySpeed = 40.0f;
in Update()
x += Input.GetAxis("Mouse X") * xSpeed * 0.02f;
y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;
Quaternion rotation = Quaternion.Euler(y, x, 0f);
turretHeadToMove.rotation = rotation;
From your description of needing those "two lines" at the end, it sounds like you have code elsewhere that's constantly trying to reset their rotation. So, we add a variable to keep rotation consistent with the Quaternion set the last time the touch control was done, and just be sure to assign it to turretHeadToMove.rotation before handling the touch controls.
Then, you can use Euler to create a Quaternion of how much you want to rotate this frame. And then multiply the current rotation by that amount in order to get the new rotation
Altogether this might look like:
As a field:
private Quaternion savedRotation = Quaternion.identity;
In Update:
// Add it before the block so that modifications to `localRotation` are consistent with memory
turretHeadToMove.rotation = savedRotation;
if (Input.touchCount > 0)
{
if (Input.GetTouch(0).phase == TouchPhase.Moved)
{
Touch touch = Input.GetTouch(0);
Quaternion previousRotation = turretHeadToMove.rotation;
Quaternion rotationThisFrame = Quaternion.Euler(
touch.deltaPosition.y * ySpeed,
touch.deltaPosition.x * xSpeed, 0f);
turretHeadToMove.rotation = previousRotation * rotationThisFrame;
savedRotation = turretHeadToMove.rotation;
}
}
I have a main camera in Unity3D that I want to rotate depending on mouse input, so it works as a first person video-game where you move the mouse depending on where do you want to look at.
The starting values of the camera (Transform tab in Inspector tab in Unity) are:
Position: X = -1, Y = 1, Z = -11.
Rotation: X = 0, Y = 0, Z = 0.
Scale: X = 1, Y = 1, Z = 1.
I added a Script component for the Main Camera. And it is the following class:
using UnityEngine;
using System.Collections;
public class CameraMove : MonoBehaviour {
float deltaRotation = 50f;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if(Input.GetAxis("Mouse X") < 0){
//Code for action on mouse moving left
transform.Rotate (new Vector3 (0f, -deltaRotation, 0f) * Time.deltaTime);
}
else if(Input.GetAxis("Mouse X") > 0){
//Code for action on mouse moving right
transform.Rotate (new Vector3 (0f, deltaRotation, 0f) * Time.deltaTime);
}
if(Input.GetAxis("Mouse Y") < 0){
//Code for action on mouse moving left
transform.Rotate (new Vector3 (deltaRotation, 0f, 0f) * Time.deltaTime);
}
else if(Input.GetAxis("Mouse Y") > 0){
//Code for action on mouse moving right
transform.Rotate (new Vector3 (-deltaRotation, 0f, 0f) * Time.deltaTime);
}
}
}
However, when I play the scene the camera doesn't rotate like it should. The values of the rotation change in x-axis, y-axis and even for z-axis.
What am I doing wrong?
That's a problem with how Quaternion is calculated. This happens when multiple axis are being modified. If you comment all the x rotation or the y rotation, and only rotate in one axis at a time, you will realize that this problem will go away.
To properly rotate your camera with the mouse input, use the eulerAngles or localEulerAngles variables. The option between these two depends on what you are doing.
public float xMoveThreshold = 1000.0f;
public float yMoveThreshold = 1000.0f;
public float yMaxLimit = 45.0f;
public float yMinLimit = -45.0f;
float yRotCounter = 0.0f;
float xRotCounter = 0.0f;
// Update is called once per frame
void Update()
{
xRotCounter += Input.GetAxis("Mouse X") * xMoveThreshold * Time.deltaTime;
yRotCounter += Input.GetAxis("Mouse Y") * yMoveThreshold * Time.deltaTime;
yRotCounter = Mathf.Clamp(yRotCounter, yMinLimit, yMaxLimit);
transform.localEulerAngles = new Vector3(-yRotCounter, xRotCounter, 0);
}