still working on VR interactions, I want to be able to rotate objects but I'm facing an issue.
For instance, I want to open/close the upper part of a laptop using my hands in VR. What I'm doing to achieve this, is that I placed the forward like that :
I'm creating a plane using position, forward, up. Then get the closest point on plane corresponding to my VR controller, then use transform.LookAt.
This is working fine, but I want to be able to clamp the rotation, so I cannot rotate too much (see the end of the video).
I've been trying everything, using eulersAngle and Quaternion, but I'm unable to do it.
I made some helpers (the text to show the localEulerAngles, and a transform to LookAt so I don't have to use the VR headset as it's getting pretty tedious)
Here is a video showing what's going on : https://www.youtube.com/watch?v=UfN97OpYElk
And here's my code :
using UnityEngine;
public class JVRLookAtRotation : MonoBehaviour, IJVRControllerInteract
{
[SerializeField] private Transform toRotate;
[SerializeField] private Vector3 minRotation;
[SerializeField] private Vector3 maxRotation;
[Header("Rotation contraints")]
[SerializeField] private bool lockX;
[SerializeField] private bool lockY;
[SerializeField] private bool lockZ;
private JVRController _jvrController;
private bool _isGrabbed;
private Vector3 _targetPosition;
private Vector3 _tmp;
public Transform followTransform;
private void LateUpdate()
{
/*
if (!_isGrabbed) return;
if (_jvrController.Grip + _jvrController.Trigger < Rules.GrabbingThreshold)
{
_isGrabbed = false;
_jvrController.StopGrabbing();
_jvrController = null;
return;
}
*/
Vector3 up = toRotate.up;
Vector3 forward = toRotate.forward;
Vector3 pos0 = toRotate.position;
Vector3 pos1 = pos0 + up;
Vector3 pos2 = pos0 + forward;
Plane p = new Plane(pos0, pos1, pos2);
// Using followTransform just to no have to use VR, otherwise it's the controller pos
_targetPosition = p.ClosestPointOnPlane(followTransform.position);
toRotate.LookAt(_targetPosition, up);
/*
_tmp = toRotate.localEulerAngles;
_tmp.x = Mathf.Clamp(WrapAngle(_tmp.x), minRotation.x, maxRotation.x);
_tmp.y = WrapAngle(_tmp.y);
_tmp.z = WrapAngle(_tmp.z);
toRotate.localRotation = Quaternion.Euler(_tmp);
*/
}
public void JVRControllerInteract(JVRController jvrController)
{
if (_isGrabbed) return;
if (!(jvrController.Grip + jvrController.Trigger > Rules.GrabbingThreshold)) return;
_jvrController = jvrController;
_jvrController.SetGrabbedObject(this);
_isGrabbed = true;
}
private static float WrapAngle(float angle)
{
angle%=360;
if(angle >180)
return angle - 360;
return angle;
}
private static float UnwrapAngle(float angle)
{
if(angle >=0)
return angle;
angle = -angle%360;
return 360-angle;
}
}
Suppose the monitor's parent transform is the body/keyboard of the laptop. Local axes of the parent shown below:
To describe the range of motion you can define a "center of rotation" vector (e.g., grey vector labeled C) that is local to the parent and an angle (e.g., 110 degrees, between each purple vector and the grey vector). For instance:
[SerializeField] private Vector3 LocalRotationRangeCenter = new Vector3(0f, 0.94f, 0.342f);
[SerializeField] private float RotationRangeExtent = 110f;
Then, you can take the forward vector it "wants" to go, and find the signed angle between the world direction of RotationRangeCenter and that point, then clamp it to ±RotationRangeExtent:
Vector3 worldRotationRangeCenter = toRotate.parent.TransformDirection(RotationRangeCenter);
Vector3 targetForward = _targetPosition - toRotate.position;
float targetAngle = Vector3.SignedAngle(worldRotationRangeCenter, targetForward,
toRotate.right);
float clampedAngle = Mathf.Clamp(targetAngle, -RotationRangeExtent, RotationRangeExtent);
Then, find the direction that corresponds to that angle. Finally, rotate the monitor so that its forward aligns with the clamped forward and its right doesn't change. You can use a cross product to find what the monitor's up would be, then use Quaternion.LookRotation to find the corresponding rotation:
Vector3 clampedForward = Quaternion.AngleAxis(clampedAngle, toRotate.right)
* worldRotationRangeCenter;
toRotate.rotation = Quaternion.LookRotation(clampedForward,
Vector3.Cross(clampedForward, toRotate.right));
If someone tries to drag the monitor too far beyond the "boundaries" it will teleport from one limit to the other. If that's not desired behavior, you might consider interpolating from SignedAngle(worldRotationRangecenter, targetForward, toRotate.right) to clampedAngle, for a movement between the limits:
private float angleChangeLimit = 90f; // max angular speed
// ...
Vector3 worldRotationRangeCenter = toRotate.parent.TransformDirection(RotationRangeCenter);
Vector3 targetForward = _targetPosition - toRotate.position;
float targetAngle = Vector3.SignedAngle(worldRotationRangeCenter, targetForward,
toRotate.right);
float clampedAngle = Mathf.Clamp(targetAngle, -RotationRangeExtent, RotationRangeExtent);
float currentAngle = Vector3.SignedAngle(worldRotationRangeCenter, toRotate.forward,
toRotate.right);
clampedAngle = Mathf.MoveTowards(currentAngle, clampedAngle,
angleChangeLimit * Time.deltaTime);
Vector3 clampedForward = Quaternion.AngleAxis(clampedAngle, toRotate.right)
* worldRotationRangeCenter;
toRotate.rotation = Quaternion.LookRotation(clampedForward,
Vector3.Cross(clampedForward, toRotate.right));
#Ruzihm's answer worked with just a bit of tweaking ! I couldn't have got that myself honestly.
Here is the full code updated for VR, if anyone is interested :
using UnityEngine;
public class JVRLookAtRotation : MonoBehaviour, IJVRControllerInteract
{
[SerializeField] private Transform toRotate;
[SerializeField] private Vector3 minRotationDelta;
[SerializeField] private Vector3 maxRotationDelta;
private JVRController _jvrController;
private bool _isGrabbed;
private Vector3 _targetPosition;
// No clue where does this come from
private Vector3 _localRotationRangeCenter = new Vector3(0, 0.999f, 0.044f);
private void LateUpdate()
{
if (!_isGrabbed) return;
if (_jvrController.Grip + _jvrController.Trigger < Rules.GrabbingThreshold)
{
_isGrabbed = false;
_jvrController.StopGrabbing();
_jvrController = null;
return;
}
Vector3 up = toRotate.up;
Vector3 forward = toRotate.forward;
Vector3 right = toRotate.right;
Vector3 rotatePosition = toRotate.position;
Vector3 pos1 = rotatePosition + up;
Vector3 pos2 = rotatePosition + forward;
Plane p = new Plane(rotatePosition, pos1, pos2);
_targetPosition = p.ClosestPointOnPlane(_jvrController.CurrentPositionWorld);
Vector3 worldRotationRangeCenter = toRotate.parent.TransformDirection(_localRotationRangeCenter);
Vector3 targetForward = _targetPosition - rotatePosition;
float targetAngle = Vector3.SignedAngle(worldRotationRangeCenter, targetForward, right);
float clampedAngle = Mathf.Clamp(targetAngle, minRotationDelta.x, maxRotationDelta.x);
Vector3 clampedForward = Quaternion.AngleAxis(clampedAngle, right) * worldRotationRangeCenter;
toRotate.rotation = Quaternion.LookRotation(clampedForward, Vector3.Cross(clampedForward, right));
}
public void JVRControllerInteract(JVRController jvrController)
{
if (_isGrabbed) return;
if (!(jvrController.Grip + jvrController.Trigger > Rules.GrabbingThreshold)) return;
_jvrController = jvrController;
_jvrController.SetGrabbedObject(this);
_isGrabbed = true;
}
}
Related
Im making a game where you click and drag with the mouse then release to launch the player. But sometimes the player gets launched in the opposite direction of where it should go. I made a debug output to show you the different values. Here is the output
In that image for example you can see that the Vector2 of force * power is positive on the y axis, but the player launched downwards, and the same happens Viceversa. I think its also worth to note that this happens inconsistantly for some reason. Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Movement : MonoBehaviour
{
public GameObject player;
public float power = 10f;
public Rigidbody2D rb;
public float maxSpeed;
public Vector2 minPower;
public Vector2 maxPower;
TragectoryLine tl;
Camera cam;
public Vector2 force;
public Vector3 startPoint;
public Vector3 endPoint;
public Vector3 currentPoint;
public Vector3 startPointMouse;
public bool isPulling = false;
float distance;
private void Start()
{
cam = Camera.main;
tl = GetComponent<TragectoryLine>();
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
startPointMouse = cam.ScreenToWorldPoint(Input.mousePosition);
startPointMouse.z = 15;
}
if (Input.GetMouseButton(0))
{
startPoint = player.transform.position;
startPoint.z = 15;
isPulling = true;
Vector3 currentPoint = cam.ScreenToWorldPoint(Input.mousePosition);
currentPoint.z = 15;
tl.RenderLine(startPoint, currentPoint);
}
if (Input.GetMouseButtonUp(0))
{
endPoint = cam.ScreenToWorldPoint(Input.mousePosition);
endPoint.z = 15;
isPulling = false;
tl.EndLine();
distance = startPointMouse.magnitude - endPoint.magnitude;
if (distance < 0)
{
distance = -distance;
}
if (distance >= 1)
{
rb.AddForce(force * power, ForceMode2D.Impulse);
}
force = new Vector2(Mathf.Clamp(startPoint.x - endPoint.x, minPower.x, maxPower.x), Mathf.Clamp(startPoint.y - endPoint.y, minPower.y, maxPower.y));
Debug.Log("distance" + distance);
Debug.Log("start" + startPoint);
Debug.Log("end" + endPoint);
Debug.Log("force" +force);
Debug.Log("force * power" + force * power);
}
}
private void FixedUpdate()
{
rb.velocity = Vector3.ClampMagnitude(rb.velocity, maxSpeed);
}
}
Here I added the force using rb.AddForce(force * power, ForceMode2D.Impulse); when the force * power value was positive on the y axis. So why did it go to the opposite direction???
This was working perfectly fine before i tried implementing a feature where the player has to move the mouse a certain distance or else it wont launch. I have tried removing it but it doesnt seem to make a difference. I think I changed something in the code that ruined it but I cant figure out what! Please help!
I´ve followed a youtube tutorial https://www.youtube.com/watch?v=c1FYp1oOFIs&list=PLD_vBJjpCwJtrHIW1SS5_BNRk6KZJZ7_d&index=4
and the problem is when i start the game my camera moves up when it shouldnt.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace BARR3TT
{
public class CameraHandler : MonoBehaviour
{
public Transform targetTransform;
public Transform cameraTransform;
public Transform cameraPivotTransform;
private Transform myTransform;
private Vector3 cameraTransformPosition;
private LayerMask ignoreLayers;
private Vector3 cameraFollowVelocity = Vector3.zero;
public static CameraHandler singleton;
public float lookSpeed = 0.1f;
public float followSpeed = 0.1f;
public float pivotSpeed = 0.03f;
private float targetPosition;
private float defaultPosition;
private float lookAngle;
private float pivotAngle;
public float minimumPivot = -35;
public float maximumPivot = 35;
private float cameraSphereRadius = 0.2f;
public float cameraColisionOffset = 0.2f;
public float minimumColisionOffset = 0.2f;
private void Awake()
{
singleton = this;
myTransform = transform;
defaultPosition = cameraTransform.localPosition.z;
ignoreLayers = ~(1 << 8 | 1 << 9 << 10);
}
public void FollowTarget(float delta)
{
Vector3 targetPosition = Vector3.SmoothDamp(myTransform.position, targetTransform.position, ref cameraFollowVelocity, delta / followSpeed);
myTransform.position = targetPosition;
HandleCameraCollision(delta);
}
public void HandleCameraRotation(float delta, float mouseXInput, float mouseYInput)
{
lookAngle += (mouseXInput * lookSpeed) / delta;
pivotAngle -= (mouseYInput * pivotSpeed) / delta;
pivotAngle = Mathf.Clamp(pivotAngle, minimumPivot, maximumPivot);
Vector3 rotation = Vector3.zero;
rotation.y = lookAngle;
Quaternion targetRotation = Quaternion.Euler(rotation);
myTransform.rotation = targetRotation;
rotation = Vector3.zero;
rotation.x = pivotAngle;
targetRotation = Quaternion.Euler(rotation);
cameraPivotTransform.localRotation = targetRotation;
}
private void HandleCameraCollision(float delta)
{
targetPosition = defaultPosition;
RaycastHit hit;
Vector3 direction = cameraTransform.position - cameraPivotTransform.position;
direction.Normalize();
if(Physics.SphereCast(cameraPivotTransform.position, cameraSphereRadius, direction, out hit ,Mathf.Abs(targetPosition)))
{
float dis = Vector3.Distance(cameraPivotTransform.position, hit.point);
targetPosition = -(dis - cameraColisionOffset);
}
if(Mathf.Abs(targetPosition)<minimumColisionOffset)
{
targetPosition = -minimumColisionOffset;
}
cameraTransformPosition.z = Mathf.Lerp(cameraTransform.localPosition.z, targetPosition, delta / 0.2f);
cameraTransform.localPosition = cameraTransformPosition;
}
}
}
Ive checked every line and i dont see any mistakes the transforms are where they should be, i can kinda fix it adding some rotation in the x axis on the main camera but its still weird
You need to check if your camera has been attached to any script or if it is a child inside some objects. I think it is where the error comes from.
If the Camera Holder or Camera Pivot changes its direction so the Main Camera will be changed too.
Your code is very complicated and it doesn't have any comments. So that is why people when seeing your code are lazy to see, analyze and answer for you.
If you just begin with coding and I recommend that you need to build a good foundation first and then go to some difficult tutorials like those you followed. Don't follow step-by-step tutorials.
I'm working on a third-person run-around game and there's a specific kind of movement I've wanted to achieve but I'm having trouble even imagining how it would work, let alone actually coding it.
Essentially, when holding left or right, I want the player to orbit the camera. Such a camera effect can be seen here. That's exactly what I want to achieve.
Here's my movement and camera code so far. I image I'll need to use the camera's Y rotation to achieve this but my tests haven't worked out. Any input would be appreciated!
Movement:
public int speed = 10;
public int rotationSpeed = 10;
public CharacterController cc;
Vector2 input;
Vector3 moveDir;
Vector3 lookDir;
Vector3 forward;
Transform camTransform;
public Transform model;
void Start () {
cc = GetComponent<CharacterController>();
camTransform = Camera.main.transform;
}
void GetInput() {
input.x = Input.GetAxisRaw("Horizontal");
input.y = Input.GetAxisRaw("Vertical");
}
void CalculateForward()
{
}
void Move()
{
moveDir.x = input.x;
moveDir.z = input.y;
if(moveDir.magnitude > 1)
{
moveDir.Normalize();
}
Vector3 rotatedDir = Camera.main.transform.TransformDirection(moveDir);
rotatedDir = new Vector3(rotatedDir.x, 0, rotatedDir.z);
rotatedDir = rotatedDir.normalized * moveDir.magnitude;
cc.Move(rotatedDir * speed * Time.deltaTime);
}
void ApplyGravity()
{
}
void FaceDir()
{
if (moveDir != Vector3.zero)
{
transform.rotation = Quaternion.Slerp(
transform.rotation,
Quaternion.LookRotation(-moveDir),
Time.deltaTime * rotationSpeed
);
}
}
private void FixedUpdate()
{
GetInput();
//ApplyGravity();
Move();
FaceDir();
ApplyGravity();
}
}
Camera:
[SerializeField]
private float distanceAway;
[SerializeField]
private float distanceUp;
[SerializeField]
private float smooth;
[SerializeField]
private Transform follow;
private Vector3 targetPosition;
private void Start()
{
follow = GameObject.FindWithTag("Player").transform;
}
private void LateUpdate()
{
targetPosition = follow.position + follow.up * distanceUp - follow.forward * distanceAway;
transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * smooth);
transform.LookAt(follow);
}
Movement around a circle is always in a direction tangential to the circle, so with each update you want to:
Set the Y rotation of the player to be looking directly at the Y axis of the camera
rotate the player 90 degrees left or right (depending on the direction you want to go)
move the player forward a distance that will depend on the speed with which you would like them to move
Remember it's impossible to traverse a circle perfectly because there are infinitely many points on a circle but this approach will approximate it well enough.
I'm working on my latest project, which is a action sidescroller, primarily 2D but using a perspective camera for 3D effect. I've gotten the basics working - design, etc, and now fixing the camera so that it follows correctly. However, I'd like to keep the player on the left side of the viewport - not actually modifying the player's position, but the camera following an offset. I have it partially worked out, but it broke the moment I started playing around with other alternative resolutions - say, from web standard, standalone, to mobile devices. This is what I want to do:
This is my code attempt thus far. The camera itself works perfectly for basic up/down right scrolling, but when the resolution changes the player can vanish:
public Transform TrackTarget;
public float dampTime = 0.15f;
public bool Ready = false;
private Vector3 velocity = Vector3.zero;
private Vector3 TargetOffset = Vector3.zero;
void Start()
{
TargetOffset = new Vector3(16,0,0);
Vector3 point = TrackTarget.position + TargetOffset;
Vector3 Destination = new Vector3(point.x, transform.position.y, transform.position.x);
transform.position = Destination;
}
void FixedUpdate () {
if (Ready) {
if (TrackTarget)
{
Vector3 point = TrackTarget.position + TargetOffset;
Vector3 Destination = new Vector3(point.x, transform.position.y, transform.position.x);
transform.position = Vector3.SmoothDamp(transform.position, Destination, ref velocity, dampTime);
}
}
}
void LateUpdate()
{
if (Ready) {
Vector3 CameraPosition = transform.position;
CameraPosition.z = -30.00f;
transform.position = CameraPosition;
}
}
This may be helpful.
public class CameraController : MonoBehaviour {
public Transform target;
public float distance = 5;
public float heigt = 1;
public float width = 6;
public float damping = 5;
void Start()
{
Vector3 unicornPos = camera.WorldToViewportPoint(target.position);
width = camera.ViewportToWorldPoint(new Vector3(1, 1, camera.nearClipPlane)).x/3;
}
void LateUpdate () {
Vector3 wantedPosition = target.TransformPoint (width, heigt, -distance);
transform.position = Vector3.Lerp (transform.position, wantedPosition, Time.deltaTime * damping);
}
}
I have a 3D object on Unity and what I want is simply move this object as soon as the user presses the screen. The problem is that it needs to accelerate first and then, when it is reaching the pressed position, it starts decelerating until it totally stops on that point.
I need this to work in a way that if the user moves his finger, the object will recalculate if it needs to accelerate/decelerate based on his current condition.
I tried this example but, it only works for acceleration and it also seems a little confusing. So, I was thinking if someone else have a better and simple idea to solve this. Can physics help with this? If so, how?
My code is in C#.
Using unity rigidbody system and simple calculate. This is using mouse position, you can change it to touch.
public class MyDragMove : MonoBehaviour {
public float speedDelta = 1.0f;
public float decelerate = 1.0f;
private bool startDrag;
private Vector3 prePos;
void Update () {
this.rigidbody.drag = decelerate; //Rigidbody system can set drag also. You can use it and remove this line.
if (Input.GetKeyDown (KeyCode.Mouse0)) {
prePos = Input.mousePosition;
startDrag = true;
}
if(Input.GetKeyUp(KeyCode.Mouse0))
startDrag = false;
if(startDrag)
ForceCalculate();
}
void ForceCalculate()
{
Vector3 curPos = Input.mousePosition;
Vector3 dir = curPos - prePos;
float dist = dir.magnitude;
float v = dist / Time.deltaTime;
this.rigidbody.AddForce (dir.normalized * v * Time.deltaTime * speedDelta);
prePos = curPos;
}
}
or just use SmoothDamp toward last position.
public class MyDragMove : MonoBehaviour {
public float speedDelta = 1.0f;
public float maxSpeed = 5.0f;
public Vector3 v;
private Vector3 prePos;
void Update () {
if(Input.GetKey(KeyCode.Mouse0))
prePos = Input.mousePosition;
TowardTarget ();
}
void TowardTarget()
{
Vector3 targetPos = Camera.main.ScreenToWorldPoint (new Vector3(prePos.x, prePos.y, 10f)); //Assume your camera's z is -10 and cube's z is 0
transform.position = Vector3.SmoothDamp (transform.position, targetPos, ref v, speedDelta, maxSpeed);
}
}