I have a zoom function on my camera that works by moving it on the Z axis when I pinch my fingers on the screen. However, when the camera moves into any value greater than zero, it has adverse effects on the rest of my camera code (movement, orbiting).
I put in a place a bool that would stop my camera moving once it got to a certain value, but it makes it very jumpy. And if your pinching, the camera will still move past the value until you let go.
So what I'm trying to do now, is use Mathf.Clamp to limit the range it can move, but I'm unsure if I'm using it correctly. This is my method right now:
void CameraZoom()
{
// if fingers moved certain distance apart, zoom
float dot = Vector2.Dot(Input.GetTouch(0).deltaPosition.normalized, Input.GetTouch(1).deltaPosition.normalized);
if (dot < fingerDistance)
{
// Store both touches.
Touch touchZero = Input.GetTouch(0);
Touch touchOne = Input.GetTouch(1);
Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
float deltaMagnitudeDiff = prevTouchDeltaMag - touchDeltaMag;
// apply zoom to camera
transform.Translate(0, 0, -deltaMagnitudeDiff * perspectiveZoomSpeed);
// clamp movement
transform.position = new Vector3(transform.position.x, transform.position.y, Mathf.Clamp(0, 0, maxDistance));
}
}
}
I only want this to affect the Z axis, but when I zoom, my whole rig gets moved. What is it that I'm doing wrong?
Update
Ive changed my code to the following, but now when I zoom, it just jumps between two points and I can no longer zoom.
New code:
transform.Translate(0, 0, -deltaMagnitudeDiff * perspectiveZoomSpeed);
Vector3 pos = transform.position;
pos.z = Mathf.Clamp(transform.position.z, 0.0f, -200.0f);
transform.position = pos;
Check the documentation for the Mathf.Clamp. You are using the parameters in the wrong order, which breaks the internal implementation of the function. The min value should be the second and the max value should be the third. So changing your line to this should stop the odd behavior:
pos.z = Mathf.Clamp(transform.position.z, -200.0f, 0.0f);
Related
I'm trying to get an angle from two Vector2 positions.
The points are found by the following raycast code:
RaycastHit2D hit = Physics2D.Raycast(groundedPos.transform.position, Vector3.down, 1, lmask); // lmask is only the blocks
Vector2 firstPos = hit.point;
RaycastHit2D hit2 = Physics2D.Raycast(groundedPos.transform.position + new Vector3(5f, 0, 0), Vector3.down, 1, lmask);
Vector2 secondPos = hit2.point;
How would I get an angle from these two Vector3 points?
I would then need to change the rotation of my object after this.
Edit:
Flat ground:
Not flat:
As you can see in the images, the red raycasts are where the raycast codes hit and they are displayed, the black square with the circle colliders is the player, and the two blocks is the thing I'm trying to change the rotation of. It is fine on flat, as you can see. But non flat it is messed up.
First a general issue with your code is the maxDistance. You are passing in 1 as the maximum distance for both your raycasts but your image shows that this might not hit the ground if it isn't flat
=> one of the positions might be "invalid" since the raycast doesn't hit the ground and therefore the value would just be a default (0, 0) which will mess up the rotation.
you either want to wrap this properly and do e.g.
var hit = Physics2D.Raycast(groundedPos.transform.position, Vector2.down, 1, lmask);
var hit2 = Physics2D.Raycast(groundedPos.transform.position + new Vector3(5f, 0, 0), Vector2.down, 1, lmask);
if(hit.collider && hit2.collider)
{
var firstPos = hit.point;
var secondPos = hit2.point;
...
}
or rather use float.PositiveInfinity as maximum distance for the rays so they will always hit something.
var hit = Physics2D.Raycast(groundedPos.transform.position, Vector2.down, float.PositiveInfinity, lmask);
var hit2 = Physics2D.Raycast(groundedPos.transform.position + new Vector3(5f, 0, 0), Vector2.down, float.PositiveInfinity, lmask);
var firstPos = hit.point;
var secondPos = hit2.point;
...
Then alternativ to this you can also use
var direction = secondPos - firstPos;
transform.rotation = Quaternion.Euler(0, 0, Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg);
which might be slightly more efficient.
Or simply set
transform.right = direction;
Or in case this is about a Rigidbody2D you would rather go through
rigidbody2D.MoveRotation(Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg);
Once you've got the two positions, find out the angle from the starting point to the second point, then apply the Euler rotation, rotating around the z-axis ..
transform.rotation = Quaternion.Euler ( 0, 0, Vector2.SignedAngle ( Vector2.right, secondaPos - firstPos ) );
Note - if you are trying to rotate a child of a GameObject, you might be "double" rotating the object, which is what it looks like in the images you updated your post with. If the colliders are attached to a child object, do you need to rotate that object as well?
So I'm working with a depth-camera that works like kinect. (it's not kinect). and with Nuitrack behind it for skeleton tracking. However, The position it returns for the player is shaky. When standing perfectly still it returns numbers that can go up or down by up to like 10.
Example: User is standing as still as he can and the data returns position 100 the first frame, and the next frame it's 102, then it's 97 then its 100 again, then it's 106 etc. It returns these positions in the update and we use it to move a image with it. (so the user controls the image) But as you might expect this image is moving very shaky because of the inconsistent data. According to Nuitrack this is right, and the user itself needs to find a solution for this.
I tried lerping from one position to another, but this makes it feel less interactive, because once i'm on the point where the lerp is actually smooth, it has a huge delay. I also tried only using the new position data if it differs lets say 4 pixels from the previous position nuitrack gave me, this works a bit better but results in jumping of the image, even if I lerp it as well. Using this function:
foreach (User user in frame.Users)
{
if (Vector3.Distance(_lastPos, user.Proj.ToVector3()) >4f)
{
Vector3 final = ((_lastPos + user.Proj.ToVector3()) /2);
userData.Add(new UserData(user.ID, user.Real.ToVector3(), final));
_lastPos = user.Proj.ToVector3();
}
else
{
userData.Add(new UserData(user.ID, user.Real.ToVector3(), _lastPos));
}
And the lerp function:
float _userX = user.ProjPosition.x * (_cameraPos.x *2)- _cameraPos.x;
Vector3 _newPos = new Vector3(_userX, _basketPos.y, _basketPos.z);
_basketPrefab.transform.position = Vector3.Lerp(_basketPrefab.transform.position, _newPos, Time.deltaTime * 30f);
EDIT: Anyone else?
You can try keeping a list of the last n positions then calculate the average of these positions by lerping them together using 0.5f (halfway value) as t. You can then increase the level of "smoothness" by Lerping the previously lerped positions again with each other, making it more smooth with every iteration. Every iteration will however make it feel a bit more sluggish, and a balance needs to be found between smooth and reactive.
(example untested).
List<Quaternion> lastPosition = new List<Quaternion>(); //Keep a list of the last positions
int smoothing = 16; //Max amount of smoothing, higher means more positions will be used for the smoothness
enum SmoothingLevels { None, Mild, Moderate, Severe, Extreme } //Level of smoothness you want to use
SmoothingLevels smoothingLevel;
Vector3 pos;
//remove the oldest entry from the list
if(lastPosition.Count > 0)
{
lastPosition.RemoveAt(0);
}
//Add the newest data to the list
while (lastPosition.Count < smoothing)
{
lastPosition.Add(transform.position);
}
Vector3 vecA = lastPosition[0];
Vector3 vecB = lastPosition[1];
Vector3 vecC = lastPosition[2];
Vector3 vecD = lastPosition[3];
Vector3 vecE = lastPosition[4];
Vector3 vecF = lastPosition[5];
Vector3 vecG = lastPosition[6];
Vector3 vecH = lastPosition[7];
Vector3 vecI = lastPosition[8];
Vector3 vecJ = lastPosition[9];
Vector3 vecK = lastPosition[10];
Vector3 vecL = lastPosition[11];
Vector3 vecM = lastPosition[12];
Vector3 vecN = lastPosition[13];
Vector3 vecO = lastPosition[14];
Vector3 vecP = lastPosition[15];
//Lerp each subsequent position by t 0.5 to get the average position of the two.
//This is the base smoothing, where smoothing is low and responsiveness is high
Vector3 vecAB = Vector3.Lerp(vecA, vecB, 0.5f);
Vector3 vecCD = Vector3.Lerp(vecC, vecD, 0.5f);
Vector3 vecEF = Vector3.Lerp(vecE, vecF, 0.5f);
Vector3 vecGH = Vector3.Lerp(vecG, vecH, 0.5f);
Vector3 vecIJ = Vector3.Lerp(vecI, vecJ, 0.5f);
Vector3 vecKL = Vector3.Lerp(vecK, vecL, 0.5f);
Vector3 vecMN = Vector3.Lerp(vecM, vecN, 0.5f);
Vector3 vecOP = Vector3.Lerp(vecO, vecP, 0.5f);
//moderate smoothing, Lerp the previously lerped position again with each other to increase the smoothness
Vector3 vecABCD = Vector3.Lerp(vecAB, vecCD, 0.5f);
Vector3 vecEFGH = Vector3.Lerp(vecEF, vecGH, 0.5f);
Vector3 vecIJKL = Vector3.Lerp(vecIJ, vecKL, 0.5f);
Vector3 vecMNOP = Vector3.Lerp(vecMN, vecOP, 0.5f);
//Severe smoothing, High smoothness, lower responsiveness
Vector3 vecABCDEFGH = Vector3.Lerp(vecABCD, vecEFGH, 0.5f);
Vector3 vecIJKLMNOP = Vector3.Lerp(vecIJKL, vecMNOP, 0.5f);
//Extreme smoothing, this will take the average of all 16 positions. Very smooth, feels really sluggish
Vector3 vecABCDEFGHIJKLMNOP = Vector3.Lerp(vecABCDEFGH, vecIJKLMNOP, 0.5f);
switch (smoothingLevel)
{
case SmoothingLevels.None:
pos = transform.position;
break;
case SmoothingLevels.Mild:
pos = vecOP;
break;
case SmoothingLevels.Moderate:
pos = vecMNOP;
break;
case SmoothingLevels.Severe:
pos = vecIJKLMNOP;
break;
case SmoothingLevels.Extreme:
pos = vecABCDEFGHIJKLMNOP;
break;
}
//apply pos to your target object
I have a problem, I'm making an smooth camera movement script, and I can't figure out how to clamp correctly camera rotation.
Actually, camera is a child of player object.
When WASD is pressed the player is rotated, but the camera doesn't follow this rotation because of the script.
var mouseDelta = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y"));
mouseDelta = Vector2.Scale(mouseDelta, CursorSensitivity); //new Vector2(CursorSensitivity.x * smoothing.x, CursorSensitivity.y * smoothing.y));
if (m_doSmooth)
{
_smoothMouse.x = Mathf.Lerp(_smoothMouse.x, mouseDelta.x, 1f / smoothing.x);
_smoothMouse.y = Mathf.Lerp(_smoothMouse.y, mouseDelta.y, 1f / smoothing.y);
// Find the absolute mouse movement value from point zero.
_mouseAbsolute += _smoothMouse;
}
else
_mouseAbsolute += mouseDelta;
if (clampInDegrees.x > 0)
_mouseAbsolute.x = Mathf.Clamp(_mouseAbsolute.x, -clampInDegrees.x, clampInDegrees.x);
if (clampInDegrees.y > 0)
_mouseAbsolute.y = Mathf.Clamp(_mouseAbsolute.y, -clampInDegrees.y, clampInDegrees.y);
Camera.transform.rotation = Quaternion.AngleAxis(-_mouseAbsolute.x, Vector3.up) //transform.InverseTransformDirection(Vector3.up))
* Quaternion.AngleAxis(_mouseAbsolute.y, Vector3.right);
Well, the problem is easy, the clamped rotation values are global, because we set Camera.transform.rotation instead of Camera.transform.localRotation.
And this happens: Link to video
The script clamps values according to player rotation.
So if we have a rotation (eulerAngle) on the player on the X axis of 90 degrees, and clamp value is from 90 to -90, the clamped rotation will be 0, 180, instead of -90, 90.
But if I change from rotation to localRotation this happens: Link to video
I also tried the following:
Vector3 euler = Camera.transform.eulerAngles;
if (clampInDegrees.x > 0)
euler.x = ClampAngle(euler.x, -clampInDegrees.x, clampInDegrees.x);
if (clampInDegrees.x > 0)
euler.y = ClampAngle(euler.y, -clampInDegrees.y, clampInDegrees.y);
Camera.transform.eulerAngles = euler;
After setting localRotation... But this only make weird things. Like for example, when reaching the min clamp value at rotating the clamp returns the max clamp value.
I have a Cube(Player) in my game scene. I have written a C# script to constrain the movement(using Mathf.Clamp()) of the Cube so it does not leave the screen.
Below is my FixedUpdate() method from the Script
private void FixedUpdate()
{
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
rb.velocity = movement * speed;
rb.position = new Vector3(
Mathf.Clamp (rb.position.x, x_min, x_max),
0.5f,
Mathf.Clamp(rb.position.z, z_min, z_max)
);
}
Values of x_min, x_max, z_min, z_max are -3, 3, -2, 8 respectively inputted via the unity inspector.
PROBLEM
The script is working fine but my player(Cube) can move up to -3.1 units in negative X-AXIS (if I keep pressing the left arrow button) which is 0.1 units more in negative X-AXIS(This behavior is true for other axes too). It obviously clamps -3.1 to -3 when I stop pressing the button.
Why is this happening? Why doesn't it(Mathf.Clamp()) restrict the Cube to -3 units in first place?
Why am I getting that extra 0.1 units?
And Why is this value 0.1 units only? Why not more or less?
Your issue could be caused because you are setting velocity and then position, but before the next frame unity adds the velocity to your objects position. Which is why it ends up 0.1 units off.
To fix this, try resetting the velocity of the object to zero if it's about to leave your boundaries.
This is happening because the internal physics update moves the cube after you have put it in its clamped position. Mathf.Clamp() does restrict the cube to the expected position in the first place, as you can verify by putting a Debug.Log directly after you assign the position
You are getting the extra .1 units because you give the object a speed that gets applied after you clamp
It is 0.1 units because of the speed and the setting for Fixed Timestep (in Project Settings > Time). If you would set a higher speed it would be more. If you would lower the Fixed Timestep it would also be a bit more.
You already got the reason why this happens.
In order to fix the problem, you can use this code to prevent the cube to go outside your boundaries:
private void FixedUpdate() {
Vector3 movement = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical"));
// Get new position in advance
float xPos = rb.position.x + movement.x * speed * Time.fixedDeltaTime;
float zPos = rb.position.z + movement.z * speed * Time.fixedDeltaTime;
// Check if new position goes over boundaries and if true clamp it
if (xPos > x_max || xPos < x_min) {
if (xPos > x_max)
rb.position = new Vector3(x_max, 0, rb.position.z);
else
rb.position = new Vector3(x_min, 0, rb.position.z);
movement.x = 0;
}
if (zPos > z_max || zPos < z_min) {
if (zPos > z_max)
rb.position = new Vector3(rb.position.x, 0, z_max);
else
rb.position = new Vector3(rb.position.x, 0, z_min);
movement.z = 0;
}
rb.velocity = movement * speed;
}
I am learning Unity and using the tutorials for Evac City with version 4.3. This sample is a top down 2D shooter that uses the mouse for rotation and the keyboard for movement using the arrows or WASD keys.
One thing that I was trying and not having much luck with was with changing the orientation of the movement. I would like the keyboard movement to be relative to the the direction of the player instead of to the world plane so that the W key will move you forward in the direction you are facing, the S key moves you back, the A key slides you left and the D keys slides you right.
The relevant parts of the code seem to be:
void FindPlayerInput()
{
// find vector to move
inputMovement = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
// find vector to the mouse
tempVector2 = new Vector3(Screen.width * 0.5f, 0, Screen.height * 0.5f); // the position of the middle of the screen
tempVector = Input.mousePosition; // find the position of the moue on screen
tempVector.z = tempVector.y; // input mouse position gives us 2D coordinates, I am moving the Y coordinate to the Z coorindate in temp Vector and setting the Y coordinate to 0, so that the Vector will read the input along the X (left and right of screen) and Z (up and down screen) axis, and not the X and Y (in and out of screen) axis
tempVector.y = 0;
inputRotation = tempVector - tempVector2; // the direction we want face/aim/shoot is from the middle of the screen to where the mouse is pointing
}
void ProcessMovement()
{
tempVector = rigidbody.GetPointVelocity(transform.position) * Time.deltaTime * 1000;
rigidbody.AddForce(-tempVector.x, -tempVector.y, -tempVector.z);
rigidbody.AddForce(inputMovement.normalized * moveSpeed * Time.deltaTime);
transform.rotation = Quaternion.LookRotation(inputRotation);
transform.eulerAngles = new Vector3(0, transform.eulerAngles.y + 180, 0);
print("x:" + transform.eulerAngles.x + " y:" + transform.eulerAngles.y + " z:" + transform.eulerAngles.z);
transform.position = new Vector3(transform.position.x, 0, transform.position.z);
}
I have tried a few different things and recognize that the Input.GetAxis calls come from the keyboard mappings but not how to re-orient the movement to the player axis.
What you want to do is transform the input direction from the player's local space to world space, to put it in the good-to-know "space" terminology. You can do this with Transform.TransformDirection:
Vector3 worldInputMovement = transform.TransformDirection(inputMovement.normalized);
rigidbody.AddForce(worldInputMovement * moveSpeed * Time.deltaTime);
This works because TransformDirection will rotate the vector you give it to put it in world space, and AddForce expects a world-space vector.