Get child object's rotation for use in RotateTowards - c#

I'm creating a 'steady cam' like object in VR that the player can grab and point like they are operating a real video camera.
The prop camera contains an empty GameObject called CameraTarget where it's lens would be, and using a script (Udon as this is within VRChat) I monitor for a notable change in location (to avoid jitter from unsteady hands) and if so, I use Vector3.MoveTowards() to smoothly translate the location of another object called CameraActual on which is attached a Camera, and then Quaternion.RotateTowards() to do the same with the angle.
However, the latter doesn't seem to do anything because as far as I can tell, the CameraTarget's object never changes its rotation angle, even as the prop its attached to is clearly being rotated in space. The value of the RotateTowards() operations is always the same even as I swing the camera around. I also tried using CameraTarget's parent object (the camera 3D prop) but that also seemed to not change its value. I can visually confirm this both from the image captured by the camera and as CameraActual has an in world visualisation and it remains visually locked in its starting orientation.
How do I get the real camera to point in the same direction it's in game representation is pointing?
Object layout:
Root
CameraPropRoot
[CameraMeshes]
CameraTarget (Empty GameObject)
CameraActualRoot
UnityCamera
Pseudo Code of Udon Graph:
speed = 3;
Update() {
var distance = Vector3.Distance(
ActualCamera.Transform.Position,
CameraTarget.Transform.Position);
if (distance > 0.05) {
var newPosition = Vector3.MoveTowards(
ActualCamera.Transform.Position,
CameraTarget.Transform.Position,
speed * Time.deltaTime);
ActualCamera.Transform.Position = newPosition;
}
// Todo: ... Code here to check rotation difference
var newRotation = Quaternion.RotateTowards(
ActualCamera.Transform.rotation,
CameraTarget.Transform.Rotation,
speed * Time.deltaTime);
ActualCamera.Transform.rotation = newRotation;
}

Related

Use MoveRotation to Look At Another Object Unity3d

Basically I am looking for a simple way for using the rigidbody/physics engine to have my ship look at another object (the enemy). I figured getting the direction between my transform and the enemy transform, converting that to a rotation, and then using the built in MoveRotation might work but it is causing this weird effect where it just kind of tilts the ship. I posted the bit of code as well as images of before and after the attempt to rotate the ship (The sphere is the enemy).
private void FixedUpdate()
{
//There is a user on the ship's control panel.
if (user != null)
{
var direction = (enemyOfFocus.transform.position - ship.transform.position);
var rotation = Quaternion.Euler(direction);
ship.GetComponent<Rigidbody>().MoveRotation(rotation);
}
}
Before.
After.
Well the Quaternion.Euler is a rotation about the given angles, for convenience provided as a Vector3.
Your direction is rather a vector pointing from ship towards enemyOfFocus and has a magnitude so the x, y, z values also depend on the distance between your objects. These are no Euler angles!
What you rather want is Quaternion.LookRotation (the example in the docs is basically exactly your use case ;) )
var direction = enemyOfFocus.transform.position - ship.transform.position;
var rotation = Quaternion.LookRotation(direction);
ship.GetComponent<Rigidbody>().MoveRotation(rotation);

Constrain rigidbody movement to spline at high speeds / sharp curves

I have a 2.5d platformer game. The character is using rigidbody movement on a spline (using the curvy splines asset) which curves into 3d space in all sorts of ways, while the camera stays fixed to the side so that you see the path and background turning, but maintain a 2d side scrolling perspective.
I'm essentially creating a look rotation based on the spline, then moving the player using that forward vector, and making sure to remove any velocity perpendicular to the path so that the player stays centered on the path even when curving. I'm removing the velocity on that vector instead of projecting all the velocity in the direction of the path so that the player can still jump and fall like normal.
void SetLookRotation()
{
// get nearest TF and point on spline
Vector3 p;
mTF = Spline.GetNearestPointTF(transform.localPosition, out p);
// Get forward and up vectors of point on spline
_localHorizontal = Spline.GetTangentFast(mTF);
_localVertical = Spline.GetOrientationUpFast(mTF);
// Set look rotation to path
transform.rotation = Quaternion.LookRotation(Vector3.Cross(_localHorizontal, _localVertical), _localVertical);
}
void Movement()
{
Vector3 m = transform.right * groundAcceleration * moveInput;
rb.AddForce(RemoveCrossVelocity(m));
rb.velocity = RemoveCrossVelocity(rb.velocity);
Vector3 localVelocity = transform.InverseTransformDirection(rb.velocity);
localVelocity.z = 0;
rb.velocity = transform.TransformDirection(localVelocity);
}
Vector3 RemoveCrossVelocity(Vector3 v)
{
// get magnitude going in the cross product / perpindicular of localHorizontal and localVertical vector
// (essentially the magnitude on "local Z" or to the sides of the player)
Vector3 crossVelocity = Vector3.Project(v, Vector3.Cross(transform.right, transform.up));
// and remove it from the vector
return v -= crossVelocity;
}
The first 2 functions are happening in FixedUpdate() in the order shown.
The problem is, when hitting sharp corners at high speeds, some inertia causes the player to deviate off the center of the path still just ever so slightly, and a lot of that momentum turns into upward momentum, launching the player upwards. Eventually the player can fall off the path completely (I do have a custom gravity acting towards the spline though). It works perfectly at lower speeds though, even when dealing with sharp corners. At least as far as I can tell.
I tried a bit of code from https://answers.unity.com/questions/205406/constraining-rigidbody-to-spline.html too but no luck.
Is there a way I could constrain the player rigidbody on a vector that is not one of the global x/y/z axes? I've tried a host of other solutions like setting the transform of the player towards at the center of the spline but I can't seem to get it without feeling very jerky. Using forces makes the player "rubber band" towards and past the center back and forth. Maybe there is something in my math wrong. In any case, I'm hoping someone could help me make sure that the player will always stay on the center of the spline but only on the vector to the sides of the player's face direction, so that it doesn't mess with jumping. Thank you very much in advance!
For potential future visitors, I have figured this out. There are a few components (and a lot more if you're trying to do full spline based physics, but just to start with movement...)
First we must orient our character, so that our local coordinate system can be referenced with transform.right etc. Luckily this package provides these functions which return useful vectors. I'm sure there is math beyond me to do this otherwise if you are building your own spline system.
void SetLookRotation()
{
// get nearest TF and point on spline
Vector3 p;
playerTF = currentSpline.GetNearestPointTF(transform.localPosition, out p);
// Get forward and up vectors of point on spline
_localHorizontal = currentSpline.GetTangentFast(playerTF);
_localVertical = currentSpline.GetOrientationUpFast(playerTF);
// Set look rotation to path
transform.rotation = Quaternion.LookRotation(Vector3.Cross(_localHorizontal, _localVertical), _localVertical);
}
Here I am setting a velocity directly but if you're using forces it's the same principle.
if (Mathf.Abs(localVelocityAs_X) <= maxDashSpeed * Mathf.Abs(moveInput))
{
Vector3 m = transform.right * maxDashSpeed * moveInput;
rb.velocity = RemoveCrossVelocity(m);
}
localVelocityAs_X is defined as (ran in fixedUpdate/ physics step):
float currLocalVelocityX = (playerTF - prevPositionX) / Time.deltaTime;
localVelocityAs_X = Mathf.Lerp(localVelocityAs_X, currLocalVelocityX, 0.5f);
prevPositionX = playerTF;
Where playerTF is your position on a spline (in this case, using the curvy spline package from the unity asset store. Those spline positions return very small floats so in my case I multiplied playerTF by around 10,000 to make it a more easily readable metric). This is essentially just manually calculating velocity of the player each frame by comparing current position on the spline to last frame's.
RemoveCrossVelocity is the same as above. Comment explanations should suffice.
Vector3 RemoveCrossVelocity(Vector3 v)
{
// get magnitude going in the cross product / perpendicular of local horizontal and local vertical vectors
// (essentially the magnitude on "local Z" of the player)
Vector3 crossVelocity = Vector3.Project(v, Vector3.Cross(transform.right, transform.up));
// and remove it from the vector
return v -= crossVelocity;
}
Finally the solution to the drift. My crude fix was essentially to just adjust the player to the center of the spline every frame. Horizontally, there is no change because it grabs the closest spline point which is calculated by this package to be sort of a float clamped between the start and end of the spline. Vertically, we are being set to the distance the player is from the spline in the local up direction - a fancy way of saying we're not moving vertically at all. The reason this must be done is to avoid the spline vertical position overwriting the players, and we obviously can't set this vector back to playerPos.y in our local coordinate space, so we must resort to using a direction vector * the distance from our everchanging floor. This isn't absolutely ideal at the end of the day, but it works, and there isn't any extra jitter from it (interpolate on your player's rigidbody and some camera dampening helps). All in all these together combine to make a player able to accelerate quickly around sharp corners of a spline with physics and intertia will never cause the player to fly off or drift from the center. Take that, rocket physics!
void ResetPlayerToSpline()
{
Vector3 P; //closest spline point to player
float pTf = currentSpline.GetNearestPointTF(transform.position, out P);
playerHeight = Vector3.Distance(transform.position, P);
transform.position = P + (transform.up * Vector3.Distance(transform.position, P));
}
Ultimately for those possibly looking to do some kind of implementation in the future, the biggest thing you'll run into is a lack of cardinal direction, global oriented axis-based functions and properties normally provided by a game engine. For a primer, here are a few I would use (not including gravity, which is simply opposite your up vector times whatever magnitude):
This one allows you to create a vector using x and y like normal (and z in theory) and run this function to convert it when you actually use the vector in a local space. That way, you don't have to try and think in directions without names. You can still think of things in terms of x and y:
Vector3 ConvertWorldToLocalVector(Vector3 v)
{
Vector3 c;
c = transform.right * v.x + transform.up * v.y;
return c;
}
This is basically the same as what is happening in RemoveCrossVelocity(), but it's important to reiterate this is how you set velocity in a direction to 0. The second part shows how to get velocity in a certain vector.
void Velocity_ZeroY()
{
rb.velocity -= GetLocalVerticalVelocity();
}
public Vector3 GetLocalVerticalVelocity()
{
return Vector3.Project(rb.velocity, _localVertical);
}
Getting height, since you cannot just compare y positions:
height = Vector3.Distance(transform.position, P);
I think that's all the good stuff I can think of. I noticed a severe lack of resources for created spline based physics movement in games, and I'm guessing now it's based on the fact that this was quite an undertaking. It has since been brought to my attention that the game "Pandemonium"(1996) is a curvy 3d spline based sidescrolling platformer - just like mine! The main difference seems to be that it isn't at all based on physics, and I'm not sure from what I can tell if it has pitch changes and gravity to compliment. Hope this helps someone someday, and thank you to those who contributed to the discussion.

Affecting an object's position through another object's rotation in Unity

I have a floating gameobject (brickRot) that looks at what object is beneath it through a raycast, and then changes that object's y-position (floorY) based on brickRot's rotation. However, I'm having trouble converting rotation into a useful metric for changing Y-position. I want the current rotation of brickRot to be equal to whatever the y-position of floorY is, and then only change relative to the rotation of brickRot (if that makes any sense).
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FloorRotation : MonoBehaviour {
private float brickRot;
private float floorY;
void Update () {
brickRot = transform.rotation.y;
RaycastHit hit;
Ray floorRay = new Ray(transform.position, Vector3.down);
if(Physics.Raycast(floorRay, out hit)) {
if(hit.collider.tag == "floor") {
floorY = hit.transform.position.y;
floorY = floorY + brickRot;
Debug.Log(floorY);
}
}
}
}
Think of brickRot as a plate you put down on the table, and when you rotate the plate, you change the table's y-position (if that analogy makes it any less confusing). The reason why I'm using a raycast is because there are multiple "tables" that the brick can find, and it needs to differentiate between them. If anyone has an idea of how to make this any easier, I would be forever thankful!
The rotation values you get are in Quaternion angles, not the common 360 degree Euler rotations. Unity, or any other game engine, uses Euler only for representation for us, but it never uses it internally for any sort of rotation calculations.
I am going to suggest not delving into deeply understanding Quaternions. But instead, think of how else you could achieve it.
For a suitable metric, add a child gameobject, rotated by 90 in x or y or z(check this, it depends on how you've oriented your parent) and some distance away from parent, to brick gameObject. Now when you rotate the brick, the child will automatically move in y, following a circular movement around the parent. You can use the child's Y as a metric for updating floorY.
use "eulerAngles" value instead , it'll convert the quaternion to an vector3
then you use angle function of vector3 to get some float, you can multiply the float with another value if you need bigger/lower scale
brickRot = angle = Vector3.Angle(transform.rotation.eulerAngles, transform.forward);

Choppy camera movement with smooth follow

I am seeing a camera stutter when using smooth follow to lerp after my player with my camera in my multiplayer browser game. The player position is received from the server, the player lerps to that position, and my goal is for the camera to smoothly follow the player with its own, extra smoothing.
You can find my game at http://orn.io to see the current state, without smooth follow camera lerping, which is better but causes choppy movement and creates a headache. The current code for camera follow is:
void LateUpdate ()
{
if (Target == null) {
return;
}
currentScale = Mathf.Lerp(currentScale, -GameManager.TotalMass, Mathf.Clamp01 (Time.deltaTime * heightDamping));
Vector3 h = currentScale * 2f * Target.up;
Vector3 f = currentScale * 3f * Target.forward;
// tried lerping to this value but it causes choppy stutters
Vector3 wantedPosition = new Vector3(Target.position.x, Target.position.y, currentScale * 2) + h + f;
myTransform.position = wantedPosition;
myTransform.LookAt (Target, -Vector3.forward); // world up
}
and I have tried for days to tinker with the values, use fixed timestamps, put the camera movement in FixedUpdate/Update, use MoveTowards, and other changes, but am still experiencing issues.
Part of my problem that that the player position changes mid lerp, which causes a stutter since the target position changes in the middle of the lerp. This causes the camera to jump/sutter due to the target position of the lerp being changed in the middle of the lerp, and shakes due to the LookAt.
I would appreciate it if anyone could suggest a way to improve the camera following code as it stands now.
Is there any particular reason you need to use the Mathf.Lerp function?
Unity has a function, Vector3.SmoothDamp that is specifically designed for movement lerping:
void FixedUpdate() {
// Code for your desired position
transform.position = Vector3.SmoothDamp(transform.position, wantedPosition, ref moveVelocity, dampTime);
}
The above will smoothly follow the player by giving the SmoothDamp method control of the velocity variable. This is assuming that you supply it with a ref to store the current velocity and the damp time.
You can also adjust the damp time to change how smooth your transition is. This function will also automatically account for player movement mid-lerp.
To clarify, quoting from the documentation, dampTime in the above is:
Approximately the time it will take to reach the target. A smaller value will reach the target faster.
Also consider using Quaternion.slerp to smoothly rotate between the two rotations.

Mega-man 2 Camera help (Unity)

So I have tried several ways to fix a Mega-man 2 camera without actually figuring out a good way of doing it. So I would love if someone could give me some tips for I could solve this issue.
The problem: As I have written I want a Mega-man 2 camera (2D). The thing from it that I have not been able to solve is how to move it in Y like they do in Mega-man 2. What happens is that the camera does not move at all in Y, unless you go outside "camera bounds" and then it "freezes" the player and moves the camera down, see this video: https://youtu.be/NP697v8WtkU?t=5m2s (Hopefully it starts where the camera does this, otherwise go to 5:02).
But I can't get this to work in any good ways for me. What I have tried so far is, I have triggers at the top and bottom of the camera and when the player hit's one of them I start a coroutine where the camera should Lerp, however, it moves too much, so if I need to move it twice in the Y it would be all messed up. (My code is at the bottom but as I said, I need some help how I can fix it)
So please, if anyone have any idea how I can solve this, please let me know. I've been trying for several days without getting it to work perfectly.
public void ChangeY()
{
StartCoroutine(MoveCameraDown());
}
IEnumerator MoveCameraDown()
{
Vector3 CurrentCameraPos = Camera.main.transform.position;
Vector3 NewCameraPos = new Vector3(Camera.main.transform.position.x, Camera.main.transform.position.y - 5.6f, Camera.main.transform.position.z);
for (float t = 0.0f; t < 1; t += Time.deltaTime / 1.5f /* TIME IT SHOULD TAKE TO MOVE THE CAMERA DOWN */)
{
Camera.main.transform.position = Vector3.Lerp(CurrentCameraPos, NewCameraPos, t);
yield return null;
}
}
And as I have already written, I call the ChangeY() when I hit one of the triggers. This is just my testing case if you are wondering why some numbers are hardcoded and all that. But as I said, I have not been able to fix this so if anyone can help me I would really appreciate it.
It is actually easy to do. You asked for some tips and I will give you one.
Assuming you have already built your 2D world just like in the video you posted. There are many ways to solve this problem but I show you the way I consider to be the easiest. I have 5 steps for you to follow.
You need a value you can use each time to move down. Based on your code, you used -5.6f each time. The go down value should never be the same each time when the player touches the bottom trigger. It should be generated during run time.
This will be addressed in step 3.
1) If the player triggers the camera bottom trigger, first thing you should do is to stop the player, by disabling gravity then setting the rigidbody velocity and torque to zero.
For example inside your OnTriggerEnter function attched to the camera:
void OnTriggerEnter(Collider other) {
Rigidbody rgbd;
rgbd= other.gameObject.GetComponent<Rigidbody> ();
rgbd.useGravity = false;
rgbd.velocity = Vector3.zero;
rgbd.angularVelocity = Vector3.zero;
}
2) Immediately throw Ray cast downward(-y) using Physics.Raycast.
The origin position of the raycast should be the position of the player. For example,
GameObject gameOBjectHitFromRaycast;//Store Game Object detected from the raycast
RaycastHit hit;
if (Physics.Raycast(player.transform.position, Vector3.down, out hit)){
gameOBjectHitFromRaycast = hit.collider.gameObject;
}
The information of the object below the player will be stored in RaycastHit.
Use RaycastHit to get the information about the object that the player is about to land on.
3) After getting the information of that object the player is about to land on, get the y-axis position of that object then get the y-axis position of the player. Add them together then divide the result by 2. That is your new y position. The new position you need to move the camera to. For example,
Vector3 cameraNewPos = new Vector3(player.transform.position.x,(gameOBjectHitFromRaycast.transform.position.y+
camera.transform.position.y)/2,player.transform.position.z);
Basically, what we did here is to find the middle of the player's current position and the the landing position so that we can position the camera right in the middle of it. That should be do it but you can also subtract another number from the y-xis if that is not enough.
4) Use a coroutine function to move the camera smoothly to the new y axis position. Inside the corutine function, there should be public bool variable that will be used to determine if this function is done running(if camera is done moving).
Replace this function with the current camera move function you have
bool cameraMoving = false; //This variable can be used to determine when the camera is done moving
private IEnumerator MoveCameraDown (Vector3 toLocationPos, float timeToFinish)
{
float runTime = 0; //Dont change
float timer = 0; //Dont change
float tempDeltaTime;
cameraMoving = true;
//Will keep looping until camera is done moving
while (timer<timeToFinish) {
//Translate camera
Camera.main.transform.position = Vector3.Lerp (Camera.main.transform.position, toLocationPos, runTime);
tempDeltaTime = Time.deltaTime; //Get delta time once to be used multiple times to improve performance
runTime += tempDeltaTime / timeToFinish;
timer += tempDeltaTime; //Increement To exit loop
yield return null;
}
//Camera is done moving
cameraMoving = false;
}
Finally, to move the camera, you can just start the coroutine and pass in the new position calcluation in step 3 and also how much time you want it to take to do the moving. For example, we can pass in the cameraNewPos from step 3 and 2 for seconds it will take to move the camera.
StartCoroutine (MoveCameraDown (cameraNewPos , 2f));
5) When the camera is done moving, enable rigidbody gravity of the player with
Rigidbody rgbd;
rgbd= player.GetComponent<Rigidbody> ();
rgbd.useGravity = true;

Categories

Resources