In a Unity VR app I'm building, I'm trying to rotate an object that is currently 'being held'. The technique I'm using is to find the desired rotation and feed it into the target objects Rigidbody via Rigidbody.MoveRotation. This works well when I use a test quaternion exposed in the inspector, but my quaternion math is completely failing me when it comes to finding the target rotation I want. I've got the position tracking working perfectly, it's just the rotation thats killing me!
The desired outcome is that the target object in question keeps its current rotation (from the moment its picked up) and simply applies the frame-by-frame difference of the VR controller. I thought this would be pretty straight forward, but I'm getting strange results. This is what I have so far:
public Rigidbody targetRigidbody;
public Quaternion controllerRotationSnapshot;
public Quaternion targetRotationSnapshot;
public float grabRadius = 0.25f;
public Collider[] grabResults;
Quaternion deltaRotation;
void Awake() {
grabResults = new Collider[5];
}
// This is triggered externally
public void Pickup() {
Physics.OverlapSphereNonAlloc(transform.position, grabRadius, grabResults);
for (int i = 0; i < grabResults.Length; i++) {
if (grabResults[i] != null) {
if (grabResults[i].CompareTag("Grabbable")) {
targetRigidbody = grabResults[0].GetComponent<Rigidbody>();
targetRotationSnapshot = targetRigidbody.transform.rotation;
i = grabResults.Length;
}
}
}
controllerRotationSnapshot = transform.rotation;
}
public void LetGo() {
if (!targetRigidbody) {
return;
}
targetRigidbody = null;
grabResults = new Collider[5];
}
void FixedUpdate() {
if (!targetRigidbody) {
return;
}
Quaternion deltaRotationDifference = transform.rotation * Quaternion.Inverse(controllerRotationSnapshot);
targetRigidbody.MoveRotation(targetRotationSnapshot * deltaRotationDifference);
}
It works perfectly as long as the target object is at a rotation of 0,0,0 (in euler angles). But the moment the target object is picked up with a different rotation at all (i.e. [X: 20, Y: -5, Z: 325] or something), the frame-by-frame controller difference (a 'delta rotation' I've heard this called?) gets applied on a completely different axis. It's very confusing because as far as I can tell, nothing about my code is applied locally, it's all using global calculations?
I've done a lot of Googlin' and can't find an answer at all, any help is greatly appreciated.
Related
I have an asteroid field spawned at runtime and droid prefabs the player can release to gather raw material from the asteroids. Everything works but the droids go to the furthest part of the asteroid field to start collecting. I realize I likely need a list of objects to sort through somehow and output the closest.
I suck at coding but managed to get this working by getting a link to the spawned asteroids adding MovementAIRigidbody target to this script & finding one with target = GameObject.Find("AsteroidNew2(Clone)").GetComponent().
This script is based on a GitHub MIT project which is using Vector3 targetPos; I believe to select the target position. Hope this makes sense to a kind soul out there.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityMovementAI
{
public class OffSetPursuitUnityMy : MonoBehaviour
{
MovementAIRigidbody target;
public Vector3 offset;
public float groupLookDist = 1.5f;
SteeringBasics steeringBasics;
OffsetPursuit offsetPursuit;
Separation separation;
NearSensor sensor;
void Start()
{
steeringBasics = GetComponent<SteeringBasics>();
offsetPursuit = GetComponent<OffsetPursuit>();
separation = GetComponent<Separation>();
target = GameObject.Find("AsteroidNew2(Clone)").GetComponent<MovementAIRigidbody>();
sensor = GameObject.Find("SeparationSensor").GetComponent<NearSensor>();
}
void LateUpdate()
{
Vector3 targetPos;
Vector3 offsetAccel = offsetPursuit.GetSteering(target, offset, out targetPos);
Vector3 sepAccel = separation.GetSteering(sensor.targets);
steeringBasics.Steer(offsetAccel + sepAccel);
/* If we are still arriving then look where we are going, else look the same direction as our formation target */
if (Vector3.Distance(transform.position, targetPos) > groupLookDist)
{
steeringBasics.LookWhereYoureGoing();
}
else
{
steeringBasics.LookAtDirection(target.Rotation);
}
}
}
}
I'm looking not so much for a throw as just maintaining motion after a ball is released by the hand in Hololens 2. Currently, I'm using the MRTK IMixedRealityTouchHandler interface, mainly the functions public void OnTouchStarted(HandTrackingInputEventData data) and OnTouchCompleted(HandTrackingInputEventData data).
On the Hololens 2 Emulator, when I release the ball with my hand (mouse), it drifts off in the air in the general direction I was pointing it towards relatively slowly, which is exactly what I want. I achieved this by reducing drag. However, once I build to the HL2 device itself, this motion is not emulated and the ball stops midair immediately after it is released. Why is this happening?
I've tried adding the line rb.AddRelativeForce(Vector3.forward * magnitude, ForceMode.Force); in OnTouchCompleted which wasn't successful. How can I maintain the ball's motion after it is released by my hand?
In general (I don't see the rest of your code) you can keep updating the velocity relative to the last frame and finally apply it.
Somewhat like e.g. (pseudo code)
private Vector3 velocity;
void BeginDrag()
{
rb.isKinematic = true;
rb.velocity = Vector3.zero;
lastFramePos = rb.position;
}
void WhileDrag(Vector3 position)
{
velocity = position -rb.position;
rb.position = position;
}
void EndDrag()
{
rb.isKinematic = false;
rb.velocity = velocity;
}
or actually even easier and probably more accurate you can directly use the
public void OnTouchCompleted(HandTrackingInputEventData data)
{
rb.velocity = data.Controller.Velocity;
}
See
HandTrackingInputEventData.Controller
IMixedRealityController.Velocity
this is what i want to achieve
I am currently trying to build a RADAR sensor on unity. I am currently using spherecast. How do i set the view angle of the sphere cast and also how do i read the angle at which an object is present in front of it.
What i have used now is Vector3.angle but this shows 160 degrees if the object is directly infront of the radar instead it should be showing 90 degrees.
Ill paste the code that i have implemented below
Any guidance is appreciated.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class spherecast : MonoBehaviour
{
Rigidbody rb;
public List<GameObject> CurrentHitObjects = new List<GameObject>();
//public GameObject curobject;
public float radius;
public float maxdist;
public LayerMask layermask;
public float velocity;
public Time deltatime;
public Vector3 previous;
private Vector3 origin;
private Vector3 direction;
private float hitdist;
// Use this for initialization
void Start()
{
foreach (GameObject cur in CurrentHitObjects)
{
previous = cur.transform.position;
}
}
// Update is called once per frame
void Update()
{
origin = transform.position;
direction = transform.forward;
hitdist = maxdist;
CurrentHitObjects.Clear();
RaycastHit[] hits = Physics.SphereCastAll(origin, radius, direction, maxdist, layermask, QueryTriggerInteraction.UseGlobal);
foreach (RaycastHit hit in hits)
{
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(Camera.main);
if (GeometryUtility.TestPlanesAABB(planes, hit.collider.bounds))
{
float angle = Vector3.Angle(transform.forward, hit.point.normalized);
float degree = Mathf.Acos(angle) * Mathf.Rad2Deg;
Vector3 pos = hit.point;
Debug.Log(hit.collider.name + "position =" + pos);
CurrentHitObjects.Add(hit.transform.gameObject);
hitdist = hit.distance;
Debug.Log(hit.transform.name + "Distance ="+ hitdist);
Debug.Log(hit.collider.name + "Angle = " + angle);
velocity = ((hit.transform.position - previous).magnitude) / Time.deltaTime;
previous = hit.transform.position;
Debug.Log(hit.transform.name + "Velocity =" + velocity);
}
else
{
return ;
}
}
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Debug.DrawLine(origin, origin + direction * hitdist);
Gizmos.DrawWireSphere(origin + direction * hitdist, radius);
}
}
As far as I can tell your code doesn't do anything. My first tip would be to remove all of your commented out code, but after that here is why your code does nothing at all:
You pass an array of objects to your script. Fine so far.
You take this entire array of objects 'CurrentHitObjects' and pass the transform.position of every single object to a single vector3. This means that all the values are overwritten except for the last one. This would be a big problem if you were trying to find the position of every single object. This would instead require vector3[]. But there is another bigger problem.
'previous', which holds transform.position of the objects is not used anywhere. So you are not actually finding the location of anything.
You use start() (which only runs once by the way) to iterate through your object array, but then you clear, CurrentHitObjects.Clear();, right at the beginning of update() (which runs many times per second by the way). The problem here, is that if you hoped to use CurrentHitObjects for anything in your code, you can't because you have wiped it before you even start doing anything with it.
Your raycast[] is shooting towards nothing. Seems to me like it just shoots forward.
You are finding the angle between the forward direction and the forward direction?
Honestly there are a lot of major problems with this code. I don't mean to be harsh, but it looks like you copy and pasted someone else's code and don't know how to use it. This needs a complete rework. If you know how to code I would throw it out and start over again. See my comment on your answer for a better way to do what you want.
If you don't know how to code, you should not be asking for freebie working code on stackoverflow. Try a unity forum instead. If you are trying to get better, see my above comments.
So here is the code I've been working on:
using System.Collections.Generic;
using UnityEngine;
public class MovePlayer : MonoBehaviour
{
public float sidewaysForce;
public float jumpingForce;
public Rigidbody2D playerRigidbody;
public Transform playerTransform;
private Vector2 forceToAdd;
private bool onGround;
private LayerMask mask = 8;
private void Start()
{
forceToAdd = new Vector2(0, 0);
}
void FixedUpdate()
{
// Basic left/right movement
forceToAdd.x = 0;
forceToAdd.y = 0;
if (Input.GetKey(KeyCode.A))
{
forceToAdd.x = -sidewaysForce;
}
else if (Input.GetKey(KeyCode.D))
{
forceToAdd.x = sidewaysForce;
}
if (Input.GetKey(KeyCode.Space) && onGround == true)
{
forceToAdd.y = jumpingForce;
onGround = false;
}
var something = Physics2D.Raycast(playerTransform.position, Vector2.down, 200000f, mask.value);
Debug.Log(something.collider);
playerRigidbody.AddForce(forceToAdd, ForceMode2D.Impulse);
}
}
And my issue is this: No matter how far I move the player upwards, whether it be to (0,5) or (0, 2044), it will still print out "Hit ground."
I've tried both Physics and Physics2D, I've tried LayerMasking, everything, and yet it won't work. I'm a beginner to Unity btw.
EDIT: I printed Physics2D.Raycast(playerTransform.position, Vector3.down, 2f).colliderand I ended up with "Player," not "Ground," any way to fix this? I tried increasing the distance to 20 and to 2000, but it yields "Player," still. Any ideas?
EDIT #2: I also tried LayerMasks, didn't work still.
The issue is that you are parsing Physics2D.Raycast the wrong parameters. As you can see in the Unity docs, the function takes a Vector2 and not a Vector3.
https://docs.unity3d.com/ScriptReference/Physics2D.Raycast.html
Also for testing Raycasts in Unity, always set the distance parameter to be Mathf.Infinity until you have designed the solution completely. This will save you a headache in debugging. It is also best practice to add a comment explaining that it is a temporary distance value and that it should be changed later.
When posting your questions in the future on forums, ensure that you properly explain your questions as I really had to look through your code to find what the purpose was.
I am going to refrain from commenting about your code design as this is stack overflow and not reddit but I teach programming at university and would love to help if you are open to leaning :)
In Unity, I want to create a platform that rotates like a propeller. When it hits an object, I want that object to go flying in a logical direction. If I just update the object's rotation every frame, the object tends to stick to the platform and pass through it at higher speeds. I'm thinking that Unity's physics would be the best solution - how can I rotate the platform at a constant speed such that it can do what I want? In addition, how can I start and stop the spinning without it speeding up or slowing down? I'm using C#.
If you're looking to incorporate Unity's physics into your platform behaviour, but don't want to deal with adding force/torque to change its rotational speed, consider using Rigidbody.angularVelocity. With this, you can also start and stop the rotation instantly (use FixedUpdate() when you're working with a RigidBody).
So your code may look like:
Vector3 activeVelocity = new Vector3(0, 10, 0);
bool isStopped = false;
RigidBody rBody;
void Start() {
rBody = GetComponent<Rigidbody>();
}
void FixedUpdate() {
if (!isStopped){
rBody.angularVelocity = activeVelocity;
}
else{
rBody.angularVelocity = Vector3.zero;
}
}
public void ActivateRotation() {
isStopped = false;
}
public void FreezeRotation() {
isStopped = true;
}
Hope this helps! Let me know if you have any questions.