Children collider not getting called - unity3d

Context
I'm working in a pickup system in my game. I've a component called AbstractSightCollider that has a sphere collider and some AbstractPickupableObject that are the objects meant to be picked up.
AbstractSightCollider is attached to the main character, but could be attached to any alive entity or anything that is able to contain inventory objects.
The way i designed it, it's that when AbstractSightCollider detects an object, it fires an UnityEvent called PickupDetected and when the player leaves the range of pickup, it call an UnityEvent called PickupLeave
The problem
I can't make OnCollisionEnter and OnCollisionExit trigger.
Some code
This is attached to AbstractSightCollider
public class AbstractObjectSight : MonoBehaviour
{
public OnPickupableDetected pickupDetected;
public OnPickupableLeave pickupLeave;
private void OnCollisionEnter(Collision col) {
GameObject gameObject = col.gameObject;
AbstractPickupableObject abstractPickupableObject =
gameObject.transform.GetComponent<AbstractPickupableObject>();
if (abstractPickupableObject != null) {
pickupDetected.Invoke(abstractPickupableObject);
}
}
private void OnCollisionExit(Collision col) {
GameObject gameObject = col.gameObject;
AbstractPickupableObject abstractInventoryObject =
gameObject.transform.GetComponent<AbstractPickupableObject>();
if (abstractInventoryObject != null) {
pickupLeave.Invoke(abstractInventoryObject);
}
}
[System.Serializable]
public class OnPickupableDetected : UnityEvent<AbstractPickupableObject> { }
[System.Serializable]
public class OnPickupableLeave : UnityEvent<AbstractPickupableObject> { }
}
And here are the properties:
Thanks for your time

Make sure both objects (the one with the script and the one that will cause the trigger) have colliders and rigidbodys, I find if one doesn't have those the triggers and collisions will not work.

I just found out the problem.
OnCollisionEnter and OnCollisionExit aren't the events that i needed to listen, because they work with rigidbody. My AbstractSight is that, a non body abstract sphere where the entities are allowed to grab items.
Instead, i used OnTriggerEnter, OnTriggerExit and now it works like a charm.

Rigid body on the parent object means the collider will work on ANY child component but the rigid body MUST be on the parent object.
Example:
MyProjectile has a rigid body and a collider - set isTrigger = true
MyEnemy parent object just holds scripts
MyEnemy childObject holds the enemy prefab including its collider,
Everything is set to isKinematic = true, with no gravity.
Everything works fine.

Related

Prefab instantiated as an object with my methods

I have an idea that there must be a way to attach a class to a prefab - basically to add my own methods to the code. Then, when I instantiate my prefab in runtime I want it to be an instance of this class.
Now, I can attach a script to a prefab and it's Start and Update works, but my idea is something like this:
public class PowerupWidget : MonoBehaviour {
void Start() {
Debug.Log("PowerupWidget created!!!");
}
void SetTitle(string title) {
// ... set the prefab's title widget text ...
}
}
When I instantiate my prefab, the Start method is called. But the instance is of course the GameObject type.
// in my scene script:
// prefab is my PowerupWidget
GameObject p = Instantiate(prefab, transform.position, Quaternion.identity, this.transform);
// here p is GameObject of course
Is there a way how to get to the instance of the PowerupWidget class, or am I completely wrong in my assuptions?
Structure in unity :
Things are divided into entities and component, when entities are gameobject and components are classes(Script) that are attached to this gameobject.
You just need to do this
PowerupWidget yourWidget = p.GetComponent<PowerupWidget>();

Why the component automatically was removed on collision event of the Unity?

My English skill is poor I'm not a native English speaker.
Please understand.
I want to make the logic that detecting collision
For that, I make a character class. The Character class inherits the MonoBehaviour of the Unity system and has a feature as below.
The class has the container to put the skill was collided with own.
The class has the coroutine to show the status of the container. This coroutine starts when the Character class starts.
The class overrides OnTriggerEnter2D function of the Unity system. In this function, the skill that collides with own is added to the container.
I made the above feature as below code.
public class Character : MonoBehaviour
{
private ConcurrentDictionary<Skill, float> _skills = new ConcurrentDictionary<Skill, float>();
protected virtual void Start()
{
StartCoroutine(Check());
}
private IEnumerator Check()
{
float delayTime = 0.1f;
First:
yield return new WaitForSeconds(delayTime);
foreach (var skill in _skills.Keys.ToList())
{
Debug.Log($"_skills: {skill}");
}
goto First;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag != "cold_wind") return;
var skill = collision.GetComponent<Skill>();
_skills.TryAdd(skill, 100);
}
}
And I added BoxCollider2D component to the prefab that to add the above Character class.
After then I made the Skill class. I omit the Skill class because the Skill class doesn't have a feature yet. I added BoxCollider2D and RigidBody2D components to the prefab that to add the Skill class.
Finally I checked the Simulated function of the RigidBody2D to receive a collision event.
The program executes as the below sequence.
The skill is generated for 3 seconds and it collides with the character.
OnTriggerEnter2D function of the Character was called and skill that collided was added to the container. (Concurrent<Skill, float>)
The Check coroutine shows the skill be included in the container.
Here, I have the problem. Logically I think the Skill class included in the container must not be removed automatically.
But after 3 seconds (the skill effect appears for 3 seconds) if the skill effect disappears then the Skill class included in the container was removed. At the result Check coroutine shows null as below.
Could someone tell me what I did wrong?
Any advice would be much appreciated.
Thanks for reading.
Most likely everything you are doing is working as intended. The one mistake would be unexpected behavior with TryAdd, where the value added can be null.
Inside of your OnTriggerEnter2D function, add a check to determine what you collided with to only check for spells. The easiest way to do this is check the tag of the object. For all of your spells, add a tag that you can check against, then change your collision code to look something like
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag == "SpellTagHere")
{
// the issue is this line can result in NULL if another object collides with your player
var skill = collision.GetComponent<Skill>();
// meaning a null is added here
_skills.TryAdd(skill, 100);
}
}

Pooled GameObject in Unity destroys itself after SetActive if Force is applied

When I retrieve an object from a list of created objects and reactivate it, it destroys itself, but only if I have force applied to start it moving. If I never apply force, everything works as intended and I can keep activating the object over and over.
I've tried putting a debug.log in the OnCollision and it isn't colliding with anything.
As said above, if I never initiate speed, the rest works fine.
When speed is applied, the first projectile works, the second destroys itself.
I've tried manually activating them instead of by code. Same result.
Exception: If I manually activate/deactivate objects myself through the inspector, they work if I only have 1 shot in the pool.
The one shot rule doesn't work if I do it through code. It breaks even if I only have one shot.
All the code worked when I wasn't using pooling.
Spawn code:
void CreateBullet(int GunID)
{
GameObject ShotFired = Guns[GunID].GetComponent<Weapon>().FireWeapon();
if (ShotFired == null)
{
ShotFired = Instantiate(Guns[GunID].GetComponent<Weapon>().Projectile,Guns[GunID].gameObject.transform);
Physics.IgnoreCollision(ShotFired.GetComponent<SphereCollider>(), Guns[GunID].GetComponentInParent<CapsuleCollider>());
Guns[GunID].GetComponent<Weapon>().AmmoPool.Add(ShotFired);
}
ShotFired.transform.position = Guns[GunID].transform.position;
ShotFired.transform.rotation = Guns[GunID].transform.rotation;
ShotFired.SetActive(true);
}
Projectile Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror;
public class Projectile : NetworkBehaviour
{
public float Speed;
public float LifeTime;
public float DamagePower;
public GameObject ExplosionFX;
// Start is called before the first frame update
void Start()
{
GetComponent<Rigidbody>().AddRelativeForce(Vector3.up * Speed, ForceMode.VelocityChange);
StartCoroutine(DeactivateSelf(5.0f));
}
private void OnDestroy()
{
Debug.Log("WHY!");
}
IEnumerator DeactivateSelf(float Sec)
{
yield return new WaitForSeconds(Sec);
Explode();
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.GetComponentInChildren<Vehicle>())
{
collision.gameObject.GetComponentInChildren<Vehicle>().CmdTakeDamage(DamagePower);
}
Explode();
}
void Explode()
{
GameObject ExplosionEvent = Instantiate(ExplosionFX, this.transform.position, Quaternion.identity);
NetworkServer.Spawn(ExplosionEvent);
GetComponent<Rigidbody>().AddRelativeForce(Vector3.zero);
gameObject.SetActive(false);
}
}
Any thoughts welcome. Thanks in advance!
In case anyone else stumbles across this, here's what happened and why assigning a movement speed to the object was what caused the issue.
My projectiles had a 'trailrenderer' on them.
By default 'Autodestruct' is true.
Autodestruct does not destruct the trail, but the game object when the trail disappears. So if you disable anything that had a trail, and activated it through movement, when the object stops moving (say to reposition in an object pool) it will destroy the parent object.
If the object never moves, it never generates a trail, and never needs to be destroyed.
To fix, just uncheck autodestruct.
https://docs.unity3d.com/ScriptReference/TrailRenderer-autodestruct.html

Scriptable Object as a custom version of sprite

I have made a scriptable object which has a one property a public sprite. I want to have one version of sprite added everywhere I want.
With the possibility that if I ever want to change it I will just pin another sprite to scriptable object and in every place where it is pinned the sprite will change.
Is there any way to walk thought this problem or any other idea to make one object where I will store one Sprite and pin it to multiple objects.
Finally is this concept possible in Unity?
I have already thought about:
Deriving prom Sprite Renderer but the class is sealed, so i cannot make my own version of it this way
Creating my own custom version of Sprite Render cause if an any update come I would have to make yet again another version.
I think you can use prefabs to achieve the same thing as you need:
You can create prefab by dragging and object to your assets.(Your prefab will be just an object with transform and e.g. Sprite Renderer)
After creating prefab you can copy it as many times as you want.
Than when you decide to change the sprite just simply go to your prefab and edit sprite there. Now every instance of your prefab will have sprite changed.
It is possible with C# events. First, make your ScriptableObject call event whenever sprite is set. Allow Sprite to be set only using property (or method), so that you could track the change, like this:
public sealed class SpriteObject : ScriptableObject
{
public event Action SpriteChanged;
[SerializeField]
private Sprite _sprite;
public Sprite Sprite
{
get { return _sprite; }
set
{
if(_sprite == value)
return;
_sprite = value;
SpriteChanged?.Invoke();
}
}
}
Then, you need script, that will react to changed and assign the changed sprite to SpriteRenderer. So, something like this:
NOTE: name is purely for example. Do NOT name your classes like this!
[RequireComponent(typeof(SpriteRenderer))]
public class ScriptThatUsedSpriteObject : MonoBehaviour
{
public SpriteObject spriteObject;
private SpriteRenderer spriteRenderer;
/// Called once the script is created.
private void Awake()
{
spriteRenderer = GetComponent<SpriteRenderer>();
if(spriteObject != null)
{
spriteRenderer.sprite = spriteObject.Sprite;
spriteObject.SpriteChanged += HandleSpriteChanged;
}
}
/// Called whenever sprite is changed inside SpriteObject.
private void HandleSpriteChanged()
{
spriteRenderer.sprite = spriteObject.Sprite;
}
}
Now, if some script changes sprite in SpriteObject, every ScriptThatUsedSpriteObject will automatically update their sprites.
public class PublicSprite : MonoBehaviour {
public Sprite publicSprite;
#region Singelton
public static PublicSprite instance;
private void Awake()
{
if (instance != null)
{
Debug.LogWarning("More than one instance found");
return;
}
instance = this;
}
}
get the sprite like this:
PublicSprite.instance.publicSprite;
as Mentioned here, you probably want to use a Singelton. It is best to avoid the use of them only if its necessary as its a very bad programming practice. (you can learn more on why to avoid them here: What is so bad about singletons?
However, I can't think of another way of doing what you seek for.

[Unity][Javascript] Why are these simple js scripts not working in Unity?

I've been sort of teaching myself and sort of learning from Jimmy Vegas on youtube: https://www.youtube.com/channel/UCRMXHQ2rJ9_0CHS7mhL7erg
If you haven't seen those tutorials or don't want to look, one of the things he does is create a small script that destroys a coin when the player collider hits it, but mine isn't working. Code below (a little mis-formatted, sorry, couldn't get it to format correctly):
function OnCollisionEnter (collision : Collision) {
if(collision.gameObject.tag == "coinCollect") {
Destroy(this.gameObject);
}
}
I applied the script to a prefab and placed a bunch of coins around a little area, additionally, I made a capsule collider in a first person controller tagged "coinCollect", and ticked "Is Trigger"
Also, I'm trying to make a teleporter that teleports the first person character from one teleporter to another. Code below:
var warptarget001 : GameObject;
var warptarget002 : GameObject;
function OnTriggerEnter (col : Collider) {
if (col.gameObject.tag == "warp001") {
this.transform.position = warptarget002.position;
} else if (col.gameObject.tag == "warp002") {
this.transform.position = warptarget001.position;
}
}
I have four objects here, two warp pads and two warp targets. The two warp pads are tagged "warp001" and "warp002", respectively and the two warp targets are not assigned anything in the code, but assigned by dragging and dropping an empty object into the Serialized Field the script provides. Both pads have capsule colliders with "Is Trigger" unticked but it doesn't work either way, ticked or unticked.
Can anyone tell me what I might be doing wrong? Thank you.
The script was all correct, my problem was that my parent "FPSController" object didn't have a Rigidbody applied to it and should be the only object (as opposed to the "FirstPersonCharacter" object I had nested inside of it) that the scripts are applied to. That seemed to fix the problem.
The correct code is:
/* coincollect.cs */
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class coincollect : MonoBehaviour {
private int _score;
[SerializeField]
private Text _text;
void OnTriggerEnter ( Collider collision ){
if(collision.gameObject.tag == "coin"){
Destroy(collision.gameObject);
_score++;
_text.text = "Score: " + _score;
}
}
}
and:
/* warp.js */
var warptarget001 : GameObject;
var warptarget002 : GameObject;
function OnTriggerEnter (col : Collider) {
if (col.gameObject.tag == "warp001") {
this.transform.position = warptarget002.transform.position;
}
if (col.gameObject.tag == "warp002") {
this.transform.position = warptarget001.transform.position;
}
}
The only thing I can think of for your first problem is that it shouldn't need IsTrigger ticked. Other than that, it sounds like it should work (unless I'm missing something).
For the second problem you're having (with the warps), I don't think you can use warptarget001 by dragging and dropping objects into the fields. The reason being that what you've dragged into that field isn't the same object instance that's inworld.
You should assign their values through the code (preferably in the Start method), by using GameObject.Find("name") for example. This way, warptarget001 corresponds to the inworld gameobject.