Calculating/Predicting a way - c#

I'm just starting with physics, so I'm not always sure about what I'm doing. It's a 2D project but I'm using 3D physical objects like SphereCollider etc..
What I have:
Objects floating in space and affecting each other through gravity:
protected virtual IEnumerator OnTriggerStay(Collider other) {
yield return new WaitForFixedUpdate();
if(other.attachedRigidbody) {
Vector3 offsetVector = this.transform.position - other.transform.position;
float distance = offsetVector.magnitude;
float gravityForce = (other.rigidbody.mass * mass) / Mathf.Pow(distance, 2);
// Clamp gravity.
if(gravityForce > 1.0F) {
gravityForce = 1.0F;
}
other.attachedRigidbody.constantForce.force = offsetVector.normalized * gravityForce;
}
}
There are controllable objects on which the player can click and drag a line away from the object in order to give it a force (shoot) in the opposite direction.
What I want to achieve:
The player should see a rough prediction of the way while aiming. That means that the way-prediction needs to take in account the current velocity, the force which would be applied when the player release the mouse button and the gravity of the surrounding objects.
What I have tried so far:
For testing purposes I just save the computed/predicted positions in an array and draw those positions in OnDrawGizmos().
I wrote a method which returns the gravity influence for a certain position called computeGravityForPosition(Vector3 position).
And thats how I try to calculate the positions:
private void drawWayPrediction() {
Vector3 pos = this.transform.position;
// The offsetVector for the shooting action.
Vector3 forceVector = pos - Camera.main.ScreenToWorldPoint(Input.mousePosition);
forceVector.z = 0.0F;
// The predicted momentum scaled up to increase the strength.
Vector3 force = (forceVector.normalized * forceVector.magnitude);
// 1. I guess that this is wrong, but don't know how to do it properly.
momentum = this.rigidbody.velocity + force;
for(int i = 0; i < predictionPoints.Length; i++) {
float t = i * Time.fixedDeltaTime;
momentum += computeGravityForPosition(pos);
pos += momentum * t * t;
predictionPoints[i] = pos;
}
}
At the beginning, when the objects just slowly approaching each other it looks okay. After the first shot, the prediction is completely wrong. I guess it is because of 1. in the code. Just adding the force to the velocity is probably horrible wrong.
Thank you very much for your time.
EDIT:
I removed seemingly unnessecary parts.
I still think that the main problem lays in 1. in the code. I just don't know how to mix up the current movement of the object (from which I only have the current velocity as far as I know the physics engine of unity) with the new created force:
Vector3 forceVector = pos - Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 force = (forceVector.normalized * forceVector.magnitude);

So if you are using a new version of unity probably above 2018, you can use the nice method
Physics.Simulate(dt); // delta time, dt, is the amount of time to simulate.
https://docs.unity3d.com/ScriptReference/Physics.Simulate.html
https://docs.unity3d.com/2018.3/Documentation/ScriptReference/PhysicsScene.Simulate.html
By using this function you can manually advance the simulation.
This method should be applied to a different physics scene.
Therefore I suggest that when you click you will simulate a few physics steps (the more you will simulate the more accurate indication the player will get),
with every step you store the position of the object and when you are done simulating draw a line between all the points.
In my opinion, it should run quite fast if done correctly.
The code should look something like this:
public PhysicsScene physicsScene;
GameObject actualBall;
GameObject simulatedBall;
OnClick() {
simulatedBall.SetPosition(actualBall.transform.position);
if (!physicsScene.IsValid())
return; // do nothing if the physics Scene is not valid.
for (int i=0; i < 10; i++) {
physicsScene.Simulate(Time.fixedDeltaTime);
// store the position.
myPoints.append(simulatedBall.rb.position);
}
// draw a line from the stored points.
}
In addition there is this video that I hope will help, good luck
https://www.youtube.com/watch?v=GLu1T5Y2SSc
I hope I answered your question and if not tell me :)

Disclaimer : Unfortunately I suck at math so can't provide any code for the calculations.
Now that the legal stuff is out of the way :)
In my opinion you are looking at this all wrong. What you need is to calculate the curve (path of the objects trajectory) and then simply plot the curve in OnDrawGizmos with a line renderer.
You don't need to simulate the behaviour of the object. Not only is this a LOT faster but it's also simpler in terms of TimeScale shenanigans. By changing the TimeScale you are also affecting the TimeScale of your trajectory simulation which will most likely look and feel weird.
By doing a basic trajectory calculation you will not have this issue.
PS: This link might help.

Related

Trouble creating particle at a raycasts end/hit point for a moving object

Preamble:
I have a 3D side scroller style game in which the player flies along avoiding stuff, you know, side-scrollery things.
I'd like to add an effect (particle system) to the player when they get close (within a preset dangerZone, say 1.6 units (meters)) to the terrain, like a dusty dragging cloud under them sort of thing. I'm familiar with the particle system and raycasts but I don't know how to marry the concepts together to achieve what I'm after. The terrain undulates randomly and is not a flat surface, if that helps.
I'd also be hoping to make the particle system 'grow' the closer the player gets to the terrain if that makes sense. There is also speed to consider, so the closer to the ground and faster the player is should have an effect on the particle system.
My Thoughts:
I already have a score multiplier that uses a raycast to check the player's position from the ground/terrain and increases the closer they get.
void Update() {
force = speed *2;
RaycastHit hit;
Ray downRay = new Ray(transform.position, -Vector3.up);
if (Physics.Raycast(downRay, out hit, dangerZone)) {
var distanceToGround = hit.distance;
float hazardMultiplier = Mathf.Round( (transform.position.y - distanceToGround)*100 ) /100;
if (hit.collider.tag == "Terrain") {
playerData.scoreMulitplier = hazardMultiplier;
}
}
else {
playerData.scoreMulitplier = playerData.baseScoreMulitplier;
}
}
I'm thinking I can use the raycast I already have to instantiate a particle system on the terrain/at the raycast hit point but I'm not sure how exactly to go about this.
Any help is appreciated and thanks in advance.
You're on the right track. A couple things first before we dive into the solution:
Raycasts are calculated on the fixed frame (physics, FixedUpdate), not the visual frame (Update). While you may invoke a Raycast during Update, it won't be calculated until the next FixedFrame anyways. I'd recommend moving this to FixedUpdate to reduce the chance of doubled logic (2 simultaneous raycasts) or skipped logic (no raycasts).
You can set your particle system's scaling mode to the hierarchy, and scale using the transform. Alternatively, you can set the startSize of the particleSystem's main attributes.. Since you want to change the size of the particleSystem to change fairly frequently, I would recommend changing the scaling mode to hierarchy and just modifying the transform of the object it is attached to.
[SerializeField]
ParticleSystem ps;
[SerializeField]
float dangerZone = 1.6f;
[SerializeField]
float maxParticleSize = 2.0f; //How much you want particles to scale up based on closeness to terrain
void FixedUpdate() {
RaycastHit hit;
Ray downRay = new Ray(transform.position, -Vector3.up);
//Consider adding a layerMask parameter here that only interacts with the "terrain" layer. This will save you physics computing power, and remove the need for tag checking every frame (which is not efficient).
if (Physics.Raycast(downRay, out hit, dangerZone)) {
var distanceToGround = hit.distance;
float hazardMultiplier = Mathf.Round( (transform.position.y - distanceToGround)*100 ) /100; //This doesn't make too much sense to me, so you may want to revisit this. Since this isn't scoped for this question we can ignore it.
//If you use a layerMask, then you wouldn't ever need to check
if (hit.collider.tag == "Terrain") {
playerData.scoreMulitplier = hazardMultiplier;
float particleScale = 1 + (dangerZone - hit.distance) / dangerZone;
ps.transform.localScale = (particleScale, particleScale, particleScale);
if(ps.isStopped)
ps.Play();
} else //In scenarios where the raycast hits a non-terrain target, you'll need to turn off particles. If you use a layermask this wouldn't be needed.
{
if(ps.isPlaying)
ps.Stop();
}
}
else {
if(ps.isPlaying)
ps.Stop();
playerData.scoreMulitplier = playerData.baseScoreMulitplier;
}
}

Is there a way to create a curve when launching a gameobject to the player?

This is what I have tried so far:
I create a raycast and if it hits an object on layer 8 (the layer in which objects need to be launched to the player), I call the SlerpToHand() function.
private void Update()
{
if(Physics.Raycast(transform.position, transform.forward * raycastLength, out hit))
{
if(hit.collider.gameObject.layer == 8)
{
// Launch object to player
SlerpToHand(hit.collider.transform);
}
}
}
Inside of SlerpToHand(), I set the object's position to Vector3.Slerp(), that vector being created from values in the hit object.
private void SlerpToHand(Transform hitObj)
{
Vector3 hitObjVector = new Vector3(hitObj.transform.position.x, hitObj.transform.position.y, hitObj.transform.position.z);
hitObj.position = Vector3.Slerp(hitObjVector, transform.position, speed);
}
But the result of this is all wrong, the object just gets teleported to the player's hands. Is Vector3.Slerp() not a good way to curve an object to the player? For context I am trying to recreate Half-Life: Alyx's grabbity gloves. There is still some work to do with the hand gestures but I am just trying to get the object curve down. Help is much appreciated, let me know if more info is needed.
See unity docs:
public static Vector3 Slerp(Vector3 a, Vector3 b, float t);
Here, t is a normalized position between two input values. It means, if t = 0, result will be exactly first value. If t = 1, result will be exactly second value. If t = 0.5, result will be the middle between two values.
So, usually, you need to call Slerp every Update, step by step increasing t from 0 to 1. For this, usually Time.deltaTime used (which equals the time between updates). For speed control, multiply your speed by Time.deltaTime.
Update()
{
if (t < 1)
{
t += Time.deltaTime * speed;
hitObj.position = Vector3.Slerp(startPosition, endPosition, t);
}
}
...and in this case, for start moving, you just need to set t = 0. Probably, you have to implement your own logic here, but this should show the idea.
In addition:
Slerp used to interpolate between vector directions, for positions use Lerp.
Consider use DOTween plugin - its free and powerful for such cases.

How to make camera relative movement

I'm learning unity and c#, and want to make my movement to be camera relative movement instead of world relative movement. How do I do that?
I'm learning unity and c#, my unity version is 2018.3.12f1. I would be happy for help.
just to let know, instead of moving the cam I'm rotating the player.
void Update()
{
float AxisY = Player.transform.eulerAngles.y;
/* Movement starts here */
Vector3 Movement = new Vector3 (Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { //running code
Player.transform.position += Movement * running_speed * Time.deltaTime;
} else {
Player.transform.position += Movement * speed * Time.deltaTime;
}
/*Movement ends here */
/* Rotation controller starts here */
Quaternion target = Quaternion.Euler(Player.transform.eulerAngles.x, Player.transform.eulerAngles.y, Player.transform.eulerAngles.z);
/*if (Player.transform.eulerAngles.x != 0 || Player.transform.eulerAngles.z != 0 || Player.transform.eulerAngles.y != 0) {
Player.transform.rotation = Quaternion.Euler(0,0,0);
}*/
if (Input.GetKey(KeyCode.E))
{
Debug.Log("E got pressed");
//float AxisYPositive = Player.transform.eulerAngles.y;
AxisY = AxisY+1;
Player.transform.rotation = Quaternion.Euler(0, AxisY, 0);
} else if (Input.GetKey(KeyCode.Q))
{
Debug.Log("Q got pressed");
//float AxisYNegetive = Player.transform.eulerAngles.y;
AxisY=AxisY-1;
Player.transform.rotation = Quaternion.Euler(0, AxisY, 0);
}
}
}
The player's movement is world relative, how to make the movement camera relative?
If you want to make the movements relative to the gameObject, call the method Transform.Rotate() on the transform of the gameObject you want to rotate rather than modifying its Quaternion directly. Just make sure the final argument is set to Space.Self.
if (Input.GetKey(KeyCode.E))
{
Debug.Log("E got pressed");
//float AxisYPositive = Player.transform.eulerAngles.y;
AxisY = AxisY+1;
Player.transform.Rotate(Quaternion.Euler(0, AxisY, 0), Space.Self);
}
In general you don't want to directly mess with objects transform.rotation, at least not unless you at least somewhat understand quaternions (I don't!).
I can see a few issues with your code, but the common thread seems to be that you don't really understand how transforms work. Specifically, you might want to look into World/Local space.
The usual way to control a player goes roughly like this:
void DoMovement(Transform player)
{
//If you move first your controls might feel 'drifty', especially at low FPS.
Turn(player);
Move(player);
}
void Turn(Transform player)
{
float yaw = Input.GetAxis("Yaw") * time.deltaTime; //Aka turn left/right
player.Rotate(0, yaw, 0, Space.Self);
// Space.Self is the default value, but I put it here for clarity.
//That means the player will rotate relative to themselves,
//...instead of relative to the world-axis, like in your code.
}
You didn't ask about movement, but as-is your character will always move relative to the world. The below should make it move relative to the camera.
Transform _cameraTransform; //Assumes this is set druing Start()
void Move(Transform player)
{
var forwardMove = _cameraTransform.Forward; //Get whatever direction is 'forward' for camera
forwardMove.Y = 0; //Don't want movement up and down.
forwardMove = forwardMove.normalized; //Normalize sets the 'power' of the vector to 1.
//If you set Y to 0 and don't normalize you'll go slower when camera looks down
//...than when camera is flat along the plane
player.position += forwardMove * Input.GetAxis("Vertical") * time.deltaTime;
//Here you could do the same for strafe/side to side movement.
//Would be same as above, but using the transform.right and Horizontal axis
}
Now, I'm making some assumptions here since you haven't specified what kind of game it is and what kind of controls you want. I'm assuming you have a character running around on a mostly flat plane (no aircraft/spaceship controls), and that the camera is attached to the player. This might not not actually be the case.
In any case I advice you to check out the tutorials, especially the Roll-a-Ball tutorial which I have found is good for beginners to get a grasp on basic players controls that are not just world-relative. The other tutorials, too, are pretty good if you think they're interesting.
Aside from the official Unity tuts a ton of decent to amazing tutorials out there, including video tutorials, so for something like this you could just search for <game type> tutorial and pick whatever seems good to you. While getting started I advice you to avoid the shortest videos, as you will likely benefit greatly from explanation that only fits in longer videos. Of course, that doesn't mean you should pick the longest videos either.
In case someone needs to move an object and don't care about colliders, you can use transform.Translate and assign to his second parameter relativeTo your camera (or any transform) to automatically calculate the translation relative to the object assigned.

Slow collision detection at low frame rates

I'm experiencing an odd issue with my collision detection. I'm using the Update method to move the player (I don't want to use FixedUpdate because that creates an undesired weird movement). The fixed timestep is set at the default 0.02 (I tried playing with time setting but that didn't work either) . I set the collision detection of the rigidbodies of both objects to "continuous dynamic". Also, I set the target frame rate to 300 and that didn't change anything...
When the framerate is low or the device itself is slow, the collision detection doesn't always work. The player can easily fall through the object it's supposed to collide with, though sometimes it doesn't.
Please tell me what I can do to fix this because I've published a game and many users are reporting this (serious) bug. Thank you for your support.
This is what is supposed to happen:
This is what actually happens:
(as you can see, the cube gets out of the wall and to the other side)
I move the player when the user releases the mouse button:
Script 1:
public Script2 Jumper;
public float TimeToJump;
public void Update()
{
if (Input.GetMouseButtonUp(0))
{
StartCoroutine (Delay (1f/50f)); //Don't mind the time.
}
}
IEnumerator Delay(float waitTime)
{
yield return new WaitForSeconds (waitTime);
if (Jumper != null)
{
Jumper.SetVelocityToJump (gameObject, TimeToJump);
}
}
Script 2 attached to player (cube):
public class Script2 : MonoBehaviour {
GameObject target;
private float timeToJump;
public bool isJumping = false;
public void SetVelocityToJump(GameObject goToJumpTo, float timeToJump)
{
StartCoroutine(jumpAndFollow(goToJumpTo, timeToJump));
this.timeToJump = timeToJump;
this.target = goToJumpTo;
}
private IEnumerator jumpAndFollow(GameObject goToJumpTo, float timeToJump)
{
var startPosition = transform.position;
var targetTransform = goToJumpTo.transform;
var lastTargetPosition = targetTransform.position;
var initialVelocity = getInitialVelocity(lastTargetPosition - startPosition, timeToJump);
var progress = 0f;
while (progress < timeToJump)
{
progress += Time.deltaTime;
if (targetTransform.position != lastTargetPosition)
{
lastTargetPosition = targetTransform.position;
initialVelocity = getInitialVelocity(lastTargetPosition - startPosition, timeToJump);
}
float percentage = progress * 100 / timeToJump;
GetComponent<Rigidbody>().isKinematic = percentage < 100.0f;
transform.position = startPosition + (progress * initialVelocity) + (0.5f * Mathf.Pow(progress, 2) * _gravity);
yield return null;
}
OnFinishJump (goToJumpTo, timeToJump);
}
private void OnFinishJump(GameObject target, float timeToJump)
{
if (stillJumping)
{
this.isJumping = false;
}
}
private Vector3 getInitialVelocity(Vector3 toTarget, float timeToJump)
{
return (toTarget - (0.5f * Mathf.Pow(timeToJump, 2) * _gravity)) / timeToJump;
}
}
The target of the cube is a child of the bigger cube (the wall).
If you require clarification, please leave a comment below. I might give the link to my game if you need more details.
Quote from here (found thanks to #Logman): "The problem exists even if you use continuous dynamic collision detection because fast moving objects can move so fast that they are too far apart from itself from one frame to the next immediate frame. It's like they teleported and no collision detection would ever be triggered because no collision existed, from each frame perspective, and thus from all calculations processed."
In my case, the cube is not going fast, but you get the concept.
There are several issues with your code.
You are asking a Coroutine to yield for 1/50th of a second. The minimum time a yield must occur for is one frame. If Time.deltaTime > 0.02f this is already one of the problems.
You are using Coroutines and yield return null to compute physics calculations. Essentially, you're computing physics in Update(), which is only called once per frame (null is equivalent to new WaitForEndOfFrame(): as mentioned in (1), a running Coroutine cannot be yielding between frames). Under low frame-rate, the amount of motion an object undertook between two frames might exceed the collision range of the target trigger. Assuming linear, non-accelerating motion: ∆S = v∆t where v = velocity, ∆S is movement to cover in the current frame, ∆t is Time.deltaTime. As you can see, ∆S scales proportionally with ∆t.
You have GetComponent<T>() calls inside loops. Always avoid doing this: store a reference as a member variable instead (initialise it in Start()).
My suggestion for the quickest working hack would be to not worry too much about "being clean", and instead create subroutines that you call from FixedUpdate(), and (create and) use member bools to conditionally test which subroutine to "execute" and which to "skip". You can also use member bools or enums as triggers to switch between various "states".
A better solution would be to let Unity handle the kinematics and you instead work with rigidbody mutators (and not transform.positions), but that may be totally unnecessary for an arcade situation, which yours might be. In that case stick to the hack above.
If you really want to control kinematics by hand, use an engine like SFML. A Particle System tutorial would be a good place to start.
It's your float percentage, among other things.
"If isKinematic is enabled, Forces, collisions or joints will not affect the rigidbody anymore."
That's from the isKinematic page of Unity's documentation. You're setting it to true when progress hits 100. So at lower framerates, there'll be a sudden jump due to Time.deltaTime steps being a lot higher, progress is suddenly >= 100, isKinematic is set to true and the player is no longer affected by collisions.
I think you're going to have to rethink a lot of the code here and do some heavy optimisations. But the other posters have laid those out already, so I don't need to.
EDIT: Misunderstood the initial question, thought that it meant you were trying to detect collisions but your code wasn't always detecting them. Didn't realise it actually meant getting the collisions to occur in the first place.

OnCollision event handler problems in C# XNA with Farseer Physics

I have this working ok(ish) in my game at the moment, but i'm not fantastic at maths. When two primatives collide, I want them to smash up into tiny bits if the force applied to a primative was over a set threshold. My collision event handler at present looks like this.
public bool Collision(Fixture fixtureA, Fixture fixtureB, Manifold manifold)
{
Vector2 position = manifold.LocalNormal;
float angle = (float)Math.Atan2(position.Y, position.X);
Vector2 force = Vector2.Zero;
if (angle < 0)
force = new Vector2((float)(Math.Cos(angle) * fixtureA.Body.LinearVelocity.X), (float)Math.Sin(MathHelper.TwoPi + angle) * fixtureA.Body.LinearVelocity.Y);
else
force = new Vector2((float)(Math.Cos(angle) * fixtureA.Body.LinearVelocity.X), (float)Math.Sin(MathHelper.TwoPi - angle) * fixtureA.Body.LinearVelocity.Y);
double XForce = Math.Sqrt(force.X * force.X);
double YForce = Math.Sqrt(force.Y * force.Y);
double totalForce = XForce + YForce;
if ((Breakable) && (totalForce > BreakForce))
{
Breakable = false;
Active = false;
BreakUp(fixtureA, fixtureB);
}
return true;
}
I put that in a LONG time ago when I was just playing around. This causes a bit of a problem in certain situations. For example, if a primative is stationary on the floor and another primative falls onto it from a decent height, almost always, the falling box blows up and the resting box survives. Also if two boxes are falling side by side and give each other the tinyest of touches, then both boxes blow up mid air. Hmmmmm, not really perfect that. Does anyone have any idea how to improve my collision handler? Thanks in advance.
Ok, so my other answer is viable. But I've looked at Farseer 3.0 (the current SVN version) more closely and found that it already implements almost exactly what you are trying to do.
Look for "BreakableBody.cs". You may be able to directly use that - but otherwise you could just copy out the functionality you want.
Specifically: Instead of attaching a function to your fixture's OnCollision you want to attach one to PostSolve. It takes a ContactConstraint which you can dive into and find the impulses from the collision.
This is the implementation of that function used by BreakableBody:
private void PostSolve(ContactConstraint contactConstraint)
{
if (!Broken)
{
float maxImpulse = 0.0f;
for (int i = 0; i < contactConstraint.manifold.PointCount; ++i)
{
maxImpulse = Math.Max(maxImpulse,
contactConstraint.manifold.Points[0].NormalImpulse);
maxImpulse = Math.Max(maxImpulse,
contactConstraint.manifold.Points[1].NormalImpulse);
}
if (maxImpulse > Strength)
{
// Flag the body for breaking.
_break = true;
}
}
}
Apparently the impulse data in the manifolds is only set correctly in PostSolve, not in OnCollision.
(While this is currently the accepted answer - I would direct anyone to my other answer for a potentially superior approach.)
Your calculation of impact force is completely wrong. You need to get the relative velocity at the contact point(s) - you're getting something quite strange...
Your code looks like it's using Farseer 3.0 (you should specify, because that's more of a fork of Box2DX than Farseer 2.1). What I did in Farseer 2.1 (where you've got ContactList contacts instead of a Manifold) to get the impact velocity was:
foreach(Contact contact in contacts)
{
Vector2 position = contact.Position;
Vector2 v0;
me.Body.GetVelocityAtWorldPoint(ref position, out v0);
Vector2 v1 = new Vector2();
if(!hit.Body.IsStatic)
hit.Body.GetVelocityAtWorldPoint(ref position, out v1);
v0 -= v1;
float hitVelocity = v0.Length();
// To then get the force, you need the mass of the two objects
}
From a brief look at the Farseer 3.0 source, it seems that Manifold has a member:
public FixedArray2<ManifoldPoint> Points;
And both Manifold and ManifoldPoint have members:
public Vector2 LocalPoint;
It should be fairly simple to modify my Farseer 2.1 code to use those instead.
Also: I recommend simply marking the two objects as needing to break, and then actually breaking them after your physics update finishes running (rather than in the collision handler).
I haven't used XNA, but as a physics problem, why not just subtract the linear velocity of A from the linear velocity of B, and get the 'force' as the square of the resulting vector (sum the squares of the components)? That should harmonize with the kinetic energy involved according to `E=(mv^2)/2', for a very simple physical model, even if we are ignoring the masses (or, for that matter, elasticity or the distinction between energy and momentum). If the objects are moving in the same general direction at the same speed, you get a small value; if one is approaching (or, of course, departing!) at high speed, you get a large value.

Categories