Reputation: 37
I was working on my top down game, but I got stuck. I am trying to make player be knockbacked when an enemy hits him(their rigidbodies collide).
Now the problem is I am using MovePosition for player movement and when I then use anything that will change the velocity of the players RB(setting velocity/AddForce()), the force/velocity is applied just for a split second. I think it is because I use the MovePosition which somehow ignores or resets the velocity.
Is there some way I can go around this without having to make the player controls based on adding forces with max velocity or with calculating how long will the knockback last for?
Shortly, I want the knock back smooth, because now I have to add a really high force to have really fast (basically instant) movement.
Upvotes: 2
Views: 5260
Reputation: 38888
The only thing that worked for me was to set up a knockedOut
temporal flag to decide when to stop invoking RigidBody.MovePosition()
temporarily to give RigidBody.AddForce()
some time to act.
Then playing around with forceValue
, RigidBody.mass
and RigidBody.linearDrag
to get the desired effect.
This is the implementation of my PlayerController:
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
[SerializeField] float speed = 1f;
[SerializeField] float impactEffect = 5f;
[SerializeField] float knockOutTime = 0.05f;
Vector2 direction = Vector2.zero;
Rigidbody2D rbody;
bool knockedOut;
IEnumerator knockOutCoroutine;
void Awake()
{
rbody = GetComponent<Rigidbody2D>();
}
void FixedUpdate()
{
if(!knockedOut) // Don't call Move() if I am knockedOut
Move();
}
// Triggered by the InputSystem
void OnMove(InputValue value)
{
// Set the direction value
direction = value.Get<Vector2>();
}
// Controls the normal movement of the Player based on the actual `direction` value
void Move()
{
Vector2 adjustedMovement = direction * speed * Time.fixedDeltaTime;
Vector2 newPos = rbody.position + adjustedMovement;
rbody.MovePosition(newPos);
}
// Invoke this method when Collision
public void Impact(Vector2 impactPosition)
{
Vector2 impactDirection = ((Vector2)transform.position - impactPosition).normalized;
rbody.velocity = Vector2.zero;
rbody.AddForce(impactDirection * impactEffect, ForceMode2D.Impulse);
KnockOut(knockOutTime);
}
void KnockOut(float seconds)
{
if(knockOutCoroutine != null)
StopCoroutine(knockOutCoroutine);
knockOutCoroutine = KnockOutCoroutine(seconds);
StartCoroutine(knockOutCoroutine);
}
// KnockedOut stop movement effect
IEnumerator KnockOutCoroutine(float seconds)
{
direction = Vector2.zero;
knockedOut = true;
yield return new WaitForSeconds(seconds);
knockedOut = false;
}
}
Upvotes: 0
Reputation: 910
Just in case someone else stumbles across this: Technically you can do what the TE initially wanted by replacing MovePosition(pos)
with transform.position = pos
.
This will maintain inertia\velocity etc. and might be what you need in very specific situations (in my case creating a custom type of non-stretchy hinge joint where the enforced position change is very small).
The main downside of this solution is that the physics engine can do unexpected things when the position is shifted into a solid collision. The engine will still try to resolve the situation and push the object out of a collision, but it doesn't know what direction it came from (unlike with MovePosition), shifting possibly in the wrong direction and thus let Objects clip through terrain. That's why in general it's not recommended to touch 'transform' when a rigidbody is applied.
Upvotes: 0
Reputation: 1775
Try to rewrite it so that the physics engine take care of everything for you.
You can try moving your object with the use of AddForce
and while at "knockback" state you can utilise AddForce
with Force.Impulse
as a parameter. It should work as intended and will take the problem of the movement from your shoulders.
Upvotes: 1