I am creating a first person rocket jumping game, with the premise of shooting a rocket launcher at the players feet to move around.
I am having problems with my OnCollisionEnter function which stores all colliders in a radius and applies explosion force to them. When the player is completely on top of the explosion, force is being applied twice. Here is the code:
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "Player")
{
}
else
{
GetComponent<AudioSource>().Stop();
Instantiate(explosionPrefab, transform.position, transform.rotation);
Vector3 explosionPos = transform.position;
//Use overlapshere to check for nearby colliders
Collider[] collidersToDestroy = Physics.OverlapSphere(explosionPos, radius);
foreach (Collider hit in collidersToDestroy)
{
//searches for what needs to be destroyed before applying force, this is for destructable objects
Destructible dest = hit.GetComponent<Destructible>();
if (dest != null)
{
dest.DestroyWall();
}
ExplosiveBarrel barrel = hit.GetComponent<ExplosiveBarrel>();
if (barrel != null)
{
barrel.BarrelExplode();
}
}
Collider[] collidersToMove = Physics.OverlapSphere(explosionPos, radius);
foreach (Collider hit in collidersToMove)
{
Rigidbody rb = hit.GetComponent<Rigidbody>();
//Add force to nearby rigidbodies
if (rb != null)
{
rb.AddExplosionForce(power * 5, explosionPos, radius, 3.0F);
if (hit.gameObject.tag == "Player")
{
//if player is hit
UnityEngine.Debug.Log("Hit");
}
}
Destroy(gameObject);
}
}
}
I can tell the force is being applied twice to the player by using UnityEngine.Debug.Log("Hit"); , which appears twice in the console. Furthermore, I am pretty sure this is happening on the same frame, as putting Destroy(gameObject); within the if (player hit) statement yields the same results.
This only occurs when the player is right next to the explosion, if the player is a small distance away the force is only applied once. I would very much like to solve this problem and have the force only applied once.
All help is greatly appreciated, thank you in advance.
I solved it!
OnCollisionEnter was being called twice because I had two colliders perfectly stacked on one another, so the projectile was hitting the 2 colliders on the same time step.
Related
I wanna make sphere collision as trigger everytime player shoot by physics.overlapsphere. but it seems like it only create make collision which is not trigger. so how can i solve this?
Collider[] cs = Physics.OverlapSphere(transform.position, redi);
foreach (Collider c in cs)
{
Rigidbody r = c.gameObject.GetComponent<Rigidbody>();
if (r != null)
{
r.AddExplosionForce(power, transform.position, redi);
}
if (c.gameObject.tag == "enemy")
{
Destroy(c.gameObject);
}
if (c.gameObject.tag == "player")
{
c.gameObject.GetComponent<movee>().health -= 5;
}
}
Physics queries rely on Physics.queriesHitTriggers property which defines, if certain calls hit trigger colliders or not (see https://docs.unity3d.com/ScriptReference/QueryTriggerInteraction.html).
You can set QueryTriggerInteraction for your OverlapSphere call as a method parameter. If you do not do so, it uses the global setting, which might be set to ignore triggers in your case.
Proper method call:
Collider[] hitColliders = Physics.OverlapSphere(position, range, layerMaskSphere, QueryTriggerInteraction.Collide);
I have one kinematic object (player) which i move very fast (it's need), also i have another objects(obstacles) - they have collider (trigger), and when collider of player enter obstacle collider and trigger event, game freeze to half or one second.
if i reduce speed, then everything is fine
i've tried make player not kinematic and mark obstacles as not trigger (for use OnCollisionEnter event)
how i move player:
void FixedUpdate() {
if (currentWalk != Direction.NONE)
{
checkDirection(); // when distance <0.1f stop moving
transform.position = Vector3.MoveTowards(transform.position, nextPosition, speed * Time.fixedDeltaTime); //speed = 25f;
}
}
nextPosition = Vector3(0f,0f,7f); // just for example, initially player stand in 0,0,0
void OnTriggerEnter(Collider collider)
{
if (collider.tag == "Finish")
{
success.SetActive(true); //success - any object which initially
inactive
}
}
I expect the game won't be freeze, without reduce speed, because i really don't know, what is problem, there are many game with high speed.
I'm making a simple character that follows the player's cursor. What I also want is for when the game object "enemy" appears the character then goes to that location to alert the player. Once the enemy is gone the character continues to follow the cursor like normal. Is there a reason why my script won't work. How else can I paraphrase it?
public class FollowCursor : MonoBehaviour
{
void Update ()
{
//transform.position = Camera.main.ScreenToWorldPoint( new Vector3(Input.mousePosition.x,Input.mousePosition.y,8.75f));
if (gameObject.FindWithTag == "Enemy")
{
GameObject.FindWithTag("Enemy").transform.position
}
if (gameObject.FindWithTag != "Enemy")
{
transform.position = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x,Input.mousePosition.y,8.75f));
}
}
}
You are not using FindWithTag correctly, as it is a method that takes a string as parameter you need to use it like this:
GameObject.FindwithTag("Something") as stated in the Unity scripting API
Now to apply this to your code you would need to do the following to set your players position based on wether or not an enemy is found (assuming this script is on your actual player object):
if(GameObject.FindWithTag("Enemy"))
{
//If an enemy is found with the tag "Enemy", set the position of the object this script is attatched to to be the same as that of the found GameObject.
transform.position = GameObject.FindWithTag("Enemy").transform.position;
}
else
{
//when no enemy with the tag "Enemy" is found, set this GameObject its position to the the same as that of the cursor
transform.position = Camera.main.ScreenToWorldPoint( new Vector3(Input.mousePosition.x,Input.mousePosition.y,8.75f));
}
However this code will just snap your player instantly to the position of the found Enemy. If this is not the desired behaviour you could use a function like Vector3.MoveTowards instead to make the player move to it gradually.
This code also has room for optimisation as searching for a GameObject every update frame is not the ideal solution. But for now it should work.
I'm going to code coding all the function for you, I'm not pretty sure about the beavihour of your code, I understand a gameobject will be attached to the mouse position, so not really following....
Vector3 targetPosition;
public float step = 0.01f;
void Update()
{
//if there is any enemy "near"/close
//targetPosition = enemy.position;
//else
//targetPosition = MouseInput;
transform.position = Vector3.MoveTowards(transform.position, targetPosition , step);
}
For the f you can use a SphereCast and from the enemies returned get the closest one.
How do I prevent a collision from applying forces in Unity? I am using 2D physics and want an arrow to stick into a crate. I can easily remove the rigid body and collider in the collision callback, but it seems that a frame of collision force is still applied to the arrow, causing slight jumps in position and rotation. Settings isKinematic on the rigid bodies in the collision callback also appears to not prevent this one frame of force being applied.
I am hoping to tell Unity to not apply physics for the collision.
Using kinematic for the life time of the arrow is not an option because the arrow needs to fly realistically until it hits something.
Here is the code for the crate object that handles the collision:
protected virtual void HandleCollision(ArrowScript arrow, Collision2D coll)
{
StickArrow(arrow, coll);
if (DestroyAfterSeconds >= 0.0f)
{
Destroy(arrow.gameObject, DestroyAfterSeconds);
}
}
private void OnCollisionEnter2D(Collision2D coll)
{
ArrowScript script = coll.gameObject.GetComponent<ArrowScript>();
if (script != null)
{
HandleCollision(script, coll);
}
}
private bool StickArrow(ArrowScript arrow, Collision2D coll)
{
Vector2 surfaceNormal = coll.contacts[0].normal;
float surfaceAngle = Mathf.Atan2(surfaceNormal.y, surfaceNormal.x);
float arrowAngle = Mathf.PI + (arrow.transform.eulerAngles.z * Mathf.Deg2Rad);
float angleDifference = Mathf.Abs(BowAndArrowUtilities.DifferenceBetweenAngles(surfaceAngle, arrowAngle));
float penetration = arrow.PercentPenetration * PenetrationPercentageModifier * (1.0f - angleDifference);
if (penetration <= MinimumPenetrationPercentage)
{
arrow.PercentPenetration = 0.0f;
return false;
}
// Make the arrow a child of the thing it's stuck to
arrow.transform.parent = transform;
arrow.gameObject.transform.Translate(new Vector3(-penetration * arrow.Length, 0.0f, 0.0f));
SpriteRenderer thisSpriteRenderer = GetComponent<SpriteRenderer>();
if (thisSpriteRenderer != null)
{
arrow.GetComponent<SpriteRenderer>().sortingLayerID = thisSpriteRenderer.sortingLayerID;
arrow.GetComponent<SpriteRenderer>().sortingOrder = Mathf.Max(0, thisSpriteRenderer.sortingOrder - 1);
}
BowAndArrowUtilities.PlayRandomSound(arrow.CollisionAudioClips, penetration * 5.0f);
// destroy physics objects from the arrow (rigid bodies, colliders, etc.). This unfortunately doesn't prevent this frame from apply force (rotation, position) to the arrow.
arrow.DestroyPhysicsObjects();
return true;
}
Unity version is 5.3.4.
I ended up making the arrow head a trigger. Inside of OnTriggerEnter2D, I then perform a circle cast in the direction the arrow is pointing with a width of the arrow head sprite. Triggers do not get affected by Unity physics calculations.
private void OnTriggerEnter2D(Collider2D coll)
{
ArrowScript script = coll.gameObject.GetComponent<ArrowScript>();
if (script != null)
{
Vector2 dir = -script.ArrowHead.transform.right;
// ray cast with the arrow size y value (thickness of arrow)
RaycastHit2D[] hits = Physics2D.CircleCastAll(script.ArrowHead.transform.position, script.Size.y, dir);
foreach (RaycastHit2D hit in hits)
{
// collider2d is a member variable assigned in Start that is the Collider2D for this object
if (hit.collider == collider2d)
{
HandleCollision(script, hit.normal);
break;
}
}
}
}
Your problem is that OnCollisionEnter and OnTriggerEnter are called after all the collisions are resolved.
The simplest way to solve this without affecting anything would be to change the weight of the box, arrow, or both.
Set the weight of the crate to a high value, and the weight of the arrow to a low value.
Another way is to use trigger colliders, as you have done. However trigger colliders have problematic side-effects. For example, it doesn't call OnCollisionEnter or OnTriggerEnter on the crate. You will have to do all the logic inside the arrow script, which is not much of a problem.
There are a lot of other ugly hacks however. You could set the velocity of the box to 0 after impact, but it would freeze the crate if you hit it as it was moving. You could use the collision information to cancel the force applied to the crate to solve the collision. You could save the last velocity of the crate every frame, and reapply it to the rigid body during the OnCollision call.
I wouldn't suggest any of these, but they are possible.
I have a issue with my 2D game in unity (pokemon style), I'm using transform.position to move the gameobjects.
I have a player and enemies that follow him, all is ok. But when the enemies make a collision, they begin to push each other
I need that nobody to be pushed when the enemies and player get a collision.
I tried to use kinematic in enemies, but the player can push them.
I tried to add a big amount of mass to the player, but he can push the enemies.
I tried to detect the collision in code with OnCollision, but when I cancel the enemy movement, they don't return to move.
----UPDATE----
I need the collision but without pushing between them, here is a video to illustrate the problem
https://www.youtube.com/watch?v=VkgnV1NOxlw
Just for the record, i'm using A* pathfinding script (http://arongranberg.com/astar/) here my enemies move script.
void FixedUpdate () {
if(path == null)
return;
if(currentWayPoint >= path.vectorPath.Count)
return;
Vector3 wayPoint = path.vectorPath [currentWayPoint];
wayPoint.z = transform.position.z;
transform.position = Vector3.MoveTowards (transform.position, wayPoint, Time.deltaTime * speed);
float distance = Vector3.Distance (transform.position, wayPoint);
if(distance == 0){
currentWayPoint++;
}
}
----UPDATE----
Finally I'll get the expected result,changing the rigidbody2D.isKinematic property to true when the target was close and stop it
Here is a video https://www.youtube.com/watch?v=0Zm0idUU75s
And the enemy movement code
void FixedUpdate () {
if(path == null)
return;
if(currentWayPoint >= path.vectorPath.Count)
return;
float distanceTarget = Vector3.Distance (transform.position, target.position);
if (distanceTarget <= 1.5f) {
rigidbody2D.isKinematic = true;
return;
}else{
rigidbody2D.isKinematic = false;
}
Vector3 wayPoint = path.vectorPath [currentWayPoint];
wayPoint.z = transform.position.z;
transform.position = Vector3.MoveTowards (transform.position, wayPoint, Time.deltaTime * speed);
float distance = Vector3.Distance (transform.position, wayPoint);
if(distance == 0){
currentWayPoint++;
}
}
You can do this in several ways,
You can use Physics2D.IgnoreCollision
Physics2D.IgnoreCollision(someGameObject.collider2D, collider2D);
Make sure that you do the IgnoreCollision call before the collision occurs, maybe when objects instantiate.
or alternatively you can use, Layer Collision Matrix
Unity Manual provides information on using this. This simply does the collision avoidance by assigning different GameObjects to different layers. Try:
Edit->Project Settings->Physics
Or if you want it to just stop moving, You can easily do it like,
bool isCollided = false;
// when when OnCollisionEnter() is called stop moving.
//maybe write your move script like
void Move() {
if(!isCollided) {
// move logic
}
}