My app (using Unity 2017.3.1f1) is set up like this:
The perspective camera is inside the "player" object (collisions are disabled) and also a child of it (to get a 1st person view)
The camera has a "MouseLook" script (I changed a few things in Unity's default one) that keeps the cursor locked and hidden in the middle of the screen (Cursor.lockState = CursorLockMode.Locked & Cursor.visible = false)
The "player" has a general "input" script and a movement script: If wasd are pressed, these key presses move the player object, which also makes the camera move
What I want to achieve:
Use the scroll wheel on the mouse to move towards the point the camera is looking at or away from it, independent if there's an object.
I do NOT want to change the FOV/scale
It's not necessary to use Lerp, I just want to move the player a step towards the target position every frame the mouse wheel is used
What I've tried:
1a. In the general input script:
if(Input.GetAxis("Mouse ScrollWheel") != 0) {
transform.Translate(0,0,Input.GetAxis("Mouse ScrollWheel") * 200);
}
1b: In the general input script:
if(Input.GetAxis("Mouse ScrollWheel") != 0) {
transform.position += transform.forward * Input.GetAxis("Mouse ScrollWheel") * 200;
}
Both work the same but only move the player/camera on the same y level, even when looking down/up. transform.up always outputs "(0.0, 1.0, 0.0)", so there's no point incorporating that.
2.A new script on the camera (source):
public class ScrollToZoom : MonoBehaviour {
public GameObject player;
void Update () {
if(Input.GetAxis("Mouse ScrollWheel") != 0) {
RaycastHit hit;
Ray ray = this.transform.GetComponent<Camera>().ScreenPointToRay(Input.mousePosition);
Vector3 desiredPosition;
if(Physics.Raycast(ray,out hit)) {
desiredPosition = hit.point;
} else {
desiredPosition = transform.position;
}
float distance = Vector3.Distance(desiredPosition,transform.position);
Vector3 direction = Vector3.Normalize(desiredPosition - transform.position) * (distance * Input.GetAxis("Mouse ScrollWheel"));
transform.position += direction;
}
}
}
This doesn't work at all because "direction" is always (0,0,0) because the mouse cursor is locked (which I can't/won't change).
How do you incorporate the rotation around the x-axis (I clamp it to +/- 90° in the "MouseLook" script) in this?
if(Input.GetAxis("Mouse ScrollWheel") != 0) {
transform.localPosition += Vector3.forward * Input.GetAxis("Mouse ScrollWheel") * 200;
}
Should work. transform.position will refer to the world position regardless of where the object is facing.
Related
Consider this working script, that handles a FPS camera movement:
using UnityEngine;
public class CameraHandler : MonoBehaviour {
public Transform target;
float dragSpeed = 10f;
float lookAtSensitivity = 200f;
float xRot;
Transform parentGO;
private void Start() {
parentGO = transform.parent;
}
void goToPivot(Transform pivot) {
parentGO.position = Vector3.Lerp(transform.position, pivot.position, 0.05f);
transform.rotation = Quaternion.Lerp(transform.rotation, pivot.rotation, 0.05f);
}
void resetCamRot() {
xRot = 0;
float yRot = transform.localEulerAngles.y;
parentGO.transform.eulerAngles += new Vector3(0, yRot, 0);
transform.localEulerAngles -= new Vector3(0, yRot, 0);
}
void LateUpdate() {
if (Input.GetKey(KeyCode.Mouse1)) {
float touchX = Input.GetAxis("Mouse X") * lookAtSensitivity * Time.deltaTime;
float touchY = Input.GetAxis("Mouse Y") * lookAtSensitivity * Time.deltaTime;
xRot -= touchY;
xRot = Mathf.Clamp(xRot, -90f, 90f);
transform.localRotation = Quaternion.Euler(xRot, 0f, 0f);
parentGO.transform.Rotate(Vector3.up * touchX);
}
if (Input.GetKey(KeyCode.Space)) {
goToPivot(target);
}
if (Input.GetKeyUp(KeyCode.Space)) {
resetCamRot();
}
}
}
Check how the rotations take place in different gameobjects in their respective axis, so that each of rotations are kept independent and everything works.
transform.localRotation = Quaternion.Euler(xRot, 0f, 0f); //camera GO only rotates in local x
parentGO.transform.Rotate(Vector3.up * touchX); //parent GO only rotates in global y
Problem comes when I need to "force" the camera look a certain direction without the inputs, to the FPS movement rules break, and for example the camera gameobject rotates also in the Y xis. That is why I need to call the resetCamRot() method, and traspass the local rotation from the camera object to the parent so that the situation meets the the FPS movement requirements (no local Y axis rotation).
Without calling the resetCamRot() method, when the FPS movement starts on right mouse button click, the camera abruptly changes to the direction it was facing before "forcing" it with goToPivot that sets the position and the rotation.(Just commentinf the resetCamRot method out)
Although resetCamRot() does the work it feels a bit hacky, so is there another way to set the camera to a forced rotation maintaining the local rotation of the child object (where the camera is) to 0?
I thought of decomposing the next step rotation given by Quaternion.Lerp(transform.rotation, pivot.rotation, 0.05f); in the goToPivot() method in each of their respective axis and gameObjects as its done when the rotation is set from the input, to have a clean local Y rot in he camera gameobject each step. Seems to be the over-complicated thing in this case, but was not able to figure that out.
I you wish to try the script out for the challenge just need to add a parent gameobject to the camera and the attach the target in the editor.
This will make the camera look in the direction, the parent transform look in the direction, only flattened, and finally update the internal state (xRot) in accordance with the difference between the two:
void LookTowards(Vector3 direction) {
Vector3 flattened = direction;
flattened.y = 0f;
parentGo.rotation = Quaternion.LookRotation(flattened);
transform.rotation = Quaternion.LookRotation(direction);
xRot = Vector3.SignedAngle(flattened, direction, parentGo.right);
}
Whenever I'm running against a wall, like if there is a wall to my left and I hold 'a' against the wall it kinda spazes out. It looks like the character is going in and out of the wall. Hopefully that made sense and you know what I'm talking about. So my question is how could I fix this so that when I am actively running into a wall it doesnt do that and instead the character is just there against the wall and appearing to move at all.
code for the movement:
void Update()
{
var movement = Input.GetAxis("Horizontal");
transform.position += new Vector3(movement, 0, 0) * Time.deltaTime * MovementSpeed;
if (Input.GetButtonDown("Jump") && Mathf.Abs(_rigidbody.velocity.y) < .001f)
{
_rigidbody.AddForce(new Vector2(0, JumpForce), ForceMode2D.Impulse);
}
}
Setting the transform.position literally teleports the player, so it sometimes teleports them into the wall then they get pushed back.
To prevent that i suggest using the rigidbody's movePosition function. This takes into account physics while moving, so it interacts with the other objects that are there.
To change your current code to that it would be something like this:
void Update()
{
var movement = Input.GetAxis("Horizontal");
//This moves the GameObject to the currentPosition + The move direction. Which means to move it in the direction that you intended to move in.
_rigidbody.MovePosition(transform.position + new Vector3(movement, 0, 0) * Time.deltaTime * movementSpeed);
if (Input.GetButtonDown("Jump") && Mathf.Abs(_rigidbody.velocity.y) < .001f)
{
_rigidbody.AddForce(new Vector2(0, JumpForce), ForceMode2D.Impulse);
}
}
However i suggest splitting it into this:
float movement = 0f; //Setting a default value.
void Update()
{
movement = Input.GetAxis("Horizontal"); //Getting movement input from player
if (Input.GetButtonDown("Jump") && Mathf.Abs(_rigidbody.velocity.y) < .001f)
{
_rigidbody.AddForce(new Vector2(0, JumpForce), ForceMode2D.Impulse);
}
}
private void FixedUpdate()
{ //Moving in Physics update
_rigidbody.MovePosition(transform.position + new Vector3(movement, 0, 0) * Time.deltaTime * movementSpeed);
}
Because Update checks every frame, so its good for checking input, however you should apply physics in FixedUpdate(), because that is applied whenever there is a physics update (Or physics frame). (For even more smoothness!)
I'm having trouble with my Camera script. The Camera rotates around the player by using a pivot that's assigned as a child of the camera. This works with 2 arrows (I'm developing this game for mobiles, so they're touch arrows) that allow the camera to rotate left and right.
The problem is when the Camera goes behind a wall or a huge object and can't see anything. I looked for a solution and I see that many developers used the RaycastHit or something similar.
Here's my code, the goal is to get the Camera to go closer to the pivot when near a wall in order to avoid blocking the Camera view. Can anyone help me?
public Transform target1;
public Transform pivot;
protected ButtonLeft buttonLeft;
protected ButtonRight buttonRight;
public Vector3 offset;
public bool useOffsetValues;
public float rotateSpeed;
private void Start()
{
buttonLeft = FindObjectOfType<ButtonLeft>();
buttonRight = FindObjectOfType<ButtonRight>();
if (!useOffsetValues)
{
offset = target1.position - transform.position;
}
pivot.transform.position = target1.transform.position;
//pivot.transform.parent = target.transform;
//USE IF U WANT TO DISAPPEAR THE CURSOR
//Cursor.lockState = CursorLockMode.Locked;
//pivot.transform.parent = target.transform;
pivot.transform.parent = null;
// usa questa dopo la costruzione del livello1
//pivot.transform.position = target.transform.position;
}
private void Update()
{
pivot.transform.position = target1.transform.position;
if (buttonLeft.Pressed)
{
pivot.Rotate(0, -90 * Time.deltaTime, 0);
Debug.Log("rotate left");
}
if (buttonRight.Pressed)
{
pivot.Rotate(0, 90 * Time.deltaTime, 0);
Debug.Log("rotate left");
}
Ray ray = new Ray(pivot.transform.position, pivot.transform.position - transform.position);
RaycastHit hit;
/*float horizontal = Input.GetAxis("Mouse X") * rotateSpeed;
float horizontal = Input.GetAxis("Mouse X") * rotateSpeed;
pivot.Rotate(0, horizontal, 0);
pivot.Rotate(0, horizontal, 0);
Use this to make the camera rotate on Mouse Y axes*/
/*float vertical = Input.GetAxis("Mouse Y") * rotateSpeed;
target.Rotate(vertical, 0, 0); */
//move camera based on the current rotation of the target and the original offset
float desiredYAngle = pivot.eulerAngles.y;
//Use this float to set the x angle of player
float desiredXAngle = pivot.eulerAngles.x;
//Use this rotation only if you want to rotate on Y
//Quaternion rotation = Quaternion.Euler(0, desiredYAngle, 0);
//Use this if u want to rotate up&down on x axes
Quaternion rotation = Quaternion.Euler(desiredXAngle, desiredYAngle, 0);
transform.position = target1.position - (rotation * offset);
//transform.position = target.position - offset;
transform.LookAt(target1);
}
Good approach is when you do raycast from pivot to camera, and, if some obstacle found, put camera on a ray, a bit closer to pivot, than hit point. Like this: (pseudocode, not tested):
Vector3 origin = pivot.transform.position;
Vector3 direction = transform.position - pivot.transform.position;
float maxCameraDistance = 10;
Ray ray = new Ray(origin, direction);
RaycastHit hit;
if (Physics.Raycast(ray, maxCameraDistance))
{
Vector3 offsetFromObstacle = -direction.normalized * 0.1f;
transform.position = hit.point + offsetFromObstacle;
}
I'm working through a book and have come to an issue. it's a top down shooting game that has the player rotate with the mouse and move with the keyboard. problem is when testing if either the mouse or the keyboard set off movement the image vibrates. If i push the arrow keys it moves in a circle the longer I hold the key the wider the circle. Below is the script I'm working with.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class PlayerBehaviour : MonoBehaviour
{
//movement modifier applied to directional movement
public float playerSpeed = 2.0f;
//current player speed
private float currentSpeed = 0.0f;
/*
* Allows us to have multiple inputs and supports keyboard,
* joystick, etc.
*/
public List<KeyCode> upButton;
public List<KeyCode> downButton;
public List<KeyCode> leftButton;
public List<KeyCode> rightButton;
//last movement made
private Vector3 lastMovement = new Vector3();
// Update is called once per frame
void Update()
{
//rotates ship to face mouse
Rotation();
//moves ship
Movement();
}
void Rotation()
{
//finds mouse in relation to player location
Vector3 worldPos = Input.mousePosition;
worldPos = Camera.main.ScreenToWorldPoint(worldPos);
/*
get x and y screen positions
*/
float dx = this.transform.position.x - worldPos.x;
float dy = this.transform.position.y - worldPos.y;
//find the angle between objects
float angle = Mathf.Atan2(dy, dx) * Mathf.Rad2Deg;
/*
* The transform's rotation property uses a Quaternion,
* so we need to convert the angle in a Vector
* (The Z axis is for rotation for 2D).
*/
Quaternion rot = Quaternion.Euler(new Vector3(0, 0, angle + 90));
// Assign the ship's rotation
this.transform.rotation = rot;
}
// Will move the player based off of keys pressed
void Movement()
{
// The movement that needs to occur this frame
Vector3 movement = new Vector3();
// Check for input
movement += MoveIfPressed(upButton, Vector3.up);
movement += MoveIfPressed(downButton, Vector3.down);
movement += MoveIfPressed(leftButton, Vector3.left);
movement += MoveIfPressed(rightButton, Vector3.right);
/*
* If we pressed multiple buttons, make sure we're only
* moving the same length.
*/
movement.Normalize();
// Check if we pressed anything
if (movement.magnitude > 0)
{
// If we did, move in that direction
currentSpeed = playerSpeed;
this.transform.Translate(movement * Time.deltaTime * playerSpeed, Space.World);
lastMovement = movement;
}
else
{
// Otherwise, move in the direction we were going
this.transform.Translate(lastMovement * Time.deltaTime * currentSpeed, Space.World);
// Slow down over time
currentSpeed *= .9f;
}
}
/*
* Will return the movement if any of the keys are pressed,
* otherwise it will return (0,0,0)
*/
Vector3 MoveIfPressed(List<KeyCode> keyList, Vector3 Movement)
{
// Check each key in our list
foreach (KeyCode element in keyList)
{
if (Input.GetKey(element))
{
/*
* It was pressed so we leave the function
* with the movement applied.
*/
return Movement;
}
}
// None of the keys were pressed, so don't need to move
return Vector3.zero;
}
}
I studied your code for a while and could not find anything wrong with it. So I tested it myself and it works perfectly.
So I suppose there's something wrong with your scene. You might for example have your player object be a child of some object that rotates your axes: this would cause problems I suppose.
Have a new, empty scene. Add a new GameObject (a 3D cube for example, or a 2D sprite) and assign PlayerBehaviour to it. Now test: it should work perfectly.
I have a method that will rotate my object to the point my mouse just clicked on. However, my object only rotates as long as my mouse button is held down.
My main rotation method is this:
void RotateShip ()
{
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
Debug.Log (Input.mousePosition.ToString ());
Plane playerPlane = new Plane (Vector3.up, transform.position);
float hitdist = 0.0f;
if (playerPlane.Raycast (ray, out hitdist))
{
Vector3 targetPoint = ray.GetPoint (hitdist);
Quaternion targetRotation = Quaternion.LookRotation (targetPoint - transform.position);
transform.rotation = Quaternion.Slerp (transform.rotation, targetRotation, speed * Time.deltaTime);
}
}
I call this method inside the FixedUpdate method and I've wrapped it inside the following if statement:
void FixedUpdate ()
{
// Generate a plane that intersects the transform's position with an upwards normal.
// Generate a ray from the cursor position
if (Input.GetMouseButton (0))
{
RotateShip ();
}
}
However, the object will still only rotate as long as my mouse button is held down. I want my object to continue to rotate to the point my mouse just clicked until it reaches that point.
How can I amend my code properly?
It's only rotating while your mouse is down because that's the only time you tell it to rotate. In your FixedUpdate (which Imtiaj rightfully pointed out should be Update), you're only calling RotateShip() while Input.GetMouseButton(0) is true. That means that you only rotate your ship while the button is pressed.
What you should do is take that mouse event and use it to set a target, then continuously rotate toward that target. For instance,
void Update() {
if (Input.GetMouseButtonDown (0)) //we only want to begin this process on the initial click, as Imtiaj noted
{
ChangeRotationTarget();
}
Quaternion targetRotation = Quaternion.LookRotation (this.targetPoint - transform.position);
transform.rotation = Quaternion.Slerp (transform.rotation, targetRotation, speed * Time.deltaTime);
}
void ChangeRotationTarget()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Plane playerPlane = new Plane (Vector3.up, transform.position);
float hitdist = 0.0f;
if (playerPlane.Raycast (ray, out hitdist))
{
this.targetPoint = ray.GetPoint (hitdist);
}
}
So now instead of only doing the rotation while MouseButton(0) is down, we do the rotation continuously in the update and instead only set the target point when we click the mouse.