OnCollisionStay Vs OnCollisionEnter - unity3d

Hi I've been using Unity for a while and for the life of me I can still not tell the difference between OnCollisionStay and OnCollisionEnter. I'm assuming that OncollisionEnter is called when a rigidbody (like a ball) makes contact with another rigidbody/collider such as a wall. But when I look at the example for OnCollisionStay, I get completely confused. Even if its called once per frame, If I for example jump up in the air and hit a ceiling above me, is it still called? What exactly are the differences? Here is what the Unity documentation says and the code I was using.
"OnCollisionEnter is called when this collider/rigidbody has begun touching another rigidbody/collider."
"OnCollisionStay is called once per frame for every collider/rigidbody that is touching rigidbody/collider."
if(Input.GetKeyDown(KeyCode.W) && OnGround == True)
{
rigidbody.velocity.y = jumpHeight;
}
onGround = false;
function OnCollisionStay()
{
onGround = true;
}

It is really pretty simple. Let's assume that the collision starts, the colliders keep intersecting for a period of time (several frames), and after a while they separate again. The events then are:
OnCollisionEnter for the first frame only, and never again until after an OnCollisionExit
OnCollisionStay for the entire duration (all frames)
OnCollisionExit for the last frame only
Even if its called once per frame, If I for example jump up in the air and hit a ceiling above me, is it still called?
Sure it is. But then for that new collision. If there is a period in between where you're not colliding with anything however, you will not get OnCollisionStay events for that duration.
To get a deeper understanding of these events, it's perhaps best to create a simple demo scene in which you manually intersect two colliders, and have some text written to the console for the various events.

Bart's answer is correct so this answer is more of an example of how you would use the two.
So OnCollisionEnter will fire 1 per collision case lets say for example a ball hitting a wall and OnCollisionEnter could maybe do some damage to the wall or destroy the wall. (Like breakout)
//Ball
#pragma strict
function OnCollisionEnter (col : Collision)
{
if(col.gameObject.name == "Wall")
{
Destroy(col.gameObject);
}
}
However in OnCollisionStay you would use that if you have a character with hit points that steps into a poison cloud and removes continuous hit points or removes incremental timed hit points as long as OnCollisionStay continues to fire after a given amount of time. (I'm thinking castlevania SOTH)
//PoisonGasCloud
#pragma strict
function OnCollisionStay(collisionInfo : Collision)
{
collisionTime = Time.deltaTime;
if ( collisionTime > 5)
{
hero.hp-=10;
}
}
Note:I usually code in C# so my UnityScript might be a little off.

When collision start: OnCollisionEnter & OnCollisionStay
When collision continue: OnCollisionStay
When collision end: OnCollisionExit
Note 1: OnCollisionStay & OnCollisionExit will not be called simultaneously.
Note 2:OnCollisionEnter call before OnCollisionStay.

Related

RaycastHit2D vs OnTriggerEnter2D

In my game you can use, axes, pickaxes, hoes, etc. Each to gather a different type of material (ore, wood, crops, etc).
My old system I just set a tiny collider on the tile in front of my player, and then in OnTriggerEnter2D on my nodes, trees, farm-tiles, etc. I checked what category of my Active Item that was entering (below example on my mining-nodes):
void OnTriggerEnter2D(Collider2D other)
{
if(other.gameObject.CompareTag("ActiveItem"))
{
ItemObject activeItemObj = GameManager.manager.activeItem.GetComponent<InventoryItem>().item;
if(activeItemObj.category == "pickaxe")
{
if (!isHit)
{
hit();
}
}
}
}
However this always felt kind of ugly, plus no matter how small the "front collider" is, there is always a chance to hit the tile intersection and hit as many as 4 tiles at the same time.
So instead I wanted to use RayCastHit2D, so I created these methods in my PlayerController Script:
public GameObject GetTileInFront()
{
Vector2 frontRay = new Vector2(transform.position.x + lastMove.x, transform.position.y + lastMove.y + 0.5f);
RaycastHit2D hit = Physics2D.Raycast(frontRay, Vector2.zero);
if (hit.collider != null)
{
Debug.Log("Target Position: " + hit.collider.gameObject.name);
return hit.collider.gameObject;
}
return null;
}
public void ActionInFront(string activeItem)
{
if (activeItem.Equals("pickaxe"))
{
if (GetTileInFront().CompareTag("Node"))
{
GetTileInFront().GetComponent<NodeManager>().hit();
}
}
}
Now I obviously need to create checks like this for every type of tool/tile.
Which leads me to my question:
Which of these 2 methods would be better for performance? I am completely self tought so I always worry I am making some obvious blunder that I cant see.
I definently like the precision of the RayCast better (even though it forces all my colliders to be at least 1x1 in size due to checking 1 tile in front).
Your solution looks OK to me!
Raycasts have the potential to be abused to the point where they're very expensive. The three things you'll want to be mindful of:
How long is the raycast? Keeping it under a tiles length should be good in your case.
What types of geometry is in your scene?: Mesh colliders can cause Raycasts to chug, but in a 2D game where I imagine everything is either a square or a circle you should be good.
How frequently do they occur? Calling a Raycast every frame can be rather expensive, but only calling it when your player is doing the action should be fine.
You mentioned that you were worried your solution will force your colliders to be at least 1x1, but there are ways to get around that. Right now what you're doing is just checking right at a point in front of your player. Instead you may want to consider casting a ray from the player in that direction and it could hit anything a long the way, even if it's smaller than a 1x1 tile. You can do this by changing the second argument of your raycast to something like this:
Physics2D.Raycast(transform.position, Vector2.up * .5f);
The Raycast function takes the point it will be shot from as the first argument, and the direction/length as the second argument (leaving the length 0 like did will just check the point at the first argument).
It's worth noting that if you try to do this with your current solution, all that will happen is that you're character will try to mine himself as it will be the first thing the ray hits! You'll need to add a LayerMask to your raycast to determine what the ray can and can't hit (or if you're lazy, just make sure the ray starts from a point outside the player's collision).

Raycasting an instantly moved collider

I need to move some transforms with attached colliders to a specific position, then check if one of them is hit by raycast.
I've done that the naive way (pseudo code) :
foreach(object in objects){
actual_position = object.transform.position
object.transform.position = object.new_position
}
if(Physics.Raycast(...)) objectHit();
// Then I revert each of them them back to their actual_position
After testing multiple times with the same context (same positions between tests for each objects), the raycast sometimes miss, sometimes not (~50/50).
Done a bit of research and found that in the Raycast doc page :
If you move colliders from scripting or by animation, there needs to
be at least one FixedUpdate executed so that the physics library can
update its data structures, before a Raycast will hit the collider at
it's new position.
So I calmed my anger and started looking for a solution.
This thread has a way using a coroutine to wait the next tick :
Raycast doesn't work properly
I'm affraid it won't work for me, as I need the objects to get back to their real position instantly.
This process can happen multiple times per frame (each time a player fire his weapon)
Is there a way to force the colliders update ?
If not... should I make my own raycasts and colliders ? :(
Thanks
Another workaround is to deactivate and activate Gameobject (attached collider) immediately. In this case collider position will be updated in single frame.
Another solution is
Physics.autoSyncTransforms and Physics.SyncTransform
Maybe you can try this out (adding to your pseudo-code):
foreach(object in objects)
{
actual_position = object.transform.position;
object.transform.position = object.new_position;
StartCoroutine(CheckToRevertOnNextFixedUpdate(object, actual_position));
}
IEnumerator CheckToRevertOnNextFixedUpdate(object, actual_position)
{
yield return new WaitForFixedUpdate();
if(Physics.Raycast(...)) objectHit();
// Then I revert each of them them back to their actual_position
}
Essentially this delays your check to the next FixedUpdate() for each object - and reverts each of them if needed. I hope this is not overcomplicated, since you only add a few lines of code.
I'm also assuming that moving the object's position for 1 FixedUpdate() frame would not have a visual effect of the the object teleporting to the new position and back. However, you can always move the collider only, and then move the rest of the transform there after the FixedUpdate().
Performance-wise, the best method seems to be updating the RigidBody.position:
private Rigidbody Rigidbody;
void Start()
{
Rigidbody = gameObject.GetComponent<Rigidbody>();
}
void Upate()
{
//.... your code
Rigidbody.position = newPosition;
}
Much faster then deactivate/activate or Physics.SyncTransform().

Unity collision not being detected

I am learning to create a simple fps game in unity the problem is that the collision does not update itself for example initially when my player is on the ground console prints "floor" by "Debug.log(collision.gameObject)" but when it intersects other objects such as a cube console will print out "cube" but when I walk away from it , console does not change back to "floor" Why????
I am using transform.translate to move and jump and using method OnCollisionEnter for collision detection
OnCollisionEnter is triggered only when object enters the collider.
A) Make a list of all encountered objects by adding them when OnCollisionEnter happens and removing when OnCollisionExit happens. Then whenever you need to make sure you are on "floor" check it in the list.
B) Use OnCollisionStay and every frame you will be notified if you are touching the "floor".
Remember one thing, the other object you want to collide with need to have a collider component asigned, make sure of it. Join this with the previous answer.
I recommend to verify collision.
Here on simple example:
void OnCollisionEnter (Collision col){
if (col.gameObject){
Debug.Log("Object name : "+ col.gameObject.name);
}
}

Unity Raycast2D results are not as expected?

I'm currently working on a practice project which involves stacking of blocks. It is sort of similar to the game of stackers (the arcade game), except my version uses free-form movement instead of grid-based movement.
I have 3 prefabs for the blocks: SingleBlock, DoubleBlock, TripleBlock.
I structured each of them like this: the parent is an empty gameobject with my MovementScript that moves them left/right, while the children are the block sprite with a BoxCollider2D.
The MovementScript is attached to the empty game object(the parent) so that the block set moves uniformly left/right, for what it's worth.
For the actual stacking logic, I'm using Raycast2D to detect if there is a block below. But the problem is the results I get is unexpected.
Here is a snippet of my current code:
foreach(Transform t in currentBlockSet.transform)
{
// get all the children of this blockset. to do this, we use the transform beause it is IEnumerable
GameObject block = t.gameObject;
RaycastHit2D hit = Physics2D.Raycast(block.transform.position, Vector3.down, rayCastLength); // 0.5f raycast length
//Debug.DrawRay(block.transform.position, Vector3.down * rayCastLength);
if(hit.collider != null)
{
// this means there is a block below, we hit something
Debug.Log("True");
}
else
{
Debug.Log("False");
}
}
This code is called each time the player stops the current blockset that is moving by the way.
The problem is I always get true in my logs even though I purposely didn't align the block set properly. I never get false even if I'm way off with my alignment. Why is this so?
I do like to mention that there's nothing else in the scene. It is just the blocks, so there can't be another object to collide with.
Is there something wrong with the logic or how I'm using Raycast2D?
Appreciate any help.
I believe what is happening is that they Raycast2D is detecting the block it is shooting from. Since you are using the block's transform.position as the origin of the Raycast, this means that the Raycast will be shooting from the center of the block.
To test this, I slightly modified your code so it would fire a Raycast in the same way, but instead of logging just "True" I logged the name of the object that was hit.
void Update ()
{
RaycastHit2D hit = Physics2D.Raycast(this.transform.position, Vector3.down, 0.5f); // 0.5f raycast length
if (hit.collider != null)
{
Debug.Log(hit.collider.gameObject.name);
}
else
{
Debug.Log("False");
}
When run, the object that was detected by the Raycast, as expected, was the object the Raycast was originating from.
To solve this issue, I would suggest adding an empty child GameObject to your blocks called "RaycastOrigin". Position this GameObject just underneath the block such that it is outside of the block's box collider. Then you fire your Raycast from the "RaycastOrigin" instead of the blocks transform.position That way, the Raycast would not hit the block its shooting from, but rather a block below it.

Trigger in Unity 3D project working not constantly

There is a remake of the Snake game. The snake's head is a trigger, and the apple uses only a collider. Now OnTriggerEnter() does not work every time - the snake's trigger have to enter several times into apple's body to get it.
There is code used for eating:
void Head.OnTriggerEnter(Collider col)
{
if(col.CompareTag("Food"))
{
gameController.FoodEated();
}
}
public void GameController FoodEated()
{
Destroy(currentFood);
InitializeMeal();
head.GrowUp();
}
void GameController.InitializeMeal()
{
currentFood = (GameObject)Instantiate(foodPrefab, FindFreeSpace(), Quaternion.identity);
}
And there is some kind of magic: The first apple could be eaten normally, but the snake can walk through the second apple a few times before the apple will be activated.
I tried every trigger functions and none of them are working. http://www.youtube.com/watch?v=z_UQi7SGOLw - video of a bug. The snake is going through the apple for 3-4 frame updates. I have another idea for realizing an apple, but a trigger is more appropriate, I think.
If your snake moves too fast and the apple collider is too small than I guess that Unity's engine can sometimes miss collisions.
If on the snake you have a Rigidbody component than you can increase collision detection quality by changing "Collision Detection" from "Discrete" to "Continues" or "Continues Dynamic".
As you can see on the video above, my snake is growing by creating a tailelement at the current transform.position. TailElement starts moving when the distance between last two tail elements is more or equals 1.
I just tried to decrease the radius of the head's collider by 10% and now the tail element is not collider during the movement of snake. Now it works fine.