I have a game that effectively has two paddles in it, and I've got it working set up so that either the players or an AI can control either paddle. However, I'm trying to figure out how to get my AI to actually smoothly run after the ball and catch it, instead of simply brute-forcing it with a basic "Direction of Ball" = Opposite direction AI moves.
In order to do so, I'm trying to write some code that will allow the AI to predict where it will land, when the ball is in it's court or going to be. It's not going that well. This is a diagram of what I want to achieve:
So, I've been studying Unity3D and came up with this:
Vector3 BallMinder = BallPosition.gameObject.rigidbody.velocity;
float BallX = BallMinder.x;
float BallY = BallMinder.y * Time.deltaTime;
float GroundHitPointX = (BallY + BallX);
Vector3 BallImpactPoint = new Vector3 (GroundHitPointX, 0, 0);
Debug.Log (BallImpactPoint);
Debug.DrawRay (BallImpactPoint, Vector3.up, Color.green);
However, I feel like I've oversimplified it or forgotten something. My calculations are way off, and the ray isn't showing up where it should, if it does at all. What have I gotten wrong?
All you need is the Kinematics equations.
These equations perfectly describe a projectile in motion. Therefore, you can use them to predict where and with what components the ball will land.
Here is a graph to help you understand.
This is going to be simplified by assuming that gravity is downward, and the floor is at Y=0. It could be expanded using vector math for arbitrary floorlines and gravity directions, but for your purposes you should be fine using the two aforementioned assumptions. I'm also assuming for this explanation that BallPosition is a Transform object, but the math would be the same if it weren't. I'm also assuming no air friction on the ball, which would make the math a fair bit more complicated.
Essentially, you need to calculate the time until the ball hits the ground and then extrapolate the ball's X-position at that time.
The classic formula dictating the motion of an accelerating object is d = vt + at^2 / 2, where v is the current velocity, a is the acceleration, and t is the amount of time that has passed. To figure out the time of impact, we simply solve for t.
To figure out when the ball will hit the ground you'll want to set d = BallPosition.position.y * -1, a = Physics.gravity.y, and v = BallPosition.rigidbody.velocity.y. This will give us the number of seconds until impact.
Since gravity is assumed to be entirely downward and no other forces are acting on the ball, we can know that the X-position of the ball at impact time is BallPosition.position.x + BallPosition.rigidbody.velocity.x * impactTime. This would be the X-position your computer player should move towards.
...The formula should work any time the gravity is entirely downward, even if the ball is on the upward portion of its trajectory or moving away from your computer player. You'll probably want to come up with some strategy for what the computer should do while waiting for the human to hit the ball and set the ball's new trajectory, since you probably don't want the computer to try to run towards the human's side of the net. Depending on how your players are able to hit the ball, you might be able to predict the new velocity the ball would have after the human hits it, and then feed that data to this formula.
essentialy you apply gravity to the ball at every step to make it seem natural something like this:
ball is at your position ready to be trown
ball.x = 20;
ball.y = 10;
xVelocity = 0;
yVelocity = 0;
gravity = 0.1;
you trow the ball
When you trow the ball you set xVelocity to a constant lets say 1 and yVelocity to 5. So:
xVelocity = 1;
yVelocity = 5;
ball.x+=xVelocity;
ball.y+=yVelocity; //to start the ball moving
Now in the gameloop you calculate the ball position like this:
tick
{
if(ball.y > 10) //if the ball is off the ground (i asume 10 is ground)
{
ball.x+=xVelocity;
ball.y+=yVelocity;
yVelocity-=gravity;
}
}
with this the ball should fly in a perfect arc now to calculate the landing zone you can simply do the same thing again with dummy numbers
while(dummyBall.y > 10)
{
dummyBall.x+=xVelocity;
dummyBall.y+=yVelocity;
yVelocity-=gravity;
}
landingPosX = dummyBall.x;
You will probably need to tweak the numbers but if i remember everything correctly it should work something like this. Hope it make sense
Related
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.
I'm recreating the movement system from the DS game Dragon Quest Heroes Rocket Slime in Unity. Currently I've got pretty desirable results but it's not 100%.
One of the things that are different is that my player bounces back from walls further on long distance slings than he does in the original game. (left is my recreation, right is original game). Obviously, I can't find out how the game was made and the exact maths/values they use for slinging but one of the things I got from the game is that:
The player moves at constant velocity while slinging in a direction (no slow down, they just stop at a certain point)
Which led me to believe the game doesn't use traditional velocity mechanics so I made it so:
While stretching, the players 'pVelocity' (potentialVelocity) goes up. When they let go a point create at curPlayerPosition + pVelocity is created. I then move the player at a constant velocity to that point. My idea to fix my issue of bouncing too far is by getting the distance from the position where I slung from to the end point and making the player bounce less the larger the distance and further the smaller the distance.
Problem is, I'm not really sure how to do that last part.
Here's my code:
Vector2 reflectedVelocity = Vector2.Reflect(pVelNormalized, foundHit.normal);
playerAnimator.SetFloat("VelocityX", reflectedVelocity.x);
playerAnimator.SetFloat("VelocityY", reflectedVelocity.y);
float DistanceFromOrigin = Vector3.Distance(slingOrigin, transform.position);
print(DistanceFromOrigin);
//ToDo: reflectedVelocity is lower the higher DistanceFromOrigin is.
The other solution I thought of is that maybe my idea of how the 'velocity' works in the original game is completely wrong and if so, please tell me how it actually is/looks to be actually done
reflectedVelocity is lower the higher DistanceFromOrigin is.
So reflectedVelocity should have an inverse relationship with DistanceFromOrigin
Vector2 reflectedVelocity = Vector2.Reflect(pVelNormalized, foundHit.normal);
float DistanceFromOrigin = Vector3.Distance(slingOrigin, transform.position);
if(DistanceFromOrigin == 0) DistanceFromOrigin = 0.00001f;//to avoid division by zero
reflectedVelocity *= (SomeConstant / DistanceFromOrigin);
playerAnimator.SetFloat("VelocityX", reflectedVelocity.x);
playerAnimator.SetFloat("VelocityY", reflectedVelocity.y);
You may use a linear relationship instead of non-linear:
reflectedVelocity *= (SomeConstant - DistanceFromOrigin);
Not really sure how to word this but lets say you are a ball and are bouncing around and moving. Its a flat surface so everything moves fine.
Now introduce some 45 degree ramps. You fall on one straight down and bounce away from it. At the moment you can instantly move back regardless of how soon you hit it or what angle or speed your traveling at where it should be harder from the momentum of the push because it was at an angle. Kind of like a small gradual momentum until you reach 0 velocity, then you can move normally.
if (playerLeft)
{
float xVelocity = (playerBody.velocity.x > 0) ? playerBody.velocity.x - playerSpeed : -playerSpeed;
playerBody.velocity = new Vector3(xVelocity, playerBody.velocity.y, 0);
}
if (playerRight)
{
float xVelocity = (playerBody.velocity.x < 0) ? playerBody.velocity.x + playerSpeed : playerSpeed;
playerBody.velocity = new Vector3(xVelocity, playerBody.velocity.y, 0);
}
Originally i only had 1 line for each where i would set the x velocity equal to the player speed regardless if the velocity was -1 or -50. You can imagine how unrealistic that looks.
I tried adding the velocity which is what i did above. But because its minimum is only ever around 6 below 0. It will reach its max speed in 2 or 3 frames which isnt gradual enough.
This is done with Physic Material.
1.First, create a a Physic Material by going to Assets ---> Create ---> Physic Material.
2.Drag the Physic Material to the Material slot of the Object's collider.
3.Below is what the Physics Material looks like when selected:
You can select the Physics Material and modify it's settings in the Editor or you can do that via script:
SphereCollider yourCollider = ...;
yourCollider.material.dynamicFriction = 0.6f;
yourCollider.material.staticFriction = 0.6f;
yourCollider.material.bounciness = 0.0f;
This allows you to modify the bounciness and friction of the object during run-time. I suggest you watch this video to understand the basic settings.
I think the title says it all. Can it be done using only vector math?
var toTarget = (enemy.transform.position - npc.transform.position).normalized;
var seesBack = Vector3.Dot(toTarget, npc.transform.forward) < 0;
It seems I should somehow mix target's forward vector into the equation, but I'm really lame when it comes to vector math (well, math in general ;) ). Anyone?
EDIT:
I've also tried doing this, but the angle is too low. For example if the NPC is on the right of it's target, the calculated angle is ~60 degrees:
var angle = Mathf.Abs(Vector3.Angle(enemy.transform.forward * -1, npc.transform.forward));
var seesBack = angle <= 70;
Close! Consider that for the npc to see the back of the enemy, the enemy has to be looking roughly in the same direction as the vector from the npc to the enemy, aka toTarget:
var toTarget = (enemy.transform.position - npc.transform.position).normalized;
var seesBack = Vector3.Dot(toTarget, enemy.transform.forward) > 0;
Note that the Dot is against the enemy transform, and greater than 0.
Both of these tell you the same thing:
Are the two facing in the same direction?
Neither one tell you anything about which one is in front.
The dot-product one is going to be better, but you need to also check who's behind whom.
The one off-the-top-of-my-head way that I can think of is to compare the distance between the two entities, add a small amount along the NPC's forward vector (like, 0.5 units, something half the size of its collision volume's thickness) and get the distance from that point to the center of the other entity.
If the offset distance is smaller, the NPC is behind (as by moving forward, it would get closer). You'll probably also want a distance check involved somewhere as well, so that "behind" doesn't include "three rooms over." But I assume you've done that already.
Can it be done using only vector math?
This will check if the enemy is both facing away and positioned in-front of the NPC.
var r = Vector3.Dot(transform.forward, enemy.transform.forward) > 0f &&
Vector3.Dot(transform.forward, enemy.transform.position - transform.position) > 0f;
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.