I want to simulate two pistons that replicates the behaviour of a balance. I'm doing this with spring joints and applying the inverse weight one another when OnCollisionStay is called. This are my pistons and their rigidbodies and joints. Are exactly the same on both.
This is my SpringForceTransmisor.cs code:
using UnityEngine;
public Rigidbody InverseJoint;
private void OnCollisionEnter(Collision collision) {
Debug.Log("Enter");
}
private void OnCollisionExit(Collision collision) {
Debug.Log("Exit");
}
private void OnCollisionStay(Collision collision) {
InverseJoint.AddForce(-(collision.rigidbody.mass * Physics.gravity));
Debug.Log("Stay");
}
And this is a video of what's happening.
So, according to the log showed on the video, OnCollisionStay() stops being called even if OnCollisionExit() had never been called. How is this possible? I've always thought that OnCollisionStay() is called every frame from OnCollisionEnter frame and OnCollisionExit frame.
Can anyone shed some light about what's going on here?
According to the documentation on OnCollisionStay, it says:
OnCollisionStay is called once per frame for every collider/rigidbody
that is touching rigidbody/collider.
Unfortunately, this is not true sometimes. The OnCollisionStay function is called few times in some cases and the call is then stopped. This is either a long time bug that has not been fixed for years or the documentation is wrong.
My usual advise to people is to abandon the OnCollisionStay function and simply set a boolean variable to true in the OnCollisionEnter function then set it to false in the OnCollisionExit function. You can then use the Update function as the OnCollisionStay function by checking that boolean variable in the Update function.
public Rigidbody InverseJoint;
bool collisionStay = false;
Collision collision = null;
private void OnCollisionEnter(Collision collision)
{
Debug.Log("Enter");
collisionStay = true;
this.collision = collision;
}
private void OnCollisionExit(Collision collision)
{
Debug.Log("Exit");
collisionStay = false;
this.collision = collision;
}
void Update()
{
if (collisionStay)
{
InverseJoint.AddForce(-(collision.rigidbody.mass * Physics.gravity));
Debug.Log("Stay");
}
}
The answer to your question would become obvious if you included in the video the part, where the "Enter" is being registered.
Now to explain what is happening, let me show an example on a traingulated sphere . Here is how it looks like (with some triangles intentionally made invisible).
Keep in mind that it is hollow, all it essentially is are a bunch of points that form triangles. Now say this is a mesh for some mesh collider component. If any other collider interacts with this sphere_mesh_collider, the only way for unity to detect it is by checking for the triangles of those two colliders having intersections. This is to say that ANY OTHER area that is not covered INSIDE the triangles will not be checked. In other words the volume of my sphere here will never detect collisions (nor call the collision stay, but the collider havent exited either, so neither it calls exit).
That is what I think is happening in your case, although I can not say for certain because I do not see the whole process of colliders starting to interact (Enter) and than coming apart (Exit).
Edit: You can easily observe this if you enable unity wireframe (or shaded wirefram) in the editor window.
Another note from the Unity documentation:
Note: [...] Collision stay events are not sent for sleeping Rigidbodies.
You could try setting Rigidbody.SleepThreshold to zero to see if that solves your problem.
Just solved this myself. This is because your rigidbodies are sleeping, try setting the rigid body sleep to "Never sleep" and it will work.
Related
novice to intermediate Unity developer here. I've been hitting a pretty significant roadblock the past ~2 days concerning the raycast detection of objects with specific layers. I've been researching this issue quite a lot, and all the solutions I've found don't seem to reflect the strange issue I'm facing.
Basically, the problem follows this sequence of events:
My player character has a vision cone shaped trigger mesh called 'InSightBox' which detects all objects with the tag 'Mob' and adds them to a List of colliders called 'MobsInRange'.
public List<Collider> mobsInRange;
public List<Collider> GetColliders()
{
return mobsInRange;
}
// Start is called before the first frame update
void Start()
{
mobsInRange = new List<Collider>();
}
// Update is called once per frame
void Update()
{
}
//add enemy with tag 'mob' to list
private void OnTriggerEnter(Collider other)
{
if(!mobsInRange.Contains(other) && other.tag == "Mob")
{
mobsInRange.Add(other);
}
}
//remove enemy with tag 'mob' to list
private void OnTriggerExit(Collider other)
{
if (mobsInRange.Contains(other) && other.tag == "Mob")
{
mobsInRange.Remove(other);
}
}
This list is then fed up to the root/parent player game object containing everything relating to the player.
public Transform closestMob;
public List<Collider> mobs;
public Transform GetClosestEnemy()
{
Transform tMin = null;
float minDist = Mathf.Infinity;
Vector3 currentPos = transform.position;
foreach(Collider trans in mobs)
{
//find enemy with closest distance and set tMin to it. Method returns tMin
float dist = Vector3.Distance(trans.transform.position, currentPos);
if(dist < minDist)
{
tMin = trans.transform;
minDist = dist;
}
}
//Debug.Log(tMin);
return tMin;
}
The player then uses a 'Look at' method to find the closest of all 'mobs' to the player. The player will set their forward transform to look at the closest mob.
Problem Step ---> 4) When the player raises their gun and attempts to shoot the closest enemy, a ray is cast with a layermask that looks only for objects on the layer 'Enemy', the 8th layer. When the ray detects the enemy, the enemy script should fire its 'TakeDamage' method which decreases the 'curHealth' variable by 8. Only problem is, the cast doesn't seem to detect the enemy object on the 'Enemy' layer.
LayerMask layerMask = 1 << 8;
void Fire()
{
//play the audio of the gunshot
StartCoroutine("SetPlaying");
RaycastHit hit;
//cast a ray from the player forward and check if the hit object is on layer 'Enemy'
if (Physics.Raycast(transform.position, transform.forward, out hit, Mathf.Infinity, layerMask))
{
//if hit object is an enemy, set its 'gotShot' bool to true
print("hit enemy");
closestMob.GetComponent<EnemyBase>().gotShot = true;
}
//play gunshot sound and stop player from turning
source.clip = fireSound;
source.PlayOneShot(fireSound, gunshotVolumeScale);
turnSpeed = 0;
}
I'll also note that all the solutions I've seen to this issue are not working for me. I declared an int variable called 'layerMask' and initialized it in Awake() by bit shifting layer 8 into it (i.e. int layerMask = 1 << 8), but it still isn't detecting it. The enemy contains all that I belive it should need for this to work, including a rigidbody, a capsule collider, the associated scripts, as well as being on the 'Enemy' layer.
This is where it gets weird (at least to my knowledge), when I invert the mask in the cast (~layerMask), it does exactly what I'd expect, and begins firing the code within the raycasts if statement when the player 'shoots' anything that doesn't have the 'Enemy' layer.
Any help would be suuuper appreciated as I'm getting to the point of slamming my face into the desk :/
Side Note: I'm getting to the point where I may just attach a 'fire range' cube trigger to my player and enabled it when the Fire() event is triggered, then have that check for game objects with the tag 'mob' as that kind of detection works most consistent to me.
First, be sure that you didn't confuse layers with tags. They are different.
Second, get rid of any implicit actions and references, i.e. don't use bitshifting, layer indices, or any non-straightforward reference. Instead, create something like this:
[SerializedField] private LayerMask _layerMask;
Use inspector to assign needed layer(s).
This way you will explicitly see, which layer you are using. This is useful not only for you, but for you in future, when you forget layers' indices. Also, for anyone who aren't familiar with the project.
Third is for debug. Be sure that your raycasting works as intended at other aspects:
Try remove layerMask and see if the ray goes where you want it to
Use custom gizmos to check if you cast the ray in a right direction
Try using RaycastAll. Maybe some objects catch (block) your ray earlier than you think
Looks like the issue was that I had my 'Enemy' prefab which contained the necessary components (RigidBody, Collider, scripts, NavmeshAgent) nested inside an empty game object. I thought at first to make something a prefab you needed to have it inside an Empty. I see now that is rather redundant and not necessary (at least in my case).
Physics.RaycastAll solved this issue as it no longer got 'halted' by the parent empty's collider and also hit the child.
I actually got it working just using a regular Physics.Raycast by rebuilding the Enemy prefab as just a single Capsule object (which I'll replace with character meshes later on).
Side Note
Before I got this new method working, I also used a different one that achieves the same goal in a pretty lightweight manner.
I added a long and thin box trigger to the front of my player so that it has enough distance to collide with any enemies within the shooting range.
I then enable the trigger any time the player enters their 'Aiming' state. If the trigger collides with any mesh that has the tag "Mob', it sets a bool on the player that indicates if the enemy is in range.
Then if the enemy is in range && the player enters the 'Firing Gun' state, a method in the Enemy Base script is fired that decrements the enemy health by a publicly decided variable called damagetaken.
Both the Raycast method and the box trigger method work equally well for me, but the box trigger one just took less time to figure out and gave me less headache haha.
Hope this helps anyone else in a bind!!!!!
So I have a wall where the player can jump on and it detect the collision and reduce the gravity. This is working fine.
This is what it looks like
Mossy Wall (L)
and here is the code:
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("MossWall"))
{
onMossyWall = true;
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("MossWall"))
{
onMossyWall = false;
}
}
As you can see it's pretty basic, all I want is to set onMossyWall to true when there is a collision and it works perfectly for this one.
Now I want to do the same but for an opposite wall, I copy the object (Moss Walls (R)), and rotate the platform effector so that it's on the opposite direction, and yet nothing happens. onMossyWall stay false. The tag is the same and the object is basically the same as the first one, why isn't this working ?
Mossy Wall (R)
I assume I must have forgotten something stupid but I have been looking for solutions for a while and I found nothing that works. I tried with a different tag but still nothing.
As unity docs states:
OnCollisionEnter is called when this collider/rigidbody has begun
touching another rigidbody/collider.
So please make sure you have colliders on both objects and one of the object must have a rigidbody.
I have a very basic infinite runner and I want to make my player jump. I have attached rigidbody to my player, have instantiated it. here is some part of my code
void FixedUpdate()
{
if (OnGround)
{
if (Input.GetKeyDown(KeyCode.Space))
{
rBody.AddForce(new Vector3(0, 10, 0), ForceMode.Impulse);
Debug.Log("jump");
OnGround = false;
}
}
}
void onCollisionEnter(Collision other)
{
Debug.Log("collision");
if (other.gameObject.CompareTag("ground"))
{
OnGround = true;
}
}
I have tried to put this part in Update(), no result.
The interesting part is that Debug.Log("jump") shows on the console, but the player doesn't want to jump.
Method void CollisonOther() is never called.
I have also tried to change velocity and use transform.translate, no result
jump. How can I make it jump?
There are a couple of things that could be the issue. Make sure the isKinematic box is unchecked. It's also possible that the character is jumping but you cannot see it if your sprites were imported too big (ie: it's jumping a very very small amount relative to the size). You could try printing some more debug statements of the character's position to see if it is actually changing or not.
it seems to be correct in your code but don't know why not jumping. BTW, you can try with this code
just without AddForce and changing the checking you are using.
if (Input.GetButtonDown("Jump") && OnGround)
{
rBody.velocity = new Vector2(rBody.velocity.x, jumpSpeed);
}
Make sure it is under your Update method coz Input calls in Update method. If your 'OnGround' Layer check is
perfect it should work.
OnCollisionEnter should have a capital O instead of lower case. As written, onCollisionEnter will never be entered.
That's the only code problem that could be wrong, assuming you actually assigned the other variables corrrectly. However, there could be a variety of game object related behavior.
Is the ground tag applied to the game obejct that you think your runner is colliding with? If not, it won't jump.
Is your mass for the jumper's rigidbody very high? With the default of 1, it jumps pretty high, but could not get the right amount of force if it was a larger mass.
I ran into the same issue and, after hours of investigating, in my case it had nothing to do with the code, but with the below Unity bug (still present in 2020.1.6f1).
For some reason, it interfered with any key inputs I checked for in the code (in your case GetKey(KeyCode.Space)
https://issuetracker.unity3d.com/issues/urp-errors-are-constantly-thrown-when-active-input-handling-is-set-to-input-system-package
The fix was going to Edit - Project Settings - Active Input Handling - select Both.
I know this has been asked many times but I have checked as many forums as I can. Here is everything I'm doing:
I have a cube (that in this case is serving as the floor) with the following properties
and I have a "Runner" that is just a cube with the following properties
I froze the z position and rotations because this is a side-scroller.
The onCollisionEnter method for the "Runner" that you can see is attached to the "Runner" object is as follows:
void onCollisionEnter(){
print("Collision Detected");
}
And when I run it, I get bupkis. So. Help please? It seems so simple but I am clearly messing something up.
It is not
void onCollisionEnter()
but as stated in the documentation
void OnCollisionEnter(Collision collision)
So the method is written in full CamelCase and has an argument.
I have an object with a mesh collider and a prefab with sphere collider. I want the instance of the prefab to be destroyed if the two collide.
I wrote the following in a script:
private void OnCollisionEnter(Collision c)
{
if (c == target)
Destroy(transform.gameObject);
print("something"); // Doesn't get printed
}
But it is not working. I have tried toggling isTrigger on both the objects.
I had the same problem of OnCollisionEnter not being called and found this question.
For me, the problem was that I was making a 2D game so the answer is to use the OnCollisionEnter2D function instead.
Have a look at this table
If you want your OnCollisionEnter to be called make sure:
(a) Both objects have a collider attached.
(b) None of the objects is a trigger collider (this will issue OnTrigger function & not OnCollisionEnter)
(c) One of the objects (doesn't matter which of them) is a rigid, non kinematic & non static object (the second don't have to be a rigid body).
(d) Due to computational difficulties MeshCollider might have hard times colliding with other mesh collider, use them with caution.
(e) Make sure both the objects are in the same layer (or at least that they collide in scene settings).
(f) If you are working in 2d - OnCollisionEnter2D will be called, rename your function.
Make sure one of them has a non-kinematic rigidbody attached. Taken from the Unity docs:
When a collision between two Colliders occurs and if at least one of them has a Rigidbody attached, three collision messages are sent out to the objects attached to them. These events can be handled in scripting, and allow you to create unique behaviors with or without making use of the built-in NVIDIA PhysX engine.
From here: Unity3D MeshCollider
I had a similar problem. The box collider wasn't as big as the collision object. Setting the x and z values to 2 units fixed the problem!
Have you tried using the OnTriggerEnter() method and setting a collider on the object to a trigger?
If it doesn't need to tell what object its colliding with you could do a simple
void OnTriggerEnter(){
Destroy(transform.gameObject);
}
Edit:
Also I have done OnCollision like this
private string hitobject;
void OnCollisionEnter(UnityEngine.Collision hit)
{
hitobject = hit.gameObject.tag;
if(hitobject == "Plane")
{
isgrounded = true;
}
}
None of the objects are triggers and they don't need rigid bodies to work.