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.
Related
I am trying to implement a function in my game which will auto-lock a target and throw a projectile so that it lands perfectly on it.
I did the maths to calculate a parabola from Player 1 -> Target wherever their positions are but realised I wanted to use Unity's physics system rather than having the ball follow a path.
The throw velocity is constant, Player 1 and Target are moving objects but their positions will be registered once only to calculate the initial angle of the throw.
I believe this is the formula I need to use:
But how can I apply it for my Player and Target both having 3D coordinates?
Here is the pseudo-code of what I tried to write in Unity to make more easily readable.
float velocity = 100f;
float g = Physics.gravity;
Transform x = Target.position.x - Player.position.x;
Transform y = Target.position.z - Player.position.z;
double theta;
theta = **big formula using the values above**
And after that I do not know how to use this value to add force to the projectile.
I wanted to use AddForce(x,y,z, ForceMode.Impulse); but I clearly cannot use an initial angle here, only an x and y value.
Using RigidBody.velocity = Vector3(vx, vy, vz); gives me the same problem.
I believe I am missing something trivial but I really am stuck on this. Would anyone be able to help?
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.
I have a ball that has an initial burst of force, launching it into the air. This ball would ideally then be able to veer towards its target while continuously losing that starting momentum. It shouldn't gain any velocity when heading towards its target.
Importantly! It can also bounce against walls, this also decreases its momentum.
Unitys built in physics system works great for throwing stuff against walls and seeing it bounce in a very natural manner, I want to keep this but gently push it towards a target.
Most solutions I have found (particually for homing missiles) add a continuous amount of force, which is fine if they are self powered.
In a script before this I would give the ball a force of say... 100 using
ballRigidbody.Addforce(targetDirection, ForceMode.Impulse);
Then I'd like it to be steered using a 'Movement' class.
Below is an example of the ball being pushed by continuously adding force, so its more like a rocket (Not what I'm after!)
public class Movement : MonoBehaviour {
[Header("Get Components")]
[SerializeField]Transform targetTransform_gc;
[SerializeField]Transform ballTransform_gc;
[SerializeField]Rigidbody ballRigidbody_gc;
[Header("Settings")]
public float turnSpeed;
public float ballSpeed;
//Private
private Vector3 targetDirection;
private Quaternion targetRotation;
void Update () {
targetDirection = targetTransform_gc.position -ballRigidbody_gc.position;
targetDirection.Normalize();
targetRotation = Quaternion.LookRotation(ballRigidbody_gc.velocity);
ballTransform_gc.rotation = Quaternion.Slerp (ballTransform_gc.rotation, targetRotation , turnSpeed * Time.deltaTime);
}
void FixedUpdate(){
ballRigidbody_gc.AddForce(targetDirection * ballSpeed);
ballRigidbody_gc.velocity = new Vector3(ballRigidbody_gc.velocity.x, 0, ballRigidbody_gc.velocity.z);
}
}
I'd be really grateful if anyone has some pointers or suggestions.
Thanks very much!
If I understand you correctly, you are looking to change the direction, but not the magnitude of your velocity vector. You can get the current magnitude in 3 dimensions using:
double magnitude = Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y,2) + Math.Pow(z, 2));
you can then just multiply a unit vector that points between your ball and the target by this magnitude
Edit
The magnitude of a vector is always computed using the formula I showed below. The magnitude is the total length of the vector, ignoring direction.
A unit vector is just a vector pointing in a given direction, with a magnitude of 1.
At this point you have your magnitude, which is how fast you want to go, and your unit vector, which is what direction you want to go. Multiplying these 2 values together gives you a vector that is both the direction and speed you want to go.
title pretty much says it all.. say I have a ship, its worldVelocity is a Vector3 with these values:
X: 0
Y: 0
Z: 1
(assuming +Z is Forward, +Y is Up, and +X is Right)
this Vector3 is added to the ships worldTranslations Matrix which stores translation and rotation.
worldTranslations.Translation += worldVelocity
and my ship goes forward, and it stops accelerating at speed 1 (correctly) as this is its target velocity, but what if the ship has been rotated Right 180 degrees, so its now flying backwards. Automatically the ships target velocity should detect this and begin trying to fly forward again.
The problem is I don't know how to get the local velocity out of my ships velocity and localTransforms, and I cant find an answer anywhere..
so at the moment my ship uses the world velocity for checking if its reached its target speed, so long as the ship doesnt rotate, it flies properly. My target speed will be one, so my ship compares its current velocity (0,0,0) to target velocity (0,0,1) and properly accelerates till it reaches its target (0,0,1) and stops accelerating to maintain a constant speed, but if i turn (say just 90 degrees right), current velocity is now (-1,0,0) because the ship is drifting left now, so it should use that relative velocity to determine that it needs to accelerate (1,0,1) to get back to (0,0,1) but I have no idea how to get relative velocity so it currently uses world velocity which is still (0,0,1) even though its flying left and so it thinks its all good but its still drifting left..
Thanks.. again everyone :)
The answer was to transform the velocity vector by the inverse rotation matrix.
This post was made as my friend realised the desired result was achieved in Unity3D using one of its built in functions, but ultimately is the same question:
XNA equivalency of unity function Transform.InverseTransformDirection
More detailed explanation can be found on that page.
However if this does not work for you, as it didn't immediately fix my problem, try examining one component of the vector at a time. I had to multiply only the Z component of the vector by -1 to make it work. Most likely interference by other maths so I recommend you examine the result vectors components results individually.
-Aaron
The ship's world matrix has a 'Forward' property which is a unit length vector that always points in the ship's forward direction. This vector is updated anytime the ship's rotation is changed and the world matrix updated to store the change.
So you can key the velocity off that vector and it will always go forward relative to the rotation of the ship.
Velocity = shipMatrix.Forward * speed;
ShipMatrix.Translation += velocity;
I've been hanging my head around this issue for some time now and I'm at the top of my head not figuring this out.
The issue:
Currently trying to do a fake "swing" moment. Where I have the player object becoming a child to an object you can latch onto during a keypush.
With this in mind, I need to get the correct rotation on the grappleobject, making the velocity of the player becoming the correct rotation on the grappleobject.
This is not meant to be 2D, but 3D. Which causes this headache.
http://img843.imageshack.us/img843/7434/rotations.jpg
I figured that if I could get the vector of position, and the vector of the direction where the player is going towards, as in velocity of the character motor, to get an unique Vector, and this vector info should be able to rotate the grappleobject correctly.
However, that's what I thought was easy, but I've been hacking at this for hours to get that vector difference to do the rotation as it was thought out to be.
tl:dr
2 Vectors, the difference of these two to one vector, this vector controls the rotation of grappleobject to "fake" a swing motion with the proper velocity.
Thank you in advance if there'll be an answer.
Added Info:
Current testings has been these.
///
Parent Orb is the grappleobjects which updates its transform depending on trigger events.
CurDirection is where the player is heading within the vector in world.
CurPos is where the player is at that point in time within the world.
CurDirection = (transform.position-ParentOrb.position);
CurDirection.Normalize();
motor.movement.velocity = (CurDirection);
CurPos = transform.position;
////
Also tried out to get the angle from the grappleobject to the player.
///
otherDirection = The direction of velocity in space.
OtherDirectionPre = Current position in space.
Vector3 targetDir = otherDirection;
Vector3 forward = otherDirectionPre;
angle = Vector3.Angle(targetDir, forward);
///
I assume these may not be helpful, but better to show where I have gotten so far.
I think you know the radius between the hit point and the center, let's call it r. With the velocity v of the player and the angular velocity ω (=omega) of the axis the player should be connected to you have the vector equation:
v = ω × r
Assuming all 3 vectors are perpendicular to each other you have ω = v / r.
EDIT: You get the radius r from the position of the center and the point of contact of your collision:
Vector3 r = collision.contacts[0].point - center;
float radius = r.magnitude;
float angularSpeed = rigidbody.veloctiy.magnitude / radius;