Box2dx: Cancel force on a body? - c#

I'm doing pathfinding where I use force to push body to waypoints. However, once they get close enough to the waypoint, I want to cancel out the force. How can I do this? Do I need to maintain separately all the forces I've applied to the body in question?
I'm using Box2dx (C#/XNA).
Here is my attempt, but it doesn't work at all:
internal PathProgressionStatus MoveAlongPath(PositionUpdater posUpdater)
{
Vector2 nextGoal = posUpdater.Goals.Peek();
Vector2 currPos = posUpdater.Model.Body.Position;
float distanceToNextGoal = Vector2.Distance(currPos, nextGoal);
bool isAtGoal = distanceToNextGoal < PROXIMITY_THRESHOLD;
Vector2 forceToApply = new Vector2();
double angleToGoal = Math.Atan2(nextGoal.Y - currPos.Y, nextGoal.X - currPos.X);
forceToApply.X = (float)Math.Cos(angleToGoal) * posUpdater.Speed;
forceToApply.Y = (float)Math.Sin(angleToGoal) * posUpdater.Speed;
float rotation = (float)(angleToGoal + Math.PI / 2);
posUpdater.Model.Body.Rotation = rotation;
if (!isAtGoal)
{
posUpdater.Model.Body.ApplyForce(forceToApply, posUpdater.Model.Body.Position);
posUpdater.forcedTowardsGoal = true;
}
if (isAtGoal)
{
// how can the body be stopped?
posUpdater.forcedTowardsGoal = false;
//posUpdater.Model.Body.SetLinearVelocity(new Vector2(0, 0));
//posUpdater.Model.Body.ApplyForce(-forceToApply, posUpdater.Model.Body.GetPosition());
posUpdater.Goals.Dequeue();
if (posUpdater.Goals.Count == 0)
{
return PathProgressionStatus.COMPLETE;
}
}
UPDATE
If I do keep track of how much force I've applied, it fails to account for other forces that may act on it.
I could use reflection and set _force to zero directly, but that feels dirty.

If you just want to stop the body dead (cancelling out all forces acting on it) you can just put it to sleep:
posUpdater.Model.Body.SetAwake( false )
This clears the body's linear and angular forces and velocities. Sleeping bodies also don't collide with other sleeping bodies and generally take up less CPU time, as effectively their physics is switched off.
If you actually want the body to take part in physics after it's stopped dead, just wake it immediately:
posUpdater.Model.Body.SetAwake( false )
posUpdater.Model.Body.SetAwake( true)
This may lead to "unnatural" looking results if the body was in the middle of colliding with other things of course.

Related

Movement is dependent to frame-rate. How can i make it independent from frame-rate

i am pretty new to coding. till now i searched lots of things yet i still couldn't solve my problem. I am trying to make a gameObject follow a line/graph. i tried to give him a negative-Y velocity and adding small numbers(something like; update{mybody.velocity += 0.005}) each update to make it move. but i encountered a problem which is at low frame-rate my object is taking a super wide path and at higher frame-rate it take super tight path. how can i make my movement independent.
private float curve = 0;
private float curvemax = 2.3f;
[SerializeField]
private float curveupdate = 0.05f;
if (tracking.found && !hareketeBaşlandı)
{
curve = -(curvemax);
triggered1 = true;
hareketeBaşlandı = true;
print(curve);
}
if (transform.position.y != y1-curvemax && tracking.found)
{
curve += curveupdate;
print(curve+"Curving");
}
if (curve >= (curvemax))
{
curve = 0;
y2 = transform.position.y;
transform.position = new Vector3(transform.position.x, y1, transform.position.z);
tracking.found = false;
tracking.shouldsearch = false;
StartCoroutine("trackCD");
print("track off");
myBody.velocity = new Vector2(myBody.velocity.x, curve);
hareketeBaşlandı = false;
}
Note that using DateTime.Now and TimeSpan calculations as in this answer are quite expensive ... especially if you use it every frame just to calculate a delta in seconds.
This is actually exactly what Unity already provides in Time.deltaTime the time in seconds passed since last few was rendered .. so why calculate it complicated if we already know the value ;)
curve += curveupdate * Time.deltaTime;
In simple words multiplication by Time.deltaTime converts any value from "value per frame" into a frame-rate independent "value per second"
Instead of thinking about "rate of change," you must think about "rate of change per unit of time".
For example, you seem to be updating curve based on curveupdate = 0.05f;, which has a rate of change of 0.05f. You need to be thinking about how much rate of change per second. Assuming you currently get 0.05f change per frame, and let's say 10 frames per second, that means your rate of change per second is 0.5f (ten times higher) per second.
You then apply this amount multiplied by the number of seconds elapsed. Here's how to do it.
First when you kick off the animation, you need to remember when it started:
var lastUpdateTime = DateTime.Now;
Then change this:
curve += curveupdate;
To this:
DateTime currentTime = DateTime.Now;
float secondsElapsed = (float)(currentTime - lastUpdateTime).TotalMilliseconds / 1000F;
curve += (curveupdate * secondsElaped);
lastUpdateTime = currentTime;
This way, the amount of change scales depending on how much time has passed. If no time has passed, it will scale all the way down to 0, and if hours have passed, it will end up very very large. To an end user, the rate of change should seem consistent over time as a result.

How to transition smoothly from AddForce() to MovePosition() in Unity?

I have a character that dashes using AddForce(), after dashing, i want him to auto walk in set speed using MoovePosition().
This project was made using the 3D engine and it work flawlessly. But I've changed it to 2D and after the AddForce, the character abruptly changes to the speed that MovePosition() set, it looks bad.
I thought about checking the velocity after the dash and when it reaches the same velocity as when auto-walk is happening, it calls for MovePosition. But it doesn't seem a good approach.. I'd love to receive some tips on this issue.
if(moveFactor!= 0)
{
Vector2 appliedForce = transform.position + transform.up * moveFactor;
Debug.Log("appliedForce: " + appliedForce.magnitude);
playerRigidbody.AddForce(appliedForce);
autoWalk = true;
moveFactor = 0;
}
if (autoWalk) playerRigidbody.MovePosition(walkForce);
moveFactor is a float value that a swipe detector script returns. The code is in FixedUpdate as well.
It would be helpful to see the problem instead of trying to reconstruct the picture from your description, but I'll try my best.
The Problem
The same frame you start the dash, you set autoWalk to true. As soon as the if statement is exited, the if (autoWalk) is evaluated and you start manually moving the RigidBody using MovePosition() — before the force has even been added.
I'm not sure why that ever worked in 3D, though.
Solution #1
Instead of using MovePosition(), move with AddForce(), with the default ForceMode2D; or with playerRigidbody.velocity, where-in you set it once, not every frame.
Solution #2
Instead of using AddForce(), for the dash, use the same old MovePosition() you use inside if (autoWalk).
Just increase the magnitude of walkForce several times for the duration of the dash.
Solution #3
The problem with using AddForce() for it is that you don't know when it's "finished", so you don't know when to transition to MovePosition().
If the above solutions don't work for you, implement the dash manually. You can do this with Vector2.Lerp() (or Vector3) inside a coroutine.
if (moveFactor != 0)
{
Vector2 endPosition = transform.position + transform.up * moveFactor;
StartCoroutine(Dash(endPosition));
moveFactor = 0;
}
Where Dash() is:
public float dashDuration = 1f;
private IEnumerator Dash(Vector2 endPosition)
{
autoWalk = false;
// Record the original position before you begin moving the object.
float startingPosition = transform.position;
float elapsed = 0f;
while (elapsed < dashDuration )
{
elapsed += Time.deltaTime;
Vector2 nextPosition = Vector2.Lerp(startingPosition, endPosition, elapsed / dashDuration );
playerRigidbody.MovePosition(nextPosition);
// Note: if the RigidBody is kinematic (isKinematic == true), this will simply teleport it to the given position.
// Otherwise, it will comply with its interpolation settings.
// You could directly modify playerRigidbody.position or transform.position (though the former is faster) for the same teleportation.
yield return null;
}
autoWalk = true;
}
appliedForce might not immediately work as endPosition — modify it so it does, in that case. dashDuration is in seconds.
Make sure Dash() is started only once, until it is finished. You can do this by saving the Coroutine returned by StartCoroutine() in a private field member and comparing it to null before you start, but you shouldn't have to. If the code inside the if (moveFactor != 0) statement is executed something like every frame, change your code accordingly.
I've not tested this, so I'm not sure it will check for collisions. If it doesn't but you need it to, you can add that funcionality by raycasting (or box-casting, sphere-casting, or whatever kind of Collider you use):
private IEnumerator Dash(Vector2 endPosition)
{
float startingPosition = transform.position;
float elapsed = 0f;
while (elapsed < dashDuration )
{
elapsed += Time.deltaTime;
Vector2 nextPosition = Vector2.Lerp(startingPosition, endPosition, elapsed / dashDuration );
// Add the raycast here and only execute the MovePosition() if the returned RaycastHit2D's collider property is null.
// (The raycast's should extend to nextPosition.)
playerRigidbody.MovePosition(nextPosition);
yield return null;
}
}

Unity | Coroutine Speeding Up After Each Executions

My coroutine to fade the player out after they die,
// Fade out ragdoll
IEnumerator RagdollFade()
{
yield return new WaitForSeconds(3f);
while (startingColour.a > 0.0f)
{
headSR.color = new Color(headSR.color.r, headSR.color.g, headSR.color.b, headSR.color.a - (Time.deltaTime / 1.5f));
bodySR.color = new Color(bodySR.color.r, bodySR.color.g, bodySR.color.b, bodySR.color.a - (Time.deltaTime / 1.5f));
leftArmSR.color = new Color(leftArmSR.color.r, leftArmSR.color.g, leftArmSR.color.b, leftArmSR.color.a - (Time.deltaTime / 1.5f));
rightArmSR.color = new Color(rightArmSR.color.r, rightArmSR.color.g, rightArmSR.color.b, rightArmSR.color.a - (Time.deltaTime / 1.5f));
leftLegSR.color = new Color(leftLegSR.color.r, leftLegSR.color.g, leftLegSR.color.b, leftLegSR.color.a - (Time.deltaTime / 1.5f));
rightLegSR.color = new Color(rightLegSR.color.r, rightLegSR.color.g, rightLegSR.color.b, rightLegSR.color.a - (Time.deltaTime / 1.5f));
yield return null;
}
}
speeds up after each execution.
For example, this first time the coroutine is called everything works fine and after 3 seconds the player is faded out. However, the next time it is called 3 seconds don't pass before the fade, the next time even less time, etc.
startingColour is set in the Start() function.
It seems that your startingColour.a value is always bigger then 0 so the while loop never finishes and your coroutine just runs forever. Hard to tell without seeing the rest of your code.
So if you start it a second time you now have both routines running parallel => now each frame you decrease the alphas by the double amount .. then triple .. etc. and it also doesn't wait the 3 seconds before the first called routines are already running the while loop so they continue to decrease the alphas.
You could use StopAllCoroutines or StopCoroutine in order to interrupt any still running routines when starting a new one. But that's actually more a kind of dirty workaround.
I would rather takle the actual issue and make sure your while loop returns which is currently unlikely to happen since you seem to not change startColor.a anywhere.
Or add a flag not allowing parallel routines at all like e.g.
private bool isFading;
IEnumerator RagdollFade()
{
if(isFading) yield brake;
// prevents other routines
isFading = true;
...
// reset the flag once routine is finished
isFading = false;
}
Then I would also rather suggest to have one single float value you use for fading using Color.Lerp like
private bool isFading;
// you can also use a fixed duration and not pass it as parameter
// but this way you are even more flexible
IEnumerator RagdollFade(float duration)
{
if(isFading) yield brake;
// prevents other routines
isFading = true;
yield return new WaitForSeconds(3f);
// it is more performant to gather all required information beforehand
headStartColor = headSR.color;
bodyStartColor = bodySR.color;
leftArmStartColor = leftArmSR.color;
rightArmStartColor = rightArmSR.color;
leftLegStartColor = leftLegSR.color;
rightLegStartColor = rightLegSR.color;
headTargetColor = new Color(headStartColor.r, headStartColor.g, headStartColor.b, 0f);
bodyTargetColor = new Color(bodyStartColor.r, bodyStartColor.g, bodyStartColor.b, 0f);
leftArmTargetColor = new Color(leftArmStartColor.r, leftArmStartColor.g, leftArmStartColor.b, 0f);
rightArmTargetColor = new Color(rightArmStartColor.r, rightArmStartColor.g, rightArmStartColor.b, 0f);
leftLegTargetColor = new Color(leftLegStartColor.r, leftLegStartColor.g, leftLegStartColor.b, 0f);
rightLegTargetColor = new Color(rightLegStartColor.r, rightLegStartColor.g, rightLegStartColor.b, 0f);
var passedTime = 0f;
while (passedTime < duration)
{
// get the interpolation factor from 0 to 1
var factor = passedTime / duration;
// for adding additional ease-in and ease-out
// factor = Mathf.SmoothStep(0, 1, factor);
headSR.color = Color.Lerp(headStartColor, headTargetColor, factor);
bodySR.color = Color.Lerp(bodyStartColor, bodyTargetColor, factor);
leftArmSR.color = Color.Lerp(leftArmStartColor, leftArmTargetColor, factor);
rightArmSR.color = Color.Lerp(rightArmStartColor, rightArmTargetColor, factor);
leftLegSR.color = Color.Lerp(leftLegStartColor, leftLegTargetColor, factor);
rightLegSR.color = Color.Lerp(rightLegStartColor, rightLegTargetColor, factor);
// avoid overshooting
passedTime += Mathf.Min(Time.deltatime, duration - passedTime);
yield return null;
}
// reset the flag once routine is finished
isFading = false;
}
This is more flexible and you can add ease-in and ease-out using whatever simple math you like.
Try call StopCoroutine() before starting a new Coroutine. Possibly you have a few coroutines working at the same time.
Ok, I've solved the problem, and it's completely my bad.
I realized that ANOTHER coroutine I had was interfering with the current one, so that's why StopCoroutine() and adding a fade check wasn't working.
Sorry guys for not including that in my question post, you would probably have been able to help me out more effectively.
So for anyone who encounters strange routine behavior in the future, make sure two coroutines are not interfering with each other.

Change movement speed in Vector.Lerp Unity 5.6

I am using Vector3.Lerp in unity game to simply move a gameobject from one position to other smoothly. Below is my code:
public class playermovement : MonoBehaviour {
public float timeTakenDuringLerp = 2f;
private bool _isLerping;
private Vector3 _startPosition;
private Vector3 _endPosition;
private float _timeStartedLerping;
void StartLerping(int i)
{
_isLerping = true;
_timeStartedLerping = Time.time ; // adding 1 to time.time here makes it wait for 1 sec before starting
//We set the start position to the current position, and the finish to 10 spaces in the 'forward' direction
_startPosition = transform.position;
_endPosition = new Vector3(transform.position.x + i,transform.position.y,transform.position.z);
}
void Update()
{
//When the user hits the spacebar, we start lerping
if(Input.GetKey(KeyCode.Space))
{
int i = 65;
StartLerping(i);
}
}
//We do the actual interpolation in FixedUpdate(), since we're dealing with a rigidbody
void FixedUpdate()
{
if(_isLerping)
{
//We want percentage = 0.0 when Time.time = _timeStartedLerping
//and percentage = 1.0 when Time.time = _timeStartedLerping + timeTakenDuringLerp
//In other words, we want to know what percentage of "timeTakenDuringLerp" the value
//"Time.time - _timeStartedLerping" is.
float timeSinceStarted = Time.time - _timeStartedLerping;
float percentageComplete = timeSinceStarted / timeTakenDuringLerp;
//Perform the actual lerping. Notice that the first two parameters will always be the same
//throughout a single lerp-processs (ie. they won't change until we hit the space-bar again
//to start another lerp)
transform.position = Vector3.Lerp (_startPosition, _endPosition, percentageComplete);
//When we've completed the lerp, we set _isLerping to false
if(percentageComplete >= 1.0f)
{
_isLerping = false;
}
}
}
}
The code works fine and the gameobject moves smoothly between two points. But it takes about 1 sec to reach destination. I want to make it move faster. I have tried decreasing the value of float timeTakenDuringLerp but the speed isn't affected. I have followed this tutorial and the explanation there also says to change timeTakenDuringLerp variable in order to change speed but its not working here.
Any Suggestions please?
H℮y, thanks for linking to my blog!
Decreasing timeTakenDuringLerp is the correct solution. That reduces the time it takes for the object to move from start to finish, which is another way of saying "it increases the speed".
If there is a specific speed you want the object to move at, you'll need to make timeTakenDuringLerp a variable rather than a constant, and set it to distance/speed. Or better yet, don't use Lerp at all, and instead set the object's velocity and let Unity's physics engine take care of it.
Multiplying percentageComplete by a constant, as suggested by #Thalthanas, is incorrect. That causes the lerping updates to continue occurring after the lerping has completed. It also makes the code hard to understand because timeTakenDuringLerp is no longer the time taken during the lerp.
I've double-checked with my code and it does indeed work, so the problem you are experiencing must be elsewhere. Or maybe you accidentally increased the time, which would decrease the speed?
The solution is to
multiply percentageComplete value with a speed value like,
transform.position = Vector3.Lerp (_startPosition, _endPosition, percentageComplete*speed);
So when you increase speed, it will go faster.

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.

Categories